diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/bindings/Configuration.py | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/bindings/Configuration.py')
-rw-r--r-- | dom/bindings/Configuration.py | 1454 |
1 files changed, 1454 insertions, 0 deletions
diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py new file mode 100644 index 0000000000..266a8db34a --- /dev/null +++ b/dom/bindings/Configuration.py @@ -0,0 +1,1454 @@ +# 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"] + + class IDLAttrGetterOrSetterTemplate: + def __init__(self, template, getter, setter, argument, attrName): + class TemplateAdditionalArg: + def __init__(self, type, name, value=None): + self.type = type + self.name = name + self.value = value + + self.descriptor = None + self.usedInOtherInterfaces = False + self.getter = getter + self.setter = setter + self.argument = TemplateAdditionalArg(*argument) + self.attrNameString = attrName + self.attr = None + + self.attributeTemplates = dict() + attributeTemplatesByInterface = dict() + for interface, templates in glbl["TemplatedAttributes"].items(): + for template in templates: + name = template.get("template") + t = IDLAttrGetterOrSetterTemplate(**template) + self.attributeTemplates[name] = t + attributeTemplatesByInterface.setdefault(interface, list()).append(t) + + 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.pop(iface.identifier.name, {}) + assert not isinstance(entry, list) + + desc = Descriptor( + self, + iface, + entry, + attributeTemplatesByInterface.get(iface.identifier.name), + ) + 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 + + if len(config) > 0: + raise NoSuchDescriptorError( + "Bindings.conf contains entries for " + + str(list(config)) + + " that aren't declared as interfaces in the .webidl files." + ) + + # 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)) + + allTemplatedAttributes = ( + (m, d) + for d in self.descriptors + if not d.interface.isExternal() + for m in d.interface.members + if m.isAttr() and m.getExtendedAttribute("BindingTemplate") is not None + ) + # attributesPerTemplate will have the template names as keys, and a + # list of tuples as values. Every tuple contains an IDLAttribute and a + # descriptor. + attributesPerTemplate = dict() + for m, d in allTemplatedAttributes: + t = m.getExtendedAttribute("BindingTemplate") + if isinstance(t[0], list): + t = t[0] + l = attributesPerTemplate.setdefault(t[0], list()) + # We want the readonly attributes last, because we use the first + # attribute in the list as the canonical attribute for the + # template, and if there are any writable attributes the + # template should have support for that. + if not m.readonly: + l.insert(0, (m, d)) + else: + l.append((m, d)) + + for name, attributes in attributesPerTemplate.items(): + # We use the first attribute to generate a canonical implementation + # of getter and setter. + firstAttribute, firstDescriptor = attributes[0] + template = self.attributeTemplates.get(name) + if template is None: + raise TypeError( + "Unknown BindingTemplate with name %s for %s on %s" + % ( + name, + firstAttribute.identifier.name, + firstDescriptor.interface.identifier.name, + ) + ) + + # This mimics a real IDL attribute for templated bindings. + class TemplateIDLAttribute: + def __init__(self, attr): + assert attr.isAttr() + assert not attr.isMaplikeOrSetlikeAttr() + assert not attr.slotIndices + + self.identifier = attr.identifier + self.type = attr.type + self.extendedAttributes = attr.getExtendedAttributes() + self.slotIndices = None + + def getExtendedAttribute(self, name): + return self.extendedAttributes.get(name) + + def isAttr(self): + return True + + def isMaplikeOrSetlikeAttr(self): + return False + + def isMethod(self): + return False + + def isStatic(self): + return False + + template.attr = TemplateIDLAttribute(firstAttribute) + + def filterExtendedAttributes(extendedAttributes): + # These are the extended attributes that we allow to have + # different values among all attributes that use the same + # template. + ignoredAttributes = { + "BindingTemplate", + "BindingAlias", + "ChromeOnly", + "Pure", + "Pref", + "Func", + "Throws", + "GetterThrows", + "SetterThrows", + } + return dict( + filter( + lambda i: i[0] not in ignoredAttributes, + extendedAttributes.items(), + ) + ) + + firstExtAttrs = filterExtendedAttributes( + firstAttribute.getExtendedAttributes() + ) + + for a, d in attributes: + # We want to make sure all getters or setters grouped by a + # template have the same WebIDL signatures, so make sure + # their types are the same. + if template.attr.type != a.type: + raise TypeError( + "%s on %s and %s on %s have different type, but they're using the same template %s." + % ( + firstAttribute.identifier.name, + firstDescriptor.interface.identifier.name, + a.identifier.name, + d.interface.identifier.name, + name, + ) + ) + + extAttrs = filterExtendedAttributes(a.getExtendedAttributes()) + if template.attr.extendedAttributes != extAttrs: + for k in extAttrs.keys() - firstExtAttrs.keys(): + raise TypeError( + "%s on %s has extended attribute %s and %s on %s does not, but they're using the same template %s." + % ( + a.identifier.name, + d.interface.identifier.name, + k, + firstAttribute.identifier.name, + firstDescriptor.interface.identifier.name, + name, + ) + ) + for k in firstExtAttrs.keys() - extAttrs.keys(): + raise TypeError( + "%s on %s has extended attribute %s and %s on %s does not, but they're using the same template %s." + % ( + firstAttribute.identifier.name, + firstDescriptor.interface.identifier.name, + k, + a.identifier.name, + d.interface.identifier.name, + name, + ) + ) + for k, v in firstExtAttrs.items(): + if extAttrs[k] != v: + raise TypeError( + "%s on %s and %s on %s have different values for extended attribute %s, but they're using the same template %s." + % ( + firstAttribute.identifier.name, + firstDescriptor.interface.identifier.name, + a.identifier.name, + d.interface.identifier.name, + k, + name, + ) + ) + + def sameThrows(getter=False, setter=False): + extAttrs1 = firstDescriptor.getExtendedAttributes( + firstAttribute, getter=getter, setter=setter + ) + extAttrs2 = d.getExtendedAttributes(a, getter=getter, setter=setter) + return ("needsErrorResult" in extAttrs1) == ( + "needsErrorResult" in extAttrs2 + ) + + if not sameThrows(getter=True) or ( + not a.readonly and not sameThrows(setter=True) + ): + raise TypeError( + "%s on %s and %s on %s have different annotations about throwing, but they're using the same template %s." + % ( + firstAttribute.identifier.name, + firstDescriptor.interface.identifier.name, + a.identifier.name, + d.interface.identifier.name, + name, + ) + ) + + for name, template in self.attributeTemplates.items(): + if template.attr is None: + print("Template %s is unused, please remove it." % name) + + 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, attributeTemplates): + DescriptorProvider.__init__(self) + self.config = config + self.interface = interface + self.attributeTemplates = attributeTemplates + if self.attributeTemplates is not None: + for t in self.attributeTemplates: + t.descriptor = self + + 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 |