Bug 1353867 - Add code generation for array of cross origin properties. r=bzbarsky
☠☠ backed out by cc4bb8c7fa92 ☠ ☠
authorPeter Van der Beken <peterv@propagandism.org>
Mon, 31 Dec 2018 10:52:37 +0000
changeset 509294 00869bed41216138d967c715a8ed601cff074720
parent 509293 700aeb906fd206725e85311d6857f429576eb8f1
child 509295 8c05f4d3f7ad300b5ee0ac6c78cc34b19107a4e9
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs1353867
milestone66.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 1353867 - Add code generation for array of cross origin properties. r=bzbarsky Differential Revision: https://phabricator.services.mozilla.com/D12654
dom/bindings/Codegen.py
dom/bindings/parser/WebIDL.py
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2083,16 +2083,85 @@ class PropertyDefiner:
         return MemberCondition(
             PropertyDefiner.getStringAttr(interfaceMember,
                                           "Pref"),
             PropertyDefiner.getStringAttr(interfaceMember,
                                           "Func"),
             interfaceMember.getExtendedAttribute("SecureContext") is not None,
             nonExposureSet)
 
+    @staticmethod
+    def generatePrefableArrayValues(array, descriptor, specFormatter, specTerminator,
+                                    getCondition, getDataTuple,
+                                    switchToCondition=None):
+        """
+        This method generates an array of spec entries for interface members. It returns
+          a tuple containing the array of spec entries and the maximum of the number of
+          spec entries per condition.
+
+        array is an array of interface members.
+
+        descriptor is the descriptor for the interface that array contains members of.
+
+        specFormatter is a function that takes a single argument, a tuple,
+          and returns a string, a spec array entry.
+
+        specTerminator is a terminator for the spec array (inserted every time
+          our controlling pref changes and at the end of the array).
+
+        getCondition is a callback function that takes an array entry and
+          returns the corresponding MemberCondition.
+
+        getDataTuple is a callback function that takes an array entry and
+          returns a tuple suitable to be passed to specFormatter.
+
+        switchToCondition is a function that takes a MemberCondition and an array of
+          previously generated spec entries. If None is passed for this function then all
+          the interface members should return the same value from getCondition.
+        """
+        def unsupportedSwitchToCondition(condition, specs):
+            # If no specs have been added yet then this is just the first call to
+            # switchToCondition that we call to avoid putting a specTerminator at the
+            # front of the list.
+            if len(specs) == 0:
+                return
+            raise "Not supported"
+
+        if switchToCondition is None:
+            switchToCondition = unsupportedSwitchToCondition
+
+        specs = []
+        numSpecsInCurPrefable = 0
+        maxNumSpecsInPrefable = 0
+
+        # So we won't put a specTerminator at the very front of the list:
+        lastCondition = getCondition(array[0], descriptor)
+
+        switchToCondition(lastCondition, specs)
+
+        for member in array:
+            curCondition = getCondition(member, descriptor)
+            if lastCondition != curCondition:
+                # Terminate previous list
+                specs.append(specTerminator)
+                if numSpecsInCurPrefable > maxNumSpecsInPrefable:
+                    maxNumSpecsInPrefable = numSpecsInCurPrefable
+                numSpecsInCurPrefable = 0
+                # And switch to our new condition
+                switchToCondition(curCondition, specs)
+                lastCondition = curCondition
+            # And the actual spec
+            specs.append(specFormatter(getDataTuple(member, descriptor)))
+            numSpecsInCurPrefable += 1
+        if numSpecsInCurPrefable > maxNumSpecsInPrefable:
+            maxNumSpecsInPrefable = numSpecsInCurPrefable
+        specs.append(specTerminator)
+
+        return (specs, maxNumSpecsInPrefable)
+
     def generatePrefableArray(self, array, name, specFormatter, specTerminator,
                               specType, getCondition, getDataTuple):
         """
         This method generates our various arrays.
 
         array is an array of interface members as passed to generateArray
 
         name is the name as passed to generateArray
@@ -2114,76 +2183,54 @@ class PropertyDefiner:
 
         # We want to generate a single list of specs, but with specTerminator
         # inserted at every point where the pref name controlling the member
         # changes.  That will make sure the order of the properties as exposed
         # on the interface and interface prototype objects does not change when
         # pref control is added to members while still allowing us to define all
         # the members in the smallest number of JSAPI calls.
         assert len(array) != 0
-        # So we won't put a specTerminator at the very front of the list:
-        lastCondition = getCondition(array[0], self.descriptor)
-
-        specs = []
+
         disablers = []
         prefableSpecs = []
 
         disablersTemplate = dedent(
             """
             // Can't be const because the pref-enabled boolean needs to be writable
             static PrefableDisablers %s_disablers%d = {
               true, %s, %s, %s
             };
             """)
         prefableWithDisablersTemplate = '  { &%s_disablers%d, &%s_specs[%d] }'
         prefableWithoutDisablersTemplate = '  { nullptr, &%s_specs[%d] }'
         prefCacheTemplate = '&%s[%d].disablers->enabled'
 
-        def switchToCondition(props, condition):
+        def switchToCondition(condition, specs):
             # Remember the info about where our pref-controlled
             # booleans live.
             if condition.pref is not None:
-                props.prefCacheData.append(
+                self.prefCacheData.append(
                     (condition.pref,
                      prefCacheTemplate % (name, len(prefableSpecs))))
             # Set up pointers to the new sets of specs inside prefableSpecs
             if condition.hasDisablers():
                 prefableSpecs.append(prefableWithDisablersTemplate %
                                      (name, len(specs), name, len(specs)))
                 disablers.append(disablersTemplate %
                                  (name, len(specs),
                                   toStringBool(condition.secureContext),
                                   condition.nonExposedGlobals,
                                   condition.func))
             else:
                 prefableSpecs.append(prefableWithoutDisablersTemplate %
                                      (name, len(specs)))
 
-        switchToCondition(self, lastCondition)
-
-        numSpecsInCurPrefable = 0
-        maxNumSpecsInPrefable = 0
-
-        for member in array:
-            curCondition = getCondition(member, self.descriptor)
-            if lastCondition != curCondition:
-                # Terminate previous list
-                specs.append(specTerminator)
-                if numSpecsInCurPrefable > maxNumSpecsInPrefable:
-                    maxNumSpecsInPrefable = numSpecsInCurPrefable
-                numSpecsInCurPrefable = 0
-                # And switch to our new condition
-                switchToCondition(self, curCondition)
-                lastCondition = curCondition
-            # And the actual spec
-            specs.append(specFormatter(getDataTuple(member)))
-            numSpecsInCurPrefable += 1
-        specs.append(specTerminator)
-        if numSpecsInCurPrefable > maxNumSpecsInPrefable:
-            maxNumSpecsInPrefable = numSpecsInCurPrefable
+        specs, maxNumSpecsInPrefable = self.generatePrefableArrayValues(
+            array, self.descriptor, specFormatter, specTerminator, getCondition,
+            getDataTuple, switchToCondition)
         prefableSpecs.append("  { nullptr, nullptr }")
 
         specType = "const " + specType
         arrays = fill(
             """
             // We deliberately use brace-elision to make Visual Studio produce better initalization code.
             #if defined(__clang__)
             #pragma clang diagnostic push
