Bug 1428531 - Add chrome-only Foo.isInstance static methods to interface objects. r?bz draft
authorCameron McCormack <cam@mcc.id.au>
Sat, 06 Jan 2018 22:36:56 +0800
changeset 718336 f10f509a83c2f9b9a0485878eec68419b5f67a7a
parent 717700 2696b906dd166731c2ec25ffe63867fe191e8ab3
child 745451 ede4d3e18d7b7f3b2bdfdab01992e06da0ceba1c
push id94892
push userbmo:cam@mcc.id.au
push dateWed, 10 Jan 2018 04:09:40 +0000
reviewersbz
bugs1428531
milestone59.0a1
Bug 1428531 - Add chrome-only Foo.isInstance static methods to interface objects. r?bz MozReview-Commit-ID: Kq0xONMnbEO
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -2494,16 +2494,57 @@ InterfaceHasInstance(JSContext* cx, int 
              "Why do we have a hasInstance hook if we don't have a prototype "
              "ID?");
 
   *bp = (domClass && domClass->mInterfaceChain[depth] == prototypeID);
   return true;
 }
 
 bool
+InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp,
+                    prototypes::ID prototypeID, int depth)
+{
+  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+  if (MOZ_UNLIKELY(args.length() < 1)) {
+    nsPrintfCString message("%s.isInstance",
+                            NamesOfInterfacesWithProtos(prototypeID));
+    return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, message.get());
+  }
+
+  if (!args[0].isObject()) {
+    nsPrintfCString message("Argument 1 of %s.isInstance",
+                            NamesOfInterfacesWithProtos(prototypeID));
+    return ThrowErrorMessage(cx, MSG_NOT_OBJECT, message.get());
+  }
+
+  JS::Rooted<JSObject*> instance(cx, &args[0].toObject());
+
+  const DOMJSClass* domClass =
+    GetDOMClass(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false));
+
+  if (domClass && domClass->mInterfaceChain[depth] == prototypeID) {
+    args.rval().setBoolean(true);
+    return true;
+  }
+
+  if (jsipc::IsWrappedCPOW(instance)) {
+    bool boolp = false;
+    if (!jsipc::DOMInstanceOf(cx, js::UncheckedUnwrap(instance), prototypeID,
+                              depth, &boolp)) {
+      return false;
+    }
+    args.rval().setBoolean(boolp);
+    return true;
+  }
+
+  args.rval().setBoolean(false);
+  return true;
+}
+
+bool
 ReportLenientThisUnwrappingFailure(JSContext* cx, JSObject* obj)
 {
   JS::Rooted<JSObject*> rootedObj(cx, obj);
   GlobalObject global(cx, rootedObj);
   if (global.Failed()) {
     return false;
   }
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global.GetAsSupports());
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2708,16 +2708,21 @@ ReparentWrapper(JSContext* aCx, JS::Hand
 bool
 InterfaceHasInstance(JSContext* cx, unsigned argc, JS::Value* vp);
 
 bool
 InterfaceHasInstance(JSContext* cx, int prototypeID, int depth,
                      JS::Handle<JSObject*> instance,
                      bool* bp);
 
+// Used to implement the cross-context <Interface>.isInstance static method.
+bool
+InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp,
+                    prototypes::ID prototypeID, int depth);
+
 // Helper for lenient getters/setters to report to console.  If this
 // returns false, we couldn't even get a global.
 bool
 ReportLenientThisUnwrappingFailure(JSContext* cx, JSObject* obj);
 
 // Given a JSObject* that represents the chrome side of a JS-implemented WebIDL
 // interface, get the nsIGlobalObject corresponding to the content side, if any.
 // A false return means an exception was thrown.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1764,16 +1764,33 @@ class CGClassObjectMovedHook(CGAbstractC
 
 
 def JSNativeArguments():
     return [Argument('JSContext*', 'cx'),
             Argument('unsigned', 'argc'),
             Argument('JS::Value*', 'vp')]
 
 
+class CGIsInstanceMethod(CGAbstractStaticMethod):
+    """
+    A class for generating the static isInstance method.
+    """
+    def __init__(self, descriptor):
+        assert descriptor.interface.hasInterfacePrototypeObject()
+        CGAbstractStaticMethod.__init__(self, descriptor, "isInstance", "bool",
+                                        JSNativeArguments())
+
+    def definition_body(self):
+        return fill(
+            """
+            return InterfaceIsInstance(cx, argc, vp, prototypes::id::${name},
+                                       PrototypeTraits<prototypes::id::${name}>::Depth);
+            """,
+            name=self.descriptor.name)
+
 class CGClassConstructor(CGAbstractStaticMethod):
     """
     JS-visible constructor for our objects
     """
     def __init__(self, descriptor, ctor, name=CONSTRUCT_HOOK_NAME):
         CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool',
                                         JSNativeArguments())
         self._ctor = ctor
