mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 22:25:30 +00:00
915 lines
41 KiB
Python
915 lines
41 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 IDLInterface, IDLExternalInterface, IDLImplementsStatement
|
|
import os
|
|
from collections import defaultdict
|
|
|
|
autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n"
|
|
|
|
|
|
class Configuration:
|
|
"""
|
|
Represents global configuration state based on IDL parse data and
|
|
the configuration file.
|
|
"""
|
|
def __init__(self, filename, parseData, generatedEvents=[]):
|
|
|
|
# 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.
|
|
#
|
|
# 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():
|
|
continue
|
|
iface = thing
|
|
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]
|
|
if not isinstance(entry, list):
|
|
assert isinstance(entry, dict)
|
|
entry = [entry]
|
|
elif len(entry) == 1:
|
|
if entry[0].get("workers", False):
|
|
# List with only a workers descriptor means we should
|
|
# infer a mainthread descriptor. If you want only
|
|
# workers bindings, don't use a list here.
|
|
entry.append({})
|
|
else:
|
|
raise TypeError("Don't use a single-element list for "
|
|
"non-worker-only interface " + iface.identifier.name +
|
|
" in Bindings.conf")
|
|
elif len(entry) == 2:
|
|
if entry[0].get("workers", False) == entry[1].get("workers", False):
|
|
raise TypeError("The two entries for interface " + iface.identifier.name +
|
|
" in Bindings.conf should not have the same value for 'workers'")
|
|
else:
|
|
raise TypeError("Interface " + iface.identifier.name +
|
|
" should have no more than two entries in Bindings.conf")
|
|
descs = [Descriptor(self, iface, x) for x in entry]
|
|
self.descriptors.extend(descs)
|
|
# Setting up descriptorsByName while iterating through interfaces
|
|
# means we can get the nativeType of iterable interfaces without
|
|
# having to do multiple loops.
|
|
for d in descs:
|
|
self.descriptorsByName.setdefault(d.interface.identifier.name,
|
|
[]).append(d)
|
|
|
|
# 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()]
|
|
|
|
# Figure out what our main-thread and worker dictionaries and callbacks
|
|
# are.
|
|
mainTypes = set()
|
|
for descriptor in ([self.getDescriptor("DummyInterface", workers=False)] +
|
|
self.getDescriptors(workers=False, isExternal=False, skipGen=False)):
|
|
mainTypes |= set(getFlatTypes(getTypesFromDescriptor(descriptor)))
|
|
(mainCallbacks, mainDictionaries) = findCallbacksAndDictionaries(mainTypes)
|
|
|
|
workerTypes = set()
|
|
for descriptor in ([self.getDescriptor("DummyInterfaceWorkers", workers=True)] +
|
|
self.getDescriptors(workers=True, isExternal=False, skipGen=False)):
|
|
workerTypes |= set(getFlatTypes(getTypesFromDescriptor(descriptor)))
|
|
(workerCallbacks, workerDictionaries) = findCallbacksAndDictionaries(workerTypes)
|
|
|
|
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 tuples containing a
|
|
# type and descriptor 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 tuple for every use of
|
|
# a union type, so there can be multiple tuples with union types that
|
|
# have the same name.
|
|
self.unionsPerFilename = defaultdict(list)
|
|
|
|
for (t, descriptor, _) 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[0].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, descriptor))
|
|
filenamesForUnion.add(t.filename())
|
|
|
|
def flagWorkerOrMainThread(items, main, worker):
|
|
for item in items:
|
|
if item in main:
|
|
item.setUserData("mainThread", True)
|
|
if item in worker:
|
|
item.setUserData("workers", True)
|
|
flagWorkerOrMainThread(self.callbacks, mainCallbacks, workerCallbacks)
|
|
|
|
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.getNavigatorProperty() is not None
|
|
elif key == 'isExposedInAnyWorker':
|
|
getter = lambda x: x.interface.isExposedInAnyWorker()
|
|
elif key == 'isExposedInWorkerDebugger':
|
|
getter = lambda x: x.interface.isExposedInWorkerDebugger()
|
|
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)
|
|
|
|
@staticmethod
|
|
def _filterForFileAndWorkers(items, filters):
|
|
"""Gets the items that match the given filters."""
|
|
for key, val in filters.iteritems():
|
|
if key == 'webIDLFile':
|
|
items = filter(lambda x: x.filename() == val, items)
|
|
elif key == 'workers':
|
|
if val:
|
|
items = filter(lambda x: x.getUserData("workers", False), items)
|
|
else:
|
|
items = filter(lambda x: x.getUserData("mainThread", False), items)
|
|
else:
|
|
assert(0) # Unknown key
|
|
return items
|
|
|
|
def getDictionaries(self, **filters):
|
|
return self._filterForFileAndWorkers(self.dictionaries, filters)
|
|
|
|
def getCallbacks(self, **filters):
|
|
return self._filterForFileAndWorkers(self.callbacks, filters)
|
|
|
|
def getDescriptor(self, interfaceName, workers):
|
|
"""
|
|
Gets the appropriate descriptor for the given interface name
|
|
and the given workers boolean.
|
|
"""
|
|
# 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 lookups. But that means we have to do our normal
|
|
# lookups in a way that will not throw if they fail.
|
|
for d in self.descriptorsByName.get(interfaceName, []):
|
|
if d.workers == workers:
|
|
return d
|
|
|
|
if workers:
|
|
for d in self.descriptorsByName.get(interfaceName, []):
|
|
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")
|
|
|
|
def getDescriptorProvider(self, workers):
|
|
"""
|
|
Gets a descriptor provider that can provide descriptors as needed,
|
|
for the given workers boolean
|
|
"""
|
|
return DescriptorProvider(self, workers)
|
|
|
|
|
|
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, workers):
|
|
self.config = config
|
|
self.workers = workers
|
|
|
|
def getDescriptor(self, interfaceName):
|
|
"""
|
|
Gets the appropriate descriptor for the given interface name given the
|
|
context of the current descriptor. This selects the appropriate
|
|
implementation for cases like workers.
|
|
"""
|
|
return self.config.getDescriptor(interfaceName, self.workers)
|
|
|
|
|
|
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, config, desc.get('workers', False))
|
|
self.interface = interface
|
|
|
|
if self.workers:
|
|
assert 'wantsXrays' not in desc
|
|
self.wantsXrays = False
|
|
else:
|
|
self.wantsXrays = desc.get('wantsXrays', True)
|
|
|
|
# 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():
|
|
assert not self.workers
|
|
nativeTypeDefault = "nsIDOM" + ifaceName
|
|
elif self.interface.isCallback():
|
|
nativeTypeDefault = "mozilla::dom::" + ifaceName
|
|
else:
|
|
if self.workers:
|
|
nativeTypeDefault = "mozilla::dom::workers::" + 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 self.workers:
|
|
headerDefault = "mozilla/dom/workers/bindings/%s.h" % ifaceName
|
|
elif 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.skipGen = desc.get('skipGen', False)
|
|
|
|
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
|
|
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))
|
|
|
|
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))
|
|
# Nasty temporary hack for supporting both DOM and SpiderMonkey promises
|
|
# without too much pain
|
|
if self.interface.identifier.name == "Promise":
|
|
assert self.wrapperCache
|
|
# But really, we're only wrappercached if we have an interface
|
|
# object (that is, when we're not using SpiderMonkey promises).
|
|
self.wrapperCache = self.interface.hasInterfaceObject()
|
|
|
|
def make_name(name):
|
|
return name + "_workers" if self.workers else name
|
|
self.name = make_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, {}))
|
|
|
|
self._binaryNames = desc.get('binaryNames', {})
|
|
self._binaryNames.setdefault('__legacycaller', 'LegacyCall')
|
|
self._binaryNames.setdefault('__stringifier', 'Stringify')
|
|
|
|
if not self.interface.isExternal():
|
|
self.anypermissions = dict()
|
|
self.allpermissions = dict()
|
|
|
|
# Adds a permission list to this descriptor and returns the index to use.
|
|
def addPermissions(ifaceOrMember, attribute):
|
|
if attribute == "CheckAllPermissions":
|
|
permissions = self.allpermissions
|
|
else:
|
|
permissions = self.anypermissions
|
|
|
|
checkPermissions = ifaceOrMember.getExtendedAttribute(attribute)
|
|
if checkPermissions is None:
|
|
return None
|
|
|
|
# It's a list of whitespace-separated strings
|
|
assert(len(checkPermissions) is 1)
|
|
assert(checkPermissions[0] is not None)
|
|
checkPermissions = checkPermissions[0]
|
|
permissionsList = checkPermissions.split()
|
|
if len(permissionsList) == 0:
|
|
raise TypeError("Need at least one permission name for %s" % attribute)
|
|
|
|
permissionsList = tuple(sorted(set(permissionsList)))
|
|
return permissions.setdefault(permissionsList, len(permissions))
|
|
|
|
self.checkAnyPermissionsIndex = addPermissions(self.interface, "CheckAnyPermissions")
|
|
self.checkAnyPermissionsIndicesForMembers = dict()
|
|
self.checkAllPermissionsIndex = addPermissions(self.interface, "CheckAllPermissions")
|
|
self.checkAllPermissionsIndicesForMembers = dict()
|
|
for m in self.interface.members:
|
|
permissionsIndex = addPermissions(m, "CheckAnyPermissions")
|
|
if permissionsIndex is not None:
|
|
self.checkAnyPermissionsIndicesForMembers[m.identifier.name] = permissionsIndex
|
|
allpermissionsIndex = addPermissions(m, "CheckAllPermissions")
|
|
if allpermissionsIndex is not None:
|
|
self.checkAllPermissionsIndicesForMembers[m.identifier.name] = allpermissionsIndex
|
|
|
|
def isTestInterface(iface):
|
|
return (iface.identifier.name in ["TestInterface",
|
|
"TestJSImplInterface",
|
|
"TestRenamedInterface"])
|
|
|
|
self.featureDetectibleThings = set()
|
|
if not isTestInterface(self.interface):
|
|
if (self.interface.getExtendedAttribute("CheckAnyPermissions") or
|
|
self.interface.getExtendedAttribute("CheckAllPermissions") or
|
|
self.interface.getExtendedAttribute("AvailableIn") == "PrivilegedApps"):
|
|
if self.interface.getNavigatorProperty():
|
|
self.featureDetectibleThings.add("Navigator.%s" % self.interface.getNavigatorProperty())
|
|
else:
|
|
iface = self.interface.identifier.name
|
|
self.featureDetectibleThings.add(iface)
|
|
for m in self.interface.members:
|
|
self.featureDetectibleThings.add("%s.%s" % (iface, m.identifier.name))
|
|
|
|
for m in self.interface.members:
|
|
if (m.getExtendedAttribute("CheckAnyPermissions") or
|
|
m.getExtendedAttribute("CheckAllPermissions") or
|
|
m.getExtendedAttribute("AvailableIn") == "PrivilegedApps"):
|
|
self.featureDetectibleThings.add("%s.%s" % (self.interface.identifier.name, m.identifier.name))
|
|
|
|
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 (on main thread)
|
|
# or be passed as a JSObject (on worker threads).
|
|
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 ensureValidThrowsExtendedAttribute(attr):
|
|
assert(attr is None or attr is True or len(attr) == 1)
|
|
if (attr is not None and attr is not True and
|
|
'Workers' not in attr and 'MainThread' not in attr):
|
|
raise TypeError("Unknown value for 'Throws': " + attr[0])
|
|
|
|
def maybeAppendInfallibleToAttrs(attrs, throws):
|
|
ensureValidThrowsExtendedAttribute(throws)
|
|
if (throws is None or
|
|
(throws is not True and
|
|
('Workers' not in throws or not self.workers) and
|
|
('MainThread' not in throws or self.workers))):
|
|
attrs.append("infallible")
|
|
|
|
name = member.identifier.name
|
|
throws = self.interface.isJSImplemented() or member.getExtendedAttribute("Throws")
|
|
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)
|
|
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)
|
|
return attrs
|
|
|
|
def supportsIndexedProperties(self):
|
|
return self.operations['IndexedGetter'] is not None
|
|
|
|
def supportsNamedProperties(self):
|
|
return self.operations['NamedGetter'] is not None
|
|
|
|
def needsConstructHookHolder(self):
|
|
assert self.interface.hasInterfaceObject()
|
|
return False
|
|
|
|
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):
|
|
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 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"))
|
|
|
|
|
|
# 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())
|
|
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 findCallbacksAndDictionaries(inputTypes):
|
|
"""
|
|
Ensure that all callbacks and dictionaries reachable from types end up in
|
|
the returned callbacks and dictionaries sets.
|
|
|
|
Note that we assume that our initial invocation already includes all types
|
|
reachable via descriptors in "types", so we only have to deal with things
|
|
that are themeselves reachable via callbacks and dictionaries.
|
|
"""
|
|
def doFindCallbacksAndDictionaries(types, callbacks, dictionaries):
|
|
unhandledTypes = set()
|
|
for type in types:
|
|
if type.isCallback() and type.callback not in callbacks:
|
|
unhandledTypes |= getFlatTypes(getTypesFromCallback(type.callback))
|
|
callbacks.add(type.callback)
|
|
elif type.isDictionary() and type.inner not in dictionaries:
|
|
d = type.inner
|
|
unhandledTypes |= getFlatTypes(getTypesFromDictionary(d))
|
|
while d:
|
|
dictionaries.add(d)
|
|
d = d.parent
|
|
if len(unhandledTypes) != 0:
|
|
doFindCallbacksAndDictionaries(unhandledTypes, callbacks, dictionaries)
|
|
|
|
retCallbacks = set()
|
|
retDictionaries = set()
|
|
doFindCallbacksAndDictionaries(inputTypes, retCallbacks, retDictionaries)
|
|
return (retCallbacks, retDictionaries)
|
|
|
|
|
|
def getAllTypes(descriptors, dictionaries, callbacks):
|
|
"""
|
|
Generate all the types we're dealing with. For each type, a tuple
|
|
containing type, descriptor, dictionary is yielded. The
|
|
descriptor and dictionary can be None if the type does not come
|
|
from a descriptor or dictionary; they will never both be non-None.
|
|
"""
|
|
for d in descriptors:
|
|
if d.interface.isExternal():
|
|
continue
|
|
for t in getTypesFromDescriptor(d):
|
|
yield (t, d, None)
|
|
for dictionary in dictionaries:
|
|
for t in getTypesFromDictionary(dictionary):
|
|
yield (t, None, dictionary)
|
|
for callback in callbacks:
|
|
for t in getTypesFromCallback(callback):
|
|
yield (t, None, None)
|
|
|
|
def iteratorNativeType(descriptor):
|
|
assert descriptor.interface.isIterable()
|
|
iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
|
|
assert iterableDecl.isPairIterator()
|
|
return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType
|