summaryrefslogtreecommitdiffstats
path: root/dom/bindings/Configuration.py
diff options
context:
space:
mode:
Diffstat (limited to 'dom/bindings/Configuration.py')
-rw-r--r--dom/bindings/Configuration.py1235
1 files changed, 1235 insertions, 0 deletions
diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py
new file mode 100644
index 0000000000..1bfdcdd100
--- /dev/null
+++ b/dom/bindings/Configuration.py
@@ -0,0 +1,1235 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import io
+import itertools
+import os
+from collections import defaultdict
+
+import six
+from WebIDL import IDLIncludesStatement
+
+autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n"
+
+
+def toStringBool(arg):
+ """
+ Converts IDL/Python Boolean (True/False) to C++ Boolean (true/false)
+ """
+ return str(not not arg).lower()
+
+
+class DescriptorProvider:
+ """
+ A way of getting descriptors for interface names. Subclasses must
+ have a getDescriptor method callable with the interface name only.
+
+ Subclasses must also have a getConfig() method that returns a
+ Configuration.
+ """
+
+ def __init__(self):
+ pass
+
+
+def isChildPath(path, basePath):
+ path = os.path.normpath(path)
+ return os.path.commonprefix((path, basePath)) == basePath
+
+
+class Configuration(DescriptorProvider):
+ """
+ Represents global configuration state based on IDL parse data and
+ the configuration file.
+ """
+
+ def __init__(self, filename, webRoots, parseData, generatedEvents=[]):
+ DescriptorProvider.__init__(self)
+
+ # Read the configuration file.
+ glbl = {}
+ exec(io.open(filename, encoding="utf-8").read(), glbl)
+ config = glbl["DOMInterfaces"]
+
+ webRoots = tuple(map(os.path.normpath, webRoots))
+
+ def isInWebIDLRoot(path):
+ return any(isChildPath(path, root) for root in webRoots)
+
+ # Build descriptors for all the interfaces we have in the parse data.
+ # This allows callers to specify a subset of interfaces by filtering
+ # |parseData|.
+ self.descriptors = []
+ self.interfaces = {}
+ self.descriptorsByName = {}
+ self.dictionariesByName = {}
+ self.generatedEvents = generatedEvents
+ self.maxProtoChainLength = 0
+ for thing in parseData:
+ if isinstance(thing, IDLIncludesStatement):
+ # Our build system doesn't support dep build involving
+ # addition/removal of "includes" statements that appear in a
+ # different .webidl file than their LHS interface. Make sure we
+ # don't have any of those. See similar block below for partial
+ # interfaces!
+ if thing.interface.filename() != thing.filename():
+ raise TypeError(
+ "The binding build system doesn't really support "
+ "'includes' statements which don't appear in the "
+ "file in which the left-hand side of the statement is "
+ "defined.\n"
+ "%s\n"
+ "%s" % (thing.location, thing.interface.location)
+ )
+
+ assert not thing.isType()
+
+ if (
+ not thing.isInterface()
+ and not thing.isNamespace()
+ and not thing.isInterfaceMixin()
+ ):
+ continue
+ # Our build system doesn't support dep builds involving
+ # addition/removal of partial interfaces/namespaces/mixins that
+ # appear in a different .webidl file than the
+ # interface/namespace/mixin they are extending. Make sure we don't
+ # have any of those. See similar block above for "includes"
+ # statements!
+ if not thing.isExternal():
+ for partial in thing.getPartials():
+ if partial.filename() != thing.filename():
+ raise TypeError(
+ "The binding build system doesn't really support "
+ "partial interfaces/namespaces/mixins which don't "
+ "appear in the file in which the "
+ "interface/namespace/mixin they are extending is "
+ "defined. Don't do this.\n"
+ "%s\n"
+ "%s" % (partial.location, thing.location)
+ )
+
+ # The rest of the logic doesn't apply to mixins.
+ if thing.isInterfaceMixin():
+ continue
+
+ iface = thing
+ if not iface.isExternal():
+ if not (
+ iface.getExtendedAttribute("ChromeOnly")
+ or iface.getExtendedAttribute("Func")
+ == ["nsContentUtils::IsCallerChromeOrFuzzingEnabled"]
+ or not iface.hasInterfaceObject()
+ or isInWebIDLRoot(iface.filename())
+ ):
+ raise TypeError(
+ "Interfaces which are exposed to the web may only be "
+ "defined in a DOM WebIDL root %r. Consider marking "
+ "the interface [ChromeOnly] or "
+ "[Func='nsContentUtils::IsCallerChromeOrFuzzingEnabled'] "
+ "if you do not want it exposed to the web.\n"
+ "%s" % (webRoots, iface.location)
+ )
+
+ self.interfaces[iface.identifier.name] = iface
+
+ entry = config.get(iface.identifier.name, {})
+ assert not isinstance(entry, list)
+
+ desc = Descriptor(self, iface, entry)
+ self.descriptors.append(desc)
+ # Setting up descriptorsByName while iterating through interfaces
+ # means we can get the nativeType of iterable interfaces without
+ # having to do multiple loops.
+ assert desc.interface.identifier.name not in self.descriptorsByName
+ self.descriptorsByName[desc.interface.identifier.name] = desc
+
+ # Keep the descriptor list sorted for determinism.
+ self.descriptors.sort(key=lambda x: x.name)
+
+ self.descriptorsByFile = {}
+ for d in self.descriptors:
+ self.descriptorsByFile.setdefault(d.interface.filename(), []).append(d)
+
+ self.enums = [e for e in parseData if e.isEnum()]
+
+ self.dictionaries = [d for d in parseData if d.isDictionary()]
+ self.dictionariesByName = {d.identifier.name: d for d in self.dictionaries}
+
+ self.callbacks = [
+ c for c in parseData if c.isCallback() and not c.isInterface()
+ ]
+
+ # Dictionary mapping from a union type name to a set of filenames where
+ # union types with that name are used.
+ self.filenamesPerUnion = defaultdict(set)
+
+ # Dictionary mapping from a filename to a list of types for
+ # the union types used in that file. If a union type is used
+ # in multiple files then it will be added to the list for the
+ # None key. Note that the list contains a type for every use
+ # of a union type, so there can be multiple entries with union
+ # types that have the same name.
+ self.unionsPerFilename = defaultdict(list)
+
+ def addUnion(t):
+ filenamesForUnion = self.filenamesPerUnion[t.name]
+ if t.filename() not in filenamesForUnion:
+ # We have a to be a bit careful: some of our built-in
+ # typedefs are for unions, and those unions end up with
+ # "<unknown>" as the filename. If that happens, we don't
+ # want to try associating this union with one particular
+ # filename, since there isn't one to associate it with,
+ # really.
+ if t.filename() == "<unknown>":
+ uniqueFilenameForUnion = None
+ elif len(filenamesForUnion) == 0:
+ # This is the first file that we found a union with this
+ # name in, record the union as part of the file.
+ uniqueFilenameForUnion = t.filename()
+ else:
+ # We already found a file that contains a union with
+ # this name.
+ if len(filenamesForUnion) == 1:
+ # This is the first time we found a union with this
+ # name in another file.
+ for f in filenamesForUnion:
+ # Filter out unions with this name from the
+ # unions for the file where we previously found
+ # them.
+ unionsForFilename = [
+ u for u in self.unionsPerFilename[f] if u.name != t.name
+ ]
+ if len(unionsForFilename) == 0:
+ del self.unionsPerFilename[f]
+ else:
+ self.unionsPerFilename[f] = unionsForFilename
+ # Unions with this name appear in multiple files, record
+ # the filename as None, so that we can detect that.
+ uniqueFilenameForUnion = None
+ self.unionsPerFilename[uniqueFilenameForUnion].append(t)
+ filenamesForUnion.add(t.filename())
+
+ def addUnions(t):
+ t = findInnermostType(t)
+ if t.isUnion():
+ addUnion(t)
+ for m in t.flatMemberTypes:
+ addUnions(m)
+
+ for (t, _) in getAllTypes(self.descriptors, self.dictionaries, self.callbacks):
+ addUnions(t)
+
+ for d in getDictionariesConvertedToJS(
+ self.descriptors, self.dictionaries, self.callbacks
+ ):
+ d.needsConversionToJS = True
+
+ for d in getDictionariesConvertedFromJS(
+ self.descriptors, self.dictionaries, self.callbacks
+ ):
+ d.needsConversionFromJS = True
+
+ # Collect all the global names exposed on a Window object (to implement
+ # the hash for looking up these names when resolving a property).
+ self.windowGlobalNames = []
+ for desc in self.getDescriptors(registersGlobalNamesOnWindow=True):
+ self.windowGlobalNames.append((desc.name, desc))
+ self.windowGlobalNames.extend(
+ (n.identifier.name, desc) for n in desc.interface.legacyFactoryFunctions
+ )
+ self.windowGlobalNames.extend(
+ (n, desc) for n in desc.interface.legacyWindowAliases
+ )
+
+ # Collect a sorted list of strings that we want to concatenate into
+ # one big string and a dict mapping each string to its offset in the
+ # concatenated string.
+
+ # We want the names of all the interfaces with a prototype (for
+ # implementing @@toStringTag).
+ names = set(
+ d.interface.getClassName()
+ for d in self.getDescriptors(hasInterfaceOrInterfacePrototypeObject=True)
+ )
+
+ # Now also add the names from windowGlobalNames, we need them for the
+ # perfect hash that we build for these.
+ names.update(n[0] for n in self.windowGlobalNames)
+
+ # Sorting is not strictly necessary, but makes the generated code a bit
+ # more readable.
+ names = sorted(names)
+
+ # We can't rely on being able to pass initial=0 to itertools.accumulate
+ # because it was only added in version 3.8, so define an accumulate
+ # function that chains the initial value into the iterator.
+ def accumulate(iterable, initial):
+ return itertools.accumulate(itertools.chain([initial], iterable))
+
+ # Calculate the offset of each name in the concatenated string. Note that
+ # we need to add 1 to the length to account for the null terminating each
+ # name.
+ offsets = accumulate(map(lambda n: len(n) + 1, names), initial=0)
+ self.namesStringOffsets = list(zip(names, offsets))
+
+ def getInterface(self, ifname):
+ return self.interfaces[ifname]
+
+ def getDescriptors(self, **filters):
+ """Gets the descriptors that match the given filters."""
+ curr = self.descriptors
+ # Collect up our filters, because we may have a webIDLFile filter that
+ # we always want to apply first.
+ tofilter = [(lambda x: x.interface.isExternal(), False)]
+ for key, val in six.iteritems(filters):
+ if key == "webIDLFile":
+ # Special-case this part to make it fast, since most of our
+ # getDescriptors calls are conditioned on a webIDLFile. We may
+ # not have this key, in which case we have no descriptors
+ # either.
+ curr = self.descriptorsByFile.get(val, [])
+ continue
+ elif key == "hasInterfaceObject":
+
+ def getter(x):
+ return x.interface.hasInterfaceObject()
+
+ elif key == "hasInterfacePrototypeObject":
+
+ def getter(x):
+ return x.interface.hasInterfacePrototypeObject()
+
+ elif key == "hasInterfaceOrInterfacePrototypeObject":
+
+ def getter(x):
+ return x.hasInterfaceOrInterfacePrototypeObject()
+
+ elif key == "isCallback":
+
+ def getter(x):
+ return x.interface.isCallback()
+
+ elif key == "isJSImplemented":
+
+ def getter(x):
+ return x.interface.isJSImplemented()
+
+ elif key == "isExposedInAnyWorker":
+
+ def getter(x):
+ return x.interface.isExposedInAnyWorker()
+
+ elif key == "isExposedInWorkerDebugger":
+
+ def getter(x):
+ return x.interface.isExposedInWorkerDebugger()
+
+ elif key == "isExposedInAnyWorklet":
+
+ def getter(x):
+ return x.interface.isExposedInAnyWorklet()
+
+ elif key == "isExposedInWindow":
+
+ def getter(x):
+ return x.interface.isExposedInWindow()
+
+ elif key == "isExposedInShadowRealms":
+
+ def getter(x):
+ return x.interface.isExposedInShadowRealms()
+
+ elif key == "isSerializable":
+
+ def getter(x):
+ return x.interface.isSerializable()
+
+ else:
+ # Have to watch out: just closing over "key" is not enough,
+ # since we're about to mutate its value
+ getter = (lambda attrName: lambda x: getattr(x, attrName))(key)
+ tofilter.append((getter, val))
+ for f in tofilter:
+ curr = [x for x in curr if f[0](x) == f[1]]
+ return curr
+
+ def getEnums(self, webIDLFile):
+ return [e for e in self.enums if e.filename() == webIDLFile]
+
+ def getDictionaries(self, webIDLFile):
+ return [d for d in self.dictionaries if d.filename() == webIDLFile]
+
+ def getCallbacks(self, webIDLFile):
+ return [c for c in self.callbacks if c.filename() == webIDLFile]
+
+ def getDescriptor(self, interfaceName):
+ """
+ Gets the appropriate descriptor for the given interface name.
+ """
+ # We may have optimized out this descriptor, but the chances of anyone
+ # asking about it are then slim. Put the check for that _after_ we've
+ # done our normal lookup. But that means we have to do our normal
+ # lookup in a way that will not throw if it fails.
+ d = self.descriptorsByName.get(interfaceName, None)
+ if d:
+ return d
+
+ raise NoSuchDescriptorError("For " + interfaceName + " found no matches")
+
+ def getConfig(self):
+ return self
+
+ def getDictionariesConvertibleToJS(self):
+ return [d for d in self.dictionaries if d.needsConversionToJS]
+
+ def getDictionariesConvertibleFromJS(self):
+ return [d for d in self.dictionaries if d.needsConversionFromJS]
+
+ def getDictionaryIfExists(self, dictionaryName):
+ return self.dictionariesByName.get(dictionaryName, None)
+
+
+class NoSuchDescriptorError(TypeError):
+ def __init__(self, str):
+ TypeError.__init__(self, str)
+
+
+def methodReturnsJSObject(method):
+ assert method.isMethod()
+
+ for signature in method.signatures():
+ returnType = signature[0]
+ if returnType.isObject() or returnType.isSpiderMonkeyInterface():
+ return True
+
+ return False
+
+
+def MemberIsLegacyUnforgeable(member, descriptor):
+ # Note: "or" and "and" return either their LHS or RHS, not
+ # necessarily booleans. Make sure to return a boolean from this
+ # method, because callers will compare its return value to
+ # booleans.
+ return bool(
+ (member.isAttr() or member.isMethod())
+ and not member.isStatic()
+ and (
+ member.isLegacyUnforgeable()
+ or descriptor.interface.getExtendedAttribute("LegacyUnforgeable")
+ )
+ )
+
+
+class Descriptor(DescriptorProvider):
+ """
+ Represents a single descriptor for an interface. See Bindings.conf.
+ """
+
+ def __init__(self, config, interface, desc):
+ DescriptorProvider.__init__(self)
+ self.config = config
+ self.interface = interface
+
+ self.wantsXrays = not interface.isExternal() and interface.isExposedInWindow()
+
+ if self.wantsXrays:
+ # We could try to restrict self.wantsXrayExpandoClass further. For
+ # example, we could set it to false if all of our slots store
+ # Gecko-interface-typed things, because we don't use Xray expando
+ # slots for those. But note that we would need to check the types
+ # of not only the members of "interface" but also of all its
+ # ancestors, because those can have members living in our slots too.
+ # For now, do the simple thing.
+ self.wantsXrayExpandoClass = interface.totalMembersInSlots != 0
+
+ # Read the desc, and fill in the relevant defaults.
+ ifaceName = self.interface.identifier.name
+ # For generated iterator interfaces for other iterable interfaces, we
+ # just use IterableIterator as the native type, templated on the
+ # nativeType of the iterable interface. That way we can have a
+ # templated implementation for all the duplicated iterator
+ # functionality.
+ if self.interface.isIteratorInterface():
+ itrName = self.interface.iterableInterface.identifier.name
+ itrDesc = self.getDescriptor(itrName)
+ nativeTypeDefault = iteratorNativeType(itrDesc)
+ elif self.interface.isAsyncIteratorInterface():
+ itrName = self.interface.asyncIterableInterface.identifier.name
+ itrDesc = self.getDescriptor(itrName)
+ nativeTypeDefault = iteratorNativeType(itrDesc)
+
+ elif self.interface.isExternal():
+ nativeTypeDefault = "nsIDOM" + ifaceName
+ else:
+ nativeTypeDefault = "mozilla::dom::" + ifaceName
+
+ self.nativeType = desc.get("nativeType", nativeTypeDefault)
+ # Now create a version of nativeType that doesn't have extra
+ # mozilla::dom:: at the beginning.
+ prettyNativeType = self.nativeType.split("::")
+ if prettyNativeType[0] == "mozilla":
+ prettyNativeType.pop(0)
+ if prettyNativeType[0] == "dom":
+ prettyNativeType.pop(0)
+ self.prettyNativeType = "::".join(prettyNativeType)
+
+ self.jsImplParent = desc.get("jsImplParent", self.nativeType)
+
+ # Do something sane for JSObject
+ if self.nativeType == "JSObject":
+ headerDefault = "js/TypeDecls.h"
+ elif self.interface.isCallback() or self.interface.isJSImplemented():
+ # A copy of CGHeaders.getDeclarationFilename; we can't
+ # import it here, sadly.
+ # Use our local version of the header, not the exported one, so that
+ # test bindings, which don't export, will work correctly.
+ basename = os.path.basename(self.interface.filename())
+ headerDefault = basename.replace(".webidl", "Binding.h")
+ else:
+ if not self.interface.isExternal() and self.interface.getExtendedAttribute(
+ "HeaderFile"
+ ):
+ headerDefault = self.interface.getExtendedAttribute("HeaderFile")[0]
+ elif (
+ self.interface.isIteratorInterface()
+ or self.interface.isAsyncIteratorInterface()
+ ):
+ headerDefault = "mozilla/dom/IterableIterator.h"
+ else:
+ headerDefault = self.nativeType
+ headerDefault = headerDefault.replace("::", "/") + ".h"
+ self.headerFile = desc.get("headerFile", headerDefault)
+ self.headerIsDefault = self.headerFile == headerDefault
+ if self.jsImplParent == self.nativeType:
+ self.jsImplParentHeader = self.headerFile
+ else:
+ self.jsImplParentHeader = self.jsImplParent.replace("::", "/") + ".h"
+
+ self.notflattened = desc.get("notflattened", False)
+ self.register = desc.get("register", True)
+
+ # If we're concrete, we need to crawl our ancestor interfaces and mark
+ # them as having a concrete descendant.
+ concreteDefault = (
+ not self.interface.isExternal()
+ and not self.interface.isCallback()
+ and not self.interface.isNamespace()
+ and
+ # We're going to assume that leaf interfaces are
+ # concrete; otherwise what's the point? Also
+ # interfaces with constructors had better be
+ # concrete; otherwise how can you construct them?
+ (
+ not self.interface.hasChildInterfaces()
+ or self.interface.ctor() is not None
+ )
+ )
+
+ self.concrete = desc.get("concrete", concreteDefault)
+ self.hasLegacyUnforgeableMembers = self.concrete and any(
+ MemberIsLegacyUnforgeable(m, self) for m in self.interface.members
+ )
+ self.operations = {
+ "IndexedGetter": None,
+ "IndexedSetter": None,
+ "IndexedDeleter": None,
+ "NamedGetter": None,
+ "NamedSetter": None,
+ "NamedDeleter": None,
+ "Stringifier": None,
+ "LegacyCaller": None,
+ }
+
+ self.hasDefaultToJSON = False
+
+ # Stringifiers need to be set up whether an interface is
+ # concrete or not, because they're actually prototype methods and hence
+ # can apply to instances of descendant interfaces. Legacy callers and
+ # named/indexed operations only need to be set up on concrete
+ # interfaces, since they affect the JSClass we end up using, not the
+ # prototype object.
+ def addOperation(operation, m):
+ if not self.operations[operation]:
+ self.operations[operation] = m
+
+ # Since stringifiers go on the prototype, we only need to worry
+ # about our own stringifier, not those of our ancestor interfaces.
+ if not self.interface.isExternal():
+ for m in self.interface.members:
+ if m.isMethod() and m.isStringifier():
+ addOperation("Stringifier", m)
+ if m.isMethod() and m.isDefaultToJSON():
+ self.hasDefaultToJSON = True
+
+ # We keep track of instrumente props for all non-external interfaces.
+ self.instrumentedProps = []
+ instrumentedProps = self.interface.getExtendedAttribute("InstrumentedProps")
+ if instrumentedProps:
+ # It's actually a one-element list, with the list
+ # we want as the only element.
+ self.instrumentedProps = instrumentedProps[0]
+
+ # Check that we don't have duplicated instrumented props.
+ uniqueInstrumentedProps = set(self.instrumentedProps)
+ if len(uniqueInstrumentedProps) != len(self.instrumentedProps):
+ duplicates = [
+ p
+ for p in uniqueInstrumentedProps
+ if self.instrumentedProps.count(p) > 1
+ ]
+ raise TypeError(
+ "Duplicated instrumented properties: %s.\n%s"
+ % (duplicates, self.interface.location)
+ )
+
+ if self.concrete:
+ self.proxy = False
+ iface = self.interface
+ for m in iface.members:
+ # Don't worry about inheriting legacycallers either: in
+ # practice these are on most-derived prototypes.
+ if m.isMethod() and m.isLegacycaller():
+ if not m.isIdentifierLess():
+ raise TypeError(
+ "We don't support legacycaller with "
+ "identifier.\n%s" % m.location
+ )
+ if len(m.signatures()) != 1:
+ raise TypeError(
+ "We don't support overloaded "
+ "legacycaller.\n%s" % m.location
+ )
+ addOperation("LegacyCaller", m)
+
+ while iface:
+ for m in iface.members:
+ if not m.isMethod():
+ continue
+
+ def addIndexedOrNamedOperation(operation, m):
+ if m.isIndexed():
+ operation = "Indexed" + operation
+ else:
+ assert m.isNamed()
+ operation = "Named" + operation
+ addOperation(operation, m)
+
+ if m.isGetter():
+ addIndexedOrNamedOperation("Getter", m)
+ if m.isSetter():
+ addIndexedOrNamedOperation("Setter", m)
+ if m.isDeleter():
+ addIndexedOrNamedOperation("Deleter", m)
+ if m.isLegacycaller() and iface != self.interface:
+ raise TypeError(
+ "We don't support legacycaller on "
+ "non-leaf interface %s.\n%s" % (iface, iface.location)
+ )
+
+ iface.setUserData("hasConcreteDescendant", True)
+ iface = iface.parent
+
+ self.proxy = (
+ self.supportsIndexedProperties()
+ or (
+ self.supportsNamedProperties() and not self.hasNamedPropertiesObject
+ )
+ or self.isMaybeCrossOriginObject()
+ )
+
+ if self.proxy:
+ if self.isMaybeCrossOriginObject() and (
+ self.supportsIndexedProperties() or self.supportsNamedProperties()
+ ):
+ raise TypeError(
+ "We don't support named or indexed "
+ "properties on maybe-cross-origin objects. "
+ "This lets us assume that their proxy "
+ "hooks are never called via Xrays. "
+ "Fix %s.\n%s" % (self.interface, self.interface.location)
+ )
+
+ if not self.operations["IndexedGetter"] and (
+ self.operations["IndexedSetter"]
+ or self.operations["IndexedDeleter"]
+ ):
+ raise SyntaxError(
+ "%s supports indexed properties but does "
+ "not have an indexed getter.\n%s"
+ % (self.interface, self.interface.location)
+ )
+ if not self.operations["NamedGetter"] and (
+ self.operations["NamedSetter"] or self.operations["NamedDeleter"]
+ ):
+ raise SyntaxError(
+ "%s supports named properties but does "
+ "not have a named getter.\n%s"
+ % (self.interface, self.interface.location)
+ )
+ iface = self.interface
+ while iface:
+ iface.setUserData("hasProxyDescendant", True)
+ iface = iface.parent
+
+ if desc.get("wantsQI", None) is not None:
+ self._wantsQI = desc.get("wantsQI", None)
+ self.wrapperCache = (
+ not self.interface.isCallback()
+ and not self.interface.isIteratorInterface()
+ and not self.interface.isAsyncIteratorInterface()
+ and desc.get("wrapperCache", True)
+ )
+
+ self.name = interface.identifier.name
+
+ # self.implicitJSContext is a list of names of methods and attributes
+ # that need a JSContext.
+ if self.interface.isJSImplemented():
+ self.implicitJSContext = ["constructor"]
+ else:
+ self.implicitJSContext = desc.get("implicitJSContext", [])
+ assert isinstance(self.implicitJSContext, list)
+
+ self._binaryNames = {}
+
+ if not self.interface.isExternal():
+
+ def maybeAddBinaryName(member):
+ binaryName = member.getExtendedAttribute("BinaryName")
+ if binaryName:
+ assert isinstance(binaryName, list)
+ assert len(binaryName) == 1
+ self._binaryNames.setdefault(
+ (member.identifier.name, member.isStatic()), binaryName[0]
+ )
+
+ for member in self.interface.members:
+ if not member.isAttr() and not member.isMethod():
+ continue
+ maybeAddBinaryName(member)
+
+ ctor = self.interface.ctor()
+ if ctor:
+ maybeAddBinaryName(ctor)
+
+ # Some default binary names for cases when nothing else got set.
+ self._binaryNames.setdefault(("__legacycaller", False), "LegacyCall")
+ self._binaryNames.setdefault(("__stringifier", False), "Stringify")
+
+ # Build the prototype chain.
+ self.prototypeChain = []
+ self.needsMissingPropUseCounters = False
+ parent = interface
+ while parent:
+ self.needsMissingPropUseCounters = (
+ self.needsMissingPropUseCounters
+ or parent.getExtendedAttribute("InstrumentedProps")
+ )
+ self.prototypeChain.insert(0, parent.identifier.name)
+ parent = parent.parent
+ config.maxProtoChainLength = max(
+ config.maxProtoChainLength, len(self.prototypeChain)
+ )
+
+ self.hasOrdinaryObjectPrototype = desc.get("hasOrdinaryObjectPrototype", False)
+
+ def binaryNameFor(self, name, isStatic):
+ return self._binaryNames.get((name, isStatic), name)
+
+ @property
+ def prototypeNameChain(self):
+ return [self.getDescriptor(p).name for p in self.prototypeChain]
+
+ @property
+ def parentPrototypeName(self):
+ if len(self.prototypeChain) == 1:
+ return None
+ return self.getDescriptor(self.prototypeChain[-2]).name
+
+ def hasInterfaceOrInterfacePrototypeObject(self):
+ return (
+ self.interface.hasInterfaceObject()
+ or self.interface.hasInterfacePrototypeObject()
+ )
+
+ @property
+ def hasNamedPropertiesObject(self):
+ return self.isGlobal() and self.supportsNamedProperties()
+
+ def getExtendedAttributes(self, member, getter=False, setter=False):
+ def ensureValidBoolExtendedAttribute(attr, name):
+ if attr is not None and attr is not True:
+ raise TypeError("Unknown value for '%s': %s" % (name, attr[0]))
+
+ def ensureValidThrowsExtendedAttribute(attr):
+ ensureValidBoolExtendedAttribute(attr, "Throws")
+
+ def ensureValidCanOOMExtendedAttribute(attr):
+ ensureValidBoolExtendedAttribute(attr, "CanOOM")
+
+ def maybeAppendNeedsErrorResultToAttrs(attrs, throws):
+ ensureValidThrowsExtendedAttribute(throws)
+ if throws is not None:
+ attrs.append("needsErrorResult")
+
+ def maybeAppendCanOOMToAttrs(attrs, canOOM):
+ ensureValidCanOOMExtendedAttribute(canOOM)
+ if canOOM is not None:
+ attrs.append("canOOM")
+
+ def maybeAppendNeedsSubjectPrincipalToAttrs(attrs, needsSubjectPrincipal):
+ if (
+ needsSubjectPrincipal is not None
+ and needsSubjectPrincipal is not True
+ and needsSubjectPrincipal != ["NonSystem"]
+ ):
+ raise TypeError(
+ "Unknown value for 'NeedsSubjectPrincipal': %s"
+ % needsSubjectPrincipal[0]
+ )
+
+ if needsSubjectPrincipal is not None:
+ attrs.append("needsSubjectPrincipal")
+ if needsSubjectPrincipal == ["NonSystem"]:
+ attrs.append("needsNonSystemSubjectPrincipal")
+
+ name = member.identifier.name
+ throws = self.interface.isJSImplemented() or member.getExtendedAttribute(
+ "Throws"
+ )
+ canOOM = member.getExtendedAttribute("CanOOM")
+ needsSubjectPrincipal = member.getExtendedAttribute("NeedsSubjectPrincipal")
+ attrs = []
+ if name in self.implicitJSContext:
+ attrs.append("implicitJSContext")
+ if member.isMethod():
+ if self.interface.isAsyncIteratorInterface() and name == "next":
+ attrs.append("implicitJSContext")
+ # JSObject-returning [NewObject] methods must be fallible,
+ # since they have to (fallibly) allocate the new JSObject.
+ if member.getExtendedAttribute("NewObject"):
+ if member.returnsPromise():
+ throws = True
+ elif methodReturnsJSObject(member):
+ canOOM = True
+ maybeAppendNeedsErrorResultToAttrs(attrs, throws)
+ maybeAppendCanOOMToAttrs(attrs, canOOM)
+ maybeAppendNeedsSubjectPrincipalToAttrs(attrs, needsSubjectPrincipal)
+ return attrs
+
+ assert member.isAttr()
+ assert bool(getter) != bool(setter)
+ if throws is None:
+ throwsAttr = "GetterThrows" if getter else "SetterThrows"
+ throws = member.getExtendedAttribute(throwsAttr)
+ maybeAppendNeedsErrorResultToAttrs(attrs, throws)
+ if canOOM is None:
+ canOOMAttr = "GetterCanOOM" if getter else "SetterCanOOM"
+ canOOM = member.getExtendedAttribute(canOOMAttr)
+ maybeAppendCanOOMToAttrs(attrs, canOOM)
+ if needsSubjectPrincipal is None:
+ needsSubjectPrincipalAttr = (
+ "GetterNeedsSubjectPrincipal"
+ if getter
+ else "SetterNeedsSubjectPrincipal"
+ )
+ needsSubjectPrincipal = member.getExtendedAttribute(
+ needsSubjectPrincipalAttr
+ )
+ maybeAppendNeedsSubjectPrincipalToAttrs(attrs, needsSubjectPrincipal)
+ return attrs
+
+ def supportsIndexedProperties(self):
+ return self.operations["IndexedGetter"] is not None
+
+ def lengthNeedsCallerType(self):
+ """
+ Determine whether our length getter needs a caller type; this is needed
+ in some indexed-getter proxy algorithms. The idea is that if our
+ indexed getter needs a caller type, our automatically-generated Length()
+ calls need one too.
+ """
+ assert self.supportsIndexedProperties()
+ indexedGetter = self.operations["IndexedGetter"]
+ return indexedGetter.getExtendedAttribute("NeedsCallerType")
+
+ def supportsNamedProperties(self):
+ return self.operations["NamedGetter"] is not None
+
+ def supportedNamesNeedCallerType(self):
+ """
+ Determine whether our GetSupportedNames call needs a caller type. The
+ idea is that if your named getter needs a caller type, then so does
+ GetSupportedNames.
+ """
+ assert self.supportsNamedProperties()
+ namedGetter = self.operations["NamedGetter"]
+ return namedGetter.getExtendedAttribute("NeedsCallerType")
+
+ def isMaybeCrossOriginObject(self):
+ # If we're isGlobal and have cross-origin members, we're a Window, and
+ # that's not a cross-origin object. The WindowProxy is.
+ return (
+ self.concrete
+ and self.interface.hasCrossOriginMembers
+ and not self.isGlobal()
+ )
+
+ def needsHeaderInclude(self):
+ """
+ An interface doesn't need a header file if it is not concrete, not
+ pref-controlled, has no prototype object, has no static methods or
+ attributes and has no parent. The parent matters because we assert
+ things about refcounting that depend on the actual underlying type if we
+ have a parent.
+
+ """
+ return (
+ self.interface.isExternal()
+ or self.concrete
+ or self.interface.hasInterfacePrototypeObject()
+ or any(
+ (m.isAttr() or m.isMethod()) and m.isStatic()
+ for m in self.interface.members
+ )
+ or self.interface.parent
+ )
+
+ def hasThreadChecks(self):
+ # isExposedConditionally does not necessarily imply thread checks
+ # (since at least [SecureContext] is independent of them), but we're
+ # only used to decide whether to include nsThreadUtils.h, so we don't
+ # worry about that.
+ return (
+ self.isExposedConditionally() and not self.interface.isExposedInWindow()
+ ) or self.interface.isExposedInSomeButNotAllWorkers()
+
+ def hasCEReactions(self):
+ return any(
+ m.getExtendedAttribute("CEReactions") for m in self.interface.members
+ )
+
+ def isExposedConditionally(self):
+ return (
+ self.interface.isExposedConditionally()
+ or self.interface.isExposedInSomeButNotAllWorkers()
+ )
+
+ def needsXrayResolveHooks(self):
+ """
+ Generally, any interface with NeedResolve needs Xray
+ resolveOwnProperty and enumerateOwnProperties hooks. But for
+ the special case of plugin-loading elements, we do NOT want
+ those, because we don't want to instantiate plug-ins simply
+ due to chrome touching them and that's all those hooks do on
+ those elements. So we special-case those here.
+ """
+ return self.interface.getExtendedAttribute(
+ "NeedResolve"
+ ) and self.interface.identifier.name not in [
+ "HTMLObjectElement",
+ "HTMLEmbedElement",
+ ]
+
+ def needsXrayNamedDeleterHook(self):
+ return self.operations["NamedDeleter"] is not None
+
+ def isGlobal(self):
+ """
+ Returns true if this is the primary interface for a global object
+ of some sort.
+ """
+ return self.interface.getExtendedAttribute("Global")
+
+ @property
+ def namedPropertiesEnumerable(self):
+ """
+ Returns whether this interface should have enumerable named properties
+ """
+ assert self.proxy
+ assert self.supportsNamedProperties()
+ iface = self.interface
+ while iface:
+ if iface.getExtendedAttribute("LegacyUnenumerableNamedProperties"):
+ return False
+ iface = iface.parent
+ return True
+
+ @property
+ def registersGlobalNamesOnWindow(self):
+ return (
+ self.interface.hasInterfaceObject()
+ and self.interface.isExposedInWindow()
+ and self.register
+ )
+
+ def getDescriptor(self, interfaceName):
+ """
+ Gets the appropriate descriptor for the given interface name.
+ """
+ return self.config.getDescriptor(interfaceName)
+
+ def getConfig(self):
+ return self.config
+
+
+# Some utility methods
+def getTypesFromDescriptor(descriptor, includeArgs=True, includeReturns=True):
+ """
+ Get argument and/or return types for all members of the descriptor. By
+ default returns all argument types (which includes types of writable
+ attributes) and all return types (which includes types of all attributes).
+ """
+ assert includeArgs or includeReturns # Must want _something_.
+ members = [m for m in descriptor.interface.members]
+ if descriptor.interface.ctor():
+ members.append(descriptor.interface.ctor())
+ members.extend(descriptor.interface.legacyFactoryFunctions)
+ 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
+ if includeReturns:
+ types.append(returnType)
+ if includeArgs:
+ types.extend(a.type for a in arguments)
+
+ types.extend(
+ a.type
+ for a in members
+ if (a.isAttr() and (includeReturns or (includeArgs and not a.readonly)))
+ )
+
+ if descriptor.interface.maplikeOrSetlikeOrIterable:
+ maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
+ if maplikeOrSetlikeOrIterable.isMaplike():
+ # The things we expand into may or may not correctly indicate in
+ # their formal IDL types what things we have as return values. For
+ # example, "keys" returns the moral equivalent of sequence<keyType>
+ # but just claims to return "object". Similarly, "values" returns
+ # the moral equivalent of sequence<valueType> but claims to return
+ # "object". And due to bug 1155340, "get" claims to return "any"
+ # instead of the right type. So let's just manually work around
+ # that lack of specificity. For our arguments, we already enforce
+ # the right types at the IDL level, so those will get picked up
+ # correctly.
+ assert maplikeOrSetlikeOrIterable.hasKeyType()
+ assert maplikeOrSetlikeOrIterable.hasValueType()
+ if includeReturns:
+ types.append(maplikeOrSetlikeOrIterable.keyType)
+ types.append(maplikeOrSetlikeOrIterable.valueType)
+ elif maplikeOrSetlikeOrIterable.isSetlike():
+ assert maplikeOrSetlikeOrIterable.hasKeyType()
+ assert maplikeOrSetlikeOrIterable.hasValueType()
+ assert (
+ maplikeOrSetlikeOrIterable.keyType
+ == maplikeOrSetlikeOrIterable.valueType
+ )
+ # As in the maplike case, we don't always declare our return values
+ # quite correctly.
+ if includeReturns:
+ types.append(maplikeOrSetlikeOrIterable.keyType)
+ else:
+ assert (
+ maplikeOrSetlikeOrIterable.isIterable()
+ or maplikeOrSetlikeOrIterable.isAsyncIterable()
+ )
+ # As in the maplike/setlike cases we don't do a good job of
+ # declaring our actual return types, while our argument types, if
+ # any, are declared fine.
+ if includeReturns:
+ if maplikeOrSetlikeOrIterable.hasKeyType():
+ types.append(maplikeOrSetlikeOrIterable.keyType)
+ if maplikeOrSetlikeOrIterable.hasValueType():
+ types.append(maplikeOrSetlikeOrIterable.valueType)
+
+ return types
+
+
+def getTypesFromDictionary(dictionary):
+ """
+ Get all member types for this dictionary
+ """
+ return [m.type for m in dictionary.members]
+
+
+def getTypesFromCallback(callback):
+ """
+ Get the types this callback depends on: its return type and the
+ types of its arguments.
+ """
+ sig = callback.signatures()[0]
+ types = [sig[0]] # Return type
+ types.extend(arg.type for arg in sig[1]) # Arguments
+ return types
+
+
+def getAllTypes(descriptors, dictionaries, callbacks):
+ """
+ Generate all the types we're dealing with. For each type, a tuple
+ containing type, dictionary is yielded. The dictionary can be None if the
+ type does not come from a dictionary.
+ """
+ for d in descriptors:
+ if d.interface.isExternal():
+ continue
+ for t in getTypesFromDescriptor(d):
+ yield (t, None)
+ for dictionary in dictionaries:
+ for t in getTypesFromDictionary(dictionary):
+ yield (t, dictionary)
+ for callback in callbacks:
+ for t in getTypesFromCallback(callback):
+ yield (t, callback)
+
+
+# For sync value iterators, we use default array implementation, for async
+# iterators and sync pair iterators, we use AsyncIterableIterator or
+# IterableIterator instead.
+def iteratorNativeType(descriptor):
+ assert descriptor.interface.isIterable() or descriptor.interface.isAsyncIterable()
+ iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
+ assert iterableDecl.isPairIterator() or descriptor.interface.isAsyncIterable()
+ if descriptor.interface.isIterable():
+ return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType
+ needReturnMethod = toStringBool(
+ descriptor.interface.maplikeOrSetlikeOrIterable.getExtendedAttribute(
+ "GenerateReturnMethod"
+ )
+ is not None
+ )
+ return "mozilla::dom::binding_detail::AsyncIterableIteratorNative<%s, %s>" % (
+ descriptor.nativeType,
+ needReturnMethod,
+ )
+
+
+def findInnermostType(t):
+ """
+ Find the innermost type of the given type, unwrapping Promise and Record
+ types, as well as everything that unroll() unwraps.
+ """
+ while True:
+ if t.isRecord():
+ t = t.inner
+ elif t.unroll() != t:
+ t = t.unroll()
+ elif t.isPromise():
+ t = t.promiseInnerType()
+ else:
+ return t
+
+
+def getDependentDictionariesFromDictionary(d):
+ """
+ Find all the dictionaries contained in the given dictionary, as ancestors or
+ members. This returns a generator.
+ """
+ while d:
+ yield d
+ for member in d.members:
+ for next in getDictionariesFromType(member.type):
+ yield next
+ d = d.parent
+
+
+def getDictionariesFromType(type):
+ """
+ Find all the dictionaries contained in type. This can be used to find
+ dictionaries that need conversion to JS (by looking at types that get
+ converted to JS) or dictionaries that need conversion from JS (by looking at
+ types that get converted from JS).
+
+ This returns a generator.
+ """
+ type = findInnermostType(type)
+ if type.isUnion():
+ # Look for dictionaries in all the member types
+ for t in type.flatMemberTypes:
+ for next in getDictionariesFromType(t):
+ yield next
+ elif type.isDictionary():
+ # Find the dictionaries that are itself, any of its ancestors, or
+ # contained in any of its member types.
+ for d in getDependentDictionariesFromDictionary(type.inner):
+ yield d
+
+
+def getDictionariesConvertedToJS(descriptors, dictionaries, callbacks):
+ for desc in descriptors:
+ if desc.interface.isExternal():
+ continue
+
+ if desc.interface.isJSImplemented():
+ # For a JS-implemented interface, we need to-JS
+ # conversions for all the types involved.
+ for t in getTypesFromDescriptor(desc):
+ for d in getDictionariesFromType(t):
+ yield d
+ elif desc.interface.isCallback():
+ # For callbacks we only want to include the arguments, since that's
+ # where the to-JS conversion happens.
+ for t in getTypesFromDescriptor(desc, includeReturns=False):
+ for d in getDictionariesFromType(t):
+ yield d
+ else:
+ # For normal interfaces, we only want to include return values,
+ # since that's where to-JS conversion happens.
+ for t in getTypesFromDescriptor(desc, includeArgs=False):
+ for d in getDictionariesFromType(t):
+ yield d
+
+ for callback in callbacks:
+ # We only want to look at the arguments
+ sig = callback.signatures()[0]
+ for arg in sig[1]:
+ for d in getDictionariesFromType(arg.type):
+ yield d
+
+ for dictionary in dictionaries:
+ if dictionary.needsConversionToJS:
+ # It's explicitly flagged as needing to-JS conversion, and all its
+ # dependent dictionaries will need to-JS conversion too.
+ for d in getDependentDictionariesFromDictionary(dictionary):
+ yield d
+
+
+def getDictionariesConvertedFromJS(descriptors, dictionaries, callbacks):
+ for desc in descriptors:
+ if desc.interface.isExternal():
+ continue
+
+ if desc.interface.isJSImplemented():
+ # For a JS-implemented interface, we need from-JS conversions for
+ # all the types involved.
+ for t in getTypesFromDescriptor(desc):
+ for d in getDictionariesFromType(t):
+ yield d
+ elif desc.interface.isCallback():
+ # For callbacks we only want to include the return value, since
+ # that's where teh from-JS conversion happens.
+ for t in getTypesFromDescriptor(desc, includeArgs=False):
+ for d in getDictionariesFromType(t):
+ yield d
+ else:
+ # For normal interfaces, we only want to include arguments values,
+ # since that's where from-JS conversion happens.
+ for t in getTypesFromDescriptor(desc, includeReturns=False):
+ for d in getDictionariesFromType(t):
+ yield d
+
+ for callback in callbacks:
+ # We only want to look at the return value
+ sig = callback.signatures()[0]
+ for d in getDictionariesFromType(sig[0]):
+ yield d
+
+ for dictionary in dictionaries:
+ if dictionary.needsConversionFromJS:
+ # It's explicitly flagged as needing from-JS conversion, and all its
+ # dependent dictionaries will need from-JS conversion too.
+ for d in getDependentDictionariesFromDictionary(dictionary):
+ yield d