Bug 861493. When passing arguments to an Xray for a WebIDL constructor, make sure to do the argument unwrapping before entering the content compartment. r=bholley,waldo
authorBoris Zbarsky <bzbarsky@mit.edu>
Thu, 25 Apr 2013 19:03:05 -0400
changeset 140936 d85f6cc0eb4b9969a8612a03763d9b488f577bb0
parent 140935 5a913ab3d2a5a3ade8e5f9dee7b4737379f82877
child 140937 d52e506162516ad0777cc3530221abfcaa6e968b
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, waldo
bugs861493
milestone23.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 861493. When passing arguments to an Xray for a WebIDL constructor, make sure to do the argument unwrapping before entering the content compartment. r=bholley,waldo There are several changes here: 1) Adds some MutableThis methods to Optional, Nullable, and dictionaries to effectively allow doing a const_cast without knowing the actual type being templated over. I needed this because I do not in fact know that type in the relevant code. I'm open to suggestions for a better name for this method. 2) Adds some operator& to RootedJSValue to make it look more like a JS::Value, and in particular so I can JS_WrapValue the thing in it. 3) Adds a Slot() method to NonNullLazyRootedObject, just like NonNull has. 4) Adds an operator& to LazyRootedObject to make it look more like JSObject* so I can JS_WrapObject the thing in it. 5) Implements the actual rewrapping of the arguments into the content compartment. 6) Fixes a small preexisting bug in which we didn't look at named constructors in getTypesFromDescriptor (this was causing my tests to not compile). 7) Changes Xrays to not enter the content compartment when calling a WebIDL constructor. 8) Adds some friend API to report things as not being functions.
dom/bindings/BindingDeclarations.h
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/Configuration.py
dom/bindings/Nullable.h
dom/bindings/test/TestBindingHeader.h
dom/bindings/test/TestCodeGen.webidl
dom/tests/mochitest/chrome/Makefile.in
dom/tests/mochitest/chrome/test_sandbox_bindings.xul
dom/tests/mochitest/chrome/test_xray_event_constructor.xul
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/xpconnect/wrappers/XrayWrapper.cpp
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -260,16 +260,21 @@ public:
     return mImpl.ref();
   }
 
   T& Value()
   {
     return mImpl.ref();
   }
 
+  Optional& AsMutable() const
+  {
+    return *const_cast<Optional*>(this);
+  }
+
   // If we ever decide to add conversion operators for optional arrays
   // like the ones Nullable has, we'll need to ensure that Maybe<> has
   // the boolean before the actual data.
 
 private:
   // Forbid copy-construction and assignment
   Optional(const Optional& other) MOZ_DELETE;
   const Optional &operator=(const Optional &other) MOZ_DELETE;
@@ -374,16 +379,26 @@ public:
 
   // Note: This operator can be const because we return by value, not
   // by reference.
   operator JS::Value() const
   {
     return mValue;
   }
 