@@ -2261,29 +2308,30 @@ def EnumerabilityFlags(member):
         return "0"
     return "JSPROP_ENUMERATE"
 
 
 class MethodDefiner(PropertyDefiner):
     """
     A class for defining methods on a prototype object.
     """
-    def __init__(self, descriptor, name, static, unforgeable=False):
+    def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
         assert not (static and unforgeable)
         PropertyDefiner.__init__(self, descriptor, name)
 
         # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822
         #       We should be able to check for special operations without an
         #       identifier. For now we check if the name starts with __
 
         # Ignore non-static methods for interfaces without a proto object
         if descriptor.interface.hasInterfacePrototypeObject() or static:
             methods = [m for m in descriptor.interface.members if
                        m.isMethod() and m.isStatic() == static and
                        MemberIsUnforgeable(m, descriptor) == unforgeable and
+                       (not crossOriginOnly or m.getExtendedAttribute("CrossOriginCallable")) and
                        not m.isIdentifierLess()]
         else:
             methods = []
         self.chrome = []
         self.regular = []
         for m in methods:
             if m.identifier.name == 'QueryInterface':
                 # QueryInterface is special, because instead of generating an
@@ -2310,26 +2358,17 @@ class MethodDefiner(PropertyDefiner):
                     "name": 'QueryInterface',
                     "methodInfo": False,
                     "length": 1,
                     "flags": "0",
                     "condition": PropertyDefiner.getControllingCondition(m, descriptor)
                 })
                 continue
 