@@ -2411,16 +2428,28 @@ class MethodDefiner(PropertyDefiner):
             if m.isStatic():
                 method["nativeName"] = CppKeywords.checkMethodName(IDLToCIdentifier(m.identifier.name))
 
             if isChromeOnly(m):
                 self.chrome.append(method)
             else:
                 self.regular.append(method)
 
+        # Generate the isInstance static method.
+        if (static and
+            (self.descriptor.interface.hasInterfaceObject() and
+             self.descriptor.interface.hasInterfacePrototypeObject())):
+            self.chrome.append({
+                "name": "isInstance",
+                "methodInfo": False,
+                "length": 1,
+                "flags": "JSPROP_ENUMERATE",
+                "condition": MemberCondition(),
+            })
+
         # TODO: Once iterable is implemented, use tiebreak rules instead of
         # failing. Also, may be more tiebreak rules to implement once spec bug
         # is resolved.
         # https://www.w3.org/Bugs/Public/show_bug.cgi?id=28592
         def hasIterator(methods, regular):
             return (any("@@iterator" in m.aliases for m in methods) or
                     any("@@iterator" == r["name"] for r in regular))
 
@@ -2798,16 +2827,17 @@ class ConstDefiner(PropertyDefiner):
             lambda fields: '  { "%s", %s }' % fields,
             '  { 0, JS::UndefinedValue() }',
             'ConstantSpec',
             PropertyDefiner.getControllingCondition, specData)
 
 
 class PropertyArrays():
     def __init__(self, descriptor):
+        self.descriptor = descriptor
         self.staticMethods = MethodDefiner(descriptor, "StaticMethods",
                                            static=True)
         self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes",
                                        static=True)
         self.methods = MethodDefiner(descriptor, "Methods", static=False)
         self.attrs = AttrDefiner(descriptor, "Attributes", static=False)
         self.unforgeableMethods = MethodDefiner(descriptor, "UnforgeableMethods",
                                                 static=False, unforgeable=True)
@@ -2816,17 +2846,21 @@ class PropertyArrays():
         self.consts = ConstDefiner(descriptor, "Constants")
 
     @staticmethod
     def arrayNames():
         return ["staticMethods", "staticAttrs", "methods", "attrs",
                 "unforgeableMethods", "unforgeableAttrs", "consts"]
 
     def hasChromeOnly(self):
-        return any(getattr(self, a).hasChromeOnly() for a in self.arrayNames())
+        # All interfaces that generate an interface object and interface
+        # prototype object have a chrome only isInstance static method.
+        return ((self.staticMethods.descriptor.interface.hasInterfaceObject() and
+                 self.staticMethods.descriptor.interface.hasInterfacePrototypeObject()) or
+                any(getattr(self, a).hasChromeOnly() for a in self.arrayNames()))
 
     def hasNonChromeOnly(self):
         return any(getattr(self, a).hasNonChromeOnly() for a in self.arrayNames())
 
     def __str__(self):
         define = ""
         for array in self.arrayNames():
             define += str(getattr(self, array))
@@ -12754,16 +12788,19 @@ class CGDescriptor(CGThing):
             hasPromiseReturningMethod, hasPromiseReturningGetter) = (
                 False, False, False, False, False, False, False)
         jsonifierMethod = None
         crossOriginMethods, crossOriginGetters, crossOriginSetters = set(), set(), set()
         unscopableNames = list()
         for n in descriptor.interface.namedConstructors:
             cgThings.append(CGClassConstructor(descriptor, n,
                                                NamedConstructorName(n)))
+        if (descriptor.interface.hasInterfaceObject() and
+            descriptor.interface.hasInterfacePrototypeObject()):
+            cgThings.append(CGIsInstanceMethod(descriptor))
         for m in descriptor.interface.members:
             if m.isMethod() and m.identifier.name == 'queryInterface':
                 continue
 
             props = memberProperties(m, descriptor)
 
             if m.isMethod():
                 if m.getExtendedAttribute("Unscopable"):
@@ -14274,19 +14311,23 @@ class CGBindingRoot(CGThing):
 
             return (any(isChromeOnly(a) or needsContainsHack(a) or
                         needsCallerType(a)
                         for a in desc.interface.members) or
                     desc.interface.getExtendedAttribute("ChromeOnly") is not None or
                     # JS-implemented interfaces with an interface object get a
                     # chromeonly _create method.  And interfaces with an
                     # interface object might have a ChromeOnly constructor.
+                    # Also interfaces whose interface prototype object is
+                    # generated (which is most of them) for the isInstance
+                    # method.
                     (desc.interface.hasInterfaceObject() and
                      (desc.interface.isJSImplemented() or
-                      (ctor and isChromeOnly(ctor)))) or
+                      (ctor and isChromeOnly(ctor)) or
+                      desc.interface.hasInterfacePrototypeObject())) or
                     # JS-implemented interfaces with clearable cached
                     # attrs have chromeonly _clearFoo methods.
                     (desc.interface.isJSImplemented() and
                      any(clearableCachedAttrs(desc))))
 
         # XXXkhuey ugly hack but this is going away soon.
         bindingHeaders['xpcprivate.h'] = webIDLFile.endswith("EventTarget.webidl")