+  JS::Value* operator&()
+  {
+    return &mValue;
+  }
+
+  const JS::Value* operator&() const
+  {
+    return &mValue;
+  }
+
 private:
   // Don't allow copy-construction of these objects, because it'll do the wrong
   // thing with our flag mCx.
   RootedJSValue(const RootedJSValue&) MOZ_DELETE;
 
   JS::Value mValue;
   JSContext* mCx;
 };
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1426,24 +1426,35 @@ protected:
 
 class NonNullLazyRootedObject : public Maybe<JS::RootedObject>
 {
 public:
   operator JSObject&() const {
     MOZ_ASSERT(!empty() && ref(), "Can not alias null.");
     return *ref();
   }
+
+  JSObject** Slot() { // To make us look like a NonNull
+    // Assert if we're empty, on purpose
+    return ref().address();
+  }
 };
 
 class LazyRootedObject : public Maybe<JS::RootedObject>
 {
 public:
   operator JSObject*() const {
     return empty() ? (JSObject*) nullptr : ref();
   }
+
+  JSObject** operator&()
+  {
+    // Assert if we're empty, on purpose
+    return ref().address();
+  }
 };
 
 // A struct that has the same layout as an nsDependentString but much
 // faster constructor and destructor behavior
 struct FakeDependentString {
   FakeDependentString() :
     mFlags(nsDependentString::F_TERMINATED)
   {
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1075,17 +1075,17 @@ class CGClassConstructor(CGAbstractStati
 
     def generate_code(self):
         preamble = """
   JS::Rooted<JSObject*> obj(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)));
 """
         name = self._ctor.identifier.name
         nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
         callGenerator = CGMethodCall(nativeName, True, self.descriptor,
-                                     self._ctor)
+                                     self._ctor, isConstructor=True)
         return preamble + callGenerator.define();
 
 class CGClassConstructHookHolder(CGGeneric):
     def __init__(self, descriptor):
         if descriptor.interface.ctor():
             constructHook = CONSTRUCT_HOOK_NAME
         else:
             constructHook = "ThrowingConstructor"
@@ -4026,16 +4026,103 @@ class CGCallGenerator(CGThing):
 
     def define(self):
         return self.cgRoot.define()
 
 class MethodNotCreatorError(Exception):
     def __init__(self, typename):
         self.typename = typename
 
+# A counter for making sure that when we're wrapping up things in
+# nested sequences we don't use the same variable name to iterate over
+# different sequences.
+sequenceWrapLevel = 0
+
+def wrapTypeIntoCurrentCompartment(type, value):
+    """
+    Take the thing named by "value" and if it contains "any",
+    "object", or spidermonkey-interface types inside return a CGThing
+    that will wrap them into the current compartment.
+    """
+    if type.isAny():
+        assert not type.nullable()
+        return CGGeneric("if (!JS_WrapValue(cx, &%s)) {\n"
+                         "  return false;\n"
+                         "}" % value)
+
+    if type.isObject():
+        if not type.nullable():
+            value = "%s.Slot()" % value
+        else:
+            value = "&%s" % value
+        return CGGeneric("if (!JS_WrapObject(cx, %s)) {\n"
+                         "  return false;\n"
+                         "}" % value)
+
+    if type.isSpiderMonkeyInterface():
+        raise TypeError("Can't handle wrapping of spidermonkey interfaces in "
+                        "constructor arguments yet")
+
+    if type.isSequence():
+        if type.nullable():
+            type = type.inner
+            value = "%s.AsMutable().Value()" % value
+        global sequenceWrapLevel
+        index = "indexName%d" % sequenceWrapLevel
+        sequenceWrapLevel += 1
+        wrapElement = wrapTypeIntoCurrentCompartment(type.inner,
+                                                     "%s[%s]" % (value, index))
+        sequenceWrapLevel -= 1
+        if not wrapElement:
+            return None
+        return CGWrapper(CGIndenter(wrapElement),
+                         pre=("for (uint32_t %s = 0; %s < %s.Length(); ++%s) {\n" %
+                              (index, index, value, index)),
+                         post="\n}")
+
+    if type.isDictionary():
+        assert not type.nullable()
+        value = "%s.AsMutable()" % value
+        myDict = type.inner
+        memberWraps = []
+        while myDict:
+            for member in myDict.members:
+                memberWrap = wrapArgIntoCurrentCompartment(
+                    member,
+                    "%s.%s" % (value, CGDictionary.makeMemberName(member.identifier.name)))
+                if memberWrap:
+                    memberWraps.append(memberWrap)
+            myDict = myDict.parent
+        return CGList(memberWraps, "\n") if len(memberWraps) != 0 else None
+
+    if type.isUnion():
+        raise TypeError("Can't handle wrapping of unions in constructor "
+                        "arguments yet")
+
+    if (type.isString() or type.isPrimitive() or type.isEnum() or
+        type.isGeckoInterface() or type.isCallback()):
+        # All of these don't need wrapping
+        return None
+
+    raise TypeError("Unknown type; we don't know how to wrap it in constructor "
+                    "arguments: %s" % type)
+
+def wrapArgIntoCurrentCompartment(arg, value):
+    """
+    As wrapTypeIntoCurrentCompartment but handles things being optional
+    """
+    origValue = value
+    isOptional = arg.optional and not arg.defaultValue
+    if isOptional:
+        value = value + ".AsMutable().Value()"
+    wrap = wrapTypeIntoCurrentCompartment(arg.type, value)
+    if wrap and isOptional:
+        wrap = CGIfWrapper(wrap, "%s.WasPassed()" % origValue)
+    return wrap
+
 class CGPerSignatureCall(CGThing):
     """
     This class handles the guts of generating code for a particular
     call signature.  A call signature consists of four things:
 
     1) A return type, which can be None to indicate that there is no
        actual return value (e.g. this is an attribute setter) or an
        IDLType if there's an IDL type involved (including |void|).
@@ -4051,17 +4138,17 @@ class CGPerSignatureCall(CGThing):
     """
     # XXXbz For now each entry in the argument list is either an
     # IDLArgument or a FakeArgument, but longer-term we may want to
     # have ways of flagging things like JSContext* or optional_argc in
     # there.
 
     def __init__(self, returnType, arguments, nativeMethodName, static,
                  descriptor, idlNode, argConversionStartsAt=0, getter=False,
-                 setter=False):
+                 setter=False, isConstructor=False):
         assert idlNode.isMethod() == (not getter and not setter)
         assert idlNode.isAttr() == (getter or setter)
 
         CGThing.__init__(self)
         self.returnType = returnType
         self.descriptor = descriptor
         self.idlNode = idlNode
         self.extendedAttributes = descriptor.getExtendedAttributes(idlNode,
@@ -4112,16 +4199,40 @@ if (global.Failed()) {
 
         cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(),
                                              self.getArgc(), self.descriptor,
                                              invalidEnumValueFatal=not setter,
                                              allowTreatNonCallableAsNull=setter,
                                              lenientFloatCode=lenientFloatCode) for
                          i in range(argConversionStartsAt, self.argCount)])
 