-            method = {
-                "name": m.identifier.name,
-                "methodInfo": not m.isStatic(),
-                "length": methodLength(m),
-                "flags": EnumerabilityFlags(m),
-                "condition": PropertyDefiner.getControllingCondition(m, descriptor),
-                "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
-                "returnsPromise": m.returnsPromise(),
-                "hasIteratorAlias": "@@iterator" in m.aliases
-            }
+            method = self.methodData(m, descriptor)
 
             if m.isStatic():
                 method["nativeName"] = CppKeywords.checkMethodName(IDLToCIdentifier(m.identifier.name))
 
             if isChromeOnly(m):
                 self.chrome.append(method)
             else:
                 self.regular.append(method)
@@ -2454,80 +2493,96 @@ class MethodDefiner(PropertyDefiner):
             if not descriptor.interface.hasInterfaceObject():
                 # static methods go on the interface object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
         else:
             if not descriptor.interface.hasInterfacePrototypeObject():
                 # non-static methods go on the interface prototype object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
 
+    @staticmethod
+    def methodData(m, descriptor, overrideFlags=None):
+        return {
+                "name": m.identifier.name,
+                "methodInfo": not m.isStatic(),
+                "length": methodLength(m),
+                "flags": EnumerabilityFlags(m) if (overrideFlags is None) else overrideFlags,
+                "condition": PropertyDefiner.getControllingCondition(m, descriptor),
+                "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
+                "returnsPromise": m.returnsPromise(),
+                "hasIteratorAlias": "@@iterator" in m.aliases
+            }
+
+    @staticmethod
+    def formatSpec(fields):
+        if fields[0].startswith("@@"):
+            fields = (fields[0][2:],) + fields[1:]
+            return '  JS_SYM_FNSPEC(%s, %s, %s, %s, %s, %s)' % fields
+        return '  JS_FNSPEC("%s", %s, %s, %s, %s, %s)' % fields
+
+    @staticmethod
+    def specData(m, descriptor, unforgeable=False):
+        def flags(m, unforgeable):
+            unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if unforgeable else ""
+            return m["flags"] + unforgeable
+
+        if "selfHostedName" in m:
+            selfHostedName = '"%s"' % m["selfHostedName"]
+            assert not m.get("methodInfo", True)
+            accessor = "nullptr"
+            jitinfo = "nullptr"
+        else:
+            selfHostedName = "nullptr"
+            # When defining symbols, function name may not match symbol name
+            methodName = m.get("methodName", m["name"])
+            accessor = m.get("nativeName", IDLToCIdentifier(methodName))
+            if m.get("methodInfo", True):
+                if m.get("returnsPromise", False):
+                    exceptionPolicy = "ConvertExceptionsToPromises"
+                else:
+                    exceptionPolicy = "ThrowExceptions"
+
+                # Cast this in case the methodInfo is a
+                # JSTypedMethodJitInfo.
+                jitinfo = ("reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor)
+                if m.get("allowCrossOriginThis", False):
+                    accessor = ("(GenericMethod<CrossOriginThisPolicy, %s>)" %
+                                exceptionPolicy)
+                elif descriptor.interface.isOnGlobalProtoChain():
+                    accessor = ("(GenericMethod<MaybeGlobalThisPolicy, %s>)" %
+                                exceptionPolicy)
+                else:
+                    accessor = ("(GenericMethod<NormalThisPolicy, %s>)" %
+                                exceptionPolicy)
+            else:
+                if m.get("returnsPromise", False):
+                    jitinfo = "&%s_methodinfo" % accessor
+                    accessor = "StaticMethodPromiseWrapper"
+                else:
+                    jitinfo = "nullptr"
+
+        return (m["name"], accessor, jitinfo, m["length"], flags(m, unforgeable), selfHostedName)
+
+    @staticmethod
+    def condition(m, d):
+        return m["condition"]
+
     def generateArray(self, array, name):
         if len(array) == 0:
             return ""
 
-        def condition(m, d):
-            return m["condition"]
-
-        def flags(m):
-            unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if self.unforgeable else ""
-            return m["flags"] + unforgeable
-
-        def specData(m):
-            if "selfHostedName" in m:
-                selfHostedName = '"%s"' % m["selfHostedName"]
-                assert not m.get("methodInfo", True)
-                accessor = "nullptr"
-                jitinfo = "nullptr"
-            else:
-                selfHostedName = "nullptr"
-                # When defining symbols, function name may not match symbol name
-                methodName = m.get("methodName", m["name"])
-                accessor = m.get("nativeName", IDLToCIdentifier(methodName))
-                if m.get("methodInfo", True):
-                    if m.get("returnsPromise", False):
-                        exceptionPolicy = "ConvertExceptionsToPromises"
-                    else:
-                        exceptionPolicy = "ThrowExceptions"
-
-                    # Cast this in case the methodInfo is a
-                    # JSTypedMethodJitInfo.
-                    jitinfo = ("reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor)
-                    if m.get("allowCrossOriginThis", False):
-                        accessor = ("(GenericMethod<CrossOriginThisPolicy, %s>)" %
-                                    exceptionPolicy)
-                    elif self.descriptor.interface.isOnGlobalProtoChain():
-                        accessor = ("(GenericMethod<MaybeGlobalThisPolicy, %s>)" %
-                                    exceptionPolicy)
-                    else:
-                        accessor = ("(GenericMethod<NormalThisPolicy, %s>)" %
-                                    exceptionPolicy)
-                else:
-                    if m.get("returnsPromise", False):
-                        jitinfo = "&%s_methodinfo" % accessor
-                        accessor = "StaticMethodPromiseWrapper"
-                    else:
-                        jitinfo = "nullptr"
-
-            return (m["name"], accessor, jitinfo, m["length"], flags(m), selfHostedName)
-
-        def formatSpec(fields):
-            if fields[0].startswith("@@"):
-                fields = (fields[0][2:],) + fields[1:]
-                return '  JS_SYM_FNSPEC(%s, %s, %s, %s, %s, %s)' % fields
-            return '  JS_FNSPEC("%s", %s, %s, %s, %s, %s)' % fields
-
         return self.generatePrefableArray(
             array, name,
-            formatSpec,
+            self.formatSpec,
             '  JS_FS_END',
             'JSFunctionSpec',
-            condition, specData)
-
-
-def IsCrossOriginWritable(attr, descriptor):
+            self.condition, functools.partial(self.specData, unforgeable=self.unforgeable))
+
+
+def isCrossOriginWritable(attr, descriptor):
     """
     Return whether the IDLAttribute in question is cross-origin writable on the
     interface represented by descriptor.  This is needed to handle the fact that
     some, but not all, interfaces implementing URLUtils want a cross-origin
     writable .href.
     """
     crossOriginWritable = attr.getExtendedAttribute("CrossOriginWritable")
     if not crossOriginWritable:
@@ -2538,62 +2593,66 @@ def IsCrossOriginWritable(attr, descript
             len(crossOriginWritable) == 1)
     return crossOriginWritable[0] == descriptor.interface.identifier.name
 
 def isNonExposedNavigatorObjectGetter(attr, descriptor):
     return (attr.navigatorObjectGetter and
             not descriptor.getDescriptor(attr.type.inner.identifier.name).register)
 
 class AttrDefiner(PropertyDefiner):
-    def __init__(self, descriptor, name, static, unforgeable=False):
+    def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
         assert not (static and unforgeable)
         PropertyDefiner.__init__(self, descriptor, name)
         self.name = name
         # Ignore non-static attributes for interfaces without a proto object
         if descriptor.interface.hasInterfacePrototypeObject() or static:
-            attributes = [m for m in descriptor.interface.members if
-                          m.isAttr() and m.isStatic() == static and
-                          MemberIsUnforgeable(m, descriptor) == unforgeable and
-                          not isNonExposedNavigatorObjectGetter(m, descriptor)]
-        else:
-            attributes = []
-
-        attributes = [
-            {"name": name, "attr": attr}
-            for attr in attributes
-            for name in [attr.identifier.name] + attr.bindingAliases
-        ]
-
+            idlAttrs = [m for m in descriptor.interface.members if
+                        m.isAttr() and m.isStatic() == static and
+                        MemberIsUnforgeable(m, descriptor) == unforgeable and
+                        (not crossOriginOnly or m.getExtendedAttribute("CrossOriginReadable") or
+                         isCrossOriginWritable(m, descriptor)) and
+                        not isNonExposedNavigatorObjectGetter(m, descriptor)]
+        else:
+            idlAttrs = []
+
+        attributes = []
+        for attr in idlAttrs:
+            attributes.extend(self.attrData(attr, unforgeable))
         self.chrome = [m for m in attributes if isChromeOnly(m["attr"])]
         self.regular = [m for m in attributes if not isChromeOnly(m["attr"])]
         self.static = static
-        self.unforgeable = unforgeable
 
         if static:
             if not descriptor.interface.hasInterfaceObject():
                 # static attributes go on the interface object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
         else:
             if not descriptor.interface.hasInterfacePrototypeObject():
                 # non-static attributes go on the interface prototype object
                 assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
 
-    def generateArray(self, array, name):
-        if len(array) == 0:
-            return ""
-
-        def condition(m, d):
-            return PropertyDefiner.getControllingCondition(m["attr"], d)
-
-        def flags(attr):
-            unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else ""
-            return EnumerabilityFlags(attr) + unforgeable
-
+    @staticmethod
+    def attrData(attr, unforgeable=False, overrideFlags=None):
+        if overrideFlags is None:
+            permanent = " | JSPROP_PERMANENT" if unforgeable else ""
+            flags = EnumerabilityFlags(attr) + permanent
+        else:
+            flags = overrideFlags
+        return ({"name": name, "attr": attr, "flags": flags} for name in [attr.identifier.name] + attr.bindingAliases)
+
+    @staticmethod
+    def condition(m, d):
+        return PropertyDefiner.getControllingCondition(m["attr"], d)
+
+    @staticmethod
+    def specData(entry, descriptor, static=False, crossOriginOnly=False):
         def getter(attr):
-            if self.static:
+            if crossOriginOnly and not attr.getExtendedAttribute("CrossOriginReadable"):
+                return "nullptr, nullptr"
+            if static:
                 if attr.type.isPromise():
                     raise TypeError("Don't know how to handle "
                                     "static Promise-returning "
                                     "attribute %s.%s" %
                                     (self.descriptor.name,
                                      attr.identifier.name))
                 accessor = 'get_' + IDLToCIdentifier(attr.identifier.name)
                 jitinfo = "nullptr"
@@ -2609,64 +2668,72 @@ class AttrDefiner(PropertyDefiner):
                                         "readable attribute %s.%s" %
                                         (self.descriptor.name,
                                          attr.identifier.name))
                     accessor = ("GenericGetter<LenientThisPolicy, %s>" %
                                 exceptionPolicy)
                 elif attr.getExtendedAttribute("CrossOriginReadable"):
                     accessor = ("GenericGetter<CrossOriginThisPolicy, %s>" %
                                 exceptionPolicy)
