mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 15:52:07 +00:00
851 lines
37 KiB
Python
851 lines
37 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 http://mozilla.org/MPL/2.0/.
|
|
|
|
from WebIDL import IDLImplementsStatement
|
|
import os
|
|
from collections import defaultdict
|
|
|
|
autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n"
|
|
|
|
|
|
class DescriptorProvider:
|
|
"""
|
|
A way of getting descriptors for interface names. Subclasses must
|
|
have a getDescriptor method callable with the interface name only.
|
|
"""
|
|
def __init__(self):
|
|
pass
|
|
|
|
|
|
class Configuration(DescriptorProvider):
|
|
"""
|
|
Represents global configuration state based on IDL parse data and
|
|
the configuration file.
|
|
"""
|
|
def __init__(self, filename, parseData, generatedEvents=[]):
|
|
DescriptorProvider.__init__(self)
|
|
|
|
# Read the configuration file.
|
|
glbl = {}
|
|
execfile(filename, 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.descriptorsByName = {}
|
|
self.optimizedOutDescriptorNames = set()
|
|
self.generatedEvents = generatedEvents
|
|
self.maxProtoChainLength = 0
|
|
for thing in parseData:
|
|
if isinstance(thing, IDLImplementsStatement):
|
|
# Our build system doesn't support dep build involving
|
|
# addition/removal of "implements" 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!
|
|
#
|
|
# But whitelist a RHS that is LegacyQueryInterface,
|
|
# since people shouldn't be adding any of those.
|
|
if (thing.implementor.filename() != thing.filename() and
|
|
thing.implementee.identifier.name != "LegacyQueryInterface"):
|
|
raise TypeError(
|
|
"The binding build system doesn't really support "
|
|
"'implements' statements which don't appear in the "
|
|
"file in which the left-hand side of the statement is "
|
|
"defined. Don't do this unless your right-hand side "
|
|
"is LegacyQueryInterface.\n"
|
|
"%s\n"
|
|
"%s" %
|
|
(thing.location, thing.implementor.location))
|
|
|
|
assert not thing.isType()
|
|
|
|
if not thing.isInterface() and not thing.isNamespace():
|
|
continue
|
|
iface = thing
|
|
# Our build system doesn't support dep builds involving
|
|
# addition/removal of partial interfaces that appear in a different
|
|
# .webidl file than the interface they are extending. Make sure we
|
|
# don't have any of those. See similar block above for "implements"
|
|
# statements!
|
|
if not iface.isExternal():
|
|
for partialIface in iface.getPartialInterfaces():
|
|
if (partialIface.filename() != iface.filename() and
|
|
# Unfortunately, NavigatorProperty does exactly the
|
|
# thing we're trying to prevent here. I'm not sure how
|
|
# to deal with that, short of effectively requiring a
|
|
# clobber when NavigatorProperty is added/removed and
|
|
# whitelisting the things it outputs here as
|
|
# restrictively as I can.
|
|
(partialIface.identifier.name != "Navigator" or
|
|
len(partialIface.members) != 1 or
|
|
partialIface.members[0].location != partialIface.location or
|
|
partialIface.members[0].identifier.location.filename() !=
|
|
"<builtin>")):
|
|
raise TypeError(
|
|
"The binding build system doesn't really support "
|
|
"partial interfaces which don't appear in the "
|
|
"file in which the interface they are extending is "
|
|
"defined. Don't do this.\n"
|
|
"%s\n"
|
|
"%s" %
|
|
(partialIface.location, iface.location))
|
|
self.interfaces[iface.identifier.name] = iface
|
|
if iface.identifier.name not in config:
|
|
# Completely skip consequential interfaces with no descriptor
|
|
# if they have no interface object because chances are we
|
|
# don't need to do anything interesting with them.
|
|
if iface.isConsequential() and not iface.hasInterfaceObject():
|
|
self.optimizedOutDescriptorNames.add(iface.identifier.name)
|
|
continue
|
|
entry = {}
|
|
else:
|
|
entry = config[iface.identifier.name]
|
|
assert not isinstance(entry, list)
|
|
desc = Descriptor(self, iface, entry)
|
|
self.descriptors.append(desc)
|
|
# Setting up descriptorsByName while iterating through interfaces
|
|
# means we can get the nativeType of iterable interfaces without
|
|
# having to do multiple loops.
|
|
assert desc.interface.identifier.name not in self.descriptorsByName
|
|
self.descriptorsByName[desc.interface.identifier.name] = desc
|
|
|
|
# Keep the descriptor list sorted for determinism.
|
|
self.descriptors.sort(lambda x, y: cmp(x.name, y.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.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)
|
|
|
|
for (t, _) in getAllTypes(self.descriptors, self.dictionaries, self.callbacks):
|
|
while True:
|
|
if t.isMozMap():
|
|
t = t.inner
|
|
elif t.unroll() != t:
|
|
t = t.unroll()
|
|
elif t.isPromise():
|
|
t = t.promiseInnerType()
|
|
else:
|
|
break
|
|
if t.isUnion():
|
|
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 = self.unionsPerFilename[f]
|
|
unionsForFilename = filter(lambda u: u.name != t.name,
|
|
unionsForFilename)
|
|
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 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 = []
|
|
for key, val in filters.iteritems():
|
|
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':
|
|
getter = lambda x: (not x.interface.isExternal() and
|
|
x.interface.hasInterfaceObject())
|
|
elif key == 'hasInterfacePrototypeObject':
|
|
getter = lambda x: (not x.interface.isExternal() and
|
|
x.interface.hasInterfacePrototypeObject())
|
|
elif key == 'hasInterfaceOrInterfacePrototypeObject':
|
|
getter = lambda x: x.hasInterfaceOrInterfacePrototypeObject()
|
|
elif key == 'isCallback':
|
|
getter = lambda x: x.interface.isCallback()
|
|
elif key == 'isExternal':
|
|
getter = lambda x: x.interface.isExternal()
|
|
elif key == 'isJSImplemented':
|
|
getter = lambda x: x.interface.isJSImplemented()
|
|
elif key == 'isNavigatorProperty':
|
|
getter = lambda x: x.interface.isNavigatorProperty()
|
|
elif key == 'isExposedInAnyWorker':
|
|
getter = lambda x: x.interface.isExposedInAnyWorker()
|
|
elif key == 'isExposedInWorkerDebugger':
|
|
getter = lambda x: x.interface.isExposedInWorkerDebugger()
|
|
elif key == 'isExposedInAnyWorklet':
|
|
getter = lambda x: x.interface.isExposedInAnyWorklet()
|
|
elif key == 'isExposedInSystemGlobals':
|
|
getter = lambda x: x.interface.isExposedInSystemGlobals()
|
|
elif key == 'isExposedInWindow':
|
|
getter = lambda x: x.interface.isExposedInWindow()
|
|
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 = filter(lambda x: f[0](x) == f[1], curr)
|
|
return curr
|
|
|
|
def getEnums(self, webIDLFile):
|
|
return filter(lambda e: e.filename() == webIDLFile, self.enums)
|
|
|
|
def getDictionaries(self, webIDLFile):
|
|
return filter(lambda d: d.filename() == webIDLFile, self.dictionaries)
|
|
|
|
def getCallbacks(self, webIDLFile):
|
|
return filter(lambda c: c.filename() == webIDLFile, self.callbacks)
|
|
|
|
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
|
|
|
|
if interfaceName in self.optimizedOutDescriptorNames:
|
|
raise NoSuchDescriptorError(
|
|
"No descriptor for '%s', which is a mixin ([NoInterfaceObject] "
|
|
"and a consequential interface) without an explicit "
|
|
"Bindings.conf annotation." % interfaceName)
|
|
|
|
raise NoSuchDescriptorError("For " + interfaceName + " found no matches")
|
|
|
|
|
|
class NoSuchDescriptorError(TypeError):
|
|
def __init__(self, str):
|
|
TypeError.__init__(self, str)
|
|
|
|
|
|
def methodReturnsJSObject(method):
|
|
assert method.isMethod()
|
|
if method.returnsPromise():
|
|
return True
|
|
|
|
for signature in method.signatures():
|
|
returnType = signature[0]
|
|
if returnType.isObject() or returnType.isSpiderMonkeyInterface():
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def MemberIsUnforgeable(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.isUnforgeable() or
|
|
descriptor.interface.getExtendedAttribute("Unforgeable")))
|
|
|
|
|
|
class Descriptor(DescriptorProvider):
|
|
"""
|
|
Represents a single descriptor for an interface. See Bindings.conf.
|
|
"""
|
|
def __init__(self, config, interface, desc):
|
|
DescriptorProvider.__init__(self)
|
|
self.config = config
|
|
self.interface = interface
|
|
|
|
self.wantsXrays = (not interface.isExternal() and
|
|
interface.isExposedInWindow())
|
|
|
|
if self.wantsXrays:
|
|
# We could try to restrict self.wantsXrayExpandoClass further. For
|
|
# example, we could set it to false if all of our slots store
|
|
# Gecko-interface-typed things, because we don't use Xray expando
|
|
# slots for those. But note that we would need to check the types
|
|
# of not only the members of "interface" but also of all its
|
|
# ancestors, because those can have members living in our slots too.
|
|
# For now, do the simple thing.
|
|
self.wantsXrayExpandoClass = (interface.totalMembersInSlots != 0)
|
|
|
|
# Read the desc, and fill in the relevant defaults.
|
|
ifaceName = self.interface.identifier.name
|
|
# For generated iterator interfaces for other iterable interfaces, we
|
|
# just use IterableIterator as the native type, templated on the
|
|
# nativeType of the iterable interface. That way we can have a
|
|
# templated implementation for all the duplicated iterator
|
|
# functionality.
|
|
if self.interface.isIteratorInterface():
|
|
itrName = self.interface.iterableInterface.identifier.name
|
|
itrDesc = self.getDescriptor(itrName)
|
|
nativeTypeDefault = iteratorNativeType(itrDesc)
|
|
|
|
elif self.interface.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():
|
|
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)
|
|
|
|
self.hasXPConnectImpls = desc.get('hasXPConnectImpls', 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.isExternal() and
|
|
not self.interface.isCallback() and
|
|
not self.interface.isNamespace() and
|
|
desc.get('concrete', True))
|
|
self.hasUnforgeableMembers = (self.concrete and
|
|
any(MemberIsUnforgeable(m, self) for m in
|
|
self.interface.members))
|
|
self.operations = {
|
|
'IndexedGetter': None,
|
|
'IndexedSetter': None,
|
|
'IndexedCreator': None,
|
|
'IndexedDeleter': None,
|
|
'NamedGetter': None,
|
|
'NamedSetter': None,
|
|
'NamedCreator': None,
|
|
'NamedDeleter': None,
|
|
'Stringifier': None,
|
|
'LegacyCaller': None,
|
|
'Jsonifier': None
|
|
}
|
|
|
|
# Stringifiers and jsonifiers 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.isJsonifier():
|
|
addOperation('Jsonifier', m)
|
|
|
|
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.isCreator():
|
|
addIndexedOrNamedOperation('Creator', 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.hasNonOrdinaryGetPrototypeOf())
|
|
|
|
if self.proxy:
|
|
if (not self.operations['IndexedGetter'] and
|
|
(self.operations['IndexedSetter'] or
|
|
self.operations['IndexedDeleter'] or
|
|
self.operations['IndexedCreator'])):
|
|
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'] or
|
|
self.operations['NamedCreator'])):
|
|
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
|
|
desc.get('wrapperCache', 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', map(lambda m: m.name, iface.members), attribute)
|
|
iface = iface.parent
|
|
else:
|
|
add('all', [config], attribute)
|
|
|
|
if self.interface.isJSImplemented():
|
|
addExtendedAttribute('implicitJSContext', ['constructor'])
|
|
else:
|
|
for attribute in ['implicitJSContext']:
|
|
addExtendedAttribute(attribute, desc.get(attribute, {}))
|
|
|
|
if self.interface.identifier.name == 'Navigator':
|
|
for m in self.interface.members:
|
|
if m.isAttr() and m.navigatorObjectGetter:
|
|
# These getters call ConstructNavigatorObject to construct
|
|
# the value, and ConstructNavigatorObject needs a JSContext.
|
|
self.extendedAttributes['all'].setdefault(m.identifier.name, []).append('implicitJSContext')
|
|
|
|
self._binaryNames = desc.get('binaryNames', {})
|
|
self._binaryNames.setdefault('__legacycaller', 'LegacyCall')
|
|
self._binaryNames.setdefault('__stringifier', 'Stringify')
|
|
|
|
if not self.interface.isExternal():
|
|
def isTestInterface(iface):
|
|
return (iface.identifier.name in ["TestInterface",
|
|
"TestJSImplInterface",
|
|
"TestRenamedInterface"])
|
|
|
|
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])
|
|
|
|
# Build the prototype chain.
|
|
self.prototypeChain = []
|
|
parent = interface
|
|
while parent:
|
|
self.prototypeChain.insert(0, parent.identifier.name)
|
|
parent = parent.parent
|
|
config.maxProtoChainLength = max(config.maxProtoChainLength,
|
|
len(self.prototypeChain))
|
|
|
|
def binaryNameFor(self, name):
|
|
return self._binaryNames.get(name, name)
|
|
|
|
@property
|
|
def prototypeNameChain(self):
|
|
return map(lambda p: self.getDescriptor(p).name, self.prototypeChain)
|
|
|
|
@property
|
|
def parentPrototypeName(self):
|
|
if len(self.prototypeChain) == 1:
|
|
return None
|
|
return self.getDescriptor(self.prototypeChain[-2]).name
|
|
|
|
def hasInterfaceOrInterfacePrototypeObject(self):
|
|
|
|
# Forward-declared interfaces don't need either interface object or
|
|
# interface prototype object as they're going to use QI.
|
|
if self.interface.isExternal():
|
|
return False
|
|
|
|
return self.interface.hasInterfaceObject() or self.interface.hasInterfacePrototypeObject()
|
|
|
|
@property
|
|
def hasNamedPropertiesObject(self):
|
|
if self.interface.isExternal():
|
|
return False
|
|
|
|
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 maybeAppendInfallibleToAttrs(attrs, throws):
|
|
ensureValidThrowsExtendedAttribute(throws)
|
|
if throws is None:
|
|
attrs.append("infallible")
|
|
|
|
def maybeAppendCanOOMToAttrs(attrs, canOOM):
|
|
ensureValidCanOOMExtendedAttribute(canOOM)
|
|
if canOOM is not None:
|
|
attrs.append("canOOM")
|
|
|
|
name = member.identifier.name
|
|
throws = self.interface.isJSImplemented() or member.getExtendedAttribute("Throws")
|
|
canOOM = member.getExtendedAttribute("CanOOM")
|
|
if member.isMethod():
|
|
# JSObject-returning [NewObject] methods must be fallible,
|
|
# since they have to (fallibly) allocate the new JSObject.
|
|
if (member.getExtendedAttribute("NewObject") and
|
|
methodReturnsJSObject(member)):
|
|
throws = True
|
|
attrs = self.extendedAttributes['all'].get(name, [])
|
|
maybeAppendInfallibleToAttrs(attrs, throws)
|
|
maybeAppendCanOOMToAttrs(attrs, canOOM)
|
|
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, [])
|
|
if throws is None:
|
|
throwsAttr = "GetterThrows" if getter else "SetterThrows"
|
|
throws = member.getExtendedAttribute(throwsAttr)
|
|
maybeAppendInfallibleToAttrs(attrs, throws)
|
|
if canOOM is None:
|
|
canOOMAttr = "GetterCanOOM" if getter else "SetterCanOOM"
|
|
canOOM = member.getExtendedAttribute(canOOMAttr)
|
|
maybeAppendCanOOMToAttrs(attrs, canOOM)
|
|
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 hasNonOrdinaryGetPrototypeOf(self):
|
|
return self.interface.getExtendedAttribute("NonOrdinaryGetPrototypeOf")
|
|
|
|
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 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",
|
|
"HTMLAppletElement"])
|
|
def needsXrayNamedDeleterHook(self):
|
|
return self.operations["NamedDeleter"] is not None
|
|
|
|
def needsSpecialGenericOps(self):
|
|
"""
|
|
Returns true if this descriptor requires generic ops other than
|
|
GenericBindingMethod/GenericBindingGetter/GenericBindingSetter.
|
|
|
|
In practice we need to do this if our this value might be an XPConnect
|
|
object or if we need to coerce null/undefined to the global.
|
|
"""
|
|
return self.hasXPConnectImpls or self.interface.isOnGlobalProtoChain()
|
|
|
|
def isGlobal(self):
|
|
"""
|
|
Returns true if this is the primary interface for a global object
|
|
of some sort.
|
|
"""
|
|
return (self.interface.getExtendedAttribute("Global") or
|
|
self.interface.getExtendedAttribute("PrimaryGlobal"))
|
|
|
|
@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 (not self.interface.isExternal() and
|
|
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)
|
|
|
|
|
|
# Some utility methods
|
|
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.namedConstructors)
|
|
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())
|
|
|
|
if descriptor.interface.maplikeOrSetlikeOrIterable:
|
|
maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
|
|
if maplikeOrSetlikeOrIterable.hasKeyType():
|
|
types.append(maplikeOrSetlikeOrIterable.keyType)
|
|
if maplikeOrSetlikeOrIterable.hasValueType():
|
|
types.append(maplikeOrSetlikeOrIterable.valueType)
|
|
return types
|
|
|
|
|
|
def getFlatTypes(types):
|
|
retval = set()
|
|
for type in types:
|
|
type = type.unroll()
|
|
if type.isUnion():
|
|
retval |= set(type.flatMemberTypes)
|
|
else:
|
|
retval.add(type)
|
|
return retval
|
|
|
|
|
|
def getTypesFromDictionary(dictionary):
|
|
"""
|
|
Get all member types for this dictionary
|
|
"""
|
|
types = []
|
|
curDict = dictionary
|
|
while curDict:
|
|
types.extend([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 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, None)
|
|
|
|
def iteratorNativeType(descriptor):
|
|
assert descriptor.interface.isIterable()
|
|
iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
|
|
assert iterableDecl.isPairIterator()
|
|
return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType
|