Bug 865951. If the return value of a JS-implemented method or attribute is a JS-implemented interface and the returned object is not a DOM object, automatically wrap it up in an instance of that interface. r=mccr8
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 20 May 2013 13:47:08 -0400
changeset 143930 3e82140fbb6366991e926c654fc571a95dd6d935
parent 143929 da75ba983321fc58e186db5e4053c4619a5bfaf1
child 143931 905e081060c5f8bfee9e9633946bc7ad24cf81d4
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs865951
milestone24.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 865951. If the return value of a JS-implemented method or attribute is a JS-implemented interface and the returned object is not a DOM object, automatically wrap it up in an instance of that interface. r=mccr8 This does not invoke __init on the chrome object, since there are no arguments to do it with anyway. It also doesn't invoke init(), so don't do this in cases in which the chrome-side object will need to know its window.
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1765,10 +1765,45 @@ Date::ToDateObject(JSContext* cx, JS::Va
   JSObject* obj = JS_NewDateObjectMsec(cx, mMsecSinceEpoch);
   if (!obj) {
     return false;
   }
   *vp = JS::ObjectValue(*obj);
   return true;
 }
 
+bool
+GetWindowForJSImplementedObject(JSContext* cx, JS::Handle<JSObject*> obj,
+                                nsPIDOMWindow** window)
+{
+  // Be very careful to not get tricked here.
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!xpc::AccessCheck::isChrome(js::GetObjectCompartment(obj))) {
+    NS_RUNTIMEABORT("Should have a chrome object here");
+  }
+
+  // Look up the content-side object.
+  JS::Rooted<JS::Value> domImplVal(cx);
+  if (!JS_GetProperty(cx, obj, "__DOM_IMPL__", domImplVal.address())) {
+    return false;
+  }
+
+  if (!domImplVal.isObject()) {
+    ThrowErrorMessage(cx, MSG_NOT_OBJECT);
+    return false;
+  }
+
+  // Go ahead and get the global from it.  GlobalObject will handle
+  // doing unwrapping as needed.
+  GlobalObject global(cx, &domImplVal.toObject());
+  if (global.Failed()) {
+    return false;
+  }
+
+  // It's OK if we have null here: that just means the content-side
+  // object really wasn't associated with any window.
+  nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(global.Get()));
+  win.forget(window);
+  return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -20,16 +20,18 @@
 #include "qsObjectHelper.h"
 #include "xpcpublic.h"
 #include "nsTraceRefcnt.h"
 #include "nsWrapperCacheInlines.h"
 #include "mozilla/Likely.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/CallbackObject.h"
 