-                elif self.descriptor.interface.isOnGlobalProtoChain():
+                elif descriptor.interface.isOnGlobalProtoChain():
                     accessor = ("GenericGetter<MaybeGlobalThisPolicy, %s>" %
                                 exceptionPolicy)
                 else:
                     accessor = ("GenericGetter<NormalThisPolicy, %s>" %
                                 exceptionPolicy)
                 jitinfo = ("&%s_getterinfo" %
                            IDLToCIdentifier(attr.identifier.name))
             return "%s, %s" % \
                    (accessor, jitinfo)
 
         def setter(attr):
             if (attr.readonly and
                 attr.getExtendedAttribute("PutForwards") is None and
                 attr.getExtendedAttribute("Replaceable") is None and
                 attr.getExtendedAttribute("LenientSetter") is None):
                 return "nullptr, nullptr"
-            if self.static:
+            if crossOriginOnly and not isCrossOriginWritable(attr, descriptor):
+                return "nullptr, nullptr"
+            if static:
                 accessor = 'set_' + IDLToCIdentifier(attr.identifier.name)
                 jitinfo = "nullptr"
             else:
                 if attr.hasLenientThis():
-                    if IsCrossOriginWritable(attr, self.descriptor):
+                    if isCrossOriginWritable(attr, descriptor):
                         raise TypeError("Can't handle lenient cross-origin "
                                         "writable attribute %s.%s" %
-                                        (self.descriptor.name,
+                                        (descriptor.name,
                                          attr.identifier.name))
                     accessor = "GenericSetter<LenientThisPolicy>"
-                elif IsCrossOriginWritable(attr, self.descriptor):
+                elif isCrossOriginWritable(attr, descriptor):
                     accessor = "GenericSetter<CrossOriginThisPolicy>"
-                elif self.descriptor.interface.isOnGlobalProtoChain():
+                elif descriptor.interface.isOnGlobalProtoChain():
                     accessor = "GenericSetter<MaybeGlobalThisPolicy>"
                 else:
                     accessor = "GenericSetter<NormalThisPolicy>"
                 jitinfo = "&%s_setterinfo" % IDLToCIdentifier(attr.identifier.name)
             return "%s, %s" % \
                    (accessor, jitinfo)
 
-        def specData(entry):
-            name, attr = entry["name"], entry["attr"]
-            return (name, flags(attr), getter(attr), setter(attr))
+        name, attr, flags = entry["name"], entry["attr"], entry["flags"]
+        return (name, flags, getter(attr), setter(attr))
+
+    @staticmethod
+    def formatSpec(fields):
+        return '  { "%s", %s, %s, %s }' % fields
+
+    def generateArray(self, array, name):
+        if len(array) == 0:
+            return ""
 
         return self.generatePrefableArray(
-            array, name,
-            lambda fields: '  { "%s", %s, %s, %s }' % fields,
-            '  { nullptr, 0, nullptr, nullptr, nullptr, nullptr }',
+            array, name, self.formatSpec,
+            '  JS_PS_END',
             'JSPropertySpec',
-            condition, specData)
+            self.condition, functools.partial(self.specData, static=self.static))
 
 
 class ConstDefiner(PropertyDefiner):
     """
     A class for definining constants on the interface object
     """
     def __init__(self, descriptor, name):
         PropertyDefiner.__init__(self, descriptor, name)
