| # 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 https://mozilla.org/MPL/2.0/. |
| |
| # fmt: off |
| |
| from __future__ import annotations |
| |
| import functools |
| import os |
| from typing import Any, TypeVar |
| |
| from WebIDL import ( |
| IDLExternalInterface, |
| IDLSequenceType, |
| IDLWrapperType, |
| WebIDLError, |
| IDLEnum, |
| IDLObject, |
| IDLObjectWithIdentifier, |
| IDLType, |
| IDLTypedef, |
| IDLInterfaceOrNamespace, |
| IDLDictionary, |
| IDLCallback, |
| IDLAttribute, |
| IDLMethod, |
| IDLInterfaceMember, |
| ) |
| |
| TargetType = TypeVar('TargetType') |
| def assert_type(object: Any, cls: type[TargetType]) -> TargetType: |
| assert isinstance(object, cls) |
| return object |
| |
| FilterItemsType = TypeVar('FilterItemsType', bound=IDLObjectWithIdentifier) |
| class Configuration: |
| """ |
| Represents global configuration state based on IDL parse data and |
| the configuration file. |
| """ |
| glbl: dict[str, Any] |
| enumConfig: dict[str, Any] |
| dictConfig: dict[str, Any] |
| unionConfig: dict[str, Any] |
| descriptors: list[Descriptor] |
| interfaces: dict[str, IDLInterfaceOrNamespace] |
| enums: list[IDLEnum] |
| typedefs: list[IDLTypedef] |
| dictionaries: list[IDLDictionary] |
| callbacks: list[IDLCallback] |
| |
| def __init__(self, filename: str, parseData: list[IDLObjectWithIdentifier]) -> None: |
| # Read the configuration file. |
| glbl = {} |
| exec(compile(open(filename).read(), filename, 'exec'), glbl) |
| config = glbl['DOMInterfaces'] |
| self.enumConfig = glbl['Enums'] |
| self.dictConfig = glbl['Dictionaries'] |
| self.unionConfig = glbl['Unions'] |
| |
| # 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.maxProtoChainLength = 0 |
| for thing in parseData: |
| # Servo does not support external interfaces. |
| if isinstance(thing, IDLExternalInterface): |
| raise WebIDLError("Servo does not support external interfaces.", |
| [thing.location]) |
| |
| assert not thing.isType() |
| |
| if not thing.isInterface() and not thing.isNamespace(): |
| continue |
| |
| iface = thing |
| assert isinstance(iface, IDLInterfaceOrNamespace) |
| self.interfaces[iface.identifier.name] = iface |
| if iface.identifier.name not in config: |
| entry = {} |
| else: |
| entry = config[iface.identifier.name] |
| if not isinstance(entry, list): |
| assert isinstance(entry, dict) |
| entry = [entry] |
| self.descriptors.extend( |
| [Descriptor(self, iface, x) for x in entry]) |
| |
| # Mark the descriptors for which only a single nativeType implements |
| # an interface. |
| for descriptor in self.descriptors: |
| interfaceName = descriptor.interface.identifier.name |
| otherDescriptors = [d for d in self.descriptors |
| if d.interface.identifier.name == interfaceName] |
| descriptor.uniqueImplementation = len(otherDescriptors) == 1 |
| |
| self.enums = [assert_type(e, IDLEnum) for e in parseData if e.isEnum()] |
| self.typedefs = [assert_type(e, IDLTypedef) for e in parseData if e.isTypedef()] |
| self.dictionaries = [assert_type(d, IDLDictionary) for d in parseData if d.isDictionary()] |
| self.callbacks = [assert_type(c, IDLCallback) for c in parseData if |
| c.isCallback() and not c.isInterface()] |
| |
| # Keep the descriptor list sorted for determinism. |
| def cmp(x: str, y: str) -> int: |
| return (x > y) - (x < y) |
| self.descriptors.sort(key=functools.cmp_to_key(lambda x, y: cmp(x.name, y.name))) |
| |
| def getInterface(self, ifname: str) -> IDLInterfaceOrNamespace: |
| return self.interfaces[ifname] |
| |
| def getDescriptors(self, **filters: Any) -> list[Descriptor]: |
| """Gets the descriptors that match the given filters.""" |
| curr = self.descriptors |
| for key, val in filters.items(): |
| if key == 'webIDLFile': |
| def getter(x: Descriptor) -> str: |
| return x.interface.location.filename |
| elif key == 'hasInterfaceObject': |
| def getter(x: Descriptor) -> bool: |
| return x.interface.hasInterfaceObject() |
| elif key == 'isCallback': |
| def getter(x: Descriptor) -> bool: |
| return x.interface.isCallback() |
| elif key == 'isNamespace': |
| def getter(x: Descriptor) -> bool: |
| return x.interface.isNamespace() |
| elif key == 'isJSImplemented': |
| def getter(x: Descriptor) -> bool: |
| return x.interface.isJSImplemented() |
| elif key == 'isGlobal': |
| def getter(x: Descriptor) -> bool: |
| return x.isGlobal() |
| elif key == 'isInline': |
| def getter(x: Descriptor) -> bool: |
| return x.interface.getExtendedAttribute('Inline') is not None |
| elif key == 'isExposedConditionally': |
| def getter(x: Descriptor) -> bool: |
| return x.interface.isExposedConditionally() |
| elif key == 'isIteratorInterface': |
| def getter(x: Descriptor) -> bool: |
| return x.interface.isIteratorInterface() |
| else: |
| def getter(x: Descriptor) -> Any: |
| return getattr(x, key) |
| curr = [x for x in curr if getter(x) == val] |
| return curr |
| |
| def getEnums(self, webIDLFile: str) -> list[IDLEnum]: |
| return [e for e in self.enums if e.filename == webIDLFile] |
| |
| def getEnumConfig(self, name: str) -> dict[str, Any]: |
| return self.enumConfig.get(name, {}) |
| |
| def getTypedefs(self, webIDLFile: str) -> list[IDLTypedef]: |
| return [e for e in self.typedefs if e.filename == webIDLFile] |
| |
| @staticmethod |
| def _filterForFile(items: list[FilterItemsType], webIDLFile: str = "") -> list[FilterItemsType]: |
| """Gets the items that match the given filters.""" |
| if not webIDLFile: |
| return items |
| |
| return [x for x in items if x.filename == webIDLFile] |
| |
| def getUnionConfig(self, name: str) -> dict[str, Any]: |
| return self.unionConfig.get(name, {}) |
| |
| def getDictionaries(self, webIDLFile: str = "") -> list[IDLDictionary]: |
| return self._filterForFile(self.dictionaries, webIDLFile=webIDLFile) |
| |
| def getDictConfig(self, name: str) -> dict[str, Any]: |
| return self.dictConfig.get(name, {}) |
| |
| def getCallbacks(self, webIDLFile: str = "") -> list[IDLCallback]: |
| return self._filterForFile(self.callbacks, webIDLFile=webIDLFile) |
| |
| def getDescriptor(self, interfaceName: str) -> Descriptor: |
| """ |
| Gets the appropriate descriptor for the given interface name. |
| """ |
| iface = self.getInterface(interfaceName) |
| descriptors = self.getDescriptors(interface=iface) |
| |
| # We should have exactly one result. |
| if len(descriptors) != 1: |
| raise NoSuchDescriptorError("For " + interfaceName + " found " |
| + str(len(descriptors)) + " matches") |
| return descriptors[0] |
| |
| def getDescriptorProvider(self) -> DescriptorProvider: |
| """ |
| Gets a descriptor provider that can provide descriptors as needed. |
| """ |
| return DescriptorProvider(self) |
| |
| |
| class NoSuchDescriptorError(TypeError): |
| def __init__(self, str: str) -> None: |
| TypeError.__init__(self, str) |
| |
| |
| class DescriptorProvider: |
| """ |
| A way of getting descriptors for interface names |
| """ |
| def __init__(self, config: Configuration) -> None: |
| self.config = config |
| |
| def getDescriptor(self, interfaceName: str) -> Descriptor: |
| """ |
| Gets the appropriate descriptor for the given interface name given the |
| context of the current descriptor. |
| """ |
| return self.config.getDescriptor(interfaceName) |
| |
| |
| def MemberIsLegacyUnforgeable(member: IDLAttribute | IDLMethod, descriptor: Descriptor) -> bool: |
| return ((member.isAttr() or member.isMethod()) |
| and not member.isStatic() |
| and (member.isLegacyUnforgeable() |
| or bool(descriptor.interface.getExtendedAttribute("LegacyUnforgeable")))) |
| |
| |
| class Descriptor(DescriptorProvider): |
| """ |
| Represents a single descriptor for an interface. See Bindings.conf. |
| """ |
| interface: IDLInterfaceOrNamespace |
| uniqueImplementation: bool |
| |
| def __init__(self, config: Configuration, interface: IDLInterfaceOrNamespace, desc: dict[str, Any]) -> None: |
| DescriptorProvider.__init__(self, config) |
| self.interface = interface |
| |
| if not self.isExposedConditionally(): |
| if interface.parent and interface.parent.isExposedConditionally(): |
| raise TypeError("%s is not conditionally exposed but inherits from " |
| "%s which is" % |
| (interface.identifier.name, interface.parent.identifier.name)) |
| |
| # Read the desc, and fill in the relevant defaults. |
| ifaceName = self.interface.identifier.name |
| nativeTypeDefault = ifaceName |
| |
| # 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. |
| prefix = "D::" |
| if self.interface.isIteratorInterface(): |
| assert self.interface.iterableInterface is not None |
| itrName = self.interface.iterableInterface.identifier.name |
| itrDesc = self.getDescriptor(itrName) |
| nativeTypeDefault = iteratorNativeType(itrDesc) |
| prefix = "" |
| |
| typeName = desc.get('nativeType', nativeTypeDefault) |
| |
| spiderMonkeyInterface = desc.get('spiderMonkeyInterface', False) |
| |
| # Callback and SpiderMonkey types do not use JS smart pointers, so we should not use the |
| # built-in rooting mechanisms for them. |
| if spiderMonkeyInterface: |
| self.returnType = 'Rc<%s>' % typeName |
| self.argumentType = '&%s' % typeName |
| self.nativeType = typeName |
| pathDefault = 'crate::dom::types::%s' % typeName |
| elif self.interface.isCallback(): |
| ty = 'crate::codegen::GenericBindings::%sBinding::%s' % (ifaceName, ifaceName) |
| pathDefault = ty |
| self.returnType = "Rc<%s<D>>" % ty |
| self.argumentType = "???" |
| self.nativeType = ty |
| else: |
| self.returnType = "DomRoot<%s%s>" % (prefix, typeName) |
| self.argumentType = "&%s%s" % (prefix, typeName) |
| self.nativeType = "*const %s%s" % (prefix, typeName) |
| if self.interface.isIteratorInterface(): |
| pathDefault = 'crate::iterable::IterableIterator' |
| else: |
| pathDefault = 'crate::dom::types::%s' % MakeNativeName(typeName) |
| |
| self.concreteType = "%s%s" % (prefix, typeName) |
| self.register = desc.get('register', True) |
| self.path = desc.get('path', pathDefault) |
| self.inRealmMethods = [name for name in desc.get('inRealms', [])] |
| self.canGcMethods = [name for name in desc.get('canGc', [])] |
| self.additionalTraits = [name for name in desc.get('additionalTraits', [])] |
| self.bindingPath = f"{getModuleFromObject(self.interface)}::{ifaceName}_Binding" |
| self.outerObjectHook = desc.get('outerObjectHook', 'None') |
| self.proxy = False |
| self.weakReferenceable = desc.get('weakReferenceable', False) |
| self.useSystemCompartment = desc.get('useSystemCompartment', False) |
| |
| # If we're concrete, we need to crawl our ancestor interfaces and mark |
| # them as having a concrete descendant. |
| self.concrete = (not self.interface.isCallback() |
| and not self.interface.isNamespace() |
| and not self.interface.getExtendedAttribute("Abstract") |
| and not self.interface.getExtendedAttribute("Inline") |
| and not spiderMonkeyInterface) |
| self.hasLegacyUnforgeableMembers = (self.concrete |
| and any(MemberIsLegacyUnforgeable(m, self) for m in |
| self.interface.members)) |
| |
| self.operations: dict[str, IDLMethod | None] = { |
| 'IndexedGetter': None, |
| 'IndexedSetter': None, |
| 'IndexedDeleter': None, |
| 'NamedGetter': None, |
| 'NamedSetter': None, |
| 'NamedDeleter': None, |
| 'Stringifier': None, |
| } |
| |
| self.hasDefaultToJSON = False |
| |
| def addOperation(operation: str, m: IDLMethod) -> None: |
| 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. |
| for m in self.interface.members: |
| if m.isMethod() and m.isStringifier(): |
| addOperation('Stringifier', m) |
| if m.isMethod() and m.isDefaultToJSON(): |
| self.hasDefaultToJSON = True |
| |
| if self.concrete: |
| iface: IDLInterfaceOrNamespace | None = self.interface |
| while iface: |
| for m in iface.members: |
| if not m.isMethod(): |
| continue |
| |
| def addIndexedOrNamedOperation(operation: str, m: IDLMethod) -> None: |
| if not self.isGlobal(): |
| self.proxy = True |
| 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) |
| |
| iface = iface.parent |
| if iface: |
| iface.setUserData('hasConcreteDescendant', True) |
| |
| if self.isMaybeCrossOriginObject(): |
| self.proxy = True |
| |
| if self.proxy: |
| iface = self.interface |
| while iface.parent: |
| iface = iface.parent |
| iface.setUserData('hasProxyDescendant', True) |
| |
| self.name = interface.identifier.name |
| |
| # self.extendedAttributes is a dict of dicts, keyed on |
| # all/getterOnly/setterOnly and then on member name. Values are an |
| # array of extended attributes. |
| self.extendedAttributes = {'all': {}, 'getterOnly': {}, 'setterOnly': {}} |
| |
| def addExtendedAttribute(attribute: dict[str, Any], config: Configuration) -> None: |
| def add(key: str, members: list[str], attribute: dict[str, Any]) -> None: |
| for member in members: |
| self.extendedAttributes[key].setdefault(member, []).append(attribute) |
| |
| if isinstance(config, dict): |
| for key in ['all', 'getterOnly', 'setterOnly']: |
| add(key, config.get(key, []), attribute) |
| elif isinstance(config, list): |
| add('all', config, attribute) |
| else: |
| assert isinstance(config, str) |
| if config == '*': |
| iface: IDLInterfaceOrNamespace | None = self.interface |
| while iface: |
| add('all', [m.name for m in iface.members], attribute) |
| iface = iface.parent |
| else: |
| add('all', [config], attribute) |
| |
| self._binaryNames = desc.get('binaryNames', {}) |
| self._binaryNames.setdefault(('__legacycaller', False), 'LegacyCall') |
| self._binaryNames.setdefault(('__stringifier', False), 'Stringifier') |
| |
| self._internalNames = desc.get('internalNames', {}) |
| |
| for member in self.interface.members: |
| if not member.isAttr() and not member.isMethod(): |
| continue |
| binaryName = member.getExtendedAttribute("BinaryName") |
| if binaryName: |
| assert isinstance(binaryName, list) |
| assert len(binaryName) == 1 |
| self._binaryNames.setdefault((member.identifier.name, member.isStatic()), |
| binaryName[0]) |
| self._internalNames.setdefault(member.identifier.name, |
| member.identifier.name.replace('-', '_')) |
| |
| # Build the prototype chain. |
| self.prototypeChain = [] |
| parent: IDLInterfaceOrNamespace | None = interface |
| while parent: |
| self.prototypeChain.insert(0, parent.identifier.name) |
| parent = parent.parent |
| self.prototypeDepth = len(self.prototypeChain) - 1 |
| config.maxProtoChainLength = max(config.maxProtoChainLength, |
| len(self.prototypeChain)) |
| |
| def maybeGetSuperModule(self) -> str | None: |
| """ |
| Returns name of super module if self is part of it |
| """ |
| filename = getIdlFileName(self.interface) |
| # if interface name is not same as webidl file |
| # webidl is super module for interface |
| if filename.lower() != self.interface.identifier.name.lower(): |
| return filename |
| return None |
| |
| def binaryNameFor(self, name: str, isStatic: bool) -> str: |
| return self._binaryNames.get((name, isStatic), name) |
| |
| def internalNameFor(self, name: str) -> str: |
| return self._internalNames.get(name, name) |
| |
| def hasNamedPropertiesObject(self) -> bool: |
| if self.interface.isExternal(): |
| return False |
| |
| return self.isGlobal() and self.supportsNamedProperties() |
| |
| def supportsNamedProperties(self) -> bool: |
| return self.operations['NamedGetter'] is not None |
| |
| def getExtendedAttributes(self, member: IDLInterfaceMember, getter: bool = False, setter: bool = False) -> list[str]: |
| def maybeAppendInfallibleToAttrs(attrs: list[str], throws: bool | None) -> None: |
| if throws is None: |
| attrs.append("infallible") |
| elif throws is True: |
| pass |
| else: |
| raise TypeError("Unknown value for 'Throws'") |
| |
| name = member.identifier.name |
| if member.isMethod(): |
| attrs = self.extendedAttributes['all'].get(name, []) |
| throws = member.getExtendedAttribute("Throws") |
| maybeAppendInfallibleToAttrs(attrs, throws) |
| return attrs |
| |
| assert member.isAttr() |
| assert bool(getter) != bool(setter) |
| key = 'getterOnly' if getter else 'setterOnly' |
| attrs = self.extendedAttributes['all'].get(name, []) + self.extendedAttributes[key].get(name, []) |
| throws = member.getExtendedAttribute("Throws") |
| if throws is None: |
| throwsAttr = "GetterThrows" if getter else "SetterThrows" |
| throws = member.getExtendedAttribute(throwsAttr) |
| maybeAppendInfallibleToAttrs(attrs, throws) |
| return attrs |
| |
| def getParentName(self) -> str | None: |
| parent = self.interface.parent |
| while parent: |
| if not parent.getExtendedAttribute("Inline"): |
| return parent.identifier.name |
| parent = parent.parent |
| return None |
| |
| def supportsIndexedProperties(self) -> bool: |
| return self.operations['IndexedGetter'] is not None |
| |
| def isMaybeCrossOriginObject(self) -> bool: |
| # 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 hasDescendants(self) -> bool: |
| return (self.interface.getUserData("hasConcreteDescendant", False) |
| or self.interface.getUserData("hasProxyDescendant", False) or False) |
| |
| def hasHTMLConstructor(self) -> bool: |
| ctor = self.interface.ctor() |
| return (ctor and ctor.isHTMLConstructor()) or False |
| |
| def shouldHaveGetConstructorObjectMethod(self) -> bool: |
| assert self.interface.hasInterfaceObject() |
| if self.interface.getExtendedAttribute("Inline"): |
| return False |
| return (self.interface.isCallback() or self.interface.isNamespace() |
| or self.hasDescendants() or self.hasHTMLConstructor() or False) |
| |
| def shouldCacheConstructor(self) -> bool: |
| return self.hasDescendants() or self.hasHTMLConstructor() |
| |
| def isExposedConditionally(self) -> bool: |
| return self.interface.isExposedConditionally() |
| |
| def isGlobal(self) -> bool: |
| """ |
| Returns true if this is the primary interface for a global object |
| of some sort. |
| """ |
| return bool(self.interface.getExtendedAttribute("Global") |
| or self.interface.getExtendedAttribute("PrimaryGlobal")) |
| |
| |
| # Some utility methods |
| |
| |
| def MakeNativeName(name: str) -> str: |
| return name[0].upper() + name[1:] |
| |
| |
| def getIdlFileName(object: IDLObject) -> str: |
| return os.path.basename(object.location.filename).split('.webidl')[0] |
| |
| |
| def getModuleFromObject(object: IDLObject) -> str: |
| return ('crate::codegen::GenericBindings::' + getIdlFileName(object) + 'Binding') |
| |
| |
| def getTypesFromDescriptor(descriptor: Descriptor) -> list[IDLType]: |
| """ |
| Get all argument and return types for all members of the descriptor |
| """ |
| members = [m for m in descriptor.interface.members] |
| if descriptor.interface.ctor(): |
| members.append(descriptor.interface.ctor()) |
| members.extend(descriptor.interface.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 |
| types.append(returnType) |
| types.extend(a.type for a in arguments) |
| |
| types.extend(a.type for a in members if a.isAttr()) |
| return types |
| |
| |
| def getTypesFromDictionary(dictionary: IDLWrapperType | IDLDictionary) -> list[IDLType]: |
| """ |
| Get all member types for this dictionary |
| """ |
| if isinstance(dictionary, IDLWrapperType): |
| dictionary = dictionary.inner |
| types = [] |
| curDict = dictionary |
| while curDict: |
| types.extend([getUnwrappedType(m.type) for m in curDict.members]) |
| curDict = curDict.parent |
| return types |
| |
| |
| def getTypesFromCallback(callback: IDLCallback) -> list[IDLType]: |
| """ |
| 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 getUnwrappedType(type: IDLType) -> IDLType: |
| while isinstance(type, IDLSequenceType): |
| type = type.inner |
| return type |
| |
| |
| def iteratorNativeType(descriptor: Descriptor, infer: bool = False) -> str: |
| assert descriptor.interface.maplikeOrSetlikeOrIterable is not None |
| iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable |
| assert (iterableDecl.isIterable() and iterableDecl.isPairIterator()) \ |
| or iterableDecl.isSetlike() or iterableDecl.isMaplike() |
| res = "IterableIterator%s" % ("" if infer else '<D, D::%s>' % descriptor.interface.identifier.name) |
| # todo: this hack is telling us that something is still wrong in codegen |
| if iterableDecl.isSetlike() or iterableDecl.isMaplike(): |
| res = f"crate::iterable::{res}" |
| return res |