+class nsPIDOMWindow;
+
 namespace mozilla {
 namespace dom {
 
 bool
 ThrowErrorMessage(JSContext* aCx, const ErrNum aErrorNumber, ...);
 
 template<bool mainThread>
 inline bool
@@ -1986,12 +1988,19 @@ inline JSObject*
 GetUnforgeableHolder(JSObject* aGlobal, prototypes::ID aId)
 {
   JSObject** protoAndIfaceArray = GetProtoAndIfaceArray(aGlobal);
   JSObject* interfaceProto = protoAndIfaceArray[aId];
   return &js::GetReservedSlot(interfaceProto,
                               DOM_INTERFACE_PROTO_SLOTS_BASE).toObject();
 }
 
+// Given a JSObject* that represents the chrome side of a JS-implemented WebIDL
+// interface, get the nsPIDOMWindow corresponding to the content side, if any.
+// A false return means an exception was thrown.
+bool
+GetWindowForJSImplementedObject(JSContext* cx, JS::Handle<JSObject*> obj,
+                                nsPIDOMWindow** window);
+
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_BindingUtils_h__ */
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2282,23 +2282,31 @@ def numericValue(t, v):
 
 class CastableObjectUnwrapper():
     """
     A class for unwrapping an object named by the "source" argument
     based on the passed-in descriptor and storing it in a variable
     called by the name in the "target" argument.
 
     codeOnFailure is the code to run if unwrapping fails.
-    """
-    def __init__(self, descriptor, source, target, codeOnFailure):
+
+    If isCallbackReturnValue is "JSImpl" and our descriptor is also
+    JS-implemented, fall back to just creating the right object if what we
+    have isn't one already.
+    """
+    def __init__(self, descriptor, source, target, codeOnFailure,
+                 exceptionCode=None, isCallbackReturnValue=False):
+        if not exceptionCode:
+            exceptionCode = codeOnFailure
         self.substitution = { "type" : descriptor.nativeType,
                               "protoID" : "prototypes::id::" + descriptor.name,
                               "source" : source,
                               "target" : target,
-                              "codeOnFailure" : CGIndenter(CGGeneric(codeOnFailure), 4).define() }
+                              "codeOnFailure" : CGIndenter(CGGeneric(codeOnFailure)).define(),
+                              "exceptionCode" : CGIndenter(CGGeneric(exceptionCode), 4).define() }
         if descriptor.hasXPConnectImpls:
             # We don't use xpc_qsUnwrapThis because it will always throw on
             # unwrap failure, whereas we want to control whether we throw or
             # not.
             self.substitution["codeOnFailure"] = CGIndenter(CGGeneric(string.Template(
                 "${type} *objPtr;\n"
                 "xpc_qsSelfRef objRef;\n"
                 "JS::Rooted<JS::Value> val(cx, JS::ObjectValue(*${source}));\n"
@@ -2306,35 +2314,58 @@ class CastableObjectUnwrapper():
                 "if (NS_FAILED(rv)) {\n"
                 "${codeOnFailure}\n"
                 "}\n"
                 "// We should be castable!\n"
                 "MOZ_ASSERT(!objRef.ptr);\n"
                 "// We should have an object, too!\n"
                 "MOZ_ASSERT(objPtr);\n"
                 "${target} = objPtr;").substitute(self.substitution)), 4).define()
+        elif (isCallbackReturnValue == "JSImpl" and
+              descriptor.interface.isJSImplemented()):
+            self.substitution["codeOnFailure"] = CGIndenter(CGGeneric(string.Template(
+                "// Be careful to not wrap random DOM objects here, even if\n"
+                "// they're wrapped in opaque security wrappers for some reason.\n"
+                "// XXXbz Wish we could check for a JS-implemented object\n"
+                "// that already has a content reflection...\n"
+                "if (!IsDOMObject(js::UncheckedUnwrap(${source}))) {\n"
+                "  nsCOMPtr<nsPIDOMWindow> ourWindow;\n"
+                "  if (!GetWindowForJSImplementedObject(cx, Callback(), getter_AddRefs(ourWindow))) {\n"
+                "${exceptionCode}\n"
+                "  }\n"
+                "  JS::Rooted<JSObject*> jsImplSourceObj(cx, ${source});\n"
+                "  ${target} = new ${type}(jsImplSourceObj, ourWindow);\n"
+                "} else {\n"
+                "${codeOnFailure}\n"
+                "}").substitute(self.substitution)), 4).define()
+        else:
+            self.substitution["codeOnFailure"] = CGIndenter(
+                CGGeneric(self.substitution["codeOnFailure"])).define()
 
     def __str__(self):
         return string.Template(
 """{
   nsresult rv = UnwrapObject<${protoID}, ${type}>(cx, ${source}, ${target});
   if (NS_FAILED(rv)) {
 ${codeOnFailure}
   }
 }""").substitute(self.substitution)
 
 class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper):
     """
     As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails
     """
-    def __init__(self, descriptor, source, target, exceptionCode):
+    def __init__(self, descriptor, source, target, exceptionCode,
+                 isCallbackReturnValue):
         CastableObjectUnwrapper.__init__(
             self, descriptor, source, target,
             'ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s");\n'
-            '%s' % (descriptor.name, exceptionCode))
+            '%s' % (descriptor.name, exceptionCode),
+            exceptionCode,
+            isCallbackReturnValue)
 
 class CallbackObjectUnwrapper:
     """
     A class for unwrapping objects implemented in JS.
 
     |source| is the JSObject we want to use in native code.
     |target| is an nsCOMPtr of the appropriate type in which we store the result.
     """
@@ -2514,19 +2545,23 @@ def getJSToNativeConversionInfo(type, de
     value is out of range.
 
     If lenientFloatCode is not None, it should be used in cases when
     we're a non-finite float that's not unrestricted.
 
     If allowTreatNonCallableAsNull is true, then [TreatNonCallableAsNull]
     extended attributes on nullable callback functions will be honored.
 
-    If isCallbackReturnValue is true, then the declType may be adjusted to make
-    it easier to return from a callback.  Since that type is never directly
-    observable by any consumers of the callback code, this is OK.
+    If isCallbackReturnValue is "JSImpl" or "Callback", then the declType may be
+    adjusted to make it easier to return from a callback.  Since that type is
+    never directly observable by any consumers of the callback code, this is OK.
+    Furthermore, if isCallbackReturnValue is "JSImpl", that affects the behavior
+    of the FailureFatalCastableObjectUnwrapper conversion; this is used for
+    implementing auto-wrapping of JS-implemented return values from a
+    JS-implemented interface.
 
     The return value from this function is a JSToNativeConversionInfo.
     """
     # If we have a defaultValue then we're not actually optional for
     # purposes of what we need to be declared as.
     assert(defaultValue is None or not isOptional)
 
     # Also, we should not have a defaultValue if we know we're an object
@@ -2993,19 +3028,24 @@ for (uint32_t i = 0; i < length; ++i) {
 
         # Allow null pointers for nullable types and old-binding classes, and
         # use an nsRefPtr or raw pointer for callback return values to make
         # them easier to return.
         argIsPointer = (type.nullable() or type.unroll().inner.isExternal() or
                         isCallbackReturnValue)
 
         # Sequences and non-worker callbacks have to hold a strong ref to the
-        # thing being passed down.
-        forceOwningType = (descriptor.interface.isCallback() and
-                           not descriptor.workers) or isMember
+        # thing being passed down.  Also, callback return values always end up
+        # addrefing anyway, so there is no point trying to avoid it here and it
+        # makes other things simpler since we can assume the return value is a
+        # strong ref.
+        forceOwningType = ((descriptor.interface.isCallback() and
+                            not descriptor.workers) or
+                           isMember or
+                           isCallbackReturnValue)
 
         typeName = descriptor.nativeType
         typePtr = typeName + "*"
 
         # Compute a few things:
         #  - declType is the type we want to return as the first element of our
         #    tuple.
         #  - holderType is the type we want to return as the third element
@@ -3045,17 +3085,18 @@ for (uint32_t i = 0; i < length; ++i) {
                         "&${val}.toObject()",
                         "${declName}",
                         failureCode))
             else:
                 templateBody += str(FailureFatalCastableObjectUnwrapper(
                         descriptor,
                         "&${val}.toObject()",
                         "${declName}",
-                        exceptionCode))
+                        exceptionCode,
+                        isCallbackReturnValue))
         elif descriptor.workers:
             return handleJSObjectType(type, isMember, failureCode)
         else:
             # Either external, or new-binding non-castable.  We always have a
             # holder for these, because we don't actually know whether we have
             # to addref when unwrapping or not.  So we just pass an
             # getter_AddRefs(nsRefPtr) to XPConnect and if we'll need a release
             # it'll put a non-null pointer in there.