+        if isConstructor:
+            # If we're called via an xray, we need to enter the underlying
+            # object's compartment and then wrap up all of our arguments into
+            # that compartment as needed.  This is all happening after we've
+            # already done the conversions from JS values to WebIDL (C++)
+            # values, so we only need to worry about cases where there are 'any'
+            # or 'object' types, or other things that we represent as actual
+            # JSAPI types, present.  Effectively, we're emulating a
+            # CrossCompartmentWrapper, but working with the C++ types, not the
+            # original list of JS::Values.
+            cgThings.append(CGGeneric("Maybe<JSAutoCompartment> ac;"))
+            xraySteps = [
+                CGGeneric("obj = js::CheckedUnwrap(obj);\n"
+                          "if (!obj) {\n"
+                          "  return false;\n"
+                          "}\n"
+                          "ac.construct(cx, obj);") ]
+            xraySteps.extend(
+                wrapArgIntoCurrentCompartment(arg, argname)
+                for (arg, argname) in self.getArguments())
+            cgThings.append(
+                CGIfWrapper(CGList(xraySteps, "\n"),
+                            "xpc::WrapperFactory::IsXrayWrapper(obj)"))
+
         cgThings.append(CGCallGenerator(
                     self.getErrorReport() if self.isFallible() else None,
                     self.getArguments(), argsPre, returnType,
                     self.extendedAttributes, descriptor, nativeMethodName,
                     static))
         self.cgRoot = CGList(cgThings, "\n")
 
     def getArgv(self):
@@ -4216,34 +4327,37 @@ class CGCase(CGList):
         self.append(CGIndenter(bodyList));
         self.append(CGGeneric("}"))
 
 class CGMethodCall(CGThing):
     """
     A class to generate selection of a method signature from a set of
     signatures and generation of a call to that signature.
     """