@@ -2674,40 +2741,42 @@ class ConstDefiner(PropertyDefiner):
         constants = [m for m in descriptor.interface.members if m.isConst()]
         self.chrome = [m for m in constants if isChromeOnly(m)]
         self.regular = [m for m in constants if not isChromeOnly(m)]
 
     def generateArray(self, array, name):
         if len(array) == 0:
             return ""
 
-        def specData(const):
+        def specData(const, descriptor):
             return (const.identifier.name,
                     convertConstIDLValueToJSVal(const.value))
 
         return self.generatePrefableArray(
             array, name,
             lambda fields: '  { "%s", %s }' % fields,
             '  { 0, JS::UndefinedValue() }',
             'ConstantSpec',
             PropertyDefiner.getControllingCondition, specData)
 
 
 class PropertyArrays():
-    def __init__(self, descriptor):
-        self.staticMethods = MethodDefiner(descriptor, "StaticMethods",
+    def __init__(self, descriptor, crossOriginOnly=False):
+        self.staticMethods = MethodDefiner(descriptor, "StaticMethods", crossOriginOnly,
                                            static=True)
-        self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes",
+        self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes", crossOriginOnly,
                                        static=True)
-        self.methods = MethodDefiner(descriptor, "Methods", static=False)
-        self.attrs = AttrDefiner(descriptor, "Attributes", static=False)
+        self.methods = MethodDefiner(descriptor, "Methods", crossOriginOnly, static=False)
+        self.attrs = AttrDefiner(descriptor, "Attributes", crossOriginOnly, static=False)
         self.unforgeableMethods = MethodDefiner(descriptor, "UnforgeableMethods",
-                                                static=False, unforgeable=True)
+                                                crossOriginOnly, static=False,
+                                                unforgeable=True)
         self.unforgeableAttrs = AttrDefiner(descriptor, "UnforgeableAttributes",
-                                            static=False, unforgeable=True)
+                                            crossOriginOnly, static=False,
+                                            unforgeable=True)
         self.consts = ConstDefiner(descriptor, "Constants")
 
     @staticmethod
     def arrayNames():
         return ["staticMethods", "staticAttrs", "methods", "attrs",
                 "unforgeableMethods", "unforgeableAttrs", "consts"]
 
     def hasChromeOnly(self):
