Bug 1134006 - Avoid IPC for domElement.QueryInterface(nsISupports) and nsIClassInfo. r=billm
authorBlake Kaplan <mrbkap@gmail.com>
Tue, 10 Mar 2015 14:36:01 -0700
changeset 232905 69c682891670e5f6dee1ced41ad68455c2841b94
parent 232904 bca6e36655a49a08db87df6f67f210ef8baab3da
child 232906 6ee3e7424d3ab853916d4ffe202dc87426152d70
push id28396
push usercbook@mozilla.com
push dateWed, 11 Mar 2015 11:49:38 +0000
treeherdermozilla-central@b574129dcac0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1134006
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1134006 - Avoid IPC for domElement.QueryInterface(nsISupports) and nsIClassInfo. r=billm
dom/base/test/chrome/cpows_parent.xul
js/ipc/JavaScriptTypes.ipdlh
js/ipc/WrapperOwner.cpp
js/ipc/WrapperOwner.h
--- a/dom/base/test/chrome/cpows_parent.xul
+++ b/dom/base/test/chrome/cpows_parent.xul
@@ -201,23 +201,41 @@
     function recvErrorReportingTest(message) {
       throw "Test Error Probe";
     }
 
     let savedElement = null;
     function recvDomTest(message) {
       savedElement = message.objects.element;
 
+      is(savedElement.QueryInterface(Components.interfaces.nsISupports), savedElement,
+         "QI to nsISupports works");
+      is(savedElement.QueryInterface(Components.interfaces.nsIDOMNode), savedElement,
+         "QI to a random (implemented) interface works");
+
+      function testNoInterface(savedElement, i) {
+        try {
+          savedElement.QueryInterface(i);
+          ok(false, "should have thrown an exception");
+        } catch (e) {
+          is(e.result, Components.results.NS_ERROR_NO_INTERFACE, "threw the right exception");
+        }
+      }
+
+      testNoInterface(savedElement, Components.interfaces.nsIDOMAttr);
+      testNoInterface(savedElement, Components.interfaces.nsIClassInfo);
+
       // Test to ensure that we don't pass CPOWs to C++-implemented interfaces.
       // See bug 1072980.
       if (test_state == "remote") {
-        // This doesn't work because we intercept toString specially
+        // This doesn't work because we intercept toString and QueryInterface specially
         // and don't cache the function pointer.
         // See bug 1140636.
         todo_is(savedElement.toString, savedElement.toString, "toString identity works");
+        todo_is(savedElement.QueryInterface, savedElement.QueryInterface, "toString identity works");
 
         // This does work because we create a CPOW for isEqualNode that stays
         // alive as long as we have a reference to the first CPOW (so as long
         // as it's detectable).
         is(savedElement.isEqualNode, savedElement.isEqualNode, "webidl function identity works");
 
         let walker = Components.classes["@mozilla.org/inspector/deep-tree-walker;1"]
                                .createInstance(Components.interfaces.inIDeepTreeWalker);
--- a/js/ipc/JavaScriptTypes.ipdlh
+++ b/js/ipc/JavaScriptTypes.ipdlh
@@ -32,16 +32,17 @@ struct LocalObject
     uint64_t serializedId;
 };
 
 struct RemoteObject
 {
     uint64_t serializedId;
     bool isCallable;
     bool isConstructor;
+    bool isDOMObject;
     nsCString objectTag;
 };
 
 union ObjectVariant
 {
     LocalObject;
     RemoteObject;
 };
--- a/js/ipc/WrapperOwner.cpp
+++ b/js/ipc/WrapperOwner.cpp
@@ -5,41 +5,48 @@
  * 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 "WrapperOwner.h"
 #include "JavaScriptLogging.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "jsfriendapi.h"
+#include "js/CharacterEncoding.h"
 #include "xpcprivate.h"
 #include "CPOWTimer.h"
 #include "WrapperFactory.h"
 
 #include "nsIRemoteTagService.h"
 
 using namespace js;
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 
 struct AuxCPOWData
 {
     ObjectId id;
     bool isCallable;
     bool isConstructor;
+    bool isDOMObject;
 
     // The object tag is just some auxilliary information that clients can use
     // however they see fit.
     nsCString objectTag;
 
-    AuxCPOWData(ObjectId id, bool isCallable, bool isConstructor, const nsACString &objectTag)
+    AuxCPOWData(ObjectId id,
+                bool isCallable,
+                bool isConstructor,
+                bool isDOMObject,
+                const nsACString &objectTag)
       : id(id),
         isCallable(isCallable),
         isConstructor(isConstructor),
+        isDOMObject(isDOMObject),
         objectTag(objectTag)
     {}
 };
 
 WrapperOwner::WrapperOwner(JSRuntime *rt)
   : JavaScriptShared(rt),
     inactive_(false)
 {
@@ -148,17 +155,17 @@ bool
 CPOWProxyHandler::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                         MutableHandle<JSPropertyDescriptor> desc) const
 {
     FORWARD(getPropertyDescriptor, (cx, proxy, id, desc));
 }
 
 bool
 WrapperOwner::getPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
-				    MutableHandle<JSPropertyDescriptor> desc)
+                                    MutableHandle<JSPropertyDescriptor> desc)
 {
     ObjectId objId = idOf(proxy);
 
     JSIDVariant idVar;
     if (!toJSIDVariant(cx, id, &idVar))
         return false;
 
     ReturnStatus status;
@@ -178,17 +185,17 @@ bool
 CPOWProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
                                            MutableHandle<JSPropertyDescriptor> desc) const
 {
     FORWARD(getOwnPropertyDescriptor, (cx, proxy, id, desc));
 }
 
 bool
 WrapperOwner::getOwnPropertyDescriptor(JSContext *cx, HandleObject proxy, HandleId id,
-				       MutableHandle<JSPropertyDescriptor> desc)
+                                       MutableHandle<JSPropertyDescriptor> desc)
 {
     ObjectId objId = idOf(proxy);
 
     JSIDVariant idVar;
     if (!toJSIDVariant(cx, id, &idVar))
         return false;
 
     ReturnStatus status;
@@ -209,17 +216,17 @@ CPOWProxyHandler::defineProperty(JSConte
                                  MutableHandle<JSPropertyDescriptor> desc,
                                  ObjectOpResult &result) const
 {
     FORWARD(defineProperty, (cx, proxy, id, desc, result));
 }
 
 bool
 WrapperOwner::defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
-			     MutableHandle<JSPropertyDescriptor> desc,
+                             MutableHandle<JSPropertyDescriptor> desc,
                              ObjectOpResult &result)
 {
     ObjectId objId = idOf(proxy);
 
     JSIDVariant idVar;
     if (!toJSIDVariant(cx, id, &idVar))
         return false;
 
@@ -334,16 +341,29 @@ WrapperOwner::hasOwn(JSContext *cx, Hand
 bool
 CPOWProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
                       HandleId id, MutableHandleValue vp) const
 {
     FORWARD(get, (cx, proxy, receiver, id, vp));
 }
 
 static bool
+CPOWDOMQI(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (!args.thisv().isObject() || !IsCPOW(&args.thisv().toObject())) {
+        JS_ReportError(cx, "bad this object passed to special QI");
+        return false;
+    }
+
+    RootedObject proxy(cx, &args.thisv().toObject());
+    FORWARD(DOMQI, (cx, proxy, args));
+}
+
+static bool
 CPOWToString(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject callee(cx, &args.callee());
     RootedValue cpowValue(cx);
     if (!JS_GetProperty(cx, callee, "__cpow__", &cpowValue))
         return false;
 
@@ -388,29 +408,88 @@ WrapperOwner::toString(JSContext *cx, Ha
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
 bool
+WrapperOwner::DOMQI(JSContext *cx, JS::HandleObject proxy, JS::CallArgs &args)
+{
+    // Someone's calling us, handle nsISupports specially to avoid unnecessary
+    // CPOW traffic.
+    HandleValue id = args[0];
+    if (id.isObject()) {
+        RootedObject idobj(cx, &id.toObject());
+        nsCOMPtr<nsIJSID> jsid;
+
+        nsresult rv = UnwrapArg<nsIJSID>(idobj, getter_AddRefs(jsid));
+        if (NS_SUCCEEDED(rv)) {
+            MOZ_ASSERT(jsid, "bad wrapJS");
+            const nsID *idptr = jsid->GetID();
+            if (idptr->Equals(NS_GET_IID(nsISupports))) {
+                args.rval().set(args.thisv());
+                return true;
+            }
+
+            // Webidl-implemented DOM objects never have nsIClassInfo.
+            if (idptr->Equals(NS_GET_IID(nsIClassInfo)))
+                return Throw(cx, NS_ERROR_NO_INTERFACE);
+        }
+    }
+
+    // It wasn't nsISupports, call into the other process to do the QI for us
+    // (since we don't know what other interfaces our object supports). Note
+    // that we have to use JS_GetPropertyDescriptor here to avoid infinite
+    // recursion back into CPOWDOMQI via WrapperOwner::get().
+    // We could stash the actual QI function on our own function object to avoid
+    // if we're called multiple times, but since we're transient, there's no
+    // point right now.
+    JS::Rooted<JSPropertyDescriptor> propDesc(cx);
+    if (!JS_GetPropertyDescriptor(cx, proxy, "QueryInterface", &propDesc))
+        return false;
+
+    if (!propDesc.value().isObject()) {
+        MOZ_ASSERT_UNREACHABLE("We didn't get QueryInterface off a node");
+        return Throw(cx, NS_ERROR_UNEXPECTED);
+    }
+    return JS_CallFunctionValue(cx, proxy, propDesc.value(), args, args.rval());
+}
+
+bool
 WrapperOwner::get(JSContext *cx, HandleObject proxy, HandleObject receiver,
                   HandleId id, MutableHandleValue vp)
 {
     ObjectId objId = idOf(proxy);
 
     ObjectVariant receiverVar;
     if (!toObjectVariant(cx, receiver, &receiverVar))
         return false;
 
     JSIDVariant idVar;
     if (!toJSIDVariant(cx, id, &idVar))
         return false;
 
+    AuxCPOWData *data = AuxCPOWDataOf(proxy);
+    if (data->isDOMObject &&
+        idVar.type() == JSIDVariant::TnsString &&
+        idVar.get_nsString().EqualsLiteral("QueryInterface"))
+    {
+        // Handle QueryInterface on DOM Objects specially since we can assume
+        // certain things about their implementation.
+        RootedFunction qi(cx, JS_NewFunction(cx, CPOWDOMQI, 1, 0,
+                                             "QueryInterface"));
+        if (!qi)
+            return false;
+
+        vp.set(ObjectValue(*JS_GetFunctionObject(qi)));
+        return true;
+    }
+
     JSVariant val;
     ReturnStatus status;
     if (!SendGet(objId, receiverVar, idVar, &status, &val))
         return ipcfail(cx);
 
     LOG_STACK();
 
     if (!ok(cx, status))
@@ -962,16 +1041,17 @@ MakeRemoteObject(JSContext *cx, ObjectId
     if (service) {
         RootedValue objVal(cx, ObjectValue(*obj));
         service->GetRemoteObjectTag(objVal, objectTag);
     }
 
     return RemoteObject(id.serialize(),
                         JS::IsCallable(obj),
                         JS::IsConstructor(obj),
+                        dom::IsDOMObject(obj),
                         objectTag);
 }
 
 bool
 WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *objVarp)
 {
     RootedObject obj(cx, objArg);
     MOZ_ASSERT(obj);
@@ -1046,16 +1126,17 @@ WrapperOwner::fromRemoteObjectVariant(JS
             return nullptr;
 
         // Incref once we know the decref will be called.
         incref();
 
         AuxCPOWData *aux = new AuxCPOWData(objId,
                                            objVar.isCallable(),
                                            objVar.isConstructor(),
+                                           objVar.isDOMObject(),
                                            objVar.objectTag());
 
         SetProxyExtra(obj, 0, PrivateValue(this));
         SetProxyExtra(obj, 1, PrivateValue(aux));
     }
 
     if (!JS_WrapObject(cx, &obj))
         return nullptr;
--- a/js/ipc/WrapperOwner.h
+++ b/js/ipc/WrapperOwner.h
@@ -58,16 +58,17 @@ class WrapperOwner : public virtual Java
     const char* className(JSContext *cx, JS::HandleObject proxy);
     bool getPrototype(JSContext *cx, JS::HandleObject proxy, JS::MutableHandleObject protop);
 
     bool regexp_toShared(JSContext *cx, JS::HandleObject proxy, js::RegExpGuard *g);
 
     nsresult instanceOf(JSObject *obj, const nsID *id, bool *bp);
 
     bool toString(JSContext *cx, JS::HandleObject callee, JS::CallArgs &args);
+    bool DOMQI(JSContext *cx, JS::HandleObject callee, JS::CallArgs &args);
 
     /*
      * Check that |obj| is a DOM wrapper whose prototype chain contains
      * |prototypeID| at depth |depth|.
      */
     bool domInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp);
 
     bool active() { return !inactive_; }