-    def __init__(self, nativeMethodName, static, descriptor, method):
+    def __init__(self, nativeMethodName, static, descriptor, method,
+                 isConstructor=False):
         CGThing.__init__(self)
 
         methodName = '"%s.%s"' % (descriptor.interface.identifier.name, method.identifier.name)
 
         def requiredArgCount(signature):
             arguments = signature[1]
             if len(arguments) == 0:
                 return 0
             requiredArgs = len(arguments)
             while requiredArgs and arguments[requiredArgs-1].optional:
                 requiredArgs -= 1
             return requiredArgs
 
         def getPerSignatureCall(signature, argConversionStartsAt=0):
             return CGPerSignatureCall(signature[0], signature[1],
                                       nativeMethodName, static, descriptor,
-                                      method, argConversionStartsAt)
+                                      method,
+                                      argConversionStartsAt=argConversionStartsAt,
+                                      isConstructor=isConstructor)
             
 
         signatures = method.signatures()
         if len(signatures) == 1:
             # Special case: we can just do a per-signature method call
             # here for our one signature and not worry about switching
             # on anything.
             signature = signatures[0]
@@ -7081,16 +7195,20 @@ class CGDictionary(CGThing):
                  "  {\n"
                  "    Maybe<JSAutoRequest> ar;\n"
                  "    Maybe<JSAutoCompartment> ac;\n"
                  "    Maybe< JS::Rooted<JS::Value> > json;\n"
                  "    JSContext* cx = ParseJSON(aJSON, ar, ac, json);\n"
                  "    NS_ENSURE_TRUE(cx, false);\n"
                  "    return Init(cx, json.ref());\n"
                  "  }\n" if not self.workers else "") +
+                "  ${selfName}& AsMutable() const\n"
+                "  {\n"
+                "    return *const_cast<${selfName}*>(this);\n"
+                "  }\n"
                 "\n" +
                 "\n".join(memberDecls) + "\n"
                 "private:\n"
                 "  // Disallow copy-construction\n"
                 "  ${selfName}(const ${selfName}&) MOZ_DELETE;\n" +
                 # NOTE: jsids are per-runtime, so don't use them in workers
                 ("  static bool InitIds(JSContext* cx);\n"
                  "  static bool initedIds;\n" if self.needToInitIds else "") +
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -470,16 +470,17 @@ class Descriptor(DescriptorProvider):
 # Some utility methods
 def getTypesFromDescriptor(descriptor):
     """
     Get all argument and return types for all members of the descriptor
     """
     members = [m for m in descriptor.interface.members]
     if descriptor.interface.ctor():
         members.append(descriptor.interface.ctor())
+    members.extend(descriptor.interface.namedConstructors)
     signatures = [s for m in members if m.isMethod() for s in m.signatures()]
     types = []
     for s in signatures:
         assert len(s) == 2
         (returnType, arguments) = s
         types.append(returnType)
         types.extend(a.type for a in arguments)
 
--- a/dom/bindings/Nullable.h
+++ b/dom/bindings/Nullable.h
@@ -59,16 +59,20 @@ public:
     MOZ_ASSERT(!mIsNull);
     return mValue;
   }
 
   bool IsNull() const {
     return mIsNull;
   }
 
+  Nullable& AsMutable() const {
+    return *const_cast<Nullable*>(this);
+  }
+
   // Make it possible to use a const Nullable of an array type with other
   // array types.
   template<typename U>
   operator const Nullable< nsTArray<U> >&() const {
     // Make sure that T is ok to reinterpret to nsTArray<U>
     const nsTArray<U>& arr = mValue;
     (void)arr;
     return *reinterpret_cast<const Nullable< nsTArray<U> >*>(this);
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -127,16 +127,28 @@ public:
                 const TestInterfaceOrOnlyForUseInConstructor&, ErrorResult&);
   */
 
   static
   already_AddRefed<TestInterface> Test(const GlobalObject&, ErrorResult&);
   static
   already_AddRefed<TestInterface> Test(const GlobalObject&, const nsAString&,
                                        ErrorResult&);