@@ -8270,31 +8311,31 @@ class CGBindingRoot(CGThing):
         return stripTrailingWhitespace(self.root.declare())
     def define(self):
         return stripTrailingWhitespace(self.root.define())
 
     def deps(self):
         return self.root.deps()
 
 class CGNativeMember(ClassMethod):
-    def __init__(self, descriptor, member, name, signature, extendedAttrs,
+    def __init__(self, descriptorProvider, member, name, signature, extendedAttrs,
                  breakAfter=True, passCxAsNeeded=True, visibility="public",
                  jsObjectsArePtr=False, variadicIsSequence=False):
         """
         If jsObjectsArePtr is true, typed arrays and "object" will be
         passed as JSObject*.
 
         If passCxAsNeeded is false, we don't automatically pass in a
         JSContext* based on the return and argument types.  We can
         still pass it based on 'implicitJSContext' annotations.
         """
-        self.descriptor = descriptor
+        self.descriptorProvider = descriptorProvider
         self.member = member
         self.extendedAttrs = extendedAttrs
-        self.resultAlreadyAddRefed = isResultAlreadyAddRefed(self.descriptor,
+        self.resultAlreadyAddRefed = isResultAlreadyAddRefed(self.descriptorProvider,
                                                              self.extendedAttrs)
         self.passCxAsNeeded = passCxAsNeeded
         self.jsObjectsArePtr = jsObjectsArePtr
         self.variadicIsSequence = variadicIsSequence
         breakAfterSelf = "\n" if breakAfter else ""
         ClassMethod.__init__(self, name,
                              self.getReturnType(signature[0], False),
                              self.getArgs(signature[0], signature[1]),
@@ -8349,17 +8390,17 @@ class CGNativeMember(ClassMethod):
                 enumName = CGTemplatedType("Nullable",
                                            CGGeneric(enumName)).define()
                 defaultValue = "%s()" % enumName
             else:
                 defaultValue = "%s(0)" % enumName
             return enumName, defaultValue, "return ${declName};"
         if type.isGeckoInterface():
             iface = type.unroll().inner;
-            nativeType = self.descriptor.getDescriptor(
+            nativeType = self.descriptorProvider.getDescriptor(
                 iface.identifier.name).nativeType
             # Now trim off unnecessary namespaces
             nativeType = nativeType.split("::")
             if nativeType[0] == "mozilla":
                 nativeType.pop(0)
                 if nativeType[0] == "dom":
                     nativeType.pop(0)
             result = CGGeneric("::".join(nativeType))
@@ -8372,35 +8413,20 @@ class CGNativeMember(ClassMethod):
                     warning = ""
                 else:
                     warning = "// Mark this as resultNotAddRefed to return raw pointers\n"
                 result = CGWrapper(result,
                                    pre=("%s%s<" % (warning, holder)),
                                    post=">")
             else:
                 result = CGWrapper(result, post="*")
-            if iface.isExternal():
-                # The holder is an nsRefPtr.  If we're nullable and end up null,
-                # the holder will be null anyway, so it's safe to just always
-                # return it here.
-                returnCode = ("(void)${declName}; // avoid warning. May end up not being read\n"
-                              "return ${holderName}.forget();")
-            elif iface.isCallback():
-                # The decl is an OwningNonNull or nsRefPtr, depending
-                # on whether we're nullable.
-                returnCode = "return ${declName}.forget();"
-            elif type.nullable():
-                # Decl is a raw pointer
-                returnCode = ("NS_IF_ADDREF(${declName});\n"
-                              "return dont_AddRef(${declName});")
-            else:
-                # Decl is a non-null raw pointer.
-                returnCode = ("NS_ADDREF(${declName});\n"
-                              "return dont_AddRef(${declName});")
-            return result.define(), "nullptr", returnCode
+            # Since we always force an owning type for callback return values,
+            # our ${declName} is an OwningNonNull or nsRefPtr.  So we can just
+            # .forget() to get our already_AddRefed.
+            return result.define(), "nullptr", "return ${declName}.forget();"
         if type.isCallback():
             return ("already_AddRefed<%s>" % type.unroll().identifier.name,
                     "nullptr", "return ${declName}.forget();")
         if type.isAny():
             return "JS::Value", "JS::UndefinedValue()", "return ${declName};"
         if type.isObject():
             return "JSObject*", "nullptr", "return ${declName};"
         if type.isSpiderMonkeyInterface():
@@ -8454,22 +8480,22 @@ class CGNativeMember(ClassMethod):
             args.append(Argument("ErrorResult&", "aRv"))
         # The legacycaller thisval
         if self.member.isMethod() and self.member.isLegacycaller():
             # If it has an identifier, we can't deal with it yet
             assert self.member.isIdentifierLess()
             args.insert(0, Argument("JS::Value", "aThisVal"))
         # And jscontext bits.
         if needCx(returnType, argList, self.extendedAttrs,
-                  self.descriptor, self.passCxAsNeeded):
+                  self.descriptorProvider, self.passCxAsNeeded):
             args.insert(0, Argument("JSContext*", "cx"))
         # And if we're static, a global
         if self.member.isStatic():
             globalObjectType = "GlobalObject"
-            if self.descriptor.workers:
+            if self.descriptorProvider.workers:
                 globalObjectType = "Worker" + globalObjectType
             args.insert(0, Argument("const %s&" % globalObjectType, "global"))
         return args
 
     def doGetArgType(self, type, optional, isMember):
         """
         The main work of getArgType.  Returns a string type decl, whether this
         is a const ref, as well as whether the type should be wrapped in
@@ -8509,17 +8535,17 @@ class CGNativeMember(ClassMethod):
                 if optional or isMember:
                     if forceOwningType:
                         typeDecl = "OwningNonNull<%s>"
                     else:
                         typeDecl = "NonNull<%s>"
                 else:
                     typeDecl = "%s&"
             return ((typeDecl %
-                     self.descriptor.getDescriptor(iface.identifier.name).nativeType),
+                     self.descriptorProvider.getDescriptor(iface.identifier.name).nativeType),
                     False, False)
 
         if type.isSpiderMonkeyInterface():
             assert not isMember
             if self.jsObjectsArePtr:
                 typeDecl = "JSObject*"
             else:
                 if type.nullable():
@@ -8570,18 +8596,18 @@ class CGNativeMember(ClassMethod):
         if type.isObject():
             if isMember:
                 declType = "JSObject*"
             else:
                 declType = "JS::Handle<JSObject*>"
             return declType, False, False
 
         if type.isDictionary():
-            typeName = CGDictionary.makeDictionaryName(type.inner,
-                                                       self.descriptor.workers)
+            typeName = CGDictionary.makeDictionaryName(
+                type.inner, self.descriptorProvider.workers)
             return typeName, True, True
 
         if type.isDate():
             return "Date", False, True
 
         assert type.isPrimitive()
 
         return builtinNames[type.tag()], False, True
@@ -8909,16 +8935,17 @@ class CGJSImplMethod(CGNativeMember):
                                 CGSpecializedMethod.makeNativeName(descriptor,
                                                                    method),
                                 signature,
                                 descriptor.getExtendedAttributes(method),
                                 breakAfter=breakAfter,
                                 variadicIsSequence=True,
                                 passCxAsNeeded=False)
         self.signature = signature
+        self.descriptor = descriptor
         if isConstructor:
             self.body = self.getConstructorImpl()
         else:
             self.body = self.getImpl()
 
     def getImpl(self):
         callbackArgs = [arg.name for arg in self.getArgs(self.signature[0], self.signature[1])]
         return 'return mImpl->%s(%s);' % (self.name, ", ".join(callbackArgs))
@@ -9154,25 +9181,29 @@ class CGJSImplClass(CGBindingImplClass):
                 "return obj;" % self.descriptor.name)
 
     def getGetParentObjectReturnType(self):
         return "nsISupports*"
 
     def getGetParentObjectBody(self):
         return "return mParent;"
 
+def isJSImplementedDescriptor(descriptorProvider):
+    return (isinstance(descriptorProvider, Descriptor) and
+            descriptorProvider.interface.isJSImplemented())
+
 class CGCallback(CGClass):
     def __init__(self, idlObject, descriptorProvider, baseName, methods,
                  getters=[], setters=[]):
         self.baseName = baseName
         self._deps = idlObject.getDeps()
         name = idlObject.identifier.name
         if descriptorProvider.workers:
             name += "Workers"
-        if isinstance(descriptorProvider, Descriptor) and descriptorProvider.interface.isJSImplemented():
+        if isJSImplementedDescriptor(descriptorProvider):
             name = jsImplName(name)
         # For our public methods that needThisHandling we want most of the
         # same args and the same return type as what CallbackMember
         # generates.  So we want to take advantage of all its
         # CGNativeMember infrastructure, but that infrastructure can't deal
         # with templates and most especially template arguments.  So just
         # cheat and have CallbackMember compute all those things for us.
         realMethods = []
@@ -9386,21 +9417,25 @@ class CallbackMember(CGNativeMember):
             "holderName" : "rvalHolder",
             "declName" : "rvalDecl",
             # We actually want to pass in a null scope object here, because
             # wrapping things into our current compartment (that of mCallback)
             # is what we want.
             "obj": "nullptr"
             }
 
+        if isJSImplementedDescriptor(self.descriptorProvider):
+            isCallbackReturnValue = "JSImpl"
+        else:
+            isCallbackReturnValue = "Callback"
         convertType = instantiateJSToNativeConversion(
             getJSToNativeConversionInfo(self.retvalType,
-                                        self.descriptor,
+                                        self.descriptorProvider,
                                         exceptionCode=self.exceptionCode,
-                                        isCallbackReturnValue=True),
+                                        isCallbackReturnValue=isCallbackReturnValue),
             replacements)
         assignRetval = string.Template(
             self.getRetvalInfo(self.retvalType,
                                False)[2]).substitute(replacements)
         return convertType.define() + "\n" + assignRetval
 
     def getArgConversions(self):
         # Just reget the arglist from self.originalSig, because our superclasses
@@ -9438,17 +9473,17 @@ class CallbackMember(CGNativeMember):
             # XXXbz if we try to do a sequence of strings, this will kinda fail.
             result = "mutableStr"
             prepend = "nsString mutableStr(%s);\n" % argval
         else:
             result = argval
             prepend = ""
 
         conversion = prepend + wrapForType(
-            arg.type, self.descriptor,
+            arg.type, self.descriptorProvider,
             {
                 'result' : result,
                 'successCode' : "continue;" if arg.variadic else "break;",
                 'jsvalRef' : "argv[%s]" % jsvalIndex,
                 'jsvalPtr' : "&argv[%s]" % jsvalIndex,
                 # XXXbz we don't have anything better to use for 'obj',
                 # really...  It's OK to use CallbackPreserveColor because
                 # CallSetup already handled the unmark-gray bits for us.