aboutsummaryrefslogtreecommitdiffstats
path: root/components/script_bindings/codegen/Configuration.py
diff options
context:
space:
mode:
authorJosh Matthews <josh@joshmatthews.net>2025-01-24 15:47:43 -0500
committerGitHub <noreply@github.com>2025-01-24 20:47:43 +0000
commitaf8d7c2de7dc5d2f844a021b97babfe4e4f839d4 (patch)
treed2a003abac01b1a2266732fa7b89c9d8e1601e88 /components/script_bindings/codegen/Configuration.py
parenta88b59534ff1d064acf76af1535c3d6847817826 (diff)
downloadservo-af8d7c2de7dc5d2f844a021b97babfe4e4f839d4.tar.gz
servo-af8d7c2de7dc5d2f844a021b97babfe4e4f839d4.zip
script: Move code generation and webidl files to new script_bindings crate. (#35157)
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
Diffstat (limited to 'components/script_bindings/codegen/Configuration.py')
-rw-r--r--components/script_bindings/codegen/Configuration.py553
1 files changed, 553 insertions, 0 deletions
diff --git a/components/script_bindings/codegen/Configuration.py b/components/script_bindings/codegen/Configuration.py
new file mode 100644
index 00000000000..07822eac1b3
--- /dev/null
+++ b/components/script_bindings/codegen/Configuration.py
@@ -0,0 +1,553 @@
+# 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/.
+
+import functools
+import os
+
+from WebIDL import IDLExternalInterface, IDLSequenceType, IDLWrapperType, WebIDLError
+
+
+class Configuration:
+ """
+ Represents global configuration state based on IDL parse data and
+ the configuration file.
+ """
+ def __init__(self, filename, parseData):
+ # 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
+ 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 = [e for e in parseData if e.isEnum()]
+ self.typedefs = [e for e in parseData if e.isTypedef()]
+ self.dictionaries = [d for d in parseData if d.isDictionary()]
+ self.callbacks = [c for c in parseData if
+ c.isCallback() and not c.isInterface()]
+
+ # Keep the descriptor list sorted for determinism.
+ def cmp(x, y):
+ 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):
+ return self.interfaces[ifname]
+
+ def getDescriptors(self, **filters):
+ """Gets the descriptors that match the given filters."""
+ curr = self.descriptors
+ for key, val in filters.items():
+ if key == 'webIDLFile':
+ def getter(x):
+ return x.interface.location.filename
+ elif key == 'hasInterfaceObject':
+ def getter(x):
+ return x.interface.hasInterfaceObject()
+ elif key == 'isCallback':
+ def getter(x):
+ return x.interface.isCallback()
+ elif key == 'isNamespace':
+ def getter(x):
+ return x.interface.isNamespace()
+ elif key == 'isJSImplemented':
+ def getter(x):
+ return x.interface.isJSImplemented()
+ elif key == 'isGlobal':
+ def getter(x):
+ return x.isGlobal()
+ elif key == 'isInline':
+ def getter(x):
+ return x.interface.getExtendedAttribute('Inline') is not None
+ elif key == 'isExposedConditionally':
+ def getter(x):
+ return x.interface.isExposedConditionally()
+ elif key == 'isIteratorInterface':
+ def getter(x):
+ return x.interface.isIteratorInterface()
+ else:
+ def getter(x):
+ return getattr(x, key)
+ curr = [x for x in curr if getter(x) == val]
+ return curr
+
+ def getEnums(self, webIDLFile):
+ return [e for e in self.enums if e.filename == webIDLFile]
+
+ def getEnumConfig(self, name):
+ return self.enumConfig.get(name, {})
+
+ def getTypedefs(self, webIDLFile):
+ return [e for e in self.typedefs if e.filename == webIDLFile]
+
+ @staticmethod
+ def _filterForFile(items, webIDLFile=""):
+ """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):
+ return self.unionConfig.get(name, {})
+
+ def getDictionaries(self, webIDLFile=""):
+ return self._filterForFile(self.dictionaries, webIDLFile=webIDLFile)
+
+ def getDictConfig(self, name):
+ return self.dictConfig.get(name, {})
+
+ def getCallbacks(self, webIDLFile=""):
+ return self._filterForFile(self.callbacks, webIDLFile=webIDLFile)
+
+ def getDescriptor(self, interfaceName):
+ """
+ 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):
+ """
+ Gets a descriptor provider that can provide descriptors as needed.
+ """
+ return DescriptorProvider(self)
+
+
+class NoSuchDescriptorError(TypeError):
+ def __init__(self, str):
+ TypeError.__init__(self, str)
+
+
+class DescriptorProvider:
+ """
+ A way of getting descriptors for interface names
+ """
+ def __init__(self, config):
+ self.config = config
+
+ def getDescriptor(self, interfaceName):
+ """
+ Gets the appropriate descriptor for the given interface name given the
+ context of the current descriptor.
+ """
+ return self.config.getDescriptor(interfaceName)
+
+
+def MemberIsLegacyUnforgeable(member, descriptor):
+ 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.
+ """
+ def __init__(self, config, interface, desc):
+ 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.
+ if self.interface.isIteratorInterface():
+ itrName = self.interface.iterableInterface.identifier.name
+ itrDesc = self.getDescriptor(itrName)
+ nativeTypeDefault = iteratorNativeType(itrDesc)
+
+ 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::dom::bindings::codegen::Bindings::%sBinding::%s' % (ifaceName, ifaceName)
+ pathDefault = ty
+ self.returnType = "Rc<%s>" % ty
+ self.argumentType = "???"
+ self.nativeType = ty
+ else:
+ self.returnType = "DomRoot<%s>" % typeName
+ self.argumentType = "&%s" % typeName
+ self.nativeType = "*const %s" % typeName
+ if self.interface.isIteratorInterface():
+ pathDefault = 'crate::dom::bindings::iterable::IterableIterator'
+ else:
+ pathDefault = 'crate::dom::types::%s' % MakeNativeName(typeName)
+
+ self.concreteType = 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.bindingPath = f"{getModuleFromObject(self.interface)}::{ifaceName}_Binding"
+ self.outerObjectHook = desc.get('outerObjectHook', 'None')
+ self.proxy = False
+ self.weakReferenceable = desc.get('weakReferenceable', 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 = {
+ 'IndexedGetter': None,
+ 'IndexedSetter': None,
+ 'IndexedDeleter': None,
+ 'NamedGetter': None,
+ 'NamedSetter': None,
+ 'NamedDeleter': None,
+ 'Stringifier': None,
+ }
+
+ self.hasDefaultToJSON = False
+
+ 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.
+ 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 = self.interface
+ while iface:
+ for m in iface.members:
+ if not m.isMethod():
+ continue
+
+ def addIndexedOrNamedOperation(operation, m):
+ 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, config):
+ def add(key, members, attribute):
+ 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 = 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', 'LegacyCall')
+ self._binaryNames.setdefault('__stringifier', '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,
+ binaryName[0])
+ self._internalNames.setdefault(member.identifier.name,
+ member.identifier.name.replace('-', '_'))
+
+ # Build the prototype chain.
+ self.prototypeChain = []
+ parent = 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):
+ """
+ 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):
+ return self._binaryNames.get(name, name)
+
+ def internalNameFor(self, name):
+ return self._internalNames.get(name, name)
+
+ def hasNamedPropertiesObject(self):
+ if self.interface.isExternal():
+ return False
+
+ return self.isGlobal() and self.supportsNamedProperties()
+
+ def supportsNamedProperties(self):
+ return self.operations['NamedGetter'] is not None
+
+ def getExtendedAttributes(self, member, getter=False, setter=False):
+ def maybeAppendInfallibleToAttrs(attrs, throws):
+ 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):
+ parent = self.interface.parent
+ while parent:
+ if not parent.getExtendedAttribute("Inline"):
+ return parent.identifier.name
+ parent = parent.parent
+ return None
+
+ def supportsIndexedProperties(self):
+ return self.operations['IndexedGetter'] is not None
+
+ 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 hasDescendants(self):
+ return (self.interface.getUserData("hasConcreteDescendant", False)
+ or self.interface.getUserData("hasProxyDescendant", False))
+
+ def hasHTMLConstructor(self):
+ ctor = self.interface.ctor()
+ return ctor and ctor.isHTMLConstructor()
+
+ def shouldHaveGetConstructorObjectMethod(self):
+ 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())
+
+ def shouldCacheConstructor(self):
+ return self.hasDescendants() or self.hasHTMLConstructor()
+
+ def isExposedConditionally(self):
+ return self.interface.isExposedConditionally()
+
+ def isGlobal(self):
+ """
+ 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):
+ return name[0].upper() + name[1:]
+
+
+def getIdlFileName(object):
+ return os.path.basename(object.location.filename).split('.webidl')[0]
+
+
+def getModuleFromObject(object):
+ return ('crate::dom::bindings::codegen::Bindings::' + getIdlFileName(object) + 'Binding')
+
+
+def getTypesFromDescriptor(descriptor):
+ """
+ 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):
+ """
+ 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):
+ """
+ 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):
+ while isinstance(type, IDLSequenceType):
+ type = type.inner
+ return type
+
+
+def iteratorNativeType(descriptor, infer=False):
+ iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
+ assert (iterableDecl.isIterable() and iterableDecl.isPairIterator()) \
+ or iterableDecl.isSetlike() or iterableDecl.isMaplike()
+ res = "IterableIterator%s" % ("" if infer else '<%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::dom::bindings::iterable::{res}"
+ return res