@@ -4076,16 +4145,87 @@ class CGClearCachedValueMethod(CGAbstrac
             declObj=declObj,
             noopRetval=noopRetval,
             saveMember=saveMember,
             slotIndex=slotIndex,
             clearXrayExpandoSlots=clearXrayExpandoSlots,
             regetMember=regetMember)
 
 
+class CGCrossOriginProperties(CGThing):
+    def __init__(self, descriptor):
+        attrs = []
+        methods = []
+        for m in descriptor.interface.members:
+            if m.isAttr() and (m.getExtendedAttribute("CrossOriginReadable") or isCrossOriginWritable(m, descriptor)):
+                if m.isStatic():
+                    raise TypeError("Don't know how to deal with static method %s" %
+                                    m.identifier.name)
+                if PropertyDefiner.getControllingCondition(m, descriptor).hasDisablers():
+                    raise TypeError("Don't know how to deal with disabler for %s" %
+                                    m.identifier.name)
+                if len(m.bindingAliases) > 0:
+                    raise TypeError("Don't know how to deal with aliases for %s" %
+                                    m.identifier.name)
+                attrs.extend(AttrDefiner.attrData(m, overrideFlags="0"))
+            elif m.isMethod() and m.getExtendedAttribute("CrossOriginCallable"):
+                if m.isStatic():
+                    raise TypeError("Don't know how to deal with static method %s" %
+                                    m.identifier.name)
+                if PropertyDefiner.getControllingCondition(m, descriptor).hasDisablers():
+                    raise TypeError("Don't know how to deal with disabler for %s" %
+                                    m.identifier.name)
+                if len(m.aliases) > 0:
+                    raise TypeError("Don't know how to deal with aliases for %s" %
+                                    m.identifier.name)
+                methods.append(MethodDefiner.methodData(m, descriptor, overrideFlags="JSPROP_READONLY"))
+
+        if len(attrs) > 0:
+            self.attributeSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
+                attrs, descriptor, AttrDefiner.formatSpec, '  JS_PS_END\n',
+                AttrDefiner.condition, functools.partial(AttrDefiner.specData, crossOriginOnly=True))
+        else:
+            self.attributeSpecs = [' JS_PS_END\n']
+        if len(methods) > 0:
+            self.methodSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
+                methods, descriptor, MethodDefiner.formatSpec, '  JS_FS_END\n',
+                MethodDefiner.condition, MethodDefiner.specData)
+        else:
+            self.methodSpecs = ['  JS_FS_END\n']
+
+    def declare(self):
+        return fill("""
+            extern JSPropertySpec sCrossOriginAttributes[${attributesLength}];
+            extern JSFunctionSpec sCrossOriginMethods[${methodsLength}];
+            """,
+            attributesLength=len(self.attributeSpecs),
+            methodsLength=len(self.methodSpecs))
+
+    def define(self):
+        return fill(
+            """
+            // We deliberately use brace-elision to make Visual Studio produce better initalization code.
+            #if defined(__clang__)
+            #pragma clang diagnostic push
+            #pragma clang diagnostic ignored "-Wmissing-braces"
+            #endif
+            JSPropertySpec sCrossOriginAttributes[] = {
+              $*{attributeSpecs}
+            };
+            JSFunctionSpec sCrossOriginMethods[] = {
+              $*{methodSpecs}
+            };
+            #if defined(__clang__)
+            #pragma clang diagnostic pop
+            #endif
+            """,
+            attributeSpecs=",\n".join(self.attributeSpecs),
+            methodSpecs=",\n".join(self.methodSpecs))
+
+
 class CGIsPermittedMethod(CGAbstractMethod):
     """
     crossOriginGetters/Setters/Methods are sets of names of the relevant members.
     """
     def __init__(self, descriptor, crossOriginGetters, crossOriginSetters,
                  crossOriginMethods):
         self.crossOriginGetters = crossOriginGetters
         self.crossOriginSetters = crossOriginSetters