+  static
+  already_AddRefed<TestInterface> Test2(const GlobalObject&,
+                                        JSContext*,
+                                        const DictForConstructor&,
+                                        JS::Value,
+                                        JSObject&,
+                                        JSObject*,
+                                        const Sequence<Dict>&,
+                                        const Optional<JS::Value>&,
+                                        const Optional<NonNull<JSObject> >&,
+                                        const Optional<JSObject*>&,
+                                        ErrorResult&);
 
   // Integer types
   int8_t ReadonlyByte();
   int8_t WritableByte();
   void SetWritableByte(int8_t);
   void PassByte(int8_t);
   int8_t ReceiveByte();
   void PassOptionalByte(const Optional<int8_t>&);
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -98,17 +98,20 @@ interface OnlyForUseInConstructor {
 
 [Constructor,
  Constructor(DOMString str),
  Constructor(unsigned long num, boolean? boolArg),
  Constructor(TestInterface? iface),
  Constructor(long arg1, IndirectlyImplementedInterface iface),
  // Constructor(long arg1, long arg2, (TestInterface or OnlyForUseInConstructor) arg3),
  NamedConstructor=Test,
- NamedConstructor=Test(DOMString str)
+ NamedConstructor=Test(DOMString str),
+ NamedConstructor=Test2(DictForConstructor dict, any any1, object obj1,
+                        object? obj2, sequence<Dict> seq, optional any any2,
+                        optional object obj3, optional object? obj4)
  ]
 interface TestInterface {
   // Integer types
   // XXXbz add tests for throwing versions of all the integer stuff
   readonly attribute byte readonlyByte;
   attribute byte writableByte;
   void passByte(byte arg);
   byte receiveByte();
@@ -626,27 +629,40 @@ dictionary Dict : ParentDict {
   unrestricted double  nanUrDouble = NaN;
 
 };
 
 dictionary ParentDict : GrandparentDict {
   long c = 5;
   TestInterface someInterface;
   TestExternalInterface someExternalInterface;
+  any parentAny;
 };
 
 dictionary DictContainingDict {
   Dict memberDict;
 };
 
 dictionary DictContainingSequence {
   sequence<long> ourSequence;
   sequence<TestInterface> ourSequence2;
 };
 
+dictionary DictForConstructor {
+  Dict dict;
+  DictContainingDict dict2;
+  sequence<Dict> seq1;
+  sequence<sequence<Dict>>? seq2;
+  sequence<sequence<Dict>?> seq3;
+  // No support for sequences of "any" or "object" as return values yet.
+  object obj1;
+  object? obj2;
+  any any1 = null;
+};
+
 interface TestIndexedGetterInterface {
   getter long item(unsigned long idx);
   readonly attribute unsigned long length;
 };
 
 interface TestNamedGetterInterface {
   getter DOMString (DOMString name);
 };
--- a/dom/tests/mochitest/chrome/Makefile.in
+++ b/dom/tests/mochitest/chrome/Makefile.in
@@ -57,16 +57,17 @@ MOCHITEST_CHROME_FILES = \
 		test_DOM_element_instanceof.xul \
 		file_DOM_element_instanceof.xul \
 		test_bug830858.xul \
 		file_bug830858.xul \
 		test_indexedSetter.html \
 		test_queryCaretRect.html \
 		queryCaretRectWin.html \
 		queryCaretRectUnix.html \
+		test_xray_event_constructor.xul \
 		$(NULL)
 
 ifeq (WINNT,$(OS_ARCH))
 MOCHITEST_CHROME_FILES += \
 		test_sizemode_attribute.xul \
 		sizemode_attribute.xul \
 		$(NULL)
 endif
--- a/dom/tests/mochitest/chrome/test_sandbox_bindings.xul
+++ b/dom/tests/mochitest/chrome/test_sandbox_bindings.xul
@@ -228,16 +228,24 @@ https://bugzilla.mozilla.org/show_bug.cg
         var nodeFilterIface = Components.utils.evalInSandbox(
           'NodeFilter.myExpando = "FAIL"; NodeFilter', sandbox);
         is(nodeFilterIface.myExpando, undefined,
            "Should have Xrays for callback interface objects");
       } catch (e) {
         ok(false, "Should be able to return NodeFilter from a sandbox " + e);
       }
 
+      try {
+        var eventCtor = Components.utils.evalInSandbox("Event", sandbox);
+        var e = new eventCtor("test", { bubbles: true });
+        is(e.bubbles, true, "Dictionary argument should work");
+      } catch (e) {
+        ok(false, "Should be able to construct my event " + e);
+      }
+
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addLoadEvent(doTest);
   ]]>
   </script>
 </window>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/chrome/test_xray_event_constructor.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=861493
