Bug 1456035: Part 1 - Add helper to generate native QueryInterface callbacks. r?bz
MozReview-Commit-ID: JpV6zYOdvHu
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -18,16 +18,17 @@ namespace devtools {
class HeapSnapshot;
} // namespace devtools
namespace dom {
class ArrayBufferViewOrArrayBuffer;
class IdleRequestCallback;
struct IdleRequestOptions;
+class MozQueryInterface;
class PrecompiledScript;
class Promise;
class ChromeUtils
{
private:
// Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
static void SaveHeapSnapshotShared(GlobalObject& global,
@@ -118,16 +119,20 @@ public:
// Implemented in js/xpconnect/loader/ChromeScriptLoader.cpp
static already_AddRefed<Promise>
CompileScript(GlobalObject& aGlobal,
const nsAString& aUrl,
const dom::CompileScriptOptionsDictionary& aOptions,
ErrorResult& aRv);
+ static MozQueryInterface*
+ GenerateQI(const GlobalObject& global, const Sequence<OwningStringOrIID>& interfaces,
+ ErrorResult& aRv);
+
static void WaiveXrays(GlobalObject& aGlobal,
JS::HandleValue aVal,
JS::MutableHandleValue aRetval,
ErrorResult& aRv);
static void UnwaiveXrays(GlobalObject& aGlobal,
JS::HandleValue aVal,
JS::MutableHandleValue aRetval,
new file mode 100644
--- /dev/null
+++ b/dom/base/MozQueryInterface.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromeUtils.h"
+#include "MozQueryInterface.h"
+
+#include <string.h>
+
+#include "jsapi.h"
+
+#include "xpcpublic.h"
+#include "xpcjsid.h"
+
+namespace mozilla {
+namespace dom {
+
+constexpr size_t IID_SIZE = 16;
+
+static int
+CompareIIDs(const nsIID& aA, const nsIID &aB)
+{
+ return memcmp((void*)&aA.m0, (void*)&aB.m0, IID_SIZE);
+}
+
+struct IIDComparator
+{
+ bool
+ LessThan(const nsIID& aA, const nsIID &aB) const
+ {
+ return CompareIIDs(aA, aB) < 0;
+ }
+
+ bool
+ Equals(const nsIID& aA, const nsIID &aB) const
+ {
+ return aA.Equals(aB);
+ }
+};
+
+/* static */
+MozQueryInterface*
+ChromeUtils::GenerateQI(const GlobalObject& aGlobal, const Sequence<OwningStringOrIID>& aInterfaces, ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ JS::RootedObject xpcIfaces(cx);
+
+ nsTArray<nsIID> ifaces;
+
+ JS::RootedValue val(cx);
+ for (auto& iface : aInterfaces) {
+ if (iface.IsIID()) {
+ ifaces.AppendElement(*iface.GetAsIID()->GetID());
+ continue;
+ }
+
+ // If we have a string value, we need to look up the interface name. The
+ // simplest and most efficient way to do this is to just grab the "Ci"
+ // object from the global scope.
+ if (!xpcIfaces) {
+ JS::RootedObject global(cx, aGlobal.Get());
+ if (!JS_GetProperty(cx, global, "Ci", &val)) {
+ aRv.NoteJSContextException(cx);
+ return nullptr;
+ }
+ if (!val.isObject()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ xpcIfaces = &val.toObject();
+ }
+
+ auto& name = iface.GetAsString();
+ if (!JS_GetUCProperty(cx, xpcIfaces, name.get(), name.Length(), &val)) {
+ aRv.NoteJSContextException(cx);
+ return nullptr;
+ }
+
+ if (val.isNullOrUndefined()) {
+ continue;
+ }
+ if (!val.isObject() || !xpc::IsReflector(&val.toObject())) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(&val.toObject());
+ nsCOMPtr<nsIJSID> iid = do_QueryInterface(base);
+ if (!iid) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return nullptr;
+ }
+ ifaces.AppendElement(*iid->GetID());
+ }
+
+ MOZ_ASSERT(!ifaces.Contains(NS_GET_IID(nsISupports), IIDComparator()));
+ ifaces.AppendElement(NS_GET_IID(nsISupports));
+
+ ifaces.Sort(IIDComparator());
+
+ return new MozQueryInterface(Move(ifaces));
+}
+
+bool
+MozQueryInterface::QueriesTo(const nsIID& aIID) const
+{
+ // We use BinarySearchIf here because nsTArray::ContainsSorted requires
+ // twice as many comparisons.
+ size_t result;
+ return BinarySearchIf(mInterfaces, 0, mInterfaces.Length(),
+ [&] (const nsIID& aOther) { return CompareIIDs(aIID, aOther); },
+ &result);
+}
+
+void
+MozQueryInterface::LegacyCall(JSContext* cx, JS::Handle<JS::Value> thisv,
+ nsIJSID* aIID,
+ JS::MutableHandle<JSObject*> aResult,
+ ErrorResult& aRv) const
+{
+ if (!QueriesTo(*aIID->GetID())) {
+ aRv.Throw(NS_ERROR_NO_INTERFACE);
+ } else if (!thisv.isObject()) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ } else {
+ aResult.set(&thisv.toObject());
+ }
+}
+
+bool
+MozQueryInterface::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector)
+{
+ return MozQueryInterfaceBinding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+} // namespace dom
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/dom/base/MozQueryInterface.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_MozQueryInterface
+#define mozilla_dom_MozQueryInterface
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+namespace dom {
+
+class MozQueryInterface final : public NonRefcountedDOMObject
+{
+public:
+ MozQueryInterface(nsTArray<nsIID>&& aInterfaces)
+ : mInterfaces(Move(aInterfaces))
+ {}
+
+ bool QueriesTo(const nsIID& aIID) const;
+
+ void LegacyCall(JSContext* cx, JS::Handle<JS::Value> thisv, nsIJSID* aIID, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv) const;
+
+ nsISupports* GetParentObject() const { return nullptr; }
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector);
+
+private:
+ nsTArray<nsIID> mInterfaces;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_MozQueryInterface
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -190,16 +190,17 @@ EXPORTS.mozilla.dom += [
'ImageEncoder.h',
'ImageTracker.h',
'IntlUtils.h',
'Link.h',
'Location.h',
'MessageListenerManager.h',
'MessageManagerGlobal.h',
'MessageSender.h',
+ 'MozQueryInterface.h',
'NameSpaceConstants.h',
'Navigator.h',
'NodeInfo.h',
'NodeInfoInlines.h',
'NodeIterator.h',
'Pose.h',
'ProcessGlobal.h',
'ResponsiveImageSelector.h',
@@ -270,16 +271,17 @@ UNIFIED_SOURCES += [
'ImageEncoder.cpp',
'ImageTracker.cpp',
'IntlUtils.cpp',
'Link.cpp',
'Location.cpp',
'MessageListenerManager.cpp',
'MessageManagerGlobal.cpp',
'MessageSender.cpp',
+ 'MozQueryInterface.cpp',
'Navigator.cpp',
'NodeInfo.cpp',
'NodeIterator.cpp',
'nsAttrAndChildArray.cpp',
'nsAttrValue.cpp',
'nsAttrValueOrString.cpp',
'nsCCUncollectableMarker.cpp',
'nsContentAreaDragDrop.cpp',
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -607,16 +607,20 @@ DOMInterfaces = {
'nativeType': 'mozilla::storage::StatementParams',
},
'MozStorageStatementRow': {
'headerFile': 'mozilla/storage/mozStorageStatementRow.h',
'nativeType': 'mozilla::storage::StatementRow',
},
+'MozQueryInterface': {
+ 'wrapperCache': False,
+},
+
'MutationObserver': {
'nativeType': 'nsDOMMutationObserver',
},
'MutationRecord': {
'nativeType': 'nsDOMMutationRecord',
'headerFile': 'nsDOMMutationObserver.h',
},
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -1,14 +1,20 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+[ChromeOnly, Exposed=(Window,System,Worker)]
+interface MozQueryInterface {
+ [Throws]
+ legacycaller object (IID aIID);
+};
+
/**
* A collection of static utility methods that are only exposed to system code.
* This is exposed in all the system globals where we can expose stuff by
* default, so should only include methods that are **thread-safe**.
*/
[ChromeOnly, Exposed=(Window,System,Worker)]
namespace ChromeUtils {
/**
@@ -194,16 +200,24 @@ partial namespace ChromeUtils {
* which may be used to execute it repeatedly, in different globals, without
* re-parsing.
*/
[NewObject]
Promise<PrecompiledScript>
compileScript(DOMString url, optional CompileScriptOptionsDictionary options);
/**
+ * Returns an optimized QueryInterface method which, when called from
+ * JavaScript, acts as an ordinary QueryInterface function call, and when
+ * called from XPConnect, circumvents JSAPI entirely.
+ */
+ [Affects=Nothing, NewObject, Throws]
+ MozQueryInterface generateQI(sequence<(DOMString or IID)> interfaces);
+
+ /**
* Waive Xray on a given value. Identity op for primitives.
*/
[Throws]
any waiveXrays(any val);
/**
* Strip off Xray waivers on a given value. Identity op for primitives.
*/
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_generateQI.js
@@ -0,0 +1,32 @@
+"use strict";
+
+add_task(async function test_generateQI() {
+ function checkQI(interfaces, iface) {
+ return ({
+ QueryInterface: ChromeUtils.generateQI(interfaces),
+ }).QueryInterface(iface);
+ }
+
+ // Test success scenarios.
+ checkQI([], Ci.nsISupports);
+
+ checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2"], Ci.nsIPropertyBag);
+ checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2"], Ci.nsIPropertyBag2);
+
+ checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2", "nsINotARealInterface"], Ci.nsIPropertyBag2);
+
+ // Non-IID values get stringified, and don't cause any errors as long
+ // as there isn't a non-IID property with the same name on Ci.
+ checkQI([Ci.nsIPropertyBag, "nsIPropertyBag2", null, Object], Ci.nsIPropertyBag2);
+
+ // Test failure scenarios.
+ Assert.throws(() => ChromeUtils.generateQI(["toString"]),
+ e => e.result == Cr.NS_ERROR_INVALID_ARG);
+
+ // No `this` object.
+ Assert.throws(() => ChromeUtils.generateQI([])(Ci.nsISupports),
+ e => e.result == Cr.NS_ERROR_UNEXPECTED);
+
+ Assert.throws(() => checkQI([], Ci.nsIPropertyBag),
+ e => e.result == Cr.NS_ERROR_NO_INTERFACE);
+});
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -68,16 +68,17 @@ support-files =
[test_compileScript.js]
[test_deepFreezeClone.js]
[test_defineModuleGetter.js]
[test_file.js]
[test_blob.js]
[test_blob2.js]
[test_file2.js]
[test_getCallerLocation.js]
+[test_generateQI.js]
[test_import.js]
[test_import_fail.js]
[test_isModuleLoaded.js]
[test_js_weak_references.js]
[test_onGarbageCollection-01.js]
head = head_ongc.js
[test_onGarbageCollection-02.js]
head = head_ongc.js