@@ -12393,24 +12533,24 @@ def memberProperties(m, descriptor):
                 if m.getExtendedAttribute("CrossOriginCallable"):
                     props.isCrossOriginMethod = True
     elif m.isAttr():
         if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
             if m.getExtendedAttribute("CrossOriginReadable"):
                 props.isCrossOriginGetter = True
         if not m.readonly:
             if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
-                if IsCrossOriginWritable(m, descriptor):
+                if isCrossOriginWritable(m, descriptor):
                     props.isCrossOriginSetter = True
         elif m.getExtendedAttribute("PutForwards"):
-            if IsCrossOriginWritable(m, descriptor):
+            if isCrossOriginWritable(m, descriptor):
                 props.isCrossOriginSetter = True
         elif (m.getExtendedAttribute("Replaceable") or
               m.getExtendedAttribute("LenientSetter")):
-            if IsCrossOriginWritable(m, descriptor):
+            if isCrossOriginWritable(m, descriptor):
                 props.isCrossOriginSetter = True
 
     return props
 
 
 class CGDescriptor(CGThing):
     def __init__(self, descriptor):
         CGThing.__init__(self)
@@ -12643,16 +12783,17 @@ class CGDescriptor(CGThing):
             if descriptor.interface.hasChildInterfaces():
                 cgThings.append(CGGetProtoObjectMethod(descriptor))
         if descriptor.interface.hasInterfaceObject():
             cgThings.append(CGGetConstructorObjectHandleMethod(descriptor))
             cgThings.append(CGGetConstructorObjectMethod(descriptor))
 
         # See whether we need we need to generate an IsPermitted method
         if crossOriginGetters or crossOriginSetters or crossOriginMethods:
+            cgThings.append(CGCrossOriginProperties(descriptor))
             cgThings.append(CGIsPermittedMethod(descriptor,
                                                 crossOriginGetters,
                                                 crossOriginSetters,
                                                 crossOriginMethods))
 
         cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n")
         cgThings = CGWrapper(cgThings, pre='\n', post='\n')
         self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name),
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -5133,16 +5133,20 @@ class IDLMethod(IDLInterfaceMember, IDLS
         elif identifier == "Exposed":
             convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
         elif (identifier == "CrossOriginCallable" or
               identifier == "WebGLHandlesContextLoss"):
             # Known no-argument attributes.
             if not attr.noArguments():
                 raise WebIDLError("[%s] must take no arguments" % identifier,
                                   [attr.location])
+            if identifier == "CrossOriginCallable" and self.isStatic():
+                raise WebIDLError("[CrossOriginCallable] is only allowed on non-static "
+                                  "attributes"
+                                  [attr.location, self.location])
         elif identifier == "Pure":
             if not attr.noArguments():
                 raise WebIDLError("[Pure] must take no arguments",
                                   [attr.location])
             self._setDependsOn("DOMState")
             self._setAffects("Nothing")
         elif identifier == "Affects":
             if not attr.hasValue():