+-->
+<window title="Mozilla Bug 861493"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <iframe id="t" type="content"></iframe>
+  
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=861493"
+     target="_blank">Mozilla Bug 861493</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+  /** Test for Bug 861493 **/
+   SimpleTest.waitForExplicitFinish();
+
+   addLoadEvent(function() {
+     ok(Components.utils.isXrayWrapper($("t").contentWindow),
+        "Should have xray");
+     var e = new $("t").contentWindow.Event("test", { bubbles: true });
+     is(e.bubbles, true, "Dictionary should have worked");
+     SimpleTest.finish();
+   })
+
+  ]]>
+  </script>
+</window>
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1052,8 +1052,14 @@ js_DefineOwnProperty(JSContext *cx, JSOb
     assertSameCompartment(cx, obj, id, descriptor.value);
     if (descriptor.attrs & JSPROP_GETTER)
         assertSameCompartment(cx, CastAsObjectJsval(descriptor.getter));
     if (descriptor.attrs & JSPROP_SETTER)
         assertSameCompartment(cx, CastAsObjectJsval(descriptor.setter));
 
     return js_DefineOwnProperty(cx, HandleObject(obj), id, descriptor, bp);
 }
+
+JS_FRIEND_API(JSBool)
+js_ReportIsNotFunction(JSContext *cx, const JS::Value& v)
+{
+    return ReportIsNotFunction(cx, v);
+}
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1449,9 +1449,12 @@ inline void assertEnteredPolicy(JSContex
 #endif
 
 } /* namespace js */
 
 extern JS_FRIEND_API(JSBool)
 js_DefineOwnProperty(JSContext *cx, JSObject *objArg, jsid idArg,
                      const js::PropertyDescriptor& descriptor, JSBool *bp);
 
+extern JS_FRIEND_API(JSBool)
+js_ReportIsNotFunction(JSContext *cx, const JS::Value& v);
+
 #endif /* jsfriendapi_h___ */
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -141,21 +141,23 @@ public:
     // NB: resolveOwnProperty may decide whether or not to cache what it finds
     // on the holder. If the result is not cached, the lookup will happen afresh
     // for each access, which is the right thing for things like dynamic NodeList
     // properties.
     virtual bool resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper,
                                     HandleObject wrapper, HandleObject holder,
                                     HandleId id, JSPropertyDescriptor *desc, unsigned flags);
 
-    static bool call(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args)
+    static bool call(JSContext *cx, HandleObject wrapper,
+                     const JS::CallArgs &args, js::Wrapper& baseInstance)
     {
         MOZ_NOT_REACHED("Call trap currently implemented only for XPCWNs");
     }
-    static bool construct(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args)
+    static bool construct(JSContext *cx, HandleObject wrapper,
+                          const JS::CallArgs &args, js::Wrapper& baseInstance)
     {
         MOZ_NOT_REACHED("Call trap currently implemented only for XPCWNs");
     }
 
     virtual void preserveWrapper(JSObject *target) = 0;
 
     JSObject* getExpandoObject(JSContext *cx, HandleObject target,
                                HandleObject consumer);
@@ -193,18 +195,20 @@ public:
     virtual bool resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, HandleObject wrapper,
                                     HandleObject holder, HandleId id,
                                     JSPropertyDescriptor *desc, unsigned flags);
     static bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                                PropertyDescriptor *desc,
                                Handle<PropertyDescriptor> existingDesc, bool *defined);
     static bool enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags,
                                AutoIdVector &props);
