Source code

Revision control

Other Tools

# 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/.
# Common codegen classes.
import os
import re
import string
import math
import textwrap
import functools
from perfecthash import PerfectHash
import six
from WebIDL import (
BuiltinTypes,
IDLBuiltinType,
IDLDefaultDictionaryValue,
IDLNullValue,
IDLSequenceType,
IDLType,
IDLAttribute,
IDLInterfaceMember,
IDLUndefinedValue,
IDLEmptySequenceValue,
IDLDictionary,
)
from Configuration import (
NoSuchDescriptorError,
getTypesFromDescriptor,
getTypesFromDictionary,
getTypesFromCallback,
getAllTypes,
Descriptor,
MemberIsUnforgeable,
iteratorNativeType,
)
AUTOGENERATED_WARNING_COMMENT = (
"/* THIS FILE IS AUTOGENERATED BY Codegen.py - DO NOT EDIT */\n\n"
)
AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT = (
"/* THIS FILE IS AUTOGENERATED FROM %s BY Codegen.py - DO NOT EDIT */\n\n"
)
ADDPROPERTY_HOOK_NAME = "_addProperty"
GETWRAPPERCACHE_HOOK_NAME = "_getWrapperCache"
FINALIZE_HOOK_NAME = "_finalize"
OBJECT_MOVED_HOOK_NAME = "_objectMoved"
CONSTRUCT_HOOK_NAME = "_constructor"
LEGACYCALLER_HOOK_NAME = "_legacycaller"
RESOLVE_HOOK_NAME = "_resolve"
MAY_RESOLVE_HOOK_NAME = "_mayResolve"
NEW_ENUMERATE_HOOK_NAME = "_newEnumerate"
ENUM_ENTRY_VARIABLE_NAME = "strings"
INSTANCE_RESERVED_SLOTS = 1
# This size is arbitrary. It is a power of 2 to make using it as a modulo
# operand cheap, and is usually around 1/3-1/5th of the set size (sometimes
# smaller for very large sets).
GLOBAL_NAMES_PHF_SIZE = 256
def memberReservedSlot(member, descriptor):
return (
"(DOM_INSTANCE_RESERVED_SLOTS + %d)"
% member.slotIndices[descriptor.interface.identifier.name]
)
def memberXrayExpandoReservedSlot(member, descriptor):
return (
"(xpc::JSSLOT_EXPANDO_COUNT + %d)"
% member.slotIndices[descriptor.interface.identifier.name]
)
def mayUseXrayExpandoSlots(descriptor, attr):
assert not attr.getExtendedAttribute("NewObject")
# For attributes whose type is a Gecko interface we always use
# slots on the reflector for caching. Also, for interfaces that
# don't want Xrays we obviously never use the Xray expando slot.
return descriptor.wantsXrays and not attr.type.isGeckoInterface()
def toStringBool(arg):
"""
Converts IDL/Python Boolean (True/False) to C++ Boolean (true/false)
"""
return str(not not arg).lower()
def toBindingNamespace(arg):
return arg + "_Binding"
def isTypeCopyConstructible(type):
# Nullable and sequence stuff doesn't affect copy-constructibility
type = type.unroll()
return (
type.isPrimitive()
or type.isString()
or type.isEnum()
or (type.isUnion() and CGUnionStruct.isUnionCopyConstructible(type))
or (
type.isDictionary()
and CGDictionary.isDictionaryCopyConstructible(type.inner)
)
or
# Interface types are only copy-constructible if they're Gecko
# interfaces. SpiderMonkey interfaces are not copy-constructible
# because of rooting issues.
(type.isInterface() and type.isGeckoInterface())
)
class CycleCollectionUnsupported(TypeError):
def __init__(self, message):
TypeError.__init__(self, message)
def idlTypeNeedsCycleCollection(type):
type = type.unroll() # Takes care of sequences and nullables
if (
(type.isPrimitive() and type.tag() in builtinNames)
or type.isEnum()
or type.isString()
or type.isAny()
or type.isObject()
or type.isSpiderMonkeyInterface()
):
return False
elif type.isCallback() or type.isPromise() or type.isGeckoInterface():
return True
elif type.isUnion():
return any(idlTypeNeedsCycleCollection(t) for t in type.flatMemberTypes)
elif type.isRecord():
if idlTypeNeedsCycleCollection(type.inner):
raise CycleCollectionUnsupported(
"Cycle collection for type %s is not supported" % type
)
return False
elif type.isDictionary():
return CGDictionary.dictionaryNeedsCycleCollection(type.inner)
else:
raise CycleCollectionUnsupported(
"Don't know whether to cycle-collect type %s" % type
)
def idlTypeNeedsCallContext(type, descriptor=None, allowTreatNonCallableAsNull=False):
"""
Returns whether the given type needs error reporting via a
BindingCallContext for JS-to-C++ conversions. This will happen when the
conversion can throw an exception due to logic in the IDL spec or
Gecko-specific security checks. In particular, a type needs a
BindingCallContext if and only if the JS-to-C++ conversion for that type can
end up calling ThrowErrorMessage.
For some types this depends on the descriptor (e.g. because we do certain
checks only for some kinds of interfaces).
The allowTreatNonCallableAsNull optimization is there so we can avoid
generating an unnecessary BindingCallContext for all the event handler
attribute setters.
"""
while True:
if type.isSequence():
# Sequences can always throw "not an object"
return True
if type.nullable():
# treatNonObjectAsNull() and treatNonCallableAsNull() are
# only sane things to test on nullable types, so do that now.
if (
allowTreatNonCallableAsNull
and type.isCallback()
and (type.treatNonObjectAsNull() or type.treatNonCallableAsNull())
):
# This can't throw. so never needs a method description.
return False
type = type.inner
else:
break
# The float check needs to come before the isPrimitive() check,
# because floats are primitives too.
if type.isFloat():
# Floats can throw if restricted.
return not type.isUnrestricted()
if type.isPrimitive() and type.tag() in builtinNames:
# Numbers can throw if enforcing range.
return type.hasEnforceRange()
if type.isEnum():
# Can throw on invalid value.
return True
if type.isString():
# Can throw if it's a ByteString
return type.isByteString()
if type.isAny():
# JS-implemented interfaces do extra security checks so need a
# method description here. If we have no descriptor, this
# might be JS-implemented thing, so it will do the security
# check and we need the method description.
return not descriptor or descriptor.interface.isJSImplemented()
if type.isPromise():
# JS-to-Promise conversion won't cause us to throw any
# specific exceptions, so does not need a method description.
return False
if (
type.isObject()
or type.isInterface()
or type.isCallback()
or type.isDictionary()
or type.isRecord()
):
# These can all throw if a primitive is passed in, at the very least.
# There are some rare cases when we know we have an object, but those
# are not worth the complexity of optimizing for.
#
# Note that we checked the [TreatNonObjectAsNull] case already when
# unwrapping nullables.
return True
if type.isUnion():
# Can throw if a type not in the union is passed in.
return True
if type.isVoid():
# Clearly doesn't need a method description; we can only get here from
# CGHeaders trying to decide whether to include the method description
# header.
return False
raise TypeError("Don't know whether type '%s' needs a method description" % type)
# TryPreserveWrapper uses the addProperty hook to preserve the wrapper of
# non-nsISupports cycle collected objects, so if wantsAddProperty is changed
# to not cover that case then TryPreserveWrapper will need to be changed.
def wantsAddProperty(desc):
return desc.concrete and desc.wrapperCache and not desc.isGlobal()
def wantsGetWrapperCache(desc):
return (
desc.concrete and desc.wrapperCache and not desc.isGlobal() and not desc.proxy
)
# We'll want to insert the indent at the beginnings of lines, but we
# don't want to indent empty lines. So only indent lines that have a
# non-newline character on them.
lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE)
def indent(s, indentLevel=2):
"""
Indent C++ code.
Weird secret feature: this doesn't indent lines that start with # (such as
#include lines or #ifdef/#endif).
"""
if s == "":
return s
return re.sub(lineStartDetector, indentLevel * " ", s)
# dedent() and fill() are often called on the same string multiple
# times. We want to memoize their return values so we don't keep
# recomputing them all the time.
def memoize(fn):
"""
Decorator to memoize a function of one argument. The cache just
grows without bound.
"""
cache = {}
@functools.wraps(fn)
def wrapper(arg):
retval = cache.get(arg)
if retval is None:
retval = cache[arg] = fn(arg)
return retval
return wrapper
@memoize
def dedent(s):
"""
Remove all leading whitespace from s, and remove a blank line
at the beginning.
"""
if s.startswith("\n"):
s = s[1:]
return textwrap.dedent(s)
# This works by transforming the fill()-template to an equivalent
# string.Template.
fill_multiline_substitution_re = re.compile(r"( *)\$\*{(\w+)}(\n)?")
find_substitutions = re.compile(r"\${")
@memoize
def compile_fill_template(template):
"""
Helper function for fill(). Given the template string passed to fill(),
do the reusable part of template processing and return a pair (t,
argModList) that can be used every time fill() is called with that
template argument.
argsModList is list of tuples that represent modifications to be
made to args. Each modification has, in order: i) the arg name,
ii) the modified name, iii) the indent depth.
"""
t = dedent(template)
assert t.endswith("\n") or "\n" not in t
argModList = []
def replace(match):
"""
Replaces a line like ' $*{xyz}\n' with '${xyz_n}',
where n is the indent depth, and add a corresponding entry to
argModList.
Note that this needs to close over argModList, so it has to be
defined inside compile_fill_template().
"""
indentation, name, nl = match.groups()
depth = len(indentation)
# Check that $*{xyz} appears by itself on a line.
prev = match.string[: match.start()]
if (prev and not prev.endswith("\n")) or nl is None:
raise ValueError(
"Invalid fill() template: $*{%s} must appear by itself on a line" % name
)
# Now replace this whole line of template with the indented equivalent.
modified_name = name + "_" + str(depth)
argModList.append((name, modified_name, depth))
return "${" + modified_name + "}"
t = re.sub(fill_multiline_substitution_re, replace, t)
if not re.search(find_substitutions, t):
raise TypeError("Using fill() when dedent() would do.")
return (string.Template(t), argModList)
def fill(template, **args):
"""
Convenience function for filling in a multiline template.
`fill(template, name1=v1, name2=v2)` is a lot like
`string.Template(template).substitute({"name1": v1, "name2": v2})`.
However, it's shorter, and has a few nice features:
* If `template` is indented, fill() automatically dedents it!
This makes code using fill() with Python's multiline strings
much nicer to look at.
* If `template` starts with a blank line, fill() strips it off.
(Again, convenient with multiline strings.)
* fill() recognizes a special kind of substitution
of the form `$*{name}`.
Use this to paste in, and automatically indent, multiple lines.
(Mnemonic: The `*` is for "multiple lines").
A `$*` substitution must appear by itself on a line, with optional
preceding indentation (spaces only). The whole line is replaced by the
corresponding keyword argument, indented appropriately. If the
argument is an empty string, no output is generated, not even a blank
line.
"""
t, argModList = compile_fill_template(template)
# Now apply argModList to args
for (name, modified_name, depth) in argModList:
if not (args[name] == "" or args[name].endswith("\n")):
raise ValueError(
"Argument %s with value %r is missing a newline" % (name, args[name])
)
args[modified_name] = indent(args[name], depth)
return t.substitute(args)
class CGThing:
"""
Abstract base class for things that spit out code.
"""
def __init__(self):
pass # Nothing for now
def declare(self):
"""Produce code for a header file."""
assert False # Override me!
def define(self):
"""Produce code for a cpp file."""
assert False # Override me!
def deps(self):
"""Produce the deps for a pp file"""
assert False # Override me!
class CGStringTable(CGThing):
"""
Generate a string table for the given strings with a function accessor:
const char *accessorName(unsigned int index) {
static const char table[] = "...";
static const uint16_t indices = { ... };
return &table[indices[index]];
}
This is more efficient than the more natural:
const char *table[] = {
...
};
The uint16_t indices are smaller than the pointer equivalents, and the
string table requires no runtime relocations.
"""
def __init__(self, accessorName, strings, static=False):
CGThing.__init__(self)
self.accessorName = accessorName
self.strings = strings
self.static = static
def declare(self):
if self.static:
return ""
return "extern const char *%s(unsigned int aIndex);\n" % self.accessorName
def define(self):
table = ' "\\0" '.join('"%s"' % s for s in self.strings)
indices = []
currentIndex = 0
for s in self.strings:
indices.append(currentIndex)
currentIndex += len(s) + 1 # for the null terminator
return fill(
"""
${static}const char *${name}(unsigned int aIndex)
{
static const char table[] = ${table};
static const uint16_t indices[] = { ${indices} };
static_assert(${currentIndex} <= UINT16_MAX, "string table overflow!");
return &table[indices[aIndex]];
}
""",
static="static " if self.static else "",
name=self.accessorName,
table=table,
indices=", ".join("%d" % index for index in indices),
currentIndex=currentIndex,
)
class CGNativePropertyHooks(CGThing):
"""
Generate a NativePropertyHooks for a given descriptor
"""
def __init__(self, descriptor, properties):
CGThing.__init__(self)
self.descriptor = descriptor
self.properties = properties
def declare(self):
if not self.descriptor.wantsXrays:
return ""
return dedent(
"""
// We declare this as an array so that retrieving a pointer to this
// binding's property hooks only requires compile/link-time resolvable
// address arithmetic. Declaring it as a pointer instead would require
// doing a run-time load to fetch a pointer to this binding's property
// hooks. And then structures which embedded a pointer to this structure
// would require a run-time load for proper initialization, which would
// then induce static constructors. Lots of static constructors.
extern const NativePropertyHooks sNativePropertyHooks[];
"""
)
def define(self):
if not self.descriptor.wantsXrays:
return ""
deleteNamedProperty = "nullptr"
if (
self.descriptor.concrete
and self.descriptor.proxy
and not self.descriptor.isMaybeCrossOriginObject()
):
resolveOwnProperty = "ResolveOwnProperty"
enumerateOwnProperties = "EnumerateOwnProperties"
if self.descriptor.needsXrayNamedDeleterHook():
deleteNamedProperty = "DeleteNamedProperty"
elif self.descriptor.needsXrayResolveHooks():
resolveOwnProperty = "ResolveOwnPropertyViaResolve"
enumerateOwnProperties = "EnumerateOwnPropertiesViaGetOwnPropertyNames"
else:
resolveOwnProperty = "nullptr"
enumerateOwnProperties = "nullptr"
if self.properties.hasNonChromeOnly():
regular = "sNativeProperties.Upcast()"
else:
regular = "nullptr"
if self.properties.hasChromeOnly():
chrome = "sChromeOnlyNativeProperties.Upcast()"
else:
chrome = "nullptr"
constructorID = "constructors::id::"
if self.descriptor.interface.hasInterfaceObject():
constructorID += self.descriptor.name
else:
constructorID += "_ID_Count"
prototypeID = "prototypes::id::"
if self.descriptor.interface.hasInterfacePrototypeObject():
prototypeID += self.descriptor.name
else:
prototypeID += "_ID_Count"
parentProtoName = self.descriptor.parentPrototypeName
parentHooks = (
toBindingNamespace(parentProtoName) + "::sNativePropertyHooks"
if parentProtoName
else "nullptr"
)
if self.descriptor.wantsXrayExpandoClass:
expandoClass = "&sXrayExpandoObjectClass"
else:
expandoClass = "&DefaultXrayExpandoObjectClass"
return fill(
"""
const NativePropertyHooks sNativePropertyHooks[] = { {
${resolveOwnProperty},
${enumerateOwnProperties},
${deleteNamedProperty},
{ ${regular}, ${chrome} },
${prototypeID},
${constructorID},
${parentHooks},
${expandoClass}
} };
""",
resolveOwnProperty=resolveOwnProperty,
enumerateOwnProperties=enumerateOwnProperties,
deleteNamedProperty=deleteNamedProperty,
regular=regular,
chrome=chrome,
prototypeID=prototypeID,
constructorID=constructorID,
parentHooks=parentHooks,
expandoClass=expandoClass,
)
def NativePropertyHooks(descriptor):
return (
"&sEmptyNativePropertyHooks"
if not descriptor.wantsXrays
else "sNativePropertyHooks"
)
def DOMClass(descriptor):
protoList = ["prototypes::id::" + proto for proto in descriptor.prototypeNameChain]
# Pad out the list to the right length with _ID_Count so we
# guarantee that all the lists are the same length. _ID_Count
# is never the ID of any prototype, so it's safe to use as
# padding.
protoList.extend(
["prototypes::id::_ID_Count"]
* (descriptor.config.maxProtoChainLength - len(protoList))
)
if descriptor.interface.isSerializable():
serializer = "Serialize"
else:
serializer = "nullptr"
if wantsGetWrapperCache(descriptor):
wrapperCacheGetter = GETWRAPPERCACHE_HOOK_NAME
else:
wrapperCacheGetter = "nullptr"
return fill(
"""
{ ${protoChain} },
std::is_base_of_v<nsISupports, ${nativeType}>,
${hooks},
FindAssociatedGlobalForNative<${nativeType}>::Get,
GetProtoObjectHandle,
GetCCParticipant<${nativeType}>::Get(),
${serializer},
${wrapperCacheGetter}
""",
protoChain=", ".join(protoList),
nativeType=descriptor.nativeType,
hooks=NativePropertyHooks(descriptor),
serializer=serializer,
wrapperCacheGetter=wrapperCacheGetter,
)
def InstanceReservedSlots(descriptor):
slots = INSTANCE_RESERVED_SLOTS + descriptor.interface.totalMembersInSlots
if descriptor.isMaybeCrossOriginObject():
# We need a slot for the cross-origin holder too.
if descriptor.interface.hasChildInterfaces():
raise TypeError(
"We don't support non-leaf cross-origin interfaces "
"like %s" % descriptor.interface.identifier.name
)
slots += 1
return slots
class CGDOMJSClass(CGThing):
"""
Generate a DOMJSClass for a given descriptor
"""
def __init__(self, descriptor):
CGThing.__init__(self)
self.descriptor = descriptor
def declare(self):
return ""
def define(self):
callHook = (
LEGACYCALLER_HOOK_NAME
if self.descriptor.operations["LegacyCaller"]
else "nullptr"
)
objectMovedHook = (
OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else "nullptr"
)
slotCount = InstanceReservedSlots(self.descriptor)
classFlags = "JSCLASS_IS_DOMJSCLASS | JSCLASS_FOREGROUND_FINALIZE | "
if self.descriptor.isGlobal():
classFlags += (
"JSCLASS_DOM_GLOBAL | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS)"
)
traceHook = "JS_GlobalObjectTraceHook"
reservedSlots = "JSCLASS_GLOBAL_APPLICATION_SLOTS"
else:
classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount
traceHook = "nullptr"
reservedSlots = slotCount
if self.descriptor.interface.hasProbablyShortLivingWrapper():
if not self.descriptor.wrapperCache:
raise TypeError(
"Need a wrapper cache to support nursery "
"allocation of DOM objects"
)
classFlags += " | JSCLASS_SKIP_NURSERY_FINALIZE"
if self.descriptor.interface.getExtendedAttribute("NeedResolve"):
resolveHook = RESOLVE_HOOK_NAME
mayResolveHook = MAY_RESOLVE_HOOK_NAME
newEnumerateHook = NEW_ENUMERATE_HOOK_NAME
elif self.descriptor.isGlobal():
resolveHook = "mozilla::dom::ResolveGlobal"
mayResolveHook = "mozilla::dom::MayResolveGlobal"
newEnumerateHook = "mozilla::dom::EnumerateGlobal"
else:
resolveHook = "nullptr"
mayResolveHook = "nullptr"
newEnumerateHook = "nullptr"
return fill(
"""
static const JSClassOps sClassOps = {
${addProperty}, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
${newEnumerate}, /* newEnumerate */
${resolve}, /* resolve */
${mayResolve}, /* mayResolve */
${finalize}, /* finalize */
${call}, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
${trace}, /* trace */
};
static const js::ClassExtension sClassExtension = {
${objectMoved} /* objectMovedOp */
};
static const DOMJSClass sClass = {
{ "${name}",
${flags},
&sClassOps,
JS_NULL_CLASS_SPEC,
&sClassExtension,
JS_NULL_OBJECT_OPS
},
$*{descriptor}
};
static_assert(${instanceReservedSlots} == DOM_INSTANCE_RESERVED_SLOTS,
"Must have the right minimal number of reserved slots.");
static_assert(${reservedSlots} >= ${slotCount},
"Must have enough reserved slots.");
""",
name=self.descriptor.interface.getClassName(),
flags=classFlags,
addProperty=ADDPROPERTY_HOOK_NAME
if wantsAddProperty(self.descriptor)
else "nullptr",
newEnumerate=newEnumerateHook,
resolve=resolveHook,
mayResolve=mayResolveHook,
finalize=FINALIZE_HOOK_NAME,
call=callHook,
trace=traceHook,
objectMoved=objectMovedHook,
descriptor=DOMClass(self.descriptor),
instanceReservedSlots=INSTANCE_RESERVED_SLOTS,
reservedSlots=reservedSlots,
slotCount=slotCount,
)
class CGDOMProxyJSClass(CGThing):
"""
Generate a DOMJSClass for a given proxy descriptor
"""
def __init__(self, descriptor):
CGThing.__init__(self)
self.descriptor = descriptor
def declare(self):
return ""
def define(self):
slotCount = InstanceReservedSlots(self.descriptor)
# We need one reserved slot (DOM_OBJECT_SLOT).
flags = ["JSCLASS_IS_DOMJSCLASS", "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount]
# We don't use an IDL annotation for JSCLASS_EMULATES_UNDEFINED because
# we don't want people ever adding that to any interface other than
# HTMLAllCollection. So just hardcode it here.
if self.descriptor.interface.identifier.name == "HTMLAllCollection":
flags.append("JSCLASS_EMULATES_UNDEFINED")
return fill(
"""
static const DOMJSClass sClass = {
PROXY_CLASS_DEF("${name}",
${flags}),
$*{descriptor}
};
""",
name=self.descriptor.interface.identifier.name,
flags=" | ".join(flags),
descriptor=DOMClass(self.descriptor),
)
class CGXrayExpandoJSClass(CGThing):
"""
Generate a JSClass for an Xray expando object. This is only
needed if we have members in slots (for [Cached] or [StoreInSlot]
stuff).
"""
def __init__(self, descriptor):
assert descriptor.interface.totalMembersInSlots != 0
assert descriptor.wantsXrays
assert descriptor.wantsXrayExpandoClass
CGThing.__init__(self)
self.descriptor = descriptor
def declare(self):
return ""
def define(self):
return fill(
"""
// This may allocate too many slots, because we only really need
// slots for our non-interface-typed members that we cache. But
// allocating slots only for those would make the slot index
// computations much more complicated, so let's do this the simple
// way for now.
DEFINE_XRAY_EXPANDO_CLASS(static, sXrayExpandoObjectClass, ${memberSlots});
""",
memberSlots=self.descriptor.interface.totalMembersInSlots,
)
def PrototypeIDAndDepth(descriptor):
prototypeID = "prototypes::id::"
if descriptor.interface.hasInterfacePrototypeObject():
prototypeID += descriptor.interface.identifier.name
depth = "PrototypeTraits<%s>::Depth" % prototypeID
else:
prototypeID += "_ID_Count"
depth = "0"
return (prototypeID, depth)
def InterfacePrototypeObjectProtoGetter(descriptor):
"""
Returns a tuple with two elements:
1) The name of the function to call to get the prototype to use for the
interface prototype object as a JSObject*.
2) The name of the function to call to get the prototype to use for the
interface prototype object as a JS::Handle<JSObject*> or None if no
such function exists.
"""
parentProtoName = descriptor.parentPrototypeName
if descriptor.hasNamedPropertiesObject:
protoGetter = "GetNamedPropertiesObject"
protoHandleGetter = None
elif parentProtoName is None:
if descriptor.interface.getExtendedAttribute("ExceptionClass"):
protoGetter = "JS::GetRealmErrorPrototype"
elif descriptor.interface.isIteratorInterface():
protoGetter = "JS::GetRealmIteratorPrototype"
else:
protoGetter = "JS::GetRealmObjectPrototype"
protoHandleGetter = None
else:
prefix = toBindingNamespace(parentProtoName)
protoGetter = prefix + "::GetProtoObject"
protoHandleGetter = prefix + "::GetProtoObjectHandle"
return (protoGetter, protoHandleGetter)
class CGPrototypeJSClass(CGThing):
def __init__(self, descriptor, properties):
CGThing.__init__(self)
self.descriptor = descriptor
self.properties = properties
def declare(self):
# We're purely for internal consumption
return ""
def define(self):
prototypeID, depth = PrototypeIDAndDepth(self.descriptor)
slotCount = "DOM_INTERFACE_PROTO_SLOTS_BASE"
# Globals handle unforgeables directly in Wrap() instead of
# via a holder.
if self.descriptor.hasUnforgeableMembers and not self.descriptor.isGlobal():
slotCount += (
" + 1 /* slot for the JSObject holding the unforgeable properties */"
)
(protoGetter, _) = InterfacePrototypeObjectProtoGetter(self.descriptor)
type = (
"eGlobalInterfacePrototype"
if self.descriptor.isGlobal()
else "eInterfacePrototype"
)
return fill(
"""
static const DOMIfaceAndProtoJSClass sPrototypeClass = {
{
"${name}Prototype",
JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
JS_NULL_OBJECT_OPS
},
${type},
false,
${prototypeID},
${depth},
${hooks},
nullptr,
${protoGetter}
};
""",
name=self.descriptor.interface.getClassName(),
slotCount=slotCount,
type=type,
hooks=NativePropertyHooks(self.descriptor),
prototypeID=prototypeID,
depth=depth,
protoGetter=protoGetter,
)
def InterfaceObjectProtoGetter(descriptor, forXrays=False):
"""
Returns a tuple with two elements:
1) The name of the function to call to get the prototype to use for the
interface object as a JSObject*.
2) The name of the function to call to get the prototype to use for the
interface prototype as a JS::Handle<JSObject*> or None if no such
function exists.
"""
parentInterface = descriptor.interface.parent
if parentInterface:
assert not descriptor.interface.isNamespace()
parentIfaceName = parentInterface.identifier.name
parentDesc = descriptor.getDescriptor(parentIfaceName)
prefix = toBindingNamespace(parentDesc.name)
protoGetter = prefix + "::GetConstructorObject"
protoHandleGetter = prefix + "::GetConstructorObjectHandle"
elif descriptor.interface.isNamespace():
if forXrays or not descriptor.interface.getExtendedAttribute("ProtoObjectHack"):
protoGetter = "JS::GetRealmObjectPrototype"
else:
protoGetter = "GetHackedNamespaceProtoObject"
protoHandleGetter = None
else:
protoGetter = "JS::GetRealmFunctionPrototype"
protoHandleGetter = None
return (protoGetter, protoHandleGetter)
class CGInterfaceObjectJSClass(CGThing):
def __init__(self, descriptor, properties):
CGThing.__init__(self)
self.descriptor = descriptor
self.properties = properties
def declare(self):
# We're purely for internal consumption
return ""
def define(self):
if self.descriptor.interface.ctor():
assert not self.descriptor.interface.isNamespace()
ctorname = CONSTRUCT_HOOK_NAME
elif self.descriptor.interface.isNamespace():
ctorname = "nullptr"
else:
ctorname = "ThrowingConstructor"
needsHasInstance = self.descriptor.interface.hasInterfacePrototypeObject()
prototypeID, depth = PrototypeIDAndDepth(self.descriptor)
slotCount = "DOM_INTERFACE_SLOTS_BASE"
if len(self.descriptor.interface.namedConstructors) > 0:
slotCount += " + %i /* slots for the named constructors */" % len(
self.descriptor.interface.namedConstructors
)
(protoGetter, _) = InterfaceObjectProtoGetter(self.descriptor, forXrays=True)
if ctorname == "ThrowingConstructor":
ret = ""
classOpsPtr = "&sBoringInterfaceObjectClassClassOps"
elif ctorname == "nullptr":
ret = ""
classOpsPtr = "JS_NULL_CLASS_OPS"
else:
ret = fill(
"""
static const JSClassOps sInterfaceObjectClassOps = {
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* enumerate */
nullptr, /* newEnumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
${ctorname}, /* call */
nullptr, /* hasInstance */
${ctorname}, /* construct */
nullptr, /* trace */
};
""",
ctorname=ctorname,
)
classOpsPtr = "&sInterfaceObjectClassOps"
if self.descriptor.interface.isNamespace():
classString = self.descriptor.interface.getExtendedAttribute("ClassString")
if classString is None:
classString = "Object"
else:
classString = classString[0]
funToString = "nullptr"
objectOps = "JS_NULL_OBJECT_OPS"
else:
classString = "Function"
funToString = (
'"function %s() {\\n [native code]\\n}"'
% self.descriptor.interface.identifier.name
)
# We need non-default ObjectOps so we can actually make
# use of our funToString.
objectOps = "&sInterfaceObjectClassObjectOps"
ret = ret + fill(
"""
static const DOMIfaceAndProtoJSClass sInterfaceObjectClass = {
{
"${classString}",
JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
${classOpsPtr},
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
${objectOps}
},
eInterface,
${needsHasInstance},
${prototypeID},
${depth},
${hooks},
${funToString},
${protoGetter}
};
""",
classString=classString,
slotCount=slotCount,
classOpsPtr=classOpsPtr,
hooks=NativePropertyHooks(self.descriptor),
objectOps=objectOps,
needsHasInstance=toStringBool(needsHasInstance),
prototypeID=prototypeID,
depth=depth,
funToString=funToString,
protoGetter=protoGetter,
)
return ret
class CGList(CGThing):
"""
Generate code for a list of GCThings. Just concatenates them together, with
an optional joiner string. "\n" is a common joiner.
"""
def __init__(self, children, joiner=""):
CGThing.__init__(self)
# Make a copy of the kids into a list, because if someone passes in a
# generator we won't be able to both declare and define ourselves, or
# define ourselves more than once!
self.children = list(children)
self.joiner = joiner
def append(self, child):
self.children.append(child)
def prepend(self, child):
self.children.insert(0, child)
def extend(self, kids):
self.children.extend(kids)
def join(self, iterable):
return self.joiner.join(s for s in iterable if len(s) > 0)
def declare(self):
return self.join(
child.declare() for child in self.children if child is not None
)
def define(self):
return self.join(child.define() for child in self.children if child is not None)
def deps(self):
deps = set()
for child in self.children:
if child is None:
continue
deps = deps.union(child.deps())
return deps
def __len__(self):
return len(self.children)
class CGGeneric(CGThing):
"""
A class that spits out a fixed string into the codegen. Can spit out a
separate string for the declaration too.
"""
def __init__(self, define="", declare=""):
self.declareText = declare
self.defineText = define
def declare(self):
return self.declareText
def define(self):
return self.defineText
def deps(self):
return set()
class CGIndenter(CGThing):
"""
A class that takes another CGThing and generates code that indents that
CGThing by some number of spaces. The default indent is two spaces.
"""
def __init__(self, child, indentLevel=2, declareOnly=False):
assert isinstance(child, CGThing)
CGThing.__init__(self)
self.child = child
self.indentLevel = indentLevel
self.declareOnly = declareOnly
def declare(self):
return indent(self.child.declare(), self.indentLevel)
def define(self):
defn = self.child.define()
if self.declareOnly:
return defn
else:
return indent(defn, self.indentLevel)
class CGWrapper(CGThing):
"""
Generic CGThing that wraps other CGThings with pre and post text.
"""
def __init__(
self,
child,
pre="",
post="",
declarePre=None,
declarePost=None,
definePre=None,
definePost=None,
declareOnly=False,
defineOnly=False,
reindent=False,
):
CGThing.__init__(self)
self.child = child
self.declarePre = declarePre or pre
self.declarePost = declarePost or post
self.definePre = definePre or pre
self.definePost = definePost or post
self.declareOnly = declareOnly
self.defineOnly = defineOnly
self.reindent = reindent
def declare(self):
if self.defineOnly:
return ""
decl = self.child.declare()
if self.reindent:
decl = self.reindentString(decl, self.declarePre)
return self.declarePre + decl + self.declarePost
def define(self):
if self.declareOnly:
return ""
defn = self.child.define()
if self.reindent:
defn = self.reindentString(defn, self.definePre)
return self.definePre + defn + self.definePost
@staticmethod
def reindentString(stringToIndent, widthString):
# We don't use lineStartDetector because we don't want to
# insert whitespace at the beginning of our _first_ line.
# Use the length of the last line of width string, in case
# it is a multiline string.
lastLineWidth = len(widthString.splitlines()[-1])
return stripTrailingWhitespace(
stringToIndent.replace("\n", "\n" + (" " * lastLineWidth))
)
def deps(self):
return self.child.deps()
class CGIfWrapper(CGList):
def __init__(self, child, condition):
CGList.__init__(
self,
[
CGWrapper(
CGGeneric(condition), pre="if (", post=") {\n", reindent=True
),
CGIndenter(child),
CGGeneric("}\n"),
],
)
class CGIfElseWrapper(CGList):
def __init__(self, condition, ifTrue, ifFalse):
CGList.__init__(
self,
[
CGWrapper(
CGGeneric(condition), pre="if (", post=") {\n", reindent=True
),
CGIndenter(ifTrue),
CGGeneric("} else {\n"),
CGIndenter(ifFalse),
CGGeneric("}\n"),
],
)
class CGElseChain(CGThing):
"""
Concatenate if statements in an if-else-if-else chain.
"""
def __init__(self, children):
self.children = [c for c in children if c is not None]
def declare(self):
assert False
def define(self):
if not self.children:
return ""
s = self.children[0].define()
assert s.endswith("\n")
for child in self.children[1:]:
code = child.define()
assert code.startswith("if") or code.startswith("{")
assert code.endswith("\n")
s = s.rstrip() + " else " + code
return s
class CGTemplatedType(CGWrapper):
def __init__(self, templateName, child, isConst=False, isReference=False):
if isinstance(child, list):
child = CGList(child, ", ")
const = "const " if isConst else ""
pre = "%s%s<" % (const, templateName)
ref = "&" if isReference else ""
post = ">%s" % ref
CGWrapper.__init__(self, child, pre=pre, post=post)
class CGNamespace(CGWrapper):
def __init__(self, namespace, child, declareOnly=False):
pre = "namespace %s {\n" % namespace
post = "} // namespace %s\n" % namespace
CGWrapper.__init__(self, child, pre=pre, post=post, declareOnly=declareOnly)
@staticmethod
def build(namespaces, child, declareOnly=False):
"""
Static helper method to build multiple wrapped namespaces.
"""
if not namespaces:
return CGWrapper(child, declareOnly=declareOnly)
inner = CGNamespace.build(namespaces[1:], child, declareOnly=declareOnly)
return CGNamespace(namespaces[0], inner, declareOnly=declareOnly)
class CGIncludeGuard(CGWrapper):
"""
Generates include guards for a header.
"""
def __init__(self, prefix, child):
"""|prefix| is the filename without the extension."""
define = "mozilla_dom_%s_h" % prefix
CGWrapper.__init__(
self,
child,
declarePre="#ifndef %s\n#define %s\n\n" % (define, define),
declarePost="\n#endif // %s\n" % define,
)
class CGHeaders(CGWrapper):
"""
Generates the appropriate include statements.
"""
def __init__(
self,
descriptors,
dictionaries,
callbacks,
callbackDescriptors,
declareIncludes,
defineIncludes,
prefix,
child,
config=None,
jsImplementedDescriptors=[],
):
"""
Builds a set of includes to cover |descriptors|.
Also includes the files in |declareIncludes| in the header
file and the files in |defineIncludes| in the .cpp.
|prefix| contains the basename of the file that we generate include
statements for.
"""
# Determine the filenames for which we need headers.
interfaceDeps = [d.interface for d in descriptors]
ancestors = []
for iface in interfaceDeps:
if iface.parent:
# We're going to need our parent's prototype, to use as the
# prototype of our prototype object.
ancestors.append(iface.parent)
# And if we have an interface object, we'll need the nearest
# ancestor with an interface object too, so we can use its
# interface object as the proto of our interface object.
if iface.hasInterfaceObject():
parent = iface.parent
while parent and not parent.hasInterfaceObject():
parent = parent.parent
if parent:
ancestors.append(parent)
interfaceDeps.extend(ancestors)
# Include parent interface headers needed for default toJSON code.
jsonInterfaceParents = []
for desc in descriptors:
if not desc.hasDefaultToJSON:
continue
parent = desc.interface.parent
while parent:
parentDesc = desc.getDescriptor(parent.identifier.name)
if parentDesc.hasDefaultToJSON:
jsonInterfaceParents.append(parentDesc.interface)
parent = parent.parent
interfaceDeps.extend(jsonInterfaceParents)
bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps)
# Grab all the implementation declaration files we need.
implementationIncludes = set(
d.headerFile for d in descriptors if d.needsHeaderInclude()
)
# Now find all the things we'll need as arguments because we
# need to wrap or unwrap them.
bindingHeaders = set()
declareIncludes = set(declareIncludes)
def addHeadersForType(typeAndPossibleDictionary):
"""
Add the relevant headers for this type. We use dictionary, if
passed, to decide what to do with interface types.
"""
t, dictionary = typeAndPossibleDictionary
# Dictionaries have members that need to be actually
# declared, not just forward-declared.
if dictionary:
headerSet = declareIncludes
else:
headerSet = bindingHeaders
# Strip off outer layers and add headers they might require. (This
# is conservative: only nullable non-pointer types need Nullable.h;
# only sequences outside unions need ForOfIterator.h; only functions
# that return, and attributes that are, sequences in interfaces need
# Array.h, &c.)
unrolled = t
while True:
if idlTypeNeedsCallContext(unrolled):
bindingHeaders.add("mozilla/dom/BindingCallContext.h")
if unrolled.nullable():
headerSet.add("mozilla/dom/Nullable.h")
elif unrolled.isSequence():
bindingHeaders.add("js/Array.h")
bindingHeaders.add("js/ForOfIterator.h")
else:
break
unrolled = unrolled.inner
if unrolled.isUnion():
headerSet.add(self.getUnionDeclarationFilename(config, unrolled))
bindingHeaders.add("mozilla/dom/UnionConversions.h")
elif unrolled.isPromise():
# See comment in the isInterface() case for why we add
# Promise.h to headerSet, not bindingHeaders.
headerSet.add("mozilla/dom/Promise.h")
# We need ToJSValue to do the Promise to JS conversion.
bindingHeaders.add("mozilla/dom/ToJSValue.h")
elif unrolled.isInterface():
if unrolled.isSpiderMonkeyInterface():
bindingHeaders.add("jsfriendapi.h")
if jsImplementedDescriptors:
# Since we can't forward-declare typed array types
# (because they're typedefs), we have to go ahead and
# just include their header if we need to have functions
# taking references to them declared in that header.
headerSet = declareIncludes
if unrolled.isReadableStream():
headerSet.add("mozilla/dom/ReadableStream.h")
else:
headerSet.add("mozilla/dom/TypedArray.h")
else:
try:
typeDesc = config.getDescriptor(unrolled.inner.identifier.name)
except NoSuchDescriptorError:
return
# Dictionaries with interface members rely on the
# actual class definition of that interface member
# being visible in the binding header, because they
# store them in RefPtr and have inline
# constructors/destructors.
#
# XXXbz maybe dictionaries with interface members
# should just have out-of-line constructors and
# destructors?
headerSet.add(typeDesc.headerFile)
elif unrolled.isDictionary():
headerSet.add(self.getDeclarationFilename(unrolled.inner))
# And if it needs rooting, we need RootedDictionary too
if typeNeedsRooting(unrolled):
headerSet.add("mozilla/dom/RootedDictionary.h")
elif unrolled.isCallback():
headerSet.add(self.getDeclarationFilename(unrolled.callback))
elif unrolled.isFloat() and not unrolled.isUnrestricted():
# Restricted floats are tested for finiteness
bindingHeaders.add("mozilla/FloatingPoint.h")
bindingHeaders.add("mozilla/dom/PrimitiveConversions.h")
elif unrolled.isEnum():
filename = self.getDeclarationFilename(unrolled.inner)
declareIncludes.add(filename)
elif unrolled.isPrimitive():
bindingHeaders.add("mozilla/dom/PrimitiveConversions.h")
elif unrolled.isRecord():
if dictionary or jsImplementedDescriptors:
declareIncludes.add("mozilla/dom/Record.h")
else:
bindingHeaders.add("mozilla/dom/Record.h")
# Also add headers for the type the record is
# parametrized over, if needed.
addHeadersForType((t.inner, dictionary))
for t in getAllTypes(
descriptors + callbackDescriptors, dictionaries, callbacks
):
addHeadersForType(t)
def addHeaderForFunc(func, desc):
if func is None:
return
# Include the right class header, which we can only do
# if this is a class member function.
if desc is not None and not desc.headerIsDefault:
# An explicit header file was provided, assume that we know
# what we're doing.
return
if "::" in func:
# Strip out the function name and convert "::" to "/"
bindingHeaders.add("/".join(func.split("::")[:-1]) + ".h")
# Now for non-callback descriptors make sure we include any
# headers needed by Func declarations and other things like that.
for desc in descriptors:
# If this is an iterator interface generated for a separate
# iterable interface, skip generating type includes, as we have
# what we need in IterableIterator.h
if desc.interface.isIteratorInterface():
continue
for m in desc.interface.members:
addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), desc)
staticTypeOverride = PropertyDefiner.getStringAttr(
m, "StaticClassOverride"
)
if staticTypeOverride:
bindingHeaders.add("/".join(staticTypeOverride.split("::")) + ".h")
# getExtendedAttribute() returns a list, extract the entry.
funcList = desc.interface.getExtendedAttribute("Func")
if funcList is not None:
addHeaderForFunc(funcList[0], desc)
if desc.interface.maplikeOrSetlikeOrIterable:
# We need ToJSValue.h for maplike/setlike type conversions
bindingHeaders.add("mozilla/dom/ToJSValue.h")
# Add headers for the key and value types of the
# maplike/setlike/iterable, since they'll be needed for
# convenience functions
if desc.interface.maplikeOrSetlikeOrIterable.hasKeyType():
addHeadersForType(
(desc.interface.maplikeOrSetlikeOrIterable.keyType, None)
)
if desc.interface.maplikeOrSetlikeOrIterable.hasValueType():
addHeadersForType(
(desc.interface.maplikeOrSetlikeOrIterable.valueType, None)
)
for d in dictionaries:
if d.parent:
declareIncludes.add(self.getDeclarationFilename(d.parent))
bindingHeaders.add(self.getDeclarationFilename(d))
for m in d.members:
addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), None)
# No need to worry about Func on members of ancestors, because that
# will happen automatically in whatever files those ancestors live
# in.
for c in callbacks:
bindingHeaders.add(self.getDeclarationFilename(c))
for c in callbackDescriptors:
bindingHeaders.add(self.getDeclarationFilename(c.interface))
if len(callbacks) != 0:
# We need CallbackFunction to serve as our parent class
declareIncludes.add("mozilla/dom/CallbackFunction.h")
# And we need ToJSValue.h so we can wrap "this" objects
declareIncludes.add("mozilla/dom/ToJSValue.h")
if len(callbackDescriptors) != 0 or len(jsImplementedDescriptors) != 0:
# We need CallbackInterface to serve as our parent class
declareIncludes.add("mozilla/dom/CallbackInterface.h")
# And we need ToJSValue.h so we can wrap "this" objects
declareIncludes.add("mozilla/dom/ToJSValue.h")
# Also need to include the headers for ancestors of
# JS-implemented interfaces.
for jsImplemented in jsImplementedDescriptors:
jsParent = jsImplemented.interface.parent
if jsParent:
parentDesc = jsImplemented.getDescriptor(jsParent.identifier.name)
declareIncludes.add(parentDesc.jsImplParentHeader)
# Now make sure we're not trying to include the header from inside itself
declareIncludes.discard(prefix + ".h")
# Let the machinery do its thing.
def _includeString(includes):
def headerName(include):
# System headers are specified inside angle brackets.
if include.startswith("<"):
return include
# Non-system headers need to be placed in quotes.
return '"%s"' % include
return "".join(["#include %s\n" % headerName(i) for i in includes]) + "\n"
CGWrapper.__init__(
self,
child,
declarePre=_includeString(sorted(declareIncludes)),
definePre=_includeString(
sorted(
set(defineIncludes)
| bindingIncludes
| bindingHeaders
| implementationIncludes
)
),
)
@staticmethod
def getDeclarationFilename(decl):
# 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(decl.filename())
return basename.replace(".webidl", "Binding.h")
@staticmethod
def getUnionDeclarationFilename(config, unionType):
assert unionType.isUnion()
assert unionType.unroll() == unionType
# If a union is "defined" in multiple files, it goes in UnionTypes.h.
if len(config.filenamesPerUnion[unionType.name]) > 1:
return "mozilla/dom/UnionTypes.h"
# If a union is defined by a built-in typedef, it also goes in
# UnionTypes.h.
assert len(config.filenamesPerUnion[unionType.name]) == 1
if "<unknown>" in config.filenamesPerUnion[unionType.name]:
return "mozilla/dom/UnionTypes.h"
return CGHeaders.getDeclarationFilename(unionType)
def SortedDictValues(d):
"""
Returns a list of values from the dict sorted by key.
"""
return [v for k, v in sorted(d.items())]
def UnionsForFile(config, webIDLFile):
"""
Returns a list of union types for all union types that are only used in
webIDLFile. If webIDLFile is None this will return the list of tuples for
union types that are used in more than one WebIDL file.
"""
return config.unionsPerFilename.get(webIDLFile, [])
def UnionTypes(unionTypes, config):
"""
The unionTypes argument should be a list of union types. This is typically
the list generated by UnionsForFile.
Returns a tuple containing a set of header filenames to include in
the header for the types in unionTypes, a set of header filenames to
include in the implementation file for the types in unionTypes, a set
of tuples containing a type declaration and a boolean if the type is a
struct for member types of the union, a list of traverse methods,
unlink methods and a list of union types. These last three lists only
contain unique union types.
"""
headers = set()
implheaders = set()
declarations = set()
unionStructs = dict()
traverseMethods = dict()
unlinkMethods = dict()
for t in unionTypes:
name = str(t)
if name not in unionStructs:
unionStructs[name] = t
def addHeadersForType(f):
if f.nullable():
headers.add("mozilla/dom/Nullable.h")
isSequence = f.isSequence()
if isSequence:
# Dealing with sequences requires for-of-compatible
# iteration.
implheaders.add("js/ForOfIterator.h")
# Sequences can always throw "not an object" exceptions.
implheaders.add("mozilla/dom/BindingCallContext.h")
f = f.unroll()
if idlTypeNeedsCallContext(f):
implheaders.add("mozilla/dom/BindingCallContext.h")
if f.isPromise():
headers.add("mozilla/dom/Promise.h")
# We need ToJSValue to do the Promise to JS conversion.
headers.add("mozilla/dom/ToJSValue.h")
elif f.isInterface():
if f.isSpiderMonkeyInterface():
headers.add("js/RootingAPI.h")
headers.add("js/Value.h")
if f.isReadableStream():
headers.add("mozilla/dom/ReadableStream.h")
else:
headers.add("mozilla/dom/TypedArray.h")
else:
try:
typeDesc = config.getDescriptor(f.inner.identifier.name)
except NoSuchDescriptorError:
return
if typeDesc.interface.isCallback() or isSequence:
# Callback interfaces always use strong refs, so
# we need to include the right header to be able
# to Release() in our inlined code.
#
# Similarly, sequences always contain strong
# refs, so we'll need the header to handler
# those.
headers.add(typeDesc.headerFile)
elif typeDesc.interface.identifier.name == "WindowProxy":
# In UnionTypes.h we need to see the declaration of the
# WindowProxyHolder that we use to store the WindowProxy, so
# we have its sizeof and know how big to make our union.
headers.add(typeDesc.headerFile)
else:
declarations.add((typeDesc.nativeType, False))
implheaders.add(typeDesc.headerFile)
elif f.isDictionary():
# For a dictionary, we need to see its declaration in
# UnionTypes.h so we have its sizeof and know how big to
# make our union.
headers.add(CGHeaders.getDeclarationFilename(f.inner))
# And if it needs rooting, we need RootedDictionary too
if typeNeedsRooting(f):
headers.add("mozilla/dom/RootedDictionary.h")
elif f.isFloat() and not f.isUnrestricted():
# Restricted floats are tested for finiteness
implheaders.add("mozilla/FloatingPoint.h")
implheaders.add("mozilla/dom/PrimitiveConversions.h")
elif f.isEnum():
# Need to see the actual definition of the enum,
# unfortunately.
headers.add(CGHeaders.getDeclarationFilename(f.inner))
elif f.isPrimitive():
implheaders.add("mozilla/dom/PrimitiveConversions.h")
elif f.isCallback():
# Callbacks always use strong refs, so we need to include
# the right header to be able to Release() in our inlined
# code.
headers.add(CGHeaders.getDeclarationFilename(f.callback))
elif f.isRecord():
headers.add("mozilla/dom/Record.h")
# And add headers for the type we're parametrized over
addHeadersForType(f.inner)
implheaders.add(CGHeaders.getUnionDeclarationFilename(config, t))
for f in t.flatMemberTypes:
assert not f.nullable()
addHeadersForType(f)
if idlTypeNeedsCycleCollection(t):
declarations.add(
("mozilla::dom::%s" % CGUnionStruct.unionTypeName(t, True), False)
)
traverseMethods[name] = CGCycleCollectionTraverseForOwningUnionMethod(t)
unlinkMethods[name] = CGCycleCollectionUnlinkForOwningUnionMethod(t)
# The order of items in CGList is important.
# Since the union structs friend the unlinkMethods, the forward-declaration
# for these methods should come before the class declaration. Otherwise
# some compilers treat the friend declaration as a forward-declaration in
# the class scope.
return (
headers,
implheaders,
declarations,
SortedDictValues(traverseMethods),
SortedDictValues(unlinkMethods),
SortedDictValues(unionStructs),
)
def UnionConversions(unionTypes, config):
"""
The unionTypes argument should be a list of tuples, each containing two
elements: a union type and a descriptor. This is typically the list
generated by UnionsForFile.
Returns a tuple containing a list of headers and a CGThing to declare all
union argument conversion helper structs.
"""
headers = set()
unionConversions = dict()
for t in unionTypes:
name = str(t)
if name not in unionConversions:
unionConversions[name] = CGUnionConversionStruct(t, config)
def addHeadersForType(f):
if f.isSequence():
# Sequences require JSAPI C++ for-of iteration code to fill
# them.
headers.add("js/ForOfIterator.h")
# Sequences can always throw "not an object" exceptions.
headers.add("mozilla/dom/BindingCallContext.h")
f = f.unroll()
if idlTypeNeedsCallContext(f):
headers.add("mozilla/dom/BindingCallContext.h")
if f.isPromise():
headers.add("mozilla/dom/Promise.h")
# We need ToJSValue to do the Promise to JS conversion.
headers.add("mozilla/dom/ToJSValue.h")
elif f.isInterface():
if f.isSpiderMonkeyInterface():
headers.add("js/RootingAPI.h")
if f.isReadableStream():
headers.add("mozilla/dom/ReadableStream.h")
else:
headers.add("mozilla/dom/TypedArray.h")
elif f.inner.isExternal():
try:
typeDesc = config.getDescriptor(f.inner.identifier.name)
except NoSuchDescriptorError:
return
headers.add(typeDesc.headerFile)
else:
headers.add(CGHeaders.getDeclarationFilename(f.inner))
elif f.isDictionary():
headers.add(CGHeaders.getDeclarationFilename(f.inner))
elif f.isFloat() and not f.isUnrestricted():
# Restricted floats are tested for finiteness
headers.add("mozilla/FloatingPoint.h")
headers.add("mozilla/dom/PrimitiveConversions.h")
elif f.isPrimitive():
headers.add("mozilla/dom/PrimitiveConversions.h")
elif f.isRecord():
headers.add("mozilla/dom/Record.h")
# And the internal type of the record
addHeadersForType(f.inner)
# We plan to include UnionTypes.h no matter what, so it's
# OK if we throw it into the set here.
headers.add(CGHeaders.getUnionDeclarationFilename(config, t))
for f in t.flatMemberTypes:
addHeadersForType(f)
return (
headers,
CGWrapper(CGList(SortedDictValues(unionConversions), "\n"), post="\n\n"),
)
class Argument:
"""
A class for outputting the type and name of an argument
"""
def __init__(self, argType, name, default=None):
self.argType = argType
self.name = name
self.default = default
def declare(self):
string = self.argType + " " + self.name
if self.default is not None:
string += " = " + self.default
return string
def define(self):
return self.argType + " " + self.name
class CGAbstractMethod(CGThing):
"""
An abstract class for generating code for a method. Subclasses
should override definition_body to create the actual code.
descriptor is the descriptor for the interface the method is associated with
name is the name of the method as a string
returnType is the IDLType of the return value
args is a list of Argument objects
inline should be True to generate an inline method, whose body is
part of the declaration.
alwaysInline should be True to generate an inline method annotated with
MOZ_ALWAYS_INLINE.
static should be True to generate a static method, which only has
a definition.
If templateArgs is not None it should be a list of strings containing
template arguments, and the function will be templatized using those
arguments.
canRunScript should be True to generate a MOZ_CAN_RUN_SCRIPT annotation.
"""
def __init__(
self,
descriptor,
name,
returnType,
args,
inline=False,
alwaysInline=False,
static=False,
templateArgs=None,
canRunScript=False,
):
CGThing.__init__(self)
self.descriptor = descriptor
self.name = name
self.returnType = returnType
self.args = args
self.inline = inline
self.alwaysInline = alwaysInline
self.static = static
self.templateArgs = templateArgs
self.canRunScript = canRunScript
def _argstring(self, declare):
return ", ".join([a.declare() if declare else a.define() for a in self.args])
def _template(self):
if self.templateArgs is None:
return ""
return "template <%s>\n" % ", ".join(self.templateArgs)
def _decorators(self):
decorators = []
if self.canRunScript:
decorators.append("MOZ_CAN_RUN_SCRIPT")
if self.alwaysInline:
decorators.append("MOZ_ALWAYS_INLINE")
elif self.inline:
decorators.append("inline")
if self.static:
decorators.append("static")
decorators.append(self.returnType)
maybeNewline = " " if self.inline else "\n"
return " ".join(decorators) + maybeNewline
def declare(self):
if self.inline:
return self._define(True)
return "%s%s%s(%s);\n" % (
self._template(),
self._decorators(),
self.name,
self._argstring(True),
)
def indent_body(self, body):
"""
Indent the code returned by self.definition_body(). Most classes
simply indent everything two spaces. This is here for
CGRegisterProtos, which needs custom indentation.
"""
return indent(body)
def _define(self, fromDeclare=False):
return (
self.definition_prologue(fromDeclare)
+ self.indent_body(self.definition_body())
+ self.definition_epilogue()
)
def define(self):
return "" if self.inline else self._define()