зеркало из https://github.com/mozilla/gecko-dev.git
1166 строки
48 KiB
Python
1166 строки
48 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 IDLIncludesStatement
|
|
import io
|
|
import itertools
|
|
import os
|
|
import six
|
|
|
|
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.
|
|
|
|
Subclasses must also have a getConfig() method that returns a
|
|
Configuration.
|
|
"""
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
|
|
def isChildPath(path, basePath):
|
|
path = os.path.normpath(path)
|
|
return os.path.commonprefix((path, basePath)) == basePath
|
|
|
|
|
|
class Configuration(DescriptorProvider):
|
|
"""
|
|
Represents global configuration state based on IDL parse data and
|
|
the configuration file.
|
|
"""
|
|
|
|
def __init__(self, filename, webRoots, parseData, generatedEvents=[]):
|
|
DescriptorProvider.__init__(self)
|
|
|
|
# Read the configuration file.
|
|
glbl = {}
|
|
exec(io.open(filename, encoding="utf-8").read(), glbl)
|
|
config = glbl["DOMInterfaces"]
|
|
|
|
webRoots = tuple(map(os.path.normpath, webRoots))
|
|
|
|
def isInWebIDLRoot(path):
|
|
return any(isChildPath(path, root) for root in webRoots)
|
|
|
|
# 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.dictionariesByName = {}
|
|
self.generatedEvents = generatedEvents
|
|
self.maxProtoChainLength = 0
|
|
for thing in parseData:
|
|
if isinstance(thing, IDLIncludesStatement):
|
|
# Our build system doesn't support dep build involving
|
|
# addition/removal of "includes" 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!
|
|
if thing.interface.filename() != thing.filename():
|
|
raise TypeError(
|
|
"The binding build system doesn't really support "
|
|
"'includes' statements which don't appear in the "
|
|
"file in which the left-hand side of the statement is "
|
|
"defined.\n"
|
|
"%s\n"
|
|
"%s" % (thing.location, thing.interface.location)
|
|
)
|
|
|
|
assert not thing.isType()
|
|
|
|
if (
|
|
not thing.isInterface()
|
|
and not thing.isNamespace()
|
|
and not thing.isInterfaceMixin()
|
|
):
|
|
continue
|
|
# Our build system doesn't support dep builds involving
|
|
# addition/removal of partial interfaces/namespaces/mixins that
|
|
# appear in a different .webidl file than the
|
|
# interface/namespace/mixin they are extending. Make sure we don't
|
|
# have any of those. See similar block above for "includes"
|
|
# statements!
|
|
if not thing.isExternal():
|
|
for partial in thing.getPartials():
|
|
if partial.filename() != thing.filename():
|
|
raise TypeError(
|
|
"The binding build system doesn't really support "
|
|
"partial interfaces/namespaces/mixins which don't "
|
|
"appear in the file in which the "
|
|
"interface/namespace/mixin they are extending is "
|
|
"defined. Don't do this.\n"
|
|
"%s\n"
|
|
"%s" % (partial.location, thing.location)
|
|
)
|
|
|
|
# The rest of the logic doesn't apply to mixins.
|
|
if thing.isInterfaceMixin():
|
|
continue
|
|
|
|
iface = thing
|
|
if not iface.isExternal():
|
|
if not (
|
|
iface.getExtendedAttribute("ChromeOnly")
|
|
or iface.getExtendedAttribute("Func")
|
|
== ["nsContentUtils::IsCallerChromeOrFuzzingEnabled"]
|
|
or not iface.hasInterfaceObject()
|
|
or isInWebIDLRoot(iface.filename())
|
|
):
|
|
raise TypeError(
|
|
"Interfaces which are exposed to the web may only be "
|
|
"defined in a DOM WebIDL root %r. Consider marking "
|
|
"the interface [ChromeOnly] or "
|
|
"[Func='nsContentUtils::IsCallerChromeOrFuzzingEnabled'] "
|
|
"if you do not want it exposed to the web.\n"
|
|
"%s" % (webRoots, iface.location)
|
|
)
|
|
|
|
self.interfaces[iface.identifier.name] = iface
|
|
|
|
entry = config.get(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(key=lambda x: x.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.dictionariesByName = {d.identifier.name: d for d in self.dictionaries}
|
|
|
|
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):
|
|
t = findInnermostType(t)
|
|
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 = [
|
|
u
|
|
for u in self.unionsPerFilename[f]
|
|
if u.name != t.name
|
|
]
|
|
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())
|
|
|
|
for d in getDictionariesConvertedToJS(
|
|
self.descriptors, self.dictionaries, self.callbacks
|
|
):
|
|
d.needsConversionToJS = True
|
|
|
|
for d in getDictionariesConvertedFromJS(
|
|
self.descriptors, self.dictionaries, self.callbacks
|
|
):
|
|
d.needsConversionFromJS = True
|
|
|
|
# Collect all the global names exposed on a Window object (to implement
|
|
# the hash for looking up these names when resolving a property).
|
|
self.windowGlobalNames = []
|
|
for desc in self.getDescriptors(registersGlobalNamesOnWindow=True):
|
|
self.windowGlobalNames.append((desc.name, desc))
|
|
self.windowGlobalNames.extend(
|
|
(n.identifier.name, desc) for n in desc.interface.legacyFactoryFunctions
|
|
)
|
|
self.windowGlobalNames.extend(
|
|
(n, desc) for n in desc.interface.legacyWindowAliases
|
|
)
|
|
|
|
# Collect a sorted list of strings that we want to concatenate into
|
|
# one big string and a dict mapping each string to its offset in the
|
|
# concatenated string.
|
|
|
|
# We want the names of all the interfaces with a prototype (for
|
|
# implementing @@toStringTag).
|
|
names = set(
|
|
d.interface.getClassName()
|
|
for d in self.getDescriptors(hasInterfaceOrInterfacePrototypeObject=True)
|
|
)
|
|
|
|
# Now also add the names from windowGlobalNames, we need them for the
|
|
# perfect hash that we build for these.
|
|
names.update(n[0] for n in self.windowGlobalNames)
|
|
|
|
# Sorting is not strictly necessary, but makes the generated code a bit
|
|
# more readable.
|
|
names = sorted(names)
|
|
|
|
# We can't rely on being able to pass initial=0 to itertools.accumulate
|
|
# because it was only added in version 3.8, so define an accumulate
|
|
# function that chains the initial value into the iterator.
|
|
def accumulate(iterable, initial):
|
|
return itertools.accumulate(itertools.chain([initial], iterable))
|
|
|
|
# Calculate the offset of each name in the concatenated string. Note that
|
|
# we need to add 1 to the length to account for the null terminating each
|
|
# name.
|
|
offsets = accumulate(map(lambda n: len(n) + 1, names), initial=0)
|
|
self.namesStringOffsets = list(zip(names, offsets))
|
|
|
|
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 = [(lambda x: x.interface.isExternal(), False)]
|
|
for key, val in six.iteritems(filters):
|
|
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: x.interface.hasInterfaceObject()
|
|
elif key == "hasInterfacePrototypeObject":
|
|
getter = lambda x: x.interface.hasInterfacePrototypeObject()
|
|
elif key == "hasInterfaceOrInterfacePrototypeObject":
|
|
getter = lambda x: x.hasInterfaceOrInterfacePrototypeObject()
|
|
elif key == "isCallback":
|
|
getter = lambda x: x.interface.isCallback()
|
|
elif key == "isJSImplemented":
|
|
getter = lambda x: x.interface.isJSImplemented()
|
|
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 == "isExposedInWindow":
|
|
getter = lambda x: x.interface.isExposedInWindow()
|
|
elif key == "isExposedInShadowRealms":
|
|
getter = lambda x: x.interface.isExposedInShadowRealms()
|
|
elif key == "isSerializable":
|
|
getter = lambda x: x.interface.isSerializable()
|
|
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 = [x for x in curr if f[0](x) == f[1]]
|
|
return curr
|
|
|
|
def getEnums(self, webIDLFile):
|
|
return [e for e in self.enums if e.filename() == webIDLFile]
|
|
|
|
def getDictionaries(self, webIDLFile):
|
|
return [d for d in self.dictionaries if d.filename() == webIDLFile]
|
|
|
|
def getCallbacks(self, webIDLFile):
|
|
return [c for c in self.callbacks if c.filename() == webIDLFile]
|
|
|
|
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
|
|
|
|
raise NoSuchDescriptorError("For " + interfaceName + " found no matches")
|
|
|
|
def getConfig(self):
|
|
return self
|
|
|
|
def getDictionariesConvertibleToJS(self):
|
|
return [d for d in self.dictionaries if d.needsConversionToJS]
|
|
|
|
def getDictionariesConvertibleFromJS(self):
|
|
return [d for d in self.dictionaries if d.needsConversionFromJS]
|
|
|
|
def getDictionaryIfExists(self, dictionaryName):
|
|
return self.dictionariesByName.get(dictionaryName, None)
|
|
|
|
|
|
class NoSuchDescriptorError(TypeError):
|
|
def __init__(self, str):
|
|
TypeError.__init__(self, str)
|
|
|
|
|
|
def methodReturnsJSObject(method):
|
|
assert method.isMethod()
|
|
|
|
for signature in method.signatures():
|
|
returnType = signature[0]
|
|
if returnType.isObject() or returnType.isSpiderMonkeyInterface():
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def MemberIsLegacyUnforgeable(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.isLegacyUnforgeable()
|
|
or 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)
|
|
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)
|
|
|
|
# If we're concrete, we need to crawl our ancestor interfaces and mark
|
|
# them as having a concrete descendant.
|
|
concreteDefault = (
|
|
not self.interface.isExternal()
|
|
and not self.interface.isCallback()
|
|
and not self.interface.isNamespace()
|
|
and
|
|
# We're going to assume that leaf interfaces are
|
|
# concrete; otherwise what's the point? Also
|
|
# interfaces with constructors had better be
|
|
# concrete; otherwise how can you construct them?
|
|
(
|
|
not self.interface.hasChildInterfaces()
|
|
or self.interface.ctor() is not None
|
|
)
|
|
)
|
|
|
|
self.concrete = desc.get("concrete", concreteDefault)
|
|
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,
|
|
"LegacyCaller": None,
|
|
}
|
|
|
|
self.hasDefaultToJSON = False
|
|
|
|
# Stringifiers 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.isDefaultToJSON():
|
|
self.hasDefaultToJSON = True
|
|
|
|
# We keep track of instrumente props for all non-external interfaces.
|
|
self.instrumentedProps = []
|
|
instrumentedProps = self.interface.getExtendedAttribute("InstrumentedProps")
|
|
if instrumentedProps:
|
|
# It's actually a one-element list, with the list
|
|
# we want as the only element.
|
|
self.instrumentedProps = instrumentedProps[0]
|
|
|
|
# Check that we don't have duplicated instrumented props.
|
|
uniqueInstrumentedProps = set(self.instrumentedProps)
|
|
if len(uniqueInstrumentedProps) != len(self.instrumentedProps):
|
|
duplicates = [
|
|
p
|
|
for p in uniqueInstrumentedProps
|
|
if self.instrumentedProps.count(p) > 1
|
|
]
|
|
raise TypeError(
|
|
"Duplicated instrumented properties: %s.\n%s"
|
|
% (duplicates, self.interface.location)
|
|
)
|
|
|
|
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.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.isMaybeCrossOriginObject()
|
|
)
|
|
|
|
if self.proxy:
|
|
if self.isMaybeCrossOriginObject() and (
|
|
self.supportsIndexedProperties() or self.supportsNamedProperties()
|
|
):
|
|
raise TypeError(
|
|
"We don't support named or indexed "
|
|
"properties on maybe-cross-origin objects. "
|
|
"This lets us assume that their proxy "
|
|
"hooks are never called via Xrays. "
|
|
"Fix %s.\n%s" % (self.interface, self.interface.location)
|
|
)
|
|
|
|
if not self.operations["IndexedGetter"] and (
|
|
self.operations["IndexedSetter"]
|
|
or self.operations["IndexedDeleter"]
|
|
):
|
|
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"]
|
|
):
|
|
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.implicitJSContext is a list of names of methods and attributes
|
|
# that need a JSContext.
|
|
if self.interface.isJSImplemented():
|
|
self.implicitJSContext = ["constructor"]
|
|
else:
|
|
self.implicitJSContext = desc.get("implicitJSContext", [])
|
|
assert isinstance(self.implicitJSContext, list)
|
|
|
|
self._binaryNames = {}
|
|
|
|
if not self.interface.isExternal():
|
|
|
|
def maybeAddBinaryName(member):
|
|
binaryName = member.getExtendedAttribute("BinaryName")
|
|
if binaryName:
|
|
assert isinstance(binaryName, list)
|
|
assert len(binaryName) == 1
|
|
self._binaryNames.setdefault(member.identifier.name, binaryName[0])
|
|
|
|
for member in self.interface.members:
|
|
if not member.isAttr() and not member.isMethod():
|
|
continue
|
|
maybeAddBinaryName(member)
|
|
|
|
ctor = self.interface.ctor()
|
|
if ctor:
|
|
maybeAddBinaryName(ctor)
|
|
|
|
# Some default binary names for cases when nothing else got set.
|
|
self._binaryNames.setdefault("__legacycaller", "LegacyCall")
|
|
self._binaryNames.setdefault("__stringifier", "Stringify")
|
|
|
|
# Build the prototype chain.
|
|
self.prototypeChain = []
|
|
self.needsMissingPropUseCounters = False
|
|
parent = interface
|
|
while parent:
|
|
self.needsMissingPropUseCounters = (
|
|
self.needsMissingPropUseCounters
|
|
or parent.getExtendedAttribute("InstrumentedProps")
|
|
)
|
|
self.prototypeChain.insert(0, parent.identifier.name)
|
|
parent = parent.parent
|
|
config.maxProtoChainLength = max(
|
|
config.maxProtoChainLength, len(self.prototypeChain)
|
|
)
|
|
|
|
self.hasOrdinaryObjectPrototype = desc.get("hasOrdinaryObjectPrototype", False)
|
|
|
|
def binaryNameFor(self, name):
|
|
return self._binaryNames.get(name, name)
|
|
|
|
@property
|
|
def prototypeNameChain(self):
|
|
return [self.getDescriptor(p).name for p in self.prototypeChain]
|
|
|
|
@property
|
|
def parentPrototypeName(self):
|
|
if len(self.prototypeChain) == 1:
|
|
return None
|
|
return self.getDescriptor(self.prototypeChain[-2]).name
|
|
|
|
def hasInterfaceOrInterfacePrototypeObject(self):
|
|
return (
|
|
self.interface.hasInterfaceObject()
|
|
or self.interface.hasInterfacePrototypeObject()
|
|
)
|
|
|
|
@property
|
|
def hasNamedPropertiesObject(self):
|
|
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 maybeAppendNeedsErrorResultToAttrs(attrs, throws):
|
|
ensureValidThrowsExtendedAttribute(throws)
|
|
if throws is not None:
|
|
attrs.append("needsErrorResult")
|
|
|
|
def maybeAppendCanOOMToAttrs(attrs, canOOM):
|
|
ensureValidCanOOMExtendedAttribute(canOOM)
|
|
if canOOM is not None:
|
|
attrs.append("canOOM")
|
|
|
|
def maybeAppendNeedsSubjectPrincipalToAttrs(attrs, needsSubjectPrincipal):
|
|
if (
|
|
needsSubjectPrincipal is not None
|
|
and needsSubjectPrincipal is not True
|
|
and needsSubjectPrincipal != ["NonSystem"]
|
|
):
|
|
raise TypeError(
|
|
"Unknown value for 'NeedsSubjectPrincipal': %s"
|
|
% needsSubjectPrincipal[0]
|
|
)
|
|
|
|
if needsSubjectPrincipal is not None:
|
|
attrs.append("needsSubjectPrincipal")
|
|
if needsSubjectPrincipal == ["NonSystem"]:
|
|
attrs.append("needsNonSystemSubjectPrincipal")
|
|
|
|
name = member.identifier.name
|
|
throws = self.interface.isJSImplemented() or member.getExtendedAttribute(
|
|
"Throws"
|
|
)
|
|
canOOM = member.getExtendedAttribute("CanOOM")
|
|
needsSubjectPrincipal = member.getExtendedAttribute("NeedsSubjectPrincipal")
|
|
attrs = []
|
|
if name in self.implicitJSContext:
|
|
attrs.append("implicitJSContext")
|
|
if member.isMethod():
|
|
# JSObject-returning [NewObject] methods must be fallible,
|
|
# since they have to (fallibly) allocate the new JSObject.
|
|
if member.getExtendedAttribute("NewObject"):
|
|
if member.returnsPromise():
|
|
throws = True
|
|
elif methodReturnsJSObject(member):
|
|
canOOM = True
|
|
maybeAppendNeedsErrorResultToAttrs(attrs, throws)
|
|
maybeAppendCanOOMToAttrs(attrs, canOOM)
|
|
maybeAppendNeedsSubjectPrincipalToAttrs(attrs, needsSubjectPrincipal)
|
|
return attrs
|
|
|
|
assert member.isAttr()
|
|
assert bool(getter) != bool(setter)
|
|
if throws is None:
|
|
throwsAttr = "GetterThrows" if getter else "SetterThrows"
|
|
throws = member.getExtendedAttribute(throwsAttr)
|
|
maybeAppendNeedsErrorResultToAttrs(attrs, throws)
|
|
if canOOM is None:
|
|
canOOMAttr = "GetterCanOOM" if getter else "SetterCanOOM"
|
|
canOOM = member.getExtendedAttribute(canOOMAttr)
|
|
maybeAppendCanOOMToAttrs(attrs, canOOM)
|
|
if needsSubjectPrincipal is None:
|
|
needsSubjectPrincipalAttr = (
|
|
"GetterNeedsSubjectPrincipal"
|
|
if getter
|
|
else "SetterNeedsSubjectPrincipal"
|
|
)
|
|
needsSubjectPrincipal = member.getExtendedAttribute(
|
|
needsSubjectPrincipalAttr
|
|
)
|
|
maybeAppendNeedsSubjectPrincipalToAttrs(attrs, needsSubjectPrincipal)
|
|
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 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 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 hasCEReactions(self):
|
|
return any(
|
|
m.getExtendedAttribute("CEReactions") for m in self.interface.members
|
|
)
|
|
|
|
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",
|
|
]
|
|
|
|
def needsXrayNamedDeleterHook(self):
|
|
return self.operations["NamedDeleter"] is not None
|
|
|
|
def isGlobal(self):
|
|
"""
|
|
Returns true if this is the primary interface for a global object
|
|
of some sort.
|
|
"""
|
|
return self.interface.getExtendedAttribute("Global")
|
|
|
|
@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 (
|
|
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)
|
|
|
|
def getConfig(self):
|
|
return self.config
|
|
|
|
|
|
# Some utility methods
|
|
def getTypesFromDescriptor(descriptor, includeArgs=True, includeReturns=True):
|
|
"""
|
|
Get argument and/or return types for all members of the descriptor. By
|
|
default returns all argument types (which includes types of writable
|
|
attributes) and all return types (which includes types of all attributes).
|
|
"""
|
|
assert includeArgs or includeReturns # Must want _something_.
|
|
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
|
|
if includeReturns:
|
|
types.append(returnType)
|
|
if includeArgs:
|
|
types.extend(a.type for a in arguments)
|
|
|
|
types.extend(
|
|
a.type
|
|
for a in members
|
|
if (a.isAttr() and (includeReturns or (includeArgs and not a.readonly)))
|
|
)
|
|
|
|
if descriptor.interface.maplikeOrSetlikeOrIterable:
|
|
maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
|
|
if maplikeOrSetlikeOrIterable.isMaplike():
|
|
# The things we expand into may or may not correctly indicate in
|
|
# their formal IDL types what things we have as return values. For
|
|
# example, "keys" returns the moral equivalent of sequence<keyType>
|
|
# but just claims to return "object". Similarly, "values" returns
|
|
# the moral equivalent of sequence<valueType> but claims to return
|
|
# "object". And due to bug 1155340, "get" claims to return "any"
|
|
# instead of the right type. So let's just manually work around
|
|
# that lack of specificity. For our arguments, we already enforce
|
|
# the right types at the IDL level, so those will get picked up
|
|
# correctly.
|
|
assert maplikeOrSetlikeOrIterable.hasKeyType()
|
|
assert maplikeOrSetlikeOrIterable.hasValueType()
|
|
if includeReturns:
|
|
types.append(maplikeOrSetlikeOrIterable.keyType)
|
|
types.append(maplikeOrSetlikeOrIterable.valueType)
|
|
elif maplikeOrSetlikeOrIterable.isSetlike():
|
|
assert maplikeOrSetlikeOrIterable.hasKeyType()
|
|
assert maplikeOrSetlikeOrIterable.hasValueType()
|
|
assert (
|
|
maplikeOrSetlikeOrIterable.keyType
|
|
== maplikeOrSetlikeOrIterable.valueType
|
|
)
|
|
# As in the maplike case, we don't always declare our return values
|
|
# quite correctly.
|
|
if includeReturns:
|
|
types.append(maplikeOrSetlikeOrIterable.keyType)
|
|
else:
|
|
assert maplikeOrSetlikeOrIterable.isIterable()
|
|
# As in the maplike/setlike cases we don't do a good job of
|
|
# declaring our actual return types, while our argument types, if
|
|
# any, are declared fine.
|
|
if includeReturns:
|
|
if maplikeOrSetlikeOrIterable.hasKeyType():
|
|
types.append(maplikeOrSetlikeOrIterable.keyType)
|
|
if maplikeOrSetlikeOrIterable.hasValueType():
|
|
types.append(maplikeOrSetlikeOrIterable.valueType)
|
|
|
|
return types
|
|
|
|
|
|
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
|
|
|
|
|
|
def findInnermostType(t):
|
|
"""
|
|
Find the innermost type of the given type, unwrapping Promise and Record
|
|
types, as well as everything that unroll() unwraps.
|
|
"""
|
|
while True:
|
|
if t.isRecord():
|
|
t = t.inner
|
|
elif t.unroll() != t:
|
|
t = t.unroll()
|
|
elif t.isPromise():
|
|
t = t.promiseInnerType()
|
|
else:
|
|
return t
|
|
|
|
|
|
def getDependentDictionariesFromDictionary(d):
|
|
"""
|
|
Find all the dictionaries contained in the given dictionary, as ancestors or
|
|
members. This returns a generator.
|
|
"""
|
|
while d:
|
|
yield d
|
|
for member in d.members:
|
|
for next in getDictionariesFromType(member.type):
|
|
yield next
|
|
d = d.parent
|
|
|
|
|
|
def getDictionariesFromType(type):
|
|
"""
|
|
Find all the dictionaries contained in type. This can be used to find
|
|
dictionaries that need conversion to JS (by looking at types that get
|
|
converted to JS) or dictionaries that need conversion from JS (by looking at
|
|
types that get converted from JS).
|
|
|
|
This returns a generator.
|
|
"""
|
|
type = findInnermostType(type)
|
|
if type.isUnion():
|
|
# Look for dictionaries in all the member types
|
|
for t in type.flatMemberTypes:
|
|
for next in getDictionariesFromType(t):
|
|
yield next
|
|
elif type.isDictionary():
|
|
# Find the dictionaries that are itself, any of its ancestors, or
|
|
# contained in any of its member types.
|
|
for d in getDependentDictionariesFromDictionary(type.inner):
|
|
yield d
|
|
|
|
|
|
def getDictionariesConvertedToJS(descriptors, dictionaries, callbacks):
|
|
for desc in descriptors:
|
|
if desc.interface.isExternal():
|
|
continue
|
|
|
|
if desc.interface.isJSImplemented():
|
|
# For a JS-implemented interface, we need to-JS
|
|
# conversions for all the types involved.
|
|
for t in getTypesFromDescriptor(desc):
|
|
for d in getDictionariesFromType(t):
|
|
yield d
|
|
elif desc.interface.isCallback():
|
|
# For callbacks we only want to include the arguments, since that's
|
|
# where the to-JS conversion happens.
|
|
for t in getTypesFromDescriptor(desc, includeReturns=False):
|
|
for d in getDictionariesFromType(t):
|
|
yield d
|
|
else:
|
|
# For normal interfaces, we only want to include return values,
|
|
# since that's where to-JS conversion happens.
|
|
for t in getTypesFromDescriptor(desc, includeArgs=False):
|
|
for d in getDictionariesFromType(t):
|
|
yield d
|
|
|
|
for callback in callbacks:
|
|
# We only want to look at the arguments
|
|
sig = callback.signatures()[0]
|
|
for arg in sig[1]:
|
|
for d in getDictionariesFromType(arg.type):
|
|
yield d
|
|
|
|
for dictionary in dictionaries:
|
|
if dictionary.needsConversionToJS:
|
|
# It's explicitly flagged as needing to-JS conversion, and all its
|
|
# dependent dictionaries will need to-JS conversion too.
|
|
for d in getDependentDictionariesFromDictionary(dictionary):
|
|
yield d
|
|
|
|
|
|
def getDictionariesConvertedFromJS(descriptors, dictionaries, callbacks):
|
|
for desc in descriptors:
|
|
if desc.interface.isExternal():
|
|
continue
|
|
|
|
if desc.interface.isJSImplemented():
|
|
# For a JS-implemented interface, we need from-JS conversions for
|
|
# all the types involved.
|
|
for t in getTypesFromDescriptor(desc):
|
|
for d in getDictionariesFromType(t):
|
|
yield d
|
|
elif desc.interface.isCallback():
|
|
# For callbacks we only want to include the return value, since
|
|
# that's where teh from-JS conversion happens.
|
|
for t in getTypesFromDescriptor(desc, includeArgs=False):
|
|
for d in getDictionariesFromType(t):
|
|
yield d
|
|
else:
|
|
# For normal interfaces, we only want to include arguments values,
|
|
# since that's where from-JS conversion happens.
|
|
for t in getTypesFromDescriptor(desc, includeReturns=False):
|
|
for d in getDictionariesFromType(t):
|
|
yield d
|
|
|
|
for callback in callbacks:
|
|
# We only want to look at the return value
|
|
sig = callback.signatures()[0]
|
|
for d in getDictionariesFromType(sig[0]):
|
|
yield d
|
|
|
|
for dictionary in dictionaries:
|
|
if dictionary.needsConversionFromJS:
|
|
# It's explicitly flagged as needing from-JS conversion, and all its
|
|
# dependent dictionaries will need from-JS conversion too.
|
|
for d in getDependentDictionariesFromDictionary(dictionary):
|
|
yield d
|