-    static bool call(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args);
-    static bool construct(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args);
+    static bool call(JSContext *cx, HandleObject wrapper,
+                     const JS::CallArgs &args, js::Wrapper& baseInstance);
+    static bool construct(JSContext *cx, HandleObject wrapper,
+                          const JS::CallArgs &args, js::Wrapper& baseInstance);
 
     static bool isResolving(JSContext *cx, JSObject *holder, jsid id);
 
     static bool resolveDOMCollectionProperty(JSContext *cx, HandleObject wrapper,
                                              HandleObject holder, HandleId id,
                                              PropertyDescriptor *desc, unsigned flags);
 
     static XPCWrappedNative* getWN(JSObject *wrapper) {
@@ -237,18 +241,20 @@ public:
     virtual bool resolveOwnProperty(JSContext *cx, Wrapper &jsWrapper, HandleObject wrapper,
                                     HandleObject holder, HandleId id,
                                     JSPropertyDescriptor *desc, unsigned flags);
     static bool defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
                                PropertyDescriptor *desc,
                                Handle<PropertyDescriptor> existingDesc, bool *defined);
     static bool enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags,
                                AutoIdVector &props);
-    static bool call(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args);
-    static bool construct(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args);
+    static bool call(JSContext *cx, HandleObject wrapper,
+                     const JS::CallArgs &args, js::Wrapper& baseInstance);
+    static bool construct(JSContext *cx, HandleObject wrapper,
+                          const JS::CallArgs &args, js::Wrapper& baseInstance);
 
     static bool isResolving(JSContext *cx, JSObject *holder, jsid id)
     {
         return false;
     }
 
     typedef ResolvingIdDummy ResolvingIdImpl;
 
