mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
* Add canGc as a parameter to autogenerated trait methods Signed-off-by: Andriy Sultanov <sultanovandriy@gmail.com> * Propagate CanGc from Document::new() Signed-off-by: Andriy Sultanov <sultanovandriy@gmail.com> --------- Signed-off-by: Andriy Sultanov <sultanovandriy@gmail.com>
541 lines
21 KiB
Python
541 lines
21 KiB
Python
# 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']
|
|
|
|
# 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 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 getDictionaries(self, webIDLFile=""):
|
|
return self._filterForFile(self.dictionaries, webIDLFile=webIDLFile)
|
|
|
|
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() and not self.interface.isIteratorInterface():
|
|
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
|