@@ -1123,17 +1129,18 @@ XPCWrappedNativeXrayTraits::createHolder
         return nullptr;
 
     js::SetReservedSlot(holder, JSSLOT_RESOLVING, PrivateValue(NULL));
     return holder;
 }
 
 bool
 XPCWrappedNativeXrayTraits::call(JSContext *cx, HandleObject wrapper,
-                                 const JS::CallArgs &args)
+                                 const JS::CallArgs &args,
+                                 js::Wrapper& baseInstance)
 {
     // Run the resolve hook of the wrapped native.
     XPCWrappedNative *wn = getWN(wrapper);
     if (NATIVE_HAS_FLAG(wn, WantCall)) {
         XPCCallContext ccx(JS_CALLER, cx, wrapper, NullPtr(), JSID_VOIDHANDLE, args.length(),
                            args.array(), args.rval().address());
         if (!ccx.IsValid())
             return false;
@@ -1148,17 +1155,18 @@ XPCWrappedNativeXrayTraits::call(JSConte
     }
 
     return true;
 
 }
 
 bool
 XPCWrappedNativeXrayTraits::construct(JSContext *cx, HandleObject wrapper,
-                                      const JS::CallArgs &args)
+                                      const JS::CallArgs &args,
+                                      js::Wrapper& baseInstance)
 {
     // Run the resolve hook of the wrapped native.
     XPCWrappedNative *wn = getWN(wrapper);
     if (NATIVE_HAS_FLAG(wn, WantConstruct)) {
         XPCCallContext ccx(JS_CALLER, cx, wrapper, NullPtr(), JSID_VOIDHANDLE, args.length(),
                            args.array(), args.rval().address());
         if (!ccx.IsValid())
             return false;
@@ -1232,52 +1240,65 @@ bool
 DOMXrayTraits::enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags,
                               AutoIdVector &props)
 {
     return XrayEnumerateProperties(cx, wrapper, getTargetObject(wrapper),
                                    flags, props);
 }
 
 bool
-DOMXrayTraits::call(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args)
+DOMXrayTraits::call(JSContext *cx, HandleObject wrapper,
+                    const JS::CallArgs &args, js::Wrapper& baseInstance)
 {
     RootedObject obj(cx, getTargetObject(wrapper));
-    {
-        JSAutoCompartment ac(cx, obj);
-        RootedValue thisv(cx, args.thisv());
-        if (!JS_WrapValue(cx, thisv.address()))
+    js::Class* clasp = js::GetObjectClass(obj);
+    // What we have is either a WebIDL interface object, a WebIDL prototype
+    // object, or a WebIDL instance object.  WebIDL prototype objects never have
+    // a clasp->call.  WebIDL interface objects we want to invoke on the xray
+    // compartment.  WebIDL instance objects either don't have a clasp->call or
+    // are using "legacycaller", which basically means plug-ins.  We want to
+    // call those on the content compartment.
+    if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) {
+        if (!clasp->call) {
+            js_ReportIsNotFunction(cx, JS::ObjectValue(*wrapper));
             return false;
-        args.setThis(thisv);
-        for (size_t i = 0; i < args.length(); ++i) {
-            if (!JS_WrapValue(cx, &args[i]))
-                return false;
         }
-        if (!Call(cx, thisv, obj, args.length(), args.array(), args.rval().address()))
+        // call it on the Xray compartment
+        if (!clasp->call(cx, args.length(), args.base()))
+            return false;
+    } else {
+        // This is only reached for WebIDL instance objects, and in practice
+        // only for plugins.  Just call them on the content compartment.
+        if (!baseInstance.call(cx, wrapper, args))
             return false;
     }
     return JS_WrapValue(cx, args.rval().address());
 }
 
 bool
-DOMXrayTraits::construct(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args)
+DOMXrayTraits::construct(JSContext *cx, HandleObject wrapper,
+                         const JS::CallArgs &args, js::Wrapper& baseInstance)
 {
     RootedObject obj(cx, getTargetObject(wrapper));
     MOZ_ASSERT(mozilla::dom::HasConstructor(obj));
-    RootedObject newObj(cx);
-    {
-        JSAutoCompartment ac(cx, obj);
-        for (size_t i = 0; i < args.length(); ++i) {
-            if (!JS_WrapValue(cx, &args[i]))
-                return false;
+    js::Class* clasp = js::GetObjectClass(obj);
+    // See comments in DOMXrayTraits::call() explaining what's going on here.
+    if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) {
+        if (!clasp->construct) {
+            js_ReportIsNotFunction(cx, JS::ObjectValue(*wrapper));
+            return false;
         }
-        newObj = JS_New(cx, obj, args.length(), args.array());
+        if (!clasp->construct(cx, args.length(), args.base()))
+            return false;
+    } else {
+        if (!baseInstance.construct(cx, wrapper, args))
+            return false;
     }
-    if (!newObj || !JS_WrapObject(cx, newObj.address()))
+    if (!args.rval().isObject() || !JS_WrapValue(cx, args.rval().address()))
         return false;
-    args.rval().setObject(*newObj);
     return true;
 }
 
 void
 DOMXrayTraits::preserveWrapper(JSObject *target)
 {
     nsISupports *identity;
     if (!mozilla::dom::UnwrapDOMObjectToISupports(target, identity))
@@ -1849,25 +1870,25 @@ XrayWrapper<Base, Traits>::iterate(JSCon
     return js::BaseProxyHandler::iterate(cx, wrapper, flags, vp);
 }
 
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::call(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args)
 {
     assertEnteredPolicy(cx, wrapper, JSID_VOID);
-    return Traits::call(cx, wrapper, args);
+    return Traits::call(cx, wrapper, args, Base::singleton);
 }
 
 template <typename Base, typename Traits>
 bool
 XrayWrapper<Base, Traits>::construct(JSContext *cx, HandleObject wrapper, const JS::CallArgs &args)
 {
     assertEnteredPolicy(cx, wrapper, JSID_VOID);
-    return Traits::construct(cx, wrapper, args);
+    return Traits::construct(cx, wrapper, args, Base::singleton);
 }
 
 /*
  * The Permissive / Security variants should be used depending on whether the
  * compartment of the wrapper is guranteed to subsume the compartment of the
  * wrapped object (i.e. - whether it is safe from a security perspective to
  * unwrap the wrapper).
  */