mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 08:42:13 +00:00
02a7b4ebdf
Allow-list all Python code in tree for use with the black linter, and re-format all code in-tree accordingly. To produce this patch I did all of the following: 1. Make changes to tools/lint/black.yml to remove include: stanza and update list of source extensions. 2. Run ./mach lint --linter black --fix 3. Make some ad-hoc manual updates to python/mozbuild/mozbuild/test/configure/test_configure.py -- it has some hard-coded line numbers that the reformat breaks. 4. Make some ad-hoc manual updates to `testing/marionette/client/setup.py`, `testing/marionette/harness/setup.py`, and `testing/firefox-ui/harness/setup.py`, which have hard-coded regexes that break after the reformat. 5. Add a set of exclusions to black.yml. These will be deleted in a follow-up bug (1672023). # ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D94045
22952 lines
823 KiB
Python
22952 lines
823 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/.
|
|
|
|
# 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("jsfriendapi.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("jsfriendapi.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()
|
|
|
|
def definition_prologue(self, fromDeclare):
|
|
error_reporting_label = self.error_reporting_label()
|
|
if error_reporting_label:
|
|
# We're going to want a BindingCallContext. Rename our JSContext*
|
|
# arg accordingly.
|
|
i = 0
|
|
while i < len(self.args):
|
|
arg = self.args[i]
|
|
if arg.argType == "JSContext*":
|
|
cxname = arg.name
|
|
self.args[i] = Argument(arg.argType, "cx_", arg.default)
|
|
break
|
|
i += 1
|
|
if i == len(self.args):
|
|
raise TypeError("Must have a JSContext* to create a BindingCallContext")
|
|
|
|
prologue = "%s%s%s(%s)\n{\n" % (
|
|
self._template(),
|
|
self._decorators(),
|
|
self.name,
|
|
self._argstring(fromDeclare),
|
|
)
|
|
if error_reporting_label:
|
|
prologue += indent(
|
|
fill(
|
|
"""
|
|
BindingCallContext ${cxname}(cx_, "${label}");
|
|
""",
|
|
cxname=cxname,
|
|
label=error_reporting_label,
|
|
)
|
|
)
|
|
|
|
profiler_label = self.auto_profiler_label()
|
|
if profiler_label:
|
|
prologue += indent(profiler_label) + "\n"
|
|
|
|
return prologue
|
|
|
|
def definition_epilogue(self):
|
|
return "}\n"
|
|
|
|
def definition_body(self):
|
|
assert False # Override me!
|
|
|
|
"""
|
|
Override this method to return a pair of (descriptive string, name of a
|
|
JSContext* variable) in order to generate a profiler label for this method.
|
|
"""
|
|
|
|
def auto_profiler_label(self):
|
|
return None # Override me!
|
|
|
|
"""
|
|
Override this method to return a string to be used as the label for a
|
|
BindingCallContext. If this does not return None, one of the arguments of
|
|
this method must be of type 'JSContext*'. Its name will be replaced with
|
|
'cx_' and a BindingCallContext named 'cx' will be instantiated with the
|
|
given label.
|
|
"""
|
|
|
|
def error_reporting_label(self):
|
|
return None # Override me!
|
|
|
|
|
|
class CGAbstractStaticMethod(CGAbstractMethod):
|
|
"""
|
|
Abstract base class for codegen of implementation-only (no
|
|
declaration) static methods.
|
|
"""
|
|
|
|
def __init__(self, descriptor, name, returnType, args, canRunScript=False):
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
name,
|
|
returnType,
|
|
args,
|
|
inline=False,
|
|
static=True,
|
|
canRunScript=canRunScript,
|
|
)
|
|
|
|
def declare(self):
|
|
# We only have implementation
|
|
return ""
|
|
|
|
|
|
class CGAbstractClassHook(CGAbstractStaticMethod):
|
|
"""
|
|
Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw
|
|
'this' unwrapping as it assumes that the unwrapped type is always known.
|
|
"""
|
|
|
|
def __init__(self, descriptor, name, returnType, args):
|
|
CGAbstractStaticMethod.__init__(self, descriptor, name, returnType, args)
|
|
|
|
def definition_body_prologue(self):
|
|
return "%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(obj);\n" % (
|
|
self.descriptor.nativeType,
|
|
self.descriptor.nativeType,
|
|
)
|
|
|
|
def definition_body(self):
|
|
return self.definition_body_prologue() + self.generate_code()
|
|
|
|
def generate_code(self):
|
|
assert False # Override me!
|
|
|
|
|
|
class CGGetJSClassMethod(CGAbstractMethod):
|
|
def __init__(self, descriptor):
|
|
CGAbstractMethod.__init__(self, descriptor, "GetJSClass", "const JSClass*", [])
|
|
|
|
def definition_body(self):
|
|
return "return sClass.ToJSClass();\n"
|
|
|
|
|
|
class CGAddPropertyHook(CGAbstractClassHook):
|
|
"""
|
|
A hook for addProperty, used to preserve our wrapper from GC.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::Handle<JS::Value>", "val"),
|
|
]
|
|
CGAbstractClassHook.__init__(
|
|
self, descriptor, ADDPROPERTY_HOOK_NAME, "bool", args
|
|
)
|
|
|
|
def generate_code(self):
|
|
assert self.descriptor.wrapperCache
|
|
# This hook is also called by TryPreserveWrapper on non-nsISupports
|
|
# cycle collected objects, so if addProperty is ever changed to do
|
|
# anything more or less than preserve the wrapper, TryPreserveWrapper
|
|
# will need to be changed.
|
|
return dedent(
|
|
"""
|
|
// We don't want to preserve if we don't have a wrapper, and we
|
|
// obviously can't preserve if we're not initialized.
|
|
if (self && self->GetWrapperPreserveColor()) {
|
|
PreserveWrapper(self);
|
|
}
|
|
return true;
|
|
"""
|
|
)
|
|
|
|
|
|
class CGGetWrapperCacheHook(CGAbstractClassHook):
|
|
"""
|
|
A hook for GetWrapperCache, used by HasReleasedWrapper to get the
|
|
nsWrapperCache pointer for a non-nsISupports object.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
args = [Argument("JS::Handle<JSObject*>", "obj")]
|
|
CGAbstractClassHook.__init__(
|
|
self, descriptor, GETWRAPPERCACHE_HOOK_NAME, "nsWrapperCache*", args
|
|
)
|
|
|
|
def generate_code(self):
|
|
assert self.descriptor.wrapperCache
|
|
return dedent(
|
|
"""
|
|
return self;
|
|
"""
|
|
)
|
|
|
|
|
|
def finalizeHook(descriptor, hookName, freeOp, obj):
|
|
finalize = "JS::SetReservedSlot(%s, DOM_OBJECT_SLOT, JS::UndefinedValue());\n" % obj
|
|
if descriptor.interface.getExtendedAttribute("OverrideBuiltins"):
|
|
finalize += fill(
|
|
"""
|
|
// Either our proxy created an expando object or not. If it did,
|
|
// then we would have preserved ourselves, and hence if we're going
|
|
// away so is our C++ object and we should reset its expando value.
|
|
// It's possible that in this situation the C++ object's reflector
|
|
// pointer has been nulled out, but if not it's pointing to us. If
|
|
// our proxy did _not_ create an expando object then it's possible
|
|
// that we're no longer the reflector for our C++ object (and
|
|
// incremental finalization is finally getting to us), and that in
|
|
// the meantime the new reflector has created an expando object.
|
|
// In that case we do NOT want to clear the expando pointer in the
|
|
// C++ object.
|
|
//
|
|
// It's important to do this before we ClearWrapper, of course.
|
|
JSObject* reflector = self->GetWrapperMaybeDead();
|
|
if (!reflector || reflector == ${obj}) {
|
|
self->mExpandoAndGeneration.expando = JS::UndefinedValue();
|
|
}
|
|
""",
|
|
obj=obj,
|
|
)
|
|
if descriptor.wrapperCache:
|
|
finalize += "ClearWrapper(self, self, %s);\n" % obj
|
|
if descriptor.isGlobal():
|
|
finalize += "mozilla::dom::FinalizeGlobal(%s, %s);\n" % (freeOp, obj)
|
|
finalize += fill(
|
|
"""
|
|
if (size_t mallocBytes = BindingJSObjectMallocBytes(self)) {
|
|
JS::RemoveAssociatedMemory(${obj}, mallocBytes,
|
|
JS::MemoryUse::DOMBinding);
|
|
}
|
|
""",
|
|
obj=obj,
|
|
)
|
|
finalize += "AddForDeferredFinalization<%s>(self);\n" % descriptor.nativeType
|
|
return CGIfWrapper(CGGeneric(finalize), "self")
|
|
|
|
|
|
class CGClassFinalizeHook(CGAbstractClassHook):
|
|
"""
|
|
A hook for finalize, used to release our native object.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
args = [Argument("JSFreeOp*", "fop"), Argument("JSObject*", "obj")]
|
|
CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME, "void", args)
|
|
|
|
def generate_code(self):
|
|
return finalizeHook(
|
|
self.descriptor, self.name, self.args[0].name, self.args[1].name
|
|
).define()
|
|
|
|
|
|
def objectMovedHook(descriptor, hookName, obj, old):
|
|
assert descriptor.wrapperCache
|
|
return fill(
|
|
"""
|
|
if (self) {
|
|
UpdateWrapper(self, self, ${obj}, ${old});
|
|
}
|
|
|
|
return 0;
|
|
""",
|
|
obj=obj,
|
|
old=old,
|
|
)
|
|
|
|
|
|
class CGClassObjectMovedHook(CGAbstractClassHook):
|
|
"""
|
|
A hook for objectMovedOp, used to update the wrapper cache when an object it
|
|
is holding moves.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
args = [Argument("JSObject*", "obj"), Argument("JSObject*", "old")]
|
|
CGAbstractClassHook.__init__(
|
|
self, descriptor, OBJECT_MOVED_HOOK_NAME, "size_t", args
|
|
)
|
|
|
|
def generate_code(self):
|
|
return objectMovedHook(
|
|
self.descriptor, self.name, self.args[0].name, self.args[1].name
|
|
)
|
|
|
|
|
|
def JSNativeArguments():
|
|
return [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("unsigned", "argc"),
|
|
Argument("JS::Value*", "vp"),
|
|
]
|
|
|
|
|
|
class CGClassConstructor(CGAbstractStaticMethod):
|
|
"""
|
|
JS-visible constructor for our objects
|
|
"""
|
|
|
|
def __init__(self, descriptor, ctor, name=CONSTRUCT_HOOK_NAME):
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, name, "bool", JSNativeArguments()
|
|
)
|
|
self._ctor = ctor
|
|
|
|
def define(self):
|
|
if not self._ctor:
|
|
return ""
|
|
return CGAbstractStaticMethod.define(self)
|
|
|
|
def definition_body(self):
|
|
return self.generate_code()
|
|
|
|
def generate_code(self):
|
|
if self._ctor.isHTMLConstructor():
|
|
# We better have a prototype object. Otherwise our proto
|
|
# id won't make sense.
|
|
assert self.descriptor.interface.hasInterfacePrototypeObject()
|
|
# We also better have a constructor object, if this is
|
|
# getting called!
|
|
assert self.descriptor.interface.hasInterfaceObject()
|
|
# We can't just pass null for the CreateInterfaceObjects callback,
|
|
# because our newTarget might be in a different compartment, in
|
|
# which case we'll need to look up constructor objects in that
|
|
# compartment.
|
|
return fill(
|
|
"""
|
|
return HTMLConstructor(cx, argc, vp,
|
|
constructors::id::${name},
|
|
prototypes::id::${name},
|
|
CreateInterfaceObjects);
|
|
""",
|
|
name=self.descriptor.name,
|
|
)
|
|
|
|
# If the interface is already SecureContext, notify getConditionList to skip that check,
|
|
# because the constructor won't be exposed in non-secure contexts to start with.
|
|
alreadySecureContext = self.descriptor.interface.getExtendedAttribute(
|
|
"SecureContext"
|
|
)
|
|
|
|
# We want to throw if any of the conditions returned by getConditionList are false.
|
|
conditionsCheck = ""
|
|
rawConditions = getRawConditionList(
|
|
self._ctor, "cx", "obj", alreadySecureContext
|
|
)
|
|
if len(rawConditions) > 0:
|
|
notConditions = " ||\n".join("!" + cond for cond in rawConditions)
|
|
failedCheckAction = CGGeneric("return ThrowingConstructor(cx, argc, vp);\n")
|
|
conditionsCheck = (
|
|
CGIfWrapper(failedCheckAction, notConditions).define() + "\n"
|
|
)
|
|
|
|
# Additionally, we want to throw if a caller does a bareword invocation
|
|
# of a constructor without |new|.
|
|
ctorName = GetConstructorNameForReporting(self.descriptor, self._ctor)
|
|
|
|
preamble = fill(
|
|
"""
|
|
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
|
JS::Rooted<JSObject*> obj(cx, &args.callee());
|
|
$*{conditionsCheck}
|
|
if (!args.isConstructing()) {
|
|
return ThrowConstructorWithoutNew(cx, "${ctorName}");
|
|
}
|
|
|
|
JS::Rooted<JSObject*> desiredProto(cx);
|
|
if (!GetDesiredProto(cx, args,
|
|
prototypes::id::${name},
|
|
CreateInterfaceObjects,
|
|
&desiredProto)) {
|
|
return false;
|
|
}
|
|
""",
|
|
conditionsCheck=conditionsCheck,
|
|
ctorName=ctorName,
|
|
name=self.descriptor.name,
|
|
)
|
|
|
|
name = self._ctor.identifier.name
|
|
nativeName = MakeNativeName(self.descriptor.binaryNameFor(name))
|
|
callGenerator = CGMethodCall(
|
|
nativeName, True, self.descriptor, self._ctor, isConstructor=True
|
|
)
|
|
return preamble + "\n" + callGenerator.define()
|
|
|
|
def auto_profiler_label(self):
|
|
return fill(
|
|
"""
|
|
AUTO_PROFILER_LABEL_DYNAMIC_FAST(
|
|
"${ctorName}", "constructor", DOM, cx,
|
|
uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
|
|
""",
|
|
ctorName=GetConstructorNameForReporting(self.descriptor, self._ctor),
|
|
)
|
|
|
|
def error_reporting_label(self):
|
|
return CGSpecializedMethod.error_reporting_label_helper(
|
|
self.descriptor, self._ctor, isConstructor=True
|
|
)
|
|
|
|
|
|
def NamedConstructorName(m):
|
|
return "_" + m.identifier.name
|
|
|
|
|
|
class CGNamedConstructors(CGThing):
|
|
def __init__(self, descriptor):
|
|
self.descriptor = descriptor
|
|
CGThing.__init__(self)
|
|
|
|
def declare(self):
|
|
return ""
|
|
|
|
def define(self):
|
|
if len(self.descriptor.interface.namedConstructors) == 0:
|
|
return ""
|
|
|
|
constructorID = "constructors::id::"
|
|
if self.descriptor.interface.hasInterfaceObject():
|
|
constructorID += self.descriptor.name
|
|
else:
|
|
constructorID += "_ID_Count"
|
|
|
|
namedConstructors = ""
|
|
for n in self.descriptor.interface.namedConstructors:
|
|
namedConstructors += (
|
|
'{ "%s", { %s, &sNamedConstructorNativePropertyHooks }, %i },\n'
|
|
% (n.identifier.name, NamedConstructorName(n), methodLength(n))
|
|
)
|
|
|
|
return fill(
|
|
"""
|
|
const NativePropertyHooks sNamedConstructorNativePropertyHooks = {
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
{ nullptr, nullptr },
|
|
prototypes::id::${name},
|
|
${constructorID},
|
|
nullptr
|
|
};
|
|
|
|
static const NamedConstructor namedConstructors[] = {
|
|
$*{namedConstructors}
|
|
{ nullptr, { nullptr, nullptr }, 0 }
|
|
};
|
|
""",
|
|
name=self.descriptor.name,
|
|
constructorID=constructorID,
|
|
namedConstructors=namedConstructors,
|
|
)
|
|
|
|
|
|
def isChromeOnly(m):
|
|
return m.getExtendedAttribute("ChromeOnly")
|
|
|
|
|
|
def prefIdentifier(pref):
|
|
return pref.replace(".", "_").replace("-", "_")
|
|
|
|
|
|
def prefHeader(pref):
|
|
return "mozilla/StaticPrefs_%s.h" % pref.partition(".")[0]
|
|
|
|
|
|
class MemberCondition:
|
|
"""
|
|
An object representing the condition for a member to actually be
|
|
exposed. Any of the arguments can be None. If not
|
|
None, they should have the following types:
|
|
|
|
pref: The name of the preference.
|
|
func: The name of the function.
|
|
secureContext: A bool indicating whether a secure context is required.
|
|
nonExposedGlobals: A set of names of globals. Can be empty, in which case
|
|
it's treated the same way as None.
|
|
"""
|
|
|
|
def __init__(
|
|
self, pref=None, func=None, secureContext=False, nonExposedGlobals=None
|
|
):
|
|
assert pref is None or isinstance(pref, str)
|
|
assert func is None or isinstance(func, str)
|
|
assert isinstance(secureContext, bool)
|
|
assert nonExposedGlobals is None or isinstance(nonExposedGlobals, set)
|
|
self.pref = pref
|
|
if self.pref:
|
|
identifier = prefIdentifier(self.pref)
|
|
self.prefFuncIndex = "WebIDLPrefIndex::" + identifier
|
|
else:
|
|
self.prefFuncIndex = "WebIDLPrefIndex::NoPref"
|
|
|
|
self.secureContext = secureContext
|
|
|
|
def toFuncPtr(val):
|
|
if val is None:
|
|
return "nullptr"
|
|
return "&" + val
|
|
|
|
self.func = toFuncPtr(func)
|
|
|
|
if nonExposedGlobals:
|
|
# Nonempty set
|
|
self.nonExposedGlobals = " | ".join(
|
|
map(lambda g: "GlobalNames::%s" % g, sorted(nonExposedGlobals))
|
|
)
|
|
else:
|
|
self.nonExposedGlobals = "0"
|
|
|
|
def __eq__(self, other):
|
|
return (
|
|
self.pref == other.pref
|
|
and self.func == other.func
|
|
and self.secureContext == other.secureContext
|
|
and self.nonExposedGlobals == other.nonExposedGlobals
|
|
)
|
|
|
|
def __ne__(self, other):
|
|
return not self.__eq__(other)
|
|
|
|
def hasDisablers(self):
|
|
return (
|
|
self.pref is not None
|
|
or self.secureContext
|
|
or self.func != "nullptr"
|
|
or self.nonExposedGlobals != "0"
|
|
)
|
|
|
|
|
|
class PropertyDefiner:
|
|
"""
|
|
A common superclass for defining things on prototype objects.
|
|
|
|
Subclasses should implement generateArray to generate the actual arrays of
|
|
things we're defining. They should also set self.chrome to the list of
|
|
things only exposed to chrome and self.regular to the list of things exposed
|
|
to both chrome and web pages.
|
|
"""
|
|
|
|
def __init__(self, descriptor, name):
|
|
self.descriptor = descriptor
|
|
self.name = name
|
|
|
|
def hasChromeOnly(self):
|
|
return len(self.chrome) > 0
|
|
|
|
def hasNonChromeOnly(self):
|
|
return len(self.regular) > 0
|
|
|
|
def variableName(self, chrome):
|
|
if chrome:
|
|
if self.hasChromeOnly():
|
|
return "sChrome" + self.name
|
|
else:
|
|
if self.hasNonChromeOnly():
|
|
return "s" + self.name
|
|
return "nullptr"
|
|
|
|
def usedForXrays(self):
|
|
return self.descriptor.wantsXrays
|
|
|
|
def length(self, chrome):
|
|
return len(self.chrome) if chrome else len(self.regular)
|
|
|
|
def __str__(self):
|
|
# We only need to generate id arrays for things that will end
|
|
# up used via ResolveProperty or EnumerateProperties.
|
|
str = self.generateArray(self.regular, self.variableName(False))
|
|
if self.hasChromeOnly():
|
|
str += self.generateArray(self.chrome, self.variableName(True))
|
|
return str
|
|
|
|
@staticmethod
|
|
def getStringAttr(member, name):
|
|
attr = member.getExtendedAttribute(name)
|
|
if attr is None:
|
|
return None
|
|
# It's a list of strings
|
|
assert len(attr) == 1
|
|
assert attr[0] is not None
|
|
return attr[0]
|
|
|
|
@staticmethod
|
|
def getControllingCondition(interfaceMember, descriptor):
|
|
interface = descriptor.interface
|
|
nonExposureSet = interface.exposureSet - interfaceMember.exposureSet
|
|
|
|
return MemberCondition(
|
|
PropertyDefiner.getStringAttr(interfaceMember, "Pref"),
|
|
PropertyDefiner.getStringAttr(interfaceMember, "Func"),
|
|
interfaceMember.getExtendedAttribute("SecureContext") is not None,
|
|
nonExposureSet,
|
|
)
|
|
|
|
@staticmethod
|
|
def generatePrefableArrayValues(
|
|
array,
|
|
descriptor,
|
|
specFormatter,
|
|
specTerminator,
|
|
getCondition,
|
|
getDataTuple,
|
|
switchToCondition=None,
|
|
):
|
|
"""
|
|
This method generates an array of spec entries for interface members. It returns
|
|
a tuple containing the array of spec entries and the maximum of the number of
|
|
spec entries per condition.
|
|
|
|
array is an array of interface members.
|
|
|
|
descriptor is the descriptor for the interface that array contains members of.
|
|
|
|
specFormatter is a function that takes a single argument, a tuple,
|
|
and returns a string, a spec array entry.
|
|
|
|
specTerminator is a terminator for the spec array (inserted every time
|
|
our controlling pref changes and at the end of the array).
|
|
|
|
getCondition is a callback function that takes an array entry and
|
|
returns the corresponding MemberCondition.
|
|
|
|
getDataTuple is a callback function that takes an array entry and
|
|
returns a tuple suitable to be passed to specFormatter.
|
|
|
|
switchToCondition is a function that takes a MemberCondition and an array of
|
|
previously generated spec entries. If None is passed for this function then all
|
|
the interface members should return the same value from getCondition.
|
|
"""
|
|
|
|
def unsupportedSwitchToCondition(condition, specs):
|
|
# If no specs have been added yet then this is just the first call to
|
|
# switchToCondition that we call to avoid putting a specTerminator at the
|
|
# front of the list.
|
|
if len(specs) == 0:
|
|
return
|
|
raise "Not supported"
|
|
|
|
if switchToCondition is None:
|
|
switchToCondition = unsupportedSwitchToCondition
|
|
|
|
specs = []
|
|
numSpecsInCurPrefable = 0
|
|
maxNumSpecsInPrefable = 0
|
|
|
|
# So we won't put a specTerminator at the very front of the list:
|
|
lastCondition = getCondition(array[0], descriptor)
|
|
|
|
switchToCondition(lastCondition, specs)
|
|
|
|
for member in array:
|
|
curCondition = getCondition(member, descriptor)
|
|
if lastCondition != curCondition:
|
|
# Terminate previous list
|
|
specs.append(specTerminator)
|
|
if numSpecsInCurPrefable > maxNumSpecsInPrefable:
|
|
maxNumSpecsInPrefable = numSpecsInCurPrefable
|
|
numSpecsInCurPrefable = 0
|
|
# And switch to our new condition
|
|
switchToCondition(curCondition, specs)
|
|
lastCondition = curCondition
|
|
# And the actual spec
|
|
specs.append(specFormatter(getDataTuple(member, descriptor)))
|
|
numSpecsInCurPrefable += 1
|
|
if numSpecsInCurPrefable > maxNumSpecsInPrefable:
|
|
maxNumSpecsInPrefable = numSpecsInCurPrefable
|
|
specs.append(specTerminator)
|
|
|
|
return (specs, maxNumSpecsInPrefable)
|
|
|
|
def generatePrefableArray(
|
|
self,
|
|
array,
|
|
name,
|
|
specFormatter,
|
|
specTerminator,
|
|
specType,
|
|
getCondition,
|
|
getDataTuple,
|
|
):
|
|
"""
|
|
This method generates our various arrays.
|
|
|
|
array is an array of interface members as passed to generateArray
|
|
|
|
name is the name as passed to generateArray
|
|
|
|
specFormatter is a function that takes a single argument, a tuple,
|
|
and returns a string, a spec array entry
|
|
|
|
specTerminator is a terminator for the spec array (inserted every time
|
|
our controlling pref changes and at the end of the array)
|
|
|
|
specType is the actual typename of our spec
|
|
|
|
getCondition is a callback function that takes an array entry and
|
|
returns the corresponding MemberCondition.
|
|
|
|
getDataTuple is a callback function that takes an array entry and
|
|
returns a tuple suitable to be passed to specFormatter.
|
|
"""
|
|
|
|
# We want to generate a single list of specs, but with specTerminator
|
|
# inserted at every point where the pref name controlling the member
|
|
# changes. That will make sure the order of the properties as exposed
|
|
# on the interface and interface prototype objects does not change when
|
|
# pref control is added to members while still allowing us to define all
|
|
# the members in the smallest number of JSAPI calls.
|
|
assert len(array) != 0
|
|
|
|
disablers = []
|
|
prefableSpecs = []
|
|
|
|
disablersTemplate = dedent(
|
|
"""
|
|
static const PrefableDisablers %s_disablers%d = {
|
|
%s, %s, %s, %s
|
|
};
|
|
"""
|
|
)
|
|
prefableWithDisablersTemplate = " { &%s_disablers%d, &%s_specs[%d] }"
|
|
prefableWithoutDisablersTemplate = " { nullptr, &%s_specs[%d] }"
|
|
prefCacheTemplate = "&%s[%d].disablers->enabled"
|
|
|
|
def switchToCondition(condition, specs):
|
|
# Set up pointers to the new sets of specs inside prefableSpecs
|
|
if condition.hasDisablers():
|
|
prefableSpecs.append(
|
|
prefableWithDisablersTemplate % (name, len(specs), name, len(specs))
|
|
)
|
|
disablers.append(
|
|
disablersTemplate
|
|
% (
|
|
name,
|
|
len(specs),
|
|
condition.prefFuncIndex,
|
|
toStringBool(condition.secureContext),
|
|
condition.nonExposedGlobals,
|
|
condition.func,
|
|
)
|
|
)
|
|
else:
|
|
prefableSpecs.append(
|
|
prefableWithoutDisablersTemplate % (name, len(specs))
|
|
)
|
|
|
|
specs, maxNumSpecsInPrefable = self.generatePrefableArrayValues(
|
|
array,
|
|
self.descriptor,
|
|
specFormatter,
|
|
specTerminator,
|
|
getCondition,
|
|
getDataTuple,
|
|
switchToCondition,
|
|
)
|
|
prefableSpecs.append(" { nullptr, nullptr }")
|
|
|
|
specType = "const " + specType
|
|
arrays = fill(
|
|
"""
|
|
// We deliberately use brace-elision to make Visual Studio produce better initalization code.
|
|
static ${specType} ${name}_specs[] = {
|
|
${specs}
|
|
};
|
|
|
|
${disablers}
|
|
static const Prefable<${specType}> ${name}[] = {
|
|
${prefableSpecs}
|
|
};
|
|
|
|
""",
|
|
specType=specType,
|
|
name=name,
|
|
disablers="\n".join(disablers),
|
|
specs=",\n".join(specs),
|
|
prefableSpecs=",\n".join(prefableSpecs),
|
|
)
|
|
|
|
if self.usedForXrays():
|
|
arrays = fill(
|
|
"""
|
|
$*{arrays}
|
|
static_assert(${numPrefableSpecs} <= 1ull << NUM_BITS_PROPERTY_INFO_PREF_INDEX,
|
|
"We have a prefable index that is >= (1 << NUM_BITS_PROPERTY_INFO_PREF_INDEX)");
|
|
static_assert(${maxNumSpecsInPrefable} <= 1ull << NUM_BITS_PROPERTY_INFO_SPEC_INDEX,
|
|
"We have a spec index that is >= (1 << NUM_BITS_PROPERTY_INFO_SPEC_INDEX)");
|
|
|
|
""",
|
|
arrays=arrays,
|
|
# Minus 1 because there's a list terminator in prefableSpecs.
|
|
numPrefableSpecs=len(prefableSpecs) - 1,
|
|
maxNumSpecsInPrefable=maxNumSpecsInPrefable,
|
|
)
|
|
|
|
return arrays
|
|
|
|
|
|
# The length of a method is the minimum of the lengths of the
|
|
# argument lists of all its overloads.
|
|
def overloadLength(arguments):
|
|
i = len(arguments)
|
|
while i > 0 and arguments[i - 1].optional:
|
|
i -= 1
|
|
return i
|
|
|
|
|
|
def methodLength(method):
|
|
signatures = method.signatures()
|
|
return min(overloadLength(arguments) for retType, arguments in signatures)
|
|
|
|
|
|
def clearableCachedAttrs(descriptor):
|
|
return (
|
|
m
|
|
for m in descriptor.interface.members
|
|
if m.isAttr() and
|
|
# Constants should never need clearing!
|
|
m.dependsOn != "Nothing" and m.slotIndices is not None
|
|
)
|
|
|
|
|
|
def MakeClearCachedValueNativeName(member):
|
|
return "ClearCached%sValue" % MakeNativeName(member.identifier.name)
|
|
|
|
|
|
def IDLToCIdentifier(name):
|
|
return name.replace("-", "_")
|
|
|
|
|
|
def EnumerabilityFlags(member):
|
|
if member.getExtendedAttribute("NonEnumerable"):
|
|
return "0"
|
|
return "JSPROP_ENUMERATE"
|
|
|
|
|
|
class MethodDefiner(PropertyDefiner):
|
|
"""
|
|
A class for defining methods on a prototype object.
|
|
"""
|
|
|
|
def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
|
|
assert not (static and unforgeable)
|
|
PropertyDefiner.__init__(self, descriptor, name)
|
|
|
|
# FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822
|
|
# We should be able to check for special operations without an
|
|
# identifier. For now we check if the name starts with __
|
|
|
|
# Ignore non-static methods for interfaces without a proto object
|
|
if descriptor.interface.hasInterfacePrototypeObject() or static:
|
|
methods = [
|
|
m
|
|
for m in descriptor.interface.members
|
|
if m.isMethod()
|
|
and m.isStatic() == static
|
|
and MemberIsUnforgeable(m, descriptor) == unforgeable
|
|
and (
|
|
not crossOriginOnly or m.getExtendedAttribute("CrossOriginCallable")
|
|
)
|
|
and not m.isIdentifierLess()
|
|
and not m.getExtendedAttribute("Unexposed")
|
|
]
|
|
else:
|
|
methods = []
|
|
self.chrome = []
|
|
self.regular = []
|
|
for m in methods:
|
|
method = self.methodData(m, descriptor)
|
|
|
|
if m.isStatic():
|
|
method["nativeName"] = CppKeywords.checkMethodName(
|
|
IDLToCIdentifier(m.identifier.name)
|
|
)
|
|
|
|
if isChromeOnly(m):
|
|
self.chrome.append(method)
|
|
else:
|
|
self.regular.append(method)
|
|
|
|
# TODO: Once iterable is implemented, use tiebreak rules instead of
|
|
# failing. Also, may be more tiebreak rules to implement once spec bug
|
|
# is resolved.
|
|
# https://www.w3.org/Bugs/Public/show_bug.cgi?id=28592
|
|
def hasIterator(methods, regular):
|
|
return any("@@iterator" in m.aliases for m in methods) or any(
|
|
"@@iterator" == r["name"] for r in regular
|
|
)
|
|
|
|
# Check whether we need to output an @@iterator due to having an indexed
|
|
# getter. We only do this while outputting non-static and
|
|
# non-unforgeable methods, since the @@iterator function will be
|
|
# neither.
|
|
if not static and not unforgeable and descriptor.supportsIndexedProperties():
|
|
if hasIterator(methods, self.regular):
|
|
raise TypeError(
|
|
"Cannot have indexed getter/attr on "
|
|
"interface %s with other members "
|
|
"that generate @@iterator, such as "
|
|
"maplike/setlike or aliased functions."
|
|
% self.descriptor.interface.identifier.name
|
|
)
|
|
self.regular.append(
|
|
{
|
|
"name": "@@iterator",
|
|
"methodInfo": False,
|
|
"selfHostedName": "$ArrayValues",
|
|
"length": 0,
|
|
"flags": "0", # Not enumerable, per spec.
|
|
"condition": MemberCondition(),
|
|
}
|
|
)
|
|
|
|
# Generate the keys/values/entries aliases for value iterables.
|
|
maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
|
|
if (
|
|
not static
|
|
and not unforgeable
|
|
and maplikeOrSetlikeOrIterable
|
|
and maplikeOrSetlikeOrIterable.isIterable()
|
|
and maplikeOrSetlikeOrIterable.isValueIterator()
|
|
):
|
|
# Add our keys/values/entries/forEach
|
|
self.regular.append(
|
|
{
|
|
"name": "keys",
|
|
"methodInfo": False,
|
|
"selfHostedName": "ArrayKeys",
|
|
"length": 0,
|
|
"flags": "JSPROP_ENUMERATE",
|
|
"condition": PropertyDefiner.getControllingCondition(
|
|
maplikeOrSetlikeOrIterable, descriptor
|
|
),
|
|
}
|
|
)
|
|
self.regular.append(
|
|
{
|
|
"name": "values",
|
|
"methodInfo": False,
|
|
"selfHostedName": "$ArrayValues",
|
|
"length": 0,
|
|
"flags": "JSPROP_ENUMERATE",
|
|
"condition": PropertyDefiner.getControllingCondition(
|
|
maplikeOrSetlikeOrIterable, descriptor
|
|
),
|
|
}
|
|
)
|
|
self.regular.append(
|
|
{
|
|
"name": "entries",
|
|
"methodInfo": False,
|
|
"selfHostedName": "ArrayEntries",
|
|
"length": 0,
|
|
"flags": "JSPROP_ENUMERATE",
|
|
"condition": PropertyDefiner.getControllingCondition(
|
|
maplikeOrSetlikeOrIterable, descriptor
|
|
),
|
|
}
|
|
)
|
|
self.regular.append(
|
|
{
|
|
"name": "forEach",
|
|
"methodInfo": False,
|
|
"selfHostedName": "ArrayForEach",
|
|
"length": 1,
|
|
"flags": "JSPROP_ENUMERATE",
|
|
"condition": PropertyDefiner.getControllingCondition(
|
|
maplikeOrSetlikeOrIterable, descriptor
|
|
),
|
|
}
|
|
)
|
|
|
|
if not static:
|
|
stringifier = descriptor.operations["Stringifier"]
|
|
if stringifier and unforgeable == MemberIsUnforgeable(
|
|
stringifier, descriptor
|
|
):
|
|
toStringDesc = {
|
|
"name": GetWebExposedName(stringifier, descriptor),
|
|
"nativeName": stringifier.identifier.name,
|
|
"length": 0,
|
|
"flags": "JSPROP_ENUMERATE",
|
|
"condition": PropertyDefiner.getControllingCondition(
|
|
stringifier, descriptor
|
|
),
|
|
}
|
|
if isChromeOnly(stringifier):
|
|
self.chrome.append(toStringDesc)
|
|
else:
|
|
self.regular.append(toStringDesc)
|
|
if unforgeable and descriptor.interface.getExtendedAttribute("Unforgeable"):
|
|
# Synthesize our valueOf method
|
|
self.regular.append(
|
|
{
|
|
"name": "valueOf",
|
|
"selfHostedName": "Object_valueOf",
|
|
"methodInfo": False,
|
|
"length": 0,
|
|
"flags": "0", # readonly/permanent added automatically.
|
|
"condition": MemberCondition(),
|
|
}
|
|
)
|
|
|
|
if descriptor.interface.isJSImplemented():
|
|
if static:
|
|
if descriptor.interface.hasInterfaceObject():
|
|
self.chrome.append(
|
|
{
|
|
"name": "_create",
|
|
"nativeName": ("%s::_Create" % descriptor.name),
|
|
"methodInfo": False,
|
|
"length": 2,
|
|
"flags": "0",
|
|
"condition": MemberCondition(),
|
|
}
|
|
)
|
|
|
|
self.unforgeable = unforgeable
|
|
|
|
if static:
|
|
if not descriptor.interface.hasInterfaceObject():
|
|
# static methods go on the interface object
|
|
assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
|
|
else:
|
|
if not descriptor.interface.hasInterfacePrototypeObject():
|
|
# non-static methods go on the interface prototype object
|
|
assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
|
|
|
|
@staticmethod
|
|
def methodData(m, descriptor, overrideFlags=None):
|
|
return {
|
|
"name": m.identifier.name,
|
|
"methodInfo": not m.isStatic(),
|
|
"length": methodLength(m),
|
|
"flags": EnumerabilityFlags(m)
|
|
if (overrideFlags is None)
|
|
else overrideFlags,
|
|
"condition": PropertyDefiner.getControllingCondition(m, descriptor),
|
|
"allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
|
|
"returnsPromise": m.returnsPromise(),
|
|
"hasIteratorAlias": "@@iterator" in m.aliases,
|
|
}
|
|
|
|
@staticmethod
|
|
def formatSpec(fields):
|
|
if fields[0].startswith("@@"):
|
|
fields = (fields[0][2:],) + fields[1:]
|
|
return " JS_SYM_FNSPEC(%s, %s, %s, %s, %s, %s)" % fields
|
|
return ' JS_FNSPEC("%s", %s, %s, %s, %s, %s)' % fields
|
|
|
|
@staticmethod
|
|
def specData(m, descriptor, unforgeable=False):
|
|
def flags(m, unforgeable):
|
|
unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if unforgeable else ""
|
|
return m["flags"] + unforgeable
|
|
|
|
if "selfHostedName" in m:
|
|
selfHostedName = '"%s"' % m["selfHostedName"]
|
|
assert not m.get("methodInfo", True)
|
|
accessor = "nullptr"
|
|
jitinfo = "nullptr"
|
|
else:
|
|
selfHostedName = "nullptr"
|
|
# When defining symbols, function name may not match symbol name
|
|
methodName = m.get("methodName", m["name"])
|
|
accessor = m.get("nativeName", IDLToCIdentifier(methodName))
|
|
if m.get("methodInfo", True):
|
|
if m.get("returnsPromise", False):
|
|
exceptionPolicy = "ConvertExceptionsToPromises"
|
|
else:
|
|
exceptionPolicy = "ThrowExceptions"
|
|
|
|
# Cast this in case the methodInfo is a
|
|
# JSTypedMethodJitInfo.
|
|
jitinfo = (
|
|
"reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor
|
|
)
|
|
if m.get("allowCrossOriginThis", False):
|
|
accessor = (
|
|
"(GenericMethod<CrossOriginThisPolicy, %s>)" % exceptionPolicy
|
|
)
|
|
elif descriptor.interface.hasDescendantWithCrossOriginMembers:
|
|
accessor = (
|
|
"(GenericMethod<MaybeCrossOriginObjectThisPolicy, %s>)"
|
|
% exceptionPolicy
|
|
)
|
|
elif descriptor.interface.isOnGlobalProtoChain():
|
|
accessor = (
|
|
"(GenericMethod<MaybeGlobalThisPolicy, %s>)" % exceptionPolicy
|
|
)
|
|
else:
|
|
accessor = "(GenericMethod<NormalThisPolicy, %s>)" % exceptionPolicy
|
|
else:
|
|
if m.get("returnsPromise", False):
|
|
jitinfo = "&%s_methodinfo" % accessor
|
|
accessor = "StaticMethodPromiseWrapper"
|
|
else:
|
|
jitinfo = "nullptr"
|
|
|
|
return (
|
|
m["name"],
|
|
accessor,
|
|
jitinfo,
|
|
m["length"],
|
|
flags(m, unforgeable),
|
|
selfHostedName,
|
|
)
|
|
|
|
@staticmethod
|
|
def condition(m, d):
|
|
return m["condition"]
|
|
|
|
def generateArray(self, array, name):
|
|
if len(array) == 0:
|
|
return ""
|
|
|
|
return self.generatePrefableArray(
|
|
array,
|
|
name,
|
|
self.formatSpec,
|
|
" JS_FS_END",
|
|
"JSFunctionSpec",
|
|
self.condition,
|
|
functools.partial(self.specData, unforgeable=self.unforgeable),
|
|
)
|
|
|
|
|
|
class AttrDefiner(PropertyDefiner):
|
|
def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False):
|
|
assert not (static and unforgeable)
|
|
PropertyDefiner.__init__(self, descriptor, name)
|
|
self.name = name
|
|
# Ignore non-static attributes for interfaces without a proto object
|
|
if descriptor.interface.hasInterfacePrototypeObject() or static:
|
|
idlAttrs = [
|
|
m
|
|
for m in descriptor.interface.members
|
|
if m.isAttr()
|
|
and m.isStatic() == static
|
|
and MemberIsUnforgeable(m, descriptor) == unforgeable
|
|
and (
|
|
not crossOriginOnly
|
|
or m.getExtendedAttribute("CrossOriginReadable")
|
|
or m.getExtendedAttribute("CrossOriginWritable")
|
|
)
|
|
]
|
|
else:
|
|
idlAttrs = []
|
|
|
|
attributes = []
|
|
for attr in idlAttrs:
|
|
attributes.extend(self.attrData(attr, unforgeable))
|
|
self.chrome = [m for m in attributes if isChromeOnly(m["attr"])]
|
|
self.regular = [m for m in attributes if not isChromeOnly(m["attr"])]
|
|
self.static = static
|
|
|
|
if (
|
|
not static
|
|
and not unforgeable
|
|
and descriptor.interface.hasInterfacePrototypeObject()
|
|
):
|
|
self.regular.append(
|
|
{"name": "@@toStringTag", "attr": None, "flags": "JSPROP_READONLY"}
|
|
)
|
|
|
|
if static:
|
|
if not descriptor.interface.hasInterfaceObject():
|
|
# static attributes go on the interface object
|
|
assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
|
|
else:
|
|
if not descriptor.interface.hasInterfacePrototypeObject():
|
|
# non-static attributes go on the interface prototype object
|
|
assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
|
|
|
|
@staticmethod
|
|
def attrData(attr, unforgeable=False, overrideFlags=None):
|
|
if overrideFlags is None:
|
|
permanent = " | JSPROP_PERMANENT" if unforgeable else ""
|
|
flags = EnumerabilityFlags(attr) + permanent
|
|
else:
|
|
flags = overrideFlags
|
|
return (
|
|
{"name": name, "attr": attr, "flags": flags}
|
|
for name in [attr.identifier.name] + attr.bindingAliases
|
|
)
|
|
|
|
@staticmethod
|
|
def condition(m, d):
|
|
if m["name"] == "@@toStringTag":
|
|
return MemberCondition()
|
|
return PropertyDefiner.getControllingCondition(m["attr"], d)
|
|
|
|
@staticmethod
|
|
def specData(entry, descriptor, static=False, crossOriginOnly=False):
|
|
if entry["name"] == "@@toStringTag":
|
|
return (entry["name"], entry["flags"], descriptor.interface.getClassName())
|
|
|
|
def getter(attr):
|
|
if crossOriginOnly and not attr.getExtendedAttribute("CrossOriginReadable"):
|
|
return "nullptr, nullptr"
|
|
if static:
|
|
if attr.type.isPromise():
|
|
raise TypeError(
|
|
"Don't know how to handle "
|
|
"static Promise-returning "
|
|
"attribute %s.%s" % (self.descriptor.name, attr.identifier.name)
|
|
)
|
|
accessor = "get_" + IDLToCIdentifier(attr.identifier.name)
|
|
jitinfo = "nullptr"
|
|
else:
|
|
if attr.type.isPromise():
|
|
exceptionPolicy = "ConvertExceptionsToPromises"
|
|
else:
|
|
exceptionPolicy = "ThrowExceptions"
|
|
|
|
if attr.hasLenientThis():
|
|
if attr.getExtendedAttribute("CrossOriginReadable"):
|
|
raise TypeError(
|
|
"Can't handle lenient cross-origin "
|
|
"readable attribute %s.%s"
|
|
% (self.descriptor.name, attr.identifier.name)
|
|
)
|
|
if descriptor.interface.hasDescendantWithCrossOriginMembers:
|
|
accessor = (
|
|
"GenericGetter<MaybeCrossOriginObjectLenientThisPolicy, %s>"
|
|
% exceptionPolicy
|
|
)
|
|
else:
|
|
accessor = (
|
|
"GenericGetter<LenientThisPolicy, %s>" % exceptionPolicy
|
|
)
|
|
elif attr.getExtendedAttribute("CrossOriginReadable"):
|
|
accessor = (
|
|
"GenericGetter<CrossOriginThisPolicy, %s>" % exceptionPolicy
|
|
)
|
|
elif descriptor.interface.hasDescendantWithCrossOriginMembers:
|
|
accessor = (
|
|
"GenericGetter<MaybeCrossOriginObjectThisPolicy, %s>"
|
|
% exceptionPolicy
|
|
)
|
|
elif descriptor.interface.isOnGlobalProtoChain():
|
|
accessor = (
|
|
"GenericGetter<MaybeGlobalThisPolicy, %s>" % exceptionPolicy
|
|
)
|
|
else:
|
|
accessor = "GenericGetter<NormalThisPolicy, %s>" % exceptionPolicy
|
|
jitinfo = "&%s_getterinfo" % IDLToCIdentifier(attr.identifier.name)
|
|
return "%s, %s" % (accessor, jitinfo)
|
|
|
|
def setter(attr):
|
|
if (
|
|
attr.readonly
|
|
and attr.getExtendedAttribute("PutForwards") is None
|
|
and attr.getExtendedAttribute("Replaceable") is None
|
|
and attr.getExtendedAttribute("LenientSetter") is None
|
|
):
|
|
return "nullptr, nullptr"
|
|
if crossOriginOnly and not attr.getExtendedAttribute("CrossOriginWritable"):
|
|
return "nullptr, nullptr"
|
|
if static:
|
|
accessor = "set_" + IDLToCIdentifier(attr.identifier.name)
|
|
jitinfo = "nullptr"
|
|
else:
|
|
if attr.hasLenientThis():
|
|
if attr.getExtendedAttribute("CrossOriginWritable"):
|
|
raise TypeError(
|
|
"Can't handle lenient cross-origin "
|
|
"writable attribute %s.%s"
|
|
% (descriptor.name, attr.identifier.name)
|
|
)
|
|
if descriptor.interface.hasDescendantWithCrossOriginMembers:
|
|
accessor = (
|
|
"GenericSetter<MaybeCrossOriginObjectLenientThisPolicy>"
|
|
)
|
|
else:
|
|
accessor = "GenericSetter<LenientThisPolicy>"
|
|
elif attr.getExtendedAttribute("CrossOriginWritable"):
|
|
accessor = "GenericSetter<CrossOriginThisPolicy>"
|
|
elif descriptor.interface.hasDescendantWithCrossOriginMembers:
|
|
accessor = "GenericSetter<MaybeCrossOriginObjectThisPolicy>"
|
|
elif descriptor.interface.isOnGlobalProtoChain():
|
|
accessor = "GenericSetter<MaybeGlobalThisPolicy>"
|
|
else:
|
|
accessor = "GenericSetter<NormalThisPolicy>"
|
|
jitinfo = "&%s_setterinfo" % IDLToCIdentifier(attr.identifier.name)
|
|
return "%s, %s" % (accessor, jitinfo)
|
|
|
|
name, attr, flags = entry["name"], entry["attr"], entry["flags"]
|
|
return (name, flags, getter(attr), setter(attr))
|
|
|
|
@staticmethod
|
|
def formatSpec(fields):
|
|
if fields[0] == "@@toStringTag":
|
|
return ' JS_STRING_SYM_PS(%s, "%s", %s)' % (
|
|
fields[0][2:],
|
|
fields[2],
|
|
fields[1],
|
|
)
|
|
|
|
return ' JSPropertySpec::nativeAccessors("%s", %s, %s, %s)' % fields
|
|
|
|
def generateArray(self, array, name):
|
|
if len(array) == 0:
|
|
return ""
|
|
|
|
return self.generatePrefableArray(
|
|
array,
|
|
name,
|
|
self.formatSpec,
|
|
" JS_PS_END",
|
|
"JSPropertySpec",
|
|
self.condition,
|
|
functools.partial(self.specData, static=self.static),
|
|
)
|
|
|
|
|
|
class ConstDefiner(PropertyDefiner):
|
|
"""
|
|
A class for definining constants on the interface object
|
|
"""
|
|
|
|
def __init__(self, descriptor, name):
|
|
PropertyDefiner.__init__(self, descriptor, name)
|
|
self.name = name
|
|
constants = [m for m in descriptor.interface.members if m.isConst()]
|
|
self.chrome = [m for m in constants if isChromeOnly(m)]
|
|
self.regular = [m for m in constants if not isChromeOnly(m)]
|
|
|
|
def generateArray(self, array, name):
|
|
if len(array) == 0:
|
|
return ""
|
|
|
|
def specData(const, descriptor):
|
|
return (const.identifier.name, convertConstIDLValueToJSVal(const.value))
|
|
|
|
return self.generatePrefableArray(
|
|
array,
|
|
name,
|
|
lambda fields: ' { "%s", %s }' % fields,
|
|
" { 0, JS::UndefinedValue() }",
|
|
"ConstantSpec",
|
|
PropertyDefiner.getControllingCondition,
|
|
specData,
|
|
)
|
|
|
|
|
|
class PropertyArrays:
|
|
def __init__(self, descriptor, crossOriginOnly=False):
|
|
self.staticMethods = MethodDefiner(
|
|
descriptor, "StaticMethods", crossOriginOnly, static=True
|
|
)
|
|
self.staticAttrs = AttrDefiner(
|
|
descriptor, "StaticAttributes", crossOriginOnly, static=True
|
|
)
|
|
self.methods = MethodDefiner(
|
|
descriptor, "Methods", crossOriginOnly, static=False
|
|
)
|
|
self.attrs = AttrDefiner(
|
|
descriptor, "Attributes", crossOriginOnly, static=False
|
|
)
|
|
self.unforgeableMethods = MethodDefiner(
|
|
descriptor,
|
|
"UnforgeableMethods",
|
|
crossOriginOnly,
|
|
static=False,
|
|
unforgeable=True,
|
|
)
|
|
self.unforgeableAttrs = AttrDefiner(
|
|
descriptor,
|
|
"UnforgeableAttributes",
|
|
crossOriginOnly,
|
|
static=False,
|
|
unforgeable=True,
|
|
)
|
|
self.consts = ConstDefiner(descriptor, "Constants")
|
|
|
|
@staticmethod
|
|
def arrayNames():
|
|
return [
|
|
"staticMethods",
|
|
"staticAttrs",
|
|
"methods",
|
|
"attrs",
|
|
"unforgeableMethods",
|
|
"unforgeableAttrs",
|
|
"consts",
|
|
]
|
|
|
|
def hasChromeOnly(self):
|
|
return any(getattr(self, a).hasChromeOnly() for a in self.arrayNames())
|
|
|
|
def hasNonChromeOnly(self):
|
|
return any(getattr(self, a).hasNonChromeOnly() for a in self.arrayNames())
|
|
|
|
def __str__(self):
|
|
define = ""
|
|
for array in self.arrayNames():
|
|
define += str(getattr(self, array))
|
|
return define
|
|
|
|
|
|
class CGConstDefinition(CGThing):
|
|
"""
|
|
Given a const member of an interface, return the C++ static const definition
|
|
for the member. Should be part of the interface namespace in the header
|
|
file.
|
|
"""
|
|
|
|
def __init__(self, member):
|
|
assert (
|
|
member.isConst()
|
|
and member.value.type.isPrimitive()
|
|
and not member.value.type.nullable()
|
|
)
|
|
|
|
name = CppKeywords.checkMethodName(IDLToCIdentifier(member.identifier.name))
|
|
tag = member.value.type.tag()
|
|
value = member.value.value
|
|
if tag == IDLType.Tags.bool:
|
|
value = toStringBool(member.value.value)
|
|
self.const = "static const %s %s = %s;" % (builtinNames[tag], name, value)
|
|
|
|
def declare(self):
|
|
return self.const
|
|
|
|
def define(self):
|
|
return ""
|
|
|
|
def deps(self):
|
|
return []
|
|
|
|
|
|
class CGNativeProperties(CGList):
|
|
def __init__(self, descriptor, properties):
|
|
def generateNativeProperties(name, chrome):
|
|
def check(p):
|
|
return p.hasChromeOnly() if chrome else p.hasNonChromeOnly()
|
|
|
|
nativePropsInts = []
|
|
nativePropsPtrs = []
|
|
nativePropsDuos = []
|
|
|
|
duosOffset = 0
|
|
idsOffset = 0
|
|
for array in properties.arrayNames():
|
|
propertyArray = getattr(properties, array)
|
|
if check(propertyArray):
|
|
varName = propertyArray.variableName(chrome)
|
|
bitfields = "true, %d /* %s */" % (duosOffset, varName)
|
|
duosOffset += 1
|
|
nativePropsInts.append(CGGeneric(bitfields))
|
|
|
|
if propertyArray.usedForXrays():
|
|
ids = "&%s_propertyInfos[%d]" % (name, idsOffset)
|
|
idsOffset += propertyArray.length(chrome)
|
|
else:
|
|
ids = "nullptr"
|
|
duo = "{ %s, %s }" % (varName, ids)
|
|
nativePropsDuos.append(CGGeneric(duo))
|
|
else:
|
|
bitfields = "false, 0"
|
|
nativePropsInts.append(CGGeneric(bitfields))
|
|
|
|
iteratorAliasIndex = -1
|
|
for index, item in enumerate(properties.methods.regular):
|
|
if item.get("hasIteratorAlias"):
|
|
iteratorAliasIndex = index
|
|
break
|
|
nativePropsInts.append(CGGeneric(str(iteratorAliasIndex)))
|
|
|
|
nativePropsDuos = [
|
|
CGWrapper(
|
|
CGIndenter(CGList(nativePropsDuos, ",\n")), pre="{\n", post="\n}"
|
|
)
|
|
]
|
|
|
|
pre = "static const NativePropertiesN<%d> %s = {\n" % (duosOffset, name)
|
|
post = "\n};\n"
|
|
if descriptor.wantsXrays:
|
|
pre = fill(
|
|
"""
|
|
static uint16_t ${name}_sortedPropertyIndices[${size}];
|
|
static PropertyInfo ${name}_propertyInfos[${size}];
|
|
|
|
$*{pre}
|
|
""",
|
|
name=name,
|
|
size=idsOffset,
|
|
pre=pre,
|
|
)
|
|
if iteratorAliasIndex > 0:
|
|
# The iteratorAliasMethodIndex is a signed integer, so the
|
|
# max value it can store is 2^(nbits-1)-1.
|
|
post = fill(
|
|
"""
|
|
$*{post}
|
|
static_assert(${iteratorAliasIndex} < 1ull << (CHAR_BIT * sizeof(${name}.iteratorAliasMethodIndex) - 1),
|
|
"We have an iterator alias index that is oversized");
|
|
""",
|
|
post=post,
|
|
iteratorAliasIndex=iteratorAliasIndex,
|
|
name=name,
|
|
)
|
|
post = fill(
|
|
"""
|
|
$*{post}
|
|
static_assert(${propertyInfoCount} < 1ull << (CHAR_BIT * sizeof(${name}.propertyInfoCount)),
|
|
"We have a property info count that is oversized");
|
|
""",
|
|
post=post,
|
|
propertyInfoCount=idsOffset,
|
|
name=name,
|
|
)
|
|
nativePropsInts.append(CGGeneric("%d" % idsOffset))
|
|
nativePropsPtrs.append(CGGeneric("%s_sortedPropertyIndices" % name))
|
|
else:
|
|
nativePropsInts.append(CGGeneric("0"))
|
|
nativePropsPtrs.append(CGGeneric("nullptr"))
|
|
nativeProps = nativePropsInts + nativePropsPtrs + nativePropsDuos
|
|
return CGWrapper(CGIndenter(CGList(nativeProps, ",\n")), pre=pre, post=post)
|
|
|
|
nativeProperties = []
|
|
if properties.hasNonChromeOnly():
|
|
nativeProperties.append(
|
|
generateNativeProperties("sNativeProperties", False)
|
|
)
|
|
if properties.hasChromeOnly():
|
|
nativeProperties.append(
|
|
generateNativeProperties("sChromeOnlyNativeProperties", True)
|
|
)
|
|
|
|
CGList.__init__(self, nativeProperties, "\n")
|
|
|
|
def declare(self):
|
|
return ""
|
|
|
|
def define(self):
|
|
return CGList.define(self)
|
|
|
|
|
|
class CGCollectJSONAttributesMethod(CGAbstractMethod):
|
|
"""
|
|
Generate the CollectJSONAttributes method for an interface descriptor
|
|
"""
|
|
|
|
def __init__(self, descriptor, toJSONMethod):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("%s*" % descriptor.nativeType, "self"),
|
|
Argument("JS::Rooted<JSObject*>&", "result"),
|
|
]
|
|
CGAbstractMethod.__init__(
|
|
self, descriptor, "CollectJSONAttributes", "bool", args, canRunScript=True
|
|
)
|
|
self.toJSONMethod = toJSONMethod
|
|
|
|
def definition_body(self):
|
|
ret = ""
|
|
interface = self.descriptor.interface
|
|
toJSONCondition = PropertyDefiner.getControllingCondition(
|
|
self.toJSONMethod, self.descriptor
|
|
)
|
|
needUnwrappedObj = False
|
|
for m in interface.members:
|
|
if m.isAttr() and not m.isStatic() and m.type.isJSONType():
|
|
getAndDefine = fill(
|
|
"""
|
|
JS::Rooted<JS::Value> temp(cx);
|
|
if (!get_${name}(cx, obj, self, JSJitGetterCallArgs(&temp))) {
|
|
return false;
|
|
}
|
|
if (!JS_DefineProperty(cx, result, "${name}", temp, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
""",
|
|
name=IDLToCIdentifier(m.identifier.name),
|
|
)
|
|
# Make sure we don't include things which are supposed to be
|
|
# disabled. Things that either don't have disablers or whose
|
|
# disablers match the disablers for our toJSON method can't
|
|
# possibly be disabled, but other things might be.
|
|
condition = PropertyDefiner.getControllingCondition(m, self.descriptor)
|
|
if condition.hasDisablers() and condition != toJSONCondition:
|
|
needUnwrappedObj = True
|
|
ret += fill(
|
|
"""
|
|
// This is unfortunately a linear scan through sAttributes, but we
|
|
// only do it for things which _might_ be disabled, which should
|
|
// help keep the performance problems down.
|
|
if (IsGetterEnabled(cx, unwrappedObj, (JSJitGetterOp)get_${name}, sAttributes)) {
|
|
$*{getAndDefine}
|
|
}
|
|
""",
|
|
name=IDLToCIdentifier(m.identifier.name),
|
|
getAndDefine=getAndDefine,
|
|
)
|
|
else:
|
|
ret += fill(
|
|
"""
|
|
{ // scope for "temp"
|
|
$*{getAndDefine}
|
|
}
|
|
""",
|
|
getAndDefine=getAndDefine,
|
|
)
|
|
ret += "return true;\n"
|
|
|
|
if needUnwrappedObj:
|
|
# If we started allowing cross-origin objects here, we'd need to
|
|
# use CheckedUnwrapDynamic and figure out whether it makes sense.
|
|
# But in practice no one is trying to add toJSON methods to those,
|
|
# so let's just guard against it.
|
|
assert not self.descriptor.isMaybeCrossOriginObject()
|
|
ret = fill(
|
|
"""
|
|
JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrapStatic(obj));
|
|
if (!unwrappedObj) {
|
|
// How did that happen? We managed to get called with that
|
|
// object as "this"! Just give up on sanity.
|
|
return false;
|
|
}
|
|
|
|
$*{ret}
|
|
""",
|
|
ret=ret,
|
|
)
|
|
|
|
return ret
|
|
|
|
|
|
class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
|
|
"""
|
|
Generate the CreateInterfaceObjects method for an interface descriptor.
|
|
|
|
properties should be a PropertyArrays instance.
|
|
"""
|
|
|
|
def __init__(
|
|
self, descriptor, properties, haveUnscopables, haveLegacyWindowAliases
|
|
):
|
|
args = [
|
|
Argument("JSContext*", "aCx"),
|
|
Argument("JS::Handle<JSObject*>", "aGlobal"),
|
|
Argument("ProtoAndIfaceCache&", "aProtoAndIfaceCache"),
|
|
Argument("bool", "aDefineOnGlobal"),
|
|
]
|
|
CGAbstractMethod.__init__(
|
|
self, descriptor, "CreateInterfaceObjects", "void", args
|
|
)
|
|
self.properties = properties
|
|
self.haveUnscopables = haveUnscopables
|
|
self.haveLegacyWindowAliases = haveLegacyWindowAliases
|
|
|
|
def definition_body(self):
|
|
(protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter(
|
|
self.descriptor
|
|
)
|
|
if protoHandleGetter is None:
|
|
parentProtoType = "Rooted"
|
|
getParentProto = "aCx, " + protoGetter
|
|
else:
|
|
parentProtoType = "Handle"
|
|
getParentProto = protoHandleGetter
|
|
getParentProto = getParentProto + "(aCx)"
|
|
|
|
(protoGetter, protoHandleGetter) = InterfaceObjectProtoGetter(self.descriptor)
|
|
if protoHandleGetter is None:
|
|
getConstructorProto = "aCx, " + protoGetter
|
|
constructorProtoType = "Rooted"
|
|
else:
|
|
getConstructorProto = protoHandleGetter
|
|
constructorProtoType = "Handle"
|
|
getConstructorProto += "(aCx)"
|
|
|
|
needInterfaceObject = self.descriptor.interface.hasInterfaceObject()
|
|
needInterfacePrototypeObject = (
|
|
self.descriptor.interface.hasInterfacePrototypeObject()
|
|
)
|
|
|
|
# if we don't need to create anything, why are we generating this?
|
|
assert needInterfaceObject or needInterfacePrototypeObject
|
|
|
|
getParentProto = fill(
|
|
"""
|
|
JS::${type}<JSObject*> parentProto(${getParentProto});
|
|
if (!parentProto) {
|
|
return;
|
|
}
|
|
""",
|
|
type=parentProtoType,
|
|
getParentProto=getParentProto,
|
|
)
|
|
|
|
getConstructorProto = fill(
|
|
"""
|
|
JS::${type}<JSObject*> constructorProto(${getConstructorProto});
|
|
if (!constructorProto) {
|
|
return;
|
|
}
|
|
""",
|
|
type=constructorProtoType,
|
|
getConstructorProto=getConstructorProto,
|
|
)
|
|
|
|
idsToInit = []
|
|
# There is no need to init any IDs in bindings that don't want Xrays.
|
|
if self.descriptor.wantsXrays:
|
|
if self.properties.hasNonChromeOnly():
|
|
idsToInit.append("sNativeProperties")
|
|
if self.properties.hasChromeOnly():
|
|
idsToInit.append("sChromeOnlyNativeProperties")
|
|
if len(idsToInit) > 0:
|
|
initIdCalls = [
|
|
"!InitIds(aCx, %s.Upcast())" % (properties) for properties in idsToInit
|
|
]
|
|
idsInitedFlag = CGGeneric(
|
|
"static Atomic<bool, Relaxed> sIdsInited(false);\n"
|
|
)
|
|
setFlag = CGGeneric("sIdsInited = true;\n")
|
|
initIdConditionals = [
|
|
CGIfWrapper(CGGeneric("return;\n"), call) for call in initIdCalls
|
|
]
|
|
initIds = CGList(
|
|
[
|
|
idsInitedFlag,
|
|
CGIfWrapper(
|
|
CGList(initIdConditionals + [setFlag]),
|
|
"!sIdsInited && NS_IsMainThread()",
|
|
),
|
|
]
|
|
)
|
|
else:
|
|
initIds = None
|
|
|
|
if self.descriptor.interface.ctor():
|
|
constructArgs = methodLength(self.descriptor.interface.ctor())
|
|
else:
|
|
constructArgs = 0
|
|
if len(self.descriptor.interface.namedConstructors) > 0:
|
|
namedConstructors = "namedConstructors"
|
|
else:
|
|
namedConstructors = "nullptr"
|
|
|
|
if needInterfacePrototypeObject:
|
|
protoClass = "&sPrototypeClass.mBase"
|
|
protoCache = (
|
|
"&aProtoAndIfaceCache.EntrySlotOrCreate(prototypes::id::%s)"
|
|
% self.descriptor.name
|
|
)
|
|
parentProto = "parentProto"
|
|
getParentProto = CGGeneric(getParentProto)
|
|
else:
|
|
protoClass = "nullptr"
|
|
protoCache = "nullptr"
|
|
parentProto = "nullptr"
|
|
getParentProto = None
|
|
|
|
if needInterfaceObject:
|
|
interfaceClass = "&sInterfaceObjectClass.mBase"
|
|
interfaceCache = (
|
|
"&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)"
|
|
% self.descriptor.name
|
|
)
|
|
getConstructorProto = CGGeneric(getConstructorProto)
|
|
constructorProto = "constructorProto"
|
|
else:
|
|
# We don't have slots to store the named constructors.
|
|
assert len(self.descriptor.interface.namedConstructors) == 0
|
|
interfaceClass = "nullptr"
|
|
interfaceCache = "nullptr"
|
|
getConstructorProto = None
|
|
constructorProto = "nullptr"
|
|
|
|
isGlobal = self.descriptor.isGlobal() is not None
|
|
if self.properties.hasNonChromeOnly():
|
|
properties = "sNativeProperties.Upcast()"
|
|
else:
|
|
properties = "nullptr"
|
|
if self.properties.hasChromeOnly():
|
|
chromeProperties = "sChromeOnlyNativeProperties.Upcast()"
|
|
else:
|
|
chromeProperties = "nullptr"
|
|
|
|
call = fill(
|
|
"""
|
|
JS::Heap<JSObject*>* protoCache = ${protoCache};
|
|
JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
|
|
dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
|
|
${protoClass}, protoCache,
|
|
${constructorProto}, ${interfaceClass}, ${constructArgs}, ${namedConstructors},
|
|
interfaceCache,
|
|
${properties},
|
|
${chromeProperties},
|
|
${name}, aDefineOnGlobal,
|
|
${unscopableNames},
|
|
${isGlobal},
|
|
${legacyWindowAliases});
|
|
""",
|
|
protoClass=protoClass,
|
|
parentProto=parentProto,
|
|
protoCache=protoCache,
|
|
constructorProto=constructorProto,
|
|
interfaceClass=interfaceClass,
|
|
constructArgs=constructArgs,
|
|
namedConstructors=namedConstructors,
|
|
interfaceCache=interfaceCache,
|
|
properties=properties,
|
|
chromeProperties=chromeProperties,
|
|
name='"' + self.descriptor.interface.identifier.name + '"'
|
|
if needInterfaceObject
|
|
else "nullptr",
|
|
unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr",
|
|
isGlobal=toStringBool(isGlobal),
|
|
legacyWindowAliases="legacyWindowAliases"
|
|
if self.haveLegacyWindowAliases
|
|
else "nullptr",
|
|
)
|
|
|
|
# If we fail after here, we must clear interface and prototype caches
|
|
# using this code: intermediate failure must not expose the interface in
|
|
# partially-constructed state. Note that every case after here needs an
|
|
# interface prototype object.
|
|
failureCode = dedent(
|
|
"""
|
|
*protoCache = nullptr;
|
|
if (interfaceCache) {
|
|
*interfaceCache = nullptr;
|
|
}
|
|
return;
|
|
"""
|
|
)
|
|
|
|
aliasedMembers = [
|
|
m for m in self.descriptor.interface.members if m.isMethod() and m.aliases
|
|
]
|
|
if aliasedMembers:
|
|
assert needInterfacePrototypeObject
|
|
|
|
def defineAlias(alias):
|
|
if alias == "@@iterator":
|
|
symbolJSID = "SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::iterator))"
|
|
getSymbolJSID = CGGeneric(
|
|
fill(
|
|
"JS::Rooted<jsid> iteratorId(aCx, ${symbolJSID});",
|
|
symbolJSID=symbolJSID,
|
|
)
|
|
)
|
|
defineFn = "JS_DefinePropertyById"
|
|
prop = "iteratorId"
|
|
enumFlags = "0" # Not enumerable, per spec.
|
|
elif alias.startswith("@@"):
|
|
raise TypeError(
|
|
"Can't handle any well-known Symbol other than @@iterator"
|
|
)
|
|
else:
|
|
getSymbolJSID = None
|
|
defineFn = "JS_DefineProperty"
|
|
prop = '"%s"' % alias
|
|
# XXX If we ever create non-enumerable properties that can
|
|
# be aliased, we should consider making the aliases
|
|
# match the enumerability of the property being aliased.
|
|
enumFlags = "JSPROP_ENUMERATE"
|
|
return CGList(
|
|
[
|
|
getSymbolJSID,
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
if (!${defineFn}(aCx, proto, ${prop}, aliasedVal, ${enumFlags})) {
|
|
$*{failureCode}
|
|
}
|
|
""",
|
|
defineFn=defineFn,
|
|
prop=prop,
|
|
enumFlags=enumFlags,
|
|
failureCode=failureCode,
|
|
)
|
|
),
|
|
],
|
|
"\n",
|
|
)
|
|
|
|
def defineAliasesFor(m):
|
|
return CGList(
|
|
[
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
if (!JS_GetProperty(aCx, proto, \"${prop}\", &aliasedVal)) {
|
|
$*{failureCode}
|
|
}
|
|
""",
|
|
failureCode=failureCode,
|
|
prop=m.identifier.name,
|
|
)
|
|
)
|
|
]
|
|
+ [defineAlias(alias) for alias in sorted(m.aliases)]
|
|
)
|
|
|
|
defineAliases = CGList(
|
|
[
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
// Set up aliases on the interface prototype object we just created.
|
|
JS::Handle<JSObject*> proto = GetProtoObjectHandle(aCx);
|
|
if (!proto) {
|
|
$*{failureCode}
|
|
}
|
|
|
|
""",
|
|
failureCode=failureCode,
|
|
)
|
|
),
|
|
CGGeneric("JS::Rooted<JS::Value> aliasedVal(aCx);\n\n"),
|
|
]
|
|
+ [
|
|
defineAliasesFor(m)
|
|
for m in sorted(aliasedMembers, key=lambda m: m.identifier.name)
|
|
]
|
|
)
|
|
else:
|
|
defineAliases = None
|
|
|
|
# Globals handle unforgeables directly in Wrap() instead of
|
|
# via a holder.
|
|
if self.descriptor.hasUnforgeableMembers and not self.descriptor.isGlobal():
|
|
assert needInterfacePrototypeObject
|
|
|
|
# We want to use the same JSClass and prototype as the object we'll
|
|
# end up defining the unforgeable properties on in the end, so that
|
|
# we can use JS_InitializePropertiesFromCompatibleNativeObject to do
|
|
# a fast copy. In the case of proxies that's null, because the
|
|
# expando object is a vanilla object, but in the case of other DOM
|
|
# objects it's whatever our class is.
|
|
if self.descriptor.proxy:
|
|
holderClass = "nullptr"
|
|
holderProto = "nullptr"
|
|
else:
|
|
holderClass = "sClass.ToJSClass()"
|
|
holderProto = "*protoCache"
|
|
createUnforgeableHolder = CGGeneric(
|
|
fill(
|
|
"""
|
|
JS::Rooted<JSObject*> unforgeableHolder(aCx);
|
|
{
|
|
JS::Rooted<JSObject*> holderProto(aCx, ${holderProto});
|
|
unforgeableHolder = JS_NewObjectWithoutMetadata(aCx, ${holderClass}, holderProto);
|
|
if (!unforgeableHolder) {
|
|
$*{failureCode}
|
|
}
|
|
}
|
|
""",
|
|
holderProto=holderProto,
|
|
holderClass=holderClass,
|
|
failureCode=failureCode,
|
|
)
|
|
)
|
|
defineUnforgeables = InitUnforgeablePropertiesOnHolder(
|
|
self.descriptor, self.properties, failureCode
|
|
)
|
|
createUnforgeableHolder = CGList(
|
|
[createUnforgeableHolder, defineUnforgeables]
|
|
)
|
|
|
|
installUnforgeableHolder = CGGeneric(
|
|
dedent(
|
|
"""
|
|
if (*protoCache) {
|
|
JS::SetReservedSlot(*protoCache, DOM_INTERFACE_PROTO_SLOTS_BASE,
|
|
JS::ObjectValue(*unforgeableHolder));
|
|
}
|
|
"""
|
|
)
|
|
)
|
|
|
|
unforgeableHolderSetup = CGList(
|
|
[createUnforgeableHolder, installUnforgeableHolder], "\n"
|
|
)
|
|
else:
|
|
unforgeableHolderSetup = None
|
|
|
|
if (
|
|
self.descriptor.interface.isOnGlobalProtoChain()
|
|
and needInterfacePrototypeObject
|
|
):
|
|
makeProtoPrototypeImmutable = CGGeneric(
|
|
fill(
|
|
"""
|
|
if (*${protoCache}) {
|
|
bool succeeded;
|
|
JS::Handle<JSObject*> prot = GetProtoObjectHandle(aCx);
|
|
if (!JS_SetImmutablePrototype(aCx, prot, &succeeded)) {
|
|
$*{failureCode}
|
|
}
|
|
|
|
MOZ_ASSERT(succeeded,
|
|
"making a fresh prototype object's [[Prototype]] "
|
|
"immutable can internally fail, but it should "
|
|
"never be unsuccessful");
|
|
}
|
|
""",
|
|
protoCache=protoCache,
|
|
failureCode=failureCode,
|
|
)
|
|
)
|
|
else:
|
|
makeProtoPrototypeImmutable = None
|
|
|
|
return CGList(
|
|
[
|
|
getParentProto,
|
|
getConstructorProto,
|
|
initIds,
|
|
CGGeneric(call),
|
|
defineAliases,
|
|
unforgeableHolderSetup,
|
|
makeProtoPrototypeImmutable,
|
|
],
|
|
"\n",
|
|
).define()
|
|
|
|
|
|
class CGGetProtoObjectHandleMethod(CGAbstractMethod):
|
|
"""
|
|
A method for getting the interface prototype object.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"GetProtoObjectHandle",
|
|
"JS::Handle<JSObject*>",
|
|
[Argument("JSContext*", "aCx")],
|
|
inline=True,
|
|
)
|
|
|
|
def definition_body(self):
|
|
return fill(
|
|
"""
|
|
/* Get the interface prototype object for this class. This will create the
|
|
object as needed. */
|
|
return GetPerInterfaceObjectHandle(aCx, prototypes::id::${name},
|
|
&CreateInterfaceObjects,
|
|
/* aDefineOnGlobal = */ true);
|
|
|
|
""",
|
|
name=self.descriptor.name,
|
|
)
|
|
|
|
|
|
class CGGetProtoObjectMethod(CGAbstractMethod):
|
|
"""
|
|
A method for getting the interface prototype object.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"GetProtoObject",
|
|
"JSObject*",
|
|
[Argument("JSContext*", "aCx")],
|
|
)
|
|
|
|
def definition_body(self):
|
|
return "return GetProtoObjectHandle(aCx);\n"
|
|
|
|
|
|
class CGGetConstructorObjectHandleMethod(CGAbstractMethod):
|
|
"""
|
|
A method for getting the interface constructor object.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"GetConstructorObjectHandle",
|
|
"JS::Handle<JSObject*>",
|
|
[
|
|
Argument("JSContext*", "aCx"),
|
|
Argument("bool", "aDefineOnGlobal", "true"),
|
|
],
|
|
inline=True,
|
|
)
|
|
|
|
def definition_body(self):
|
|
return fill(
|
|
"""
|
|
/* Get the interface object for this class. This will create the object as
|
|
needed. */
|
|
|
|
return GetPerInterfaceObjectHandle(aCx, constructors::id::${name},
|
|
&CreateInterfaceObjects,
|
|
aDefineOnGlobal);
|
|
""",
|
|
name=self.descriptor.name,
|
|
)
|
|
|
|
|
|
class CGGetConstructorObjectMethod(CGAbstractMethod):
|
|
"""
|
|
A method for getting the interface constructor object.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"GetConstructorObject",
|
|
"JSObject*",
|
|
[Argument("JSContext*", "aCx")],
|
|
)
|
|
|
|
def definition_body(self):
|
|
return "return GetConstructorObjectHandle(aCx);\n"
|
|
|
|
|
|
class CGGetNamedPropertiesObjectMethod(CGAbstractStaticMethod):
|
|
def __init__(self, descriptor):
|
|
args = [Argument("JSContext*", "aCx")]
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, "GetNamedPropertiesObject", "JSObject*", args
|
|
)
|
|
|
|
def definition_body(self):
|
|
parentProtoName = self.descriptor.parentPrototypeName
|
|
if parentProtoName is None:
|
|
getParentProto = ""
|
|
parentProto = "nullptr"
|
|
else:
|
|
getParentProto = fill(
|
|
"""
|
|
JS::Rooted<JSObject*> parentProto(aCx, ${parent}::GetProtoObjectHandle(aCx));
|
|
if (!parentProto) {
|
|
return nullptr;
|
|
}
|
|
""",
|
|
parent=toBindingNamespace(parentProtoName),
|
|
)
|
|
parentProto = "parentProto"
|
|
return fill(
|
|
"""
|
|
/* Make sure our global is sane. Hopefully we can remove this sometime */
|
|
JSObject* global = JS::CurrentGlobalOrNull(aCx);
|
|
if (!(JS::GetClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* Check to see whether the named properties object has already been created */
|
|
ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global);
|
|
|
|
JS::Heap<JSObject*>& namedPropertiesObject = protoAndIfaceCache.EntrySlotOrCreate(namedpropertiesobjects::id::${ifaceName});
|
|
if (!namedPropertiesObject) {
|
|
$*{getParentProto}
|
|
namedPropertiesObject = ${nativeType}::CreateNamedPropertiesObject(aCx, ${parentProto});
|
|
DebugOnly<const DOMIfaceAndProtoJSClass*> clasp =
|
|
DOMIfaceAndProtoJSClass::FromJSClass(JS::GetClass(namedPropertiesObject));
|
|
MOZ_ASSERT(clasp->mType == eNamedPropertiesObject,
|
|
"Expected ${nativeType}::CreateNamedPropertiesObject to return a named properties object");
|
|
MOZ_ASSERT(clasp->mNativeHooks,
|
|
"The named properties object for ${nativeType} should have NativePropertyHooks.");
|
|
MOZ_ASSERT(!clasp->mNativeHooks->mResolveOwnProperty,
|
|
"Shouldn't resolve the properties of the named properties object for ${nativeType} for Xrays.");
|
|
MOZ_ASSERT(!clasp->mNativeHooks->mEnumerateOwnProperties,
|
|
"Shouldn't enumerate the properties of the named properties object for ${nativeType} for Xrays.");
|
|
}
|
|
return namedPropertiesObject.get();
|
|
""",
|
|
getParentProto=getParentProto,
|
|
ifaceName=self.descriptor.name,
|
|
parentProto=parentProto,
|
|
nativeType=self.descriptor.nativeType,
|
|
)
|
|
|
|
|
|
def getRawConditionList(idlobj, cxName, objName, ignoreSecureContext=False):
|
|
"""
|
|
Get the list of conditions for idlobj (to be used in "is this enabled"
|
|
checks). This will be returned as a CGList with " &&\n" as the separator,
|
|
for readability.
|
|
|
|
objName is the name of the object that we're working with, because some of
|
|
our test functions want that.
|
|
|
|
ignoreSecureContext is used only for constructors in which the WebIDL interface
|
|
itself is already marked as [SecureContext]. There is no need to do the work twice.
|
|
"""
|
|
conditions = []
|
|
pref = idlobj.getExtendedAttribute("Pref")
|
|
if pref:
|
|
assert isinstance(pref, list) and len(pref) == 1
|
|
conditions.append("StaticPrefs::%s()" % prefIdentifier(pref[0]))
|
|
if isChromeOnly(idlobj):
|
|
conditions.append("nsContentUtils::ThreadsafeIsSystemCaller(%s)" % cxName)
|
|
func = idlobj.getExtendedAttribute("Func")
|
|
if func:
|
|
assert isinstance(func, list) and len(func) == 1
|
|
conditions.append("%s(%s, %s)" % (func[0], cxName, objName))
|
|
if not ignoreSecureContext and idlobj.getExtendedAttribute("SecureContext"):
|
|
conditions.append(
|
|
"mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(%s, %s)"
|
|
% (cxName, objName)
|
|
)
|
|
return conditions
|
|
|
|
|
|
def getConditionList(idlobj, cxName, objName, ignoreSecureContext=False):
|
|
"""
|
|
Get the list of conditions from getRawConditionList
|
|
See comment on getRawConditionList above for more info about arguments.
|
|
|
|
The return value is a possibly-empty conjunctive CGList of conditions.
|
|
"""
|
|
conditions = getRawConditionList(idlobj, cxName, objName, ignoreSecureContext)
|
|
return CGList((CGGeneric(cond) for cond in conditions), " &&\n")
|
|
|
|
|
|
class CGConstructorEnabled(CGAbstractMethod):
|
|
"""
|
|
A method for testing whether we should be exposing this interface object.
|
|
This can perform various tests depending on what conditions are specified
|
|
on the interface.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"ConstructorEnabled",
|
|
"bool",
|
|
[Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
|
|
)
|
|
|
|
def definition_body(self):
|
|
body = CGList([], "\n")
|
|
|
|
iface = self.descriptor.interface
|
|
|
|
if not iface.isExposedInWindow():
|
|
exposedInWindowCheck = dedent(
|
|
"""
|
|
MOZ_ASSERT(!NS_IsMainThread(), "Why did we even get called?");
|
|
"""
|
|
)
|
|
body.append(CGGeneric(exposedInWindowCheck))
|
|
|
|
if iface.isExposedInSomeButNotAllWorkers():
|
|
workerGlobals = sorted(iface.getWorkerExposureSet())
|
|
workerCondition = CGList(
|
|
(
|
|
CGGeneric('strcmp(name, "%s")' % workerGlobal)
|
|
for workerGlobal in workerGlobals
|
|
),
|
|
" && ",
|
|
)
|
|
exposedInWorkerCheck = fill(
|
|
"""
|
|
const char* name = JS::GetClass(aObj)->name;
|
|
if (${workerCondition}) {
|
|
return false;
|
|
}
|
|
""",
|
|
workerCondition=workerCondition.define(),
|
|
)
|
|
exposedInWorkerCheck = CGGeneric(exposedInWorkerCheck)
|
|
if iface.isExposedInWindow():
|
|
exposedInWorkerCheck = CGIfWrapper(
|
|
exposedInWorkerCheck, "!NS_IsMainThread()"
|
|
)
|
|
body.append(exposedInWorkerCheck)
|
|
|
|
conditions = getConditionList(iface, "aCx", "aObj")
|
|
|
|
# We should really have some conditions
|
|
assert len(body) or len(conditions)
|
|
|
|
conditionsWrapper = ""
|
|
if len(conditions):
|
|
conditionsWrapper = CGWrapper(
|
|
conditions, pre="return ", post=";\n", reindent=True
|
|
)
|
|
else:
|
|
conditionsWrapper = CGGeneric("return true;\n")
|
|
|
|
body.append(conditionsWrapper)
|
|
return body.define()
|
|
|
|
|
|
def StructuredCloneTag(name):
|
|
return "SCTAG_DOM_%s" % name.upper()
|
|
|
|
|
|
class CGSerializer(CGAbstractStaticMethod):
|
|
"""
|
|
Implementation of serialization for things marked [Serializable].
|
|
This gets stored in our DOMJSClass, so it can be static.
|
|
|
|
The caller is expected to pass in the object whose DOMJSClass it
|
|
used to get the serializer.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "aCx"),
|
|
Argument("JSStructuredCloneWriter*", "aWriter"),
|
|
Argument("JS::Handle<JSObject*>", "aObj"),
|
|
]
|
|
CGAbstractStaticMethod.__init__(self, descriptor, "Serialize", "bool", args)
|
|
|
|
def definition_body(self):
|
|
return fill(
|
|
"""
|
|
MOZ_ASSERT(IsDOMObject(aObj), "Non-DOM object passed");
|
|
MOZ_ASSERT(GetDOMClass(aObj)->mSerializer == &Serialize,
|
|
"Wrong object passed");
|
|
return JS_WriteUint32Pair(aWriter, ${tag}, 0) &&
|
|
UnwrapDOMObject<${type}>(aObj)->WriteStructuredClone(aCx, aWriter);
|
|
""",
|
|
tag=StructuredCloneTag(self.descriptor.name),
|
|
type=self.descriptor.nativeType,
|
|
)
|
|
|
|
|
|
class CGDeserializer(CGAbstractMethod):
|
|
"""
|
|
Implementation of deserialization for things marked [Serializable].
|
|
This will need to be accessed from WebIDLSerializable, so can't be static.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "aCx"),
|
|
Argument("nsIGlobalObject*", "aGlobal"),
|
|
Argument("JSStructuredCloneReader*", "aReader"),
|
|
]
|
|
CGAbstractMethod.__init__(self, descriptor, "Deserialize", "JSObject*", args)
|
|
|
|
def definition_body(self):
|
|
# WrapObject has different signatures depending on whether
|
|
# the object is wrappercached.
|
|
if self.descriptor.wrapperCache:
|
|
wrapCall = dedent(
|
|
"""
|
|
result = obj->WrapObject(aCx, nullptr);
|
|
if (!result) {
|
|
return nullptr;
|
|
}
|
|
"""
|
|
)
|
|
else:
|
|
wrapCall = dedent(
|
|
"""
|
|
if (!obj->WrapObject(aCx, nullptr, &result)) {
|
|
return nullptr;
|
|
}
|
|
"""
|
|
)
|
|
|
|
return fill(
|
|
"""
|
|
// Protect the result from a moving GC in ~RefPtr
|
|
JS::Rooted<JSObject*> result(aCx);
|
|
{ // Scope for the RefPtr
|
|
RefPtr<${type}> obj = ${type}::ReadStructuredClone(aCx, aGlobal, aReader);
|
|
if (!obj) {
|
|
return nullptr;
|
|
}
|
|
$*{wrapCall}
|
|
}
|
|
return result;
|
|
""",
|
|
type=self.descriptor.nativeType,
|
|
wrapCall=wrapCall,
|
|
)
|
|
|
|
|
|
def CreateBindingJSObject(descriptor, properties):
|
|
objDecl = "BindingJSObjectCreator<%s> creator(aCx);\n" % descriptor.nativeType
|
|
|
|
# We don't always need to root obj, but there are a variety
|
|
# of cases where we do, so for simplicity, just always root it.
|
|
if descriptor.proxy:
|
|
if descriptor.interface.getExtendedAttribute("OverrideBuiltins"):
|
|
assert not descriptor.isMaybeCrossOriginObject()
|
|
create = dedent(
|
|
"""
|
|
aObject->mExpandoAndGeneration.expando.setUndefined();
|
|
JS::Rooted<JS::Value> expandoValue(aCx, JS::PrivateValue(&aObject->mExpandoAndGeneration));
|
|
creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
|
|
proto, /* aLazyProto = */ false, aObject,
|
|
expandoValue, aReflector);
|
|
"""
|
|
)
|
|
else:
|
|
if descriptor.isMaybeCrossOriginObject():
|
|
proto = "nullptr"
|
|
lazyProto = "true"
|
|
else:
|
|
proto = "proto"
|
|
lazyProto = "false"
|
|
create = fill(
|
|
"""
|
|
creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
|
|
${proto}, /* aLazyProto = */ ${lazyProto},
|
|
aObject, JS::UndefinedHandleValue, aReflector);
|
|
""",
|
|
proto=proto,
|
|
lazyProto=lazyProto,
|
|
)
|
|
else:
|
|
create = dedent(
|
|
"""
|
|
creator.CreateObject(aCx, sClass.ToJSClass(), proto, aObject, aReflector);
|
|
"""
|
|
)
|
|
return (
|
|
objDecl
|
|
+ create
|
|
+ dedent(
|
|
"""
|
|
if (!aReflector) {
|
|
return false;
|
|
}
|
|
"""
|
|
)
|
|
)
|
|
|
|
|
|
def InitUnforgeablePropertiesOnHolder(
|
|
descriptor, properties, failureCode, holderName="unforgeableHolder"
|
|
):
|
|
"""
|
|
Define the unforgeable properties on the unforgeable holder for
|
|
the interface represented by descriptor.
|
|
|
|
properties is a PropertyArrays instance.
|
|
|
|
"""
|
|
assert (
|
|
properties.unforgeableAttrs.hasNonChromeOnly()
|
|
or properties.unforgeableAttrs.hasChromeOnly()
|
|
or properties.unforgeableMethods.hasNonChromeOnly()
|
|
or properties.unforgeableMethods.hasChromeOnly()
|
|
)
|
|
|
|
unforgeables = []
|
|
|
|
defineUnforgeableAttrs = fill(
|
|
"""
|
|
if (!DefineUnforgeableAttributes(aCx, ${holderName}, %s)) {
|
|
$*{failureCode}
|
|
}
|
|
""",
|
|
failureCode=failureCode,
|
|
holderName=holderName,
|
|
)
|
|
defineUnforgeableMethods = fill(
|
|
"""
|
|
if (!DefineUnforgeableMethods(aCx, ${holderName}, %s)) {
|
|
$*{failureCode}
|
|
}
|
|
""",
|
|
failureCode=failureCode,
|
|
holderName=holderName,
|
|
)
|
|
|
|
unforgeableMembers = [
|
|
(defineUnforgeableAttrs, properties.unforgeableAttrs),
|
|
(defineUnforgeableMethods, properties.unforgeableMethods),
|
|
]
|
|
for (template, array) in unforgeableMembers:
|
|
if array.hasNonChromeOnly():
|
|
unforgeables.append(CGGeneric(template % array.variableName(False)))
|
|
if array.hasChromeOnly():
|
|
unforgeables.append(
|
|
CGIfWrapper(
|
|
CGGeneric(template % array.variableName(True)),
|
|
"nsContentUtils::ThreadsafeIsSystemCaller(aCx)",
|
|
)
|
|
)
|
|
|
|
if descriptor.interface.getExtendedAttribute("Unforgeable"):
|
|
# We do our undefined toPrimitive here, not as a regular property
|
|
# because we don't have a concept of value props anywhere in IDL.
|
|
unforgeables.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
JS::RootedId toPrimitive(aCx,
|
|
SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::toPrimitive)));
|
|
if (!JS_DefinePropertyById(aCx, ${holderName}, toPrimitive,
|
|
JS::UndefinedHandleValue,
|
|
JSPROP_READONLY | JSPROP_PERMANENT)) {
|
|
$*{failureCode}
|
|
}
|
|
""",
|
|
failureCode=failureCode,
|
|
holderName=holderName,
|
|
)
|
|
)
|
|
)
|
|
|
|
return CGWrapper(CGList(unforgeables), pre="\n")
|
|
|
|
|
|
def CopyUnforgeablePropertiesToInstance(descriptor, failureCode):
|
|
"""
|
|
Copy the unforgeable properties from the unforgeable holder for
|
|
this interface to the instance object we have.
|
|
"""
|
|
assert not descriptor.isGlobal()
|
|
|
|
if not descriptor.hasUnforgeableMembers:
|
|
return ""
|
|
|
|
copyCode = [
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
// Important: do unforgeable property setup after we have handed
|
|
// over ownership of the C++ object to obj as needed, so that if
|
|
// we fail and it ends up GCed it won't have problems in the
|
|
// finalizer trying to drop its ownership of the C++ object.
|
|
"""
|
|
)
|
|
)
|
|
]
|
|
|
|
# For proxies, we want to define on the expando object, not directly on the
|
|
# reflector, so we can make sure we don't get confused by named getters.
|
|
if descriptor.proxy:
|
|
copyCode.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
JS::Rooted<JSObject*> expando(aCx,
|
|
DOMProxyHandler::EnsureExpandoObject(aCx, aReflector));
|
|
if (!expando) {
|
|
$*{failureCode}
|
|
}
|
|
""",
|
|
failureCode=failureCode,
|
|
)
|
|
)
|
|
)
|
|
obj = "expando"
|
|
else:
|
|
obj = "aReflector"
|
|
|
|
copyCode.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
JS::Rooted<JSObject*> unforgeableHolder(aCx,
|
|
&JS::GetReservedSlot(canonicalProto, DOM_INTERFACE_PROTO_SLOTS_BASE).toObject());
|
|
if (!JS_InitializePropertiesFromCompatibleNativeObject(aCx, ${obj}, unforgeableHolder)) {
|
|
$*{failureCode}
|
|
}
|
|
""",
|
|
obj=obj,
|
|
failureCode=failureCode,
|
|
)
|
|
)
|
|
)
|
|
|
|
return CGWrapper(CGList(copyCode), pre="\n").define()
|
|
|
|
|
|
def AssertInheritanceChain(descriptor):
|
|
asserts = ""
|
|
iface = descriptor.interface
|
|
while iface:
|
|
desc = descriptor.getDescriptor(iface.identifier.name)
|
|
asserts += (
|
|
"MOZ_ASSERT(static_cast<%s*>(aObject) == \n"
|
|
" reinterpret_cast<%s*>(aObject),\n"
|
|
' "Multiple inheritance for %s is broken.");\n'
|
|
% (desc.nativeType, desc.nativeType, desc.nativeType)
|
|
)
|
|
iface = iface.parent
|
|
asserts += "MOZ_ASSERT(ToSupportsIsCorrect(aObject));\n"
|
|
return asserts
|
|
|
|
|
|
def InitMemberSlots(descriptor, failureCode):
|
|
"""
|
|
Initialize member slots on our JS object if we're supposed to have some.
|
|
|
|
Note that this is called after the SetWrapper() call in the
|
|
wrapperCache case, since that can affect how our getters behave
|
|
and we plan to invoke them here. So if we fail, we need to
|
|
ClearWrapper.
|
|
"""
|
|
if not descriptor.interface.hasMembersInSlots():
|
|
return ""
|
|
return fill(
|
|
"""
|
|
if (!UpdateMemberSlots(aCx, aReflector, aObject)) {
|
|
$*{failureCode}
|
|
}
|
|
""",
|
|
failureCode=failureCode,
|
|
)
|
|
|
|
|
|
def DeclareProto(descriptor):
|
|
"""
|
|
Declare the canonicalProto and proto we have for our wrapping operation.
|
|
"""
|
|
preamble = dedent(
|
|
"""
|
|
JS::Handle<JSObject*> canonicalProto = GetProtoObjectHandle(aCx);
|
|
if (!canonicalProto) {
|
|
return false;
|
|
}
|
|
JS::Rooted<JSObject*> proto(aCx);
|
|
"""
|
|
)
|
|
if descriptor.isMaybeCrossOriginObject():
|
|
return preamble + dedent(
|
|
"""
|
|
MOZ_ASSERT(!aGivenProto,
|
|
"Shouldn't have constructors on cross-origin objects");
|
|
// Set proto to canonicalProto to avoid preserving our wrapper if
|
|
// we don't have to.
|
|
proto = canonicalProto;
|
|
"""
|
|
)
|
|
|
|
return preamble + dedent(
|
|
"""
|
|
if (aGivenProto) {
|
|
proto = aGivenProto;
|
|
// Unfortunately, while aGivenProto was in the compartment of aCx
|
|
// coming in, we changed compartments to that of "parent" so may need
|
|
// to wrap the proto here.
|
|
if (js::GetContextCompartment(aCx) != JS::GetCompartment(proto)) {
|
|
if (!JS_WrapObject(aCx, &proto)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
proto = canonicalProto;
|
|
}
|
|
"""
|
|
)
|
|
|
|
|
|
class CGWrapWithCacheMethod(CGAbstractMethod):
|
|
"""
|
|
Create a wrapper JSObject for a given native that implements nsWrapperCache.
|
|
|
|
properties should be a PropertyArrays instance.
|
|
"""
|
|
|
|
def __init__(self, descriptor, properties):
|
|
assert descriptor.interface.hasInterfacePrototypeObject()
|
|
args = [
|
|
Argument("JSContext*", "aCx"),
|
|
Argument(descriptor.nativeType + "*", "aObject"),
|
|
Argument("nsWrapperCache*", "aCache"),
|
|
Argument("JS::Handle<JSObject*>", "aGivenProto"),
|
|
Argument("JS::MutableHandle<JSObject*>", "aReflector"),
|
|
]
|
|
CGAbstractMethod.__init__(self, descriptor, "Wrap", "bool", args)
|
|
self.properties = properties
|
|
|
|
def definition_body(self):
|
|
failureCode = dedent(
|
|
"""
|
|
aCache->ReleaseWrapper(aObject);
|
|
aCache->ClearWrapper();
|
|
return false;
|
|
"""
|
|
)
|
|
|
|
if self.descriptor.proxy:
|
|
finalize = "DOMProxyHandler::getInstance()->finalize"
|
|
else:
|
|
finalize = FINALIZE_HOOK_NAME
|
|
|
|
return fill(
|
|
"""
|
|
static_assert(!std::is_base_of_v<NonRefcountedDOMObject, ${nativeType}>,
|
|
"Shouldn't have wrappercached things that are not refcounted.");
|
|
$*{assertInheritance}
|
|
MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
|
|
MOZ_ASSERT(!aCache->GetWrapper(),
|
|
"You should probably not be using Wrap() directly; use "
|
|
"GetOrCreateDOMReflector instead");
|
|
|
|
MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
|
|
"nsISupports must be on our primary inheritance chain");
|
|
|
|
// If the wrapper cache contains a dead reflector then finalize that
|
|
// now, ensuring that the finalizer for the old reflector always
|
|
// runs before the new reflector is created and attached. This
|
|
// avoids the awkward situation where there are multiple reflector
|
|
// objects that contain pointers to the same native.
|
|
|
|
if (JSObject* oldReflector = aCache->GetWrapperMaybeDead()) {
|
|
${finalize}(nullptr /* unused */, oldReflector);
|
|
MOZ_ASSERT(!aCache->GetWrapperMaybeDead());
|
|
}
|
|
|
|
JS::Rooted<JSObject*> global(aCx, FindAssociatedGlobal(aCx, aObject->GetParentObject()));
|
|
if (!global) {
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(JS_IsGlobalObject(global));
|
|
JS::AssertObjectIsNotGray(global);
|
|
|
|
// That might have ended up wrapping us already, due to the wonders
|
|
// of XBL. Check for that, and bail out as needed.
|
|
aReflector.set(aCache->GetWrapper());
|
|
if (aReflector) {
|
|
#ifdef DEBUG
|
|
AssertReflectorHasGivenProto(aCx, aReflector, aGivenProto);
|
|
#endif // DEBUG
|
|
return true;
|
|
}
|
|
|
|
JSAutoRealm ar(aCx, global);
|
|
$*{declareProto}
|
|
|
|
$*{createObject}
|
|
|
|
aCache->SetWrapper(aReflector);
|
|
$*{unforgeable}
|
|
$*{slots}
|
|
creator.InitializationSucceeded();
|
|
|
|
MOZ_ASSERT(aCache->GetWrapperPreserveColor() &&
|
|
aCache->GetWrapperPreserveColor() == aReflector);
|
|
// If proto != canonicalProto, we have to preserve our wrapper;
|
|
// otherwise we won't be able to properly recreate it later, since
|
|
// we won't know what proto to use. Note that we don't check
|
|
// aGivenProto here, since it's entirely possible (and even
|
|
// somewhat common) to have a non-null aGivenProto which is the
|
|
// same as canonicalProto.
|
|
if (proto != canonicalProto) {
|
|
PreserveWrapper(aObject);
|
|
}
|
|
|
|
return true;
|
|
""",
|
|
nativeType=self.descriptor.nativeType,
|
|
assertInheritance=AssertInheritanceChain(self.descriptor),
|
|
declareProto=DeclareProto(self.descriptor),
|
|
createObject=CreateBindingJSObject(self.descriptor, self.properties),
|
|
unforgeable=CopyUnforgeablePropertiesToInstance(
|
|
self.descriptor, failureCode
|
|
),
|
|
slots=InitMemberSlots(self.descriptor, failureCode),
|
|
finalize=finalize,
|
|
)
|
|
|
|
|
|
class CGWrapMethod(CGAbstractMethod):
|
|
def __init__(self, descriptor):
|
|
# XXX can we wrap if we don't have an interface prototype object?
|
|
assert descriptor.interface.hasInterfacePrototypeObject()
|
|
args = [
|
|
Argument("JSContext*", "aCx"),
|
|
Argument("T*", "aObject"),
|
|
Argument("JS::Handle<JSObject*>", "aGivenProto"),
|
|
]
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"Wrap",
|
|
"JSObject*",
|
|
args,
|
|
inline=True,
|
|
templateArgs=["class T"],
|
|
)
|
|
|
|
def definition_body(self):
|
|
return dedent(
|
|
"""
|
|
JS::Rooted<JSObject*> reflector(aCx);
|
|
return Wrap(aCx, aObject, aObject, aGivenProto, &reflector) ? reflector.get() : nullptr;
|
|
"""
|
|
)
|
|
|
|
|
|
class CGWrapNonWrapperCacheMethod(CGAbstractMethod):
|
|
"""
|
|
Create a wrapper JSObject for a given native that does not implement
|
|
nsWrapperCache.
|
|
|
|
properties should be a PropertyArrays instance.
|
|
"""
|
|
|
|
def __init__(self, descriptor, properties):
|
|
# XXX can we wrap if we don't have an interface prototype object?
|
|
assert descriptor.interface.hasInterfacePrototypeObject()
|
|
args = [
|
|
Argument("JSContext*", "aCx"),
|
|
Argument(descriptor.nativeType + "*", "aObject"),
|
|
Argument("JS::Handle<JSObject*>", "aGivenProto"),
|
|
Argument("JS::MutableHandle<JSObject*>", "aReflector"),
|
|
]
|
|
CGAbstractMethod.__init__(self, descriptor, "Wrap", "bool", args)
|
|
self.properties = properties
|
|
|
|
def definition_body(self):
|
|
failureCode = "return false;\n"
|
|
|
|
return fill(
|
|
"""
|
|
$*{assertions}
|
|
MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
|
|
|
|
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
|
|
$*{declareProto}
|
|
|
|
$*{createObject}
|
|
|
|
$*{unforgeable}
|
|
|
|
$*{slots}
|
|
|
|
creator.InitializationSucceeded();
|
|
return true;
|
|
""",
|
|
assertions=AssertInheritanceChain(self.descriptor),
|
|
declareProto=DeclareProto(self.descriptor),
|
|
createObject=CreateBindingJSObject(self.descriptor, self.properties),
|
|
unforgeable=CopyUnforgeablePropertiesToInstance(
|
|
self.descriptor, failureCode
|
|
),
|
|
slots=InitMemberSlots(self.descriptor, failureCode),
|
|
)
|
|
|
|
|
|
class CGWrapGlobalMethod(CGAbstractMethod):
|
|
"""
|
|
Create a wrapper JSObject for a global. The global must implement
|
|
nsWrapperCache.
|
|
|
|
properties should be a PropertyArrays instance.
|
|
"""
|
|
|
|
def __init__(self, descriptor, properties):
|
|
assert descriptor.interface.hasInterfacePrototypeObject()
|
|
args = [
|
|
Argument("JSContext*", "aCx"),
|
|
Argument(descriptor.nativeType + "*", "aObject"),
|
|
Argument("nsWrapperCache*", "aCache"),
|
|
Argument("JS::RealmOptions&", "aOptions"),
|
|
Argument("JSPrincipals*", "aPrincipal"),
|
|
Argument("bool", "aInitStandardClasses"),
|
|
Argument("JS::MutableHandle<JSObject*>", "aReflector"),
|
|
]
|
|
CGAbstractMethod.__init__(self, descriptor, "Wrap", "bool", args)
|
|
self.descriptor = descriptor
|
|
self.properties = properties
|
|
|
|
def definition_body(self):
|
|
if self.properties.hasNonChromeOnly():
|
|
properties = "sNativeProperties.Upcast()"
|
|
else:
|
|
properties = "nullptr"
|
|
if self.properties.hasChromeOnly():
|
|
chromeProperties = "nsContentUtils::ThreadsafeIsSystemCaller(aCx) ? sChromeOnlyNativeProperties.Upcast() : nullptr"
|
|
else:
|
|
chromeProperties = "nullptr"
|
|
|
|
failureCode = dedent(
|
|
"""
|
|
aCache->ReleaseWrapper(aObject);
|
|
aCache->ClearWrapper();
|
|
return false;
|
|
"""
|
|
)
|
|
|
|
if self.descriptor.hasUnforgeableMembers:
|
|
unforgeable = InitUnforgeablePropertiesOnHolder(
|
|
self.descriptor, self.properties, failureCode, "aReflector"
|
|
).define()
|
|
else:
|
|
unforgeable = ""
|
|
|
|
return fill(
|
|
"""
|
|
$*{assertions}
|
|
MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
|
|
"nsISupports must be on our primary inheritance chain");
|
|
|
|
if (!CreateGlobal<${nativeType}, GetProtoObjectHandle>(aCx,
|
|
aObject,
|
|
aCache,
|
|
sClass.ToJSClass(),
|
|
aOptions,
|
|
aPrincipal,
|
|
aInitStandardClasses,
|
|
aReflector)) {
|
|
$*{failureCode}
|
|
}
|
|
|
|
// aReflector is a new global, so has a new realm. Enter it
|
|
// before doing anything with it.
|
|
JSAutoRealm ar(aCx, aReflector);
|
|
|
|
if (!DefineProperties(aCx, aReflector, ${properties}, ${chromeProperties})) {
|
|
$*{failureCode}
|
|
}
|
|
$*{unforgeable}
|
|
|
|
$*{slots}
|
|
|
|
return true;
|
|
""",
|
|
assertions=AssertInheritanceChain(self.descriptor),
|
|
nativeType=self.descriptor.nativeType,
|
|
properties=properties,
|
|
chromeProperties=chromeProperties,
|
|
failureCode=failureCode,
|
|
unforgeable=unforgeable,
|
|
slots=InitMemberSlots(self.descriptor, failureCode),
|
|
)
|
|
|
|
|
|
class CGUpdateMemberSlotsMethod(CGAbstractStaticMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "aCx"),
|
|
Argument("JS::Handle<JSObject*>", "aWrapper"),
|
|
Argument(descriptor.nativeType + "*", "aObject"),
|
|
]
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, "UpdateMemberSlots", "bool", args
|
|
)
|
|
|
|
def definition_body(self):
|
|
body = "JS::Rooted<JS::Value> temp(aCx);\n" "JSJitGetterCallArgs args(&temp);\n"
|
|
for m in self.descriptor.interface.members:
|
|
if m.isAttr() and m.getExtendedAttribute("StoreInSlot"):
|
|
# Skip doing this for the "window" and "self" attributes on the
|
|
# Window interface, because those can't be gotten safely until
|
|
# we have hooked it up correctly to the outer window. The
|
|
# window code handles doing the get itself.
|
|
if self.descriptor.interface.identifier.name == "Window" and (
|
|
m.identifier.name == "window" or m.identifier.name == "self"
|
|
):
|
|
continue
|
|
body += fill(
|
|
"""
|
|
|
|
static_assert(${slot} < JS::shadow::Object::MAX_FIXED_SLOTS,
|
|
"Not enough fixed slots to fit '${interface}.${member}. Ion's visitGetDOMMemberV/visitGetDOMMemberT assume StoreInSlot things are all in fixed slots.");
|
|
if (!get_${member}(aCx, aWrapper, aObject, args)) {
|
|
return false;
|
|
}
|
|
// Getter handled setting our reserved slots
|
|
""",
|
|
slot=memberReservedSlot(m, self.descriptor),
|
|
interface=self.descriptor.interface.identifier.name,
|
|
member=m.identifier.name,
|
|
)
|
|
|
|
body += "\nreturn true;\n"
|
|
return body
|
|
|
|
|
|
class CGClearCachedValueMethod(CGAbstractMethod):
|
|
def __init__(self, descriptor, member):
|
|
self.member = member
|
|
# If we're StoreInSlot, we'll need to call the getter
|
|
if member.getExtendedAttribute("StoreInSlot"):
|
|
args = [Argument("JSContext*", "aCx")]
|
|
returnType = "bool"
|
|
else:
|
|
args = []
|
|
returnType = "void"
|
|
args.append(Argument(descriptor.nativeType + "*", "aObject"))
|
|
name = MakeClearCachedValueNativeName(member)
|
|
CGAbstractMethod.__init__(self, descriptor, name, returnType, args)
|
|
|
|
def definition_body(self):
|
|
slotIndex = memberReservedSlot(self.member, self.descriptor)
|
|
if self.member.getExtendedAttribute("StoreInSlot"):
|
|
# We have to root things and save the old value in case
|
|
# regetting fails, so we can restore it.
|
|
declObj = "JS::Rooted<JSObject*> obj(aCx);\n"
|
|
noopRetval = " true"
|
|
saveMember = (
|
|
"JS::Rooted<JS::Value> oldValue(aCx, JS::GetReservedSlot(obj, %s));\n"
|
|
% slotIndex
|
|
)
|
|
regetMember = fill(
|
|
"""
|
|
JS::Rooted<JS::Value> temp(aCx);
|
|
JSJitGetterCallArgs args(&temp);
|
|
JSAutoRealm ar(aCx, obj);
|
|
if (!get_${name}(aCx, obj, aObject, args)) {
|
|
JS::SetReservedSlot(obj, ${slotIndex}, oldValue);
|
|
return false;
|
|
}
|
|
return true;
|
|
""",
|
|
name=self.member.identifier.name,
|
|
slotIndex=slotIndex,
|
|
)
|
|
else:
|
|
declObj = "JSObject* obj;\n"
|
|
noopRetval = ""
|
|
saveMember = ""
|
|
regetMember = ""
|
|
|
|
if self.descriptor.wantsXrays:
|
|
clearXrayExpandoSlots = fill(
|
|
"""
|
|
xpc::ClearXrayExpandoSlots(obj, ${xraySlotIndex});
|
|
""",
|
|
xraySlotIndex=memberXrayExpandoReservedSlot(
|
|
self.member, self.descriptor
|
|
),
|
|
)
|
|
else:
|
|
clearXrayExpandoSlots = ""
|
|
|
|
return fill(
|
|
"""
|
|
$*{declObj}
|
|
obj = aObject->GetWrapper();
|
|
if (!obj) {
|
|
return${noopRetval};
|
|
}
|
|
$*{saveMember}
|
|
JS::SetReservedSlot(obj, ${slotIndex}, JS::UndefinedValue());
|
|
$*{clearXrayExpandoSlots}
|
|
$*{regetMember}
|
|
""",
|
|
declObj=declObj,
|
|
noopRetval=noopRetval,
|
|
saveMember=saveMember,
|
|
slotIndex=slotIndex,
|
|
clearXrayExpandoSlots=clearXrayExpandoSlots,
|
|
regetMember=regetMember,
|
|
)
|
|
|
|
|
|
class CGCrossOriginProperties(CGThing):
|
|
def __init__(self, descriptor):
|
|
attrs = []
|
|
chromeOnlyAttrs = []
|
|
methods = []
|
|
chromeOnlyMethods = []
|
|
for m in descriptor.interface.members:
|
|
if m.isAttr() and (
|
|
m.getExtendedAttribute("CrossOriginReadable")
|
|
or m.getExtendedAttribute("CrossOriginWritable")
|
|
):
|
|
if m.isStatic():
|
|
raise TypeError(
|
|
"Don't know how to deal with static method %s"
|
|
% m.identifier.name
|
|
)
|
|
if PropertyDefiner.getControllingCondition(
|
|
m, descriptor
|
|
).hasDisablers():
|
|
raise TypeError(
|
|
"Don't know how to deal with disabler for %s"
|
|
% m.identifier.name
|
|
)
|
|
if len(m.bindingAliases) > 0:
|
|
raise TypeError(
|
|
"Don't know how to deal with aliases for %s" % m.identifier.name
|
|
)
|
|
if m.getExtendedAttribute("ChromeOnly") is not None:
|
|
chromeOnlyAttrs.extend(AttrDefiner.attrData(m, overrideFlags="0"))
|
|
else:
|
|
attrs.extend(AttrDefiner.attrData(m, overrideFlags="0"))
|
|
elif m.isMethod() and m.getExtendedAttribute("CrossOriginCallable"):
|
|
if m.isStatic():
|
|
raise TypeError(
|
|
"Don't know how to deal with static method %s"
|
|
% m.identifier.name
|
|
)
|
|
if PropertyDefiner.getControllingCondition(
|
|
m, descriptor
|
|
).hasDisablers():
|
|
raise TypeError(
|
|
"Don't know how to deal with disabler for %s"
|
|
% m.identifier.name
|
|
)
|
|
if len(m.aliases) > 0:
|
|
raise TypeError(
|
|
"Don't know how to deal with aliases for %s" % m.identifier.name
|
|
)
|
|
if m.getExtendedAttribute("ChromeOnly") is not None:
|
|
chromeOnlyMethods.append(
|
|
MethodDefiner.methodData(
|
|
m, descriptor, overrideFlags="JSPROP_READONLY"
|
|
)
|
|
)
|
|
else:
|
|
methods.append(
|
|
MethodDefiner.methodData(
|
|
m, descriptor, overrideFlags="JSPROP_READONLY"
|
|
)
|
|
)
|
|
|
|
if len(attrs) > 0:
|
|
self.attributeSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
|
|
attrs,
|
|
descriptor,
|
|
AttrDefiner.formatSpec,
|
|
" JS_PS_END\n",
|
|
AttrDefiner.condition,
|
|
functools.partial(AttrDefiner.specData, crossOriginOnly=True),
|
|
)
|
|
else:
|
|
self.attributeSpecs = [" JS_PS_END\n"]
|
|
if len(methods) > 0:
|
|
self.methodSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
|
|
methods,
|
|
descriptor,
|
|
MethodDefiner.formatSpec,
|
|
" JS_FS_END\n",
|
|
MethodDefiner.condition,
|
|
MethodDefiner.specData,
|
|
)
|
|
else:
|
|
self.methodSpecs = [" JS_FS_END\n"]
|
|
|
|
if len(chromeOnlyAttrs) > 0:
|
|
(
|
|
self.chromeOnlyAttributeSpecs,
|
|
_,
|
|
) = PropertyDefiner.generatePrefableArrayValues(
|
|
chromeOnlyAttrs,
|
|
descriptor,
|
|
AttrDefiner.formatSpec,
|
|
" JS_PS_END\n",
|
|
AttrDefiner.condition,
|
|
functools.partial(AttrDefiner.specData, crossOriginOnly=True),
|
|
)
|
|
else:
|
|
self.chromeOnlyAttributeSpecs = []
|
|
if len(chromeOnlyMethods) > 0:
|
|
self.chromeOnlyMethodSpecs, _ = PropertyDefiner.generatePrefableArrayValues(
|
|
chromeOnlyMethods,
|
|
descriptor,
|
|
MethodDefiner.formatSpec,
|
|
" JS_FS_END\n",
|
|
MethodDefiner.condition,
|
|
MethodDefiner.specData,
|
|
)
|
|
else:
|
|
self.chromeOnlyMethodSpecs = []
|
|
|
|
def declare(self):
|
|
return dedent(
|
|
"""
|
|
extern const CrossOriginProperties sCrossOriginProperties;
|
|
"""
|
|
)
|
|
|
|
def define(self):
|
|
def defineChromeOnly(name, specs, specType):
|
|
if len(specs) == 0:
|
|
return ("", "nullptr")
|
|
name = "sChromeOnlyCrossOrigin" + name
|
|
define = fill(
|
|
"""
|
|
static const ${specType} ${name}[] = {
|
|
$*{specs}
|
|
};
|
|
""",
|
|
specType=specType,
|
|
name=name,
|
|
specs=",\n".join(specs),
|
|
)
|
|
return (define, name)
|
|
|
|
chromeOnlyAttributes = defineChromeOnly(
|
|
"Attributes", self.chromeOnlyAttributeSpecs, "JSPropertySpec"
|
|
)
|
|
chromeOnlyMethods = defineChromeOnly(
|
|
"Methods", self.chromeOnlyMethodSpecs, "JSFunctionSpec"
|
|
)
|
|
return fill(
|
|
"""
|
|
// We deliberately use brace-elision to make Visual Studio produce better initalization code.
|
|
static const JSPropertySpec sCrossOriginAttributes[] = {
|
|
$*{attributeSpecs}
|
|
};
|
|
static const JSFunctionSpec sCrossOriginMethods[] = {
|
|
$*{methodSpecs}
|
|
};
|
|
$*{chromeOnlyAttributeSpecs}
|
|
$*{chromeOnlyMethodSpecs}
|
|
const CrossOriginProperties sCrossOriginProperties = {
|
|
sCrossOriginAttributes,
|
|
sCrossOriginMethods,
|
|
${chromeOnlyAttributes},
|
|
${chromeOnlyMethods}
|
|
};
|
|
""",
|
|
attributeSpecs=",\n".join(self.attributeSpecs),
|
|
methodSpecs=",\n".join(self.methodSpecs),
|
|
chromeOnlyAttributeSpecs=chromeOnlyAttributes[0],
|
|
chromeOnlyMethodSpecs=chromeOnlyMethods[0],
|
|
chromeOnlyAttributes=chromeOnlyAttributes[1],
|
|
chromeOnlyMethods=chromeOnlyMethods[1],
|
|
)
|
|
|
|
|
|
class CGCycleCollectionTraverseForOwningUnionMethod(CGAbstractMethod):
|
|
"""
|
|
ImplCycleCollectionUnlink for owning union type.
|
|
"""
|
|
|
|
def __init__(self, type):
|
|
self.type = type
|
|
args = [
|
|
Argument("nsCycleCollectionTraversalCallback&", "aCallback"),
|
|
Argument("%s&" % CGUnionStruct.unionTypeName(type, True), "aUnion"),
|
|
Argument("const char*", "aName"),
|
|
Argument("uint32_t", "aFlags", "0"),
|
|
]
|
|
CGAbstractMethod.__init__(
|
|
self, None, "ImplCycleCollectionTraverse", "void", args
|
|
)
|
|
|
|
def deps(self):
|
|
return self.type.getDeps()
|
|
|
|
def definition_body(self):
|
|
memberNames = [
|
|
getUnionMemberName(t)
|
|
for t in self.type.flatMemberTypes
|
|
if idlTypeNeedsCycleCollection(t)
|
|
]
|
|
assert memberNames
|
|
|
|
conditionTemplate = "aUnion.Is%s()"
|
|
functionCallTemplate = (
|
|
'ImplCycleCollectionTraverse(aCallback, aUnion.GetAs%s(), "m%s", aFlags);\n'
|
|
)
|
|
|
|
ifStaments = (
|
|
CGIfWrapper(CGGeneric(functionCallTemplate % (m, m)), conditionTemplate % m)
|
|
for m in memberNames
|
|
)
|
|
|
|
return CGElseChain(ifStaments).define()
|
|
|
|
|
|
class CGCycleCollectionUnlinkForOwningUnionMethod(CGAbstractMethod):
|
|
"""
|
|
ImplCycleCollectionUnlink for owning union type.
|
|
"""
|
|
|
|
def __init__(self, type):
|
|
self.type = type
|
|
args = [Argument("%s&" % CGUnionStruct.unionTypeName(type, True), "aUnion")]
|
|
CGAbstractMethod.__init__(self, None, "ImplCycleCollectionUnlink", "void", args)
|
|
|
|
def deps(self):
|
|
return self.type.getDeps()
|
|
|
|
def definition_body(self):
|
|
return "aUnion.Uninit();\n"
|
|
|
|
|
|
builtinNames = {
|
|
IDLType.Tags.bool: "bool",
|
|
IDLType.Tags.int8: "int8_t",
|
|
IDLType.Tags.int16: "int16_t",
|
|
IDLType.Tags.int32: "int32_t",
|
|
IDLType.Tags.int64: "int64_t",
|
|
IDLType.Tags.uint8: "uint8_t",
|
|
IDLType.Tags.uint16: "uint16_t",
|
|
IDLType.Tags.uint32: "uint32_t",
|
|
IDLType.Tags.uint64: "uint64_t",
|
|
IDLType.Tags.unrestricted_float: "float",
|
|
IDLType.Tags.float: "float",
|
|
IDLType.Tags.unrestricted_double: "double",
|
|
IDLType.Tags.double: "double",
|
|
}
|
|
|
|
numericSuffixes = {
|
|
IDLType.Tags.int8: "",
|
|
IDLType.Tags.uint8: "",
|
|
IDLType.Tags.int16: "",
|
|
IDLType.Tags.uint16: "",
|
|
IDLType.Tags.int32: "",
|
|
IDLType.Tags.uint32: "U",
|
|
IDLType.Tags.int64: "LL",
|
|
IDLType.Tags.uint64: "ULL",
|
|
IDLType.Tags.unrestricted_float: "F",
|
|
IDLType.Tags.float: "F",
|
|
IDLType.Tags.unrestricted_double: "",
|
|
IDLType.Tags.double: "",
|
|
}
|
|
|
|
|
|
def numericValue(t, v):
|
|
if t == IDLType.Tags.unrestricted_double or t == IDLType.Tags.unrestricted_float:
|
|
typeName = builtinNames[t]
|
|
if v == float("inf"):
|
|
return "mozilla::PositiveInfinity<%s>()" % typeName
|
|
if v == float("-inf"):
|
|
return "mozilla::NegativeInfinity<%s>()" % typeName
|
|
if math.isnan(v):
|
|
return "mozilla::UnspecifiedNaN<%s>()" % typeName
|
|
return "%s%s" % (v, numericSuffixes[t])
|
|
|
|
|
|
class CastableObjectUnwrapper:
|
|
"""
|
|
A class for unwrapping an object stored in a JS Value (or
|
|
MutableHandle<Value> or Handle<Value>) named by the "source" and
|
|
"mutableSource" arguments based on the passed-in descriptor and storing it
|
|
in a variable called by the name in the "target" argument. The "source"
|
|
argument should be able to produce a Value or Handle<Value>; the
|
|
"mutableSource" argument should be able to produce a MutableHandle<Value>
|
|
|
|
codeOnFailure is the code to run if unwrapping fails.
|
|
|
|
If isCallbackReturnValue is "JSImpl" and our descriptor is also
|
|
JS-implemented, fall back to just creating the right object if what we
|
|
have isn't one already.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
source,
|
|
mutableSource,
|
|
target,
|
|
codeOnFailure,
|
|
exceptionCode=None,
|
|
isCallbackReturnValue=False,
|
|
):
|
|
self.substitution = {
|
|
"type": descriptor.nativeType,
|
|
"protoID": "prototypes::id::" + descriptor.name,
|
|
"target": target,
|
|
"codeOnFailure": codeOnFailure,
|
|
"source": source,
|
|
"mutableSource": mutableSource,
|
|
}
|
|
|
|
if isCallbackReturnValue == "JSImpl" and descriptor.interface.isJSImplemented():
|
|
exceptionCode = exceptionCode or codeOnFailure
|
|
self.substitution["codeOnFailure"] = fill(
|
|
"""
|
|
// Be careful to not wrap random DOM objects here, even if
|
|
// they're wrapped in opaque security wrappers for some reason.
|
|
// XXXbz Wish we could check for a JS-implemented object
|
|
// that already has a content reflection...
|
|
if (!IsDOMObject(js::UncheckedUnwrap(&${source}.toObject()))) {
|
|
nsCOMPtr<nsIGlobalObject> contentGlobal;
|
|
JS::Rooted<JSObject*> callback(cx, CallbackOrNull());
|
|
if (!callback ||
|
|
!GetContentGlobalForJSImplementedObject(cx, callback, getter_AddRefs(contentGlobal))) {
|
|
$*{exceptionCode}
|
|
}
|
|
JS::Rooted<JSObject*> jsImplSourceObj(cx, &${source}.toObject());
|
|
MOZ_RELEASE_ASSERT(!js::IsWrapper(jsImplSourceObj),
|
|
"Don't return JS implementations from other compartments");
|
|
JS::Rooted<JSObject*> jsImplSourceGlobal(cx, JS::GetNonCCWObjectGlobal(jsImplSourceObj));
|
|
${target} = new ${type}(jsImplSourceObj, jsImplSourceGlobal, contentGlobal);
|
|
} else {
|
|
$*{codeOnFailure}
|
|
}
|
|
""",
|
|
exceptionCode=exceptionCode,
|
|
**self.substitution
|
|
)
|
|
else:
|
|
self.substitution["codeOnFailure"] = codeOnFailure
|
|
|
|
def __str__(self):
|
|
substitution = self.substitution.copy()
|
|
substitution["codeOnFailure"] %= {
|
|
"securityError": "rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO"
|
|
}
|
|
return fill(
|
|
"""
|
|
{
|
|
// Our JSContext should be in the right global to do unwrapping in.
|
|
nsresult rv = UnwrapObject<${protoID}, ${type}>(${mutableSource}, ${target}, cx);
|
|
if (NS_FAILED(rv)) {
|
|
$*{codeOnFailure}
|
|
}
|
|
}
|
|
""",
|
|
**substitution
|
|
)
|
|
|
|
|
|
class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper):
|
|
"""
|
|
As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
source,
|
|
mutableSource,
|
|
target,
|
|
exceptionCode,
|
|
isCallbackReturnValue,
|
|
sourceDescription,
|
|
):
|
|
CastableObjectUnwrapper.__init__(
|
|
self,
|
|
descriptor,
|
|
source,
|
|
mutableSource,
|
|
target,
|
|
'cx.ThrowErrorMessage<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("%s", "%s");\n'
|
|
"%s"
|
|
% (sourceDescription, descriptor.interface.identifier.name, exceptionCode),
|
|
exceptionCode,
|
|
isCallbackReturnValue,
|
|
)
|
|
|
|
|
|
def getCallbackConversionInfo(
|
|
type, idlObject, isMember, isCallbackReturnValue, isOptional
|
|
):
|
|
"""
|
|
Returns a tuple containing the declType, declArgs, and basic
|
|
conversion for the given callback type, with the given callback
|
|
idl object in the given context (isMember/isCallbackReturnValue/isOptional).
|
|
"""
|
|
name = idlObject.identifier.name
|
|
|
|
# We can't use fast callbacks if isOptional because then we get an
|
|
# Optional<RootedCallback> thing, which is not transparent to consumers.
|
|
useFastCallback = not isMember and not isCallbackReturnValue and not isOptional
|
|
if useFastCallback:
|
|
name = "binding_detail::Fast%s" % name
|
|
rootArgs = ""
|
|
args = "&${val}.toObject(), JS::CurrentGlobalOrNull(cx)"
|
|
else:
|
|
rootArgs = dedent(
|
|
"""
|
|
JS::Rooted<JSObject*> tempRoot(cx, &${val}.toObject());
|
|
JS::Rooted<JSObject*> tempGlobalRoot(cx, JS::CurrentGlobalOrNull(cx));
|
|
"""
|
|
)
|
|
args = "cx, tempRoot, tempGlobalRoot, GetIncumbentGlobal()"
|
|
|
|
if type.nullable() or isCallbackReturnValue:
|
|
declType = CGGeneric("RefPtr<%s>" % name)
|
|
else:
|
|
declType = CGGeneric("OwningNonNull<%s>" % name)
|
|
|
|
if useFastCallback:
|
|
declType = CGTemplatedType("RootedCallback", declType)
|
|
declArgs = "cx"
|
|
else:
|
|
declArgs = None
|
|
|
|
conversion = fill(
|
|
"""
|
|
{ // scope for tempRoot and tempGlobalRoot if needed
|
|
$*{rootArgs}
|
|
$${declName} = new ${name}(${args});
|
|
}
|
|
""",
|
|
rootArgs=rootArgs,
|
|
name=name,
|
|
args=args,
|
|
)
|
|
return (declType, declArgs, conversion)
|
|
|
|
|
|
class JSToNativeConversionInfo:
|
|
"""
|
|
An object representing information about a JS-to-native conversion.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
template,
|
|
declType=None,
|
|
holderType=None,
|
|
dealWithOptional=False,
|
|
declArgs=None,
|
|
holderArgs=None,
|
|
):
|
|
"""
|
|
template: A string representing the conversion code. This will have
|
|
template substitution performed on it as follows:
|
|
|
|
${val} is a handle to the JS::Value in question
|
|
${maybeMutableVal} May be a mutable handle to the JS::Value in
|
|
question. This is only OK to use if ${val} is
|
|
known to not be undefined.
|
|
${holderName} replaced by the holder's name, if any
|
|
${declName} replaced by the declaration's name
|
|
${haveValue} replaced by an expression that evaluates to a boolean
|
|
for whether we have a JS::Value. Only used when
|
|
defaultValue is not None or when True is passed for
|
|
checkForValue to instantiateJSToNativeConversion.
|
|
This expression may not be already-parenthesized, so if
|
|
you use it with && or || make sure to put parens
|
|
around it.
|
|
${passedToJSImpl} replaced by an expression that evaluates to a boolean
|
|
for whether this value is being passed to a JS-
|
|
implemented interface.
|
|
|
|
declType: A CGThing representing the native C++ type we're converting
|
|
to. This is allowed to be None if the conversion code is
|
|
supposed to be used as-is.
|
|
|
|
holderType: A CGThing representing the type of a "holder" which will
|
|
hold a possible reference to the C++ thing whose type we
|
|
returned in declType, or None if no such holder is needed.
|
|
|
|
dealWithOptional: A boolean indicating whether the caller has to do
|
|
optional-argument handling. This should only be set
|
|
to true if the JS-to-native conversion is being done
|
|
for an optional argument or dictionary member with no
|
|
default value and if the returned template expects
|
|
both declType and holderType to be wrapped in
|
|
Optional<>, with ${declName} and ${holderName}
|
|
adjusted to point to the Value() of the Optional, and
|
|
Construct() calls to be made on the Optional<>s as
|
|
needed.
|
|
|
|
declArgs: If not None, the arguments to pass to the ${declName}
|
|
constructor. These will have template substitution performed
|
|
on them so you can use things like ${val}. This is a
|
|
single string, not a list of strings.
|
|
|
|
holderArgs: If not None, the arguments to pass to the ${holderName}
|
|
constructor. These will have template substitution
|
|
performed on them so you can use things like ${val}.
|
|
This is a single string, not a list of strings.
|
|
|
|
${declName} must be in scope before the code from 'template' is entered.
|
|
|
|
If holderType is not None then ${holderName} must be in scope before
|
|
the code from 'template' is entered.
|
|
"""
|
|
assert isinstance(template, str)
|
|
assert declType is None or isinstance(declType, CGThing)
|
|
assert holderType is None or isinstance(holderType, CGThing)
|
|
self.template = template
|
|
self.declType = declType
|
|
self.holderType = holderType
|
|
self.dealWithOptional = dealWithOptional
|
|
self.declArgs = declArgs
|
|
self.holderArgs = holderArgs
|
|
|
|
|
|
def getHandleDefault(defaultValue):
|
|
tag = defaultValue.type.tag()
|
|
if tag in numericSuffixes:
|
|
# Some numeric literals require a suffix to compile without warnings
|
|
return numericValue(tag, defaultValue.value)
|
|
assert tag == IDLType.Tags.bool
|
|
return toStringBool(defaultValue.value)
|
|
|
|
|
|
def handleDefaultStringValue(defaultValue, method):
|
|
"""
|
|
Returns a string which ends up calling 'method' with a (char_t*, length)
|
|
pair that sets this string default value. This string is suitable for
|
|
passing as the second argument of handleDefault.
|
|
"""
|
|
assert (
|
|
defaultValue.type.isDOMString()
|
|
or defaultValue.type.isUSVString()
|
|
or defaultValue.type.isUTF8String()
|
|
or defaultValue.type.isByteString()
|
|
)
|
|
# There shouldn't be any non-ASCII or embedded nulls in here; if
|
|
# it ever sneaks in we will need to think about how to properly
|
|
# represent that in the C++.
|
|
assert all(ord(c) < 128 and ord(c) > 0 for c in defaultValue.value)
|
|
if defaultValue.type.isByteString() or defaultValue.type.isUTF8String():
|
|
prefix = ""
|
|
else:
|
|
prefix = "u"
|
|
return fill(
|
|
"""
|
|
${method}(${prefix}"${value}");
|
|
""",
|
|
method=method,
|
|
prefix=prefix,
|
|
value=defaultValue.value,
|
|
)
|
|
|
|
|
|
def recordKeyType(recordType):
|
|
assert recordType.keyType.isString()
|
|
if recordType.keyType.isByteString() or recordType.keyType.isUTF8String():
|
|
return "nsCString"
|
|
return "nsString"
|
|
|
|
|
|
def recordKeyDeclType(recordType):
|
|
return CGGeneric(recordKeyType(recordType))
|
|
|
|
|
|
def initializerForType(type):
|
|
"""
|
|
Get the right initializer for the given type for a data location where we
|
|
plan to then initialize it from a JS::Value. Some types need to always be
|
|
initialized even before we start the JS::Value-to-IDL-value conversion.
|
|
|
|
Returns a string or None if no initialization is needed.
|
|
"""
|
|
if type.isObject():
|
|
return "nullptr"
|
|
# We could probably return CGDictionary.getNonInitializingCtorArg() for the
|
|
# dictionary case, but code outside DictionaryBase subclasses can't use
|
|
# that, so we can't do it across the board.
|
|
return None
|
|
|
|
|
|
# If this function is modified, modify CGNativeMember.getArg and
|
|
# CGNativeMember.getRetvalInfo accordingly. The latter cares about the decltype
|
|
# and holdertype we end up using, because it needs to be able to return the code
|
|
# that will convert those to the actual return value of the callback function.
|
|
def getJSToNativeConversionInfo(
|
|
type,
|
|
descriptorProvider,
|
|
failureCode=None,
|
|
isDefinitelyObject=False,
|
|
isMember=False,
|
|
isOptional=False,
|
|
invalidEnumValueFatal=True,
|
|
defaultValue=None,
|
|
isNullOrUndefined=False,
|
|
isKnownMissing=False,
|
|
exceptionCode=None,
|
|
lenientFloatCode=None,
|
|
allowTreatNonCallableAsNull=False,
|
|
isCallbackReturnValue=False,
|
|
sourceDescription="value",
|
|
nestingLevel="",
|
|
):
|
|
"""
|
|
Get a template for converting a JS value to a native object based on the
|
|
given type and descriptor. If failureCode is given, then we're actually
|
|
testing whether we can convert the argument to the desired type. That
|
|
means that failures to convert due to the JS value being the wrong type of
|
|
value need to use failureCode instead of throwing exceptions. Failures to
|
|
convert that are due to JS exceptions (from toString or valueOf methods) or
|
|
out of memory conditions need to throw exceptions no matter what
|
|
failureCode is. However what actually happens when throwing an exception
|
|
can be controlled by exceptionCode. The only requirement on that is that
|
|
exceptionCode must end up doing a return, and every return from this
|
|
function must happen via exceptionCode if exceptionCode is not None.
|
|
|
|
If isDefinitelyObject is True, that means we have a value and the value
|
|
tests true for isObject(), so we have no need to recheck that.
|
|
|
|
If isNullOrUndefined is True, that means we have a value and the value
|
|
tests true for isNullOrUndefined(), so we have no need to recheck that.
|
|
|
|
If isKnownMissing is True, that means that we are known-missing, and for
|
|
cases when we have a default value we only need to output the default value.
|
|
|
|
if isMember is not False, we're being converted from a property of some JS
|
|
object, not from an actual method argument, so we can't rely on our jsval
|
|
being rooted or outliving us in any way. Callers can pass "Dictionary",
|
|
"Variadic", "Sequence", or "OwningUnion" to indicate that the conversion is
|
|
for something that is a dictionary member, a variadic argument, a sequence,
|
|
or an owning union respectively.
|
|
|
|
If isOptional is true, then we are doing conversion of an optional
|
|
argument with no default value.
|
|
|
|
invalidEnumValueFatal controls whether an invalid enum value conversion
|
|
attempt will throw (if true) or simply return without doing anything (if
|
|
false).
|
|
|
|
If defaultValue is not None, it's the IDL default value for this conversion
|
|
|
|
If isEnforceRange is true, we're converting an integer and throwing if the
|
|
value is out of range.
|
|
|
|
If isClamp is true, we're converting an integer and clamping if the
|
|
value is out of range.
|
|
|
|
If isAllowShared is false, we're converting a buffer source and throwing if
|
|
it is a SharedArrayBuffer or backed by a SharedArrayBuffer.
|
|
|
|
If lenientFloatCode is not None, it should be used in cases when
|
|
we're a non-finite float that's not unrestricted.
|
|
|
|
If allowTreatNonCallableAsNull is true, then [TreatNonCallableAsNull] and
|
|
[TreatNonObjectAsNull] extended attributes on nullable callback functions
|
|
will be honored.
|
|
|
|
If isCallbackReturnValue is "JSImpl" or "Callback", then the declType may be
|
|
adjusted to make it easier to return from a callback. Since that type is
|
|
never directly observable by any consumers of the callback code, this is OK.
|
|
Furthermore, if isCallbackReturnValue is "JSImpl", that affects the behavior
|
|
of the FailureFatalCastableObjectUnwrapper conversion; this is used for
|
|
implementing auto-wrapping of JS-implemented return values from a
|
|
JS-implemented interface.
|
|
|
|
sourceDescription is a description of what this JS value represents, to be
|
|
used in error reporting. Callers should assume that it might get placed in
|
|
the middle of a sentence. If it ends up at the beginning of a sentence, its
|
|
first character will be automatically uppercased.
|
|
|
|
The return value from this function is a JSToNativeConversionInfo.
|
|
"""
|
|
# If we have a defaultValue then we're not actually optional for
|
|
# purposes of what we need to be declared as.
|
|
assert defaultValue is None or not isOptional
|
|
|
|
# Also, we should not have a defaultValue if we know we're an object
|
|
assert not isDefinitelyObject or defaultValue is None
|
|
|
|
# And we can't both be an object and be null or undefined
|
|
assert not isDefinitelyObject or not isNullOrUndefined
|
|
|
|
isClamp = type.hasClamp()
|
|
isEnforceRange = type.hasEnforceRange()
|
|
isAllowShared = type.hasAllowShared()
|
|
|
|
# If exceptionCode is not set, we'll just rethrow the exception we got.
|
|
# Note that we can't just set failureCode to exceptionCode, because setting
|
|
# failureCode will prevent pending exceptions from being set in cases when
|
|
# they really should be!
|
|
if exceptionCode is None:
|
|
exceptionCode = "return false;\n"
|
|
|
|
# Unfortunately, .capitalize() on a string will lowercase things inside the
|
|
# string, which we do not want.
|
|
def firstCap(string):
|
|
return string[0].upper() + string[1:]
|
|
|
|
# Helper functions for dealing with failures due to the JS value being the
|
|
# wrong type of value
|
|
def onFailureNotAnObject(failureCode):
|
|
return CGGeneric(
|
|
failureCode
|
|
or (
|
|
'cx.ThrowErrorMessage<MSG_NOT_OBJECT>("%s");\n'
|
|
"%s" % (firstCap(sourceDescription), exceptionCode)
|
|
)
|
|
)
|
|
|
|
def onFailureBadType(failureCode, typeName):
|
|
return CGGeneric(
|
|
failureCode
|
|
or (
|
|
'cx.ThrowErrorMessage<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("%s", "%s");\n'
|
|
"%s" % (firstCap(sourceDescription), typeName, exceptionCode)
|
|
)
|
|
)
|
|
|
|
# It's a failure in the committed-to conversion, not a failure to match up
|
|
# to a type, so we don't want to use failureCode in here. We want to just
|
|
# throw an exception unconditionally.
|
|
def onFailureIsShared():
|
|
return CGGeneric(
|
|
'cx.ThrowErrorMessage<MSG_TYPEDARRAY_IS_SHARED>("%s");\n'
|
|
"%s" % (firstCap(sourceDescription), exceptionCode)
|
|
)
|
|
|
|
def onFailureNotCallable(failureCode):
|
|
return CGGeneric(
|
|
failureCode
|
|
or (
|
|
'cx.ThrowErrorMessage<MSG_NOT_CALLABLE>("%s");\n'
|
|
"%s" % (firstCap(sourceDescription), exceptionCode)
|
|
)
|
|
)
|
|
|
|
# A helper function for handling default values. Takes a template
|
|
# body and the C++ code to set the default value and wraps the
|
|
# given template body in handling for the default value.
|
|
def handleDefault(template, setDefault):
|
|
if defaultValue is None:
|
|
return template
|
|
if isKnownMissing:
|
|
return fill(
|
|
"""
|
|
{
|
|
// scope for any temporaries our default value setting needs.
|
|
$*{setDefault}
|
|
}
|
|
""",
|
|
setDefault=setDefault,
|
|
)
|
|
return fill(
|
|
"""
|
|
if ($${haveValue}) {
|
|
$*{templateBody}
|
|
} else {
|
|
$*{setDefault}
|
|
}
|
|
""",
|
|
templateBody=template,
|
|
setDefault=setDefault,
|
|
)
|
|
|
|
# A helper function for wrapping up the template body for
|
|
# possibly-nullable objecty stuff
|
|
def wrapObjectTemplate(templateBody, type, codeToSetNull, failureCode=None):
|
|
if isNullOrUndefined and type.nullable():
|
|
# Just ignore templateBody and set ourselves to null.
|
|
# Note that we don't have to worry about default values
|
|
# here either, since we already examined this value.
|
|
return codeToSetNull
|
|
|
|
if not isDefinitelyObject:
|
|
# Handle the non-object cases by wrapping up the whole
|
|
# thing in an if cascade.
|
|
if type.nullable():
|
|
elifLine = "} else if (${val}.isNullOrUndefined()) {\n"
|
|
elifBody = codeToSetNull
|
|
else:
|
|
elifLine = ""
|
|
elifBody = ""
|
|
|
|
# Note that $${val} below expands to ${val}. This string is
|
|
# used as a template later, and val will be filled in then.
|
|
templateBody = fill(
|
|
"""
|
|
if ($${val}.isObject()) {
|
|
$*{templateBody}
|
|
$*{elifLine}
|
|
$*{elifBody}
|
|
} else {
|
|
$*{failureBody}
|
|
}
|
|
""",
|
|
templateBody=templateBody,
|
|
elifLine=elifLine,
|
|
elifBody=elifBody,
|
|
failureBody=onFailureNotAnObject(failureCode).define(),
|
|
)
|
|
|
|
if isinstance(defaultValue, IDLNullValue):
|
|
assert type.nullable() # Parser should enforce this
|
|
templateBody = handleDefault(templateBody, codeToSetNull)
|
|
elif isinstance(defaultValue, IDLEmptySequenceValue):
|
|
# Our caller will handle it
|
|
pass
|
|
else:
|
|
assert defaultValue is None
|
|
|
|
return templateBody
|
|
|
|
# A helper function for converting things that look like a JSObject*.
|
|
def handleJSObjectType(
|
|
type, isMember, failureCode, exceptionCode, sourceDescription
|
|
):
|
|
if not isMember:
|
|
if isOptional:
|
|
# We have a specialization of Optional that will use a
|
|
# Rooted for the storage here.
|
|
declType = CGGeneric("JS::Handle<JSObject*>")
|
|
else:
|
|
declType = CGGeneric("JS::Rooted<JSObject*>")
|
|
declArgs = "cx"
|
|
else:
|
|
assert isMember in (
|
|
"Sequence",
|
|
"Variadic",
|
|
"Dictionary",
|
|
"OwningUnion",
|
|
"Record",
|
|
)
|
|
# We'll get traced by the sequence or dictionary or union tracer
|
|
declType = CGGeneric("JSObject*")
|
|
declArgs = None
|
|
templateBody = "${declName} = &${val}.toObject();\n"
|
|
|
|
# For JS-implemented APIs, we refuse to allow passing objects that the
|
|
# API consumer does not subsume. The extra parens around
|
|
# ($${passedToJSImpl}) suppress unreachable code warnings when
|
|
# $${passedToJSImpl} is the literal `false`. But Apple is shipping a
|
|
# buggy clang (clang 3.9) in Xcode 8.3, so there even the parens are not
|
|
# enough. So we manually disable some warnings in clang.
|
|
if (
|
|
not isinstance(descriptorProvider, Descriptor)
|
|
or descriptorProvider.interface.isJSImplemented()
|
|
):
|
|
templateBody = (
|
|
fill(
|
|
"""
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wunreachable-code"
|
|
#pragma clang diagnostic ignored "-Wunreachable-code-return"
|
|
#endif // __clang__
|
|
if (($${passedToJSImpl}) && !CallerSubsumes($${val})) {
|
|
cx.ThrowErrorMessage<MSG_PERMISSION_DENIED_TO_PASS_ARG>("${sourceDescription}");
|
|
$*{exceptionCode}
|
|
}
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif // __clang__
|
|
""",
|
|
sourceDescription=sourceDescription,
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
+ templateBody
|
|
)
|
|
|
|
setToNullCode = "${declName} = nullptr;\n"
|
|
template = wrapObjectTemplate(templateBody, type, setToNullCode, failureCode)
|
|
return JSToNativeConversionInfo(
|
|
template, declType=declType, dealWithOptional=isOptional, declArgs=declArgs
|
|
)
|
|
|
|
def incrementNestingLevel():
|
|
if nestingLevel == "":
|
|
return 1
|
|
return nestingLevel + 1
|
|
|
|
assert not (isEnforceRange and isClamp) # These are mutually exclusive
|
|
|
|
if type.isSequence():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
|
|
if failureCode is None:
|
|
notSequence = 'cx.ThrowErrorMessage<MSG_NOT_SEQUENCE>("%s");\n' "%s" % (
|
|
firstCap(sourceDescription),
|
|
exceptionCode,
|
|
)
|
|
else:
|
|
notSequence = failureCode
|
|
|
|
nullable = type.nullable()
|
|
# Be very careful not to change "type": we need it later
|
|
if nullable:
|
|
elementType = type.inner.inner
|
|
else:
|
|
elementType = type.inner
|
|
|
|
# We want to use auto arrays if we can, but we have to be careful with
|
|
# reallocation behavior for arrays. In particular, if we use auto
|
|
# arrays for sequences and have a sequence of elements which are
|
|
# themselves sequences or have sequences as members, we have a problem.
|
|
# In that case, resizing the outermost AutoTArray to the right size
|
|
# will memmove its elements, but AutoTArrays are not memmovable and
|
|
# hence will end up with pointers to bogus memory, which is bad. To
|
|
# deal with this, we typically map WebIDL sequences to our Sequence
|
|
# type, which is in fact memmovable. The one exception is when we're
|
|
# passing in a sequence directly as an argument without any sort of
|
|
# optional or nullable complexity going on. In that situation, we can
|
|
# use an AutoSequence instead. We have to keep using Sequence in the
|
|
# nullable and optional cases because we don't want to leak the
|
|
# AutoSequence type to consumers, which would be unavoidable with
|
|
# Nullable<AutoSequence> or Optional<AutoSequence>.
|
|
if isMember or isOptional or nullable or isCallbackReturnValue:
|
|
sequenceClass = "Sequence"
|
|
else:
|
|
sequenceClass = "binding_detail::AutoSequence"
|
|
|
|
# XXXbz we can't include the index in the sourceDescription, because
|
|
# we don't really have a way to pass one in dynamically at runtime...
|
|
elementInfo = getJSToNativeConversionInfo(
|
|
elementType,
|
|
descriptorProvider,
|
|
isMember="Sequence",
|
|
exceptionCode=exceptionCode,
|
|
lenientFloatCode=lenientFloatCode,
|
|
isCallbackReturnValue=isCallbackReturnValue,
|
|
sourceDescription="element of %s" % sourceDescription,
|
|
nestingLevel=incrementNestingLevel(),
|
|
)
|
|
if elementInfo.dealWithOptional:
|
|
raise TypeError("Shouldn't have optional things in sequences")
|
|
if elementInfo.holderType is not None:
|
|
raise TypeError("Shouldn't need holders for sequences")
|
|
|
|
typeName = CGTemplatedType(sequenceClass, elementInfo.declType)
|
|
sequenceType = typeName.define()
|
|
if nullable:
|
|
typeName = CGTemplatedType("Nullable", typeName)
|
|
arrayRef = "${declName}.SetValue()"
|
|
else:
|
|
arrayRef = "${declName}"
|
|
|
|
elementConversion = string.Template(elementInfo.template).substitute(
|
|
{
|
|
"val": "temp" + str(nestingLevel),
|
|
"maybeMutableVal": "&temp" + str(nestingLevel),
|
|
"declName": "slot" + str(nestingLevel),
|
|
# We only need holderName here to handle isExternal()
|
|
# interfaces, which use an internal holder for the
|
|
# conversion even when forceOwningType ends up true.
|
|
"holderName": "tempHolder" + str(nestingLevel),
|
|
"passedToJSImpl": "${passedToJSImpl}",
|
|
}
|
|
)
|
|
|
|
elementInitializer = initializerForType(elementType)
|
|
if elementInitializer is None:
|
|
elementInitializer = ""
|
|
else:
|
|
elementInitializer = elementInitializer + ", "
|
|
|
|
# NOTE: Keep this in sync with variadic conversions as needed
|
|
templateBody = fill(
|
|
"""
|
|
JS::ForOfIterator iter${nestingLevel}(cx);
|
|
if (!iter${nestingLevel}.init($${val}, JS::ForOfIterator::AllowNonIterable)) {
|
|
$*{exceptionCode}
|
|
}
|
|
if (!iter${nestingLevel}.valueIsIterable()) {
|
|
$*{notSequence}
|
|
}
|
|
${sequenceType} &arr${nestingLevel} = ${arrayRef};
|
|
JS::Rooted<JS::Value> temp${nestingLevel}(cx);
|
|
while (true) {
|
|
bool done${nestingLevel};
|
|
if (!iter${nestingLevel}.next(&temp${nestingLevel}, &done${nestingLevel})) {
|
|
$*{exceptionCode}
|
|
}
|
|
if (done${nestingLevel}) {
|
|
break;
|
|
}
|
|
${elementType}* slotPtr${nestingLevel} = arr${nestingLevel}.AppendElement(${elementInitializer}mozilla::fallible);
|
|
if (!slotPtr${nestingLevel}) {
|
|
JS_ReportOutOfMemory(cx);
|
|
$*{exceptionCode}
|
|
}
|
|
${elementType}& slot${nestingLevel} = *slotPtr${nestingLevel};
|
|
$*{elementConversion}
|
|
}
|
|
""",
|
|
exceptionCode=exceptionCode,
|
|
notSequence=notSequence,
|
|
sequenceType=sequenceType,
|
|
arrayRef=arrayRef,
|
|
elementType=elementInfo.declType.define(),
|
|
elementConversion=elementConversion,
|
|
elementInitializer=elementInitializer,
|
|
nestingLevel=str(nestingLevel),
|
|
)
|
|
|
|
templateBody = wrapObjectTemplate(
|
|
templateBody, type, "${declName}.SetNull();\n", notSequence
|
|
)
|
|
if isinstance(defaultValue, IDLEmptySequenceValue):
|
|
if type.nullable():
|
|
codeToSetEmpty = "${declName}.SetValue();\n"
|
|
else:
|
|
codeToSetEmpty = (
|
|
"/* ${declName} array is already empty; nothing to do */\n"
|
|
)
|
|
templateBody = handleDefault(templateBody, codeToSetEmpty)
|
|
|
|
# Sequence arguments that might contain traceable things need
|
|
# to get traced
|
|
if not isMember and typeNeedsRooting(elementType):
|
|
holderType = CGTemplatedType("SequenceRooter", elementInfo.declType)
|
|
# If our sequence is nullable, this will set the Nullable to be
|
|
# not-null, but that's ok because we make an explicit SetNull() call
|
|
# on it as needed if our JS value is actually null.
|
|
holderArgs = "cx, &%s" % arrayRef
|
|
else:
|
|
holderType = None
|
|
holderArgs = None
|
|
|
|
return JSToNativeConversionInfo(
|
|
templateBody,
|
|
declType=typeName,
|
|
holderType=holderType,
|
|
dealWithOptional=isOptional,
|
|
holderArgs=holderArgs,
|
|
)
|
|
|
|
if type.isRecord():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
if failureCode is None:
|
|
notRecord = 'cx.ThrowErrorMessage<MSG_NOT_OBJECT>("%s");\n' "%s" % (
|
|
firstCap(sourceDescription),
|
|
exceptionCode,
|
|
)
|
|
else:
|
|
notRecord = failureCode
|
|
|
|
nullable = type.nullable()
|
|
# Be very careful not to change "type": we need it later
|
|
if nullable:
|
|
recordType = type.inner
|
|
else:
|
|
recordType = type
|
|
valueType = recordType.inner
|
|
|
|
valueInfo = getJSToNativeConversionInfo(
|
|
valueType,
|
|
descriptorProvider,
|
|
isMember="Record",
|
|
exceptionCode=exceptionCode,
|
|
lenientFloatCode=lenientFloatCode,
|
|
isCallbackReturnValue=isCallbackReturnValue,
|
|
sourceDescription="value in %s" % sourceDescription,
|
|
nestingLevel=incrementNestingLevel(),
|
|
)
|
|
if valueInfo.dealWithOptional:
|
|
raise TypeError("Shouldn't have optional things in record")
|
|
if valueInfo.holderType is not None:
|
|
raise TypeError("Shouldn't need holders for record")
|
|
|
|
declType = CGTemplatedType(
|
|
"Record", [recordKeyDeclType(recordType), valueInfo.declType]
|
|
)
|
|
typeName = declType.define()
|
|
if nullable:
|
|
declType = CGTemplatedType("Nullable", declType)
|
|
recordRef = "${declName}.SetValue()"
|
|
else:
|
|
recordRef = "${declName}"
|
|
|
|
valueConversion = string.Template(valueInfo.template).substitute(
|
|
{
|
|
"val": "temp",
|
|
"maybeMutableVal": "&temp",
|
|
"declName": "slot",
|
|
# We only need holderName here to handle isExternal()
|
|
# interfaces, which use an internal holder for the
|
|
# conversion even when forceOwningType ends up true.
|
|
"holderName": "tempHolder",
|
|
"passedToJSImpl": "${passedToJSImpl}",
|
|
}
|
|
)
|
|
|
|
keyType = recordKeyType(recordType)
|
|
if recordType.keyType.isJSString():
|
|
raise TypeError(
|
|
"Have do deal with JSString record type, but don't know how"
|
|
)
|
|
if recordType.keyType.isByteString() or recordType.keyType.isUTF8String():
|
|
hashKeyType = "nsCStringHashKey"
|
|
if recordType.keyType.isByteString():
|
|
keyConversionFunction = "ConvertJSValueToByteString"
|
|
else:
|
|
keyConversionFunction = "ConvertJSValueToString"
|
|
|
|
else:
|
|
hashKeyType = "nsStringHashKey"
|
|
if recordType.keyType.isDOMString():
|
|
keyConversionFunction = "ConvertJSValueToString"
|
|
else:
|
|
assert recordType.keyType.isUSVString()
|
|
keyConversionFunction = "ConvertJSValueToUSVString"
|
|
|
|
templateBody = fill(
|
|
"""
|
|
auto& recordEntries = ${recordRef}.Entries();
|
|
|
|
JS::Rooted<JSObject*> recordObj(cx, &$${val}.toObject());
|
|
JS::RootedVector<jsid> ids(cx);
|
|
if (!js::GetPropertyKeys(cx, recordObj,
|
|
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &ids)) {
|
|
$*{exceptionCode}
|
|
}
|
|
if (!recordEntries.SetCapacity(ids.length(), mozilla::fallible)) {
|
|
JS_ReportOutOfMemory(cx);
|
|
$*{exceptionCode}
|
|
}
|
|
JS::Rooted<JS::Value> propNameValue(cx);
|
|
JS::Rooted<JS::Value> temp(cx);
|
|
JS::Rooted<jsid> curId(cx);
|
|
JS::Rooted<JS::Value> idVal(cx);
|
|
// Use a hashset to keep track of ids seen, to avoid
|
|
// introducing nasty O(N^2) behavior scanning for them all the
|
|
// time. Ideally we'd use a data structure with O(1) lookup
|
|
// _and_ ordering for the MozMap, but we don't have one lying
|
|
// around.
|
|
nsTHashtable<${hashKeyType}> idsSeen;
|
|
for (size_t i = 0; i < ids.length(); ++i) {
|
|
curId = ids[i];
|
|
|
|
JS::Rooted<JS::PropertyDescriptor> desc(cx);
|
|
if (!JS_GetOwnPropertyDescriptorById(cx, recordObj, curId,
|
|
&desc)) {
|
|
$*{exceptionCode}
|
|
}
|
|
|
|
if (!desc.object() /* == undefined in spec terms */ ||
|
|
!desc.enumerable()) {
|
|
continue;
|
|
}
|
|
|
|
idVal = js::IdToValue(curId);
|
|
${keyType} propName;
|
|
// This will just throw if idVal is a Symbol, like the spec says
|
|
// to do.
|
|
if (!${keyConversionFunction}(cx, idVal, "key of ${sourceDescription}", propName)) {
|
|
$*{exceptionCode}
|
|
}
|
|
|
|
if (!JS_GetPropertyById(cx, recordObj, curId, &temp)) {
|
|
$*{exceptionCode}
|
|
}
|
|
|
|
${typeName}::EntryType* entry;
|
|
if (!idsSeen.EnsureInserted(propName)) {
|
|
// Find the existing entry.
|
|
auto idx = recordEntries.IndexOf(propName);
|
|
MOZ_ASSERT(idx != recordEntries.NoIndex,
|
|
"Why is it not found?");
|
|
// Now blow it away to make it look like it was just added
|
|
// to the array, because it's not obvious that it's
|
|
// safe to write to its already-initialized mValue via our
|
|
// normal codegen conversions. For example, the value
|
|
// could be a union and this would change its type, but
|
|
// codegen assumes we won't do that.
|
|
entry = recordEntries.ReconstructElementAt(idx);
|
|
} else {
|
|
// Safe to do an infallible append here, because we did a
|
|
// SetCapacity above to the right capacity.
|
|
entry = recordEntries.AppendElement();
|
|
}
|
|
entry->mKey = propName;
|
|
${valueType}& slot = entry->mValue;
|
|
$*{valueConversion}
|
|
}
|
|
""",
|
|
exceptionCode=exceptionCode,
|
|
recordRef=recordRef,
|
|
hashKeyType=hashKeyType,
|
|
keyType=keyType,
|
|
keyConversionFunction=keyConversionFunction,
|
|
sourceDescription=sourceDescription,
|
|
typeName=typeName,
|
|
valueType=valueInfo.declType.define(),
|
|
valueConversion=valueConversion,
|
|
)
|
|
|
|
templateBody = wrapObjectTemplate(
|
|
templateBody, type, "${declName}.SetNull();\n", notRecord
|
|
)
|
|
|
|
declArgs = None
|
|
holderType = None
|
|
holderArgs = None
|
|
# record arguments that might contain traceable things need
|
|
# to get traced
|
|
if not isMember and isCallbackReturnValue:
|
|
# Go ahead and just convert directly into our actual return value
|
|
declType = CGWrapper(declType, post="&")
|
|
declArgs = "aRetVal"
|
|
elif not isMember and typeNeedsRooting(valueType):
|
|
holderType = CGTemplatedType(
|
|
"RecordRooter", [recordKeyDeclType(recordType), valueInfo.declType]
|
|
)
|
|
# If our record is nullable, this will set the Nullable to be
|
|
# not-null, but that's ok because we make an explicit SetNull() call
|
|
# on it as needed if our JS value is actually null.
|
|
holderArgs = "cx, &%s" % recordRef
|
|
|
|
return JSToNativeConversionInfo(
|
|
templateBody,
|
|
declType=declType,
|
|
declArgs=declArgs,
|
|
holderType=holderType,
|
|
dealWithOptional=isOptional,
|
|
holderArgs=holderArgs,
|
|
)
|
|
|
|
if type.isUnion():
|
|
nullable = type.nullable()
|
|
if nullable:
|
|
type = type.inner
|
|
|
|
isOwningUnion = isMember or isCallbackReturnValue
|
|
unionArgumentObj = "${declName}" if isOwningUnion else "${holderName}"
|
|
if nullable:
|
|
# If we're owning, we're a Nullable, which hasn't been told it has
|
|
# a value. Otherwise we're an already-constructed Maybe.
|
|
unionArgumentObj += ".SetValue()" if isOwningUnion else ".ref()"
|
|
|
|
memberTypes = type.flatMemberTypes
|
|
prettyNames = []
|
|
|
|
interfaceMemberTypes = [t for t in memberTypes if t.isNonCallbackInterface()]
|
|
if len(interfaceMemberTypes) > 0:
|
|
interfaceObject = []
|
|
for memberType in interfaceMemberTypes:
|
|
name = getUnionMemberName(memberType)
|
|
interfaceObject.append(
|
|
CGGeneric(
|
|
"(failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext"
|
|
% (unionArgumentObj, name)
|
|
)
|
|
)
|
|
prettyNames.append(memberType.prettyName())
|
|
interfaceObject = CGWrapper(
|
|
CGList(interfaceObject, " ||\n"),
|
|
pre="done = ",
|
|
post=";\n\n",
|
|
reindent=True,
|
|
)
|
|
else:
|
|
interfaceObject = None
|
|
|
|
sequenceObjectMemberTypes = [t for t in memberTypes if t.isSequence()]
|
|
if len(sequenceObjectMemberTypes) > 0:
|
|
assert len(sequenceObjectMemberTypes) == 1
|
|
memberType = sequenceObjectMemberTypes[0]
|
|
name = getUnionMemberName(memberType)
|
|
sequenceObject = CGGeneric(
|
|
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
|
|
% (unionArgumentObj, name)
|
|
)
|
|
prettyNames.append(memberType.prettyName())
|
|
else:
|
|
sequenceObject = None
|
|
|
|
callbackMemberTypes = [
|
|
t for t in memberTypes if t.isCallback() or t.isCallbackInterface()
|
|
]
|
|
if len(callbackMemberTypes) > 0:
|
|
assert len(callbackMemberTypes) == 1
|
|
memberType = callbackMemberTypes[0]
|
|
name = getUnionMemberName(memberType)
|
|
callbackObject = CGGeneric(
|
|
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
|
|
% (unionArgumentObj, name)
|
|
)
|
|
prettyNames.append(memberType.prettyName())
|
|
else:
|
|
callbackObject = None
|
|
|
|
dictionaryMemberTypes = [t for t in memberTypes if t.isDictionary()]
|
|
if len(dictionaryMemberTypes) > 0:
|
|
assert len(dictionaryMemberTypes) == 1
|
|
memberType = dictionaryMemberTypes[0]
|
|
name = getUnionMemberName(memberType)
|
|
setDictionary = CGGeneric(
|
|
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
|
|
% (unionArgumentObj, name)
|
|
)
|
|
prettyNames.append(memberType.prettyName())
|
|
else:
|
|
setDictionary = None
|
|
|
|
recordMemberTypes = [t for t in memberTypes if t.isRecord()]
|
|
if len(recordMemberTypes) > 0:
|
|
assert len(recordMemberTypes) == 1
|
|
memberType = recordMemberTypes[0]
|
|
name = getUnionMemberName(memberType)
|
|
recordObject = CGGeneric(
|
|
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
|
|
% (unionArgumentObj, name)
|
|
)
|
|
prettyNames.append(memberType.prettyName())
|
|
else:
|
|
recordObject = None
|
|
|
|
objectMemberTypes = [t for t in memberTypes if t.isObject()]
|
|
if len(objectMemberTypes) > 0:
|
|
assert len(objectMemberTypes) == 1
|
|
# Very important to NOT construct a temporary Rooted here, since the
|
|
# SetToObject call can call a Rooted constructor and we need to keep
|
|
# stack discipline for Rooted.
|
|
object = CGGeneric(
|
|
"if (!%s.SetToObject(cx, &${val}.toObject(), ${passedToJSImpl})) {\n"
|
|
"%s"
|
|
"}\n"
|
|
"done = true;\n" % (unionArgumentObj, indent(exceptionCode))
|
|
)
|
|
prettyNames.append(objectMemberTypes[0].prettyName())
|
|
else:
|
|
object = None
|
|
|
|
hasObjectTypes = (
|
|
interfaceObject
|
|
or sequenceObject
|
|
or callbackObject
|
|
or object
|
|
or recordObject
|
|
)
|
|
if hasObjectTypes:
|
|
# "object" is not distinguishable from other types
|
|
assert not object or not (
|
|
interfaceObject or sequenceObject or callbackObject or recordObject
|
|
)
|
|
if sequenceObject or callbackObject:
|
|
# An object can be both an sequence object and a callback or
|
|
# dictionary, but we shouldn't have both in the union's members
|
|
# because they are not distinguishable.
|
|
assert not (sequenceObject and callbackObject)
|
|
templateBody = CGElseChain([sequenceObject, callbackObject])
|
|
else:
|
|
templateBody = None
|
|
if interfaceObject:
|
|
assert not object
|
|
if templateBody:
|
|
templateBody = CGIfWrapper(templateBody, "!done")
|
|
templateBody = CGList([interfaceObject, templateBody])
|
|
else:
|
|
templateBody = CGList([templateBody, object])
|
|
|
|
if recordObject:
|
|
templateBody = CGList(
|
|
[templateBody, CGIfWrapper(recordObject, "!done")]
|
|
)
|
|
|
|
templateBody = CGIfWrapper(templateBody, "${val}.isObject()")
|
|
else:
|
|
templateBody = CGGeneric()
|
|
|
|
if setDictionary:
|
|
assert not object
|
|
templateBody = CGList([templateBody, CGIfWrapper(setDictionary, "!done")])
|
|
|
|
stringTypes = [t for t in memberTypes if t.isString() or t.isEnum()]
|
|
numericTypes = [t for t in memberTypes if t.isNumeric()]
|
|
booleanTypes = [t for t in memberTypes if t.isBoolean()]
|
|
if stringTypes or numericTypes or booleanTypes:
|
|
assert len(stringTypes) <= 1
|
|
assert len(numericTypes) <= 1
|
|
assert len(booleanTypes) <= 1
|
|
|
|
# We will wrap all this stuff in a do { } while (0); so we
|
|
# can use "break" for flow control.
|
|
def getStringOrPrimitiveConversion(memberType):
|
|
name = getUnionMemberName(memberType)
|
|
return CGGeneric(
|
|
"done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n"
|
|
"break;\n" % (unionArgumentObj, name)
|
|
)
|
|
|
|
other = CGList([])
|
|
stringConversion = [getStringOrPrimitiveConversion(t) for t in stringTypes]
|
|
numericConversion = [
|
|
getStringOrPrimitiveConversion(t) for t in numericTypes
|
|
]
|
|
booleanConversion = [
|
|
getStringOrPrimitiveConversion(t) for t in booleanTypes
|
|
]
|
|
if stringConversion:
|
|
if booleanConversion:
|
|
other.append(
|
|
CGIfWrapper(booleanConversion[0], "${val}.isBoolean()")
|
|
)
|
|
if numericConversion:
|
|
other.append(CGIfWrapper(numericConversion[0], "${val}.isNumber()"))
|
|
other.append(stringConversion[0])
|
|
elif numericConversion:
|
|
if booleanConversion:
|
|
other.append(
|
|
CGIfWrapper(booleanConversion[0], "${val}.isBoolean()")
|
|
)
|
|
other.append(numericConversion[0])
|
|
else:
|
|
assert booleanConversion
|
|
other.append(booleanConversion[0])
|
|
|
|
other = CGWrapper(
|
|
CGIndenter(other), pre="do {\n", post="} while (false);\n"
|
|
)
|
|
if hasObjectTypes or setDictionary:
|
|
other = CGWrapper(CGIndenter(other), "{\n", post="}\n")
|
|
if object:
|
|
templateBody = CGElseChain([templateBody, other])
|
|
else:
|
|
other = CGWrapper(other, pre="if (!done) ")
|
|
templateBody = CGList([templateBody, other])
|
|
else:
|
|
assert templateBody.define() == ""
|
|
templateBody = other
|
|
else:
|
|
other = None
|
|
|
|
templateBody = CGWrapper(
|
|
templateBody, pre="bool done = false, failed = false, tryNext;\n"
|
|
)
|
|
throw = CGGeneric(
|
|
fill(
|
|
"""
|
|
if (failed) {
|
|
$*{exceptionCode}
|
|
}
|
|
if (!done) {
|
|
cx.ThrowErrorMessage<MSG_NOT_IN_UNION>("${desc}", "${names}");
|
|
$*{exceptionCode}
|
|
}
|
|
""",
|
|
exceptionCode=exceptionCode,
|
|
desc=firstCap(sourceDescription),
|
|
names=", ".join(prettyNames),
|
|
)
|
|
)
|
|
|
|
templateBody = CGWrapper(
|
|
CGIndenter(CGList([templateBody, throw])), pre="{\n", post="}\n"
|
|
)
|
|
|
|
typeName = CGUnionStruct.unionTypeDecl(type, isOwningUnion)
|
|
argumentTypeName = typeName + "Argument"
|
|
if nullable:
|
|
typeName = "Nullable<" + typeName + " >"
|
|
|
|
def handleNull(templateBody, setToNullVar, extraConditionForNull=""):
|
|
nullTest = "%s${val}.isNullOrUndefined()" % extraConditionForNull
|
|
return CGIfElseWrapper(
|
|
nullTest, CGGeneric("%s.SetNull();\n" % setToNullVar), templateBody
|
|
)
|
|
|
|
if type.hasNullableType:
|
|
assert not nullable
|
|
# Make sure to handle a null default value here
|
|
if defaultValue and isinstance(defaultValue, IDLNullValue):
|
|
assert defaultValue.type == type
|
|
extraConditionForNull = "!(${haveValue}) || "
|
|
else:
|
|
extraConditionForNull = ""
|
|
templateBody = handleNull(
|
|
templateBody,
|
|
unionArgumentObj,
|
|
extraConditionForNull=extraConditionForNull,
|
|
)
|
|
|
|
declType = CGGeneric(typeName)
|
|
if isOwningUnion:
|
|
holderType = None
|
|
else:
|
|
holderType = CGGeneric(argumentTypeName)
|
|
if nullable:
|
|
holderType = CGTemplatedType("Maybe", holderType)
|
|
|
|
# If we're isOptional and not nullable the normal optional handling will
|
|
# handle lazy construction of our holder. If we're nullable and not
|
|
# owning we do it all by hand because we do not want our holder
|
|
# constructed if we're null. But if we're owning we don't have a
|
|
# holder anyway, so we can do the normal Optional codepath.
|
|
declLoc = "${declName}"
|
|
constructDecl = None
|
|
if nullable:
|
|
if isOptional and not isOwningUnion:
|
|
holderArgs = "${declName}.Value().SetValue()"
|
|
declType = CGTemplatedType("Optional", declType)
|
|
constructDecl = CGGeneric("${declName}.Construct();\n")
|
|
declLoc = "${declName}.Value()"
|
|
else:
|
|
holderArgs = "${declName}.SetValue()"
|
|
if holderType is not None:
|
|
constructHolder = CGGeneric("${holderName}.emplace(%s);\n" % holderArgs)
|
|
else:
|
|
constructHolder = None
|
|
# Don't need to pass those args when the holder is being constructed
|
|
holderArgs = None
|
|
else:
|
|
holderArgs = "${declName}"
|
|
constructHolder = None
|
|
|
|
if not isMember and isCallbackReturnValue:
|
|
declType = CGWrapper(declType, post="&")
|
|
declArgs = "aRetVal"
|
|
else:
|
|
declArgs = None
|
|
|
|
if (
|
|
defaultValue
|
|
and not isinstance(defaultValue, IDLNullValue)
|
|
and not isinstance(defaultValue, IDLDefaultDictionaryValue)
|
|
):
|
|
tag = defaultValue.type.tag()
|
|
|
|
if tag in numericSuffixes or tag is IDLType.Tags.bool:
|
|
defaultStr = getHandleDefault(defaultValue)
|
|
# Make sure we actually construct the thing inside the nullable.
|
|
value = declLoc + (".SetValue()" if nullable else "")
|
|
name = getUnionMemberName(defaultValue.type)
|
|
default = CGGeneric(
|
|
"%s.RawSetAs%s() = %s;\n" % (value, name, defaultStr)
|
|
)
|
|
elif isinstance(defaultValue, IDLEmptySequenceValue):
|
|
name = getUnionMemberName(defaultValue.type)
|
|
# Make sure we actually construct the thing inside the nullable.
|
|
value = declLoc + (".SetValue()" if nullable else "")
|
|
# It's enough to set us to the right type; that will
|
|
# create an empty array, which is all we need here.
|
|
default = CGGeneric("%s.RawSetAs%s();\n" % (value, name))
|
|
elif defaultValue.type.isEnum():
|
|
name = getUnionMemberName(defaultValue.type)
|
|
# Make sure we actually construct the thing inside the nullable.
|
|
value = declLoc + (".SetValue()" if nullable else "")
|
|
default = CGGeneric(
|
|
"%s.RawSetAs%s() = %s::%s;\n"
|
|
% (
|
|
value,
|
|
name,
|
|
defaultValue.type.inner.identifier.name,
|
|
getEnumValueName(defaultValue.value),
|
|
)
|
|
)
|
|
else:
|
|
default = CGGeneric(
|
|
handleDefaultStringValue(
|
|
defaultValue, "%s.SetStringLiteral" % unionArgumentObj
|
|
)
|
|
)
|
|
|
|
templateBody = CGIfElseWrapper("!(${haveValue})", default, templateBody)
|
|
|
|
templateBody = CGList([constructHolder, templateBody])
|
|
|
|
if nullable:
|
|
if defaultValue:
|
|
if isinstance(defaultValue, IDLNullValue):
|
|
extraConditionForNull = "!(${haveValue}) || "
|
|
else:
|
|
extraConditionForNull = "(${haveValue}) && "
|
|
else:
|
|
extraConditionForNull = ""
|
|
templateBody = handleNull(
|
|
templateBody, declLoc, extraConditionForNull=extraConditionForNull
|
|
)
|
|
elif (
|
|
not type.hasNullableType
|
|
and defaultValue
|
|
and isinstance(defaultValue, IDLDefaultDictionaryValue)
|
|
):
|
|
assert type.hasDictionaryType()
|
|
assert defaultValue.type.isDictionary()
|
|
if not isOwningUnion and typeNeedsRooting(defaultValue.type):
|
|
ctorArgs = "cx"
|
|
else:
|
|
ctorArgs = ""
|
|
initDictionaryWithNull = CGIfWrapper(
|
|
CGGeneric("return false;\n"),
|
|
(
|
|
'!%s.RawSetAs%s(%s).Init(cx, JS::NullHandleValue, "Member of %s")'
|
|
% (
|
|
declLoc,
|
|
getUnionMemberName(defaultValue.type),
|
|
ctorArgs,
|
|
type.prettyName(),
|
|
)
|
|
),
|
|
)
|
|
templateBody = CGIfElseWrapper(
|
|
"!(${haveValue})", initDictionaryWithNull, templateBody
|
|
)
|
|
|
|
templateBody = CGList([constructDecl, templateBody])
|
|
|
|
return JSToNativeConversionInfo(
|
|
templateBody.define(),
|
|
declType=declType,
|
|
declArgs=declArgs,
|
|
holderType=holderType,
|
|
holderArgs=holderArgs,
|
|
dealWithOptional=isOptional and (not nullable or isOwningUnion),
|
|
)
|
|
|
|
if type.isPromise():
|
|
assert not type.nullable()
|
|
assert defaultValue is None
|
|
|
|
# We always have to hold a strong ref to Promise here, because
|
|
# Promise::resolve returns an addrefed thing.
|
|
argIsPointer = isCallbackReturnValue
|
|
if argIsPointer:
|
|
declType = CGGeneric("RefPtr<Promise>")
|
|
else:
|
|
declType = CGGeneric("OwningNonNull<Promise>")
|
|
|
|
# Per spec, what we're supposed to do is take the original
|
|
# Promise.resolve and call it with the original Promise as this
|
|
# value to make a Promise out of whatever value we actually have
|
|
# here. The question is which global we should use. There are
|
|
# several cases to consider:
|
|
#
|
|
# 1) Normal call to API with a Promise argument. This is a case the
|
|
# spec covers, and we should be using the current Realm's
|
|
# Promise. That means the current compartment.
|
|
# 2) Call to API with a Promise argument over Xrays. In practice,
|
|
# this sort of thing seems to be used for giving an API
|
|
# implementation a way to wait for conclusion of an asyc
|
|
# operation, _not_ to expose the Promise to content code. So we
|
|
# probably want to allow callers to use such an API in a
|
|
# "natural" way, by passing chrome-side promises; indeed, that
|
|
# may be all that the caller has to represent their async
|
|
# operation. That means we really need to do the
|
|
# Promise.resolve() in the caller (chrome) compartment: if we do
|
|
# it in the content compartment, we will try to call .then() on
|
|
# the chrome promise while in the content compartment, which will
|
|
# throw and we'll just get a rejected Promise. Note that this is
|
|
# also the reason why a caller who has a chrome Promise
|
|
# representing an async operation can't itself convert it to a
|
|
# content-side Promise (at least not without some serious
|
|
# gyrations).
|
|
# 3) Promise return value from a callback or callback interface.
|
|
# Per spec, this should use the Realm of the callback object. In
|
|
# our case, that's the compartment of the underlying callback,
|
|
# not the current compartment (which may be the compartment of
|
|
# some cross-compartment wrapper around said callback).
|
|
# 4) Return value from a JS-implemented interface. In this case we
|
|
# have a problem. Our current compartment is the compartment of
|
|
# the JS implementation. But if the JS implementation returned
|
|
# a page-side Promise (which is a totally sane thing to do, and
|
|
# in fact the right thing to do given that this return value is
|
|
# going right to content script) then we don't want to
|
|
# Promise.resolve with our current compartment Promise, because
|
|
# that will wrap it up in a chrome-side Promise, which is
|
|
# decidedly _not_ what's desired here. So in that case we
|
|
# should really unwrap the return value and use the global of
|
|
# the result. CheckedUnwrapStatic should be good enough for that;
|
|
# if it fails, then we're failing unwrap while in a
|
|
# system-privileged compartment, so presumably we have a dead
|
|
# object wrapper. Just error out. Do NOT fall back to using
|
|
# the current compartment instead: that will return a
|
|
# system-privileged rejected (because getting .then inside
|
|
# resolve() failed) Promise to the caller, which they won't be
|
|
# able to touch. That's not helpful. If we error out, on the
|
|
# other hand, they will get a content-side rejected promise.
|
|
# Same thing if the value returned is not even an object.
|
|
if isCallbackReturnValue == "JSImpl":
|
|
# Case 4 above. Note that globalObj defaults to the current
|
|
# compartment global. Note that we don't use $*{exceptionCode}
|
|
# here because that will try to aRv.Throw(NS_ERROR_UNEXPECTED)
|
|
# which we don't really want here.
|
|
assert exceptionCode == "aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n"
|
|
getPromiseGlobal = fill(
|
|
"""
|
|
if (!$${val}.isObject()) {
|
|
aRv.ThrowTypeError<MSG_NOT_OBJECT>("${sourceDescription}");
|
|
return nullptr;
|
|
}
|
|
JSObject* unwrappedVal = js::CheckedUnwrapStatic(&$${val}.toObject());
|
|
if (!unwrappedVal) {
|
|
// A slight lie, but not much of one, for a dead object wrapper.
|
|
aRv.ThrowTypeError<MSG_NOT_OBJECT>("${sourceDescription}");
|
|
return nullptr;
|
|
}
|
|
globalObj = JS::GetNonCCWObjectGlobal(unwrappedVal);
|
|
""",
|
|
sourceDescription=sourceDescription,
|
|
)
|
|
elif isCallbackReturnValue == "Callback":
|
|
getPromiseGlobal = dedent(
|
|
"""
|
|
// We basically want our entry global here. Play it safe
|
|
// and use GetEntryGlobal() to get it, with whatever
|
|
// principal-clamping it ends up doing.
|
|
globalObj = GetEntryGlobal()->GetGlobalJSObject();
|
|
"""
|
|
)
|
|
else:
|
|
getPromiseGlobal = dedent(
|
|
"""
|
|
globalObj = JS::CurrentGlobalOrNull(cx);
|
|
"""
|
|
)
|
|
|
|
templateBody = fill(
|
|
"""
|
|
{ // Scope for our GlobalObject, FastErrorResult, JSAutoRealm,
|
|
// etc.
|
|
|
|
JS::Rooted<JSObject*> globalObj(cx);
|
|
$*{getPromiseGlobal}
|
|
JSAutoRealm ar(cx, globalObj);
|
|
GlobalObject promiseGlobal(cx, globalObj);
|
|
if (promiseGlobal.Failed()) {
|
|
$*{exceptionCode}
|
|
}
|
|
|
|
JS::Rooted<JS::Value> valueToResolve(cx, $${val});
|
|
if (!JS_WrapValue(cx, &valueToResolve)) {
|
|
$*{exceptionCode}
|
|
}
|
|
binding_detail::FastErrorResult promiseRv;
|
|
nsCOMPtr<nsIGlobalObject> global =
|
|
do_QueryInterface(promiseGlobal.GetAsSupports());
|
|
if (!global) {
|
|
promiseRv.Throw(NS_ERROR_UNEXPECTED);
|
|
MOZ_ALWAYS_TRUE(promiseRv.MaybeSetPendingException(cx));
|
|
$*{exceptionCode}
|
|
}
|
|
$${declName} = Promise::Resolve(global, cx, valueToResolve,
|
|
promiseRv);
|
|
if (promiseRv.MaybeSetPendingException(cx)) {
|
|
$*{exceptionCode}
|
|
}
|
|
}
|
|
""",
|
|
getPromiseGlobal=getPromiseGlobal,
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
|
|
return JSToNativeConversionInfo(
|
|
templateBody, declType=declType, dealWithOptional=isOptional
|
|
)
|
|
|
|
if type.isGeckoInterface():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
|
|
descriptor = descriptorProvider.getDescriptor(
|
|
type.unroll().inner.identifier.name
|
|
)
|
|
|
|
assert descriptor.nativeType != "JSObject"
|
|
|
|
if descriptor.interface.isCallback():
|
|
(declType, declArgs, conversion) = getCallbackConversionInfo(
|
|
type, descriptor.interface, isMember, isCallbackReturnValue, isOptional
|
|
)
|
|
template = wrapObjectTemplate(
|
|
conversion, type, "${declName} = nullptr;\n", failureCode
|
|
)
|
|
return JSToNativeConversionInfo(
|
|
template,
|
|
declType=declType,
|
|
declArgs=declArgs,
|
|
dealWithOptional=isOptional,
|
|
)
|
|
|
|
if descriptor.interface.identifier.name == "WindowProxy":
|
|
declType = CGGeneric("mozilla::dom::WindowProxyHolder")
|
|
if type.nullable():
|
|
declType = CGTemplatedType("Nullable", declType)
|
|
windowProxyHolderRef = "${declName}.SetValue()"
|
|
else:
|
|
windowProxyHolderRef = "${declName}"
|
|
|
|
failureCode = onFailureBadType(
|
|
failureCode, descriptor.interface.identifier.name
|
|
).define()
|
|
templateBody = fill(
|
|
"""
|
|
JS::Rooted<JSObject*> source(cx, &$${val}.toObject());
|
|
if (NS_FAILED(UnwrapWindowProxyArg(cx, source, ${windowProxyHolderRef}))) {
|
|
$*{onFailure}
|
|
}
|
|
""",
|
|
windowProxyHolderRef=windowProxyHolderRef,
|
|
onFailure=failureCode,
|
|
)
|
|
templateBody = wrapObjectTemplate(
|
|
templateBody, type, "${declName}.SetNull();\n", failureCode
|
|
)
|
|
return JSToNativeConversionInfo(
|
|
templateBody, declType=declType, dealWithOptional=isOptional
|
|
)
|
|
|
|
# This is an interface that we implement as a concrete class
|
|
# or an XPCOM interface.
|
|
|
|
# Allow null pointers for nullable types and old-binding classes, and
|
|
# use an RefPtr or raw pointer for callback return values to make
|
|
# them easier to return.
|
|
argIsPointer = (
|
|
type.nullable() or type.unroll().inner.isExternal() or isCallbackReturnValue
|
|
)
|
|
|
|
# Sequence and dictionary members, as well as owning unions (which can
|
|
# appear here as return values in JS-implemented interfaces) have to
|
|
# hold a strong ref to the thing being passed down. Those all set
|
|
# isMember.
|
|
#
|
|
# Also, callback return values always end up addrefing anyway, so there
|
|
# is no point trying to avoid it here and it makes other things simpler
|
|
# since we can assume the return value is a strong ref.
|
|
assert not descriptor.interface.isCallback()
|
|
forceOwningType = isMember or isCallbackReturnValue
|
|
|
|
typeName = descriptor.nativeType
|
|
typePtr = typeName + "*"
|
|
|
|
# Compute a few things:
|
|
# - declType is the type we want to return as the first element of our
|
|
# tuple.
|
|
# - holderType is the type we want to return as the third element
|
|
# of our tuple.
|
|
|
|
# Set up some sensible defaults for these things insofar as we can.
|
|
holderType = None
|
|
if argIsPointer:
|
|
if forceOwningType:
|
|
declType = "RefPtr<" + typeName + ">"
|
|
else:
|
|
declType = typePtr
|
|
else:
|
|
if forceOwningType:
|
|
declType = "OwningNonNull<" + typeName + ">"
|
|
else:
|
|
declType = "NonNull<" + typeName + ">"
|
|
|
|
templateBody = ""
|
|
if forceOwningType:
|
|
templateBody += fill(
|
|
"""
|
|
static_assert(IsRefcounted<${typeName}>::value, "We can only store refcounted classes.");
|
|
""",
|
|
typeName=typeName,
|
|
)
|
|
|
|
if not descriptor.interface.isExternal():
|
|
if failureCode is not None:
|
|
templateBody += str(
|
|
CastableObjectUnwrapper(
|
|
descriptor,
|
|
"${val}",
|
|
"${maybeMutableVal}",
|
|
"${declName}",
|
|
failureCode,
|
|
)
|
|
)
|
|
else:
|
|
templateBody += str(
|
|
FailureFatalCastableObjectUnwrapper(
|
|
descriptor,
|
|
"${val}",
|
|
"${maybeMutableVal}",
|
|
"${declName}",
|
|
exceptionCode,
|
|
isCallbackReturnValue,
|
|
firstCap(sourceDescription),
|
|
)
|
|
)
|
|
else:
|
|
# External interface. We always have a holder for these, because we
|
|
# don't actually know whether we have to addref when unwrapping or not.
|
|
# So we just pass an getter_AddRefs(RefPtr) to XPConnect and if we'll
|
|
# need a release it'll put a non-null pointer in there.
|
|
if forceOwningType:
|
|
# Don't return a holderType in this case; our declName
|
|
# will just own stuff.
|
|
templateBody += "RefPtr<" + typeName + "> ${holderName};\n"
|
|
else:
|
|
holderType = "RefPtr<" + typeName + ">"
|
|
templateBody += (
|
|
"JS::Rooted<JSObject*> source(cx, &${val}.toObject());\n"
|
|
+ "if (NS_FAILED(UnwrapArg<"
|
|
+ typeName
|
|
+ ">(cx, source, getter_AddRefs(${holderName})))) {\n"
|
|
)
|
|
templateBody += CGIndenter(
|
|
onFailureBadType(failureCode, descriptor.interface.identifier.name)
|
|
).define()
|
|
templateBody += "}\n" "MOZ_ASSERT(${holderName});\n"
|
|
|
|
# And store our value in ${declName}
|
|
templateBody += "${declName} = ${holderName};\n"
|
|
|
|
# Just pass failureCode, not onFailureBadType, here, so we'll report
|
|
# the thing as not an object as opposed to not implementing whatever
|
|
# our interface is.
|
|
templateBody = wrapObjectTemplate(
|
|
templateBody, type, "${declName} = nullptr;\n", failureCode
|
|
)
|
|
|
|
declType = CGGeneric(declType)
|
|
if holderType is not None:
|
|
holderType = CGGeneric(holderType)
|
|
return JSToNativeConversionInfo(
|
|
templateBody,
|
|
declType=declType,
|
|
holderType=holderType,
|
|
dealWithOptional=isOptional,
|
|
)
|
|
|
|
if type.isSpiderMonkeyInterface():
|
|
assert not isEnforceRange and not isClamp
|
|
name = type.unroll().name # unroll() because it may be nullable
|
|
interfaceType = CGGeneric(name)
|
|
declType = interfaceType
|
|
if type.nullable():
|
|
declType = CGTemplatedType("Nullable", declType)
|
|
objRef = "${declName}.SetValue()"
|
|
else:
|
|
objRef = "${declName}"
|
|
|
|
# Again, this is a bit strange since we are actually building a
|
|
# template string here. ${objRef} and $*{badType} below are filled in
|
|
# right now; $${val} expands to ${val}, to be filled in later.
|
|
template = fill(
|
|
"""
|
|
if (!${objRef}.Init(&$${val}.toObject())) {
|
|
$*{badType}
|
|
}
|
|
""",
|
|
objRef=objRef,
|
|
badType=onFailureBadType(failureCode, type.name).define(),
|
|
)
|
|
if not isAllowShared and type.isBufferSource():
|
|
if type.isArrayBuffer():
|
|
isSharedMethod = "JS::IsSharedArrayBufferObject"
|
|
else:
|
|
assert type.isArrayBufferView() or type.isTypedArray()
|
|
isSharedMethod = "JS::IsArrayBufferViewShared"
|
|
template += fill(
|
|
"""
|
|
if (${isSharedMethod}(${objRef}.Obj())) {
|
|
$*{badType}
|
|
}
|
|
""",
|
|
isSharedMethod=isSharedMethod,
|
|
objRef=objRef,
|
|
badType=onFailureIsShared().define(),
|
|
)
|
|
template = wrapObjectTemplate(
|
|
template, type, "${declName}.SetNull();\n", failureCode
|
|
)
|
|
if not isMember:
|
|
# This is a bit annoying. In a union we don't want to have a
|
|
# holder, since unions don't support that. But if we're optional we
|
|
# want to have a holder, so that the callee doesn't see
|
|
# Optional<RootedSpiderMonkeyInterface<InterfaceType>>. So do a
|
|
# holder if we're optional and use a RootedSpiderMonkeyInterface
|
|
# otherwise.
|
|
if isOptional:
|
|
holderType = CGTemplatedType(
|
|
"SpiderMonkeyInterfaceRooter", interfaceType
|
|
)
|
|
# If our SpiderMonkey interface is nullable, this will set the
|
|
# Nullable to be not-null, but that's ok because we make an
|
|
# explicit SetNull() call on it as needed if our JS value is
|
|
# actually null. XXXbz Because "Maybe" takes const refs for
|
|
# constructor arguments, we can't pass a reference here; have
|
|
# to pass a pointer.
|
|
holderArgs = "cx, &%s" % objRef
|
|
declArgs = None
|
|
else:
|
|
holderType = None
|
|
holderArgs = None
|
|
declType = CGTemplatedType("RootedSpiderMonkeyInterface", declType)
|
|
declArgs = "cx"
|
|
else:
|
|
holderType = None
|
|
holderArgs = None
|
|
declArgs = None
|
|
return JSToNativeConversionInfo(
|
|
template,
|
|
declType=declType,
|
|
holderType=holderType,
|
|
dealWithOptional=isOptional,
|
|
declArgs=declArgs,
|
|
holderArgs=holderArgs,
|
|
)
|
|
|
|
if type.isJSString():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
if type.nullable():
|
|
raise TypeError("Nullable JSString not supported")
|
|
|
|
declArgs = "cx"
|
|
if isMember:
|
|
raise TypeError("JSString not supported as member")
|
|
else:
|
|
declType = "JS::Rooted<JSString*>"
|
|
|
|
if isOptional:
|
|
raise TypeError("JSString not supported as optional")
|
|
templateBody = fill(
|
|
"""
|
|
if (!($${declName} = ConvertJSValueToJSString(cx, $${val}))) {
|
|
$*{exceptionCode}
|
|
}
|
|
""",
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
|
|
if defaultValue is not None:
|
|
assert not isinstance(defaultValue, IDLNullValue)
|
|
defaultCode = fill(
|
|
"""
|
|
static const char data[] = { ${data} };
|
|
$${declName} = JS_NewStringCopyN(cx, data, ArrayLength(data) - 1);
|
|
if (!$${declName}) {
|
|
$*{exceptionCode}
|
|
}
|
|
""",
|
|
data=", ".join(
|
|
["'" + char + "'" for char in defaultValue.value] + ["0"]
|
|
),
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
|
|
templateBody = handleDefault(templateBody, defaultCode)
|
|
return JSToNativeConversionInfo(
|
|
templateBody, declType=CGGeneric(declType), declArgs=declArgs
|
|
)
|
|
|
|
if type.isDOMString() or type.isUSVString() or type.isUTF8String():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
|
|
treatAs = {
|
|
"Default": "eStringify",
|
|
"EmptyString": "eEmpty",
|
|
"Null": "eNull",
|
|
}
|
|
if type.nullable():
|
|
# For nullable strings null becomes a null string.
|
|
treatNullAs = "Null"
|
|
# For nullable strings undefined also becomes a null string.
|
|
undefinedBehavior = "eNull"
|
|
else:
|
|
undefinedBehavior = "eStringify"
|
|
if type.treatNullAsEmpty:
|
|
treatNullAs = "EmptyString"
|
|
else:
|
|
treatNullAs = "Default"
|
|
nullBehavior = treatAs[treatNullAs]
|
|
|
|
def getConversionCode(varName):
|
|
normalizeCode = ""
|
|
if type.isUSVString():
|
|
normalizeCode = fill(
|
|
"""
|
|
if (!NormalizeUSVString(${var})) {
|
|
JS_ReportOutOfMemory(cx);
|
|
$*{exceptionCode}
|
|
}
|
|
""",
|
|
var=varName,
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
|
|
conversionCode = fill(
|
|
"""
|
|
if (!ConvertJSValueToString(cx, $${val}, ${nullBehavior}, ${undefinedBehavior}, ${varName})) {
|
|
$*{exceptionCode}
|
|
}
|
|
$*{normalizeCode}
|
|
""",
|
|
nullBehavior=nullBehavior,
|
|
undefinedBehavior=undefinedBehavior,
|
|
varName=varName,
|
|
exceptionCode=exceptionCode,
|
|
normalizeCode=normalizeCode,
|
|
)
|
|
|
|
if defaultValue is None:
|
|
return conversionCode
|
|
|
|
if isinstance(defaultValue, IDLNullValue):
|
|
assert type.nullable()
|
|
defaultCode = "%s.SetIsVoid(true);\n" % varName
|
|
else:
|
|
defaultCode = handleDefaultStringValue(
|
|
defaultValue, "%s.AssignLiteral" % varName
|
|
)
|
|
return handleDefault(conversionCode, defaultCode)
|
|
|
|
if isMember:
|
|
# Convert directly into the ns[C]String member we have.
|
|
if type.isUTF8String():
|
|
declType = "nsCString"
|
|
else:
|
|
declType = "nsString"
|
|
return JSToNativeConversionInfo(
|
|
getConversionCode("${declName}"),
|
|
declType=CGGeneric(declType),
|
|
dealWithOptional=isOptional,
|
|
)
|
|
|
|
if isOptional:
|
|
if type.isUTF8String():
|
|
declType = "Optional<nsACString>"
|
|
holderType = CGGeneric("binding_detail::FakeString<char>")
|
|
else:
|
|
declType = "Optional<nsAString>"
|
|
holderType = CGGeneric("binding_detail::FakeString<char16_t>")
|
|
conversionCode = "%s" "${declName} = &${holderName};\n" % getConversionCode(
|
|
"${holderName}"
|
|
)
|
|
else:
|
|
if type.isUTF8String():
|
|
declType = "binding_detail::FakeString<char>"
|
|
else:
|
|
declType = "binding_detail::FakeString<char16_t>"
|
|
holderType = None
|
|
conversionCode = getConversionCode("${declName}")
|
|
|
|
# No need to deal with optional here; we handled it already
|
|
return JSToNativeConversionInfo(
|
|
conversionCode, declType=CGGeneric(declType), holderType=holderType
|
|
)
|
|
|
|
if type.isByteString():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
|
|
nullable = toStringBool(type.nullable())
|
|
|
|
conversionCode = fill(
|
|
"""
|
|
if (!ConvertJSValueToByteString(cx, $${val}, ${nullable}, "${sourceDescription}", $${declName})) {
|
|
$*{exceptionCode}
|
|
}
|
|
""",
|
|
nullable=nullable,
|
|
sourceDescription=sourceDescription,
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
|
|
if defaultValue is not None:
|
|
if isinstance(defaultValue, IDLNullValue):
|
|
assert type.nullable()
|
|
defaultCode = "${declName}.SetIsVoid(true);\n"
|
|
else:
|
|
defaultCode = handleDefaultStringValue(
|
|
defaultValue, "${declName}.AssignLiteral"
|
|
)
|
|
conversionCode = handleDefault(conversionCode, defaultCode)
|
|
|
|
return JSToNativeConversionInfo(
|
|
conversionCode, declType=CGGeneric("nsCString"), dealWithOptional=isOptional
|
|
)
|
|
|
|
if type.isEnum():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
|
|
enumName = type.unroll().inner.identifier.name
|
|
declType = CGGeneric(enumName)
|
|
if type.nullable():
|
|
declType = CGTemplatedType("Nullable", declType)
|
|
declType = declType.define()
|
|
enumLoc = "${declName}.SetValue()"
|
|
else:
|
|
enumLoc = "${declName}"
|
|
declType = declType.define()
|
|
|
|
if invalidEnumValueFatal:
|
|
handleInvalidEnumValueCode = "MOZ_ASSERT(index >= 0);\n"
|
|
else:
|
|
# invalidEnumValueFatal is false only for attributes. So we won't
|
|
# have a non-default exceptionCode here unless attribute "arg
|
|
# conversion" code starts passing in an exceptionCode. At which
|
|
# point we'll need to figure out what that even means.
|
|
assert exceptionCode == "return false;\n"
|
|
handleInvalidEnumValueCode = dedent(
|
|
"""
|
|
if (index < 0) {
|
|
return true;
|
|
}
|
|
"""
|
|
)
|
|
|
|
template = fill(
|
|
"""
|
|
{
|
|
int index;
|
|
if (!FindEnumStringIndex<${invalidEnumValueFatal}>(cx, $${val}, ${values}, "${enumtype}", "${sourceDescription}", &index)) {
|
|
$*{exceptionCode}
|
|
}
|
|
$*{handleInvalidEnumValueCode}
|
|
${enumLoc} = static_cast<${enumtype}>(index);
|
|
}
|
|
""",
|
|
enumtype=enumName,
|
|
values=enumName + "Values::" + ENUM_ENTRY_VARIABLE_NAME,
|
|
invalidEnumValueFatal=toStringBool(invalidEnumValueFatal),
|
|
handleInvalidEnumValueCode=handleInvalidEnumValueCode,
|
|
exceptionCode=exceptionCode,
|
|
enumLoc=enumLoc,
|
|
sourceDescription=sourceDescription,
|
|
)
|
|
|
|
setNull = "${declName}.SetNull();\n"
|
|
|
|
if type.nullable():
|
|
template = CGIfElseWrapper(
|
|
"${val}.isNullOrUndefined()", CGGeneric(setNull), CGGeneric(template)
|
|
).define()
|
|
|
|
if defaultValue is not None:
|
|
if isinstance(defaultValue, IDLNullValue):
|
|
assert type.nullable()
|
|
template = handleDefault(template, setNull)
|
|
else:
|
|
assert defaultValue.type.tag() == IDLType.Tags.domstring
|
|
template = handleDefault(
|
|
template,
|
|
(
|
|
"%s = %s::%s;\n"
|
|
% (enumLoc, enumName, getEnumValueName(defaultValue.value))
|
|
),
|
|
)
|
|
return JSToNativeConversionInfo(
|
|
template, declType=CGGeneric(declType), dealWithOptional=isOptional
|
|
)
|
|
|
|
if type.isCallback():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
assert not type.treatNonCallableAsNull() or type.nullable()
|
|
assert not type.treatNonObjectAsNull() or type.nullable()
|
|
assert not type.treatNonObjectAsNull() or not type.treatNonCallableAsNull()
|
|
|
|
callback = type.unroll().callback
|
|
name = callback.identifier.name
|
|
(declType, declArgs, conversion) = getCallbackConversionInfo(
|
|
type, callback, isMember, isCallbackReturnValue, isOptional
|
|
)
|
|
|
|
if allowTreatNonCallableAsNull and type.treatNonCallableAsNull():
|
|
haveCallable = "JS::IsCallable(&${val}.toObject())"
|
|
if not isDefinitelyObject:
|
|
haveCallable = "${val}.isObject() && " + haveCallable
|
|
if defaultValue is not None:
|
|
assert isinstance(defaultValue, IDLNullValue)
|
|
haveCallable = "(${haveValue}) && " + haveCallable
|
|
template = (
|
|
("if (%s) {\n" % haveCallable) + conversion + "} else {\n"
|
|
" ${declName} = nullptr;\n"
|
|
"}\n"
|
|
)
|
|
elif allowTreatNonCallableAsNull and type.treatNonObjectAsNull():
|
|
if not isDefinitelyObject:
|
|
haveObject = "${val}.isObject()"
|
|
if defaultValue is not None:
|
|
assert isinstance(defaultValue, IDLNullValue)
|
|
haveObject = "(${haveValue}) && " + haveObject
|
|
template = CGIfElseWrapper(
|
|
haveObject,
|
|
CGGeneric(conversion),
|
|
CGGeneric("${declName} = nullptr;\n"),
|
|
).define()
|
|
else:
|
|
template = conversion
|
|
else:
|
|
template = wrapObjectTemplate(
|
|
"if (JS::IsCallable(&${val}.toObject())) {\n"
|
|
+ conversion
|
|
+ "} else {\n"
|
|
+ indent(onFailureNotCallable(failureCode).define())
|
|
+ "}\n",
|
|
type,
|
|
"${declName} = nullptr;\n",
|
|
failureCode,
|
|
)
|
|
return JSToNativeConversionInfo(
|
|
template, declType=declType, declArgs=declArgs, dealWithOptional=isOptional
|
|
)
|
|
|
|
if type.isAny():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
|
|
declArgs = None
|
|
if isMember in ("Variadic", "Sequence", "Dictionary", "Record"):
|
|
# Rooting is handled by the sequence and dictionary tracers.
|
|
declType = "JS::Value"
|
|
else:
|
|
assert not isMember
|
|
declType = "JS::Rooted<JS::Value>"
|
|
declArgs = "cx"
|
|
|
|
assert not isOptional
|
|
templateBody = "${declName} = ${val};\n"
|
|
|
|
# For JS-implemented APIs, we refuse to allow passing objects that the
|
|
# API consumer does not subsume. The extra parens around
|
|
# ($${passedToJSImpl}) suppress unreachable code warnings when
|
|
# $${passedToJSImpl} is the literal `false`. But Apple is shipping a
|
|
# buggy clang (clang 3.9) in Xcode 8.3, so there even the parens are not
|
|
# enough. So we manually disable some warnings in clang.
|
|
if (
|
|
not isinstance(descriptorProvider, Descriptor)
|
|
or descriptorProvider.interface.isJSImplemented()
|
|
):
|
|
templateBody = (
|
|
fill(
|
|
"""
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wunreachable-code"
|
|
#pragma clang diagnostic ignored "-Wunreachable-code-return"
|
|
#endif // __clang__
|
|
if (($${passedToJSImpl}) && !CallerSubsumes($${val})) {
|
|
cx.ThrowErrorMessage<MSG_PERMISSION_DENIED_TO_PASS_ARG>("${sourceDescription}");
|
|
$*{exceptionCode}
|
|
}
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif // __clang__
|
|
""",
|
|
sourceDescription=sourceDescription,
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
+ templateBody
|
|
)
|
|
|
|
# We may not have a default value if we're being converted for
|
|
# a setter, say.
|
|
if defaultValue:
|
|
if isinstance(defaultValue, IDLNullValue):
|
|
defaultHandling = "${declName} = JS::NullValue();\n"
|
|
else:
|
|
assert isinstance(defaultValue, IDLUndefinedValue)
|
|
defaultHandling = "${declName} = JS::UndefinedValue();\n"
|
|
templateBody = handleDefault(templateBody, defaultHandling)
|
|
return JSToNativeConversionInfo(
|
|
templateBody, declType=CGGeneric(declType), declArgs=declArgs
|
|
)
|
|
|
|
if type.isObject():
|
|
assert not isEnforceRange and not isClamp and not isAllowShared
|
|
return handleJSObjectType(
|
|
type, isMember, failureCode, exceptionCode, sourceDescription
|
|
)
|
|
|
|
if type.isDictionary():
|
|
# There are no nullable dictionary-typed arguments or dictionary-typed
|
|
# dictionary members.
|
|
assert (
|
|
not type.nullable()
|
|
or isCallbackReturnValue
|
|
or (isMember and isMember != "Dictionary")
|
|
)
|
|
# All optional dictionary-typed arguments always have default values,
|
|
# but dictionary-typed dictionary members can be optional.
|
|
assert not isOptional or isMember == "Dictionary"
|
|
# In the callback return value case we never have to worry
|
|
# about a default value; we always have a value.
|
|
assert not isCallbackReturnValue or defaultValue is None
|
|
|
|
typeName = CGDictionary.makeDictionaryName(type.unroll().inner)
|
|
if not isMember and not isCallbackReturnValue:
|
|
# Since we're not a member and not nullable or optional, no one will
|
|
# see our real type, so we can do the fast version of the dictionary
|
|
# that doesn't pre-initialize members.
|
|
typeName = "binding_detail::Fast" + typeName
|
|
|
|
declType = CGGeneric(typeName)
|
|
|
|
# We do manual default value handling here, because we actually do want
|
|
# a jsval, and we only handle the default-dictionary case (which we map
|
|
# into initialization with the JS value `null`) anyway
|
|
# NOTE: if isNullOrUndefined or isDefinitelyObject are true,
|
|
# we know we have a value, so we don't have to worry about the
|
|
# default value.
|
|
if (
|
|
not isNullOrUndefined
|
|
and not isDefinitelyObject
|
|
and defaultValue is not None
|
|
):
|
|
assert isinstance(defaultValue, IDLDefaultDictionaryValue)
|
|
# Initializing from JS null does the right thing to give
|
|
# us a default-initialized dictionary.
|
|
val = "(${haveValue}) ? ${val} : JS::NullHandleValue"
|
|
else:
|
|
val = "${val}"
|
|
|
|
dictLoc = "${declName}"
|
|
if type.nullable():
|
|
dictLoc += ".SetValue()"
|
|
|
|
if type.unroll().inner.needsConversionFromJS:
|
|
args = "cx, %s, " % val
|
|
else:
|
|
# We can end up in this case if a dictionary that does not need
|
|
# conversion from JS has a dictionary-typed member with a default
|
|
# value of {}.
|
|
args = ""
|
|
conversionCode = fill(
|
|
"""
|
|
if (!${dictLoc}.Init(${args}"${desc}", $${passedToJSImpl})) {
|
|
$*{exceptionCode}
|
|
}
|
|
""",
|
|
dictLoc=dictLoc,
|
|
args=args,
|
|
desc=firstCap(sourceDescription),
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
|
|
if failureCode is not None:
|
|
# This means we're part of an overload or union conversion, and
|
|
# should simply skip stuff if our value is not convertible to
|
|
# dictionary, instead of trying and throwing. If we're either
|
|
# isDefinitelyObject or isNullOrUndefined then we're convertible to
|
|
# dictionary and don't need to check here.
|
|
if isDefinitelyObject or isNullOrUndefined:
|
|
template = conversionCode
|
|
else:
|
|
template = fill(
|
|
"""
|
|
if (!IsConvertibleToDictionary(${val})) {
|
|
$*{failureCode}
|
|
}
|
|
$*{conversionCode}
|
|
""",
|
|
val=val,
|
|
failureCode=failureCode,
|
|
conversionCode=conversionCode,
|
|
)
|
|
else:
|
|
template = conversionCode
|
|
|
|
if type.nullable():
|
|
declType = CGTemplatedType("Nullable", declType)
|
|
template = CGIfElseWrapper(
|
|
"${val}.isNullOrUndefined()",
|
|
CGGeneric("${declName}.SetNull();\n"),
|
|
CGGeneric(template),
|
|
).define()
|
|
|
|
# Dictionary arguments that might contain traceable things need to get
|
|
# traced
|
|
if not isMember and isCallbackReturnValue:
|
|
# Go ahead and just convert directly into our actual return value
|
|
declType = CGWrapper(declType, post="&")
|
|
declArgs = "aRetVal"
|
|
elif not isMember and typeNeedsRooting(type):
|
|
declType = CGTemplatedType("RootedDictionary", declType)
|
|
declArgs = "cx"
|
|
else:
|
|
declArgs = None
|
|
|
|
return JSToNativeConversionInfo(
|
|
template, declType=declType, declArgs=declArgs, dealWithOptional=isOptional
|
|
)
|
|
|
|
if type.isVoid():
|
|
assert not isOptional
|
|
# This one only happens for return values, and its easy: Just
|
|
# ignore the jsval.
|
|
return JSToNativeConversionInfo("")
|
|
|
|
if not type.isPrimitive():
|
|
raise TypeError("Need conversion for argument type '%s'" % str(type))
|
|
|
|
typeName = builtinNames[type.tag()]
|
|
|
|
conversionBehavior = "eDefault"
|
|
if isEnforceRange:
|
|
assert type.isInteger()
|
|
conversionBehavior = "eEnforceRange"
|
|
elif isClamp:
|
|
assert type.isInteger()
|
|
conversionBehavior = "eClamp"
|
|
|
|
alwaysNull = False
|
|
if type.nullable():
|
|
declType = CGGeneric("Nullable<" + typeName + ">")
|
|
writeLoc = "${declName}.SetValue()"
|
|
readLoc = "${declName}.Value()"
|
|
nullCondition = "${val}.isNullOrUndefined()"
|
|
if defaultValue is not None and isinstance(defaultValue, IDLNullValue):
|
|
nullCondition = "!(${haveValue}) || " + nullCondition
|
|
if isKnownMissing:
|
|
alwaysNull = True
|
|
template = dedent(
|
|
"""
|
|
${declName}.SetNull();
|
|
"""
|
|
)
|
|
if not alwaysNull:
|
|
template = fill(
|
|
"""
|
|
if (${nullCondition}) {
|
|
$${declName}.SetNull();
|
|
} else if (!ValueToPrimitive<${typeName}, ${conversionBehavior}>(cx, $${val}, "${sourceDescription}", &${writeLoc})) {
|
|
$*{exceptionCode}
|
|
}
|
|
""",
|
|
nullCondition=nullCondition,
|
|
typeName=typeName,
|
|
conversionBehavior=conversionBehavior,
|
|
sourceDescription=firstCap(sourceDescription),
|
|
writeLoc=writeLoc,
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
else:
|
|
assert defaultValue is None or not isinstance(defaultValue, IDLNullValue)
|
|
writeLoc = "${declName}"
|
|
readLoc = writeLoc
|
|
template = fill(
|
|
"""
|
|
if (!ValueToPrimitive<${typeName}, ${conversionBehavior}>(cx, $${val}, "${sourceDescription}", &${writeLoc})) {
|
|
$*{exceptionCode}
|
|
}
|
|
""",
|
|
typeName=typeName,
|
|
conversionBehavior=conversionBehavior,
|
|
sourceDescription=firstCap(sourceDescription),
|
|
writeLoc=writeLoc,
|
|
exceptionCode=exceptionCode,
|
|
)
|
|
declType = CGGeneric(typeName)
|
|
|
|
if type.isFloat() and not type.isUnrestricted() and not alwaysNull:
|
|
if lenientFloatCode is not None:
|
|
nonFiniteCode = lenientFloatCode
|
|
else:
|
|
nonFiniteCode = 'cx.ThrowErrorMessage<MSG_NOT_FINITE>("%s");\n' "%s" % (
|
|
firstCap(sourceDescription),
|
|
exceptionCode,
|
|
)
|
|
|
|
# We're appending to an if-block brace, so strip trailing whitespace
|
|
# and add an extra space before the else.
|
|
template = template.rstrip()
|
|
template += fill(
|
|
"""
|
|
else if (!mozilla::IsFinite(${readLoc})) {
|
|
$*{nonFiniteCode}
|
|
}
|
|
""",
|
|
readLoc=readLoc,
|
|
nonFiniteCode=nonFiniteCode,
|
|
)
|
|
|
|
if (
|
|
defaultValue is not None
|
|
and
|
|
# We already handled IDLNullValue, so just deal with the other ones
|
|
not isinstance(defaultValue, IDLNullValue)
|
|
):
|
|
tag = defaultValue.type.tag()
|
|
defaultStr = getHandleDefault(defaultValue)
|
|
template = handleDefault(template, "%s = %s;\n" % (writeLoc, defaultStr))
|
|
|
|
return JSToNativeConversionInfo(
|
|
template, declType=declType, dealWithOptional=isOptional
|
|
)
|
|
|
|
|
|
def instantiateJSToNativeConversion(info, replacements, checkForValue=False):
|
|
"""
|
|
Take a JSToNativeConversionInfo as returned by getJSToNativeConversionInfo
|
|
and a set of replacements as required by the strings in such an object, and
|
|
generate code to convert into stack C++ types.
|
|
|
|
If checkForValue is True, then the conversion will get wrapped in
|
|
a check for ${haveValue}.
|
|
"""
|
|
templateBody, declType, holderType, dealWithOptional = (
|
|
info.template,
|
|
info.declType,
|
|
info.holderType,
|
|
info.dealWithOptional,
|
|
)
|
|
|
|
if dealWithOptional and not checkForValue:
|
|
raise TypeError("Have to deal with optional things, but don't know how")
|
|
if checkForValue and declType is None:
|
|
raise TypeError(
|
|
"Need to predeclare optional things, so they will be "
|
|
"outside the check for big enough arg count!"
|
|
)
|
|
|
|
# We can't precompute our holder constructor arguments, since
|
|
# those might depend on ${declName}, which we change below. Just
|
|
# compute arguments at the point when we need them as we go.
|
|
def getArgsCGThing(args):
|
|
return CGGeneric(string.Template(args).substitute(replacements))
|
|
|
|
result = CGList([])
|
|
# Make a copy of "replacements" since we may be about to start modifying it
|
|
replacements = dict(replacements)
|
|
originalDeclName = replacements["declName"]
|
|
if declType is not None:
|
|
if dealWithOptional:
|
|
replacements["declName"] = "%s.Value()" % originalDeclName
|
|
declType = CGTemplatedType("Optional", declType)
|
|
declCtorArgs = None
|
|
elif info.declArgs is not None:
|
|
declCtorArgs = CGWrapper(getArgsCGThing(info.declArgs), pre="(", post=")")
|
|
else:
|
|
declCtorArgs = None
|
|
result.append(
|
|
CGList(
|
|
[
|
|
declType,
|
|
CGGeneric(" "),
|
|
CGGeneric(originalDeclName),
|
|
declCtorArgs,
|
|
CGGeneric(";\n"),
|
|
]
|
|
)
|
|
)
|
|
|
|
originalHolderName = replacements["holderName"]
|
|
if holderType is not None:
|
|
if dealWithOptional:
|
|
replacements["holderName"] = "%s.ref()" % originalHolderName
|
|
holderType = CGTemplatedType("Maybe", holderType)
|
|
holderCtorArgs = None
|
|
elif info.holderArgs is not None:
|
|
holderCtorArgs = CGWrapper(
|
|
getArgsCGThing(info.holderArgs), pre="(", post=")"
|
|
)
|
|
else:
|
|
holderCtorArgs = None
|
|
result.append(
|
|
CGList(
|
|
[
|
|
holderType,
|
|
CGGeneric(" "),
|
|
CGGeneric(originalHolderName),
|
|
holderCtorArgs,
|
|
CGGeneric(";\n"),
|
|
]
|
|
)
|
|
)
|
|
|
|
if "maybeMutableVal" not in replacements:
|
|
replacements["maybeMutableVal"] = replacements["val"]
|
|
|
|
conversion = CGGeneric(string.Template(templateBody).substitute(replacements))
|
|
|
|
if checkForValue:
|
|
if dealWithOptional:
|
|
declConstruct = CGIndenter(
|
|
CGGeneric(
|
|
"%s.Construct(%s);\n"
|
|
% (
|
|
originalDeclName,
|
|
getArgsCGThing(info.declArgs).define() if info.declArgs else "",
|
|
)
|
|
)
|
|
)
|
|
if holderType is not None:
|
|
holderConstruct = CGIndenter(
|
|
CGGeneric(
|
|
"%s.emplace(%s);\n"
|
|
% (
|
|
originalHolderName,
|
|
getArgsCGThing(info.holderArgs).define()
|
|
if info.holderArgs
|
|
else "",
|
|
)
|
|
)
|
|
)
|
|
else:
|
|
holderConstruct = None
|
|
else:
|
|
declConstruct = None
|
|
holderConstruct = None
|
|
|
|
conversion = CGList(
|
|
[
|
|
CGGeneric(
|
|
string.Template("if (${haveValue}) {\n").substitute(replacements)
|
|
),
|
|
declConstruct,
|
|
holderConstruct,
|
|
CGIndenter(conversion),
|
|
CGGeneric("}\n"),
|
|
]
|
|
)
|
|
|
|
result.append(conversion)
|
|
return result
|
|
|
|
|
|
def convertConstIDLValueToJSVal(value):
|
|
if isinstance(value, IDLNullValue):
|
|
return "JS::NullValue()"
|
|
if isinstance(value, IDLUndefinedValue):
|
|
return "JS::UndefinedValue()"
|
|
tag = value.type.tag()
|
|
if tag in [
|
|
IDLType.Tags.int8,
|
|
IDLType.Tags.uint8,
|
|
IDLType.Tags.int16,
|
|
IDLType.Tags.uint16,
|
|
IDLType.Tags.int32,
|
|
]:
|
|
return "JS::Int32Value(%s)" % (value.value)
|
|
if tag == IDLType.Tags.uint32:
|
|
return "JS::NumberValue(%sU)" % (value.value)
|
|
if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]:
|
|
return "JS::CanonicalizedDoubleValue(%s)" % numericValue(tag, value.value)
|
|
if tag == IDLType.Tags.bool:
|
|
return "JS::BooleanValue(%s)" % (toStringBool(value.value))
|
|
if tag in [IDLType.Tags.float, IDLType.Tags.double]:
|
|
return "JS::CanonicalizedDoubleValue(%s)" % (value.value)
|
|
raise TypeError("Const value of unhandled type: %s" % value.type)
|
|
|
|
|
|
class CGArgumentConverter(CGThing):
|
|
"""
|
|
A class that takes an IDL argument object and its index in the
|
|
argument list and generates code to unwrap the argument to the
|
|
right native type.
|
|
|
|
argDescription is a description of the argument for error-reporting
|
|
purposes. Callers should assume that it might get placed in the middle of a
|
|
sentence. If it ends up at the beginning of a sentence, its first character
|
|
will be automatically uppercased.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
argument,
|
|
index,
|
|
descriptorProvider,
|
|
argDescription,
|
|
member,
|
|
invalidEnumValueFatal=True,
|
|
lenientFloatCode=None,
|
|
):
|
|
CGThing.__init__(self)
|
|
self.argument = argument
|
|
self.argDescription = argDescription
|
|
assert not argument.defaultValue or argument.optional
|
|
|
|
replacer = {"index": index, "argc": "args.length()"}
|
|
self.replacementVariables = {
|
|
"declName": "arg%d" % index,
|
|
"holderName": ("arg%d" % index) + "_holder",
|
|
"obj": "obj",
|
|
"passedToJSImpl": toStringBool(
|
|
isJSImplementedDescriptor(descriptorProvider)
|
|
),
|
|
}
|
|
# If we have a method generated by the maplike/setlike portion of an
|
|
# interface, arguments can possibly be undefined, but will need to be
|
|
# converted to the key/value type of the backing object. In this case,
|
|
# use .get() instead of direct access to the argument. This won't
|
|
# matter for iterable since generated functions for those interface
|
|
# don't take arguments.
|
|
if member.isMethod() and member.isMaplikeOrSetlikeOrIterableMethod():
|
|
self.replacementVariables["val"] = string.Template(
|
|
"args.get(${index})"
|
|
).substitute(replacer)
|
|
self.replacementVariables["maybeMutableVal"] = string.Template(
|
|
"args[${index}]"
|
|
).substitute(replacer)
|
|
else:
|
|
self.replacementVariables["val"] = string.Template(
|
|
"args[${index}]"
|
|
).substitute(replacer)
|
|
haveValueCheck = string.Template("args.hasDefined(${index})").substitute(
|
|
replacer
|
|
)
|
|
self.replacementVariables["haveValue"] = haveValueCheck
|
|
self.descriptorProvider = descriptorProvider
|
|
if self.argument.canHaveMissingValue():
|
|
self.argcAndIndex = replacer
|
|
else:
|
|
self.argcAndIndex = None
|
|
self.invalidEnumValueFatal = invalidEnumValueFatal
|
|
self.lenientFloatCode = lenientFloatCode
|
|
|
|
def define(self):
|
|
typeConversion = getJSToNativeConversionInfo(
|
|
self.argument.type,
|
|
self.descriptorProvider,
|
|
isOptional=(self.argcAndIndex is not None and not self.argument.variadic),
|
|
invalidEnumValueFatal=self.invalidEnumValueFatal,
|
|
defaultValue=self.argument.defaultValue,
|
|
lenientFloatCode=self.lenientFloatCode,
|
|
isMember="Variadic" if self.argument.variadic else False,
|
|
allowTreatNonCallableAsNull=self.argument.allowTreatNonCallableAsNull(),
|
|
sourceDescription=self.argDescription,
|
|
)
|
|
|
|
if not self.argument.variadic:
|
|
return instantiateJSToNativeConversion(
|
|
typeConversion, self.replacementVariables, self.argcAndIndex is not None
|
|
).define()
|
|
|
|
# Variadic arguments get turned into a sequence.
|
|
if typeConversion.dealWithOptional:
|
|
raise TypeError("Shouldn't have optional things in variadics")
|
|
if typeConversion.holderType is not None:
|
|
raise TypeError("Shouldn't need holders for variadics")
|
|
|
|
replacer = dict(self.argcAndIndex, **self.replacementVariables)
|
|
replacer["seqType"] = CGTemplatedType(
|
|
"AutoSequence", typeConversion.declType
|
|
).define()
|
|
if typeNeedsRooting(self.argument.type):
|
|
rooterDecl = (
|
|
"SequenceRooter<%s> ${holderName}(cx, &${declName});\n"
|
|
% typeConversion.declType.define()
|
|
)
|
|
else:
|
|
rooterDecl = ""
|
|
replacer["elemType"] = typeConversion.declType.define()
|
|
|
|
replacer["elementInitializer"] = initializerForType(self.argument.type) or ""
|
|
|
|
# NOTE: Keep this in sync with sequence conversions as needed
|
|
variadicConversion = string.Template(
|
|
"${seqType} ${declName};\n"
|
|
+ rooterDecl
|
|
+ dedent(
|
|
"""
|
|
if (${argc} > ${index}) {
|
|
if (!${declName}.SetCapacity(${argc} - ${index}, mozilla::fallible)) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
for (uint32_t variadicArg = ${index}; variadicArg < ${argc}; ++variadicArg) {
|
|
// OK to do infallible append here, since we ensured capacity already.
|
|
${elemType}& slot = *${declName}.AppendElement(${elementInitializer});
|
|
"""
|
|
)
|
|
).substitute(replacer)
|
|
|
|
val = string.Template("args[variadicArg]").substitute(replacer)
|
|
variadicConversion += indent(
|
|
string.Template(typeConversion.template).substitute(
|
|
{
|
|
"val": val,
|
|
"maybeMutableVal": val,
|
|
"declName": "slot",
|
|
# We only need holderName here to handle isExternal()
|
|
# interfaces, which use an internal holder for the
|
|
# conversion even when forceOwningType ends up true.
|
|
"holderName": "tempHolder",
|
|
# Use the same ${obj} as for the variadic arg itself
|
|
"obj": replacer["obj"],
|
|
"passedToJSImpl": toStringBool(
|
|
isJSImplementedDescriptor(self.descriptorProvider)
|
|
),
|
|
}
|
|
),
|
|
4,
|
|
)
|
|
|
|
variadicConversion += " }\n" "}\n"
|
|
return variadicConversion
|
|
|
|
|
|
def getMaybeWrapValueFuncForType(type):
|
|
if type.isJSString():
|
|
return "MaybeWrapStringValue"
|
|
# Callbacks might actually be DOM objects; nothing prevents a page from
|
|
# doing that.
|
|
if type.isCallback() or type.isCallbackInterface() or type.isObject():
|
|
if type.nullable():
|
|
return "MaybeWrapObjectOrNullValue"
|
|
return "MaybeWrapObjectValue"
|
|
# SpiderMonkey interfaces are never DOM objects. Neither are sequences or
|
|
# dictionaries, since those are always plain JS objects.
|
|
if type.isSpiderMonkeyInterface() or type.isDictionary() or type.isSequence():
|
|
if type.nullable():
|
|
return "MaybeWrapNonDOMObjectOrNullValue"
|
|
return "MaybeWrapNonDOMObjectValue"
|
|
if type.isAny():
|
|
return "MaybeWrapValue"
|
|
|
|
# For other types, just go ahead an fall back on MaybeWrapValue for now:
|
|
# it's always safe to do, and shouldn't be particularly slow for any of
|
|
# them
|
|
return "MaybeWrapValue"
|
|
|
|
|
|
sequenceWrapLevel = 0
|
|
recordWrapLevel = 0
|
|
|
|
|
|
def getWrapTemplateForType(
|
|
type,
|
|
descriptorProvider,
|
|
result,
|
|
successCode,
|
|
returnsNewObject,
|
|
exceptionCode,
|
|
spiderMonkeyInterfacesAreStructs,
|
|
isConstructorRetval=False,
|
|
):
|
|
"""
|
|
Reflect a C++ value stored in "result", of IDL type "type" into JS. The
|
|
"successCode" is the code to run once we have successfully done the
|
|
conversion and must guarantee that execution of the conversion template
|
|
stops once the successCode has executed (e.g. by doing a 'return', or by
|
|
doing a 'break' if the entire conversion template is inside a block that
|
|
the 'break' will exit).
|
|
|
|
If spiderMonkeyInterfacesAreStructs is true, then if the type is a
|
|
SpiderMonkey interface, "result" is one of the
|
|
dom::SpiderMonkeyInterfaceObjectStorage subclasses, not a JSObject*.
|
|
|
|
The resulting string should be used with string.Template. It
|
|
needs the following keys when substituting:
|
|
|
|
jsvalHandle: something that can be passed to methods taking a
|
|
JS::MutableHandle<JS::Value>. This can be a
|
|
JS::MutableHandle<JS::Value> or a JS::Rooted<JS::Value>*.
|
|
jsvalRef: something that can have .address() called on it to get a
|
|
JS::Value* and .set() called on it to set it to a JS::Value.
|
|
This can be a JS::MutableHandle<JS::Value> or a
|
|
JS::Rooted<JS::Value>.
|
|
obj: a JS::Handle<JSObject*>.
|
|
|
|
Returns (templateString, infallibility of conversion template)
|
|
"""
|
|
if successCode is None:
|
|
successCode = "return true;\n"
|
|
|
|
def setUndefined():
|
|
return _setValue("", setter="setUndefined")
|
|
|
|
def setNull():
|
|
return _setValue("", setter="setNull")
|
|
|
|
def setInt32(value):
|
|
return _setValue(value, setter="setInt32")
|
|
|
|
def setString(value):
|
|
return _setValue(value, wrapAsType=type, setter="setString")
|
|
|
|
def setObject(value, wrapAsType=None):
|
|
return _setValue(value, wrapAsType=wrapAsType, setter="setObject")
|
|
|
|
def setObjectOrNull(value, wrapAsType=None):
|
|
return _setValue(value, wrapAsType=wrapAsType, setter="setObjectOrNull")
|
|
|
|
def setUint32(value):
|
|
return _setValue(value, setter="setNumber")
|
|
|
|
def setDouble(value):
|
|
return _setValue("JS_NumberValue(%s)" % value)
|
|
|
|
def setBoolean(value):
|
|
return _setValue(value, setter="setBoolean")
|
|
|
|
def _setValue(value, wrapAsType=None, setter="set"):
|
|
"""
|
|
Returns the code to set the jsval to value.
|
|
|
|
If wrapAsType is not None, then will wrap the resulting value using the
|
|
function that getMaybeWrapValueFuncForType(wrapAsType) returns.
|
|
Otherwise, no wrapping will be done.
|
|
"""
|
|
if wrapAsType is None:
|
|
tail = successCode
|
|
else:
|
|
tail = fill(
|
|
"""
|
|
if (!${maybeWrap}(cx, $${jsvalHandle})) {
|
|
$*{exceptionCode}
|
|
}
|
|
$*{successCode}
|
|
""",
|
|
maybeWrap=getMaybeWrapValueFuncForType(wrapAsType),
|
|
exceptionCode=exceptionCode,
|
|
successCode=successCode,
|
|
)
|
|
return ("${jsvalRef}.%s(%s);\n" % (setter, value)) + tail
|
|
|
|
def wrapAndSetPtr(wrapCall, failureCode=None):
|
|
"""
|
|
Returns the code to set the jsval by calling "wrapCall". "failureCode"
|
|
is the code to run if calling "wrapCall" fails
|
|
"""
|
|
if failureCode is None:
|
|
failureCode = exceptionCode
|
|
return fill(
|
|
"""
|
|
if (!${wrapCall}) {
|
|
$*{failureCode}
|
|
}
|
|
$*{successCode}
|
|
""",
|
|
wrapCall=wrapCall,
|
|
failureCode=failureCode,
|
|
successCode=successCode,
|
|
)
|
|
|
|
if type is None or type.isVoid():
|
|
return (setUndefined(), True)
|
|
|
|
if (type.isSequence() or type.isRecord()) and type.nullable():
|
|
# These are both wrapped in Nullable<>
|
|
recTemplate, recInfall = getWrapTemplateForType(
|
|
type.inner,
|
|
descriptorProvider,
|
|
"%s.Value()" % result,
|
|
successCode,
|
|
returnsNewObject,
|
|
exceptionCode,
|
|
spiderMonkeyInterfacesAreStructs,
|
|
)
|
|
code = fill(
|
|
"""
|
|
|
|
if (${result}.IsNull()) {
|
|
$*{setNull}
|
|
}
|
|
$*{recTemplate}
|
|
""",
|
|
result=result,
|
|
setNull=setNull(),
|
|
recTemplate=recTemplate,
|
|
)
|
|
return code, recInfall
|
|
|
|
if type.isSequence():
|
|
# Now do non-nullable sequences. Our success code is just to break to
|
|
# where we set the element in the array. Note that we bump the
|
|
# sequenceWrapLevel around this call so that nested sequence conversions
|
|
# will use different iteration variables.
|
|
global sequenceWrapLevel
|
|
index = "sequenceIdx%d" % sequenceWrapLevel
|
|
sequenceWrapLevel += 1
|
|
innerTemplate = wrapForType(
|
|
type.inner,
|
|
descriptorProvider,
|
|
{
|
|
"result": "%s[%s]" % (result, index),
|
|
"successCode": "break;\n",
|
|
"jsvalRef": "tmp",
|
|
"jsvalHandle": "&tmp",
|
|
"returnsNewObject": returnsNewObject,
|
|
"exceptionCode": exceptionCode,
|
|
"obj": "returnArray",
|
|
"spiderMonkeyInterfacesAreStructs": spiderMonkeyInterfacesAreStructs,
|
|
},
|
|
)
|
|
sequenceWrapLevel -= 1
|
|
code = fill(
|
|
"""
|
|
|
|
uint32_t length = ${result}.Length();
|
|
JS::Rooted<JSObject*> returnArray(cx, JS::NewArrayObject(cx, length));
|
|
if (!returnArray) {
|
|
$*{exceptionCode}
|
|
}
|
|
// Scope for 'tmp'
|
|
{
|
|
JS::Rooted<JS::Value> tmp(cx);
|
|
for (uint32_t ${index} = 0; ${index} < length; ++${index}) {
|
|
// Control block to let us common up the JS_DefineElement calls when there
|
|
// are different ways to succeed at wrapping the object.
|
|
do {
|
|
$*{innerTemplate}
|
|
} while (false);
|
|
if (!JS_DefineElement(cx, returnArray, ${index}, tmp,
|
|
JSPROP_ENUMERATE)) {
|
|
$*{exceptionCode}
|
|
}
|
|
}
|
|
}
|
|
$*{set}
|
|
""",
|
|
result=result,
|
|
exceptionCode=exceptionCode,
|
|
index=index,
|
|
innerTemplate=innerTemplate,
|
|
set=setObject("*returnArray"),
|
|
)
|
|
|
|
return (code, False)
|
|
|
|
if type.isRecord():
|
|
# Now do non-nullable record. Our success code is just to break to
|
|
# where we define the property on the object. Note that we bump the
|
|
# recordWrapLevel around this call so that nested record conversions
|
|
# will use different temp value names.
|
|
global recordWrapLevel
|
|
valueName = "recordValue%d" % recordWrapLevel
|
|
recordWrapLevel += 1
|
|
innerTemplate = wrapForType(
|
|
type.inner,
|
|
descriptorProvider,
|
|
{
|
|
"result": valueName,
|
|
"successCode": "break;\n",
|
|
"jsvalRef": "tmp",
|
|
"jsvalHandle": "&tmp",
|
|
"returnsNewObject": returnsNewObject,
|
|
"exceptionCode": exceptionCode,
|
|
"obj": "returnObj",
|
|
"spiderMonkeyInterfacesAreStructs": spiderMonkeyInterfacesAreStructs,
|
|
},
|
|
)
|
|
recordWrapLevel -= 1
|
|
if type.keyType.isByteString():
|
|
# There is no length-taking JS_DefineProperty. So to keep
|
|
# things sane with embedded nulls, we want to byte-inflate
|
|
# to an nsAString. The only byte-inflation function we
|
|
# have around is AppendASCIItoUTF16, which luckily doesn't
|
|
# assert anything about the input being ASCII.
|
|
expandedKeyDecl = "NS_ConvertASCIItoUTF16 expandedKey(entry.mKey);\n"
|
|
keyName = "expandedKey"
|
|
elif type.keyType.isUTF8String():
|
|
# We do the same as above for utf8 strings. We could do better if
|
|
# we had a DefineProperty API that takes utf-8 property names.
|
|
expandedKeyDecl = "NS_ConvertUTF8toUTF16 expandedKey(entry.mKey);\n"
|
|
keyName = "expandedKey"
|
|
else:
|
|
expandedKeyDecl = ""
|
|
keyName = "entry.mKey"
|
|
|
|
code = fill(
|
|
"""
|
|
|
|
JS::Rooted<JSObject*> returnObj(cx, JS_NewPlainObject(cx));
|
|
if (!returnObj) {
|
|
$*{exceptionCode}
|
|
}
|
|
// Scope for 'tmp'
|
|
{
|
|
JS::Rooted<JS::Value> tmp(cx);
|
|
for (auto& entry : ${result}.Entries()) {
|
|
auto& ${valueName} = entry.mValue;
|
|
// Control block to let us common up the JS_DefineUCProperty calls when there
|
|
// are different ways to succeed at wrapping the value.
|
|
do {
|
|
$*{innerTemplate}
|
|
} while (false);
|
|
$*{expandedKeyDecl}
|
|
if (!JS_DefineUCProperty(cx, returnObj,
|
|
${keyName}.BeginReading(),
|
|
${keyName}.Length(), tmp,
|
|
JSPROP_ENUMERATE)) {
|
|
$*{exceptionCode}
|
|
}
|
|
}
|
|
}
|
|
$*{set}
|
|
""",
|
|
result=result,
|
|
exceptionCode=exceptionCode,
|
|
valueName=valueName,
|
|
innerTemplate=innerTemplate,
|
|
expandedKeyDecl=expandedKeyDecl,
|
|
keyName=keyName,
|
|
set=setObject("*returnObj"),
|
|
)
|
|
|
|
return (code, False)
|
|
|
|
if type.isPromise():
|
|
assert not type.nullable()
|
|
# The use of ToJSValue here is a bit annoying because the Promise
|
|
# version is not inlined. But we can't put an inline version in either
|
|
# ToJSValue.h or BindingUtils.h, because Promise.h includes ToJSValue.h
|
|
# and that includes BindingUtils.h, so we'd get an include loop if
|
|
# either of those headers included Promise.h. And trying to write the
|
|
# conversion by hand here is pretty annoying because we have to handle
|
|
# the various RefPtr, rawptr, NonNull, etc cases, which ToJSValue will
|
|
# handle for us. So just eat the cost of the function call.
|
|
return (wrapAndSetPtr("ToJSValue(cx, %s, ${jsvalHandle})" % result), False)
|
|
|
|
if type.isGeckoInterface() and not type.isCallbackInterface():
|
|
descriptor = descriptorProvider.getDescriptor(
|
|
type.unroll().inner.identifier.name
|
|
)
|
|
if type.nullable():
|
|
if descriptor.interface.identifier.name == "WindowProxy":
|
|
template, infal = getWrapTemplateForType(
|
|
type.inner,
|
|
descriptorProvider,
|
|
"%s.Value()" % result,
|
|
successCode,
|
|
returnsNewObject,
|
|
exceptionCode,
|
|
spiderMonkeyInterfacesAreStructs,
|
|
)
|
|
return (
|
|
"if (%s.IsNull()) {\n" % result
|
|
+ indent(setNull())
|
|
+ "}\n"
|
|
+ template,
|
|
infal,
|
|
)
|
|
|
|
wrappingCode = "if (!%s) {\n" % (result) + indent(setNull()) + "}\n"
|
|
else:
|
|
wrappingCode = ""
|
|
|
|
if not descriptor.interface.isExternal():
|
|
if descriptor.wrapperCache:
|
|
wrapMethod = "GetOrCreateDOMReflector"
|
|
wrapArgs = "cx, %s, ${jsvalHandle}" % result
|
|
else:
|
|
wrapMethod = "WrapNewBindingNonWrapperCachedObject"
|
|
wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result
|
|
if isConstructorRetval:
|
|
wrapArgs += ", desiredProto"
|
|
wrap = "%s(%s)" % (wrapMethod, wrapArgs)
|
|
# Can only fail to wrap as a new-binding object if they already
|
|
# threw an exception.
|
|
failed = "MOZ_ASSERT(JS_IsExceptionPending(cx));\n" + exceptionCode
|
|
else:
|
|
if descriptor.notflattened:
|
|
getIID = "&NS_GET_IID(%s), " % descriptor.nativeType
|
|
else:
|
|
getIID = ""
|
|
wrap = "WrapObject(cx, %s, %s${jsvalHandle})" % (result, getIID)
|
|
failed = None
|
|
|
|
wrappingCode += wrapAndSetPtr(wrap, failed)
|
|
return (wrappingCode, False)
|
|
|
|
if type.isJSString():
|
|
return (setString(result), False)
|
|
|
|
if type.isDOMString() or type.isUSVString():
|
|
if type.nullable():
|
|
return (
|
|
wrapAndSetPtr("xpc::StringToJsval(cx, %s, ${jsvalHandle})" % result),
|
|
False,
|
|
)
|
|
else:
|
|
return (
|
|
wrapAndSetPtr(
|
|
"xpc::NonVoidStringToJsval(cx, %s, ${jsvalHandle})" % result
|
|
),
|
|
False,
|
|
)
|
|
|
|
if type.isByteString():
|
|
if type.nullable():
|
|
return (
|
|
wrapAndSetPtr("ByteStringToJsval(cx, %s, ${jsvalHandle})" % result),
|
|
False,
|
|
)
|
|
else:
|
|
return (
|
|
wrapAndSetPtr(
|
|
"NonVoidByteStringToJsval(cx, %s, ${jsvalHandle})" % result
|
|
),
|
|
False,
|
|
)
|
|
|
|
if type.isUTF8String():
|
|
if type.nullable():
|
|
return (
|
|
wrapAndSetPtr("UTF8StringToJsval(cx, %s, ${jsvalHandle})" % result),
|
|
False,
|
|
)
|
|
else:
|
|
return (
|
|
wrapAndSetPtr(
|
|
"NonVoidUTF8StringToJsval(cx, %s, ${jsvalHandle})" % result
|
|
),
|
|
False,
|
|
)
|
|
|
|
if type.isEnum():
|
|
if type.nullable():
|
|
resultLoc = "%s.Value()" % result
|
|
else:
|
|
resultLoc = result
|
|
conversion = fill(
|
|
"""
|
|
if (!ToJSValue(cx, ${result}, $${jsvalHandle})) {
|
|
$*{exceptionCode}
|
|
}
|
|
$*{successCode}
|
|
""",
|
|
result=resultLoc,
|
|
exceptionCode=exceptionCode,
|
|
successCode=successCode,
|
|
)
|
|
|
|
if type.nullable():
|
|
conversion = CGIfElseWrapper(
|
|
"%s.IsNull()" % result, CGGeneric(setNull()), CGGeneric(conversion)
|
|
).define()
|
|
return conversion, False
|
|
|
|
if type.isCallback() or type.isCallbackInterface():
|
|
# Callbacks can store null if we nuked the compartments their
|
|
# objects lived in.
|
|
wrapCode = setObjectOrNull(
|
|
"GetCallbackFromCallbackObject(cx, %(result)s)", wrapAsType=type
|
|
)
|
|
if type.nullable():
|
|
wrapCode = (
|
|
"if (%(result)s) {\n"
|
|
+ indent(wrapCode)
|
|
+ "} else {\n"
|
|
+ indent(setNull())
|
|
+ "}\n"
|
|
)
|
|
wrapCode = wrapCode % {"result": result}
|
|
return wrapCode, False
|
|
|
|
if type.isAny():
|
|
# See comments in GetOrCreateDOMReflector explaining why we need
|
|
# to wrap here.
|
|
# NB: _setValue(..., type-that-is-any) calls JS_WrapValue(), so is fallible
|
|
head = "JS::ExposeValueToActiveJS(%s);\n" % result
|
|
return (head + _setValue(result, wrapAsType=type), False)
|
|
|
|
if type.isObject() or (
|
|
type.isSpiderMonkeyInterface() and not spiderMonkeyInterfacesAreStructs
|
|
):
|
|
# See comments in GetOrCreateDOMReflector explaining why we need
|
|
# to wrap here.
|
|
if type.nullable():
|
|
toValue = "%s"
|
|
setter = setObjectOrNull
|
|
head = """if (%s) {
|
|
JS::ExposeObjectToActiveJS(%s);
|
|
}
|
|
""" % (
|
|
result,
|
|
result,
|
|
)
|
|
else:
|
|
toValue = "*%s"
|
|
setter = setObject
|
|
head = "JS::ExposeObjectToActiveJS(%s);\n" % result
|
|
# NB: setObject{,OrNull}(..., some-object-type) calls JS_WrapValue(), so is fallible
|
|
return (head + setter(toValue % result, wrapAsType=type), False)
|
|
|
|
if not (
|
|
type.isUnion()
|
|
or type.isPrimitive()
|
|
or type.isDictionary()
|
|
or (type.isSpiderMonkeyInterface() and spiderMonkeyInterfacesAreStructs)
|
|
):
|
|
raise TypeError("Need to learn to wrap %s" % type)
|
|
|
|
if type.nullable():
|
|
recTemplate, recInfal = getWrapTemplateForType(
|
|
type.inner,
|
|
descriptorProvider,
|
|
"%s.Value()" % result,
|
|
successCode,
|
|
returnsNewObject,
|
|
exceptionCode,
|
|
spiderMonkeyInterfacesAreStructs,
|
|
)
|
|
return (
|
|
"if (%s.IsNull()) {\n" % result + indent(setNull()) + "}\n" + recTemplate,
|
|
recInfal,
|
|
)
|
|
|
|
if type.isSpiderMonkeyInterface():
|
|
assert spiderMonkeyInterfacesAreStructs
|
|
# See comments in GetOrCreateDOMReflector explaining why we need
|
|
# to wrap here.
|
|
# NB: setObject(..., some-object-type) calls JS_WrapValue(), so is fallible
|
|
return (setObject("*%s.Obj()" % result, wrapAsType=type), False)
|
|
|
|
if type.isUnion():
|
|
return (wrapAndSetPtr("%s.ToJSVal(cx, ${obj}, ${jsvalHandle})" % result), False)
|
|
|
|
if type.isDictionary():
|
|
return (
|
|
wrapAndSetPtr("%s.ToObjectInternal(cx, ${jsvalHandle})" % result),
|
|
False,
|
|
)
|
|
|
|
tag = type.tag()
|
|
|
|
if tag in [
|
|
IDLType.Tags.int8,
|
|
IDLType.Tags.uint8,
|
|
IDLType.Tags.int16,
|
|
IDLType.Tags.uint16,
|
|
IDLType.Tags.int32,
|
|
]:
|
|
return (setInt32("int32_t(%s)" % result), True)
|
|
|
|
elif tag in [
|
|
IDLType.Tags.int64,
|
|
IDLType.Tags.uint64,
|
|
IDLType.Tags.unrestricted_float,
|
|
IDLType.Tags.float,
|
|
IDLType.Tags.unrestricted_double,
|
|
IDLType.Tags.double,
|
|
]:
|
|
# XXXbz will cast to double do the "even significand" thing that webidl
|
|
# calls for for 64-bit ints? Do we care?
|
|
return (setDouble("double(%s)" % result), True)
|
|
|
|
elif tag == IDLType.Tags.uint32:
|
|
return (setUint32(result), True)
|
|
|
|
elif tag == IDLType.Tags.bool:
|
|
return (setBoolean(result), True)
|
|
|
|
else:
|
|
raise TypeError("Need to learn to wrap primitive: %s" % type)
|
|
|
|
|
|
def wrapForType(type, descriptorProvider, templateValues):
|
|
"""
|
|
Reflect a C++ value of IDL type "type" into JS. TemplateValues is a dict
|
|
that should contain:
|
|
|
|
* 'jsvalRef': something that can have .address() called on it to get a
|
|
JS::Value* and .set() called on it to set it to a JS::Value.
|
|
This can be a JS::MutableHandle<JS::Value> or a
|
|
JS::Rooted<JS::Value>.
|
|
* 'jsvalHandle': something that can be passed to methods taking a
|
|
JS::MutableHandle<JS::Value>. This can be a
|
|
JS::MutableHandle<JS::Value> or a JS::Rooted<JS::Value>*.
|
|
* 'obj' (optional): the name of the variable that contains the JSObject to
|
|
use as a scope when wrapping, if not supplied 'obj'
|
|
will be used as the name
|
|
* 'result' (optional): the name of the variable in which the C++ value is
|
|
stored, if not supplied 'result' will be used as
|
|
the name
|
|
* 'successCode' (optional): the code to run once we have successfully
|
|
done the conversion, if not supplied 'return
|
|
true;' will be used as the code. The
|
|
successCode must ensure that once it runs no
|
|
more of the conversion template will be
|
|
executed (e.g. by doing a 'return' or 'break'
|
|
as appropriate).
|
|
* 'returnsNewObject' (optional): If true, we're wrapping for the return
|
|
value of a [NewObject] method. Assumed
|
|
false if not set.
|
|
* 'exceptionCode' (optional): Code to run when a JS exception is thrown.
|
|
The default is "return false;". The code
|
|
passed here must return.
|
|
* 'isConstructorRetval' (optional): If true, we're wrapping a constructor
|
|
return value.
|
|
"""
|
|
wrap = getWrapTemplateForType(
|
|
type,
|
|
descriptorProvider,
|
|
templateValues.get("result", "result"),
|
|
templateValues.get("successCode", None),
|
|
templateValues.get("returnsNewObject", False),
|
|
templateValues.get("exceptionCode", "return false;\n"),
|
|
templateValues.get("spiderMonkeyInterfacesAreStructs", False),
|
|
isConstructorRetval=templateValues.get("isConstructorRetval", False),
|
|
)[0]
|
|
|
|
defaultValues = {"obj": "obj"}
|
|
return string.Template(wrap).substitute(defaultValues, **templateValues)
|
|
|
|
|
|
def infallibleForMember(member, type, descriptorProvider):
|
|
"""
|
|
Determine the fallibility of changing a C++ value of IDL type "type" into
|
|
JS for the given attribute. Apart from returnsNewObject, all the defaults
|
|
are used, since the fallbility does not change based on the boolean values,
|
|
and the template will be discarded.
|
|
|
|
CURRENT ASSUMPTIONS:
|
|
We assume that successCode for wrapping up return values cannot contain
|
|
failure conditions.
|
|
"""
|
|
return getWrapTemplateForType(
|
|
type,
|
|
descriptorProvider,
|
|
"result",
|
|
None,
|
|
memberReturnsNewObject(member),
|
|
"return false;\n",
|
|
False,
|
|
)[1]
|
|
|
|
|
|
def leafTypeNeedsCx(type, retVal):
|
|
return (
|
|
type.isAny()
|
|
or type.isObject()
|
|
or type.isJSString()
|
|
or (retVal and type.isSpiderMonkeyInterface())
|
|
)
|
|
|
|
|
|
def leafTypeNeedsScopeObject(type, retVal):
|
|
return retVal and type.isSpiderMonkeyInterface()
|
|
|
|
|
|
def leafTypeNeedsRooting(type):
|
|
return leafTypeNeedsCx(type, False) or type.isSpiderMonkeyInterface()
|
|
|
|
|
|
def typeNeedsRooting(type):
|
|
return typeMatchesLambda(type, lambda t: leafTypeNeedsRooting(t))
|
|
|
|
|
|
def typeNeedsCx(type, retVal=False):
|
|
return typeMatchesLambda(type, lambda t: leafTypeNeedsCx(t, retVal))
|
|
|
|
|
|
def typeNeedsScopeObject(type, retVal=False):
|
|
return typeMatchesLambda(type, lambda t: leafTypeNeedsScopeObject(t, retVal))
|
|
|
|
|
|
def typeMatchesLambda(type, func):
|
|
if type is None:
|
|
return False
|
|
if type.nullable():
|
|
return typeMatchesLambda(type.inner, func)
|
|
if type.isSequence() or type.isRecord():
|
|
return typeMatchesLambda(type.inner, func)
|
|
if type.isUnion():
|
|
return any(typeMatchesLambda(t, func) for t in type.unroll().flatMemberTypes)
|
|
if type.isDictionary():
|
|
return dictionaryMatchesLambda(type.inner, func)
|
|
return func(type)
|
|
|
|
|
|
def dictionaryMatchesLambda(dictionary, func):
|
|
return any(typeMatchesLambda(m.type, func) for m in dictionary.members) or (
|
|
dictionary.parent and dictionaryMatchesLambda(dictionary.parent, func)
|
|
)
|
|
|
|
|
|
# Whenever this is modified, please update CGNativeMember.getRetvalInfo as
|
|
# needed to keep the types compatible.
|
|
def getRetvalDeclarationForType(returnType, descriptorProvider, isMember=False):
|
|
"""
|
|
Returns a tuple containing five things:
|
|
|
|
1) A CGThing for the type of the return value, or None if there is no need
|
|
for a return value.
|
|
|
|
2) A value indicating the kind of ourparam to pass the value as. Valid
|
|
options are None to not pass as an out param at all, "ref" (to pass a
|
|
reference as an out param), and "ptr" (to pass a pointer as an out
|
|
param).
|
|
|
|
3) A CGThing for a tracer for the return value, or None if no tracing is
|
|
needed.
|
|
|
|
4) An argument string to pass to the retval declaration
|
|
constructor or None if there are no arguments.
|
|
|
|
5) The name of a function that needs to be called with the return value
|
|
before using it, or None if no function needs to be called.
|
|
"""
|
|
if returnType is None or returnType.isVoid():
|
|
# Nothing to declare
|
|
return None, None, None, None, None
|
|
if returnType.isPrimitive() and returnType.tag() in builtinNames:
|
|
result = CGGeneric(builtinNames[returnType.tag()])
|
|
if returnType.nullable():
|
|
result = CGTemplatedType("Nullable", result)
|
|
return result, None, None, None, None
|
|
if returnType.isJSString():
|
|
if isMember:
|
|
raise TypeError("JSString not supported as return type member")
|
|
return CGGeneric("JS::Rooted<JSString*>"), "ptr", None, "cx", None
|
|
if returnType.isDOMString() or returnType.isUSVString():
|
|
if isMember:
|
|
return CGGeneric("nsString"), "ref", None, None, None
|
|
return CGGeneric("DOMString"), "ref", None, None, None
|
|
if returnType.isByteString() or returnType.isUTF8String():
|
|
return CGGeneric("nsCString"), "ref", None, None, None
|
|
if returnType.isEnum():
|
|
result = CGGeneric(returnType.unroll().inner.identifier.name)
|
|
if returnType.nullable():
|
|
result = CGTemplatedType("Nullable", result)
|
|
return result, None, None, None, None
|
|
if returnType.isGeckoInterface() or returnType.isPromise():
|
|
if returnType.isGeckoInterface():
|
|
typeName = returnType.unroll().inner.identifier.name
|
|
if typeName == "WindowProxy":
|
|
result = CGGeneric("WindowProxyHolder")
|
|
if returnType.nullable():
|
|
result = CGTemplatedType("Nullable", result)
|
|
return result, None, None, None, None
|
|
|
|
typeName = descriptorProvider.getDescriptor(typeName).nativeType
|
|
else:
|
|
typeName = "Promise"
|
|
if isMember:
|
|
conversion = None
|
|
result = CGGeneric("StrongPtrForMember<%s>" % typeName)
|
|
else:
|
|
conversion = CGGeneric("StrongOrRawPtr<%s>" % typeName)
|
|
result = CGGeneric("auto")
|
|
return result, None, None, None, conversion
|
|
if returnType.isCallback():
|
|
name = returnType.unroll().callback.identifier.name
|
|
return CGGeneric("RefPtr<%s>" % name), None, None, None, None
|
|
if returnType.isAny():
|
|
if isMember:
|
|
return CGGeneric("JS::Value"), None, None, None, None
|
|
return CGGeneric("JS::Rooted<JS::Value>"), "ptr", None, "cx", None
|
|
if returnType.isObject() or returnType.isSpiderMonkeyInterface():
|
|
if isMember:
|
|
return CGGeneric("JSObject*"), None, None, None, None
|
|
return CGGeneric("JS::Rooted<JSObject*>"), "ptr", None, "cx", None
|
|
if returnType.isSequence():
|
|
nullable = returnType.nullable()
|
|
if nullable:
|
|
returnType = returnType.inner
|
|
result, _, _, _, _ = getRetvalDeclarationForType(
|
|
returnType.inner, descriptorProvider, isMember="Sequence"
|
|
)
|
|
# While we have our inner type, set up our rooter, if needed
|
|
if not isMember and typeNeedsRooting(returnType):
|
|
rooter = CGGeneric(
|
|
"SequenceRooter<%s > resultRooter(cx, &result);\n" % result.define()
|
|
)
|
|
else:
|
|
rooter = None
|
|
result = CGTemplatedType("nsTArray", result)
|
|
if nullable:
|
|
result = CGTemplatedType("Nullable", result)
|
|
return result, "ref", rooter, None, None
|
|
if returnType.isRecord():
|
|
nullable = returnType.nullable()
|
|
if nullable:
|
|
returnType = returnType.inner
|
|
result, _, _, _, _ = getRetvalDeclarationForType(
|
|
returnType.inner, descriptorProvider, isMember="Record"
|
|
)
|
|
# While we have our inner type, set up our rooter, if needed
|
|
if not isMember and typeNeedsRooting(returnType):
|
|
rooter = CGGeneric(
|
|
"RecordRooter<%s> resultRooter(cx, &result);\n"
|
|
% ("nsString, " + result.define())
|
|
)
|
|
else:
|
|
rooter = None
|
|
result = CGTemplatedType("Record", [recordKeyDeclType(returnType), result])
|
|
if nullable:
|
|
result = CGTemplatedType("Nullable", result)
|
|
return result, "ref", rooter, None, None
|
|
if returnType.isDictionary():
|
|
nullable = returnType.nullable()
|
|
dictName = CGDictionary.makeDictionaryName(returnType.unroll().inner)
|
|
result = CGGeneric(dictName)
|
|
if not isMember and typeNeedsRooting(returnType):
|
|
if nullable:
|
|
result = CGTemplatedType("NullableRootedDictionary", result)
|
|
else:
|
|
result = CGTemplatedType("RootedDictionary", result)
|
|
resultArgs = "cx"
|
|
else:
|
|
if nullable:
|
|
result = CGTemplatedType("Nullable", result)
|
|
resultArgs = None
|
|
return result, "ref", None, resultArgs, None
|
|
if returnType.isUnion():
|
|
result = CGGeneric(CGUnionStruct.unionTypeName(returnType.unroll(), True))
|
|
if not isMember and typeNeedsRooting(returnType):
|
|
if returnType.nullable():
|
|
result = CGTemplatedType("NullableRootedUnion", result)
|
|
else:
|
|
result = CGTemplatedType("RootedUnion", result)
|
|
resultArgs = "cx"
|
|
else:
|
|
if returnType.nullable():
|
|
result = CGTemplatedType("Nullable", result)
|
|
resultArgs = None
|
|
return result, "ref", None, resultArgs, None
|
|
raise TypeError("Don't know how to declare return value for %s" % returnType)
|
|
|
|
|
|
def needCx(returnType, arguments, extendedAttributes, considerTypes, static=False):
|
|
return (
|
|
not static
|
|
and considerTypes
|
|
and (
|
|
typeNeedsCx(returnType, True) or any(typeNeedsCx(a.type) for a in arguments)
|
|
)
|
|
or "implicitJSContext" in extendedAttributes
|
|
)
|
|
|
|
|
|
def needScopeObject(
|
|
returnType, arguments, extendedAttributes, isWrapperCached, considerTypes, isMember
|
|
):
|
|
"""
|
|
isMember should be true if we're dealing with an attribute
|
|
annotated as [StoreInSlot].
|
|
"""
|
|
return (
|
|
considerTypes
|
|
and not isWrapperCached
|
|
and (
|
|
(not isMember and typeNeedsScopeObject(returnType, True))
|
|
or any(typeNeedsScopeObject(a.type) for a in arguments)
|
|
)
|
|
)
|
|
|
|
|
|
def callerTypeGetterForDescriptor(descriptor):
|
|
if descriptor.interface.isExposedInAnyWorker():
|
|
systemCallerGetter = "nsContentUtils::ThreadsafeIsSystemCaller"
|
|
else:
|
|
systemCallerGetter = "nsContentUtils::IsSystemCaller"
|
|
return "%s(cx) ? CallerType::System : CallerType::NonSystem" % systemCallerGetter
|
|
|
|
|
|
class CGCallGenerator(CGThing):
|
|
"""
|
|
A class to generate an actual call to a C++ object. Assumes that the C++
|
|
object is stored in a variable whose name is given by the |object| argument.
|
|
|
|
needsCallerType is a boolean indicating whether the call should receive
|
|
a PrincipalType for the caller.
|
|
|
|
isFallible is a boolean indicating whether the call should be fallible.
|
|
|
|
resultVar: If the returnType is not void, then the result of the call is
|
|
stored in a C++ variable named by resultVar. The caller is responsible for
|
|
declaring the result variable. If the caller doesn't care about the result
|
|
value, resultVar can be omitted.
|
|
|
|
context: The context string to pass to MaybeSetPendingException.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
isFallible,
|
|
needsCallerType,
|
|
isChromeOnly,
|
|
arguments,
|
|
argsPre,
|
|
returnType,
|
|
extendedAttributes,
|
|
descriptor,
|
|
nativeMethodName,
|
|
static,
|
|
object="self",
|
|
argsPost=[],
|
|
resultVar=None,
|
|
context="nullptr",
|
|
):
|
|
CGThing.__init__(self)
|
|
|
|
(
|
|
result,
|
|
resultOutParam,
|
|
resultRooter,
|
|
resultArgs,
|
|
resultConversion,
|
|
) = getRetvalDeclarationForType(returnType, descriptor)
|
|
|
|
args = CGList([CGGeneric(arg) for arg in argsPre], ", ")
|
|
for a, name in arguments:
|
|
arg = CGGeneric(name)
|
|
|
|
# Now constify the things that need it
|
|
def needsConst(a):
|
|
if a.type.isDictionary():
|
|
return True
|
|
if a.type.isSequence():
|
|
return True
|
|
if a.type.isRecord():
|
|
return True
|
|
# isObject() types are always a JS::Rooted, whether
|
|
# nullable or not, and it turns out a const JS::Rooted
|
|
# is not very helpful at all (in particular, it won't
|
|
# even convert to a JS::Handle).
|
|
# XXX bz Well, why not???
|
|
if a.type.nullable() and not a.type.isObject():
|
|
return True
|
|
if a.type.isString():
|
|
return True
|
|
if a.canHaveMissingValue():
|
|
# This will need an Optional or it's a variadic;
|
|
# in both cases it should be const.
|
|
return True
|
|
if a.type.isUnion():
|
|
return True
|
|
if a.type.isSpiderMonkeyInterface():
|
|
return True
|
|
return False
|
|
|
|
if needsConst(a):
|
|
arg = CGWrapper(arg, pre="Constify(", post=")")
|
|
# And convert NonNull<T> to T&
|
|
if (
|
|
(a.type.isGeckoInterface() or a.type.isCallback() or a.type.isPromise())
|
|
and not a.type.nullable()
|
|
) or a.type.isDOMString():
|
|
arg = CGWrapper(arg, pre="NonNullHelper(", post=")")
|
|
|
|
# If it's a refcounted object, let the static analysis know it's
|
|
# alive for the duration of the call.
|
|
if a.type.isGeckoInterface() or a.type.isCallback():
|
|
arg = CGWrapper(arg, pre="MOZ_KnownLive(", post=")")
|
|
|
|
args.append(arg)
|
|
|
|
needResultDecl = False
|
|
|
|
# Build up our actual call
|
|
self.cgRoot = CGList([])
|
|
|
|
# Return values that go in outparams go here
|
|
if resultOutParam is not None:
|
|
if resultVar is None:
|
|
needResultDecl = True
|
|
resultVar = "result"
|
|
if resultOutParam == "ref":
|
|
args.append(CGGeneric(resultVar))
|
|
else:
|
|
assert resultOutParam == "ptr"
|
|
args.append(CGGeneric("&" + resultVar))
|
|
|
|
needsSubjectPrincipal = "needsSubjectPrincipal" in extendedAttributes
|
|
if needsSubjectPrincipal:
|
|
needsNonSystemPrincipal = (
|
|
"needsNonSystemSubjectPrincipal" in extendedAttributes
|
|
)
|
|
if needsNonSystemPrincipal:
|
|
checkPrincipal = dedent(
|
|
"""
|
|
if (principal->IsSystemPrincipal()) {
|
|
principal = nullptr;
|
|
}
|
|
"""
|
|
)
|
|
else:
|
|
checkPrincipal = ""
|
|
|
|
getPrincipal = fill(
|
|
"""
|
|
JS::Realm* realm = js::GetContextRealm(cx);
|
|
MOZ_ASSERT(realm);
|
|
JSPrincipals* principals = JS::GetRealmPrincipals(realm);
|
|
nsIPrincipal* principal = nsJSPrincipals::get(principals);
|
|
${checkPrincipal}
|
|
""",
|
|
checkPrincipal=checkPrincipal,
|
|
)
|
|
|
|
if descriptor.interface.isExposedInAnyWorker():
|
|
self.cgRoot.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
Maybe<nsIPrincipal*> subjectPrincipal;
|
|
if (NS_IsMainThread()) {
|
|
$*{getPrincipal}
|
|
subjectPrincipal.emplace(principal);
|
|
}
|
|
""",
|
|
getPrincipal=getPrincipal,
|
|
)
|
|
)
|
|
)
|
|
subjectPrincipalArg = "subjectPrincipal"
|
|
else:
|
|
if needsNonSystemPrincipal:
|
|
principalType = "nsIPrincipal*"
|
|
subjectPrincipalArg = "subjectPrincipal"
|
|
else:
|
|
principalType = "NonNull<nsIPrincipal>"
|
|
subjectPrincipalArg = "NonNullHelper(subjectPrincipal)"
|
|
|
|
self.cgRoot.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
${principalType} subjectPrincipal;
|
|
{
|
|
$*{getPrincipal}
|
|
subjectPrincipal = principal;
|
|
}
|
|
""",
|
|
principalType=principalType,
|
|
getPrincipal=getPrincipal,
|
|
)
|
|
)
|
|
)
|
|
|
|
args.append(CGGeneric("MOZ_KnownLive(%s)" % subjectPrincipalArg))
|
|
|
|
if needsCallerType:
|
|
if isChromeOnly:
|
|
args.append(CGGeneric("SystemCallerGuarantee()"))
|
|
else:
|
|
args.append(CGGeneric(callerTypeGetterForDescriptor(descriptor)))
|
|
|
|
canOOM = "canOOM" in extendedAttributes
|
|
if isFallible:
|
|
args.append(CGGeneric("rv"))
|
|
elif canOOM:
|
|
args.append(CGGeneric("OOMReporter::From(rv)"))
|
|
args.extend(CGGeneric(arg) for arg in argsPost)
|
|
|
|
call = CGGeneric(nativeMethodName)
|
|
if not static:
|
|
call = CGWrapper(call, pre="%s->" % object)
|
|
call = CGList([call, CGWrapper(args, pre="(", post=")")])
|
|
if returnType is None or returnType.isVoid() or resultOutParam is not None:
|
|
assert resultConversion is None
|
|
call = CGList(
|
|
[
|
|
CGWrapper(
|
|
call,
|
|
pre=(
|
|
"// NOTE: This assert does NOT call the function.\n"
|
|
"static_assert(std::is_void_v<decltype("
|
|
),
|
|
post=')>, "Should be returning void here");',
|
|
),
|
|
call,
|
|
],
|
|
"\n",
|
|
)
|
|
elif resultConversion is not None:
|
|
call = CGList([resultConversion, CGWrapper(call, pre="(", post=")")])
|
|
if resultVar is None and result is not None:
|
|
needResultDecl = True
|
|
resultVar = "result"
|
|
|
|
if needResultDecl:
|
|
if resultArgs is not None:
|
|
resultArgsStr = "(%s)" % resultArgs
|
|
else:
|
|
resultArgsStr = ""
|
|
result = CGWrapper(result, post=(" %s%s" % (resultVar, resultArgsStr)))
|
|
if resultOutParam is None and resultArgs is None:
|
|
call = CGList([result, CGWrapper(call, pre="(", post=")")])
|
|
else:
|
|
self.cgRoot.append(CGWrapper(result, post=";\n"))
|
|
if resultOutParam is None:
|
|
call = CGWrapper(call, pre=resultVar + " = ")
|
|
if resultRooter is not None:
|
|
self.cgRoot.append(resultRooter)
|
|
elif result is not None:
|
|
assert resultOutParam is None
|
|
call = CGWrapper(call, pre=resultVar + " = ")
|
|
|
|
call = CGWrapper(call, post=";\n")
|
|
self.cgRoot.append(call)
|
|
|
|
if isFallible or canOOM:
|
|
self.cgRoot.prepend(CGGeneric("FastErrorResult rv;\n"))
|
|
self.cgRoot.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
if (MOZ_UNLIKELY(rv.MaybeSetPendingException(cx, ${context}))) {
|
|
return false;
|
|
}
|
|
""",
|
|
context=context,
|
|
)
|
|
)
|
|
)
|
|
|
|
self.cgRoot.append(CGGeneric("MOZ_ASSERT(!JS_IsExceptionPending(cx));\n"))
|
|
|
|
def define(self):
|
|
return self.cgRoot.define()
|
|
|
|
|
|
def getUnionMemberName(type):
|
|
# Promises can't be in unions, because they're not distinguishable
|
|
# from anything else.
|
|
assert not type.isPromise()
|
|
if type.isGeckoInterface():
|
|
return type.inner.identifier.name
|
|
if type.isEnum():
|
|
return type.inner.identifier.name
|
|
return type.name
|
|
|
|
|
|
# A counter for making sure that when we're wrapping up things in
|
|
# nested sequences we don't use the same variable name to iterate over
|
|
# different sequences.
|
|
sequenceWrapLevel = 0
|
|
recordWrapLevel = 0
|
|
|
|
|
|
def wrapTypeIntoCurrentCompartment(type, value, isMember=True):
|
|
"""
|
|
Take the thing named by "value" and if it contains "any",
|
|
"object", or spidermonkey-interface types inside return a CGThing
|
|
that will wrap them into the current compartment.
|
|
"""
|
|
if type.isAny():
|
|
assert not type.nullable()
|
|
if isMember:
|
|
value = "JS::MutableHandle<JS::Value>::fromMarkedLocation(&%s)" % value
|
|
else:
|
|
value = "&" + value
|
|
return CGGeneric(
|
|
"if (!JS_WrapValue(cx, %s)) {\n" " return false;\n" "}\n" % value
|
|
)
|
|
|
|
if type.isObject():
|
|
if isMember:
|
|
value = "JS::MutableHandle<JSObject*>::fromMarkedLocation(&%s)" % value
|
|
else:
|
|
value = "&" + value
|
|
return CGGeneric(
|
|
"if (!JS_WrapObject(cx, %s)) {\n" " return false;\n" "}\n" % value
|
|
)
|
|
|
|
if type.isSpiderMonkeyInterface():
|
|
origValue = value
|
|
if type.nullable():
|
|
value = "%s.Value()" % value
|
|
wrapCode = CGGeneric(
|
|
"if (!%s.WrapIntoNewCompartment(cx)) {\n" " return false;\n" "}\n" % value
|
|
)
|
|
if type.nullable():
|
|
wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % origValue)
|
|
return wrapCode
|
|
|
|
if type.isSequence():
|
|
origValue = value
|
|
origType = type
|
|
if type.nullable():
|
|
type = type.inner
|
|
value = "%s.Value()" % value
|
|
global sequenceWrapLevel
|
|
index = "indexName%d" % sequenceWrapLevel
|
|
sequenceWrapLevel += 1
|
|
wrapElement = wrapTypeIntoCurrentCompartment(
|
|
type.inner, "%s[%s]" % (value, index)
|
|
)
|
|
sequenceWrapLevel -= 1
|
|
if not wrapElement:
|
|
return None
|
|
wrapCode = CGWrapper(
|
|
CGIndenter(wrapElement),
|
|
pre=(
|
|
"for (uint32_t %s = 0; %s < %s.Length(); ++%s) {\n"
|
|
% (index, index, value, index)
|
|
),
|
|
post="}\n",
|
|
)
|
|
if origType.nullable():
|
|
wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % origValue)
|
|
return wrapCode
|
|
|
|
if type.isRecord():
|
|
origType = type
|
|
if type.nullable():
|
|
type = type.inner
|
|
recordRef = "%s.Value()" % value
|
|
else:
|
|
recordRef = value
|
|
global recordWrapLevel
|
|
entryRef = "mapEntry%d" % recordWrapLevel
|
|
recordWrapLevel += 1
|
|
wrapElement = wrapTypeIntoCurrentCompartment(type.inner, "%s.mValue" % entryRef)
|
|
recordWrapLevel -= 1
|
|
if not wrapElement:
|
|
return None
|
|
wrapCode = CGWrapper(
|
|
CGIndenter(wrapElement),
|
|
pre=("for (auto& %s : %s.Entries()) {\n" % (entryRef, recordRef)),
|
|
post="}\n",
|
|
)
|
|
if origType.nullable():
|
|
wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % value)
|
|
return wrapCode
|
|
|
|
if type.isDictionary():
|
|
assert not type.nullable()
|
|
myDict = type.inner
|
|
memberWraps = []
|
|
while myDict:
|
|
for member in myDict.members:
|
|
memberWrap = wrapArgIntoCurrentCompartment(
|
|
member,
|
|
"%s.%s"
|
|
% (value, CGDictionary.makeMemberName(member.identifier.name)),
|
|
)
|
|
if memberWrap:
|
|
memberWraps.append(memberWrap)
|
|
myDict = myDict.parent
|
|
return CGList(memberWraps) if len(memberWraps) != 0 else None
|
|
|
|
if type.isUnion():
|
|
memberWraps = []
|
|
if type.nullable():
|
|
type = type.inner
|
|
value = "%s.Value()" % value
|
|
for member in type.flatMemberTypes:
|
|
memberName = getUnionMemberName(member)
|
|
memberWrap = wrapTypeIntoCurrentCompartment(
|
|
member, "%s.GetAs%s()" % (value, memberName)
|
|
)
|
|
if memberWrap:
|
|
memberWrap = CGIfWrapper(memberWrap, "%s.Is%s()" % (value, memberName))
|
|
memberWraps.append(memberWrap)
|
|
return CGList(memberWraps, "else ") if len(memberWraps) != 0 else None
|
|
|
|
if (
|
|
type.isString()
|
|
or type.isPrimitive()
|
|
or type.isEnum()
|
|
or type.isGeckoInterface()
|
|
or type.isCallback()
|
|
or type.isPromise()
|
|
):
|
|
# All of these don't need wrapping.
|
|
return None
|
|
|
|
raise TypeError(
|
|
"Unknown type; we don't know how to wrap it in constructor "
|
|
"arguments: %s" % type
|
|
)
|
|
|
|
|
|
def wrapArgIntoCurrentCompartment(arg, value, isMember=True):
|
|
"""
|
|
As wrapTypeIntoCurrentCompartment but handles things being optional
|
|
"""
|
|
origValue = value
|
|
isOptional = arg.canHaveMissingValue()
|
|
if isOptional:
|
|
value = value + ".Value()"
|
|
wrap = wrapTypeIntoCurrentCompartment(arg.type, value, isMember)
|
|
if wrap and isOptional:
|
|
wrap = CGIfWrapper(wrap, "%s.WasPassed()" % origValue)
|
|
return wrap
|
|
|
|
|
|
def needsContainsHack(m):
|
|
return m.getExtendedAttribute("ReturnValueNeedsContainsHack")
|
|
|
|
|
|
def needsCallerType(m):
|
|
return m.getExtendedAttribute("NeedsCallerType")
|
|
|
|
|
|
class CGPerSignatureCall(CGThing):
|
|
"""
|
|
This class handles the guts of generating code for a particular
|
|
call signature. A call signature consists of four things:
|
|
|
|
1) A return type, which can be None to indicate that there is no
|
|
actual return value (e.g. this is an attribute setter) or an
|
|
IDLType if there's an IDL type involved (including |void|).
|
|
2) An argument list, which is allowed to be empty.
|
|
3) A name of a native method to call.
|
|
4) Whether or not this method is static. Note that this only controls how
|
|
the method is called (|self->nativeMethodName(...)| vs
|
|
|nativeMethodName(...)|).
|
|
|
|
We also need to know whether this is a method or a getter/setter
|
|
to do error reporting correctly.
|
|
|
|
The idlNode parameter can be either a method or an attr. We can query
|
|
|idlNode.identifier| in both cases, so we can be agnostic between the two.
|
|
|
|
dontSetSlot should be set to True if the value should not be cached in a
|
|
slot (even if the attribute is marked as StoreInSlot or Cached in the
|
|
WebIDL).
|
|
"""
|
|
|
|
# XXXbz For now each entry in the argument list is either an
|
|
# IDLArgument or a FakeArgument, but longer-term we may want to
|
|
# have ways of flagging things like JSContext* or optional_argc in
|
|
# there.
|
|
|
|
def __init__(
|
|
self,
|
|
returnType,
|
|
arguments,
|
|
nativeMethodName,
|
|
static,
|
|
descriptor,
|
|
idlNode,
|
|
argConversionStartsAt=0,
|
|
getter=False,
|
|
setter=False,
|
|
isConstructor=False,
|
|
useCounterName=None,
|
|
resultVar=None,
|
|
objectName="obj",
|
|
dontSetSlot=False,
|
|
extendedAttributes=None,
|
|
):
|
|
assert idlNode.isMethod() == (not getter and not setter)
|
|
assert idlNode.isAttr() == (getter or setter)
|
|
# Constructors are always static
|
|
assert not isConstructor or static
|
|
|
|
CGThing.__init__(self)
|
|
self.returnType = returnType
|
|
self.descriptor = descriptor
|
|
self.idlNode = idlNode
|
|
if extendedAttributes is None:
|
|
extendedAttributes = descriptor.getExtendedAttributes(
|
|
idlNode, getter=getter, setter=setter
|
|
)
|
|
self.extendedAttributes = extendedAttributes
|
|
self.arguments = arguments
|
|
self.argCount = len(arguments)
|
|
self.isConstructor = isConstructor
|
|
self.setSlot = (
|
|
not dontSetSlot and idlNode.isAttr() and idlNode.slotIndices is not None
|
|
)
|
|
cgThings = []
|
|
|
|
deprecated = idlNode.getExtendedAttribute("Deprecated") or (
|
|
idlNode.isStatic()
|
|
and descriptor.interface.getExtendedAttribute("Deprecated")
|
|
)
|
|
if deprecated:
|
|
cgThings.append(
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
DeprecationWarning(cx, obj, Document::e%s);
|
|
"""
|
|
% deprecated[0]
|
|
)
|
|
)
|
|
)
|
|
|
|
lenientFloatCode = None
|
|
if idlNode.getExtendedAttribute("LenientFloat") is not None and (
|
|
setter or idlNode.isMethod()
|
|
):
|
|
cgThings.append(
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
bool foundNonFiniteFloat = false;
|
|
"""
|
|
)
|
|
)
|
|
)
|
|
lenientFloatCode = "foundNonFiniteFloat = true;\n"
|
|
|
|
argsPre = []
|
|
if idlNode.isStatic():
|
|
# If we're a constructor, "obj" may not be a function, so calling
|
|
# XrayAwareCalleeGlobal() on it is not safe. Of course in the
|
|
# constructor case either "obj" is an Xray or we're already in the
|
|
# content compartment, not the Xray compartment, so just
|
|
# constructing the GlobalObject from "obj" is fine.
|
|
if isConstructor:
|
|
objForGlobalObject = "obj"
|
|
else:
|
|
objForGlobalObject = "xpc::XrayAwareCalleeGlobal(obj)"
|
|
cgThings.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
GlobalObject global(cx, ${obj});
|
|
if (global.Failed()) {
|
|
return false;
|
|
}
|
|
|
|
""",
|
|
obj=objForGlobalObject,
|
|
)
|
|
)
|
|
)
|
|
argsPre.append("global")
|
|
|
|
# For JS-implemented interfaces we do not want to base the
|
|
# needsCx decision on the types involved, just on our extended
|
|
# attributes. Also, JSContext is not needed for the static case
|
|
# since GlobalObject already contains the context.
|
|
needsCx = needCx(
|
|
returnType,
|
|
arguments,
|
|
self.extendedAttributes,
|
|
not descriptor.interface.isJSImplemented(),
|
|
static,
|
|
)
|
|
if needsCx:
|
|
argsPre.append("cx")
|
|
|
|
needsUnwrap = False
|
|
argsPost = []
|
|
runConstructorInCallerCompartment = descriptor.interface.getExtendedAttribute(
|
|
"RunConstructorInCallerCompartment"
|
|
)
|
|
if isConstructor and not runConstructorInCallerCompartment:
|
|
needsUnwrap = True
|
|
needsUnwrappedVar = False
|
|
unwrappedVar = "obj"
|
|
if descriptor.interface.isJSImplemented():
|
|
# We need the desired proto in our constructor, because the
|
|
# constructor will actually construct our reflector.
|
|
argsPost.append("desiredProto")
|
|
elif descriptor.interface.isJSImplemented():
|
|
if not idlNode.isStatic():
|
|
needsUnwrap = True
|
|
needsUnwrappedVar = True
|
|
argsPost.append(
|
|
"(unwrappedObj ? js::GetNonCCWObjectRealm(*unwrappedObj) : js::GetContextRealm(cx))"
|
|
)
|
|
elif needScopeObject(
|
|
returnType,
|
|
arguments,
|
|
self.extendedAttributes,
|
|
descriptor.wrapperCache,
|
|
True,
|
|
idlNode.getExtendedAttribute("StoreInSlot"),
|
|
):
|
|
# If we ever end up with APIs like this on cross-origin objects,
|
|
# figure out how the CheckedUnwrapDynamic bits should work. Chances
|
|
# are, just calling it with "cx" is fine... For now, though, just
|
|
# assert that it does not matter.
|
|
assert not descriptor.isMaybeCrossOriginObject()
|
|
# The scope object should always be from the relevant
|
|
# global. Make sure to unwrap it as needed.
|
|
cgThings.append(
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrapStatic(obj));
|
|
// Caller should have ensured that "obj" can be unwrapped already.
|
|
MOZ_DIAGNOSTIC_ASSERT(unwrappedObj);
|
|
"""
|
|
)
|
|
)
|
|
)
|
|
argsPre.append("unwrappedObj")
|
|
|
|
if needsUnwrap and needsUnwrappedVar:
|
|
# We cannot assign into obj because it's a Handle, not a
|
|
# MutableHandle, so we need a separate Rooted.
|
|
cgThings.append(CGGeneric("Maybe<JS::Rooted<JSObject*> > unwrappedObj;\n"))
|
|
unwrappedVar = "unwrappedObj.ref()"
|
|
|
|
if idlNode.isMethod() and idlNode.isLegacycaller():
|
|
# If we can have legacycaller with identifier, we can't
|
|
# just use the idlNode to determine whether we're
|
|
# generating code for the legacycaller or not.
|
|
assert idlNode.isIdentifierLess()
|
|
# Pass in our thisVal
|
|
argsPre.append("args.thisv()")
|
|
|
|
if idlNode.isMethod():
|
|
argDescription = "argument %(index)d"
|
|
elif setter:
|
|
argDescription = "value being assigned"
|
|
else:
|
|
assert self.argCount == 0
|
|
|
|
if needsUnwrap:
|
|
# It's very important that we construct our unwrappedObj, if we need
|
|
# to do it, before we might start setting up Rooted things for our
|
|
# arguments, so that we don't violate the stack discipline Rooted
|
|
# depends on.
|
|
cgThings.append(
|
|
CGGeneric("bool objIsXray = xpc::WrapperFactory::IsXrayWrapper(obj);\n")
|
|
)
|
|
if needsUnwrappedVar:
|
|
cgThings.append(
|
|
CGIfWrapper(
|
|
CGGeneric("unwrappedObj.emplace(cx, obj);\n"), "objIsXray"
|
|
)
|
|
)
|
|
|
|
for i in range(argConversionStartsAt, self.argCount):
|
|
cgThings.append(
|
|
CGArgumentConverter(
|
|
arguments[i],
|
|
i,
|
|
self.descriptor,
|
|
argDescription % {"index": i + 1},
|
|
idlNode,
|
|
invalidEnumValueFatal=not setter,
|
|
lenientFloatCode=lenientFloatCode,
|
|
)
|
|
)
|
|
|
|
# Now that argument processing is done, enforce the LenientFloat stuff
|
|
if lenientFloatCode:
|
|
if setter:
|
|
foundNonFiniteFloatBehavior = "return true;\n"
|
|
else:
|
|
assert idlNode.isMethod()
|
|
foundNonFiniteFloatBehavior = dedent(
|
|
"""
|
|
args.rval().setUndefined();
|
|
return true;
|
|
"""
|
|
)
|
|
cgThings.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
if (foundNonFiniteFloat) {
|
|
$*{returnSteps}
|
|
}
|
|
""",
|
|
returnSteps=foundNonFiniteFloatBehavior,
|
|
)
|
|
)
|
|
)
|
|
|
|
if needsUnwrap:
|
|
# Something depends on having the unwrapped object, so unwrap it now.
|
|
xraySteps = []
|
|
# XXXkhuey we should be able to MOZ_ASSERT that ${obj} is
|
|
# not null.
|
|
xraySteps.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
// Since our object is an Xray, we can just CheckedUnwrapStatic:
|
|
// we know Xrays have no dynamic unwrap behavior.
|
|
${obj} = js::CheckedUnwrapStatic(${obj});
|
|
if (!${obj}) {
|
|
return false;
|
|
}
|
|
""",
|
|
obj=unwrappedVar,
|
|
)
|
|
)
|
|
)
|
|
if isConstructor:
|
|
# If we're called via an xray, we need to enter the underlying
|
|
# object's compartment and then wrap up all of our arguments into
|
|
# that compartment as needed. This is all happening after we've
|
|
# already done the conversions from JS values to WebIDL (C++)
|
|
# values, so we only need to worry about cases where there are 'any'
|
|
# or 'object' types, or other things that we represent as actual
|
|
# JSAPI types, present. Effectively, we're emulating a
|
|
# CrossCompartmentWrapper, but working with the C++ types, not the
|
|
# original list of JS::Values.
|
|
cgThings.append(CGGeneric("Maybe<JSAutoRealm> ar;\n"))
|
|
xraySteps.append(CGGeneric("ar.emplace(cx, obj);\n"))
|
|
xraySteps.append(
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
if (!JS_WrapObject(cx, &desiredProto)) {
|
|
return false;
|
|
}
|
|
"""
|
|
)
|
|
)
|
|
)
|
|
xraySteps.extend(
|
|
wrapArgIntoCurrentCompartment(arg, argname, isMember=False)
|
|
for arg, argname in self.getArguments()
|
|
)
|
|
|
|
cgThings.append(CGIfWrapper(CGList(xraySteps), "objIsXray"))
|
|
|
|
if idlNode.getExtendedAttribute("CEReactions") is not None and not getter:
|
|
cgThings.append(
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
Maybe<AutoCEReaction> ceReaction;
|
|
DocGroup* docGroup = self->GetDocGroup();
|
|
if (docGroup) {
|
|
ceReaction.emplace(docGroup->CustomElementReactionsStack(), cx);
|
|
}
|
|
"""
|
|
)
|
|
)
|
|
)
|
|
|
|
# If this is a method that was generated by a maplike/setlike
|
|
# interface, use the maplike/setlike generator to fill in the body.
|
|
# Otherwise, use CGCallGenerator to call the native method.
|
|
if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod():
|
|
if (
|
|
idlNode.maplikeOrSetlikeOrIterable.isMaplike()
|
|
or idlNode.maplikeOrSetlikeOrIterable.isSetlike()
|
|
):
|
|
cgThings.append(
|
|
CGMaplikeOrSetlikeMethodGenerator(
|
|
descriptor,
|
|
idlNode.maplikeOrSetlikeOrIterable,
|
|
idlNode.identifier.name,
|
|
)
|
|
)
|
|
else:
|
|
cgThings.append(
|
|
CGIterableMethodGenerator(
|
|
descriptor,
|
|
idlNode.maplikeOrSetlikeOrIterable,
|
|
idlNode.identifier.name,
|
|
)
|
|
)
|
|
else:
|
|
context = GetLabelForErrorReporting(descriptor, idlNode, isConstructor)
|
|
if getter:
|
|
context = context + " getter"
|
|
elif setter:
|
|
context = context + " setter"
|
|
# Callee expects a quoted string for the context if
|
|
# there's a context.
|
|
context = '"%s"' % context
|
|
cgThings.append(
|
|
CGCallGenerator(
|
|
self.isFallible(),
|
|
needsCallerType(idlNode),
|
|
isChromeOnly(idlNode),
|
|
self.getArguments(),
|
|
argsPre,
|
|
returnType,
|
|
self.extendedAttributes,
|
|
descriptor,
|
|
nativeMethodName,
|
|
static,
|
|
# We know our "self" must be being kept alive; otherwise we have
|
|
# a serious problem. In common cases it's just an argument and
|
|
# we're MOZ_CAN_RUN_SCRIPT, but in some cases it's on the stack
|
|
# and being kept alive via references from JS.
|
|
object="MOZ_KnownLive(self)",
|
|
argsPost=argsPost,
|
|
resultVar=resultVar,
|
|
context=context,
|
|
)
|
|
)
|
|
|
|
if useCounterName:
|
|
# Generate a telemetry call for when [UseCounter] is used.
|
|
windowCode = fill(
|
|
"""
|
|
SetUseCounter(obj, eUseCounter_${useCounterName});
|
|
""",
|
|
useCounterName=useCounterName,
|
|
)
|
|
workerCode = fill(
|
|
"""
|
|
SetUseCounter(UseCounterWorker::${useCounterName});
|
|
""",
|
|
useCounterName=useCounterName,
|
|
)
|
|
code = ""
|
|
if idlNode.isExposedInWindow() and idlNode.isExposedInAnyWorker():
|
|
code += fill(
|
|
"""
|
|
if (NS_IsMainThread()) {
|
|
${windowCode}
|
|
} else {
|
|
${workerCode}
|
|
}
|
|
""",
|
|
windowCode=windowCode,
|
|
workerCode=workerCode,
|
|
)
|
|
elif idlNode.isExposedInWindow():
|
|
code += windowCode
|
|
elif idlNode.isExposedInAnyWorker():
|
|
code += workerCode
|
|
|
|
cgThings.append(CGGeneric(code))
|
|
|
|
self.cgRoot = CGList(cgThings)
|
|
|
|
def getArguments(self):
|
|
return [(a, "arg" + str(i)) for i, a in enumerate(self.arguments)]
|
|
|
|
def isFallible(self):
|
|
return "infallible" not in self.extendedAttributes
|
|
|
|
def wrap_return_value(self):
|
|
wrapCode = ""
|
|
|
|
returnsNewObject = memberReturnsNewObject(self.idlNode)
|
|
if returnsNewObject and (
|
|
self.returnType.isGeckoInterface() or self.returnType.isPromise()
|
|
):
|
|
wrapCode += dedent(
|
|
"""
|
|
static_assert(!std::is_pointer_v<decltype(result)>,
|
|
"NewObject implies that we need to keep the object alive with a strong reference.");
|
|
"""
|
|
)
|
|
|
|
if self.setSlot:
|
|
# For attributes in slots, we want to do some
|
|
# post-processing once we've wrapped them.
|
|
successCode = "break;\n"
|
|
else:
|
|
successCode = None
|
|
|
|
resultTemplateValues = {
|
|
"jsvalRef": "args.rval()",
|
|
"jsvalHandle": "args.rval()",
|
|
"returnsNewObject": returnsNewObject,
|
|
"isConstructorRetval": self.isConstructor,
|
|
"successCode": successCode,
|
|
# 'obj' in this dictionary is the thing whose compartment we are
|
|
# trying to do the to-JS conversion in. We're going to put that
|
|
# thing in a variable named "conversionScope" if setSlot is true.
|
|
# Otherwise, just use "obj" for lack of anything better.
|
|
"obj": "conversionScope" if self.setSlot else "obj",
|
|
}
|
|
|
|
wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues)
|
|
|
|
if self.setSlot:
|
|
if self.idlNode.isStatic():
|
|
raise TypeError(
|
|
"Attribute %s.%s is static, so we don't have a useful slot "
|
|
"to cache it in, because we don't have support for that on "
|
|
"interface objects. See "
|
|
"https://bugzilla.mozilla.org/show_bug.cgi?id=1363870"
|
|
% (
|
|
self.descriptor.interface.identifier.name,
|
|
self.idlNode.identifier.name,
|
|
)
|
|
)
|
|
|
|
# When using a slot on the Xray expando, we need to make sure that
|
|
# our initial conversion to a JS::Value is done in the caller
|
|
# compartment. When using a slot on our reflector, we want to do
|
|
# the conversion in the compartment of that reflector (that is,
|
|
# slotStorage). In both cases we want to make sure that we finally
|
|
# set up args.rval() to be in the caller compartment. We also need
|
|
# to make sure that the conversion steps happen inside a do/while
|
|
# that they can break out of on success.
|
|
#
|
|
# Of course we always have to wrap the value into the slotStorage
|
|
# compartment before we store it in slotStorage.
|
|
|
|
# postConversionSteps are the steps that run while we're still in
|
|
# the compartment we do our conversion in but after we've finished
|
|
# the initial conversion into args.rval().
|
|
postConversionSteps = ""
|
|
if needsContainsHack(self.idlNode):
|
|
# Define a .contains on the object that has the same value as
|
|
# .includes; needed for backwards compat in extensions as we
|
|
# migrate some DOMStringLists to FrozenArray.
|
|
postConversionSteps += dedent(
|
|
"""
|
|
if (args.rval().isObject() && nsContentUtils::ThreadsafeIsSystemCaller(cx)) {
|
|
JS::Rooted<JSObject*> rvalObj(cx, &args.rval().toObject());
|
|
JS::Rooted<JS::Value> includesVal(cx);
|
|
if (!JS_GetProperty(cx, rvalObj, "includes", &includesVal) ||
|
|
!JS_DefineProperty(cx, rvalObj, "contains", includesVal, JSPROP_ENUMERATE)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
"""
|
|
)
|
|
if self.idlNode.getExtendedAttribute("Frozen"):
|
|
assert (
|
|
self.idlNode.type.isSequence() or self.idlNode.type.isDictionary()
|
|
)
|
|
freezeValue = CGGeneric(
|
|
"JS::Rooted<JSObject*> rvalObj(cx, &args.rval().toObject());\n"
|
|
"if (!JS_FreezeObject(cx, rvalObj)) {\n"
|
|
" return false;\n"
|
|
"}\n"
|
|
)
|
|
if self.idlNode.type.nullable():
|
|
freezeValue = CGIfWrapper(freezeValue, "args.rval().isObject()")
|
|
postConversionSteps += freezeValue.define()
|
|
|
|
# slotStorageSteps are steps that run once we have entered the
|
|
# slotStorage compartment.
|
|
slotStorageSteps = fill(
|
|
"""
|
|
// Make a copy so that we don't do unnecessary wrapping on args.rval().
|
|
JS::Rooted<JS::Value> storedVal(cx, args.rval());
|
|
if (!${maybeWrap}(cx, &storedVal)) {
|
|
return false;
|
|
}
|
|
JS::SetReservedSlot(slotStorage, slotIndex, storedVal);
|
|
""",
|
|
maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type),
|
|
)
|
|
|
|
checkForXray = mayUseXrayExpandoSlots(self.descriptor, self.idlNode)
|
|
|
|
# For the case of Cached attributes, go ahead and preserve our
|
|
# wrapper if needed. We need to do this because otherwise the
|
|
# wrapper could get garbage-collected and the cached value would
|
|
# suddenly disappear, but the whole premise of cached values is that
|
|
# they never change without explicit action on someone's part. We
|
|
# don't do this for StoreInSlot, since those get dealt with during
|
|
# wrapper setup, and failure would involve us trying to clear an
|
|
# already-preserved wrapper.
|
|
if (
|
|
self.idlNode.getExtendedAttribute("Cached")
|
|
and self.descriptor.wrapperCache
|
|
):
|
|
preserveWrapper = dedent(
|
|
"""
|
|
PreserveWrapper(self);
|
|
"""
|
|
)
|
|
if checkForXray:
|
|
preserveWrapper = fill(
|
|
"""
|
|
if (!isXray) {
|
|
// In the Xray case we don't need to do this, because getting the
|
|
// expando object already preserved our wrapper.
|
|
$*{preserveWrapper}
|
|
}
|
|
""",
|
|
preserveWrapper=preserveWrapper,
|
|
)
|
|
slotStorageSteps += preserveWrapper
|
|
|
|
if checkForXray:
|
|
# In the Xray case we use the current global as conversion
|
|
# scope, as explained in the big compartment/conversion comment
|
|
# above.
|
|
conversionScope = "isXray ? JS::CurrentGlobalOrNull(cx) : slotStorage"
|
|
else:
|
|
conversionScope = "slotStorage"
|
|
|
|
wrapCode = fill(
|
|
"""
|
|
{
|
|
JS::Rooted<JSObject*> conversionScope(cx, ${conversionScope});
|
|
JSAutoRealm ar(cx, conversionScope);
|
|
do { // block we break out of when done wrapping
|
|
$*{wrapCode}
|
|
} while (false);
|
|
$*{postConversionSteps}
|
|
}
|
|
{ // And now store things in the realm of our slotStorage.
|
|
JSAutoRealm ar(cx, slotStorage);
|
|
$*{slotStorageSteps}
|
|
}
|
|
// And now make sure args.rval() is in the caller realm.
|
|
return ${maybeWrap}(cx, args.rval());
|
|
""",
|
|
conversionScope=conversionScope,
|
|
wrapCode=wrapCode,
|
|
postConversionSteps=postConversionSteps,
|
|
slotStorageSteps=slotStorageSteps,
|
|
maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type),
|
|
)
|
|
return wrapCode
|
|
|
|
def define(self):
|
|
return self.cgRoot.define() + self.wrap_return_value()
|
|
|
|
|
|
class CGSwitch(CGList):
|
|
"""
|
|
A class to generate code for a switch statement.
|
|
|
|
Takes three constructor arguments: an expression, a list of cases,
|
|
and an optional default.
|
|
|
|
Each case is a CGCase. The default is a CGThing for the body of
|
|
the default case, if any.
|
|
"""
|
|
|
|
def __init__(self, expression, cases, default=None):
|
|
CGList.__init__(self, [CGIndenter(c) for c in cases])
|
|
self.prepend(CGGeneric("switch (" + expression + ") {\n"))
|
|
if default is not None:
|
|
self.append(
|
|
CGIndenter(
|
|
CGWrapper(
|
|
CGIndenter(default), pre="default: {\n", post=" break;\n}\n"
|
|
)
|
|
)
|
|
)
|
|
|
|
self.append(CGGeneric("}\n"))
|
|
|
|
|
|
class CGCase(CGList):
|
|
"""
|
|
A class to generate code for a case statement.
|
|
|
|
Takes three constructor arguments: an expression, a CGThing for
|
|
the body (allowed to be None if there is no body), and an optional
|
|
argument (defaulting to False) for whether to fall through.
|
|
"""
|
|
|
|
def __init__(self, expression, body, fallThrough=False):
|
|
CGList.__init__(self, [])
|
|
self.append(CGGeneric("case " + expression + ": {\n"))
|
|
bodyList = CGList([body])
|
|
if fallThrough:
|
|
bodyList.append(CGGeneric("[[fallthrough]];\n"))
|
|
else:
|
|
bodyList.append(CGGeneric("break;\n"))
|
|
self.append(CGIndenter(bodyList))
|
|
self.append(CGGeneric("}\n"))
|
|
|
|
|
|
class CGMethodCall(CGThing):
|
|
"""
|
|
A class to generate selection of a method signature from a set of
|
|
signatures and generation of a call to that signature.
|
|
"""
|
|
|
|
def __init__(
|
|
self, nativeMethodName, static, descriptor, method, isConstructor=False
|
|
):
|
|
CGThing.__init__(self)
|
|
|
|
methodName = GetLabelForErrorReporting(descriptor, method, isConstructor)
|
|
argDesc = "argument %d"
|
|
|
|
if method.getExtendedAttribute("UseCounter"):
|
|
useCounterName = methodName.replace(".", "_")
|
|
else:
|
|
useCounterName = None
|
|
|
|
if method.isStatic():
|
|
nativeType = descriptor.nativeType
|
|
staticTypeOverride = PropertyDefiner.getStringAttr(
|
|
method, "StaticClassOverride"
|
|
)
|
|
if staticTypeOverride:
|
|
nativeType = staticTypeOverride
|
|
nativeMethodName = "%s::%s" % (nativeType, nativeMethodName)
|
|
|
|
def requiredArgCount(signature):
|
|
arguments = signature[1]
|
|
if len(arguments) == 0:
|
|
return 0
|
|
requiredArgs = len(arguments)
|
|
while requiredArgs and arguments[requiredArgs - 1].optional:
|
|
requiredArgs -= 1
|
|
return requiredArgs
|
|
|
|
def getPerSignatureCall(signature, argConversionStartsAt=0):
|
|
return CGPerSignatureCall(
|
|
signature[0],
|
|
signature[1],
|
|
nativeMethodName,
|
|
static,
|
|
descriptor,
|
|
method,
|
|
argConversionStartsAt=argConversionStartsAt,
|
|
isConstructor=isConstructor,
|
|
useCounterName=useCounterName,
|
|
)
|
|
|
|
signatures = method.signatures()
|
|
if len(signatures) == 1:
|
|
# Special case: we can just do a per-signature method call
|
|
# here for our one signature and not worry about switching
|
|
# on anything.
|
|
signature = signatures[0]
|
|
self.cgRoot = CGList([getPerSignatureCall(signature)])
|
|
requiredArgs = requiredArgCount(signature)
|
|
|
|
# Skip required arguments check for maplike/setlike interfaces, as
|
|
# they can have arguments which are not passed, and are treated as
|
|
# if undefined had been explicitly passed.
|
|
if requiredArgs > 0 and not method.isMaplikeOrSetlikeOrIterableMethod():
|
|
code = fill(
|
|
"""
|
|
if (!args.requireAtLeast(cx, "${methodName}", ${requiredArgs})) {
|
|
return false;
|
|
}
|
|
""",
|
|
requiredArgs=requiredArgs,
|
|
methodName=methodName,
|
|
)
|
|
self.cgRoot.prepend(CGGeneric(code))
|
|
return
|
|
|
|
# Need to find the right overload
|
|
maxArgCount = method.maxArgCount
|
|
allowedArgCounts = method.allowedArgCounts
|
|
|
|
argCountCases = []
|
|
for argCountIdx, argCount in enumerate(allowedArgCounts):
|
|
possibleSignatures = method.signaturesForArgCount(argCount)
|
|
|
|
# Try to optimize away cases when the next argCount in the list
|
|
# will have the same code as us; if it does, we can fall through to
|
|
# that case.
|
|
if argCountIdx + 1 < len(allowedArgCounts):
|
|
nextPossibleSignatures = method.signaturesForArgCount(
|
|
allowedArgCounts[argCountIdx + 1]
|
|
)
|
|
else:
|
|
nextPossibleSignatures = None
|
|
if possibleSignatures == nextPossibleSignatures:
|
|
# Same set of signatures means we better have the same
|
|
# distinguishing index. So we can in fact just fall through to
|
|
# the next case here.
|
|
assert len(possibleSignatures) == 1 or (
|
|
method.distinguishingIndexForArgCount(argCount)
|
|
== method.distinguishingIndexForArgCount(
|
|
allowedArgCounts[argCountIdx + 1]
|
|
)
|
|
)
|
|
argCountCases.append(CGCase(str(argCount), None, True))
|
|
continue
|
|
|
|
if len(possibleSignatures) == 1:
|
|
# easy case!
|
|
signature = possibleSignatures[0]
|
|
argCountCases.append(
|
|
CGCase(str(argCount), getPerSignatureCall(signature))
|
|
)
|
|
continue
|
|
|
|
distinguishingIndex = method.distinguishingIndexForArgCount(argCount)
|
|
|
|
def distinguishingArgument(signature):
|
|
args = signature[1]
|
|
if distinguishingIndex < len(args):
|
|
return args[distinguishingIndex]
|
|
assert args[-1].variadic
|
|
return args[-1]
|
|
|
|
def distinguishingType(signature):
|
|
return distinguishingArgument(signature).type
|
|
|
|
for sig in possibleSignatures:
|
|
# We should not have "any" args at distinguishingIndex,
|
|
# since we have multiple possible signatures remaining,
|
|
# but "any" is never distinguishable from anything else.
|
|
assert not distinguishingType(sig).isAny()
|
|
# We can't handle unions at the distinguishing index.
|
|
if distinguishingType(sig).isUnion():
|
|
raise TypeError(
|
|
"No support for unions as distinguishing "
|
|
"arguments yet: %s" % distinguishingArgument(sig).location
|
|
)
|
|
# We don't support variadics as the distinguishingArgument yet.
|
|
# If you want to add support, consider this case:
|
|
#
|
|
# void(long... foo);
|
|
# void(long bar, Int32Array baz);
|
|
#
|
|
# in which we have to convert argument 0 to long before picking
|
|
# an overload... but all the variadic stuff needs to go into a
|
|
# single array in case we pick that overload, so we have to have
|
|
# machinery for converting argument 0 to long and then either
|
|
# placing it in the variadic bit or not. Or something. We may
|
|
# be able to loosen this restriction if the variadic arg is in
|
|
# fact at distinguishingIndex, perhaps. Would need to
|
|
# double-check.
|
|
if distinguishingArgument(sig).variadic:
|
|
raise TypeError(
|
|
"No support for variadics as distinguishing "
|
|
"arguments yet: %s" % distinguishingArgument(sig).location
|
|
)
|
|
|
|
# Convert all our arguments up to the distinguishing index.
|
|
# Doesn't matter which of the possible signatures we use, since
|
|
# they all have the same types up to that point; just use
|
|
# possibleSignatures[0]
|
|
caseBody = [
|
|
CGArgumentConverter(
|
|
possibleSignatures[0][1][i],
|
|
i,
|
|
descriptor,
|
|
argDesc % (i + 1),
|
|
method,
|
|
)
|
|
for i in range(0, distinguishingIndex)
|
|
]
|
|
|
|
# Select the right overload from our set.
|
|
distinguishingArg = "args[%d]" % distinguishingIndex
|
|
|
|
def tryCall(
|
|
signature, indent, isDefinitelyObject=False, isNullOrUndefined=False
|
|
):
|
|
assert not isDefinitelyObject or not isNullOrUndefined
|
|
assert isDefinitelyObject or isNullOrUndefined
|
|
if isDefinitelyObject:
|
|
failureCode = "break;\n"
|
|
else:
|
|
failureCode = None
|
|
type = distinguishingType(signature)
|
|
# The argument at index distinguishingIndex can't possibly be
|
|
# unset here, because we've already checked that argc is large
|
|
# enough that we can examine this argument. But note that we
|
|
# still want to claim that optional arguments are optional, in
|
|
# case undefined was passed in.
|
|
argIsOptional = distinguishingArgument(signature).canHaveMissingValue()
|
|
testCode = instantiateJSToNativeConversion(
|
|
getJSToNativeConversionInfo(
|
|
type,
|
|
descriptor,
|
|
failureCode=failureCode,
|
|
isDefinitelyObject=isDefinitelyObject,
|
|
isNullOrUndefined=isNullOrUndefined,
|
|
isOptional=argIsOptional,
|
|
sourceDescription=(argDesc % (distinguishingIndex + 1)),
|
|
),
|
|
{
|
|
"declName": "arg%d" % distinguishingIndex,
|
|
"holderName": ("arg%d" % distinguishingIndex) + "_holder",
|
|
"val": distinguishingArg,
|
|
"obj": "obj",
|
|
"haveValue": "args.hasDefined(%d)" % distinguishingIndex,
|
|
"passedToJSImpl": toStringBool(
|
|
isJSImplementedDescriptor(descriptor)
|
|
),
|
|
},
|
|
checkForValue=argIsOptional,
|
|
)
|
|
caseBody.append(CGIndenter(testCode, indent))
|
|
|
|
# If we got this far, we know we unwrapped to the right
|
|
# C++ type, so just do the call. Start conversion with
|
|
# distinguishingIndex + 1, since we already converted
|
|
# distinguishingIndex.
|
|
caseBody.append(
|
|
CGIndenter(
|
|
getPerSignatureCall(signature, distinguishingIndex + 1), indent
|
|
)
|
|
)
|
|
|
|
def hasConditionalConversion(type):
|
|
"""
|
|
Return whether the argument conversion for this type will be
|
|
conditional on the type of incoming JS value. For example, for
|
|
interface types the conversion is conditional on the incoming
|
|
value being isObject().
|
|
|
|
For the types for which this returns false, we do not have to
|
|
output extra isUndefined() or isNullOrUndefined() cases, because
|
|
null/undefined values will just fall through into our
|
|
unconditional conversion.
|
|
"""
|
|
if type.isString() or type.isEnum():
|
|
return False
|
|
if type.isBoolean():
|
|
distinguishingTypes = (
|
|
distinguishingType(s) for s in possibleSignatures
|
|
)
|
|
return any(
|
|
t.isString() or t.isEnum() or t.isNumeric()
|
|
for t in distinguishingTypes
|
|
)
|
|
if type.isNumeric():
|
|
distinguishingTypes = (
|
|
distinguishingType(s) for s in possibleSignatures
|
|
)
|
|
return any(t.isString() or t.isEnum() for t in distinguishingTypes)
|
|
return True
|
|
|
|
def needsNullOrUndefinedCase(type):
|
|
"""
|
|
Return true if the type needs a special isNullOrUndefined() case
|
|
"""
|
|
return (
|
|
type.nullable() and hasConditionalConversion(type)
|
|
) or type.isDictionary()
|
|
|
|
# First check for undefined and optional distinguishing arguments
|
|
# and output a special branch for that case. Note that we don't
|
|
# use distinguishingArgument here because we actualy want to
|
|
# exclude variadic arguments. Also note that we skip this check if
|
|
# we plan to output a isNullOrUndefined() special case for this
|
|
# argument anyway, since that will subsume our isUndefined() check.
|
|
# This is safe, because there can be at most one nullable
|
|
# distinguishing argument, so if we're it we'll definitely get
|
|
# picked up by the nullable handling. Also, we can skip this check
|
|
# if the argument has an unconditional conversion later on.
|
|
undefSigs = [
|
|
s
|
|
for s in possibleSignatures
|
|
if distinguishingIndex < len(s[1])
|
|
and s[1][distinguishingIndex].optional
|
|
and hasConditionalConversion(s[1][distinguishingIndex].type)
|
|
and not needsNullOrUndefinedCase(s[1][distinguishingIndex].type)
|
|
]
|
|
# Can't have multiple signatures with an optional argument at the
|
|
# same index.
|
|
assert len(undefSigs) < 2
|
|
if len(undefSigs) > 0:
|
|
caseBody.append(
|
|
CGGeneric("if (%s.isUndefined()) {\n" % distinguishingArg)
|
|
)
|
|
tryCall(undefSigs[0], 2, isNullOrUndefined=True)
|
|
caseBody.append(CGGeneric("}\n"))
|
|
|
|
# Next, check for null or undefined. That means looking for
|
|
# nullable arguments at the distinguishing index and outputting a
|
|
# separate branch for them. But if the nullable argument has an
|
|
# unconditional conversion, we don't need to do that. The reason
|
|
# for that is that at most one argument at the distinguishing index
|
|
# is nullable (since two nullable arguments are not
|
|
# distinguishable), and null/undefined values will always fall
|
|
# through to the unconditional conversion we have, if any, since
|
|
# they will fail whatever the conditions on the input value are for
|
|
# our other conversions.
|
|
nullOrUndefSigs = [
|
|
s
|
|
for s in possibleSignatures
|
|
if needsNullOrUndefinedCase(distinguishingType(s))
|
|
]
|
|
# Can't have multiple nullable types here
|
|
assert len(nullOrUndefSigs) < 2
|
|
if len(nullOrUndefSigs) > 0:
|
|
caseBody.append(
|
|
CGGeneric("if (%s.isNullOrUndefined()) {\n" % distinguishingArg)
|
|
)
|
|
tryCall(nullOrUndefSigs[0], 2, isNullOrUndefined=True)
|
|
caseBody.append(CGGeneric("}\n"))
|
|
|
|
# Now check for distinguishingArg being various kinds of objects.
|
|
# The spec says to check for the following things in order:
|
|
# 1) A platform object that's not a platform array object, being
|
|
# passed to an interface or "object" arg.
|
|
# 2) A callable object being passed to a callback or "object" arg.
|
|
# 3) An iterable object being passed to a sequence arg.
|
|
# 4) Any object being passed to a array or callback interface or
|
|
# dictionary or "object" arg.
|
|
|
|
# First grab all the overloads that have a non-callback interface
|
|
# (which includes SpiderMonkey interfaces) at the distinguishing
|
|
# index. We can also include the ones that have an "object" here,
|
|
# since if those are present no other object-typed argument will
|
|
# be.
|
|
objectSigs = [
|
|
s
|
|
for s in possibleSignatures
|
|
if (
|
|
distinguishingType(s).isObject()
|
|
or distinguishingType(s).isNonCallbackInterface()
|
|
)
|
|
]
|
|
|
|
# And all the overloads that take callbacks
|
|
objectSigs.extend(
|
|
s for s in possibleSignatures if distinguishingType(s).isCallback()
|
|
)
|
|
|
|
# And all the overloads that take sequences
|
|
objectSigs.extend(
|
|
s for s in possibleSignatures if distinguishingType(s).isSequence()
|
|
)
|
|
|
|
# Now append all the overloads that take a dictionary or callback
|
|
# interface or record. There should be only one of these!
|
|
genericObjectSigs = [
|
|
s
|
|
for s in possibleSignatures
|
|
if (
|
|
distinguishingType(s).isDictionary()
|
|
or distinguishingType(s).isRecord()
|
|
or distinguishingType(s).isCallbackInterface()
|
|
)
|
|
]
|
|
assert len(genericObjectSigs) <= 1
|
|
objectSigs.extend(genericObjectSigs)
|
|
|
|
# There might be more than one thing in objectSigs; we need to check
|
|
# which ones we unwrap to.
|
|
if len(objectSigs) > 0:
|
|
# Here it's enough to guard on our argument being an object.
|
|
# The code for unwrapping non-callback interfaces, spiderMonkey
|
|
# interfaces, and sequences will just bail out and move
|
|
# on to the next overload if the object fails to unwrap
|
|
# correctly, while "object" accepts any object anyway. We
|
|
# could even not do the isObject() check up front here, but in
|
|
# cases where we have multiple object overloads it makes sense
|
|
# to do it only once instead of for each overload. That will
|
|
# also allow the unwrapping test to skip having to do codegen
|
|
# for the null-or-undefined case, which we already handled
|
|
# above.
|
|
caseBody.append(CGGeneric("if (%s.isObject()) {\n" % distinguishingArg))
|
|
for sig in objectSigs:
|
|
caseBody.append(CGIndenter(CGGeneric("do {\n")))
|
|
# Indent by 4, since we need to indent further
|
|
# than our "do" statement
|
|
tryCall(sig, 4, isDefinitelyObject=True)
|
|
caseBody.append(CGIndenter(CGGeneric("} while (false);\n")))
|
|
|
|
caseBody.append(CGGeneric("}\n"))
|
|
|
|
# Now we only have to consider booleans, numerics, and strings. If
|
|
# we only have one of them, then we can just output it. But if not,
|
|
# then we need to output some of the cases conditionally: if we have
|
|
# a string overload, then boolean and numeric are conditional, and
|
|
# if not then boolean is conditional if we have a numeric overload.
|
|
def findUniqueSignature(filterLambda):
|
|
sigs = [s for s in possibleSignatures if filterLambda(s)]
|
|
assert len(sigs) < 2
|
|
if len(sigs) > 0:
|
|
return sigs[0]
|
|
return None
|
|
|
|
stringSignature = findUniqueSignature(
|
|
lambda s: (
|
|
distinguishingType(s).isString() or distinguishingType(s).isEnum()
|
|
)
|
|
)
|
|
numericSignature = findUniqueSignature(
|
|
lambda s: distinguishingType(s).isNumeric()
|
|
)
|
|
booleanSignature = findUniqueSignature(
|
|
lambda s: distinguishingType(s).isBoolean()
|
|
)
|
|
|
|
if stringSignature or numericSignature:
|
|
booleanCondition = "%s.isBoolean()"
|
|
else:
|
|
booleanCondition = None
|
|
|
|
if stringSignature:
|
|
numericCondition = "%s.isNumber()"
|
|
else:
|
|
numericCondition = None
|
|
|
|
def addCase(sig, condition):
|
|
sigCode = getPerSignatureCall(sig, distinguishingIndex)
|
|
if condition:
|
|
sigCode = CGIfWrapper(sigCode, condition % distinguishingArg)
|
|
caseBody.append(sigCode)
|
|
|
|
if booleanSignature:
|
|
addCase(booleanSignature, booleanCondition)
|
|
if numericSignature:
|
|
addCase(numericSignature, numericCondition)
|
|
if stringSignature:
|
|
addCase(stringSignature, None)
|
|
|
|
if not booleanSignature and not numericSignature and not stringSignature:
|
|
# Just throw; we have no idea what we're supposed to
|
|
# do with this.
|
|
caseBody.append(
|
|
CGGeneric(
|
|
'return cx.ThrowErrorMessage<MSG_OVERLOAD_RESOLUTION_FAILED>("%d", "%d");\n'
|
|
% (distinguishingIndex + 1, argCount)
|
|
)
|
|
)
|
|
|
|
argCountCases.append(CGCase(str(argCount), CGList(caseBody)))
|
|
|
|
overloadCGThings = []
|
|
overloadCGThings.append(
|
|
CGGeneric(
|
|
"unsigned argcount = std::min(args.length(), %du);\n" % maxArgCount
|
|
)
|
|
)
|
|
overloadCGThings.append(
|
|
CGSwitch(
|
|
"argcount",
|
|
argCountCases,
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
// Using nsPrintfCString here would require including that
|
|
// header. Let's not worry about it.
|
|
nsAutoCString argCountStr;
|
|
argCountStr.AppendPrintf("%u", args.length());
|
|
return cx.ThrowErrorMessage<MSG_INVALID_OVERLOAD_ARGCOUNT>(argCountStr.get());
|
|
"""
|
|
)
|
|
),
|
|
)
|
|
)
|
|
overloadCGThings.append(
|
|
CGGeneric(
|
|
'MOZ_CRASH("We have an always-returning default case");\n'
|
|
"return false;\n"
|
|
)
|
|
)
|
|
self.cgRoot = CGList(overloadCGThings)
|
|
|
|
def define(self):
|
|
return self.cgRoot.define()
|
|
|
|
|
|
class CGGetterCall(CGPerSignatureCall):
|
|
"""
|
|
A class to generate a native object getter call for a particular IDL
|
|
getter.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
returnType,
|
|
nativeMethodName,
|
|
descriptor,
|
|
attr,
|
|
dontSetSlot=False,
|
|
extendedAttributes=None,
|
|
):
|
|
if attr.getExtendedAttribute("UseCounter"):
|
|
useCounterName = "%s_%s_getter" % (
|
|
descriptor.interface.identifier.name,
|
|
attr.identifier.name,
|
|
)
|
|
else:
|
|
useCounterName = None
|
|
if attr.isStatic():
|
|
nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName)
|
|
CGPerSignatureCall.__init__(
|
|
self,
|
|
returnType,
|
|
[],
|
|
nativeMethodName,
|
|
attr.isStatic(),
|
|
descriptor,
|
|
attr,
|
|
getter=True,
|
|
useCounterName=useCounterName,
|
|
dontSetSlot=dontSetSlot,
|
|
extendedAttributes=extendedAttributes,
|
|
)
|
|
|
|
|
|
class FakeIdentifier:
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
|
|
class FakeArgument:
|
|
"""
|
|
A class that quacks like an IDLArgument. This is used to make
|
|
setters look like method calls or for special operations.
|
|
"""
|
|
|
|
def __init__(
|
|
self, type, interfaceMember, name="arg", allowTreatNonCallableAsNull=False
|
|
):
|
|
self.type = type
|
|
self.optional = False
|
|
self.variadic = False
|
|
self.defaultValue = None
|
|
self._allowTreatNonCallableAsNull = allowTreatNonCallableAsNull
|
|
|
|
self.identifier = FakeIdentifier(name)
|
|
|
|
def allowTreatNonCallableAsNull(self):
|
|
return self._allowTreatNonCallableAsNull
|
|
|
|
def canHaveMissingValue(self):
|
|
return False
|
|
|
|
|
|
class CGSetterCall(CGPerSignatureCall):
|
|
"""
|
|
A class to generate a native object setter call for a particular IDL
|
|
setter.
|
|
"""
|
|
|
|
def __init__(self, argType, nativeMethodName, descriptor, attr):
|
|
if attr.getExtendedAttribute("UseCounter"):
|
|
useCounterName = "%s_%s_setter" % (
|
|
descriptor.interface.identifier.name,
|
|
attr.identifier.name,
|
|
)
|
|
else:
|
|
useCounterName = None
|
|
if attr.isStatic():
|
|
nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName)
|
|
CGPerSignatureCall.__init__(
|
|
self,
|
|
None,
|
|
[FakeArgument(argType, attr, allowTreatNonCallableAsNull=True)],
|
|
nativeMethodName,
|
|
attr.isStatic(),
|
|
descriptor,
|
|
attr,
|
|
setter=True,
|
|
useCounterName=useCounterName,
|
|
)
|
|
|
|
def wrap_return_value(self):
|
|
attr = self.idlNode
|
|
if self.descriptor.wrapperCache and attr.slotIndices is not None:
|
|
if attr.getExtendedAttribute("StoreInSlot"):
|
|
args = "cx, self"
|
|
else:
|
|
args = "self"
|
|
clearSlot = "%s(%s);\n" % (
|
|
MakeClearCachedValueNativeName(self.idlNode),
|
|
args,
|
|
)
|
|
else:
|
|
clearSlot = ""
|
|
|
|
# We have no return value
|
|
return "\n" "%s" "return true;\n" % clearSlot
|
|
|
|
|
|
class CGAbstractBindingMethod(CGAbstractStaticMethod):
|
|
"""
|
|
Common class to generate some of our class hooks. This will generate the
|
|
function declaration, get a reference to the JS object for our binding
|
|
object (which might be an argument of the class hook or something we get
|
|
from a JS::CallArgs), and unwrap into the right C++ type. Subclasses are
|
|
expected to override the generate_code function to do the rest of the work.
|
|
This function should return a CGThing which is already properly indented.
|
|
|
|
getThisObj should be code for getting a JSObject* for the binding
|
|
object. "" can be passed in if the binding object is already stored in
|
|
'obj'.
|
|
|
|
callArgs should be code for getting a JS::CallArgs into a variable
|
|
called 'args'. This can be "" if there is already such a variable
|
|
around or if the body does not need a JS::CallArgs.
|
|
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
name,
|
|
args,
|
|
getThisObj,
|
|
callArgs="JS::CallArgs args = JS::CallArgsFromVp(argc, vp);\n",
|
|
):
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, name, "bool", args, canRunScript=True
|
|
)
|
|
|
|
# This can't ever happen, because we only use this for class hooks.
|
|
self.unwrapFailureCode = fill(
|
|
"""
|
|
MOZ_CRASH("Unexpected object in '${name}' hook");
|
|
return false;
|
|
""",
|
|
name=name,
|
|
)
|
|
|
|
if getThisObj == "":
|
|
self.getThisObj = None
|
|
else:
|
|
self.getThisObj = CGGeneric(
|
|
"JS::Rooted<JSObject*> obj(cx, %s);\n" % getThisObj
|
|
)
|
|
self.callArgs = callArgs
|
|
|
|
def definition_body(self):
|
|
body = self.callArgs
|
|
if self.getThisObj is not None:
|
|
body += self.getThisObj.define() + "\n"
|
|
body += "%s* self;\n" % self.descriptor.nativeType
|
|
body += dedent(
|
|
"""
|
|
JS::Rooted<JS::Value> rootSelf(cx, JS::ObjectValue(*obj));
|
|
"""
|
|
)
|
|
|
|
body += str(
|
|
CastableObjectUnwrapper(
|
|
self.descriptor, "rootSelf", "&rootSelf", "self", self.unwrapFailureCode
|
|
)
|
|
)
|
|
|
|
return body + self.generate_code().define()
|
|
|
|
def generate_code(self):
|
|
assert False # Override me
|
|
|
|
|
|
class CGAbstractStaticBindingMethod(CGAbstractStaticMethod):
|
|
"""
|
|
Common class to generate the JSNatives for all our static methods, getters
|
|
and setters. This will generate the function declaration and unwrap the
|
|
global object. Subclasses are expected to override the generate_code
|
|
function to do the rest of the work. This function should return a
|
|
CGThing which is already properly indented.
|
|
"""
|
|
|
|
def __init__(self, descriptor, name):
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, name, "bool", JSNativeArguments(), canRunScript=True
|
|
)
|
|
|
|
def definition_body(self):
|
|
# Make sure that "obj" is in the same compartment as "cx", since we'll
|
|
# later use it to wrap return values.
|
|
unwrap = dedent(
|
|
"""
|
|
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
|
JS::Rooted<JSObject*> obj(cx, &args.callee());
|
|
|
|
"""
|
|
)
|
|
return unwrap + self.generate_code().define()
|
|
|
|
def generate_code(self):
|
|
assert False # Override me
|
|
|
|
|
|
def MakeNativeName(name):
|
|
return name[0].upper() + IDLToCIdentifier(name[1:])
|
|
|
|
|
|
def GetWebExposedName(idlObject, descriptor):
|
|
if idlObject == descriptor.operations["Stringifier"]:
|
|
return "toString"
|
|
name = idlObject.identifier.name
|
|
if name == "__namedsetter":
|
|
return "named setter"
|
|
if name == "__namedgetter":
|
|
return "named getter"
|
|
if name == "__indexedsetter":
|
|
return "indexed setter"
|
|
if name == "__indexedgetter":
|
|
return "indexed getter"
|
|
if name == "__legacycaller":
|
|
return "legacy caller"
|
|
return name
|
|
|
|
|
|
def GetConstructorNameForReporting(descriptor, ctor):
|
|
# Figure out the name of our constructor for reporting purposes.
|
|
# For unnamed webidl constructors, identifier.name is "constructor" but
|
|
# the name JS sees is the interface name; for named constructors
|
|
# identifier.name is the actual name.
|
|
ctorName = ctor.identifier.name
|
|
if ctorName == "constructor":
|
|
return descriptor.interface.identifier.name
|
|
return ctorName
|
|
|
|
|
|
def GetLabelForErrorReporting(descriptor, idlObject, isConstructor):
|
|
"""
|
|
descriptor is the descriptor for the interface involved
|
|
|
|
idlObject is the method (regular or static), attribute (regular or
|
|
static), or constructor (named or not) involved.
|
|
|
|
isConstructor is true if idlObject is a constructor and false otherwise.
|
|
"""
|
|
if isConstructor:
|
|
return "%s constructor" % GetConstructorNameForReporting(descriptor, idlObject)
|
|
|
|
namePrefix = descriptor.interface.identifier.name
|
|
name = GetWebExposedName(idlObject, descriptor)
|
|
if " " in name:
|
|
# It's got a space already, so just space-separate.
|
|
return "%s %s" % (namePrefix, name)
|
|
|
|
return "%s.%s" % (namePrefix, name)
|
|
|
|
|
|
class CGSpecializedMethod(CGAbstractStaticMethod):
|
|
"""
|
|
A class for generating the C++ code for a specialized method that the JIT
|
|
can call with lower overhead.
|
|
"""
|
|
|
|
def __init__(self, descriptor, method):
|
|
self.method = method
|
|
name = CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name))
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("void*", "void_self"),
|
|
Argument("const JSJitMethodCallArgs&", "args"),
|
|
]
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, name, "bool", args, canRunScript=True
|
|
)
|
|
|
|
def definition_body(self):
|
|
nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, self.method)
|
|
call = CGMethodCall(
|
|
nativeName, self.method.isStatic(), self.descriptor, self.method
|
|
).define()
|
|
prefix = ""
|
|
if self.method.getExtendedAttribute("CrossOriginCallable"):
|
|
for signature in self.method.signatures():
|
|
# non-void signatures would require us to deal with remote proxies for the
|
|
# return value here.
|
|
if not signature[0].isVoid():
|
|
raise TypeError(
|
|
"We don't support a method marked as CrossOriginCallable "
|
|
"with non-void return type"
|
|
)
|
|
prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
|
|
prefix = fill(
|
|
"""
|
|
// CrossOriginThisPolicy::UnwrapThisObject stores a ${nativeType}::RemoteProxy in void_self
|
|
// if obj is a proxy with a RemoteObjectProxy handler for the right type, or else it stores
|
|
// a ${nativeType}. If we get here from the JIT (without going through UnwrapThisObject) we
|
|
// know void_self contains a ${nativeType}; we don't have special cases in the JIT to deal
|
|
// with remote object proxies.
|
|
if (IsRemoteObjectProxy(obj, ${prototypeID})) {
|
|
auto* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
|
|
$*{call}
|
|
}
|
|
""",
|
|
prototypeID=prototypeID,
|
|
nativeType=self.descriptor.nativeType,
|
|
call=call,
|
|
)
|
|
return prefix + fill(
|
|
"""
|
|
auto* self = static_cast<${nativeType}*>(void_self);
|
|
$*{call}
|
|
""",
|
|
nativeType=self.descriptor.nativeType,
|
|
call=call,
|
|
)
|
|
|
|
def auto_profiler_label(self):
|
|
interface_name = self.descriptor.interface.identifier.name
|
|
method_name = self.method.identifier.name
|
|
return fill(
|
|
"""
|
|
AUTO_PROFILER_LABEL_DYNAMIC_FAST(
|
|
"${interface_name}", "${method_name}", DOM, cx,
|
|
uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_METHOD) |
|
|
uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
|
|
""",
|
|
interface_name=interface_name,
|
|
method_name=method_name,
|
|
)
|
|
|
|
@staticmethod
|
|
def should_have_method_description(descriptor, idlMethod):
|
|
"""
|
|
Returns whether the given IDL method (static, non-static, constructor)
|
|
should have a method description declaration, for use in error
|
|
reporting.
|
|
"""
|
|
# If a method has overloads, it needs a method description, because it
|
|
# can throw MSG_INVALID_OVERLOAD_ARGCOUNT at the very least.
|
|
if len(idlMethod.signatures()) != 1:
|
|
return True
|
|
|
|
# Methods with only one signature need a method description if one of
|
|
# their args needs it.
|
|
sig = idlMethod.signatures()[0]
|
|
args = sig[1]
|
|
return any(
|
|
idlTypeNeedsCallContext(
|
|
arg.type,
|
|
descriptor,
|
|
allowTreatNonCallableAsNull=arg.allowTreatNonCallableAsNull(),
|
|
)
|
|
for arg in args
|
|
)
|
|
|
|
@staticmethod
|
|
def error_reporting_label_helper(descriptor, idlMethod, isConstructor):
|
|
"""
|
|
Returns the method description to use for error reporting for the given
|
|
IDL method. Used to implement common error_reporting_label() functions
|
|
across different classes.
|
|
"""
|
|
if not CGSpecializedMethod.should_have_method_description(
|
|
descriptor, idlMethod
|
|
):
|
|
return None
|
|
return GetLabelForErrorReporting(descriptor, idlMethod, isConstructor)
|
|
|
|
def error_reporting_label(self):
|
|
return CGSpecializedMethod.error_reporting_label_helper(
|
|
self.descriptor, self.method, isConstructor=False
|
|
)
|
|
|
|
@staticmethod
|
|
def makeNativeName(descriptor, method):
|
|
if method.underlyingAttr:
|
|
return CGSpecializedGetter.makeNativeName(descriptor, method.underlyingAttr)
|
|
name = method.identifier.name
|
|
return MakeNativeName(descriptor.binaryNameFor(name))
|
|
|
|
|
|
class CGMethodPromiseWrapper(CGAbstractStaticMethod):
|
|
"""
|
|
A class for generating a wrapper around another method that will
|
|
convert exceptions to promises.
|
|
"""
|
|
|
|
def __init__(self, descriptor, methodToWrap):
|
|
self.method = methodToWrap
|
|
name = self.makeName(methodToWrap.name)
|
|
args = list(methodToWrap.args)
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, name, "bool", args, canRunScript=True
|
|
)
|
|
|
|
def definition_body(self):
|
|
return fill(
|
|
"""
|
|
bool ok = ${methodName}(${args});
|
|
if (ok) {
|
|
return true;
|
|
}
|
|
return ConvertExceptionToPromise(cx, args.rval());
|
|
""",
|
|
methodName=self.method.name,
|
|
args=", ".join(arg.name for arg in self.args),
|
|
)
|
|
|
|
@staticmethod
|
|
def makeName(methodName):
|
|
return methodName + "_promiseWrapper"
|
|
|
|
|
|
class CGDefaultToJSONMethod(CGSpecializedMethod):
|
|
def __init__(self, descriptor, method):
|
|
assert method.isDefaultToJSON()
|
|
CGSpecializedMethod.__init__(self, descriptor, method)
|
|
|
|
def definition_body(self):
|
|
ret = fill(
|
|
"""
|
|
auto* self = static_cast<${nativeType}*>(void_self);
|
|
JS::Rooted<JSObject*> result(cx, JS_NewPlainObject(cx));
|
|
if (!result) {
|
|
return false;
|
|
}
|
|
""",
|
|
nativeType=self.descriptor.nativeType,
|
|
)
|
|
|
|
jsonDescriptors = [self.descriptor]
|
|
interface = self.descriptor.interface.parent
|
|
while interface:
|
|
descriptor = self.descriptor.getDescriptor(interface.identifier.name)
|
|
if descriptor.hasDefaultToJSON:
|
|
jsonDescriptors.append(descriptor)
|
|
interface = interface.parent
|
|
|
|
# Iterate the array in reverse: oldest ancestor first
|
|
for descriptor in jsonDescriptors[::-1]:
|
|
ret += fill(
|
|
"""
|
|
if (!${parentclass}::CollectJSONAttributes(cx, obj, MOZ_KnownLive(self), result)) {
|
|
return false;
|
|
}
|
|
""",
|
|
parentclass=toBindingNamespace(descriptor.name),
|
|
)
|
|
ret += "args.rval().setObject(*result);\n" "return true;\n"
|
|
return ret
|
|
|
|
|
|
class CGLegacyCallHook(CGAbstractBindingMethod):
|
|
"""
|
|
Call hook for our object
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
self._legacycaller = descriptor.operations["LegacyCaller"]
|
|
# Our "self" is actually the callee in this case, not the thisval.
|
|
CGAbstractBindingMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
LEGACYCALLER_HOOK_NAME,
|
|
JSNativeArguments(),
|
|
getThisObj="&args.callee()",
|
|
)
|
|
|
|
def define(self):
|
|
if not self._legacycaller:
|
|
return ""
|
|
return CGAbstractBindingMethod.define(self)
|
|
|
|
def generate_code(self):
|
|
name = self._legacycaller.identifier.name
|
|
nativeName = MakeNativeName(self.descriptor.binaryNameFor(name))
|
|
return CGMethodCall(nativeName, False, self.descriptor, self._legacycaller)
|
|
|
|
def error_reporting_label(self):
|
|
# Should act like methods.
|
|
return CGSpecializedMethod.error_reporting_label_helper(
|
|
self.descriptor, self._legacycaller, isConstructor=False
|
|
)
|
|
|
|
|
|
class CGResolveHook(CGAbstractClassHook):
|
|
"""
|
|
Resolve hook for objects that have the NeedResolve extended attribute.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
assert descriptor.interface.getExtendedAttribute("NeedResolve")
|
|
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("bool*", "resolvedp"),
|
|
]
|
|
CGAbstractClassHook.__init__(self, descriptor, RESOLVE_HOOK_NAME, "bool", args)
|
|
|
|
def generate_code(self):
|
|
return dedent(
|
|
"""
|
|
JS::Rooted<JS::PropertyDescriptor> desc(cx);
|
|
if (!self->DoResolve(cx, obj, id, &desc)) {
|
|
return false;
|
|
}
|
|
if (!desc.object()) {
|
|
return true;
|
|
}
|
|
// If desc.value() is undefined, then the DoResolve call
|
|
// has already defined it on the object. Don't try to also
|
|
// define it.
|
|
if (!desc.value().isUndefined()) {
|
|
desc.attributesRef() |= JSPROP_RESOLVING;
|
|
if (!JS_DefinePropertyById(cx, obj, id, desc)) {
|
|
return false;
|
|
}
|
|
}
|
|
*resolvedp = true;
|
|
return true;
|
|
"""
|
|
)
|
|
|
|
def definition_body(self):
|
|
if self.descriptor.isGlobal():
|
|
# Resolve standard classes
|
|
prefix = dedent(
|
|
"""
|
|
if (!ResolveGlobal(cx, obj, id, resolvedp)) {
|
|
return false;
|
|
}
|
|
if (*resolvedp) {
|
|
return true;
|
|
}
|
|
|
|
"""
|
|
)
|
|
else:
|
|
prefix = ""
|
|
return prefix + CGAbstractClassHook.definition_body(self)
|
|
|
|
|
|
class CGMayResolveHook(CGAbstractStaticMethod):
|
|
"""
|
|
Resolve hook for objects that have the NeedResolve extended attribute.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
assert descriptor.interface.getExtendedAttribute("NeedResolve")
|
|
|
|
args = [
|
|
Argument("const JSAtomState&", "names"),
|
|
Argument("jsid", "id"),
|
|
Argument("JSObject*", "maybeObj"),
|
|
]
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, MAY_RESOLVE_HOOK_NAME, "bool", args
|
|
)
|
|
|
|
def definition_body(self):
|
|
if self.descriptor.isGlobal():
|
|
# Check whether this would resolve as a standard class.
|
|
prefix = dedent(
|
|
"""
|
|
if (MayResolveGlobal(names, id, maybeObj)) {
|
|
return true;
|
|
}
|
|
|
|
"""
|
|
)
|
|
else:
|
|
prefix = ""
|
|
return prefix + "return %s::MayResolve(id);\n" % self.descriptor.nativeType
|
|
|
|
|
|
class CGEnumerateHook(CGAbstractBindingMethod):
|
|
"""
|
|
Enumerate hook for objects with custom hooks.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
assert descriptor.interface.getExtendedAttribute("NeedResolve")
|
|
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("JS::MutableHandleVector<jsid>", "properties"),
|
|
Argument("bool", "enumerableOnly"),
|
|
]
|
|
# Our "self" is actually the "obj" argument in this case, not the thisval.
|
|
CGAbstractBindingMethod.__init__(
|
|
self, descriptor, NEW_ENUMERATE_HOOK_NAME, args, getThisObj="", callArgs=""
|
|
)
|
|
|
|
def generate_code(self):
|
|
return CGGeneric(
|
|
dedent(
|
|
"""
|
|
FastErrorResult rv;
|
|
self->GetOwnPropertyNames(cx, properties, enumerableOnly, rv);
|
|
if (rv.MaybeSetPendingException(cx)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
"""
|
|
)
|
|
)
|
|
|
|
def definition_body(self):
|
|
if self.descriptor.isGlobal():
|
|
# Enumerate standard classes
|
|
prefix = dedent(
|
|
"""
|
|
if (!EnumerateGlobal(cx, obj, properties, enumerableOnly)) {
|
|
return false;
|
|
}
|
|
|
|
"""
|
|
)
|
|
else:
|
|
prefix = ""
|
|
return prefix + CGAbstractBindingMethod.definition_body(self)
|
|
|
|
|
|
class CppKeywords:
|
|
"""
|
|
A class for checking if method names declared in webidl
|
|
are not in conflict with C++ keywords.
|
|
"""
|
|
|
|
keywords = frozenset(
|
|
[
|
|
"alignas",
|
|
"alignof",
|
|
"and",
|
|
"and_eq",
|
|
"asm",
|
|
"assert",
|
|
"auto",
|
|
"bitand",
|
|
"bitor",
|
|
"bool",
|
|
"break",
|
|
"case",
|
|
"catch",
|
|
"char",
|
|
"char16_t",
|
|
"char32_t",
|
|
"class",
|
|
"compl",
|
|
"const",
|
|
"constexpr",
|
|
"const_cast",
|
|
"continue",
|
|
"decltype",
|
|
"default",
|
|
"delete",
|
|
"do",
|
|
"double",
|
|
"dynamic_cast",
|
|
"else",
|
|
"enum",
|
|
"explicit",
|
|
"export",
|
|
"extern",
|
|
"false",
|
|
"final",
|
|
"float",
|
|
"for",
|
|
"friend",
|
|
"goto",
|
|
"if",
|
|
"inline",
|
|
"int",
|
|
"long",
|
|
"mutable",
|
|
"namespace",
|
|
"new",
|
|
"noexcept",
|
|
"not",
|
|
"not_eq",
|
|
"nullptr",
|
|
"operator",
|
|
"or",
|
|
"or_eq",
|
|
"override",
|
|
"private",
|
|
"protected",
|
|
"public",
|
|
"register",
|
|
"reinterpret_cast",
|
|
"return",
|
|
"short",
|
|
"signed",
|
|
"sizeof",
|
|
"static",
|
|
"static_assert",
|
|
"static_cast",
|
|
"struct",
|
|
"switch",
|
|
"template",
|
|
"this",
|
|
"thread_local",
|
|
"throw",
|
|
"true",
|
|
"try",
|
|
"typedef",
|
|
"typeid",
|
|
"typename",
|
|
"union",
|
|
"unsigned",
|
|
"using",
|
|
"virtual",
|
|
"void",
|
|
"volatile",
|
|
"wchar_t",
|
|
"while",
|
|
"xor",
|
|
"xor_eq",
|
|
]
|
|
)
|
|
|
|
@staticmethod
|
|
def checkMethodName(name):
|
|
# Double '_' because 'assert' and '_assert' cannot be used in MS2013 compiler.
|
|
# Bug 964892 and bug 963560.
|
|
if name in CppKeywords.keywords:
|
|
name = "_" + name + "_"
|
|
return name
|
|
|
|
|
|
class CGStaticMethod(CGAbstractStaticBindingMethod):
|
|
"""
|
|
A class for generating the C++ code for an IDL static method.
|
|
"""
|
|
|
|
def __init__(self, descriptor, method):
|
|
self.method = method
|
|
name = CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name))
|
|
CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
|
|
|
|
def generate_code(self):
|
|
nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, self.method)
|
|
return CGMethodCall(nativeName, True, self.descriptor, self.method)
|
|
|
|
def auto_profiler_label(self):
|
|
interface_name = self.descriptor.interface.identifier.name
|
|
method_name = self.method.identifier.name
|
|
return fill(
|
|
"""
|
|
AUTO_PROFILER_LABEL_DYNAMIC_FAST(
|
|
"${interface_name}", "${method_name}", DOM, cx,
|
|
uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_METHOD) |
|
|
uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
|
|
""",
|
|
interface_name=interface_name,
|
|
method_name=method_name,
|
|
)
|
|
|
|
def error_reporting_label(self):
|
|
return CGSpecializedMethod.error_reporting_label_helper(
|
|
self.descriptor, self.method, isConstructor=False
|
|
)
|
|
|
|
|
|
class CGSpecializedGetter(CGAbstractStaticMethod):
|
|
"""
|
|
A class for generating the code for a specialized attribute getter
|
|
that the JIT can call with lower overhead.
|
|
"""
|
|
|
|
def __init__(self, descriptor, attr):
|
|
self.attr = attr
|
|
name = "get_" + IDLToCIdentifier(attr.identifier.name)
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("void*", "void_self"),
|
|
Argument("JSJitGetterCallArgs", "args"),
|
|
]
|
|
# StoreInSlot attributes have their getters called from Wrap(). We
|
|
# really hope they can't run script, and don't want to annotate Wrap()
|
|
# methods as doing that anyway, so let's not annotate them as
|
|
# MOZ_CAN_RUN_SCRIPT.
|
|
CGAbstractStaticMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
name,
|
|
"bool",
|
|
args,
|
|
canRunScript=not attr.getExtendedAttribute("StoreInSlot"),
|
|
)
|
|
|
|
def definition_body(self):
|
|
prefix = fill(
|
|
"""
|
|
auto* self = static_cast<${nativeType}*>(void_self);
|
|
""",
|
|
nativeType=self.descriptor.nativeType,
|
|
)
|
|
|
|
if self.attr.isMaplikeOrSetlikeAttr():
|
|
assert not self.attr.getExtendedAttribute("CrossOriginReadable")
|
|
# If the interface is maplike/setlike, there will be one getter
|
|
# method for the size property of the backing object. Due to having
|
|
# to unpack the backing object from the slot, this requires its own
|
|
# generator.
|
|
return prefix + getMaplikeOrSetlikeSizeGetterBody(
|
|
self.descriptor, self.attr
|
|
)
|
|
nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, self.attr)
|
|
type = self.attr.type
|
|
if self.attr.getExtendedAttribute("CrossOriginReadable"):
|
|
remoteType = type
|
|
extendedAttributes = self.descriptor.getExtendedAttributes(
|
|
self.attr, getter=True
|
|
)
|
|
if (
|
|
remoteType.isGeckoInterface()
|
|
and not remoteType.unroll().inner.isExternal()
|
|
and remoteType.unroll().inner.getExtendedAttribute("ChromeOnly") is None
|
|
):
|
|
# We'll use a JSObject. It might make more sense to use remoteType's
|
|
# RemoteProxy, but it's not easy to construct a type for that from here.
|
|
remoteType = BuiltinTypes[IDLBuiltinType.Types.object]
|
|
extendedAttributes.remove("infallible")
|
|
prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
|
|
prefix = (
|
|
fill(
|
|
"""
|
|
if (IsRemoteObjectProxy(obj, ${prototypeID})) {
|
|
${nativeType}::RemoteProxy* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
|
|
$*{call}
|
|
}
|
|
""",
|
|
prototypeID=prototypeID,
|
|
nativeType=self.descriptor.nativeType,
|
|
call=CGGetterCall(
|
|
remoteType,
|
|
nativeName,
|
|
self.descriptor,
|
|
self.attr,
|
|
dontSetSlot=True,
|
|
extendedAttributes=extendedAttributes,
|
|
).define(),
|
|
)
|
|
+ prefix
|
|
)
|
|
|
|
if self.attr.slotIndices is not None:
|
|
# We're going to store this return value in a slot on some object,
|
|
# to cache it. The question is, which object? For dictionary and
|
|
# sequence return values, we want to use a slot on the Xray expando
|
|
# if we're called via Xrays, and a slot on our reflector otherwise.
|
|
# On the other hand, when dealing with some interfacce types
|
|
# (e.g. window.document) we want to avoid calling the getter more
|
|
# than once. In the case of window.document, it's because the
|
|
# getter can start returning null, which would get hidden in the
|
|
# non-Xray case by the fact that it's [StoreOnSlot], so the cached
|
|
# version is always around.
|
|
#
|
|
# The upshot is that we use the reflector slot for any getter whose
|
|
# type is a gecko interface, whether we're called via Xrays or not.
|
|
# Since [Cached] and [StoreInSlot] cannot be used with "NewObject",
|
|
# we know that in the interface type case the returned object is
|
|
# wrappercached. So creating Xrays to it is reasonable.
|
|
if mayUseXrayExpandoSlots(self.descriptor, self.attr):
|
|
prefix += fill(
|
|
"""
|
|
// Have to either root across the getter call or reget after.
|
|
bool isXray;
|
|
JS::Rooted<JSObject*> slotStorage(cx, GetCachedSlotStorageObject(cx, obj, &isXray));
|
|
if (!slotStorage) {
|
|
return false;
|
|
}
|
|
const size_t slotIndex = isXray ? ${xraySlotIndex} : ${slotIndex};
|
|
""",
|
|
xraySlotIndex=memberXrayExpandoReservedSlot(
|
|
self.attr, self.descriptor
|
|
),
|
|
slotIndex=memberReservedSlot(self.attr, self.descriptor),
|
|
)
|
|
else:
|
|
prefix += fill(
|
|
"""
|
|
// Have to either root across the getter call or reget after.
|
|
JS::Rooted<JSObject*> slotStorage(cx, js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false));
|
|
MOZ_ASSERT(IsDOMObject(slotStorage));
|
|
const size_t slotIndex = ${slotIndex};
|
|
""",
|
|
slotIndex=memberReservedSlot(self.attr, self.descriptor),
|
|
)
|
|
|
|
prefix += fill(
|
|
"""
|
|
MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(JS::GetClass(slotStorage)) > slotIndex);
|
|
{
|
|
// Scope for cachedVal
|
|
JS::Value cachedVal = JS::GetReservedSlot(slotStorage, slotIndex);
|
|
if (!cachedVal.isUndefined()) {
|
|
args.rval().set(cachedVal);
|
|
// The cached value is in the compartment of slotStorage,
|
|
// so wrap into the caller compartment as needed.
|
|
return ${maybeWrap}(cx, args.rval());
|
|
}
|
|
}
|
|
|
|
""",
|
|
maybeWrap=getMaybeWrapValueFuncForType(self.attr.type),
|
|
)
|
|
|
|
return (
|
|
prefix + CGGetterCall(type, nativeName, self.descriptor, self.attr).define()
|
|
)
|
|
|
|
def auto_profiler_label(self):
|
|
interface_name = self.descriptor.interface.identifier.name
|
|
attr_name = self.attr.identifier.name
|
|
return fill(
|
|
"""
|
|
AUTO_PROFILER_LABEL_DYNAMIC_FAST(
|
|
"${interface_name}", "${attr_name}", DOM, cx,
|
|
uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_GETTER) |
|
|
uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
|
|
""",
|
|
interface_name=interface_name,
|
|
attr_name=attr_name,
|
|
)
|
|
|
|
def error_reporting_label(self):
|
|
# Getters never need a BindingCallContext.
|
|
return None
|
|
|
|
@staticmethod
|
|
def makeNativeName(descriptor, attr):
|
|
name = attr.identifier.name
|
|
nativeName = MakeNativeName(descriptor.binaryNameFor(name))
|
|
_, resultOutParam, _, _, _ = getRetvalDeclarationForType(attr.type, descriptor)
|
|
extendedAttrs = descriptor.getExtendedAttributes(attr, getter=True)
|
|
canFail = "infallible" not in extendedAttrs or "canOOM" in extendedAttrs
|
|
if resultOutParam or attr.type.nullable() or canFail:
|
|
nativeName = "Get" + nativeName
|
|
return nativeName
|
|
|
|
|
|
class CGGetterPromiseWrapper(CGAbstractStaticMethod):
|
|
"""
|
|
A class for generating a wrapper around another getter that will
|
|
convert exceptions to promises.
|
|
"""
|
|
|
|
def __init__(self, descriptor, getterToWrap):
|
|
self.getter = getterToWrap
|
|
name = self.makeName(getterToWrap.name)
|
|
args = list(getterToWrap.args)
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, name, "bool", args, canRunScript=True
|
|
)
|
|
|
|
def definition_body(self):
|
|
return fill(
|
|
"""
|
|
bool ok = ${getterName}(${args});
|
|
if (ok) {
|
|
return true;
|
|
}
|
|
return ConvertExceptionToPromise(cx, args.rval());
|
|
""",
|
|
getterName=self.getter.name,
|
|
args=", ".join(arg.name for arg in self.args),
|
|
)
|
|
|
|
@staticmethod
|
|
def makeName(getterName):
|
|
return getterName + "_promiseWrapper"
|
|
|
|
|
|
class CGStaticGetter(CGAbstractStaticBindingMethod):
|
|
"""
|
|
A class for generating the C++ code for an IDL static attribute getter.
|
|
"""
|
|
|
|
def __init__(self, descriptor, attr):
|
|
self.attr = attr
|
|
name = "get_" + IDLToCIdentifier(attr.identifier.name)
|
|
CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
|
|
|
|
def generate_code(self):
|
|
nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, self.attr)
|
|
return CGGetterCall(self.attr.type, nativeName, self.descriptor, self.attr)
|
|
|
|
def auto_profiler_label(self):
|
|
interface_name = self.descriptor.interface.identifier.name
|
|
attr_name = self.attr.identifier.name
|
|
return fill(
|
|
"""
|
|
AUTO_PROFILER_LABEL_DYNAMIC_FAST(
|
|
"${interface_name}", "${attr_name}", DOM, cx,
|
|
uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_GETTER) |
|
|
uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
|
|
""",
|
|
interface_name=interface_name,
|
|
attr_name=attr_name,
|
|
)
|
|
|
|
def error_reporting_label(self):
|
|
# Getters never need a BindingCallContext.
|
|
return None
|
|
|
|
|
|
class CGSpecializedSetter(CGAbstractStaticMethod):
|
|
"""
|
|
A class for generating the code for a specialized attribute setter
|
|
that the JIT can call with lower overhead.
|
|
"""
|
|
|
|
def __init__(self, descriptor, attr):
|
|
self.attr = attr
|
|
name = "set_" + IDLToCIdentifier(attr.identifier.name)
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("void*", "void_self"),
|
|
Argument("JSJitSetterCallArgs", "args"),
|
|
]
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, name, "bool", args, canRunScript=True
|
|
)
|
|
|
|
def definition_body(self):
|
|
nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, self.attr)
|
|
type = self.attr.type
|
|
call = CGSetterCall(type, nativeName, self.descriptor, self.attr).define()
|
|
prefix = ""
|
|
if self.attr.getExtendedAttribute("CrossOriginWritable"):
|
|
if type.isGeckoInterface() and not type.unroll().inner.isExternal():
|
|
# a setter taking a Gecko interface would require us to deal with remote
|
|
# proxies for the value here.
|
|
raise TypeError(
|
|
"We don't support the setter of %s marked as "
|
|
"CrossOriginWritable because it takes a Gecko interface "
|
|
"as the value",
|
|
attr.identifier.name,
|
|
)
|
|
prototypeID, _ = PrototypeIDAndDepth(self.descriptor)
|
|
prefix = fill(
|
|
"""
|
|
if (IsRemoteObjectProxy(obj, ${prototypeID})) {
|
|
auto* self = static_cast<${nativeType}::RemoteProxy*>(void_self);
|
|
$*{call}
|
|
}
|
|
""",
|
|
prototypeID=prototypeID,
|
|
nativeType=self.descriptor.nativeType,
|
|
call=call,
|
|
)
|
|
return prefix + fill(
|
|
"""
|
|
auto* self = static_cast<${nativeType}*>(void_self);
|
|
$*{call}
|
|
""",
|
|
nativeType=self.descriptor.nativeType,
|
|
call=call,
|
|
)
|
|
|
|
def auto_profiler_label(self):
|
|
interface_name = self.descriptor.interface.identifier.name
|
|
attr_name = self.attr.identifier.name
|
|
return fill(
|
|
"""
|
|
AUTO_PROFILER_LABEL_DYNAMIC_FAST(
|
|
"${interface_name}", "${attr_name}", DOM, cx,
|
|
uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_SETTER) |
|
|
uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
|
|
""",
|
|
interface_name=interface_name,
|
|
attr_name=attr_name,
|
|
)
|
|
|
|
@staticmethod
|
|
def error_reporting_label_helper(descriptor, attr):
|
|
# Setters need a BindingCallContext if the type of the attribute needs
|
|
# one.
|
|
if not idlTypeNeedsCallContext(
|
|
attr.type, descriptor, allowTreatNonCallableAsNull=True
|
|
):
|
|
return None
|
|
return (
|
|
GetLabelForErrorReporting(descriptor, attr, isConstructor=False) + " setter"
|
|
)
|
|
|
|
def error_reporting_label(self):
|
|
return CGSpecializedSetter.error_reporting_label_helper(
|
|
self.descriptor, self.attr
|
|
)
|
|
|
|
@staticmethod
|
|
def makeNativeName(descriptor, attr):
|
|
name = attr.identifier.name
|
|
return "Set" + MakeNativeName(descriptor.binaryNameFor(name))
|
|
|
|
|
|
class CGStaticSetter(CGAbstractStaticBindingMethod):
|
|
"""
|
|
A class for generating the C++ code for an IDL static attribute setter.
|
|
"""
|
|
|
|
def __init__(self, descriptor, attr):
|
|
self.attr = attr
|
|
name = "set_" + IDLToCIdentifier(attr.identifier.name)
|
|
CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
|
|
|
|
def generate_code(self):
|
|
nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, self.attr)
|
|
checkForArg = CGGeneric(
|
|
fill(
|
|
"""
|
|
if (!args.requireAtLeast(cx, "${name} setter", 1)) {
|
|
return false;
|
|
}
|
|
""",
|
|
name=self.attr.identifier.name,
|
|
)
|
|
)
|
|
call = CGSetterCall(self.attr.type, nativeName, self.descriptor, self.attr)
|
|
return CGList([checkForArg, call])
|
|
|
|
def auto_profiler_label(self):
|
|
interface_name = self.descriptor.interface.identifier.name
|
|
attr_name = self.attr.identifier.name
|
|
return fill(
|
|
"""
|
|
AUTO_PROFILER_LABEL_DYNAMIC_FAST(
|
|
"${interface_name}", "${attr_name}", DOM, cx,
|
|
uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_SETTER) |
|
|
uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
|
|
""",
|
|
interface_name=interface_name,
|
|
attr_name=attr_name,
|
|
)
|
|
|
|
def error_reporting_label(self):
|
|
return CGSpecializedSetter.error_reporting_label_helper(
|
|
self.descriptor, self.attr
|
|
)
|
|
|
|
|
|
class CGSpecializedForwardingSetter(CGSpecializedSetter):
|
|
"""
|
|
A class for generating the code for a specialized attribute setter with
|
|
PutForwards that the JIT can call with lower overhead.
|
|
"""
|
|
|
|
def __init__(self, descriptor, attr):
|
|
CGSpecializedSetter.__init__(self, descriptor, attr)
|
|
|
|
def definition_body(self):
|
|
attrName = self.attr.identifier.name
|
|
forwardToAttrName = self.attr.getExtendedAttribute("PutForwards")[0]
|
|
# JS_GetProperty and JS_SetProperty can only deal with ASCII
|
|
assert all(ord(c) < 128 for c in attrName)
|
|
assert all(ord(c) < 128 for c in forwardToAttrName)
|
|
return fill(
|
|
"""
|
|
JS::Rooted<JS::Value> v(cx);
|
|
if (!JS_GetProperty(cx, obj, "${attr}", &v)) {
|
|
return false;
|
|
}
|
|
|
|
if (!v.isObject()) {
|
|
return cx.ThrowErrorMessage<MSG_NOT_OBJECT>("${interface}.${attr}");
|
|
}
|
|
|
|
JS::Rooted<JSObject*> targetObj(cx, &v.toObject());
|
|
return JS_SetProperty(cx, targetObj, "${forwardToAttrName}", args[0]);
|
|
""",
|
|
attr=attrName,
|
|
interface=self.descriptor.interface.identifier.name,
|
|
forwardToAttrName=forwardToAttrName,
|
|
)
|
|
|
|
def error_reporting_label(self):
|
|
# We always need to be able to throw.
|
|
return (
|
|
GetLabelForErrorReporting(self.descriptor, self.attr, isConstructor=False)
|
|
+ " setter"
|
|
)
|
|
|
|
|
|
class CGSpecializedReplaceableSetter(CGSpecializedSetter):
|
|
"""
|
|
A class for generating the code for a specialized attribute setter with
|
|
Replaceable that the JIT can call with lower overhead.
|
|
"""
|
|
|
|
def __init__(self, descriptor, attr):
|
|
CGSpecializedSetter.__init__(self, descriptor, attr)
|
|
|
|
def definition_body(self):
|
|
attrName = self.attr.identifier.name
|
|
# JS_DefineProperty can only deal with ASCII
|
|
assert all(ord(c) < 128 for c in attrName)
|
|
return (
|
|
'return JS_DefineProperty(cx, obj, "%s", args[0], JSPROP_ENUMERATE);\n'
|
|
% attrName
|
|
)
|
|
|
|
def error_reporting_label(self):
|
|
# We never throw directly.
|
|
return None
|
|
|
|
|
|
class CGSpecializedLenientSetter(CGSpecializedSetter):
|
|
"""
|
|
A class for generating the code for a specialized attribute setter with
|
|
LenientSetter that the JIT can call with lower overhead.
|
|
"""
|
|
|
|
def __init__(self, descriptor, attr):
|
|
CGSpecializedSetter.__init__(self, descriptor, attr)
|
|
|
|
def definition_body(self):
|
|
attrName = self.attr.identifier.name
|
|
# JS_DefineProperty can only deal with ASCII
|
|
assert all(ord(c) < 128 for c in attrName)
|
|
return dedent(
|
|
"""
|
|
DeprecationWarning(cx, obj, Document::eLenientSetter);
|
|
return true;
|
|
"""
|
|
)
|
|
|
|
def error_reporting_label(self):
|
|
# We never throw; that's the whole point.
|
|
return None
|
|
|
|
|
|
def memberReturnsNewObject(member):
|
|
return member.getExtendedAttribute("NewObject") is not None
|
|
|
|
|
|
class CGMemberJITInfo(CGThing):
|
|
"""
|
|
A class for generating the JITInfo for a property that points to
|
|
our specialized getter and setter.
|
|
"""
|
|
|
|
def __init__(self, descriptor, member):
|
|
self.member = member
|
|
self.descriptor = descriptor
|
|
|
|
def declare(self):
|
|
return ""
|
|
|
|
def defineJitInfo(
|
|
self,
|
|
infoName,
|
|
opName,
|
|
opType,
|
|
infallible,
|
|
movable,
|
|
eliminatable,
|
|
aliasSet,
|
|
alwaysInSlot,
|
|
lazilyInSlot,
|
|
slotIndex,
|
|
returnTypes,
|
|
args,
|
|
):
|
|
"""
|
|
aliasSet is a JSJitInfo::AliasSet value, without the "JSJitInfo::" bit.
|
|
|
|
args is None if we don't want to output argTypes for some
|
|
reason (e.g. we have overloads or we're not a method) and
|
|
otherwise an iterable of the arguments for this method.
|
|
"""
|
|
assert (
|
|
not movable or aliasSet != "AliasEverything"
|
|
) # Can't move write-aliasing things
|
|
assert (
|
|
not alwaysInSlot or movable
|
|
) # Things always in slots had better be movable
|
|
assert (
|
|
not eliminatable or aliasSet != "AliasEverything"
|
|
) # Can't eliminate write-aliasing things
|
|
assert (
|
|
not alwaysInSlot or eliminatable
|
|
) # Things always in slots had better be eliminatable
|
|
|
|
def jitInfoInitializer(isTypedMethod):
|
|
initializer = fill(
|
|
"""
|
|
{
|
|
{ ${opName} },
|
|
{ prototypes::id::${name} },
|
|
{ PrototypeTraits<prototypes::id::${name}>::Depth },
|
|
JSJitInfo::${opType},
|
|
JSJitInfo::${aliasSet}, /* aliasSet. Not relevant for setters. */
|
|
${returnType}, /* returnType. Not relevant for setters. */
|
|
${isInfallible}, /* isInfallible. False in setters. */
|
|
${isMovable}, /* isMovable. Not relevant for setters. */
|
|
${isEliminatable}, /* isEliminatable. Not relevant for setters. */
|
|
${isAlwaysInSlot}, /* isAlwaysInSlot. Only relevant for getters. */
|
|
${isLazilyCachedInSlot}, /* isLazilyCachedInSlot. Only relevant for getters. */
|
|
${isTypedMethod}, /* isTypedMethod. Only relevant for methods. */
|
|
${slotIndex} /* Reserved slot index, if we're stored in a slot, else 0. */
|
|
}
|
|
""",
|
|
opName=opName,
|
|
name=self.descriptor.name,
|
|
opType=opType,
|
|
aliasSet=aliasSet,
|
|
returnType=functools.reduce(
|
|
CGMemberJITInfo.getSingleReturnType, returnTypes, ""
|
|
),
|
|
isInfallible=toStringBool(infallible),
|
|
isMovable=toStringBool(movable),
|
|
isEliminatable=toStringBool(eliminatable),
|
|
isAlwaysInSlot=toStringBool(alwaysInSlot),
|
|
isLazilyCachedInSlot=toStringBool(lazilyInSlot),
|
|
isTypedMethod=toStringBool(isTypedMethod),
|
|
slotIndex=slotIndex,
|
|
)
|
|
return initializer.rstrip()
|
|
|
|
slotAssert = fill(
|
|
"""
|
|
static_assert(${slotIndex} <= JSJitInfo::maxSlotIndex, "We won't fit");
|
|
static_assert(${slotIndex} < ${classReservedSlots}, "There is no slot for us");
|
|
""",
|
|
slotIndex=slotIndex,
|
|
classReservedSlots=INSTANCE_RESERVED_SLOTS
|
|
+ self.descriptor.interface.totalMembersInSlots,
|
|
)
|
|
if args is not None:
|
|
argTypes = "%s_argTypes" % infoName
|
|
args = [CGMemberJITInfo.getJSArgType(arg.type) for arg in args]
|
|
args.append("JSJitInfo::ArgTypeListEnd")
|
|
argTypesDecl = "static const JSJitInfo::ArgType %s[] = { %s };\n" % (
|
|
argTypes,
|
|
", ".join(args),
|
|
)
|
|
return fill(
|
|
"""
|
|
$*{argTypesDecl}
|
|
static const JSTypedMethodJitInfo ${infoName} = {
|
|
${jitInfo},
|
|
${argTypes}
|
|
};
|
|
$*{slotAssert}
|
|
""",
|
|
argTypesDecl=argTypesDecl,
|
|
infoName=infoName,
|
|
jitInfo=indent(jitInfoInitializer(True)),
|
|
argTypes=argTypes,
|
|
slotAssert=slotAssert,
|
|
)
|
|
|
|
# Unexposed things are meant to be used from C++ directly, so we make
|
|
# their jitinfo non-static. That way C++ can get at it.
|
|
if self.member.getExtendedAttribute("Unexposed"):
|
|
storageClass = "extern"
|
|
else:
|
|
storageClass = "static"
|
|
|
|
return fill(
|
|
"""
|
|
${storageClass} const JSJitInfo ${infoName} = ${jitInfo};
|
|
$*{slotAssert}
|
|
""",
|
|
storageClass=storageClass,
|
|
infoName=infoName,
|
|
jitInfo=jitInfoInitializer(False),
|
|
slotAssert=slotAssert,
|
|
)
|
|
|
|
def define(self):
|
|
if self.member.isAttr():
|
|
getterinfo = "%s_getterinfo" % IDLToCIdentifier(self.member.identifier.name)
|
|
name = IDLToCIdentifier(self.member.identifier.name)
|
|
if self.member.type.isPromise():
|
|
name = CGGetterPromiseWrapper.makeName(name)
|
|
getter = "get_%s" % name
|
|
extendedAttrs = self.descriptor.getExtendedAttributes(
|
|
self.member, getter=True
|
|
)
|
|
getterinfal = "infallible" in extendedAttrs
|
|
|
|
# At this point getterinfal is true if our getter either can't throw
|
|
# at all, or can only throw OOM. In both cases, it's safe to move,
|
|
# or dead-code-eliminate, the getter, because throwing OOM is not
|
|
# semantically meaningful, so code can't rely on it happening. Note
|
|
# that this makes the behavior consistent for OOM thrown from the
|
|
# getter itself and OOM thrown from the to-JS conversion of the
|
|
# return value (see the "canOOM" and "infallibleForMember" checks
|
|
# below).
|
|
movable = self.mayBeMovable() and getterinfal
|
|
eliminatable = self.mayBeEliminatable() and getterinfal
|
|
aliasSet = self.aliasSet()
|
|
|
|
# Now we have to set getterinfal to whether we can _really_ ever
|
|
# throw, from the point of view of the JS engine.
|
|
getterinfal = (
|
|
getterinfal
|
|
and "canOOM" not in extendedAttrs
|
|
and infallibleForMember(self.member, self.member.type, self.descriptor)
|
|
)
|
|
isAlwaysInSlot = self.member.getExtendedAttribute("StoreInSlot")
|
|
if self.member.slotIndices is not None:
|
|
assert isAlwaysInSlot or self.member.getExtendedAttribute("Cached")
|
|
isLazilyCachedInSlot = not isAlwaysInSlot
|
|
slotIndex = memberReservedSlot(self.member, self.descriptor)
|
|
# We'll statically assert that this is not too big in
|
|
# CGUpdateMemberSlotsMethod, in the case when
|
|
# isAlwaysInSlot is true.
|
|
else:
|
|
isLazilyCachedInSlot = False
|
|
slotIndex = "0"
|
|
|
|
result = self.defineJitInfo(
|
|
getterinfo,
|
|
getter,
|
|
"Getter",
|
|
getterinfal,
|
|
movable,
|
|
eliminatable,
|
|
aliasSet,
|
|
isAlwaysInSlot,
|
|
isLazilyCachedInSlot,
|
|
slotIndex,
|
|
[self.member.type],
|
|
None,
|
|
)
|
|
if (
|
|
not self.member.readonly
|
|
or self.member.getExtendedAttribute("PutForwards") is not None
|
|
or self.member.getExtendedAttribute("Replaceable") is not None
|
|
or self.member.getExtendedAttribute("LenientSetter") is not None
|
|
):
|
|
setterinfo = "%s_setterinfo" % IDLToCIdentifier(
|
|
self.member.identifier.name
|
|
)
|
|
# Actually a JSJitSetterOp, but JSJitGetterOp is first in the
|
|
# union.
|
|
setter = "(JSJitGetterOp)set_%s" % IDLToCIdentifier(
|
|
self.member.identifier.name
|
|
)
|
|
# Setters are always fallible, since they have to do a typed unwrap.
|
|
result += self.defineJitInfo(
|
|
setterinfo,
|
|
setter,
|
|
"Setter",
|
|
False,
|
|
False,
|
|
False,
|
|
"AliasEverything",
|
|
False,
|
|
False,
|
|
"0",
|
|
[BuiltinTypes[IDLBuiltinType.Types.void]],
|
|
None,
|
|
)
|
|
return result
|
|
if self.member.isMethod():
|
|
methodinfo = "%s_methodinfo" % IDLToCIdentifier(self.member.identifier.name)
|
|
name = CppKeywords.checkMethodName(
|
|
IDLToCIdentifier(self.member.identifier.name)
|
|
)
|
|
if self.member.returnsPromise():
|
|
name = CGMethodPromiseWrapper.makeName(name)
|
|
# Actually a JSJitMethodOp, but JSJitGetterOp is first in the union.
|
|
method = "(JSJitGetterOp)%s" % name
|
|
|
|
# Methods are infallible if they are infallible, have no arguments
|
|
# to unwrap, and have a return type that's infallible to wrap up for
|
|
# return.
|
|
sigs = self.member.signatures()
|
|
if len(sigs) != 1:
|
|
# Don't handle overloading. If there's more than one signature,
|
|
# one of them must take arguments.
|
|
methodInfal = False
|
|
args = None
|
|
movable = False
|
|
eliminatable = False
|
|
else:
|
|
sig = sigs[0]
|
|
# For methods that affect nothing, it's OK to set movable to our
|
|
# notion of infallible on the C++ side, without considering
|
|
# argument conversions, since argument conversions that can
|
|
# reliably throw would be effectful anyway and the jit doesn't
|
|
# move effectful things.
|
|
extendedAttrs = self.descriptor.getExtendedAttributes(self.member)
|
|
hasInfallibleImpl = "infallible" in extendedAttrs
|
|
# At this point hasInfallibleImpl is true if our method either
|
|
# can't throw at all, or can only throw OOM. In both cases, it
|
|
# may be safe to move, or dead-code-eliminate, the method,
|
|
# because throwing OOM is not semantically meaningful, so code
|
|
# can't rely on it happening. Note that this makes the behavior
|
|
# consistent for OOM thrown from the method itself and OOM
|
|
# thrown from the to-JS conversion of the return value (see the
|
|
# "canOOM" and "infallibleForMember" checks below).
|
|
movable = self.mayBeMovable() and hasInfallibleImpl
|
|
eliminatable = self.mayBeEliminatable() and hasInfallibleImpl
|
|
# XXXbz can we move the smarts about fallibility due to arg
|
|
# conversions into the JIT, using our new args stuff?
|
|
if len(sig[1]) != 0 or not infallibleForMember(
|
|
self.member, sig[0], self.descriptor
|
|
):
|
|
# We have arguments or our return-value boxing can fail
|
|
methodInfal = False
|
|
else:
|
|
methodInfal = hasInfallibleImpl and "canOOM" not in extendedAttrs
|
|
# For now, only bother to output args if we're side-effect-free.
|
|
if self.member.affects == "Nothing":
|
|
args = sig[1]
|
|
else:
|
|
args = None
|
|
|
|
aliasSet = self.aliasSet()
|
|
result = self.defineJitInfo(
|
|
methodinfo,
|
|
method,
|
|
"Method",
|
|
methodInfal,
|
|
movable,
|
|
eliminatable,
|
|
aliasSet,
|
|
False,
|
|
False,
|
|
"0",
|
|
[s[0] for s in sigs],
|
|
args,
|
|
)
|
|
return result
|
|
raise TypeError("Illegal member type to CGPropertyJITInfo")
|
|
|
|
def mayBeMovable(self):
|
|
"""
|
|
Returns whether this attribute or method may be movable, just
|
|
based on Affects/DependsOn annotations.
|
|
"""
|
|
affects = self.member.affects
|
|
dependsOn = self.member.dependsOn
|
|
assert affects in IDLInterfaceMember.AffectsValues
|
|
assert dependsOn in IDLInterfaceMember.DependsOnValues
|
|
# Things that are DependsOn=DeviceState are not movable, because we
|
|
# don't want them coalesced with each other or loop-hoisted, since
|
|
# their return value can change even if nothing is going on from our
|
|
# point of view.
|
|
return affects == "Nothing" and (
|
|
dependsOn != "Everything" and dependsOn != "DeviceState"
|
|
)
|
|
|
|
def mayBeEliminatable(self):
|
|
"""
|
|
Returns whether this attribute or method may be eliminatable, just
|
|
based on Affects/DependsOn annotations.
|
|
"""
|
|
# dependsOn shouldn't affect this decision at all, except in jitinfo we
|
|
# have no way to express "Depends on everything, affects nothing",
|
|
# because we only have three alias set values: AliasNone ("depends on
|
|
# nothing, affects nothing"), AliasDOMSets ("depends on DOM sets,
|
|
# affects nothing"), AliasEverything ("depends on everything, affects
|
|
# everything"). So the [Affects=Nothing, DependsOn=Everything] case
|
|
# gets encoded as AliasEverything and defineJitInfo asserts that if our
|
|
# alias state is AliasEverything then we're not eliminatable (because it
|
|
# thinks we might have side-effects at that point). Bug 1155796 is
|
|
# tracking possible solutions for this.
|
|
affects = self.member.affects
|
|
dependsOn = self.member.dependsOn
|
|
assert affects in IDLInterfaceMember.AffectsValues
|
|
assert dependsOn in IDLInterfaceMember.DependsOnValues
|
|
return affects == "Nothing" and dependsOn != "Everything"
|
|
|
|
def aliasSet(self):
|
|
"""
|
|
Returns the alias set to store in the jitinfo. This may not be the
|
|
effective alias set the JIT uses, depending on whether we have enough
|
|
information about our args to allow the JIT to prove that effectful
|
|
argument conversions won't happen.
|
|
"""
|
|
dependsOn = self.member.dependsOn
|
|
assert dependsOn in IDLInterfaceMember.DependsOnValues
|
|
|
|
if dependsOn == "Nothing" or dependsOn == "DeviceState":
|
|
assert self.member.affects == "Nothing"
|
|
return "AliasNone"
|
|
|
|
if dependsOn == "DOMState":
|
|
assert self.member.affects == "Nothing"
|
|
return "AliasDOMSets"
|
|
|
|
return "AliasEverything"
|
|
|
|
@staticmethod
|
|
def getJSReturnTypeTag(t):
|
|
if t.nullable():
|
|
# Sometimes it might return null, sometimes not
|
|
return "JSVAL_TYPE_UNKNOWN"
|
|
if t.isVoid():
|
|
# No return, every time
|
|
return "JSVAL_TYPE_UNDEFINED"
|
|
if t.isSequence():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isRecord():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isPromise():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isGeckoInterface():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isString():
|
|
return "JSVAL_TYPE_STRING"
|
|
if t.isEnum():
|
|
return "JSVAL_TYPE_STRING"
|
|
if t.isCallback():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isAny():
|
|
# The whole point is to return various stuff
|
|
return "JSVAL_TYPE_UNKNOWN"
|
|
if t.isObject():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isSpiderMonkeyInterface():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isUnion():
|
|
u = t.unroll()
|
|
if u.hasNullableType:
|
|
# Might be null or not
|
|
return "JSVAL_TYPE_UNKNOWN"
|
|
return functools.reduce(
|
|
CGMemberJITInfo.getSingleReturnType, u.flatMemberTypes, ""
|
|
)
|
|
if t.isDictionary():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if not t.isPrimitive():
|
|
raise TypeError("No idea what type " + str(t) + " is.")
|
|
tag = t.tag()
|
|
if tag == IDLType.Tags.bool:
|
|
return "JSVAL_TYPE_BOOLEAN"
|
|
if tag in [
|
|
IDLType.Tags.int8,
|
|
IDLType.Tags.uint8,
|
|
IDLType.Tags.int16,
|
|
IDLType.Tags.uint16,
|
|
IDLType.Tags.int32,
|
|
]:
|
|
return "JSVAL_TYPE_INT32"
|
|
if tag in [
|
|
IDLType.Tags.int64,
|
|
IDLType.Tags.uint64,
|
|
IDLType.Tags.unrestricted_float,
|
|
IDLType.Tags.float,
|
|
IDLType.Tags.unrestricted_double,
|
|
IDLType.Tags.double,
|
|
]:
|
|
# These all use JS_NumberValue, which can return int or double.
|
|
# But TI treats "double" as meaning "int or double", so we're
|
|
# good to return JSVAL_TYPE_DOUBLE here.
|
|
return "JSVAL_TYPE_DOUBLE"
|
|
if tag != IDLType.Tags.uint32:
|
|
raise TypeError("No idea what type " + str(t) + " is.")
|
|
# uint32 is sometimes int and sometimes double.
|
|
return "JSVAL_TYPE_DOUBLE"
|
|
|
|
@staticmethod
|
|
def getSingleReturnType(existingType, t):
|
|
type = CGMemberJITInfo.getJSReturnTypeTag(t)
|
|
if existingType == "":
|
|
# First element of the list; just return its type
|
|
return type
|
|
|
|
if type == existingType:
|
|
return existingType
|
|
if (type == "JSVAL_TYPE_DOUBLE" and existingType == "JSVAL_TYPE_INT32") or (
|
|
existingType == "JSVAL_TYPE_DOUBLE" and type == "JSVAL_TYPE_INT32"
|
|
):
|
|
# Promote INT32 to DOUBLE as needed
|
|
return "JSVAL_TYPE_DOUBLE"
|
|
# Different types
|
|
return "JSVAL_TYPE_UNKNOWN"
|
|
|
|
@staticmethod
|
|
def getJSArgType(t):
|
|
assert not t.isVoid()
|
|
if t.nullable():
|
|
# Sometimes it might return null, sometimes not
|
|
return (
|
|
"JSJitInfo::ArgType(JSJitInfo::Null | %s)"
|
|
% CGMemberJITInfo.getJSArgType(t.inner)
|
|
)
|
|
if t.isSequence():
|
|
return "JSJitInfo::Object"
|
|
if t.isPromise():
|
|
return "JSJitInfo::Object"
|
|
if t.isGeckoInterface():
|
|
return "JSJitInfo::Object"
|
|
if t.isString():
|
|
return "JSJitInfo::String"
|
|
if t.isEnum():
|
|
return "JSJitInfo::String"
|
|
if t.isCallback():
|
|
return "JSJitInfo::Object"
|
|
if t.isAny():
|
|
# The whole point is to return various stuff
|
|
return "JSJitInfo::Any"
|
|
if t.isObject():
|
|
return "JSJitInfo::Object"
|
|
if t.isSpiderMonkeyInterface():
|
|
return "JSJitInfo::Object"
|
|
if t.isUnion():
|
|
u = t.unroll()
|
|
type = "JSJitInfo::Null" if u.hasNullableType else ""
|
|
return "JSJitInfo::ArgType(%s)" % functools.reduce(
|
|
CGMemberJITInfo.getSingleArgType, u.flatMemberTypes, type
|
|
)
|
|
if t.isDictionary():
|
|
return "JSJitInfo::Object"
|
|
if not t.isPrimitive():
|
|
raise TypeError("No idea what type " + str(t) + " is.")
|
|
tag = t.tag()
|
|
if tag == IDLType.Tags.bool:
|
|
return "JSJitInfo::Boolean"
|
|
if tag in [
|
|
IDLType.Tags.int8,
|
|
IDLType.Tags.uint8,
|
|
IDLType.Tags.int16,
|
|
IDLType.Tags.uint16,
|
|
IDLType.Tags.int32,
|
|
]:
|
|
return "JSJitInfo::Integer"
|
|
if tag in [
|
|
IDLType.Tags.int64,
|
|
IDLType.Tags.uint64,
|
|
IDLType.Tags.unrestricted_float,
|
|
IDLType.Tags.float,
|
|
IDLType.Tags.unrestricted_double,
|
|
IDLType.Tags.double,
|
|
]:
|
|
# These all use JS_NumberValue, which can return int or double.
|
|
# But TI treats "double" as meaning "int or double", so we're
|
|
# good to return JSVAL_TYPE_DOUBLE here.
|
|
return "JSJitInfo::Double"
|
|
if tag != IDLType.Tags.uint32:
|
|
raise TypeError("No idea what type " + str(t) + " is.")
|
|
# uint32 is sometimes int and sometimes double.
|
|
return "JSJitInfo::Double"
|
|
|
|
@staticmethod
|
|
def getSingleArgType(existingType, t):
|
|
type = CGMemberJITInfo.getJSArgType(t)
|
|
if existingType == "":
|
|
# First element of the list; just return its type
|
|
return type
|
|
|
|
if type == existingType:
|
|
return existingType
|
|
return "%s | %s" % (existingType, type)
|
|
|
|
|
|
class CGStaticMethodJitinfo(CGGeneric):
|
|
"""
|
|
A class for generating the JITInfo for a promise-returning static method.
|
|
"""
|
|
|
|
def __init__(self, method):
|
|
CGGeneric.__init__(
|
|
self,
|
|
"\n"
|
|
"static const JSJitInfo %s_methodinfo = {\n"
|
|
" { (JSJitGetterOp)%s },\n"
|
|
" { prototypes::id::_ID_Count }, { 0 }, JSJitInfo::StaticMethod,\n"
|
|
" JSJitInfo::AliasEverything, JSVAL_TYPE_OBJECT, false, false,\n"
|
|
" false, false, 0\n"
|
|
"};\n"
|
|
% (
|
|
IDLToCIdentifier(method.identifier.name),
|
|
CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name)),
|
|
),
|
|
)
|
|
|
|
|
|
def getEnumValueName(value):
|
|
# Some enum values can be empty strings. Others might have weird
|
|
# characters in them. Deal with the former by returning "_empty",
|
|
# deal with possible name collisions from that by throwing if the
|
|
# enum value is actually "_empty", and throw on any value
|
|
# containing non-ASCII chars for now. Replace all chars other than
|
|
# [0-9A-Za-z_] with '_'.
|
|
if re.match("[^\x20-\x7E]", value):
|
|
raise SyntaxError('Enum value "' + value + '" contains non-ASCII characters')
|
|
if re.match("^[0-9]", value):
|
|
value = "_" + value
|
|
value = re.sub(r"[^0-9A-Za-z_]", "_", value)
|
|
if re.match("^_[A-Z]|__", value):
|
|
raise SyntaxError('Enum value "' + value + '" is reserved by the C++ spec')
|
|
if value == "_empty":
|
|
raise SyntaxError('"_empty" is not an IDL enum value we support yet')
|
|
if value == "":
|
|
return "_empty"
|
|
nativeName = MakeNativeName(value)
|
|
if nativeName == "EndGuard_":
|
|
raise SyntaxError(
|
|
'Enum value "' + value + '" cannot be used because it'
|
|
" collides with our internal EndGuard_ value. Please"
|
|
" rename our internal EndGuard_ to something else"
|
|
)
|
|
return nativeName
|
|
|
|
|
|
class CGEnumToJSValue(CGAbstractMethod):
|
|
def __init__(self, enum):
|
|
enumType = enum.identifier.name
|
|
self.stringsArray = enumType + "Values::" + ENUM_ENTRY_VARIABLE_NAME
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
None,
|
|
"ToJSValue",
|
|
"bool",
|
|
[
|
|
Argument("JSContext*", "aCx"),
|
|
Argument(enumType, "aArgument"),
|
|
Argument("JS::MutableHandle<JS::Value>", "aValue"),
|
|
],
|
|
)
|
|
|
|
def definition_body(self):
|
|
return fill(
|
|
"""
|
|
MOZ_ASSERT(uint32_t(aArgument) < ArrayLength(${strings}));
|
|
JSString* resultStr =
|
|
JS_NewStringCopyN(aCx, ${strings}[uint32_t(aArgument)].value,
|
|
${strings}[uint32_t(aArgument)].length);
|
|
if (!resultStr) {
|
|
return false;
|
|
}
|
|
aValue.setString(resultStr);
|
|
return true;
|
|
""",
|
|
strings=self.stringsArray,
|
|
)
|
|
|
|
|
|
class CGEnum(CGThing):
|
|
def __init__(self, enum):
|
|
CGThing.__init__(self)
|
|
self.enum = enum
|
|
entryDecl = fill(
|
|
"""
|
|
extern const EnumEntry ${entry_array}[${entry_count}];
|
|
|
|
static constexpr size_t Count = ${real_entry_count};
|
|
|
|
// Our "${entry_array}" contains an extra entry with a null string.
|
|
static_assert(mozilla::ArrayLength(${entry_array}) - 1 == Count,
|
|
"Mismatch between enum strings and enum count");
|
|
|
|
static_assert(static_cast<size_t>(${name}::EndGuard_) == Count,
|
|
"Mismatch between enum value and enum count");
|
|
|
|
inline auto GetString(${name} stringId) {
|
|
MOZ_ASSERT(static_cast<${type}>(stringId) < Count);
|
|
const EnumEntry& entry = ${entry_array}[static_cast<${type}>(stringId)];
|
|
return Span<const char>{entry.value, entry.length};
|
|
}
|
|
""",
|
|
entry_array=ENUM_ENTRY_VARIABLE_NAME,
|
|
entry_count=self.nEnumStrings(),
|
|
# -1 because nEnumStrings() includes a string for EndGuard_
|
|
real_entry_count=self.nEnumStrings() - 1,
|
|
name=self.enum.identifier.name,
|
|
type=self.underlyingType(),
|
|
)
|
|
strings = CGNamespace(
|
|
self.stringsNamespace(),
|
|
CGGeneric(
|
|
declare=entryDecl,
|
|
define=fill(
|
|
"""
|
|
extern const EnumEntry ${name}[${count}] = {
|
|
$*{entries}
|
|
{ nullptr, 0 }
|
|
};
|
|
""",
|
|
name=ENUM_ENTRY_VARIABLE_NAME,
|
|
count=self.nEnumStrings(),
|
|
entries="".join(
|
|
'{"%s", %d},\n' % (val, len(val)) for val in self.enum.values()
|
|
),
|
|
),
|
|
),
|
|
)
|
|
toJSValue = CGEnumToJSValue(enum)
|
|
self.cgThings = CGList([strings, toJSValue], "\n")
|
|
|
|
def stringsNamespace(self):
|
|
return self.enum.identifier.name + "Values"
|
|
|
|
def nEnumStrings(self):
|
|
return len(self.enum.values()) + 1
|
|
|
|
def underlyingType(self):
|
|
count = self.nEnumStrings()
|
|
if count <= 256:
|
|
return "uint8_t"
|
|
if count <= 65536:
|
|
return "uint16_t"
|
|
raise ValueError(
|
|
"Enum " + self.enum.identifier.name + " has more than 65536 values"
|
|
)
|
|
|
|
def declare(self):
|
|
decl = fill(
|
|
"""
|
|
enum class ${name} : ${ty} {
|
|
$*{enums}
|
|
EndGuard_
|
|
};
|
|
""",
|
|
name=self.enum.identifier.name,
|
|
ty=self.underlyingType(),
|
|
enums=",\n".join(map(getEnumValueName, self.enum.values())) + ",\n",
|
|
)
|
|
|
|
return decl + "\n" + self.cgThings.declare()
|
|
|
|
def define(self):
|
|
return self.cgThings.define()
|
|
|
|
def deps(self):
|
|
return self.enum.getDeps()
|
|
|
|
|
|
def getUnionAccessorSignatureType(type, descriptorProvider):
|
|
"""
|
|
Returns the types that are used in the getter and setter signatures for
|
|
union types
|
|
"""
|
|
# Flat member types have already unwrapped nullables.
|
|
assert not type.nullable()
|
|
|
|
# Promise types can never appear in unions, because Promise is not
|
|
# distinguishable from anything.
|
|
assert not type.isPromise()
|
|
|
|
if type.isSequence() or type.isRecord():
|
|
if type.isSequence():
|
|
wrapperType = "Sequence"
|
|
else:
|
|
wrapperType = "Record"
|
|
# We don't use the returned template here, so it's OK to just pass no
|
|
# sourceDescription.
|
|
elementInfo = getJSToNativeConversionInfo(
|
|
type.inner, descriptorProvider, isMember=wrapperType
|
|
)
|
|
if wrapperType == "Sequence":
|
|
innerType = elementInfo.declType
|
|
else:
|
|
innerType = [recordKeyDeclType(type), elementInfo.declType]
|
|
|
|
return CGTemplatedType(wrapperType, innerType, isConst=True, isReference=True)
|
|
|
|
# Nested unions are unwrapped automatically into our flatMemberTypes.
|
|
assert not type.isUnion()
|
|
|
|
if type.isGeckoInterface():
|
|
descriptor = descriptorProvider.getDescriptor(
|
|
type.unroll().inner.identifier.name
|
|
)
|
|
typeName = CGGeneric(descriptor.nativeType)
|
|
if not type.unroll().inner.isExternal():
|
|
typeName = CGWrapper(typeName, post="&")
|
|
elif descriptor.interface.identifier.name == "WindowProxy":
|
|
typeName = CGGeneric("WindowProxyHolder const&")
|
|
else:
|
|
# Allow null pointers for old-binding classes.
|
|
typeName = CGWrapper(typeName, post="*")
|
|
return typeName
|
|
|
|
if type.isSpiderMonkeyInterface():
|
|
typeName = CGGeneric(type.name)
|
|
return CGWrapper(typeName, post=" const &")
|
|
|
|
if type.isJSString():
|
|
raise TypeError("JSString not supported in unions")
|
|
|
|
if type.isDOMString() or type.isUSVString():
|
|
return CGGeneric("const nsAString&")
|
|
|
|
if type.isUTF8String():
|
|
return CGGeneric("const nsACString&")
|
|
|
|
if type.isByteString():
|
|
return CGGeneric("const nsCString&")
|
|
|
|
if type.isEnum():
|
|
return CGGeneric(type.inner.identifier.name)
|
|
|
|
if type.isCallback():
|
|
return CGGeneric("%s&" % type.unroll().callback.identifier.name)
|
|
|
|
if type.isAny():
|
|
return CGGeneric("JS::Value")
|
|
|
|
if type.isObject():
|
|
return CGGeneric("JSObject*")
|
|
|
|
if type.isDictionary():
|
|
return CGGeneric("const %s&" % type.inner.identifier.name)
|
|
|
|
if not type.isPrimitive():
|
|
raise TypeError("Need native type for argument type '%s'" % str(type))
|
|
|
|
return CGGeneric(builtinNames[type.tag()])
|
|
|
|
|
|
def getUnionTypeTemplateVars(unionType, type, descriptorProvider, ownsMembers=False):
|
|
name = getUnionMemberName(type)
|
|
holderName = "m" + name + "Holder"
|
|
|
|
# By the time tryNextCode is invoked, we're guaranteed the union has been
|
|
# constructed as some type, since we've been trying to convert into the
|
|
# corresponding member.
|
|
prefix = "" if ownsMembers else "mUnion."
|
|
tryNextCode = (
|
|
"$*{destroyHolder}\n"
|
|
"%sDestroy%s();\n"
|
|
"tryNext = true;\n"
|
|
"return true;\n" % (prefix, name)
|
|
)
|
|
|
|
sourceDescription = "%s branch of %s" % (type.prettyName(), unionType.prettyName())
|
|
|
|
conversionInfo = getJSToNativeConversionInfo(
|
|
type,
|
|
descriptorProvider,
|
|
failureCode=tryNextCode,
|
|
isDefinitelyObject=not type.isDictionary(),
|
|
isMember=("OwningUnion" if ownsMembers else None),
|
|
sourceDescription=sourceDescription,
|
|
)
|
|
|
|
if conversionInfo.holderType is not None:
|
|
assert not ownsMembers
|
|
destroyHolder = "%s.reset();\n" % holderName
|
|
else:
|
|
destroyHolder = ""
|
|
|
|
ctorNeedsCx = conversionInfo.declArgs == "cx"
|
|
ctorArgs = "cx" if ctorNeedsCx else ""
|
|
|
|
structType = conversionInfo.declType.define()
|
|
externalType = getUnionAccessorSignatureType(type, descriptorProvider).define()
|
|
|
|
if type.isObject():
|
|
if ownsMembers:
|
|
body = dedent(
|
|
"""
|
|
MOZ_ASSERT(mType == eUninitialized);
|
|
mValue.mObject.SetValue(obj);
|
|
mType = eObject;
|
|
"""
|
|
)
|
|
else:
|
|
body = dedent(
|
|
"""
|
|
MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized);
|
|
mUnion.mValue.mObject.SetValue(cx, obj);
|
|
mUnion.mType = mUnion.eObject;
|
|
"""
|
|
)
|
|
|
|
# It's a bit sketchy to do the security check after setting the value,
|
|
# but it keeps the code cleaner and lets us avoid rooting |obj| over the
|
|
# call to CallerSubsumes().
|
|
body = body + fill(
|
|
"""
|
|
if (passedToJSImpl && !CallerSubsumes(obj)) {
|
|
cx.ThrowErrorMessage<MSG_PERMISSION_DENIED_TO_PASS_ARG>("${sourceDescription}");
|
|
return false;
|
|
}
|
|
return true;
|
|
""",
|
|
sourceDescription=sourceDescription,
|
|
)
|
|
|
|
setters = [
|
|
ClassMethod(
|
|
"SetToObject",
|
|
"bool",
|
|
[
|
|
Argument("BindingCallContext&", "cx"),
|
|
Argument("JSObject*", "obj"),
|
|
Argument("bool", "passedToJSImpl", default="false"),
|
|
],
|
|
inline=True,
|
|
bodyInHeader=True,
|
|
body=body,
|
|
)
|
|
]
|
|
elif type.isDictionary() and not type.inner.needsConversionFromJS:
|
|
# In this case we are never initialized from JS to start with
|
|
setters = None
|
|
else:
|
|
# Important: we need to not have our declName involve
|
|
# maybe-GCing operations.
|
|
if conversionInfo.holderType is not None:
|
|
holderArgs = conversionInfo.holderArgs
|
|
if holderArgs is None:
|
|
holderArgs = ""
|
|
initHolder = "%s.emplace(%s);\n" % (holderName, holderArgs)
|
|
else:
|
|
initHolder = ""
|
|
|
|
jsConversion = fill(
|
|
initHolder + conversionInfo.template,
|
|
val="value",
|
|
maybeMutableVal="value",
|
|
declName="memberSlot",
|
|
holderName=(holderName if ownsMembers else "%s.ref()" % holderName),
|
|
destroyHolder=destroyHolder,
|
|
passedToJSImpl="passedToJSImpl",
|
|
)
|
|
|
|
jsConversion = fill(
|
|
"""
|
|
tryNext = false;
|
|
{ // scope for memberSlot
|
|
${structType}& memberSlot = RawSetAs${name}(${ctorArgs});
|
|
$*{jsConversion}
|
|
}
|
|
return true;
|
|
""",
|
|
structType=structType,
|
|
name=name,
|
|
ctorArgs=ctorArgs,
|
|
jsConversion=jsConversion,
|
|
)
|
|
|
|
if ownsMembers:
|
|
handleType = "JS::Handle<JS::Value>"
|
|
else:
|
|
handleType = "JS::MutableHandle<JS::Value>"
|
|
|
|
needCallContext = idlTypeNeedsCallContext(type)
|
|
if needCallContext:
|
|
cxType = "BindingCallContext&"
|
|
else:
|
|
cxType = "JSContext*"
|
|
setters = [
|
|
ClassMethod(
|
|
"TrySetTo" + name,
|
|
"bool",
|
|
[
|
|
Argument(cxType, "cx"),
|
|
Argument(handleType, "value"),
|
|
Argument("bool&", "tryNext"),
|
|
Argument("bool", "passedToJSImpl", default="false"),
|
|
],
|
|
inline=not ownsMembers,
|
|
bodyInHeader=not ownsMembers,
|
|
body=jsConversion,
|
|
)
|
|
]
|
|
if needCallContext:
|
|
# Add a method for non-binding uses of unions to allow them to set
|
|
# things in the union without providing a call context (though if
|
|
# they want good error reporting they'll provide one anyway).
|
|
shimBody = fill(
|
|
"""
|
|
BindingCallContext cx(cx_, nullptr);
|
|
return TrySetTo${name}(cx, value, tryNext, passedToJSImpl);
|
|
""",
|
|
name=name,
|
|
)
|
|
setters.append(
|
|
ClassMethod(
|
|
"TrySetTo" + name,
|
|
"bool",
|
|
[
|
|
Argument("JSContext*", "cx_"),
|
|
Argument(handleType, "value"),
|
|
Argument("bool&", "tryNext"),
|
|
Argument("bool", "passedToJSImpl", default="false"),
|
|
],
|
|
inline=not ownsMembers,
|
|
bodyInHeader=not ownsMembers,
|
|
body=shimBody,
|
|
)
|
|
)
|
|
|
|
return {
|
|
"name": name,
|
|
"structType": structType,
|
|
"externalType": externalType,
|
|
"setters": setters,
|
|
"holderType": conversionInfo.holderType.define()
|
|
if conversionInfo.holderType
|
|
else None,
|
|
"ctorArgs": ctorArgs,
|
|
"ctorArgList": [Argument("JSContext*", "cx")] if ctorNeedsCx else [],
|
|
}
|
|
|
|
|
|
class CGUnionStruct(CGThing):
|
|
def __init__(self, type, descriptorProvider, ownsMembers=False):
|
|
CGThing.__init__(self)
|
|
self.type = type.unroll()
|
|
self.descriptorProvider = descriptorProvider
|
|
self.ownsMembers = ownsMembers
|
|
self.struct = self.getStruct()
|
|
|
|
def declare(self):
|
|
return self.struct.declare()
|
|
|
|
def define(self):
|
|
return self.struct.define()
|
|
|
|
def deps(self):
|
|
return self.type.getDeps()
|
|
|
|
def getStruct(self):
|
|
|
|
members = [
|
|
ClassMember("mType", "Type", body="eUninitialized"),
|
|
ClassMember("mValue", "Value"),
|
|
]
|
|
ctor = ClassConstructor(
|
|
[], bodyInHeader=True, visibility="public", explicit=True
|
|
)
|
|
|
|
methods = []
|
|
enumValues = ["eUninitialized"]
|
|
toJSValCases = [CGCase("eUninitialized", CGGeneric("return false;\n"))]
|
|
destructorCases = [CGCase("eUninitialized", None)]
|
|
assignmentCases = [
|
|
CGCase(
|
|
"eUninitialized",
|
|
CGGeneric(
|
|
"MOZ_ASSERT(mType == eUninitialized,\n"
|
|
' "We need to destroy ourselves?");\n'
|
|
),
|
|
)
|
|
]
|
|
traceCases = []
|
|
unionValues = []
|
|
if self.type.hasNullableType:
|
|
enumValues.append("eNull")
|
|
methods.append(
|
|
ClassMethod(
|
|
"IsNull",
|
|
"bool",
|
|
[],
|
|
const=True,
|
|
inline=True,
|
|
body="return mType == eNull;\n",
|
|
bodyInHeader=True,
|
|
)
|
|
)
|
|
methods.append(
|
|
ClassMethod(
|
|
"SetNull",
|
|
"void",
|
|
[],
|
|
inline=True,
|
|
body=("Uninit();\n" "mType = eNull;\n"),
|
|
bodyInHeader=True,
|
|
)
|
|
)
|
|
destructorCases.append(CGCase("eNull", None))
|
|
assignmentCases.append(
|
|
CGCase(
|
|
"eNull",
|
|
CGGeneric(
|
|
"MOZ_ASSERT(mType == eUninitialized);\n" "mType = eNull;\n"
|
|
),
|
|
)
|
|
)
|
|
toJSValCases.append(
|
|
CGCase("eNull", CGGeneric("rval.setNull();\n" "return true;\n"))
|
|
)
|
|
|
|
hasObjectType = any(t.isObject() for t in self.type.flatMemberTypes)
|
|
skipToJSVal = False
|
|
for t in self.type.flatMemberTypes:
|
|
vars = getUnionTypeTemplateVars(
|
|
self.type, t, self.descriptorProvider, ownsMembers=self.ownsMembers
|
|
)
|
|
if vars["name"] != "Object" or self.ownsMembers:
|
|
body = fill(
|
|
"""
|
|
if (mType == e${name}) {
|
|
return mValue.m${name}.Value();
|
|
}
|
|
%s
|
|
mType = e${name};
|
|
return mValue.m${name}.SetValue(${ctorArgs});
|
|
""",
|
|
**vars
|
|
)
|
|
|
|
# bodyInHeader must be false for return values because they own
|
|
# their union members and we don't want include headers in
|
|
# UnionTypes.h just to call Addref/Release
|
|
methods.append(
|
|
ClassMethod(
|
|
"RawSetAs" + vars["name"],
|
|
vars["structType"] + "&",
|
|
vars["ctorArgList"],
|
|
bodyInHeader=not self.ownsMembers,
|
|
body=body % "MOZ_ASSERT(mType == eUninitialized);",
|
|
)
|
|
)
|
|
uninit = "Uninit();"
|
|
if hasObjectType and not self.ownsMembers:
|
|
uninit = (
|
|
'MOZ_ASSERT(mType != eObject, "This will not play well with Rooted");\n'
|
|
+ uninit
|
|
)
|
|
methods.append(
|
|
ClassMethod(
|
|
"SetAs" + vars["name"],
|
|
vars["structType"] + "&",
|
|
vars["ctorArgList"],
|
|
bodyInHeader=not self.ownsMembers,
|
|
body=body % uninit,
|
|
)
|
|
)
|
|
if self.ownsMembers:
|
|
if vars["setters"]:
|
|
methods.extend(vars["setters"])
|
|
# Provide a SetStringLiteral() method to support string defaults.
|
|
if t.isByteString() or t.isUTF8String():
|
|
charType = "const nsCString::char_type"
|
|
elif t.isString():
|
|
charType = "const nsString::char_type"
|
|
else:
|
|
charType = None
|
|
|
|
if charType:
|
|
methods.append(
|
|
ClassMethod(
|
|
"SetStringLiteral",
|
|
"void",
|
|
# Hack, but it works...
|
|
[Argument(charType, "(&aData)[N]")],
|
|
inline=True,
|
|
bodyInHeader=True,
|
|
templateArgs=["int N"],
|
|
body="RawSetAs%s().AssignLiteral(aData);\n" % t.name,
|
|
)
|
|
)
|
|
|
|
body = fill(
|
|
"""
|
|
MOZ_ASSERT(Is${name}(), "Wrong type!");
|
|
mValue.m${name}.Destroy();
|
|
mType = eUninitialized;
|
|
""",
|
|
**vars
|
|
)
|
|
methods.append(
|
|
ClassMethod(
|
|
"Destroy" + vars["name"],
|
|
"void",
|
|
[],
|
|
visibility="private",
|
|
bodyInHeader=not self.ownsMembers,
|
|
body=body,
|
|
)
|
|
)
|
|
|
|
body = fill("return mType == e${name};\n", **vars)
|
|
methods.append(
|
|
ClassMethod(
|
|
"Is" + vars["name"],
|
|
"bool",
|
|
[],
|
|
const=True,
|
|
bodyInHeader=True,
|
|
body=body,
|
|
)
|
|
)
|
|
|
|
body = fill(
|
|
"""
|
|
MOZ_ASSERT(Is${name}(), "Wrong type!");
|
|
return mValue.m${name}.Value();
|
|
""",
|
|
**vars
|
|
)
|
|
# The non-const version of GetAs* returns our internal type
|
|
getterReturnType = "%s&" % vars["structType"]
|
|
methods.append(
|
|
ClassMethod(
|
|
"GetAs" + vars["name"],
|
|
getterReturnType,
|
|
[],
|
|
bodyInHeader=True,
|
|
body=body,
|
|
)
|
|
)
|
|
# The const version of GetAs* returns our internal type
|
|
# for owning unions, but our external type for non-owning
|
|
# ones.
|
|
if self.ownsMembers:
|
|
getterReturnType = "%s const &" % vars["structType"]
|
|
else:
|
|
getterReturnType = vars["externalType"]
|
|
methods.append(
|
|
ClassMethod(
|
|
"GetAs" + vars["name"],
|
|
getterReturnType,
|
|
[],
|
|
const=True,
|
|
bodyInHeader=True,
|
|
body=body,
|
|
)
|
|
)
|
|
|
|
unionValues.append(fill("UnionMember<${structType} > m${name}", **vars))
|
|
enumValues.append("e" + vars["name"])
|
|
|
|
conversionToJS = self.getConversionToJS(vars, t)
|
|
if conversionToJS:
|
|
toJSValCases.append(CGCase("e" + vars["name"], conversionToJS))
|
|
else:
|
|
skipToJSVal = True
|
|
|
|
destructorCases.append(
|
|
CGCase("e" + vars["name"], CGGeneric("Destroy%s();\n" % vars["name"]))
|
|
)
|
|
assignmentCases.append(
|
|
CGCase(
|
|
"e" + vars["name"],
|
|
CGGeneric(
|
|
"SetAs%s() = aOther.GetAs%s();\n" % (vars["name"], vars["name"])
|
|
),
|
|
)
|
|
)
|
|
if self.ownsMembers and typeNeedsRooting(t):
|
|
if t.isObject():
|
|
traceCases.append(
|
|
CGCase(
|
|
"e" + vars["name"],
|
|
CGGeneric(
|
|
'JS::UnsafeTraceRoot(trc, %s, "%s");\n'
|
|
% (
|
|
"&mValue.m" + vars["name"] + ".Value()",
|
|
"mValue.m" + vars["name"],
|
|
)
|
|
),
|
|
)
|
|
)
|
|
elif t.isDictionary():
|
|
traceCases.append(
|
|
CGCase(
|
|
"e" + vars["name"],
|
|
CGGeneric(
|
|
"mValue.m%s.Value().TraceDictionary(trc);\n"
|
|
% vars["name"]
|
|
),
|
|
)
|
|
)
|
|
elif t.isSequence():
|
|
traceCases.append(
|
|
CGCase(
|
|
"e" + vars["name"],
|
|
CGGeneric(
|
|
"DoTraceSequence(trc, mValue.m%s.Value());\n"
|
|
% vars["name"]
|
|
),
|
|
)
|
|
)
|
|
elif t.isRecord():
|
|
traceCases.append(
|
|
CGCase(
|
|
"e" + vars["name"],
|
|
CGGeneric(
|
|
"TraceRecord(trc, mValue.m%s.Value());\n" % vars["name"]
|
|
),
|
|
)
|
|
)
|
|
else:
|
|
assert t.isSpiderMonkeyInterface()
|
|
traceCases.append(
|
|
CGCase(
|
|
"e" + vars["name"],
|
|
CGGeneric(
|
|
"mValue.m%s.Value().TraceSelf(trc);\n" % vars["name"]
|
|
),
|
|
)
|
|
)
|
|
|
|
dtor = CGSwitch("mType", destructorCases).define()
|
|
|
|
methods.append(
|
|
ClassMethod(
|
|
"Uninit",
|
|
"void",
|
|
[],
|
|
visibility="public",
|
|
body=dtor,
|
|
bodyInHeader=not self.ownsMembers,
|
|
inline=not self.ownsMembers,
|
|
)
|
|
)
|
|
|
|
if not skipToJSVal:
|
|
methods.append(
|
|
ClassMethod(
|
|
"ToJSVal",
|
|
"bool",
|
|
[
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "scopeObj"),
|
|
Argument("JS::MutableHandle<JS::Value>", "rval"),
|
|
],
|
|
body=CGSwitch(
|
|
"mType", toJSValCases, default=CGGeneric("return false;\n")
|
|
).define()
|
|
+ "\nreturn false;\n",
|
|
const=True,
|
|
)
|
|
)
|
|
|
|
constructors = [ctor]
|
|
selfName = CGUnionStruct.unionTypeName(self.type, self.ownsMembers)
|
|
if self.ownsMembers:
|
|
if traceCases:
|
|
traceBody = CGSwitch(
|
|
"mType", traceCases, default=CGGeneric("")
|
|
).define()
|
|
else:
|
|
traceBody = ""
|
|
methods.append(
|
|
ClassMethod(
|
|
"TraceUnion", "void", [Argument("JSTracer*", "trc")], body=traceBody
|
|
)
|
|
)
|
|
if CGUnionStruct.isUnionCopyConstructible(self.type):
|
|
constructors.append(
|
|
ClassConstructor(
|
|
[Argument("const %s&" % selfName, "aOther")],
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
explicit=True,
|
|
body="*this = aOther;\n",
|
|
)
|
|
)
|
|
op_body = CGList([])
|
|
op_body.append(CGSwitch("aOther.mType", assignmentCases))
|
|
op_body.append(CGGeneric("return *this;\n"))
|
|
methods.append(
|
|
ClassMethod(
|
|
"operator=",
|
|
"%s&" % selfName,
|
|
[Argument("const %s&" % selfName, "aOther")],
|
|
body=op_body.define(),
|
|
)
|
|
)
|
|
disallowCopyConstruction = False
|
|
else:
|
|
disallowCopyConstruction = True
|
|
else:
|
|
disallowCopyConstruction = True
|
|
|
|
if self.ownsMembers:
|
|
friend = (
|
|
" friend void ImplCycleCollectionUnlink(%s& aUnion);\n"
|
|
% CGUnionStruct.unionTypeName(self.type, True)
|
|
)
|
|
else:
|
|
friend = " friend class %sArgument;\n" % str(self.type)
|
|
|
|
bases = [ClassBase("AllOwningUnionBase")] if self.ownsMembers else []
|
|
return CGClass(
|
|
selfName,
|
|
bases=bases,
|
|
members=members,
|
|
constructors=constructors,
|
|
methods=methods,
|
|
disallowCopyConstruction=disallowCopyConstruction,
|
|
extradeclarations=friend,
|
|
destructor=ClassDestructor(
|
|
visibility="public", body="Uninit();\n", bodyInHeader=True
|
|
),
|
|
enums=[ClassEnum("Type", enumValues, visibility="private")],
|
|
unions=[ClassUnion("Value", unionValues, visibility="private")],
|
|
)
|
|
|
|
def getConversionToJS(self, templateVars, type):
|
|
if type.isDictionary() and not type.inner.needsConversionToJS:
|
|
# We won't be able to convert this dictionary to a JS value, nor
|
|
# will we need to, since we don't need a ToJSVal method at all.
|
|
return None
|
|
|
|
assert not type.nullable() # flatMemberTypes never has nullable types
|
|
val = "mValue.m%(name)s.Value()" % templateVars
|
|
wrapCode = wrapForType(
|
|
type,
|
|
self.descriptorProvider,
|
|
{
|
|
"jsvalRef": "rval",
|
|
"jsvalHandle": "rval",
|
|
"obj": "scopeObj",
|
|
"result": val,
|
|
"spiderMonkeyInterfacesAreStructs": True,
|
|
},
|
|
)
|
|
return CGGeneric(wrapCode)
|
|
|
|
@staticmethod
|
|
def isUnionCopyConstructible(type):
|
|
return all(isTypeCopyConstructible(t) for t in type.flatMemberTypes)
|
|
|
|
@staticmethod
|
|
def unionTypeName(type, ownsMembers):
|
|
"""
|
|
Returns a string name for this known union type.
|
|
"""
|
|
assert type.isUnion() and not type.nullable()
|
|
return ("Owning" if ownsMembers else "") + type.name
|
|
|
|
@staticmethod
|
|
def unionTypeDecl(type, ownsMembers):
|
|
"""
|
|
Returns a string for declaring this possibly-nullable union type.
|
|
"""
|
|
assert type.isUnion()
|
|
nullable = type.nullable()
|
|
if nullable:
|
|
type = type.inner
|
|
decl = CGGeneric(CGUnionStruct.unionTypeName(type, ownsMembers))
|
|
if nullable:
|
|
decl = CGTemplatedType("Nullable", decl)
|
|
return decl.define()
|
|
|
|
|
|
class CGUnionConversionStruct(CGThing):
|
|
def __init__(self, type, descriptorProvider):
|
|
CGThing.__init__(self)
|
|
self.type = type.unroll()
|
|
self.descriptorProvider = descriptorProvider
|
|
|
|
def declare(self):
|
|
|
|
structName = str(self.type)
|
|
members = [
|
|
ClassMember(
|
|
"mUnion", structName + "&", body="const_cast<%s&>(aUnion)" % structName
|
|
)
|
|
]
|
|
# Argument needs to be a const ref because that's all Maybe<> allows
|
|
ctor = ClassConstructor(
|
|
[Argument("const %s&" % structName, "aUnion")],
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
explicit=True,
|
|
)
|
|
methods = []
|
|
|
|
if self.type.hasNullableType:
|
|
methods.append(
|
|
ClassMethod(
|
|
"SetNull",
|
|
"bool",
|
|
[],
|
|
body=(
|
|
"MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized);\n"
|
|
"mUnion.mType = mUnion.eNull;\n"
|
|
"return true;\n"
|
|
),
|
|
inline=True,
|
|
bodyInHeader=True,
|
|
)
|
|
)
|
|
|
|
for t in self.type.flatMemberTypes:
|
|
vars = getUnionTypeTemplateVars(self.type, t, self.descriptorProvider)
|
|
if vars["setters"]:
|
|
methods.extend(vars["setters"])
|
|
if vars["name"] != "Object":
|
|
body = fill(
|
|
"""
|
|
MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized);
|
|
mUnion.mType = mUnion.e${name};
|
|
return mUnion.mValue.m${name}.SetValue(${ctorArgs});
|
|
""",
|
|
**vars
|
|
)
|
|
methods.append(
|
|
ClassMethod(
|
|
"RawSetAs" + vars["name"],
|
|
vars["structType"] + "&",
|
|
vars["ctorArgList"],
|
|
bodyInHeader=True,
|
|
body=body,
|
|
visibility="private",
|
|
)
|
|
)
|
|
# Provide a SetStringLiteral() method to support string defaults.
|
|
if t.isByteString() or t.isUTF8String():
|
|
charType = "const nsCString::char_type"
|
|
elif t.isString():
|
|
charType = "const nsString::char_type"
|
|
else:
|
|
charType = None
|
|
|
|
if charType:
|
|
methods.append(
|
|
ClassMethod(
|
|
"SetStringLiteral",
|
|
"void",
|
|
# Hack, but it works...
|
|
[Argument(charType, "(&aData)[N]")],
|
|
inline=True,
|
|
bodyInHeader=True,
|
|
templateArgs=["int N"],
|
|
body="RawSetAs%s().AssignLiteral(aData);\n" % t.name,
|
|
)
|
|
)
|
|
|
|
if vars["holderType"] is not None:
|
|
holderType = CGTemplatedType(
|
|
"Maybe", CGGeneric(vars["holderType"])
|
|
).define()
|
|
members.append(ClassMember("m%sHolder" % vars["name"], holderType))
|
|
|
|
return CGClass(
|
|
structName + "Argument",
|
|
members=members,
|
|
constructors=[ctor],
|
|
methods=methods,
|
|
disallowCopyConstruction=True,
|
|
).declare()
|
|
|
|
def define(self):
|
|
return ""
|
|
|
|
def deps(self):
|
|
return set()
|
|
|
|
|
|
class ClassItem:
|
|
""" Use with CGClass """
|
|
|
|
def __init__(self, name, visibility):
|
|
self.name = name
|
|
self.visibility = visibility
|
|
|
|
def declare(self, cgClass):
|
|
assert False
|
|
|
|
def define(self, cgClass):
|
|
assert False
|
|
|
|
|
|
class ClassBase(ClassItem):
|
|
def __init__(self, name, visibility="public"):
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def declare(self, cgClass):
|
|
return "%s %s" % (self.visibility, self.name)
|
|
|
|
def define(self, cgClass):
|
|
# Only in the header
|
|
return ""
|
|
|
|
|
|
class ClassMethod(ClassItem):
|
|
def __init__(
|
|
self,
|
|
name,
|
|
returnType,
|
|
args,
|
|
inline=False,
|
|
static=False,
|
|
virtual=False,
|
|
const=False,
|
|
bodyInHeader=False,
|
|
templateArgs=None,
|
|
visibility="public",
|
|
body=None,
|
|
breakAfterReturnDecl="\n",
|
|
breakAfterSelf="\n",
|
|
override=False,
|
|
canRunScript=False,
|
|
):
|
|
"""
|
|
override indicates whether to flag the method as override
|
|
"""
|
|
assert not override or virtual
|
|
assert not (override and static)
|
|
self.returnType = returnType
|
|
self.args = args
|
|
self.inline = inline or bodyInHeader
|
|
self.static = static
|
|
self.virtual = virtual
|
|
self.const = const
|
|
self.bodyInHeader = bodyInHeader
|
|
self.templateArgs = templateArgs
|
|
self.body = body
|
|
self.breakAfterReturnDecl = breakAfterReturnDecl
|
|
self.breakAfterSelf = breakAfterSelf
|
|
self.override = override
|
|
self.canRunScript = canRunScript
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def getDecorators(self, declaring):
|
|
decorators = []
|
|
if self.canRunScript:
|
|
decorators.append("MOZ_CAN_RUN_SCRIPT")
|
|
if self.inline:
|
|
decorators.append("inline")
|
|
if declaring:
|
|
if self.static:
|
|
decorators.append("static")
|
|
if self.virtual and not self.override:
|
|
decorators.append("virtual")
|
|
if decorators:
|
|
return " ".join(decorators) + " "
|
|
return ""
|
|
|
|
def getBody(self):
|
|
# Override me or pass a string to constructor
|
|
assert self.body is not None
|
|
return self.body
|
|
|
|
def declare(self, cgClass):
|
|
templateClause = (
|
|
"template <%s>\n" % ", ".join(self.templateArgs)
|
|
if self.bodyInHeader and self.templateArgs
|
|
else ""
|
|
)
|
|
args = ", ".join([a.declare() for a in self.args])
|
|
if self.bodyInHeader:
|
|
body = indent(self.getBody())
|
|
body = "\n{\n" + body + "}\n"
|
|
else:
|
|
body = ";\n"
|
|
|
|
return fill(
|
|
"${templateClause}${decorators}${returnType}${breakAfterReturnDecl}"
|
|
"${name}(${args})${const}${override}${body}"
|
|
"${breakAfterSelf}",
|
|
templateClause=templateClause,
|
|
decorators=self.getDecorators(True),
|
|
returnType=self.returnType,
|
|
breakAfterReturnDecl=self.breakAfterReturnDecl,
|
|
name=self.name,
|
|
args=args,
|
|
const=" const" if self.const else "",
|
|
override=" override" if self.override else "",
|
|
body=body,
|
|
breakAfterSelf=self.breakAfterSelf,
|
|
)
|
|
|
|
def define(self, cgClass):
|
|
if self.bodyInHeader:
|
|
return ""
|
|
|
|
templateArgs = cgClass.templateArgs
|
|
if templateArgs:
|
|
if cgClass.templateSpecialization:
|
|
templateArgs = templateArgs[len(cgClass.templateSpecialization) :]
|
|
|
|
if templateArgs:
|
|
templateClause = "template <%s>\n" % ", ".join(
|
|
[str(a) for a in templateArgs]
|
|
)
|
|
else:
|
|
templateClause = ""
|
|
|
|
return fill(
|
|
"""
|
|
${templateClause}${decorators}${returnType}
|
|
${className}::${name}(${args})${const}
|
|
{
|
|
$*{body}
|
|
}
|
|
""",
|
|
templateClause=templateClause,
|
|
decorators=self.getDecorators(False),
|
|
returnType=self.returnType,
|
|
className=cgClass.getNameString(),
|
|
name=self.name,
|
|
args=", ".join([a.define() for a in self.args]),
|
|
const=" const" if self.const else "",
|
|
body=self.getBody(),
|
|
)
|
|
|
|
|
|
class ClassUsingDeclaration(ClassItem):
|
|
"""
|
|
Used for importing a name from a base class into a CGClass
|
|
|
|
baseClass is the name of the base class to import the name from
|
|
|
|
name is the name to import
|
|
|
|
visibility determines the visibility of the name (public,
|
|
protected, private), defaults to public.
|
|
"""
|
|
|
|
def __init__(self, baseClass, name, visibility="public"):
|
|
self.baseClass = baseClass
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def declare(self, cgClass):
|
|
return "using %s::%s;\n\n" % (self.baseClass, self.name)
|
|
|
|
def define(self, cgClass):
|
|
return ""
|
|
|
|
|
|
class ClassConstructor(ClassItem):
|
|
"""
|
|
Used for adding a constructor to a CGClass.
|
|
|
|
args is a list of Argument objects that are the arguments taken by the
|
|
constructor.
|
|
|
|
inline should be True if the constructor should be marked inline.
|
|
|
|
bodyInHeader should be True if the body should be placed in the class
|
|
declaration in the header.
|
|
|
|
visibility determines the visibility of the constructor (public,
|
|
protected, private), defaults to private.
|
|
|
|
explicit should be True if the constructor should be marked explicit.
|
|
|
|
baseConstructors is a list of strings containing calls to base constructors,
|
|
defaults to None.
|
|
|
|
body contains a string with the code for the constructor, defaults to empty.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
args,
|
|
inline=False,
|
|
bodyInHeader=False,
|
|
visibility="private",
|
|
explicit=False,
|
|
constexpr=False,
|
|
baseConstructors=None,
|
|
body="",
|
|
):
|
|
assert not (inline and constexpr)
|
|
assert not (bodyInHeader and constexpr)
|
|
self.args = args
|
|
self.inline = inline or bodyInHeader
|
|
self.bodyInHeader = bodyInHeader or constexpr
|
|
self.explicit = explicit
|
|
self.constexpr = constexpr
|
|
self.baseConstructors = baseConstructors or []
|
|
self.body = body
|
|
ClassItem.__init__(self, None, visibility)
|
|
|
|
def getDecorators(self, declaring):
|
|
decorators = []
|
|
if self.explicit:
|
|
decorators.append("explicit")
|
|
if self.inline and declaring:
|
|
decorators.append("inline")
|
|
if self.constexpr and declaring:
|
|
decorators.append("constexpr")
|
|
if decorators:
|
|
return " ".join(decorators) + " "
|
|
return ""
|
|
|
|
def getInitializationList(self, cgClass):
|
|
items = [str(c) for c in self.baseConstructors]
|
|
for m in cgClass.members:
|
|
if not m.static:
|
|
initialize = m.body
|
|
if initialize:
|
|
items.append(m.name + "(" + initialize + ")")
|
|
|
|
if len(items) > 0:
|
|
return "\n : " + ",\n ".join(items)
|
|
return ""
|
|
|
|
def getBody(self):
|
|
return self.body
|
|
|
|
def declare(self, cgClass):
|
|
args = ", ".join([a.declare() for a in self.args])
|
|
if self.bodyInHeader:
|
|
body = (
|
|
self.getInitializationList(cgClass)
|
|
+ "\n{\n"
|
|
+ indent(self.getBody())
|
|
+ "}\n"
|
|
)
|
|
else:
|
|
body = ";\n"
|
|
|
|
return fill(
|
|
"${decorators}${className}(${args})${body}\n",
|
|
decorators=self.getDecorators(True),
|
|
className=cgClass.getNameString(),
|
|
args=args,
|
|
body=body,
|
|
)
|
|
|
|
def define(self, cgClass):
|
|
if self.bodyInHeader:
|
|
return ""
|
|
|
|
return fill(
|
|
"""
|
|
${decorators}
|
|
${className}::${className}(${args})${initializationList}
|
|
{
|
|
$*{body}
|
|
}
|
|
""",
|
|
decorators=self.getDecorators(False),
|
|
className=cgClass.getNameString(),
|
|
args=", ".join([a.define() for a in self.args]),
|
|
initializationList=self.getInitializationList(cgClass),
|
|
body=self.getBody(),
|
|
)
|
|
|
|
|
|
class ClassDestructor(ClassItem):
|
|
"""
|
|
Used for adding a destructor to a CGClass.
|
|
|
|
inline should be True if the destructor should be marked inline.
|
|
|
|
bodyInHeader should be True if the body should be placed in the class
|
|
declaration in the header.
|
|
|
|
visibility determines the visibility of the destructor (public,
|
|
protected, private), defaults to private.
|
|
|
|
body contains a string with the code for the destructor, defaults to empty.
|
|
|
|
virtual determines whether the destructor is virtual, defaults to False.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
inline=False,
|
|
bodyInHeader=False,
|
|
visibility="private",
|
|
body="",
|
|
virtual=False,
|
|
):
|
|
self.inline = inline or bodyInHeader
|
|
self.bodyInHeader = bodyInHeader
|
|
self.body = body
|
|
self.virtual = virtual
|
|
ClassItem.__init__(self, None, visibility)
|
|
|
|
def getDecorators(self, declaring):
|
|
decorators = []
|
|
if self.virtual and declaring:
|
|
decorators.append("virtual")
|
|
if self.inline and declaring:
|
|
decorators.append("inline")
|
|
if decorators:
|
|
return " ".join(decorators) + " "
|
|
return ""
|
|
|
|
def getBody(self):
|
|
return self.body
|
|
|
|
def declare(self, cgClass):
|
|
if self.bodyInHeader:
|
|
body = "\n{\n" + indent(self.getBody()) + "}\n"
|
|
else:
|
|
body = ";\n"
|
|
|
|
return fill(
|
|
"${decorators}~${className}()${body}\n",
|
|
decorators=self.getDecorators(True),
|
|
className=cgClass.getNameString(),
|
|
body=body,
|
|
)
|
|
|
|
def define(self, cgClass):
|
|
if self.bodyInHeader:
|
|
return ""
|
|
return fill(
|
|
"""
|
|
${decorators}
|
|
${className}::~${className}()
|
|
{
|
|
$*{body}
|
|
}
|
|
""",
|
|
decorators=self.getDecorators(False),
|
|
className=cgClass.getNameString(),
|
|
body=self.getBody(),
|
|
)
|
|
|
|
|
|
class ClassMember(ClassItem):
|
|
def __init__(
|
|
self,
|
|
name,
|
|
type,
|
|
visibility="private",
|
|
static=False,
|
|
body=None,
|
|
hasIgnoreInitCheckFlag=False,
|
|
):
|
|
self.type = type
|
|
self.static = static
|
|
self.body = body
|
|
self.hasIgnoreInitCheckFlag = hasIgnoreInitCheckFlag
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def declare(self, cgClass):
|
|
return "%s%s%s %s;\n" % (
|
|
"static " if self.static else "",
|
|
"MOZ_INIT_OUTSIDE_CTOR " if self.hasIgnoreInitCheckFlag else "",
|
|
self.type,
|
|
self.name,
|
|
)
|
|
|
|
def define(self, cgClass):
|
|
if not self.static:
|
|
return ""
|
|
if self.body:
|
|
body = " = " + self.body
|
|
else:
|
|
body = ""
|
|
return "%s %s::%s%s;\n" % (self.type, cgClass.getNameString(), self.name, body)
|
|
|
|
|
|
class ClassEnum(ClassItem):
|
|
def __init__(self, name, entries, values=None, visibility="public"):
|
|
self.entries = entries
|
|
self.values = values
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def declare(self, cgClass):
|
|
entries = []
|
|
for i in range(0, len(self.entries)):
|
|
if not self.values or i >= len(self.values):
|
|
entry = "%s" % self.entries[i]
|
|
else:
|
|
entry = "%s = %s" % (self.entries[i], self.values[i])
|
|
entries.append(entry)
|
|
name = "" if not self.name else " " + self.name
|
|
return "enum%s\n{\n%s\n};\n" % (name, indent(",\n".join(entries)))
|
|
|
|
def define(self, cgClass):
|
|
# Only goes in the header
|
|
return ""
|
|
|
|
|
|
class ClassUnion(ClassItem):
|
|
def __init__(self, name, entries, visibility="public"):
|
|
self.entries = [entry + ";\n" for entry in entries]
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def declare(self, cgClass):
|
|
return "union %s\n{\n%s\n};\n" % (self.name, indent("".join(self.entries)))
|
|
|
|
def define(self, cgClass):
|
|
# Only goes in the header
|
|
return ""
|
|
|
|
|
|
class CGClass(CGThing):
|
|
def __init__(
|
|
self,
|
|
name,
|
|
bases=[],
|
|
members=[],
|
|
constructors=[],
|
|
destructor=None,
|
|
methods=[],
|
|
enums=[],
|
|
unions=[],
|
|
templateArgs=[],
|
|
templateSpecialization=[],
|
|
isStruct=False,
|
|
disallowCopyConstruction=False,
|
|
indent="",
|
|
decorators="",
|
|
extradeclarations="",
|
|
extradefinitions="",
|
|
):
|
|
CGThing.__init__(self)
|
|
self.name = name
|
|
self.bases = bases
|
|
self.members = members
|
|
self.constructors = constructors
|
|
# We store our single destructor in a list, since all of our
|
|
# code wants lists of members.
|
|
self.destructors = [destructor] if destructor else []
|
|
self.methods = methods
|
|
self.enums = enums
|
|
self.unions = unions
|
|
self.templateArgs = templateArgs
|
|
self.templateSpecialization = templateSpecialization
|
|
self.isStruct = isStruct
|
|
self.disallowCopyConstruction = disallowCopyConstruction
|
|
self.indent = indent
|
|
self.defaultVisibility = "public" if isStruct else "private"
|
|
self.decorators = decorators
|
|
self.extradeclarations = extradeclarations
|
|
self.extradefinitions = extradefinitions
|
|
|
|
def getNameString(self):
|
|
className = self.name
|
|
if self.templateSpecialization:
|
|
className += "<%s>" % ", ".join(
|
|
[str(a) for a in self.templateSpecialization]
|
|
)
|
|
return className
|
|
|
|
def declare(self):
|
|
result = ""
|
|
if self.templateArgs:
|
|
templateArgs = [a.declare() for a in self.templateArgs]
|
|
templateArgs = templateArgs[len(self.templateSpecialization) :]
|
|
result += "template <%s>\n" % ",".join([str(a) for a in templateArgs])
|
|
|
|
type = "struct" if self.isStruct else "class"
|
|
|
|
if self.templateSpecialization:
|
|
specialization = "<%s>" % ", ".join(
|
|
[str(a) for a in self.templateSpecialization]
|
|
)
|
|
else:
|
|
specialization = ""
|
|
|
|
myself = "%s %s%s" % (type, self.name, specialization)
|
|
if self.decorators != "":
|
|
myself += " " + self.decorators
|
|
result += myself
|
|
|
|
if self.bases:
|
|
inherit = " : "
|
|
result += inherit
|
|
# Grab our first base
|
|
baseItems = [CGGeneric(b.declare(self)) for b in self.bases]
|
|
bases = baseItems[:1]
|
|
# Indent the rest
|
|
bases.extend(
|
|
CGIndenter(b, len(myself) + len(inherit)) for b in baseItems[1:]
|
|
)
|
|
result += ",\n".join(b.define() for b in bases)
|
|
|
|
result += "\n{\n"
|
|
|
|
result += self.extradeclarations
|
|
|
|
def declareMembers(cgClass, memberList, defaultVisibility):
|
|
members = {"private": [], "protected": [], "public": []}
|
|
|
|
for member in memberList:
|
|
members[member.visibility].append(member)
|
|
|
|
if defaultVisibility == "public":
|
|
order = ["public", "protected", "private"]
|
|
else:
|
|
order = ["private", "protected", "public"]
|
|
|
|
result = ""
|
|
|
|
lastVisibility = defaultVisibility
|
|
for visibility in order:
|
|
list = members[visibility]
|
|
if list:
|
|
if visibility != lastVisibility:
|
|
result += visibility + ":\n"
|
|
for member in list:
|
|
result += indent(member.declare(cgClass))
|
|
lastVisibility = visibility
|
|
return (result, lastVisibility)
|
|
|
|
if self.disallowCopyConstruction:
|
|
|
|
class DisallowedCopyConstructor(object):
|
|
def __init__(self):
|
|
self.visibility = "private"
|
|
|
|
def declare(self, cgClass):
|
|
name = cgClass.getNameString()
|
|
return (
|
|
"%s(const %s&) = delete;\n"
|
|
"%s& operator=(const %s&) = delete;\n"
|
|
% (name, name, name, name)
|
|
)
|
|
|
|
disallowedCopyConstructors = [DisallowedCopyConstructor()]
|
|
else:
|
|
disallowedCopyConstructors = []
|
|
|
|
order = [
|
|
self.enums,
|
|
self.unions,
|
|
self.members,
|
|
self.constructors + disallowedCopyConstructors,
|
|
self.destructors,
|
|
self.methods,
|
|
]
|
|
|
|
lastVisibility = self.defaultVisibility
|
|
pieces = []
|
|
for memberList in order:
|
|
code, lastVisibility = declareMembers(self, memberList, lastVisibility)
|
|
|
|
if code:
|
|
code = code.rstrip() + "\n" # remove extra blank lines at the end
|
|
pieces.append(code)
|
|
|
|
result += "\n".join(pieces)
|
|
result += "};\n"
|
|
result = indent(result, len(self.indent))
|
|
return result
|
|
|
|
def define(self):
|
|
def defineMembers(cgClass, memberList, itemCount, separator=""):
|
|
result = ""
|
|
for member in memberList:
|
|
if itemCount != 0:
|
|
result = result + separator
|
|
definition = member.define(cgClass)
|
|
if definition:
|
|
# Member variables would only produce empty lines here.
|
|
result += definition
|
|
itemCount += 1
|
|
return (result, itemCount)
|
|
|
|
order = [
|
|
(self.members, ""),
|
|
(self.constructors, "\n"),
|
|
(self.destructors, "\n"),
|
|
(self.methods, "\n"),
|
|
]
|
|
|
|
result = self.extradefinitions
|
|
itemCount = 0
|
|
for memberList, separator in order:
|
|
memberString, itemCount = defineMembers(
|
|
self, memberList, itemCount, separator
|
|
)
|
|
result = result + memberString
|
|
return result
|
|
|
|
|
|
class CGResolveOwnProperty(CGAbstractStaticMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "wrapper"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::MutableHandle<JS::PropertyDescriptor>", "desc"),
|
|
]
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, "ResolveOwnProperty", "bool", args
|
|
)
|
|
|
|
def definition_body(self):
|
|
return "return js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, wrapper, id, desc);\n"
|
|
|
|
|
|
class CGResolveOwnPropertyViaResolve(CGAbstractBindingMethod):
|
|
"""
|
|
An implementation of Xray ResolveOwnProperty stuff for things that have a
|
|
resolve hook.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "wrapper"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::MutableHandle<JS::PropertyDescriptor>", "desc"),
|
|
]
|
|
CGAbstractBindingMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"ResolveOwnPropertyViaResolve",
|
|
args,
|
|
getThisObj="",
|
|
callArgs="",
|
|
)
|
|
|
|
def generate_code(self):
|
|
return CGGeneric(
|
|
dedent(
|
|
"""
|
|
{
|
|
// Since we're dealing with an Xray, do the resolve on the
|
|
// underlying object first. That gives it a chance to
|
|
// define properties on the actual object as needed, and
|
|
// then use the fact that it created the objects as a flag
|
|
// to avoid re-resolving the properties if someone deletes
|
|
// them.
|
|
JSAutoRealm ar(cx, obj);
|
|
JS_MarkCrossZoneId(cx, id);
|
|
JS::Rooted<JS::PropertyDescriptor> objDesc(cx);
|
|
if (!self->DoResolve(cx, obj, id, &objDesc)) {
|
|
return false;
|
|
}
|
|
// If desc.value() is undefined, then the DoResolve call
|
|
// has already defined the property on the object. Don't
|
|
// try to also define it.
|
|
if (objDesc.object() &&
|
|
!objDesc.value().isUndefined() &&
|
|
!JS_DefinePropertyById(cx, obj, id, objDesc)) {
|
|
return false;
|
|
}
|
|
}
|
|
return self->DoResolve(cx, wrapper, id, desc);
|
|
"""
|
|
)
|
|
)
|
|
|
|
|
|
class CGEnumerateOwnProperties(CGAbstractStaticMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "wrapper"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("JS::MutableHandleVector<jsid>", "props"),
|
|
]
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, "EnumerateOwnProperties", "bool", args
|
|
)
|
|
|
|
def definition_body(self):
|
|
return "return js::GetProxyHandler(obj)->ownPropertyKeys(cx, wrapper, props);\n"
|
|
|
|
|
|
class CGEnumerateOwnPropertiesViaGetOwnPropertyNames(CGAbstractBindingMethod):
|
|
"""
|
|
An implementation of Xray EnumerateOwnProperties stuff for things
|
|
that have a resolve hook.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "wrapper"),
|
|
Argument("JS::Handle<JSObject*>", "obj"),
|
|
Argument("JS::MutableHandleVector<jsid>", "props"),
|
|
]
|
|
CGAbstractBindingMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"EnumerateOwnPropertiesViaGetOwnPropertyNames",
|
|
args,
|
|
getThisObj="",
|
|
callArgs="",
|
|
)
|
|
|
|
def generate_code(self):
|
|
return CGGeneric(
|
|
dedent(
|
|
"""
|
|
FastErrorResult rv;
|
|
// This wants all own props, not just enumerable ones.
|
|
self->GetOwnPropertyNames(cx, props, false, rv);
|
|
if (rv.MaybeSetPendingException(cx)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
"""
|
|
)
|
|
)
|
|
|
|
|
|
class CGPrototypeTraitsClass(CGClass):
|
|
def __init__(self, descriptor, indent=""):
|
|
templateArgs = [Argument("prototypes::ID", "PrototypeID")]
|
|
templateSpecialization = ["prototypes::id::" + descriptor.name]
|
|
enums = [ClassEnum("", ["Depth"], [descriptor.interface.inheritanceDepth()])]
|
|
CGClass.__init__(
|
|
self,
|
|
"PrototypeTraits",
|
|
indent=indent,
|
|
templateArgs=templateArgs,
|
|
templateSpecialization=templateSpecialization,
|
|
enums=enums,
|
|
isStruct=True,
|
|
)
|
|
|
|
def deps(self):
|
|
return set()
|
|
|
|
|
|
class CGClassForwardDeclare(CGThing):
|
|
def __init__(self, name, isStruct=False):
|
|
CGThing.__init__(self)
|
|
self.name = name
|
|
self.isStruct = isStruct
|
|
|
|
def declare(self):
|
|
type = "struct" if self.isStruct else "class"
|
|
return "%s %s;\n" % (type, self.name)
|
|
|
|
def define(self):
|
|
# Header only
|
|
return ""
|
|
|
|
def deps(self):
|
|
return set()
|
|
|
|
|
|
class CGProxySpecialOperation(CGPerSignatureCall):
|
|
"""
|
|
Base class for classes for calling an indexed or named special operation
|
|
(don't use this directly, use the derived classes below).
|
|
|
|
If checkFound is False, will just assert that the prop is found instead of
|
|
checking that it is before wrapping the value.
|
|
|
|
resultVar: See the docstring for CGCallGenerator.
|
|
|
|
foundVar: For getters and deleters, the generated code can also set a bool
|
|
variable, declared by the caller, if the given indexed or named property
|
|
already existed. If the caller wants this, it should pass the name of the
|
|
bool variable as the foundVar keyword argument to the constructor. The
|
|
caller is responsible for declaring the variable and initializing it to
|
|
false.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
operation,
|
|
checkFound=True,
|
|
argumentHandleValue=None,
|
|
resultVar=None,
|
|
foundVar=None,
|
|
):
|
|
self.checkFound = checkFound
|
|
self.foundVar = foundVar or "found"
|
|
|
|
nativeName = MakeNativeName(descriptor.binaryNameFor(operation))
|
|
operation = descriptor.operations[operation]
|
|
assert len(operation.signatures()) == 1
|
|
signature = operation.signatures()[0]
|
|
|
|
returnType, arguments = signature
|
|
|
|
# We pass len(arguments) as the final argument so that the
|
|
# CGPerSignatureCall won't do any argument conversion of its own.
|
|
CGPerSignatureCall.__init__(
|
|
self,
|
|
returnType,
|
|
arguments,
|
|
nativeName,
|
|
False,
|
|
descriptor,
|
|
operation,
|
|
len(arguments),
|
|
resultVar=resultVar,
|
|
objectName="proxy",
|
|
)
|
|
|
|
if operation.isSetter():
|
|
# arguments[0] is the index or name of the item that we're setting.
|
|
argument = arguments[1]
|
|
info = getJSToNativeConversionInfo(
|
|
argument.type,
|
|
descriptor,
|
|
sourceDescription=(
|
|
"value being assigned to %s setter"
|
|
% descriptor.interface.identifier.name
|
|
),
|
|
)
|
|
if argumentHandleValue is None:
|
|
argumentHandleValue = "desc.value()"
|
|
rootedValue = fill(
|
|
"""
|
|
JS::Rooted<JS::Value> rootedValue(cx, ${argumentHandleValue});
|
|
""",
|
|
argumentHandleValue=argumentHandleValue,
|
|
)
|
|
templateValues = {
|
|
"declName": argument.identifier.name,
|
|
"holderName": argument.identifier.name + "_holder",
|
|
"val": argumentHandleValue,
|
|
"maybeMutableVal": "&rootedValue",
|
|
"obj": "obj",
|
|
"passedToJSImpl": "false",
|
|
}
|
|
self.cgRoot.prepend(instantiateJSToNativeConversion(info, templateValues))
|
|
# rootedValue needs to come before the conversion, so we
|
|
# need to prepend it last.
|
|
self.cgRoot.prepend(CGGeneric(rootedValue))
|
|
elif operation.isGetter() or operation.isDeleter():
|
|
if foundVar is None:
|
|
self.cgRoot.prepend(CGGeneric("bool found = false;\n"))
|
|
|
|
def getArguments(self):
|
|
args = [(a, a.identifier.name) for a in self.arguments]
|
|
if self.idlNode.isGetter() or self.idlNode.isDeleter():
|
|
args.append(
|
|
(
|
|
FakeArgument(
|
|
BuiltinTypes[IDLBuiltinType.Types.boolean], self.idlNode
|
|
),
|
|
self.foundVar,
|
|
)
|
|
)
|
|
return args
|
|
|
|
def wrap_return_value(self):
|
|
if not self.idlNode.isGetter() or self.templateValues is None:
|
|
return ""
|
|
|
|
wrap = CGGeneric(
|
|
wrapForType(self.returnType, self.descriptor, self.templateValues)
|
|
)
|
|
if self.checkFound:
|
|
wrap = CGIfWrapper(wrap, self.foundVar)
|
|
else:
|
|
wrap = CGList([CGGeneric("MOZ_ASSERT(" + self.foundVar + ");\n"), wrap])
|
|
return "\n" + wrap.define()
|
|
|
|
|
|
class CGProxyIndexedOperation(CGProxySpecialOperation):
|
|
"""
|
|
Class to generate a call to an indexed operation.
|
|
|
|
If doUnwrap is False, the caller is responsible for making sure a variable
|
|
named 'self' holds the C++ object somewhere where the code we generate
|
|
will see it.
|
|
|
|
If checkFound is False, will just assert that the prop is found instead of
|
|
checking that it is before wrapping the value.
|
|
|
|
resultVar: See the docstring for CGCallGenerator.
|
|
|
|
foundVar: See the docstring for CGProxySpecialOperation.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
name,
|
|
doUnwrap=True,
|
|
checkFound=True,
|
|
argumentHandleValue=None,
|
|
resultVar=None,
|
|
foundVar=None,
|
|
):
|
|
self.doUnwrap = doUnwrap
|
|
CGProxySpecialOperation.__init__(
|
|
self,
|
|
descriptor,
|
|
name,
|
|
checkFound,
|
|
argumentHandleValue=argumentHandleValue,
|
|
resultVar=resultVar,
|
|
foundVar=foundVar,
|
|
)
|
|
|
|
def define(self):
|
|
# Our first argument is the id we're getting.
|
|
argName = self.arguments[0].identifier.name
|
|
if argName == "index":
|
|
# We already have our index in a variable with that name
|
|
setIndex = ""
|
|
else:
|
|
setIndex = "uint32_t %s = index;\n" % argName
|
|
if self.doUnwrap:
|
|
unwrap = "%s* self = UnwrapProxy(proxy);\n" % self.descriptor.nativeType
|
|
else:
|
|
unwrap = ""
|
|
return setIndex + unwrap + CGProxySpecialOperation.define(self)
|
|
|
|
|
|
class CGProxyIndexedGetter(CGProxyIndexedOperation):
|
|
"""
|
|
Class to generate a call to an indexed getter. If templateValues is not None
|
|
the returned value will be wrapped with wrapForType using templateValues.
|
|
|
|
If doUnwrap is False, the caller is responsible for making sure a variable
|
|
named 'self' holds the C++ object somewhere where the code we generate
|
|
will see it.
|
|
|
|
If checkFound is False, will just assert that the prop is found instead of
|
|
checking that it is before wrapping the value.
|
|
|
|
foundVar: See the docstring for CGProxySpecialOperation.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
templateValues=None,
|
|
doUnwrap=True,
|
|
checkFound=True,
|
|
foundVar=None,
|
|
):
|
|
self.templateValues = templateValues
|
|
CGProxyIndexedOperation.__init__(
|
|
self, descriptor, "IndexedGetter", doUnwrap, checkFound, foundVar=foundVar
|
|
)
|
|
|
|
|
|
class CGProxyIndexedPresenceChecker(CGProxyIndexedGetter):
|
|
"""
|
|
Class to generate a call that checks whether an indexed property exists.
|
|
|
|
For now, we just delegate to CGProxyIndexedGetter
|
|
|
|
foundVar: See the docstring for CGProxySpecialOperation.
|
|
"""
|
|
|
|
def __init__(self, descriptor, foundVar):
|
|
CGProxyIndexedGetter.__init__(self, descriptor, foundVar=foundVar)
|
|
self.cgRoot.append(CGGeneric("(void)result;\n"))
|
|
|
|
|
|
class CGProxyIndexedSetter(CGProxyIndexedOperation):
|
|
"""
|
|
Class to generate a call to an indexed setter.
|
|
"""
|
|
|
|
def __init__(self, descriptor, argumentHandleValue=None):
|
|
CGProxyIndexedOperation.__init__(
|
|
self, descriptor, "IndexedSetter", argumentHandleValue=argumentHandleValue
|
|
)
|
|
|
|
|
|
class CGProxyNamedOperation(CGProxySpecialOperation):
|
|
"""
|
|
Class to generate a call to a named operation.
|
|
|
|
'value' is the jsval to use for the name; None indicates that it should be
|
|
gotten from the property id.
|
|
|
|
resultVar: See the docstring for CGCallGenerator.
|
|
|
|
foundVar: See the docstring for CGProxySpecialOperation.
|
|
|
|
tailCode: if we end up with a non-symbol string id, run this code after
|
|
we do all our other work.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
name,
|
|
value=None,
|
|
argumentHandleValue=None,
|
|
resultVar=None,
|
|
foundVar=None,
|
|
tailCode="",
|
|
):
|
|
CGProxySpecialOperation.__init__(
|
|
self,
|
|
descriptor,
|
|
name,
|
|
argumentHandleValue=argumentHandleValue,
|
|
resultVar=resultVar,
|
|
foundVar=foundVar,
|
|
)
|
|
self.value = value
|
|
self.tailCode = tailCode
|
|
|
|
def define(self):
|
|
# Our first argument is the id we're getting.
|
|
argName = self.arguments[0].identifier.name
|
|
if argName == "id":
|
|
# deal with the name collision
|
|
decls = "JS::Rooted<jsid> id_(cx, id);\n"
|
|
idName = "id_"
|
|
else:
|
|
decls = ""
|
|
idName = "id"
|
|
|
|
decls += "FakeString<char16_t> %s;\n" % argName
|
|
|
|
main = fill(
|
|
"""
|
|
${nativeType}* self = UnwrapProxy(proxy);
|
|
$*{op}
|
|
$*{tailCode}
|
|
""",
|
|
nativeType=self.descriptor.nativeType,
|
|
op=CGProxySpecialOperation.define(self),
|
|
tailCode=self.tailCode,
|
|
)
|
|
|
|
if self.value is None:
|
|
return fill(
|
|
"""
|
|
$*{decls}
|
|
bool isSymbol;
|
|
if (!ConvertIdToString(cx, ${idName}, ${argName}, isSymbol)) {
|
|
return false;
|
|
}
|
|
if (!isSymbol) {
|
|
$*{main}
|
|
}
|
|
""",
|
|
decls=decls,
|
|
idName=idName,
|
|
argName=argName,
|
|
main=main,
|
|
)
|
|
|
|
# Sadly, we have to set up nameVal even if we have an atom id,
|
|
# because we don't know for sure, and we can end up needing it
|
|
# so it needs to be higher up the stack. Using a Maybe here
|
|
# seems like probable overkill.
|
|
return fill(
|
|
"""
|
|
$*{decls}
|
|
JS::Rooted<JS::Value> nameVal(cx, ${value});
|
|
if (!nameVal.isSymbol()) {
|
|
if (!ConvertJSValueToString(cx, nameVal, eStringify, eStringify,
|
|
${argName})) {
|
|
return false;
|
|
}
|
|
$*{main}
|
|
}
|
|
""",
|
|
decls=decls,
|
|
value=self.value,
|
|
argName=argName,
|
|
main=main,
|
|
)
|
|
|
|
|
|
class CGProxyNamedGetter(CGProxyNamedOperation):
|
|
"""
|
|
Class to generate a call to an named getter. If templateValues is not None
|
|
the returned value will be wrapped with wrapForType using templateValues.
|
|
'value' is the jsval to use for the name; None indicates that it should be
|
|
gotten from the property id.
|
|
|
|
foundVar: See the docstring for CGProxySpecialOperation.
|
|
"""
|
|
|
|
def __init__(self, descriptor, templateValues=None, value=None, foundVar=None):
|
|
self.templateValues = templateValues
|
|
CGProxyNamedOperation.__init__(
|
|
self, descriptor, "NamedGetter", value, foundVar=foundVar
|
|
)
|
|
|
|
|
|
class CGProxyNamedPresenceChecker(CGProxyNamedGetter):
|
|
"""
|
|
Class to generate a call that checks whether a named property exists.
|
|
|
|
For now, we just delegate to CGProxyNamedGetter
|
|
|
|
foundVar: See the docstring for CGProxySpecialOperation.
|
|
"""
|
|
|
|
def __init__(self, descriptor, foundVar=None):
|
|
CGProxyNamedGetter.__init__(self, descriptor, foundVar=foundVar)
|
|
self.cgRoot.append(CGGeneric("(void)result;\n"))
|
|
|
|
|
|
class CGProxyNamedSetter(CGProxyNamedOperation):
|
|
"""
|
|
Class to generate a call to a named setter.
|
|
"""
|
|
|
|
def __init__(self, descriptor, tailCode, argumentHandleValue=None):
|
|
CGProxyNamedOperation.__init__(
|
|
self,
|
|
descriptor,
|
|
"NamedSetter",
|
|
argumentHandleValue=argumentHandleValue,
|
|
tailCode=tailCode,
|
|
)
|
|
|
|
|
|
class CGProxyNamedDeleter(CGProxyNamedOperation):
|
|
"""
|
|
Class to generate a call to a named deleter.
|
|
|
|
resultVar: See the docstring for CGCallGenerator.
|
|
|
|
foundVar: See the docstring for CGProxySpecialOperation.
|
|
"""
|
|
|
|
def __init__(self, descriptor, resultVar=None, foundVar=None):
|
|
CGProxyNamedOperation.__init__(
|
|
self, descriptor, "NamedDeleter", resultVar=resultVar, foundVar=foundVar
|
|
)
|
|
|
|
|
|
class CGProxyIsProxy(CGAbstractMethod):
|
|
def __init__(self, descriptor):
|
|
args = [Argument("JSObject*", "obj")]
|
|
CGAbstractMethod.__init__(
|
|
self, descriptor, "IsProxy", "bool", args, alwaysInline=True
|
|
)
|
|
|
|
def declare(self):
|
|
return ""
|
|
|
|
def definition_body(self):
|
|
return "return js::IsProxy(obj) && js::GetProxyHandler(obj) == DOMProxyHandler::getInstance();\n"
|
|
|
|
|
|
class CGProxyUnwrap(CGAbstractMethod):
|
|
def __init__(self, descriptor):
|
|
args = [Argument("JSObject*", "obj")]
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"UnwrapProxy",
|
|
descriptor.nativeType + "*",
|
|
args,
|
|
alwaysInline=True,
|
|
)
|
|
|
|
def declare(self):
|
|
return ""
|
|
|
|
def definition_body(self):
|
|
return fill(
|
|
"""
|
|
MOZ_ASSERT(js::IsProxy(obj));
|
|
if (js::GetProxyHandler(obj) != DOMProxyHandler::getInstance()) {
|
|
MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(obj));
|
|
obj = js::UncheckedUnwrap(obj);
|
|
}
|
|
MOZ_ASSERT(IsProxy(obj));
|
|
return static_cast<${type}*>(js::GetProxyReservedSlot(obj, DOM_OBJECT_SLOT).toPrivate());
|
|
""",
|
|
type=self.descriptor.nativeType,
|
|
)
|
|
|
|
|
|
MISSING_PROP_PREF = "dom.missing_prop_counters.enabled"
|
|
|
|
|
|
def missingPropUseCountersForDescriptor(desc):
|
|
if not desc.needsMissingPropUseCounters:
|
|
return ""
|
|
|
|
return fill(
|
|
"""
|
|
if (StaticPrefs::${pref}() && JSID_IS_ATOM(id)) {
|
|
CountMaybeMissingProperty(proxy, id);
|
|
}
|
|
|
|
""",
|
|
pref=prefIdentifier(MISSING_PROP_PREF),
|
|
)
|
|
|
|
|
|
def findAncestorWithInstrumentedProps(desc):
|
|
"""
|
|
Find an ancestor of desc.interface (not including desc.interface
|
|
itself) that has instrumented properties on it. May return None
|
|
if there is no such ancestor.
|
|
"""
|
|
ancestor = desc.interface.parent
|
|
while ancestor:
|
|
if ancestor.getExtendedAttribute("InstrumentedProps"):
|
|
return ancestor
|
|
ancestor = ancestor.parent
|
|
return None
|
|
|
|
|
|
class CGCountMaybeMissingProperty(CGAbstractMethod):
|
|
def __init__(self, descriptor):
|
|
"""
|
|
Returns whether we counted the property involved.
|
|
"""
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
descriptor,
|
|
"CountMaybeMissingProperty",
|
|
"bool",
|
|
[
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
],
|
|
)
|
|
|
|
def gen_switch(self, switchDecriptor):
|
|
"""
|
|
Generate a switch from the switch descriptor. The descriptor
|
|
dictionary must have the following properties:
|
|
|
|
1) A "precondition" property that contains code to run before the
|
|
switch statement. Its value ie a string.
|
|
2) A "condition" property for the condition. Its value is a string.
|
|
3) A "cases" property. Its value is an object that has property names
|
|
corresponding to the case labels. The values of those properties
|
|
are either new switch descriptor dictionaries (which will then
|
|
generate nested switches) or strings to use for case bodies.
|
|
"""
|
|
cases = []
|
|
for label, body in sorted(six.iteritems(switchDecriptor["cases"])):
|
|
if isinstance(body, dict):
|
|
body = self.gen_switch(body)
|
|
cases.append(
|
|
fill(
|
|
"""
|
|
case ${label}: {
|
|
$*{body}
|
|
break;
|
|
}
|
|
""",
|
|
label=label,
|
|
body=body,
|
|
)
|
|
)
|
|
return fill(
|
|
"""
|
|
$*{precondition}
|
|
switch (${condition}) {
|
|
$*{cases}
|
|
}
|
|
""",
|
|
precondition=switchDecriptor["precondition"],
|
|
condition=switchDecriptor["condition"],
|
|
cases="".join(cases),
|
|
)
|
|
|
|
def charSwitch(self, props, charIndex):
|
|
"""
|
|
Create a switch for the given props, based on the first char where
|
|
they start to differ at index charIndex or more. Each prop is a tuple
|
|
containing interface name and prop name.
|
|
|
|
Incoming props should be a sorted list.
|
|
"""
|
|
if len(props) == 1:
|
|
# We're down to one string: just check whether we match it.
|
|
return fill(
|
|
"""
|
|
if (JS_LinearStringEqualsLiteral(str, "${name}")) {
|
|
counter.emplace(eUseCounter_${iface}_${name});
|
|
}
|
|
""",
|
|
iface=self.descriptor.name,
|
|
name=props[0],
|
|
)
|
|
|
|
switch = dict()
|
|
if charIndex == 0:
|
|
switch["precondition"] = "StringIdChars chars(nogc, str);\n"
|
|
else:
|
|
switch["precondition"] = ""
|
|
|
|
# Find the first place where we might actually have a difference.
|
|
while all(prop[charIndex] == props[0][charIndex] for prop in props):
|
|
charIndex += 1
|
|
|
|
switch["condition"] = "chars[%d]" % charIndex
|
|
switch["cases"] = dict()
|
|
current_props = None
|
|
curChar = None
|
|
idx = 0
|
|
while idx < len(props):
|
|
nextChar = "'%s'" % props[idx][charIndex]
|
|
if nextChar != curChar:
|
|
if curChar:
|
|
switch["cases"][curChar] = self.charSwitch(
|
|
current_props, charIndex + 1
|
|
)
|
|
current_props = []
|
|
curChar = nextChar
|
|
current_props.append(props[idx])
|
|
idx += 1
|
|
switch["cases"][curChar] = self.charSwitch(current_props, charIndex + 1)
|
|
return switch
|
|
|
|
def definition_body(self):
|
|
ancestor = findAncestorWithInstrumentedProps(self.descriptor)
|
|
|
|
if ancestor:
|
|
body = fill(
|
|
"""
|
|
if (${ancestor}_Binding::CountMaybeMissingProperty(proxy, id)) {
|
|
return true;
|
|
}
|
|
|
|
""",
|
|
ancestor=ancestor.identifier.name,
|
|
)
|
|
else:
|
|
body = ""
|
|
|
|
instrumentedProps = self.descriptor.instrumentedProps
|
|
if not instrumentedProps:
|
|
return body + dedent(
|
|
"""
|
|
return false;
|
|
"""
|
|
)
|
|
|
|
lengths = set(len(prop) for prop in instrumentedProps)
|
|
switchDesc = {"condition": "JS::GetLinearStringLength(str)", "precondition": ""}
|
|
switchDesc["cases"] = dict()
|
|
for length in sorted(lengths):
|
|
switchDesc["cases"][str(length)] = self.charSwitch(
|
|
list(sorted(prop for prop in instrumentedProps if len(prop) == length)),
|
|
0,
|
|
)
|
|
|
|
return body + fill(
|
|
"""
|
|
MOZ_ASSERT(StaticPrefs::${pref}() && JSID_IS_ATOM(id));
|
|
Maybe<UseCounter> counter;
|
|
{
|
|
// Scope for our no-GC section, so we don't need to rely on SetUseCounter not GCing.
|
|
JS::AutoCheckCannotGC nogc;
|
|
JSLinearString* str = JS::AtomToLinearString(JSID_TO_ATOM(id));
|
|
// Don't waste time fetching the chars until we've done the length switch.
|
|
$*{switch}
|
|
}
|
|
if (counter) {
|
|
SetUseCounter(proxy, *counter);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
""",
|
|
pref=prefIdentifier(MISSING_PROP_PREF),
|
|
switch=self.gen_switch(switchDesc),
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_getOwnPropDescriptor(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("bool", "ignoreNamedProps"),
|
|
Argument("JS::MutableHandle<JS::PropertyDescriptor>", "desc"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self,
|
|
"getOwnPropDescriptor",
|
|
"bool",
|
|
args,
|
|
virtual=True,
|
|
override=True,
|
|
const=True,
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
indexedGetter = self.descriptor.operations["IndexedGetter"]
|
|
indexedSetter = self.descriptor.operations["IndexedSetter"]
|
|
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
xrayDecl = dedent(
|
|
"""
|
|
MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy));
|
|
MOZ_ASSERT(IsPlatformObjectSameOrigin(cx, proxy),
|
|
"getOwnPropertyDescriptor() and set() should have dealt");
|
|
MOZ_ASSERT(js::IsObjectInContextCompartment(proxy, cx),
|
|
"getOwnPropertyDescriptor() and set() should have dealt");
|
|
|
|
"""
|
|
)
|
|
xrayCheck = ""
|
|
else:
|
|
xrayDecl = "bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);\n"
|
|
xrayCheck = "!isXray &&"
|
|
|
|
if self.descriptor.supportsIndexedProperties():
|
|
readonly = toStringBool(indexedSetter is None)
|
|
fillDescriptor = (
|
|
"FillPropertyDescriptor(desc, proxy, %s);\nreturn true;\n" % readonly
|
|
)
|
|
templateValues = {
|
|
"jsvalRef": "desc.value()",
|
|
"jsvalHandle": "desc.value()",
|
|
"obj": "proxy",
|
|
"successCode": fillDescriptor,
|
|
}
|
|
getIndexed = fill(
|
|
"""
|
|
uint32_t index = GetArrayIndexFromId(id);
|
|
if (IsArrayIndex(index)) {
|
|
$*{callGetter}
|
|
}
|
|
|
|
""",
|
|
callGetter=CGProxyIndexedGetter(
|
|
self.descriptor, templateValues
|
|
).define(),
|
|
)
|
|
else:
|
|
getIndexed = ""
|
|
|
|
missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor)
|
|
|
|
if self.descriptor.supportsNamedProperties():
|
|
operations = self.descriptor.operations
|
|
readonly = toStringBool(operations["NamedSetter"] is None)
|
|
fillDescriptor = (
|
|
"FillPropertyDescriptor(desc, proxy, %s, %s);\n"
|
|
"return true;\n"
|
|
% (readonly, toStringBool(self.descriptor.namedPropertiesEnumerable))
|
|
)
|
|
templateValues = {
|
|
"jsvalRef": "desc.value()",
|
|
"jsvalHandle": "desc.value()",
|
|
"obj": "proxy",
|
|
"successCode": fillDescriptor,
|
|
}
|
|
|
|
computeCondition = dedent(
|
|
"""
|
|
bool hasOnProto;
|
|
if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
|
|
return false;
|
|
}
|
|
callNamedGetter = !hasOnProto;
|
|
"""
|
|
)
|
|
if self.descriptor.interface.getExtendedAttribute("OverrideBuiltins"):
|
|
computeCondition = fill(
|
|
"""
|
|
if (!isXray) {
|
|
callNamedGetter = true;
|
|
} else {
|
|
$*{hasOnProto}
|
|
}
|
|
""",
|
|
hasOnProto=computeCondition,
|
|
)
|
|
|
|
outerCondition = "!ignoreNamedProps"
|
|
if self.descriptor.supportsIndexedProperties():
|
|
outerCondition = "!IsArrayIndex(index) && " + outerCondition
|
|
|
|
namedGetCode = CGProxyNamedGetter(self.descriptor, templateValues).define()
|
|
namedGet = fill(
|
|
"""
|
|
bool callNamedGetter = false;
|
|
if (${outerCondition}) {
|
|
$*{computeCondition}
|
|
}
|
|
if (callNamedGetter) {
|
|
$*{namedGetCode}
|
|
}
|
|
""",
|
|
outerCondition=outerCondition,
|
|
computeCondition=computeCondition,
|
|
namedGetCode=namedGetCode,
|
|
)
|
|
namedGet += "\n"
|
|
else:
|
|
namedGet = ""
|
|
|
|
return fill(
|
|
"""
|
|
$*{xrayDecl}
|
|
$*{getIndexed}
|
|
$*{missingPropUseCounters}
|
|
JS::Rooted<JSObject*> expando(cx);
|
|
if (${xrayCheck}(expando = GetExpandoObject(proxy))) {
|
|
if (!JS_GetOwnPropertyDescriptorById(cx, expando, id, desc)) {
|
|
return false;
|
|
}
|
|
if (desc.object()) {
|
|
// Pretend the property lives on the wrapper.
|
|
desc.object().set(proxy);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
$*{namedGet}
|
|
desc.object().set(nullptr);
|
|
return true;
|
|
""",
|
|
xrayDecl=xrayDecl,
|
|
xrayCheck=xrayCheck,
|
|
getIndexed=getIndexed,
|
|
missingPropUseCounters=missingPropUseCounters,
|
|
namedGet=namedGet,
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_defineProperty(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
# The usual convention is to name the ObjectOpResult out-parameter
|
|
# `result`, but that name is a bit overloaded around here.
|
|
args = [
|
|
Argument("JSContext*", "cx_"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::Handle<JS::PropertyDescriptor>", "desc"),
|
|
Argument("JS::ObjectOpResult&", "opresult"),
|
|
Argument("bool*", "done"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self,
|
|
"defineProperty",
|
|
"bool",
|
|
args,
|
|
virtual=True,
|
|
override=True,
|
|
const=True,
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
set = ""
|
|
|
|
indexedSetter = self.descriptor.operations["IndexedSetter"]
|
|
if indexedSetter:
|
|
error_label = CGSpecializedMethod.error_reporting_label_helper(
|
|
self.descriptor, indexedSetter, isConstructor=False
|
|
)
|
|
if error_label:
|
|
cxDecl = fill(
|
|
"""
|
|
BindingCallContext cx(cx_, "${error_label}");
|
|
""",
|
|
error_label=error_label,
|
|
)
|
|
else:
|
|
cxDecl = dedent(
|
|
"""
|
|
JSContext* cx = cx_;
|
|
"""
|
|
)
|
|
set += fill(
|
|
"""
|
|
uint32_t index = GetArrayIndexFromId(id);
|
|
if (IsArrayIndex(index)) {
|
|
$*{cxDecl}
|
|
*done = true;
|
|
// https://heycam.github.io/webidl/#legacy-platform-object-defineownproperty
|
|
// Step 1.1. The no-indexed-setter case is handled by step 1.2.
|
|
if (!desc.isDataDescriptor()) {
|
|
return opresult.failNotDataDescriptor();
|
|
}
|
|
|
|
$*{callSetter}
|
|
return opresult.succeed();
|
|
}
|
|
""",
|
|
cxDecl=cxDecl,
|
|
callSetter=CGProxyIndexedSetter(self.descriptor).define(),
|
|
)
|
|
elif self.descriptor.supportsIndexedProperties():
|
|
# We allow untrusted content to prevent Xrays from setting a
|
|
# property if that property is an indexed property and we have no
|
|
# indexed setter. That's how the object would normally behave if
|
|
# you tried to set the property on it. That means we don't need to
|
|
# do anything special for Xrays here.
|
|
set += dedent(
|
|
"""
|
|
if (IsArrayIndex(GetArrayIndexFromId(id))) {
|
|
*done = true;
|
|
return opresult.failNoIndexedSetter();
|
|
}
|
|
"""
|
|
)
|
|
|
|
namedSetter = self.descriptor.operations["NamedSetter"]
|
|
if namedSetter:
|
|
error_label = CGSpecializedMethod.error_reporting_label_helper(
|
|
self.descriptor, namedSetter, isConstructor=False
|
|
)
|
|
if error_label:
|
|
set += fill(
|
|
"""
|
|
BindingCallContext cx(cx_, "${error_label}");
|
|
""",
|
|
error_label=error_label,
|
|
)
|
|
else:
|
|
set += dedent(
|
|
"""
|
|
JSContext* cx = cx_;
|
|
"""
|
|
)
|
|
if self.descriptor.hasUnforgeableMembers:
|
|
raise TypeError(
|
|
"Can't handle a named setter on an interface "
|
|
"that has unforgeables. Figure out how that "
|
|
"should work!"
|
|
)
|
|
tailCode = dedent(
|
|
"""
|
|
*done = true;
|
|
return opresult.succeed();
|
|
"""
|
|
)
|
|
set += CGProxyNamedSetter(self.descriptor, tailCode).define()
|
|
else:
|
|
# We allow untrusted content to prevent Xrays from setting a
|
|
# property if that property is already a named property on the
|
|
# object and we have no named setter. That's how the object would
|
|
# normally behave if you tried to set the property on it. That
|
|
# means we don't need to do anything special for Xrays here.
|
|
if self.descriptor.supportsNamedProperties():
|
|
set += fill(
|
|
"""
|
|
JSContext* cx = cx_;
|
|
bool found = false;
|
|
$*{presenceChecker}
|
|
|
|
if (found) {
|
|
*done = true;
|
|
return opresult.failNoNamedSetter();
|
|
}
|
|
""",
|
|
presenceChecker=CGProxyNamedPresenceChecker(
|
|
self.descriptor, foundVar="found"
|
|
).define(),
|
|
)
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
set += dedent(
|
|
"""
|
|
MOZ_ASSERT(IsPlatformObjectSameOrigin(cx_, proxy),
|
|
"Why did the MaybeCrossOriginObject defineProperty override fail?");
|
|
MOZ_ASSERT(js::IsObjectInContextCompartment(proxy, cx_),
|
|
"Why did the MaybeCrossOriginObject defineProperty override fail?");
|
|
"""
|
|
)
|
|
|
|
# In all cases we want to tail-call to our base class; we can
|
|
# always land here for symbols.
|
|
set += (
|
|
"return mozilla::dom::DOMProxyHandler::defineProperty(%s);\n"
|
|
% ", ".join(a.name for a in self.args)
|
|
)
|
|
return set
|
|
|
|
|
|
def getDeleterBody(descriptor, type, foundVar=None):
|
|
"""
|
|
type should be "Named" or "Indexed"
|
|
|
|
The possible outcomes:
|
|
- an error happened (the emitted code returns false)
|
|
- own property not found (foundVar=false, deleteSucceeded=true)
|
|
- own property found and deleted (foundVar=true, deleteSucceeded=true)
|
|
- own property found but can't be deleted (foundVar=true, deleteSucceeded=false)
|
|
"""
|
|
assert type in ("Named", "Indexed")
|
|
deleter = descriptor.operations[type + "Deleter"]
|
|
if deleter:
|
|
assert type == "Named"
|
|
assert foundVar is not None
|
|
if descriptor.hasUnforgeableMembers:
|
|
raise TypeError(
|
|
"Can't handle a deleter on an interface "
|
|
"that has unforgeables. Figure out how "
|
|
"that should work!"
|
|
)
|
|
# See if the deleter method is fallible.
|
|
t = deleter.signatures()[0][0]
|
|
if t.isPrimitive() and not t.nullable() and t.tag() == IDLType.Tags.bool:
|
|
# The deleter method has a boolean return value. When a
|
|
# property is found, the return value indicates whether it
|
|
# was successfully deleted.
|
|
setDS = fill(
|
|
"""
|
|
if (!${foundVar}) {
|
|
deleteSucceeded = true;
|
|
}
|
|
""",
|
|
foundVar=foundVar,
|
|
)
|
|
else:
|
|
# No boolean return value: if a property is found,
|
|
# deleting it always succeeds.
|
|
setDS = "deleteSucceeded = true;\n"
|
|
|
|
body = (
|
|
CGProxyNamedDeleter(
|
|
descriptor, resultVar="deleteSucceeded", foundVar=foundVar
|
|
).define()
|
|
+ setDS
|
|
)
|
|
elif getattr(descriptor, "supports%sProperties" % type)():
|
|
presenceCheckerClass = globals()["CGProxy%sPresenceChecker" % type]
|
|
foundDecl = ""
|
|
if foundVar is None:
|
|
foundVar = "found"
|
|
foundDecl = "bool found = false;\n"
|
|
body = fill(
|
|
"""
|
|
$*{foundDecl}
|
|
$*{presenceChecker}
|
|
deleteSucceeded = !${foundVar};
|
|
""",
|
|
foundDecl=foundDecl,
|
|
presenceChecker=presenceCheckerClass(
|
|
descriptor, foundVar=foundVar
|
|
).define(),
|
|
foundVar=foundVar,
|
|
)
|
|
else:
|
|
body = None
|
|
return body
|
|
|
|
|
|
class CGDeleteNamedProperty(CGAbstractStaticMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "xray"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::ObjectOpResult&", "opresult"),
|
|
]
|
|
CGAbstractStaticMethod.__init__(
|
|
self, descriptor, "DeleteNamedProperty", "bool", args
|
|
)
|
|
|
|
def definition_body(self):
|
|
return fill(
|
|
"""
|
|
MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(xray));
|
|
MOZ_ASSERT(js::IsProxy(proxy));
|
|
MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy));
|
|
JSAutoRealm ar(cx, proxy);
|
|
bool deleteSucceeded = false;
|
|
bool found = false;
|
|
$*{namedBody}
|
|
if (!found || deleteSucceeded) {
|
|
return opresult.succeed();
|
|
}
|
|
return opresult.failCantDelete();
|
|
""",
|
|
namedBody=getDeleterBody(self.descriptor, "Named", foundVar="found"),
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_delete(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::ObjectOpResult&", "opresult"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self, "delete_", "bool", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
delete = dedent(
|
|
"""
|
|
MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
|
|
"Should not have a XrayWrapper here");
|
|
|
|
"""
|
|
)
|
|
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
delete += dedent(
|
|
"""
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
return ReportCrossOriginDenial(cx, id, "delete"_ns);
|
|
}
|
|
|
|
// Safe to enter the Realm of proxy now.
|
|
JSAutoRealm ar(cx, proxy);
|
|
JS_MarkCrossZoneId(cx, id);
|
|
"""
|
|
)
|
|
|
|
indexedBody = getDeleterBody(self.descriptor, "Indexed")
|
|
if indexedBody is not None:
|
|
# Can't handle cross-origin objects here.
|
|
assert not self.descriptor.isMaybeCrossOriginObject()
|
|
delete += fill(
|
|
"""
|
|
uint32_t index = GetArrayIndexFromId(id);
|
|
if (IsArrayIndex(index)) {
|
|
bool deleteSucceeded;
|
|
$*{indexedBody}
|
|
return deleteSucceeded ? opresult.succeed() : opresult.failCantDelete();
|
|
}
|
|
""",
|
|
indexedBody=indexedBody,
|
|
)
|
|
|
|
namedBody = getDeleterBody(self.descriptor, "Named", foundVar="found")
|
|
if namedBody is not None:
|
|
delete += dedent(
|
|
"""
|
|
// Try named delete only if the named property visibility
|
|
// algorithm says the property is visible.
|
|
bool tryNamedDelete = true;
|
|
{ // Scope for expando
|
|
JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
|
|
if (expando) {
|
|
bool hasProp;
|
|
if (!JS_HasPropertyById(cx, expando, id, &hasProp)) {
|
|
return false;
|
|
}
|
|
tryNamedDelete = !hasProp;
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
|
|
if not self.descriptor.interface.getExtendedAttribute("OverrideBuiltins"):
|
|
delete += dedent(
|
|
"""
|
|
if (tryNamedDelete) {
|
|
bool hasOnProto;
|
|
if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
|
|
return false;
|
|
}
|
|
tryNamedDelete = !hasOnProto;
|
|
}
|
|
"""
|
|
)
|
|
|
|
# We always return above for an index id in the case when we support
|
|
# indexed properties, so we can just treat the id as a name
|
|
# unconditionally here.
|
|
delete += fill(
|
|
"""
|
|
if (tryNamedDelete) {
|
|
bool found = false;
|
|
bool deleteSucceeded;
|
|
$*{namedBody}
|
|
if (found) {
|
|
return deleteSucceeded ? opresult.succeed() : opresult.failCantDelete();
|
|
}
|
|
}
|
|
""",
|
|
namedBody=namedBody,
|
|
)
|
|
|
|
delete += dedent(
|
|
"""
|
|
|
|
return dom::DOMProxyHandler::delete_(cx, proxy, id, opresult);
|
|
"""
|
|
)
|
|
|
|
return delete
|
|
|
|
|
|
class CGDOMJSProxyHandler_ownPropNames(ClassMethod):
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("unsigned", "flags"),
|
|
Argument("JS::MutableHandleVector<jsid>", "props"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self, "ownPropNames", "bool", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
xrayDecl = dedent(
|
|
"""
|
|
MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy));
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
if (!(flags & JSITER_HIDDEN)) {
|
|
// There are no enumerable cross-origin props, so we're done.
|
|
return true;
|
|
}
|
|
|
|
JS::Rooted<JSObject*> holder(cx);
|
|
if (!EnsureHolder(cx, proxy, &holder)) {
|
|
return false;
|
|
}
|
|
|
|
if (!js::GetPropertyKeys(cx, holder, flags, props)) {
|
|
return false;
|
|
}
|
|
|
|
return xpc::AppendCrossOriginWhitelistedPropNames(cx, props);
|
|
}
|
|
|
|
"""
|
|
)
|
|
xrayCheck = ""
|
|
else:
|
|
xrayDecl = "bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);\n"
|
|
xrayCheck = "!isXray &&"
|
|
|
|
# Per spec, we do indices, then named props, then everything else.
|
|
if self.descriptor.supportsIndexedProperties():
|
|
if self.descriptor.lengthNeedsCallerType():
|
|
callerType = callerTypeGetterForDescriptor(self.descriptor)
|
|
else:
|
|
callerType = ""
|
|
addIndices = fill(
|
|
"""
|
|
|
|
uint32_t length = UnwrapProxy(proxy)->Length(${callerType});
|
|
MOZ_ASSERT(int32_t(length) >= 0);
|
|
for (int32_t i = 0; i < int32_t(length); ++i) {
|
|
if (!props.append(INT_TO_JSID(i))) {
|
|
return false;
|
|
}
|
|
}
|
|
""",
|
|
callerType=callerType,
|
|
)
|
|
else:
|
|
addIndices = ""
|
|
|
|
if self.descriptor.supportsNamedProperties():
|
|
if self.descriptor.interface.getExtendedAttribute("OverrideBuiltins"):
|
|
shadow = "!isXray"
|
|
else:
|
|
shadow = "false"
|
|
|
|
if self.descriptor.supportedNamesNeedCallerType():
|
|
callerType = ", " + callerTypeGetterForDescriptor(self.descriptor)
|
|
else:
|
|
callerType = ""
|
|
|
|
addNames = fill(
|
|
"""
|
|
nsTArray<nsString> names;
|
|
UnwrapProxy(proxy)->GetSupportedNames(names${callerType});
|
|
if (!AppendNamedPropertyIds(cx, proxy, names, ${shadow}, props)) {
|
|
return false;
|
|
}
|
|
""",
|
|
callerType=callerType,
|
|
shadow=shadow,
|
|
)
|
|
if not self.descriptor.namedPropertiesEnumerable:
|
|
addNames = CGIfWrapper(
|
|
CGGeneric(addNames), "flags & JSITER_HIDDEN"
|
|
).define()
|
|
addNames = "\n" + addNames
|
|
else:
|
|
addNames = ""
|
|
|
|
addExpandoProps = fill(
|
|
"""
|
|
JS::Rooted<JSObject*> expando(cx);
|
|
if (${xrayCheck}(expando = DOMProxyHandler::GetExpandoObject(proxy)) &&
|
|
!js::GetPropertyKeys(cx, expando, flags, props)) {
|
|
return false;
|
|
}
|
|
""",
|
|
xrayCheck=xrayCheck,
|
|
)
|
|
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
# We need to enter our compartment (which we might not be
|
|
# in right now) to get the expando props.
|
|
addExpandoProps = fill(
|
|
"""
|
|
{ // Scope for accessing the expando.
|
|
// Safe to enter our compartment, because IsPlatformObjectSameOrigin tested true.
|
|
JSAutoRealm ar(cx, proxy);
|
|
$*{addExpandoProps}
|
|
}
|
|
for (auto& id : props) {
|
|
JS_MarkCrossZoneId(cx, id);
|
|
}
|
|
""",
|
|
addExpandoProps=addExpandoProps,
|
|
)
|
|
|
|
return fill(
|
|
"""
|
|
$*{xrayDecl}
|
|
$*{addIndices}
|
|
$*{addNames}
|
|
|
|
$*{addExpandoProps}
|
|
|
|
return true;
|
|
""",
|
|
xrayDecl=xrayDecl,
|
|
addIndices=addIndices,
|
|
addNames=addNames,
|
|
addExpandoProps=addExpandoProps,
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_hasOwn(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("bool*", "bp"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self, "hasOwn", "bool", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
maybeCrossOrigin = dedent(
|
|
"""
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
// Just hand this off to BaseProxyHandler to do the slow-path thing.
|
|
// The BaseProxyHandler code is OK with this happening without entering the
|
|
// compartment of "proxy", which is important to get the right answers.
|
|
return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp);
|
|
}
|
|
|
|
// Now safe to enter the Realm of proxy and do the rest of the work there.
|
|
JSAutoRealm ar(cx, proxy);
|
|
JS_MarkCrossZoneId(cx, id);
|
|
"""
|
|
)
|
|
else:
|
|
maybeCrossOrigin = ""
|
|
|
|
if self.descriptor.supportsIndexedProperties():
|
|
indexed = fill(
|
|
"""
|
|
uint32_t index = GetArrayIndexFromId(id);
|
|
if (IsArrayIndex(index)) {
|
|
bool found = false;
|
|
$*{presenceChecker}
|
|
|
|
*bp = found;
|
|
return true;
|
|
}
|
|
|
|
""",
|
|
presenceChecker=CGProxyIndexedPresenceChecker(
|
|
self.descriptor, foundVar="found"
|
|
).define(),
|
|
)
|
|
else:
|
|
indexed = ""
|
|
|
|
if self.descriptor.supportsNamedProperties():
|
|
# If we support indexed properties we always return above for index
|
|
# property names, so no need to check for those here.
|
|
named = fill(
|
|
"""
|
|
bool found = false;
|
|
$*{presenceChecker}
|
|
|
|
*bp = found;
|
|
""",
|
|
presenceChecker=CGProxyNamedPresenceChecker(
|
|
self.descriptor, foundVar="found"
|
|
).define(),
|
|
)
|
|
if not self.descriptor.interface.getExtendedAttribute("OverrideBuiltins"):
|
|
named = fill(
|
|
"""
|
|
bool hasOnProto;
|
|
if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
|
|
return false;
|
|
}
|
|
if (!hasOnProto) {
|
|
$*{protoLacksProperty}
|
|
return true;
|
|
}
|
|
""",
|
|
protoLacksProperty=named,
|
|
)
|
|
named += "*bp = false;\n"
|
|
else:
|
|
named += "\n"
|
|
else:
|
|
named = "*bp = false;\n"
|
|
|
|
missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor)
|
|
|
|
return fill(
|
|
"""
|
|
MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
|
|
"Should not have a XrayWrapper here");
|
|
$*{maybeCrossOrigin}
|
|
$*{indexed}
|
|
|
|
$*{missingPropUseCounters}
|
|
JS::Rooted<JSObject*> expando(cx, GetExpandoObject(proxy));
|
|
if (expando) {
|
|
bool b = true;
|
|
bool ok = JS_HasPropertyById(cx, expando, id, &b);
|
|
*bp = !!b;
|
|
if (!ok || *bp) {
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
$*{named}
|
|
return true;
|
|
""",
|
|
maybeCrossOrigin=maybeCrossOrigin,
|
|
indexed=indexed,
|
|
missingPropUseCounters=missingPropUseCounters,
|
|
named=named,
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_get(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<JS::Value>", "receiver"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::MutableHandle<JS::Value>", "vp"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self, "get", "bool", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
maybeCrossOriginGet = dedent(
|
|
"""
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
return CrossOriginGet(cx, proxy, receiver, id, vp);
|
|
}
|
|
|
|
"""
|
|
)
|
|
else:
|
|
maybeCrossOriginGet = ""
|
|
|
|
missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor)
|
|
|
|
getUnforgeableOrExpando = dedent(
|
|
"""
|
|
{ // Scope for expando
|
|
JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
|
|
if (expando) {
|
|
if (!JS_HasPropertyById(cx, expando, id, &expandoHasProp)) {
|
|
return false;
|
|
}
|
|
|
|
if (expandoHasProp) {
|
|
// Forward the get to the expando object, but our receiver is whatever our
|
|
// receiver is.
|
|
if (!JS_ForwardGetPropertyTo(cx, expando, id, rootedReceiver, vp)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
getUnforgeableOrExpando = fill(
|
|
"""
|
|
{ // Scope for the JSAutoRealm accessing expando
|
|
JSAutoRealm ar(cx, proxy);
|
|
if (!MaybeWrapValue(cx, &rootedReceiver)) {
|
|
return false;
|
|
}
|
|
JS_MarkCrossZoneId(cx, id);
|
|
|
|
$*{getUnforgeableOrExpando}
|
|
}
|
|
if (expandoHasProp) {
|
|
return MaybeWrapValue(cx, vp);
|
|
}
|
|
""",
|
|
getUnforgeableOrExpando=getUnforgeableOrExpando,
|
|
)
|
|
else:
|
|
getUnforgeableOrExpando = fill(
|
|
"""
|
|
$*{getUnforgeableOrExpando}
|
|
|
|
if (expandoHasProp) {
|
|
return true;
|
|
}
|
|
""",
|
|
getUnforgeableOrExpando=getUnforgeableOrExpando,
|
|
)
|
|
|
|
getUnforgeableOrExpando = fill(
|
|
"""
|
|
bool expandoHasProp = false;
|
|
$*{getUnforgeableOrExpando}
|
|
""",
|
|
getUnforgeableOrExpando=getUnforgeableOrExpando,
|
|
)
|
|
|
|
templateValues = {"jsvalRef": "vp", "jsvalHandle": "vp", "obj": "proxy"}
|
|
|
|
if self.descriptor.supportsIndexedProperties():
|
|
# We can't handle this for cross-origin objects
|
|
assert not self.descriptor.isMaybeCrossOriginObject()
|
|
|
|
getIndexedOrExpando = fill(
|
|
"""
|
|
uint32_t index = GetArrayIndexFromId(id);
|
|
if (IsArrayIndex(index)) {
|
|
$*{callGetter}
|
|
// Even if we don't have this index, we don't forward the
|
|
// get on to our expando object.
|
|
} else {
|
|
$*{getUnforgeableOrExpando}
|
|
}
|
|
""",
|
|
callGetter=CGProxyIndexedGetter(
|
|
self.descriptor, templateValues
|
|
).define(),
|
|
getUnforgeableOrExpando=getUnforgeableOrExpando,
|
|
)
|
|
else:
|
|
getIndexedOrExpando = getUnforgeableOrExpando
|
|
|
|
if self.descriptor.supportsNamedProperties():
|
|
# We can't handle this for cross-origin objects
|
|
assert not self.descriptor.isMaybeCrossOriginObject()
|
|
|
|
getNamed = CGProxyNamedGetter(self.descriptor, templateValues)
|
|
if self.descriptor.supportsIndexedProperties():
|
|
getNamed = CGIfWrapper(getNamed, "!IsArrayIndex(index)")
|
|
getNamed = getNamed.define() + "\n"
|
|
else:
|
|
getNamed = ""
|
|
|
|
getOnPrototype = dedent(
|
|
"""
|
|
if (!GetPropertyOnPrototype(cx, proxy, rootedReceiver, id, &foundOnPrototype, vp)) {
|
|
return false;
|
|
}
|
|
|
|
"""
|
|
)
|
|
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
getOnPrototype = fill(
|
|
"""
|
|
bool foundOnPrototype;
|
|
{ // Scope for JSAutoRealm
|
|
JSAutoRealm ar(cx, proxy);
|
|
// We already wrapped rootedReceiver
|
|
MOZ_ASSERT_IF(rootedReceiver.isObject(),
|
|
js::IsObjectInContextCompartment(&rootedReceiver.toObject(), cx));
|
|
JS_MarkCrossZoneId(cx, id);
|
|
$*{getOnPrototype}
|
|
}
|
|
|
|
if (foundOnPrototype) {
|
|
return MaybeWrapValue(cx, vp);
|
|
}
|
|
|
|
""",
|
|
getOnPrototype=getOnPrototype,
|
|
)
|
|
else:
|
|
getOnPrototype = fill(
|
|
"""
|
|
bool foundOnPrototype;
|
|
$*{getOnPrototype}
|
|
if (foundOnPrototype) {
|
|
return true;
|
|
}
|
|
|
|
""",
|
|
getOnPrototype=getOnPrototype,
|
|
)
|
|
|
|
if self.descriptor.interface.getExtendedAttribute("OverrideBuiltins"):
|
|
getNamed = getNamed + getOnPrototype
|
|
else:
|
|
getNamed = getOnPrototype + getNamed
|
|
|
|
return fill(
|
|
"""
|
|
MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
|
|
"Should not have a XrayWrapper here");
|
|
|
|
$*{maybeCrossOriginGet}
|
|
JS::Rooted<JS::Value> rootedReceiver(cx, receiver);
|
|
|
|
$*{missingPropUseCounters}
|
|
$*{indexedOrExpando}
|
|
|
|
$*{named}
|
|
vp.setUndefined();
|
|
return true;
|
|
""",
|
|
maybeCrossOriginGet=maybeCrossOriginGet,
|
|
missingPropUseCounters=missingPropUseCounters,
|
|
indexedOrExpando=getIndexedOrExpando,
|
|
named=getNamed,
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_setCustom(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx_"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::Handle<JS::Value>", "v"),
|
|
Argument("bool*", "done"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self, "setCustom", "bool", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
assertion = (
|
|
"MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),\n"
|
|
' "Should not have a XrayWrapper here");\n'
|
|
)
|
|
|
|
# Correctness first. If we have a NamedSetter and [OverrideBuiltins],
|
|
# always call the NamedSetter and never do anything else.
|
|
namedSetter = self.descriptor.operations["NamedSetter"]
|
|
if namedSetter is not None and self.descriptor.interface.getExtendedAttribute(
|
|
"OverrideBuiltins"
|
|
):
|
|
# Check assumptions.
|
|
if self.descriptor.supportsIndexedProperties():
|
|
raise ValueError(
|
|
"In interface "
|
|
+ self.descriptor.name
|
|
+ ": "
|
|
+ "Can't cope with [OverrideBuiltins] and an indexed getter"
|
|
)
|
|
if self.descriptor.hasUnforgeableMembers:
|
|
raise ValueError(
|
|
"In interface "
|
|
+ self.descriptor.name
|
|
+ ": "
|
|
+ "Can't cope with [OverrideBuiltins] and unforgeable members"
|
|
)
|
|
|
|
tailCode = dedent(
|
|
"""
|
|
*done = true;
|
|
return true;
|
|
"""
|
|
)
|
|
callSetter = CGProxyNamedSetter(
|
|
self.descriptor, tailCode, argumentHandleValue="v"
|
|
)
|
|
error_label = CGSpecializedMethod.error_reporting_label_helper(
|
|
self.descriptor, namedSetter, isConstructor=False
|
|
)
|
|
if error_label:
|
|
cxDecl = fill(
|
|
"""
|
|
BindingCallContext cx(cx_, "${error_label}");
|
|
""",
|
|
error_label=error_label,
|
|
)
|
|
else:
|
|
cxDecl = dedent(
|
|
"""
|
|
JSContext* cx = cx_;
|
|
"""
|
|
)
|
|
return fill(
|
|
"""
|
|
$*{assertion}
|
|
$*{cxDecl}
|
|
$*{callSetter}
|
|
*done = false;
|
|
return true;
|
|
""",
|
|
assertion=assertion,
|
|
cxDecl=cxDecl,
|
|
callSetter=callSetter.define(),
|
|
)
|
|
|
|
# As an optimization, if we are going to call an IndexedSetter, go
|
|
# ahead and call it and have done.
|
|
indexedSetter = self.descriptor.operations["IndexedSetter"]
|
|
if indexedSetter is not None:
|
|
error_label = CGSpecializedMethod.error_reporting_label_helper(
|
|
self.descriptor, indexedSetter, isConstructor=False
|
|
)
|
|
if error_label:
|
|
cxDecl = fill(
|
|
"""
|
|
BindingCallContext cx(cx_, "${error_label}");
|
|
""",
|
|
error_label=error_label,
|
|
)
|
|
else:
|
|
cxDecl = dedent(
|
|
"""
|
|
JSContext* cx = cx_;
|
|
"""
|
|
)
|
|
setIndexed = fill(
|
|
"""
|
|
uint32_t index = GetArrayIndexFromId(id);
|
|
if (IsArrayIndex(index)) {
|
|
$*{cxDecl}
|
|
$*{callSetter}
|
|
*done = true;
|
|
return true;
|
|
}
|
|
|
|
""",
|
|
cxDecl=cxDecl,
|
|
callSetter=CGProxyIndexedSetter(
|
|
self.descriptor, argumentHandleValue="v"
|
|
).define(),
|
|
)
|
|
else:
|
|
setIndexed = ""
|
|
|
|
return assertion + setIndexed + "*done = false;\n" "return true;\n"
|
|
|
|
|
|
class CGDOMJSProxyHandler_className(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self,
|
|
"className",
|
|
"const char*",
|
|
args,
|
|
virtual=True,
|
|
override=True,
|
|
const=True,
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
if self.descriptor.isMaybeCrossOriginObject():
|
|
crossOrigin = dedent(
|
|
"""
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
return "Object";
|
|
}
|
|
|
|
"""
|
|
)
|
|
else:
|
|
crossOrigin = ""
|
|
return fill(
|
|
"""
|
|
$*{crossOrigin}
|
|
return "${name}";
|
|
""",
|
|
crossOrigin=crossOrigin,
|
|
name=self.descriptor.name,
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_finalizeInBackground(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
args = [Argument("const JS::Value&", "priv")]
|
|
ClassMethod.__init__(
|
|
self,
|
|
"finalizeInBackground",
|
|
"bool",
|
|
args,
|
|
virtual=True,
|
|
override=True,
|
|
const=True,
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
return "return false;\n"
|
|
|
|
|
|
class CGDOMJSProxyHandler_finalize(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
args = [Argument("JSFreeOp*", "fop"), Argument("JSObject*", "proxy")]
|
|
ClassMethod.__init__(
|
|
self, "finalize", "void", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
return (
|
|
"%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(proxy);\n"
|
|
% (self.descriptor.nativeType, self.descriptor.nativeType)
|
|
) + finalizeHook(
|
|
self.descriptor,
|
|
FINALIZE_HOOK_NAME,
|
|
self.args[0].name,
|
|
self.args[1].name,
|
|
).define()
|
|
|
|
|
|
class CGDOMJSProxyHandler_objectMoved(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
args = [Argument("JSObject*", "obj"), Argument("JSObject*", "old")]
|
|
ClassMethod.__init__(
|
|
self, "objectMoved", "size_t", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
return (
|
|
"%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(obj);\n"
|
|
% (self.descriptor.nativeType, self.descriptor.nativeType)
|
|
) + objectMovedHook(
|
|
self.descriptor,
|
|
OBJECT_MOVED_HOOK_NAME,
|
|
self.args[0].name,
|
|
self.args[1].name,
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_getElements(ClassMethod):
|
|
def __init__(self, descriptor):
|
|
assert descriptor.supportsIndexedProperties()
|
|
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("uint32_t", "begin"),
|
|
Argument("uint32_t", "end"),
|
|
Argument("js::ElementAdder*", "adder"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self, "getElements", "bool", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
# Just like ownPropertyKeys we'll assume that we have no holes, so
|
|
# we have all properties from 0 to length. If that ever changes
|
|
# (unlikely), we'll need to do something a bit more clever with how we
|
|
# forward on to our ancestor.
|
|
|
|
templateValues = {
|
|
"jsvalRef": "temp",
|
|
"jsvalHandle": "&temp",
|
|
"obj": "proxy",
|
|
"successCode": (
|
|
"if (!adder->append(cx, temp)) return false;\n" "continue;\n"
|
|
),
|
|
}
|
|
get = CGProxyIndexedGetter(
|
|
self.descriptor, templateValues, False, False
|
|
).define()
|
|
|
|
if self.descriptor.lengthNeedsCallerType():
|
|
callerType = callerTypeGetterForDescriptor(self.descriptor)
|
|
else:
|
|
callerType = ""
|
|
|
|
return fill(
|
|
"""
|
|
JS::Rooted<JS::Value> temp(cx);
|
|
MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
|
|
"Should not have a XrayWrapper here");
|
|
|
|
${nativeType}* self = UnwrapProxy(proxy);
|
|
uint32_t length = self->Length(${callerType});
|
|
// Compute the end of the indices we'll get ourselves
|
|
uint32_t ourEnd = std::max(begin, std::min(end, length));
|
|
|
|
for (uint32_t index = begin; index < ourEnd; ++index) {
|
|
$*{get}
|
|
}
|
|
|
|
if (end > ourEnd) {
|
|
JS::Rooted<JSObject*> proto(cx);
|
|
if (!js::GetObjectProto(cx, proxy, &proto)) {
|
|
return false;
|
|
}
|
|
return js::GetElementsWithAdder(cx, proto, proxy, ourEnd, end, adder);
|
|
}
|
|
|
|
return true;
|
|
""",
|
|
nativeType=self.descriptor.nativeType,
|
|
callerType=callerType,
|
|
get=get,
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_getInstance(ClassMethod):
|
|
def __init__(self):
|
|
ClassMethod.__init__(
|
|
self, "getInstance", "const DOMProxyHandler*", [], static=True
|
|
)
|
|
|
|
def getBody(self):
|
|
return dedent(
|
|
"""
|
|
static const DOMProxyHandler instance;
|
|
return &instance;
|
|
"""
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_call(ClassMethod):
|
|
def __init__(self):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("const JS::CallArgs&", "args"),
|
|
]
|
|
|
|
ClassMethod.__init__(
|
|
self, "call", "bool", args, virtual=True, override=True, const=True
|
|
)
|
|
|
|
def getBody(self):
|
|
return fill(
|
|
"""
|
|
return js::ForwardToNative(cx, ${legacyCaller}, args);
|
|
""",
|
|
legacyCaller=LEGACYCALLER_HOOK_NAME,
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_isCallable(ClassMethod):
|
|
def __init__(self):
|
|
ClassMethod.__init__(
|
|
self,
|
|
"isCallable",
|
|
"bool",
|
|
[Argument("JSObject*", "obj")],
|
|
virtual=True,
|
|
override=True,
|
|
const=True,
|
|
)
|
|
|
|
def getBody(self):
|
|
return dedent(
|
|
"""
|
|
return true;
|
|
"""
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_canNurseryAllocate(ClassMethod):
|
|
"""
|
|
Override the default canNurseryAllocate in BaseProxyHandler, for cases when
|
|
we should be nursery-allocated.
|
|
"""
|
|
|
|
def __init__(self):
|
|
ClassMethod.__init__(
|
|
self,
|
|
"canNurseryAllocate",
|
|
"bool",
|
|
[],
|
|
virtual=True,
|
|
override=True,
|
|
const=True,
|
|
)
|
|
|
|
def getBody(self):
|
|
return dedent(
|
|
"""
|
|
return true;
|
|
"""
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_getOwnPropertyDescriptor(ClassMethod):
|
|
"""
|
|
Implementation of getOwnPropertyDescriptor. We only use this for
|
|
cross-origin objects.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
assert descriptor.isMaybeCrossOriginObject()
|
|
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::MutableHandle<JS::PropertyDescriptor>", "desc"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self,
|
|
"getOwnPropertyDescriptor",
|
|
"bool",
|
|
args,
|
|
virtual=True,
|
|
override=True,
|
|
const=True,
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
return dedent(
|
|
"""
|
|
// Implementation of <https://html.spec.whatwg.org/multipage/history.html#location-getownproperty>.
|
|
MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy));
|
|
|
|
// Step 1.
|
|
if (IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
{ // Scope so we can wrap our PropertyDescriptor back into
|
|
// the caller compartment.
|
|
// Enter the Realm of "proxy" so we can work with it.
|
|
JSAutoRealm ar(cx, proxy);
|
|
|
|
JS_MarkCrossZoneId(cx, id);
|
|
|
|
// The spec messes around with configurability of the returned
|
|
// descriptor here, but it's not clear what should actually happen
|
|
// here. See <https://github.com/whatwg/html/issues/4157>. For
|
|
// now, keep our old behavior and don't do any magic.
|
|
if (!dom::DOMProxyHandler::getOwnPropertyDescriptor(cx, proxy, id, desc)) {
|
|
return false;
|
|
}
|
|
}
|
|
return JS_WrapPropertyDescriptor(cx, desc);
|
|
}
|
|
|
|
// Step 2.
|
|
if (!CrossOriginGetOwnPropertyHelper(cx, proxy, id, desc)) {
|
|
return false;
|
|
}
|
|
|
|
// Step 3.
|
|
if (desc.object()) {
|
|
return true;
|
|
}
|
|
|
|
// And step 4.
|
|
return CrossOriginPropertyFallback(cx, proxy, id, desc);
|
|
"""
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_getSameOriginPrototype(ClassMethod):
|
|
"""
|
|
Implementation of getSameOriginPrototype. We only use this for
|
|
cross-origin objects.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
assert descriptor.isMaybeCrossOriginObject()
|
|
|
|
args = [Argument("JSContext*", "cx")]
|
|
ClassMethod.__init__(
|
|
self,
|
|
"getSameOriginPrototype",
|
|
"JSObject*",
|
|
args,
|
|
virtual=True,
|
|
override=True,
|
|
const=True,
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
return dedent(
|
|
"""
|
|
return GetProtoObjectHandle(cx);
|
|
"""
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_definePropertySameOrigin(ClassMethod):
|
|
"""
|
|
Implementation of definePropertySameOrigin. We only use this for
|
|
cross-origin objects.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
assert descriptor.isMaybeCrossOriginObject()
|
|
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::Handle<JS::PropertyDescriptor>", "desc"),
|
|
Argument("JS::ObjectOpResult&", "result"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self,
|
|
"definePropertySameOrigin",
|
|
"bool",
|
|
args,
|
|
virtual=True,
|
|
override=True,
|
|
const=True,
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
return dedent(
|
|
"""
|
|
return dom::DOMProxyHandler::defineProperty(cx, proxy, id, desc, result);
|
|
"""
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_set(ClassMethod):
|
|
"""
|
|
Implementation of set(). We only use this for cross-origin objects.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
assert descriptor.isMaybeCrossOriginObject()
|
|
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::Handle<jsid>", "id"),
|
|
Argument("JS::Handle<JS::Value>", "v"),
|
|
Argument("JS::Handle<JS::Value>", "receiver"),
|
|
Argument("JS::ObjectOpResult&", "result"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self, "set", "bool", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
return dedent(
|
|
"""
|
|
if (!IsPlatformObjectSameOrigin(cx, proxy)) {
|
|
return CrossOriginSet(cx, proxy, id, v, receiver, result);
|
|
}
|
|
|
|
// Safe to enter the Realm of proxy now, since it's same-origin with us.
|
|
JSAutoRealm ar(cx, proxy);
|
|
JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
|
|
if (!MaybeWrapValue(cx, &wrappedReceiver)) {
|
|
return false;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> wrappedValue(cx, v);
|
|
if (!MaybeWrapValue(cx, &wrappedValue)) {
|
|
return false;
|
|
}
|
|
|
|
JS_MarkCrossZoneId(cx, id);
|
|
|
|
return dom::DOMProxyHandler::set(cx, proxy, id, wrappedValue, wrappedReceiver, result);
|
|
"""
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler_EnsureHolder(ClassMethod):
|
|
"""
|
|
Implementation of EnsureHolder(). We only use this for cross-origin objects.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
args = [
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::Handle<JSObject*>", "proxy"),
|
|
Argument("JS::MutableHandle<JSObject*>", "holder"),
|
|
]
|
|
ClassMethod.__init__(
|
|
self, "EnsureHolder", "bool", args, virtual=True, override=True, const=True
|
|
)
|
|
self.descriptor = descriptor
|
|
|
|
def getBody(self):
|
|
return dedent(
|
|
"""
|
|
return EnsureHolder(cx, proxy,
|
|
JSCLASS_RESERVED_SLOTS(JS::GetClass(proxy)) - 1,
|
|
sCrossOriginProperties, holder);
|
|
"""
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandler(CGClass):
|
|
def __init__(self, descriptor):
|
|
assert (
|
|
descriptor.supportsIndexedProperties()
|
|
or descriptor.supportsNamedProperties()
|
|
or descriptor.isMaybeCrossOriginObject()
|
|
)
|
|
methods = [
|
|
CGDOMJSProxyHandler_getOwnPropDescriptor(descriptor),
|
|
CGDOMJSProxyHandler_defineProperty(descriptor),
|
|
ClassUsingDeclaration("mozilla::dom::DOMProxyHandler", "defineProperty"),
|
|
CGDOMJSProxyHandler_ownPropNames(descriptor),
|
|
CGDOMJSProxyHandler_hasOwn(descriptor),
|
|
CGDOMJSProxyHandler_get(descriptor),
|
|
CGDOMJSProxyHandler_className(descriptor),
|
|
CGDOMJSProxyHandler_finalizeInBackground(descriptor),
|
|
CGDOMJSProxyHandler_finalize(descriptor),
|
|
CGDOMJSProxyHandler_getInstance(),
|
|
CGDOMJSProxyHandler_delete(descriptor),
|
|
]
|
|
constructors = [
|
|
ClassConstructor([], constexpr=True, visibility="public", explicit=True)
|
|
]
|
|
|
|
if descriptor.supportsIndexedProperties():
|
|
methods.append(CGDOMJSProxyHandler_getElements(descriptor))
|
|
if descriptor.operations["IndexedSetter"] is not None or (
|
|
descriptor.operations["NamedSetter"] is not None
|
|
and descriptor.interface.getExtendedAttribute("OverrideBuiltins")
|
|
):
|
|
methods.append(CGDOMJSProxyHandler_setCustom(descriptor))
|
|
if descriptor.operations["LegacyCaller"]:
|
|
methods.append(CGDOMJSProxyHandler_call())
|
|
methods.append(CGDOMJSProxyHandler_isCallable())
|
|
if descriptor.interface.hasProbablyShortLivingWrapper():
|
|
if not descriptor.wrapperCache:
|
|
raise TypeError(
|
|
"Need a wrapper cache to support nursery "
|
|
"allocation of DOM objects"
|
|
)
|
|
methods.append(CGDOMJSProxyHandler_canNurseryAllocate())
|
|
if descriptor.wrapperCache:
|
|
methods.append(CGDOMJSProxyHandler_objectMoved(descriptor))
|
|
|
|
if descriptor.isMaybeCrossOriginObject():
|
|
methods.extend(
|
|
[
|
|
CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor),
|
|
CGDOMJSProxyHandler_getSameOriginPrototype(descriptor),
|
|
CGDOMJSProxyHandler_definePropertySameOrigin(descriptor),
|
|
CGDOMJSProxyHandler_set(descriptor),
|
|
CGDOMJSProxyHandler_EnsureHolder(descriptor),
|
|
ClassUsingDeclaration(
|
|
"MaybeCrossOriginObjectMixins", "EnsureHolder"
|
|
),
|
|
]
|
|
)
|
|
|
|
if descriptor.interface.getExtendedAttribute("OverrideBuiltins"):
|
|
assert not descriptor.isMaybeCrossOriginObject()
|
|
parentClass = "ShadowingDOMProxyHandler"
|
|
elif descriptor.isMaybeCrossOriginObject():
|
|
parentClass = "MaybeCrossOriginObject<mozilla::dom::DOMProxyHandler>"
|
|
else:
|
|
parentClass = "mozilla::dom::DOMProxyHandler"
|
|
|
|
CGClass.__init__(
|
|
self,
|
|
"DOMProxyHandler",
|
|
bases=[ClassBase(parentClass)],
|
|
constructors=constructors,
|
|
methods=methods,
|
|
)
|
|
|
|
|
|
class CGDOMJSProxyHandlerDeclarer(CGThing):
|
|
"""
|
|
A class for declaring a DOMProxyHandler.
|
|
"""
|
|
|
|
def __init__(self, handlerThing):
|
|
self.handlerThing = handlerThing
|
|
|
|
def declare(self):
|
|
# Our class declaration should happen when we're defining
|
|
return ""
|
|
|
|
def define(self):
|
|
return self.handlerThing.declare()
|
|
|
|
|
|
class CGDOMJSProxyHandlerDefiner(CGThing):
|
|
"""
|
|
A class for defining a DOMProxyHandler.
|
|
"""
|
|
|
|
def __init__(self, handlerThing):
|
|
self.handlerThing = handlerThing
|
|
|
|
def declare(self):
|
|
return ""
|
|
|
|
def define(self):
|
|
return self.handlerThing.define()
|
|
|
|
|
|
def stripTrailingWhitespace(text):
|
|
tail = "\n" if text.endswith("\n") else ""
|
|
lines = text.splitlines()
|
|
return "\n".join(line.rstrip() for line in lines) + tail
|
|
|
|
|
|
class MemberProperties:
|
|
def __init__(self):
|
|
self.isCrossOriginMethod = False
|
|
self.isCrossOriginGetter = False
|
|
self.isCrossOriginSetter = False
|
|
|
|
|
|
def memberProperties(m, descriptor):
|
|
props = MemberProperties()
|
|
if m.isMethod():
|
|
if not m.isIdentifierLess() or m == descriptor.operations["Stringifier"]:
|
|
if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
|
|
if m.getExtendedAttribute("CrossOriginCallable"):
|
|
props.isCrossOriginMethod = True
|
|
elif m.isAttr():
|
|
if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
|
|
if m.getExtendedAttribute("CrossOriginReadable"):
|
|
props.isCrossOriginGetter = True
|
|
if not m.readonly:
|
|
if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
|
|
if m.getExtendedAttribute("CrossOriginWritable"):
|
|
props.isCrossOriginSetter = True
|
|
elif m.getExtendedAttribute("PutForwards"):
|
|
if m.getExtendedAttribute("CrossOriginWritable"):
|
|
props.isCrossOriginSetter = True
|
|
elif m.getExtendedAttribute("Replaceable") or m.getExtendedAttribute(
|
|
"LenientSetter"
|
|
):
|
|
if m.getExtendedAttribute("CrossOriginWritable"):
|
|
props.isCrossOriginSetter = True
|
|
|
|
return props
|
|
|
|
|
|
class CGDescriptor(CGThing):
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
|
|
assert (
|
|
not descriptor.concrete
|
|
or descriptor.interface.hasInterfacePrototypeObject()
|
|
)
|
|
|
|
self._deps = descriptor.interface.getDeps()
|
|
|
|
cgThings = []
|
|
cgThings.append(
|
|
CGGeneric(declare="typedef %s NativeType;\n" % descriptor.nativeType)
|
|
)
|
|
parent = descriptor.interface.parent
|
|
if parent:
|
|
cgThings.append(
|
|
CGGeneric(
|
|
"static_assert(IsRefcounted<NativeType>::value == IsRefcounted<%s::NativeType>::value,\n"
|
|
' "Can\'t inherit from an interface with a different ownership model.");\n'
|
|
% toBindingNamespace(descriptor.parentPrototypeName)
|
|
)
|
|
)
|
|
|
|
defaultToJSONMethod = None
|
|
needCrossOriginPropertyArrays = False
|
|
unscopableNames = list()
|
|
for n in descriptor.interface.namedConstructors:
|
|
cgThings.append(CGClassConstructor(descriptor, n, NamedConstructorName(n)))
|
|
for m in descriptor.interface.members:
|
|
if m.isMethod() and m.identifier.name == "QueryInterface":
|
|
continue
|
|
|
|
props = memberProperties(m, descriptor)
|
|
|
|
if m.isMethod():
|
|
if m.getExtendedAttribute("Unscopable"):
|
|
assert not m.isStatic()
|
|
unscopableNames.append(m.identifier.name)
|
|
if m.isDefaultToJSON():
|
|
defaultToJSONMethod = m
|
|
elif (
|
|
not m.isIdentifierLess()
|
|
or m == descriptor.operations["Stringifier"]
|
|
):
|
|
if m.isStatic():
|
|
assert descriptor.interface.hasInterfaceObject()
|
|
cgThings.append(CGStaticMethod(descriptor, m))
|
|
if m.returnsPromise():
|
|
cgThings.append(CGStaticMethodJitinfo(m))
|
|
elif descriptor.interface.hasInterfacePrototypeObject():
|
|
specializedMethod = CGSpecializedMethod(descriptor, m)
|
|
cgThings.append(specializedMethod)
|
|
if m.returnsPromise():
|
|
cgThings.append(
|
|
CGMethodPromiseWrapper(descriptor, specializedMethod)
|
|
)
|
|
cgThings.append(CGMemberJITInfo(descriptor, m))
|
|
if props.isCrossOriginMethod:
|
|
needCrossOriginPropertyArrays = True
|
|
# If we've hit the maplike/setlike member itself, go ahead and
|
|
# generate its convenience functions.
|
|
elif m.isMaplikeOrSetlike():
|
|
cgThings.append(CGMaplikeOrSetlikeHelperGenerator(descriptor, m))
|
|
elif m.isAttr():
|
|
if m.getExtendedAttribute("Unscopable"):
|
|
assert not m.isStatic()
|
|
unscopableNames.append(m.identifier.name)
|
|
if m.isStatic():
|
|
assert descriptor.interface.hasInterfaceObject()
|
|
cgThings.append(CGStaticGetter(descriptor, m))
|
|
elif descriptor.interface.hasInterfacePrototypeObject():
|
|
specializedGetter = CGSpecializedGetter(descriptor, m)
|
|
cgThings.append(specializedGetter)
|
|
if m.type.isPromise():
|
|
cgThings.append(
|
|
CGGetterPromiseWrapper(descriptor, specializedGetter)
|
|
)
|
|
if props.isCrossOriginGetter:
|
|
needCrossOriginPropertyArrays = True
|
|
if not m.readonly:
|
|
if m.isStatic():
|
|
assert descriptor.interface.hasInterfaceObject()
|
|
cgThings.append(CGStaticSetter(descriptor, m))
|
|
elif descriptor.interface.hasInterfacePrototypeObject():
|
|
cgThings.append(CGSpecializedSetter(descriptor, m))
|
|
if props.isCrossOriginSetter:
|
|
needCrossOriginPropertyArrays = True
|
|
elif m.getExtendedAttribute("PutForwards"):
|
|
cgThings.append(CGSpecializedForwardingSetter(descriptor, m))
|
|
if props.isCrossOriginSetter:
|
|
needCrossOriginPropertyArrays = True
|
|
elif m.getExtendedAttribute("Replaceable"):
|
|
cgThings.append(CGSpecializedReplaceableSetter(descriptor, m))
|
|
elif m.getExtendedAttribute("LenientSetter"):
|
|
cgThings.append(CGSpecializedLenientSetter(descriptor, m))
|
|
if (
|
|
not m.isStatic()
|
|
and descriptor.interface.hasInterfacePrototypeObject()
|
|
):
|
|
cgThings.append(CGMemberJITInfo(descriptor, m))
|
|
if m.isConst() and m.type.isPrimitive():
|
|
cgThings.append(CGConstDefinition(m))
|
|
|
|
if defaultToJSONMethod:
|
|
cgThings.append(CGDefaultToJSONMethod(descriptor, defaultToJSONMethod))
|
|
cgThings.append(CGMemberJITInfo(descriptor, defaultToJSONMethod))
|
|
|
|
if descriptor.concrete and not descriptor.proxy:
|
|
if wantsAddProperty(descriptor):
|
|
cgThings.append(CGAddPropertyHook(descriptor))
|
|
|
|
# Always have a finalize hook, regardless of whether the class
|
|
# wants a custom hook.
|
|
cgThings.append(CGClassFinalizeHook(descriptor))
|
|
|
|
if wantsGetWrapperCache(descriptor):
|
|
cgThings.append(CGGetWrapperCacheHook(descriptor))
|
|
|
|
if descriptor.concrete and descriptor.wrapperCache and not descriptor.proxy:
|
|
cgThings.append(CGClassObjectMovedHook(descriptor))
|
|
|
|
properties = PropertyArrays(descriptor)
|
|
cgThings.append(CGGeneric(define=str(properties)))
|
|
cgThings.append(CGNativeProperties(descriptor, properties))
|
|
|
|
if defaultToJSONMethod:
|
|
# Now that we know about our property arrays, we can
|
|
# output our "collect attribute values" method, which uses those.
|
|
cgThings.append(
|
|
CGCollectJSONAttributesMethod(descriptor, defaultToJSONMethod)
|
|
)
|
|
|
|
if descriptor.interface.hasInterfaceObject():
|
|
cgThings.append(CGClassConstructor(descriptor, descriptor.interface.ctor()))
|
|
cgThings.append(CGInterfaceObjectJSClass(descriptor, properties))
|
|
cgThings.append(CGNamedConstructors(descriptor))
|
|
|
|
cgThings.append(CGLegacyCallHook(descriptor))
|
|
if descriptor.interface.getExtendedAttribute("NeedResolve"):
|
|
cgThings.append(CGResolveHook(descriptor))
|
|
cgThings.append(CGMayResolveHook(descriptor))
|
|
cgThings.append(CGEnumerateHook(descriptor))
|
|
|
|
if descriptor.hasNamedPropertiesObject:
|
|
cgThings.append(CGGetNamedPropertiesObjectMethod(descriptor))
|
|
|
|
if descriptor.interface.hasInterfacePrototypeObject():
|
|
cgThings.append(CGPrototypeJSClass(descriptor, properties))
|
|
|
|
if (
|
|
descriptor.interface.hasInterfaceObject()
|
|
and not descriptor.interface.isExternal()
|
|
and descriptor.isExposedConditionally()
|
|
):
|
|
cgThings.append(CGConstructorEnabled(descriptor))
|
|
|
|
if (
|
|
descriptor.interface.hasMembersInSlots()
|
|
and descriptor.interface.hasChildInterfaces()
|
|
):
|
|
raise TypeError(
|
|
"We don't support members in slots on "
|
|
"non-leaf interfaces like %s" % descriptor.interface.identifier.name
|
|
)
|
|
|
|
if descriptor.needsMissingPropUseCounters:
|
|
cgThings.append(CGCountMaybeMissingProperty(descriptor))
|
|
|
|
if descriptor.concrete:
|
|
if descriptor.interface.isSerializable():
|
|
cgThings.append(CGSerializer(descriptor))
|
|
cgThings.append(CGDeserializer(descriptor))
|
|
|
|
if descriptor.proxy:
|
|
cgThings.append(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
static_assert(std::is_base_of_v<nsISupports, ${nativeType}>,
|
|
"We don't support non-nsISupports native classes for "
|
|
"proxy-based bindings yet");
|
|
|
|
""",
|
|
nativeType=descriptor.nativeType,
|
|
)
|
|
)
|
|
)
|
|
if not descriptor.wrapperCache:
|
|
raise TypeError(
|
|
"We need a wrappercache to support expandos for proxy-based "
|
|
"bindings (" + descriptor.name + ")"
|
|
)
|
|
handlerThing = CGDOMJSProxyHandler(descriptor)
|
|
cgThings.append(CGDOMJSProxyHandlerDeclarer(handlerThing))
|
|
cgThings.append(CGProxyIsProxy(descriptor))
|
|
cgThings.append(CGProxyUnwrap(descriptor))
|
|
cgThings.append(CGDOMJSProxyHandlerDefiner(handlerThing))
|
|
cgThings.append(CGDOMProxyJSClass(descriptor))
|
|
else:
|
|
cgThings.append(CGDOMJSClass(descriptor))
|
|
cgThings.append(CGGetJSClassMethod(descriptor))
|
|
|
|
if descriptor.interface.hasMembersInSlots():
|
|
cgThings.append(CGUpdateMemberSlotsMethod(descriptor))
|
|
|
|
if descriptor.isGlobal():
|
|
assert descriptor.wrapperCache
|
|
cgThings.append(CGWrapGlobalMethod(descriptor, properties))
|
|
elif descriptor.wrapperCache:
|
|
cgThings.append(CGWrapWithCacheMethod(descriptor, properties))
|
|
cgThings.append(CGWrapMethod(descriptor))
|
|
else:
|
|
cgThings.append(CGWrapNonWrapperCacheMethod(descriptor, properties))
|
|
|
|
# Set up our Xray callbacks as needed. This needs to come
|
|
# after we have our DOMProxyHandler defined.
|
|
if descriptor.wantsXrays:
|
|
if descriptor.concrete and descriptor.proxy:
|
|
if not descriptor.isMaybeCrossOriginObject():
|
|
cgThings.append(CGResolveOwnProperty(descriptor))
|
|
cgThings.append(CGEnumerateOwnProperties(descriptor))
|
|
if descriptor.needsXrayNamedDeleterHook():
|
|
cgThings.append(CGDeleteNamedProperty(descriptor))
|
|
elif descriptor.needsXrayResolveHooks():
|
|
cgThings.append(CGResolveOwnPropertyViaResolve(descriptor))
|
|
cgThings.append(
|
|
CGEnumerateOwnPropertiesViaGetOwnPropertyNames(descriptor)
|
|
)
|
|
if descriptor.wantsXrayExpandoClass:
|
|
cgThings.append(CGXrayExpandoJSClass(descriptor))
|
|
|
|
# Now that we have our ResolveOwnProperty/EnumerateOwnProperties stuff
|
|
# done, set up our NativePropertyHooks.
|
|
cgThings.append(CGNativePropertyHooks(descriptor, properties))
|
|
|
|
# If we're not wrappercached, we don't know how to clear our
|
|
# cached values, since we can't get at the JSObject.
|
|
if descriptor.wrapperCache:
|
|
cgThings.extend(
|
|
CGClearCachedValueMethod(descriptor, m)
|
|
for m in clearableCachedAttrs(descriptor)
|
|
)
|
|
|
|
haveUnscopables = (
|
|
len(unscopableNames) != 0
|
|
and descriptor.interface.hasInterfacePrototypeObject()
|
|
)
|
|
if haveUnscopables:
|
|
cgThings.append(
|
|
CGList(
|
|
[
|
|
CGGeneric("static const char* const unscopableNames[] = {"),
|
|
CGIndenter(
|
|
CGList(
|
|
[CGGeneric('"%s"' % name) for name in unscopableNames]
|
|
+ [CGGeneric("nullptr")],
|
|
",\n",
|
|
)
|
|
),
|
|
CGGeneric("};\n"),
|
|
],
|
|
"\n",
|
|
)
|
|
)
|
|
|
|
legacyWindowAliases = descriptor.interface.legacyWindowAliases
|
|
haveLegacyWindowAliases = len(legacyWindowAliases) != 0
|
|
if haveLegacyWindowAliases:
|
|
cgThings.append(
|
|
CGList(
|
|
[
|
|
CGGeneric("static const char* const legacyWindowAliases[] = {"),
|
|
CGIndenter(
|
|
CGList(
|
|
[
|
|
CGGeneric('"%s"' % name)
|
|
for name in legacyWindowAliases
|
|
]
|
|
+ [CGGeneric("nullptr")],
|
|
",\n",
|
|
)
|
|
),
|
|
CGGeneric("};\n"),
|
|
],
|
|
"\n",
|
|
)
|
|
)
|
|
|
|
# CGCreateInterfaceObjectsMethod needs to come after our
|
|
# CGDOMJSClass and unscopables, if any.
|
|
cgThings.append(
|
|
CGCreateInterfaceObjectsMethod(
|
|
descriptor, properties, haveUnscopables, haveLegacyWindowAliases
|
|
)
|
|
)
|
|
|
|
# CGGetProtoObjectMethod and CGGetConstructorObjectMethod need
|
|
# to come after CGCreateInterfaceObjectsMethod.
|
|
if descriptor.interface.hasInterfacePrototypeObject():
|
|
cgThings.append(CGGetProtoObjectHandleMethod(descriptor))
|
|
if descriptor.interface.hasChildInterfaces():
|
|
cgThings.append(CGGetProtoObjectMethod(descriptor))
|
|
if descriptor.interface.hasInterfaceObject():
|
|
cgThings.append(CGGetConstructorObjectHandleMethod(descriptor))
|
|
cgThings.append(CGGetConstructorObjectMethod(descriptor))
|
|
|
|
# See whether we need to generate cross-origin property arrays.
|
|
if needCrossOriginPropertyArrays:
|
|
cgThings.append(CGCrossOriginProperties(descriptor))
|
|
|
|
cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n")
|
|
cgThings = CGWrapper(cgThings, pre="\n", post="\n")
|
|
self.cgRoot = CGWrapper(
|
|
CGNamespace(toBindingNamespace(descriptor.name), cgThings), post="\n"
|
|
)
|
|
|
|
def declare(self):
|
|
return self.cgRoot.declare()
|
|
|
|
def define(self):
|
|
return self.cgRoot.define()
|
|
|
|
def deps(self):
|
|
return self._deps
|
|
|
|
|
|
class CGNamespacedEnum(CGThing):
|
|
def __init__(self, namespace, enumName, names, values, comment=""):
|
|
|
|
if not values:
|
|
values = []
|
|
|
|
# Account for explicit enum values.
|
|
entries = []
|
|
for i in range(0, len(names)):
|
|
if len(values) > i and values[i] is not None:
|
|
entry = "%s = %s" % (names[i], values[i])
|
|
else:
|
|
entry = names[i]
|
|
entries.append(entry)
|
|
|
|
# Append a Count.
|
|
entries.append("_" + enumName + "_Count")
|
|
|
|
# Indent.
|
|
entries = [" " + e for e in entries]
|
|
|
|
# Build the enum body.
|
|
enumstr = comment + "enum %s : uint16_t\n{\n%s\n};\n" % (
|
|
enumName,
|
|
",\n".join(entries),
|
|
)
|
|
curr = CGGeneric(declare=enumstr)
|
|
|
|
# Add some whitespace padding.
|
|
curr = CGWrapper(curr, pre="\n", post="\n")
|
|
|
|
# Add the namespace.
|
|
curr = CGNamespace(namespace, curr)
|
|
|
|
# Add the typedef
|
|
typedef = "\ntypedef %s::%s %s;\n\n" % (namespace, enumName, enumName)
|
|
curr = CGList([curr, CGGeneric(declare=typedef)])
|
|
|
|
# Save the result.
|
|
self.node = curr
|
|
|
|
def declare(self):
|
|
return self.node.declare()
|
|
|
|
def define(self):
|
|
return ""
|
|
|
|
|
|
def initIdsClassMethod(identifiers, atomCacheName):
|
|
idinit = [
|
|
'!atomsCache->%s.init(cx, "%s")' % (CGDictionary.makeIdName(id), id)
|
|
for id in identifiers
|
|
]
|
|
idinit.reverse()
|
|
body = fill(
|
|
"""
|
|
MOZ_ASSERT(JSID_IS_VOID(*reinterpret_cast<jsid*>(atomsCache)));
|
|
|
|
// Initialize these in reverse order so that any failure leaves the first one
|
|
// uninitialized.
|
|
if (${idinit}) {
|
|
return false;
|
|
}
|
|
return true;
|
|
""",
|
|
idinit=" ||\n ".join(idinit),
|
|
)
|
|
return ClassMethod(
|
|
"InitIds",
|
|
"bool",
|
|
[Argument("JSContext*", "cx"), Argument("%s*" % atomCacheName, "atomsCache")],
|
|
static=True,
|
|
body=body,
|
|
visibility="private",
|
|
)
|
|
|
|
|
|
class CGDictionary(CGThing):
|
|
def __init__(self, dictionary, descriptorProvider):
|
|
self.dictionary = dictionary
|
|
self.descriptorProvider = descriptorProvider
|
|
self.needToInitIds = len(dictionary.members) > 0
|
|
self.memberInfo = [
|
|
(
|
|
member,
|
|
getJSToNativeConversionInfo(
|
|
member.type,
|
|
descriptorProvider,
|
|
isMember="Dictionary",
|
|
isOptional=member.canHaveMissingValue(),
|
|
isKnownMissing=not dictionary.needsConversionFromJS,
|
|
defaultValue=member.defaultValue,
|
|
sourceDescription=self.getMemberSourceDescription(member),
|
|
),
|
|
)
|
|
for member in dictionary.members
|
|
]
|
|
|
|
# If we have a union member which is going to be declared in a different
|
|
# header but contains something that will be declared in the same header
|
|
# as us, bail: the C++ includes won't work out.
|
|
for member in dictionary.members:
|
|
type = member.type.unroll()
|
|
if type.isUnion() and CGHeaders.getUnionDeclarationFilename(
|
|
descriptorProvider.getConfig(), type
|
|
) != CGHeaders.getDeclarationFilename(dictionary):
|
|
for t in type.flatMemberTypes:
|
|
if t.isDictionary() and CGHeaders.getDeclarationFilename(
|
|
t.inner
|
|
) == CGHeaders.getDeclarationFilename(dictionary):
|
|
raise TypeError(
|
|
"Dictionary contains a union that will live in a different "
|
|
"header that contains a dictionary from the same header as "
|
|
"the original dictionary. This won't compile. Move the "
|
|
"inner dictionary to a different Web IDL file to move it "
|
|
"to a different header.\n%s\n%s"
|
|
% (t.location, t.inner.location)
|
|
)
|
|
self.structs = self.getStructs()
|
|
|
|
def declare(self):
|
|
return self.structs.declare()
|
|
|
|
def define(self):
|
|
return self.structs.define()
|
|
|
|
def base(self):
|
|
if self.dictionary.parent:
|
|
return self.makeClassName(self.dictionary.parent)
|
|
return "DictionaryBase"
|
|
|
|
def initMethod(self):
|
|
"""
|
|
This function outputs the body of the Init() method for the dictionary.
|
|
|
|
For the most part, this is some bookkeeping for our atoms so
|
|
we can avoid atomizing strings all the time, then we just spit
|
|
out the getMemberConversion() output for each member,
|
|
separated by newlines.
|
|
|
|
"""
|
|
body = dedent(
|
|
"""
|
|
// Passing a null JSContext is OK only if we're initing from null,
|
|
// Since in that case we will not have to do any property gets
|
|
// Also evaluate isNullOrUndefined in order to avoid false-positive
|
|
// checkers by static analysis tools
|
|
MOZ_ASSERT_IF(!cx, val.isNull() && val.isNullOrUndefined());
|
|
"""
|
|
)
|
|
|
|
if self.needToInitIds:
|
|
body += fill(
|
|
"""
|
|
${dictName}Atoms* atomsCache = nullptr;
|
|
if (cx) {
|
|
atomsCache = GetAtomCache<${dictName}Atoms>(cx);
|
|
if (JSID_IS_VOID(*reinterpret_cast<jsid*>(atomsCache)) &&
|
|
!InitIds(cx, atomsCache)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
""",
|
|
dictName=self.makeClassName(self.dictionary),
|
|
)
|
|
|
|
if self.dictionary.parent:
|
|
body += fill(
|
|
"""
|
|
// Per spec, we init the parent's members first
|
|
if (!${dictName}::Init(cx, val)) {
|
|
return false;
|
|
}
|
|
|
|
""",
|
|
dictName=self.makeClassName(self.dictionary.parent),
|
|
)
|
|
else:
|
|
body += dedent(
|
|
"""
|
|
if (!IsConvertibleToDictionary(val)) {
|
|
return cx.ThrowErrorMessage<MSG_NOT_DICTIONARY>(sourceDescription);
|
|
}
|
|
|
|
"""
|
|
)
|
|
|
|
memberInits = [self.getMemberConversion(m).define() for m in self.memberInfo]
|
|
if memberInits:
|
|
body += fill(
|
|
"""
|
|
bool isNull = val.isNullOrUndefined();
|
|
// We only need these if !isNull, in which case we have |cx|.
|
|
Maybe<JS::Rooted<JSObject *> > object;
|
|
Maybe<JS::Rooted<JS::Value> > temp;
|
|
if (!isNull) {
|
|
MOZ_ASSERT(cx);
|
|
object.emplace(cx, &val.toObject());
|
|
temp.emplace(cx);
|
|
}
|
|
$*{memberInits}
|
|
""",
|
|
memberInits="\n".join(memberInits),
|
|
)
|
|
|
|
body += "return true;\n"
|
|
|
|
return ClassMethod(
|
|
"Init",
|
|
"bool",
|
|
[
|
|
Argument("BindingCallContext&", "cx"),
|
|
Argument("JS::Handle<JS::Value>", "val"),
|
|
Argument("const char*", "sourceDescription", default='"Value"'),
|
|
Argument("bool", "passedToJSImpl", default="false"),
|
|
],
|
|
body=body,
|
|
)
|
|
|
|
def initWithoutCallContextMethod(self):
|
|
"""
|
|
This function outputs the body of an Init() method for the dictionary
|
|
that takes just a JSContext*. This is needed for non-binding consumers.
|
|
"""
|
|
body = dedent(
|
|
"""
|
|
// We don't want to use sourceDescription for our context here;
|
|
// that's not really what it's formatted for.
|
|
BindingCallContext cx(cx_, nullptr);
|
|
return Init(cx, val, sourceDescription, passedToJSImpl);
|
|
"""
|
|
)
|
|
return ClassMethod(
|
|
"Init",
|
|
"bool",
|
|
[
|
|
Argument("JSContext*", "cx_"),
|
|
Argument("JS::Handle<JS::Value>", "val"),
|
|
Argument("const char*", "sourceDescription", default='"Value"'),
|
|
Argument("bool", "passedToJSImpl", default="false"),
|
|
],
|
|
body=body,
|
|
)
|
|
|
|
def simpleInitMethod(self):
|
|
"""
|
|
This function outputs the body of the Init() method for the dictionary,
|
|
for cases when we are just default-initializing it.
|
|
|
|
"""
|
|
relevantMembers = [
|
|
m
|
|
for m in self.memberInfo
|
|
# We only need to init the things that can have
|
|
# default values.
|
|
if m[0].optional and m[0].defaultValue
|
|
]
|
|
|
|
# We mostly avoid outputting code that uses cx in our native-to-JS
|
|
# conversions, but there is one exception: we may have a
|
|
# dictionary-typed member that _does_ generally support conversion from
|
|
# JS. If we have such a thing, we can pass it a null JSContext and
|
|
# JS::NullHandleValue to default-initialize it, but since the
|
|
# native-to-JS templates hardcode `cx` as the JSContext value, we're
|
|
# going to need to provide that.
|
|
haveMemberThatNeedsCx = any(
|
|
m[0].type.isDictionary() and m[0].type.unroll().inner.needsConversionFromJS
|
|
for m in relevantMembers
|
|
)
|
|
if haveMemberThatNeedsCx:
|
|
body = dedent(
|
|
"""
|
|
JSContext* cx = nullptr;
|
|
"""
|
|
)
|
|
else:
|
|
body = ""
|
|
|
|
if self.dictionary.parent:
|
|
if self.dictionary.parent.needsConversionFromJS:
|
|
args = "nullptr, JS::NullHandleValue"
|
|
else:
|
|
args = ""
|
|
body += fill(
|
|
"""
|
|
// We init the parent's members first
|
|
if (!${dictName}::Init(${args})) {
|
|
return false;
|
|
}
|
|
|
|
""",
|
|
dictName=self.makeClassName(self.dictionary.parent),
|
|
args=args,
|
|
)
|
|
|
|
memberInits = [
|
|
self.getMemberConversion(m, isKnownMissing=True).define()
|
|
for m in relevantMembers
|
|
]
|
|
if memberInits:
|
|
body += fill(
|
|
"""
|
|
$*{memberInits}
|
|
""",
|
|
memberInits="\n".join(memberInits),
|
|
)
|
|
|
|
body += "return true;\n"
|
|
|
|
return ClassMethod(
|
|
"Init",
|
|
"bool",
|
|
[
|
|
Argument("const char*", "sourceDescription", default='"Value"'),
|
|
Argument("bool", "passedToJSImpl", default="false"),
|
|
],
|
|
body=body,
|
|
)
|
|
|
|
def initFromJSONMethod(self):
|
|
return ClassMethod(
|
|
"Init",
|
|
"bool",
|
|
[Argument("const nsAString&", "aJSON")],
|
|
body=dedent(
|
|
"""
|
|
AutoJSAPI jsapi;
|
|
JSObject* cleanGlobal = SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::BindingDetail);
|
|
if (!cleanGlobal) {
|
|
return false;
|
|
}
|
|
if (!jsapi.Init(cleanGlobal)) {
|
|
return false;
|
|
}
|
|
JSContext* cx = jsapi.cx();
|
|
JS::Rooted<JS::Value> json(cx);
|
|
bool ok = ParseJSON(cx, aJSON, &json);
|
|
NS_ENSURE_TRUE(ok, false);
|
|
return Init(cx, json);
|
|
"""
|
|
),
|
|
)
|
|
|
|
def toJSONMethod(self):
|
|
return ClassMethod(
|
|
"ToJSON",
|
|
"bool",
|
|
[Argument("nsAString&", "aJSON")],
|
|
body=dedent(
|
|
"""
|
|
AutoJSAPI jsapi;
|
|
jsapi.Init();
|
|
JSContext *cx = jsapi.cx();
|
|
// It's safe to use UnprivilegedJunkScopeOrWorkerGlobal here
|
|
// because we'll only be creating objects, in ways that have no
|
|
// side-effects, followed by a call to JS::ToJSONMaybeSafely,
|
|
// which likewise guarantees no side-effects for the sorts of
|
|
// things we will pass it.
|
|
JSObject* scope = UnprivilegedJunkScopeOrWorkerGlobal(fallible);
|
|
if (!scope) {
|
|
JS_ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
JSAutoRealm ar(cx, scope);
|
|
JS::Rooted<JS::Value> val(cx);
|
|
if (!ToObjectInternal(cx, &val)) {
|
|
return false;
|
|
}
|
|
JS::Rooted<JSObject*> obj(cx, &val.toObject());
|
|
return StringifyToJSON(cx, obj, aJSON);
|
|
"""
|
|
),
|
|
const=True,
|
|
)
|
|
|
|
def toObjectInternalMethod(self):
|
|
body = ""
|
|
if self.needToInitIds:
|
|
body += fill(
|
|
"""
|
|
${dictName}Atoms* atomsCache = GetAtomCache<${dictName}Atoms>(cx);
|
|
if (JSID_IS_VOID(*reinterpret_cast<jsid*>(atomsCache)) &&
|
|
!InitIds(cx, atomsCache)) {
|
|
return false;
|
|
}
|
|
|
|
""",
|
|
dictName=self.makeClassName(self.dictionary),
|
|
)
|
|
|
|
if self.dictionary.parent:
|
|
body += fill(
|
|
"""
|
|
// Per spec, we define the parent's members first
|
|
if (!${dictName}::ToObjectInternal(cx, rval)) {
|
|
return false;
|
|
}
|
|
JS::Rooted<JSObject*> obj(cx, &rval.toObject());
|
|
|
|
""",
|
|
dictName=self.makeClassName(self.dictionary.parent),
|
|
)
|
|
else:
|
|
body += dedent(
|
|
"""
|
|
JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx));
|
|
if (!obj) {
|
|
return false;
|
|
}
|
|
rval.set(JS::ObjectValue(*obj));
|
|
|
|
"""
|
|
)
|
|
|
|
if self.memberInfo:
|
|
body += "\n".join(
|
|
self.getMemberDefinition(m).define() for m in self.memberInfo
|
|
)
|
|
body += "\nreturn true;\n"
|
|
|
|
return ClassMethod(
|
|
"ToObjectInternal",
|
|
"bool",
|
|
[
|
|
Argument("JSContext*", "cx"),
|
|
Argument("JS::MutableHandle<JS::Value>", "rval"),
|
|
],
|
|
const=True,
|
|
body=body,
|
|
)
|
|
|
|
def initIdsMethod(self):
|
|
assert self.needToInitIds
|
|
return initIdsClassMethod(
|
|
[m.identifier.name for m in self.dictionary.members],
|
|
"%sAtoms" % self.makeClassName(self.dictionary),
|
|
)
|
|
|
|
def traceDictionaryMethod(self):
|
|
body = ""
|
|
if self.dictionary.parent:
|
|
cls = self.makeClassName(self.dictionary.parent)
|
|
body += "%s::TraceDictionary(trc);\n" % cls
|
|
|
|
memberTraces = [
|
|
self.getMemberTrace(m)
|
|
for m in self.dictionary.members
|
|
if typeNeedsRooting(m.type)
|
|
]
|
|
|
|
if memberTraces:
|
|
body += "\n".join(memberTraces)
|
|
|
|
return ClassMethod(
|
|
"TraceDictionary",
|
|
"void",
|
|
[
|
|
Argument("JSTracer*", "trc"),
|
|
],
|
|
body=body,
|
|
)
|
|
|
|
@staticmethod
|
|
def dictionaryNeedsCycleCollection(dictionary):
|
|
return any(idlTypeNeedsCycleCollection(m.type) for m in dictionary.members) or (
|
|
dictionary.parent
|
|
and CGDictionary.dictionaryNeedsCycleCollection(dictionary.parent)
|
|
)
|
|
|
|
def traverseForCCMethod(self):
|
|
body = ""
|
|
if self.dictionary.parent and self.dictionaryNeedsCycleCollection(
|
|
self.dictionary.parent
|
|
):
|
|
cls = self.makeClassName(self.dictionary.parent)
|
|
body += "%s::TraverseForCC(aCallback, aFlags);\n" % cls
|
|
|
|
for m, _ in self.memberInfo:
|
|
if idlTypeNeedsCycleCollection(m.type):
|
|
memberName = self.makeMemberName(m.identifier.name)
|
|
body += (
|
|
'ImplCycleCollectionTraverse(aCallback, %s, "%s", aFlags);\n'
|
|
% (memberName, memberName)
|
|
)
|
|
|
|
return ClassMethod(
|
|
"TraverseForCC",
|
|
"void",
|
|
[
|
|
Argument("nsCycleCollectionTraversalCallback&", "aCallback"),
|
|
Argument("uint32_t", "aFlags"),
|
|
],
|
|
body=body,
|
|
# Inline so we don't pay a codesize hit unless someone actually uses
|
|
# this traverse method.
|
|
inline=True,
|
|
bodyInHeader=True,
|
|
)
|
|
|
|
def unlinkForCCMethod(self):
|
|
body = ""
|
|
if self.dictionary.parent and self.dictionaryNeedsCycleCollection(
|
|
self.dictionary.parent
|
|
):
|
|
cls = self.makeClassName(self.dictionary.parent)
|
|
body += "%s::UnlinkForCC();\n" % cls
|
|
|
|
for m, _ in self.memberInfo:
|
|
if idlTypeNeedsCycleCollection(m.type):
|
|
memberName = self.makeMemberName(m.identifier.name)
|
|
body += "ImplCycleCollectionUnlink(%s);\n" % memberName
|
|
|
|
return ClassMethod(
|
|
"UnlinkForCC",
|
|
"void",
|
|
[],
|
|
body=body,
|
|
# Inline so we don't pay a codesize hit unless someone actually uses
|
|
# this unlink method.
|
|
inline=True,
|
|
bodyInHeader=True,
|
|
)
|
|
|
|
def assignmentOperator(self):
|
|
body = CGList([])
|
|
body.append(CGGeneric("%s::operator=(aOther);\n" % self.base()))
|
|
|
|
for m, _ in self.memberInfo:
|
|
memberName = self.makeMemberName(m.identifier.name)
|
|
if m.canHaveMissingValue():
|
|
memberAssign = CGGeneric(
|
|
fill(
|
|
"""
|
|
${name}.Reset();
|
|
if (aOther.${name}.WasPassed()) {
|
|
${name}.Construct(aOther.${name}.Value());
|
|
}
|
|
""",
|
|
name=memberName,
|
|
)
|
|
)
|
|
else:
|
|
memberAssign = CGGeneric("%s = aOther.%s;\n" % (memberName, memberName))
|
|
body.append(memberAssign)
|
|
body.append(CGGeneric("return *this;\n"))
|
|
return ClassMethod(
|
|
"operator=",
|
|
"%s&" % self.makeClassName(self.dictionary),
|
|
[Argument("const %s&" % self.makeClassName(self.dictionary), "aOther")],
|
|
body=body.define(),
|
|
)
|
|
|
|
def canHaveEqualsOperator(self):
|
|
return all(
|
|
m.type.isString() or m.type.isPrimitive() for (m, _) in self.memberInfo
|
|
)
|
|
|
|
def equalsOperator(self):
|
|
body = CGList([])
|
|
|
|
for m, _ in self.memberInfo:
|
|
memberName = self.makeMemberName(m.identifier.name)
|
|
memberTest = CGGeneric(
|
|
fill(
|
|
"""
|
|
if (${memberName} != aOther.${memberName}) {
|
|
return false;
|
|
}
|
|
""",
|
|
memberName=memberName,
|
|
)
|
|
)
|
|
body.append(memberTest)
|
|
body.append(CGGeneric("return true;\n"))
|
|
return ClassMethod(
|
|
"operator==",
|
|
"bool",
|
|
[Argument("const %s&" % self.makeClassName(self.dictionary), "aOther")],
|
|
const=True,
|
|
body=body.define(),
|
|
)
|
|
|
|
def getStructs(self):
|
|
d = self.dictionary
|
|
selfName = self.makeClassName(d)
|
|
members = [
|
|
ClassMember(
|
|
self.makeMemberName(m[0].identifier.name),
|
|
self.getMemberType(m),
|
|
visibility="public",
|
|
body=self.getMemberInitializer(m),
|
|
hasIgnoreInitCheckFlag=True,
|
|
)
|
|
for m in self.memberInfo
|
|
]
|
|
if d.parent:
|
|
# We always want to init our parent with our non-initializing
|
|
# constructor arg, because either we're about to init ourselves (and
|
|
# hence our parent) or we don't want any init happening.
|
|
baseConstructors = [
|
|
"%s(%s)"
|
|
% (self.makeClassName(d.parent), self.getNonInitializingCtorArg())
|
|
]
|
|
else:
|
|
baseConstructors = None
|
|
|
|
if d.needsConversionFromJS:
|
|
initArgs = "nullptr, JS::NullHandleValue"
|
|
else:
|
|
initArgs = ""
|
|
ctors = [
|
|
ClassConstructor(
|
|
[],
|
|
visibility="public",
|
|
baseConstructors=baseConstructors,
|
|
body=(
|
|
"// Safe to pass a null context if we pass a null value\n"
|
|
"Init(%s);\n" % initArgs
|
|
),
|
|
),
|
|
ClassConstructor(
|
|
[Argument("const FastDictionaryInitializer&", "")],
|
|
visibility="public",
|
|
baseConstructors=baseConstructors,
|
|
explicit=True,
|
|
bodyInHeader=True,
|
|
body='// Do nothing here; this is used by our "Fast" subclass\n',
|
|
),
|
|
]
|
|
methods = []
|
|
|
|
if self.needToInitIds:
|
|
methods.append(self.initIdsMethod())
|
|
|
|
if d.needsConversionFromJS:
|
|
methods.append(self.initMethod())
|
|
methods.append(self.initWithoutCallContextMethod())
|
|
else:
|
|
methods.append(self.simpleInitMethod())
|
|
|
|
canBeRepresentedAsJSON = self.dictionarySafeToJSONify(d)
|
|
if canBeRepresentedAsJSON and d.getExtendedAttribute("GenerateInitFromJSON"):
|
|
methods.append(self.initFromJSONMethod())
|
|
|
|
if d.needsConversionToJS:
|
|
methods.append(self.toObjectInternalMethod())
|
|
|
|
if canBeRepresentedAsJSON and d.getExtendedAttribute("GenerateToJSON"):
|
|
methods.append(self.toJSONMethod())
|
|
|
|
methods.append(self.traceDictionaryMethod())
|
|
|
|
try:
|
|
if self.dictionaryNeedsCycleCollection(d):
|
|
methods.append(self.traverseForCCMethod())
|
|
methods.append(self.unlinkForCCMethod())
|
|
except CycleCollectionUnsupported:
|
|
# We have some member that we don't know how to CC. Don't output
|
|
# our cycle collection overloads, so attempts to CC us will fail to
|
|
# compile instead of misbehaving.
|
|
pass
|
|
|
|
if CGDictionary.isDictionaryCopyConstructible(d):
|
|
disallowCopyConstruction = False
|
|
# Note: gcc's -Wextra has a warning against not initializng our
|
|
# base explicitly. If we have one. Use our non-initializing base
|
|
# constructor to get around that.
|
|
ctors.append(
|
|
ClassConstructor(
|
|
[Argument("const %s&" % selfName, "aOther")],
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
baseConstructors=baseConstructors,
|
|
explicit=True,
|
|
body="*this = aOther;\n",
|
|
)
|
|
)
|
|
methods.append(self.assignmentOperator())
|
|
else:
|
|
disallowCopyConstruction = True
|
|
|
|
if self.canHaveEqualsOperator():
|
|
methods.append(self.equalsOperator())
|
|
|
|
struct = CGClass(
|
|
selfName,
|
|
bases=[ClassBase(self.base())],
|
|
members=members,
|
|
constructors=ctors,
|
|
methods=methods,
|
|
isStruct=True,
|
|
disallowCopyConstruction=disallowCopyConstruction,
|
|
)
|
|
|
|
fastDictionaryCtor = ClassConstructor(
|
|
[],
|
|
visibility="public",
|
|
bodyInHeader=True,
|
|
baseConstructors=["%s(%s)" % (selfName, self.getNonInitializingCtorArg())],
|
|
body="// Doesn't matter what int we pass to the parent constructor\n",
|
|
)
|
|
|
|
fastStruct = CGClass(
|
|
"Fast" + selfName,
|
|
bases=[ClassBase(selfName)],
|
|
constructors=[fastDictionaryCtor],
|
|
isStruct=True,
|
|
)
|
|
|
|
return CGList([struct, CGNamespace("binding_detail", fastStruct)], "\n")
|
|
|
|
def deps(self):
|
|
return self.dictionary.getDeps()
|
|
|
|
@staticmethod
|
|
def makeDictionaryName(dictionary):
|
|
return dictionary.identifier.name
|
|
|
|
def makeClassName(self, dictionary):
|
|
return self.makeDictionaryName(dictionary)
|
|
|
|
@staticmethod
|
|
def makeMemberName(name):
|
|
return "m" + name[0].upper() + IDLToCIdentifier(name[1:])
|
|
|
|
def getMemberType(self, memberInfo):
|
|
_, conversionInfo = memberInfo
|
|
# We can't handle having a holderType here
|
|
assert conversionInfo.holderType is None
|
|
declType = conversionInfo.declType
|
|
if conversionInfo.dealWithOptional:
|
|
declType = CGTemplatedType("Optional", declType)
|
|
return declType.define()
|
|
|
|
def getMemberConversion(self, memberInfo, isKnownMissing=False):
|
|
"""
|
|
A function that outputs the initialization of a single dictionary
|
|
member from the given dictionary value.
|
|
|
|
We start with our conversionInfo, which tells us how to
|
|
convert a JS::Value to whatever type this member is. We
|
|
substiture the template from the conversionInfo with values
|
|
that point to our "temp" JS::Value and our member (which is
|
|
the C++ value we want to produce). The output is a string of
|
|
code to do the conversion. We store this string in
|
|
conversionReplacements["convert"].
|
|
|
|
Now we have three different ways we might use (or skip) this
|
|
string of code, depending on whether the value is required,
|
|
optional with default value, or optional without default
|
|
value. We set up a template in the 'conversion' variable for
|
|
exactly how to do this, then substitute into it from the
|
|
conversionReplacements dictionary.
|
|
"""
|
|
member, conversionInfo = memberInfo
|
|
|
|
# We should only be initializing things with default values if
|
|
# we're always-missing.
|
|
assert not isKnownMissing or (member.optional and member.defaultValue)
|
|
|
|
replacements = {
|
|
"declName": self.makeMemberName(member.identifier.name),
|
|
# We need a holder name for external interfaces, but
|
|
# it's scoped down to the conversion so we can just use
|
|
# anything we want.
|
|
"holderName": "holder",
|
|
"passedToJSImpl": "passedToJSImpl",
|
|
}
|
|
|
|
if isKnownMissing:
|
|
replacements["val"] = "(JS::NullHandleValue)"
|
|
else:
|
|
replacements["val"] = "temp.ref()"
|
|
replacements["maybeMutableVal"] = "temp.ptr()"
|
|
|
|
# We can't handle having a holderType here
|
|
assert conversionInfo.holderType is None
|
|
if conversionInfo.dealWithOptional:
|
|
replacements["declName"] = "(" + replacements["declName"] + ".Value())"
|
|
if member.defaultValue:
|
|
if isKnownMissing:
|
|
replacements["haveValue"] = "false"
|
|
else:
|
|
replacements["haveValue"] = "!isNull && !temp->isUndefined()"
|
|
|
|
propId = self.makeIdName(member.identifier.name)
|
|
propGet = "JS_GetPropertyById(cx, *object, atomsCache->%s, temp.ptr())" % propId
|
|
|
|
conversionReplacements = {
|
|
"prop": self.makeMemberName(member.identifier.name),
|
|
"convert": string.Template(conversionInfo.template).substitute(
|
|
replacements
|
|
),
|
|
"propGet": propGet,
|
|
}
|
|
# The conversion code will only run where a default value or a value passed
|
|
# by the author needs to get converted, so we can remember if we have any
|
|
# members present here.
|
|
conversionReplacements["convert"] += "mIsAnyMemberPresent = true;\n"
|
|
if isKnownMissing:
|
|
conversion = ""
|
|
else:
|
|
setTempValue = CGGeneric(
|
|
dedent(
|
|
"""
|
|
if (!${propGet}) {
|
|
return false;
|
|
}
|
|
"""
|
|
)
|
|
)
|
|
conditions = getConditionList(member, "cx", "*object")
|
|
if len(conditions) != 0:
|
|
setTempValue = CGIfElseWrapper(
|
|
conditions.define(),
|
|
setTempValue,
|
|
CGGeneric("temp->setUndefined();\n"),
|
|
)
|
|
setTempValue = CGIfWrapper(setTempValue, "!isNull")
|
|
conversion = setTempValue.define()
|
|
|
|
if member.defaultValue:
|
|
if member.type.isUnion() and (
|
|
not member.type.nullable()
|
|
or not isinstance(member.defaultValue, IDLNullValue)
|
|
):
|
|
# Since this has a default value, it might have been initialized
|
|
# already. Go ahead and uninit it before we try to init it
|
|
# again.
|
|
memberName = self.makeMemberName(member.identifier.name)
|
|
if member.type.nullable():
|
|
conversion += fill(
|
|
"""
|
|
if (!${memberName}.IsNull()) {
|
|
${memberName}.Value().Uninit();
|
|
}
|
|
""",
|
|
memberName=memberName,
|
|
)
|
|
else:
|
|
conversion += "%s.Uninit();\n" % memberName
|
|
conversion += "${convert}"
|
|
elif not conversionInfo.dealWithOptional:
|
|
# We're required, but have no default value. Make sure
|
|
# that we throw if we have no value provided.
|
|
conversion += dedent(
|
|
"""
|
|
if (!isNull && !temp->isUndefined()) {
|
|
${convert}
|
|
} else if (cx) {
|
|
// Don't error out if we have no cx. In that
|
|
// situation the caller is default-constructing us and we'll
|
|
// just assume they know what they're doing.
|
|
return cx.ThrowErrorMessage<MSG_MISSING_REQUIRED_DICTIONARY_MEMBER>("%s");
|
|
}
|
|
"""
|
|
% self.getMemberSourceDescription(member)
|
|
)
|
|
conversionReplacements["convert"] = indent(
|
|
conversionReplacements["convert"]
|
|
).rstrip()
|
|
else:
|
|
conversion += (
|
|
"if (!isNull && !temp->isUndefined()) {\n"
|
|
" ${prop}.Construct();\n"
|
|
"${convert}"
|
|
"}\n"
|
|
)
|
|
conversionReplacements["convert"] = indent(
|
|
conversionReplacements["convert"]
|
|
)
|
|
|
|
return CGGeneric(string.Template(conversion).substitute(conversionReplacements))
|
|
|
|
def getMemberDefinition(self, memberInfo):
|
|
member = memberInfo[0]
|
|
declType = memberInfo[1].declType
|
|
memberLoc = self.makeMemberName(member.identifier.name)
|
|
if not member.canHaveMissingValue():
|
|
memberData = memberLoc
|
|
else:
|
|
# The data is inside the Optional<>
|
|
memberData = "%s.InternalValue()" % memberLoc
|
|
|
|
# If you have to change this list (which you shouldn't!), make sure it
|
|
# continues to match the list in test_Object.prototype_props.html
|
|
if member.identifier.name in [
|
|
"constructor",
|
|
"toString",
|
|
"toLocaleString",
|
|
"valueOf",
|
|
"hasOwnProperty",
|
|
"isPrototypeOf",
|
|
"propertyIsEnumerable",
|
|
"__defineGetter__",
|
|
"__defineSetter__",
|
|
"__lookupGetter__",
|
|
"__lookupSetter__",
|
|
"__proto__",
|
|
]:
|
|
raise TypeError(
|
|
"'%s' member of %s dictionary shadows "
|
|
"a property of Object.prototype, and Xrays to "
|
|
"Object can't handle that.\n"
|
|
"%s"
|
|
% (
|
|
member.identifier.name,
|
|
self.dictionary.identifier.name,
|
|
member.location,
|
|
)
|
|
)
|
|
|
|
propDef = (
|
|
"JS_DefinePropertyById(cx, obj, atomsCache->%s, temp, JSPROP_ENUMERATE)"
|
|
% self.makeIdName(member.identifier.name)
|
|
)
|
|
|
|
innerTemplate = wrapForType(
|
|
member.type,
|
|
self.descriptorProvider,
|
|
{
|
|
"result": "currentValue",
|
|
"successCode": (
|
|
"if (!%s) {\n" " return false;\n" "}\n" "break;\n" % propDef
|
|
),
|
|
"jsvalRef": "temp",
|
|
"jsvalHandle": "&temp",
|
|
"returnsNewObject": False,
|
|
# 'obj' can just be allowed to be the string "obj", since that
|
|
# will be our dictionary object, which is presumably itself in
|
|
# the right scope.
|
|
"spiderMonkeyInterfacesAreStructs": True,
|
|
},
|
|
)
|
|
conversion = CGGeneric(innerTemplate)
|
|
conversion = CGWrapper(
|
|
conversion,
|
|
pre=(
|
|
"JS::Rooted<JS::Value> temp(cx);\n"
|
|
"%s const & currentValue = %s;\n" % (declType.define(), memberData)
|
|
),
|
|
)
|
|
|
|
# Now make sure that our successCode can actually break out of the
|
|
# conversion. This incidentally gives us a scope for 'temp' and
|
|
# 'currentValue'.
|
|
conversion = CGWrapper(
|
|
CGIndenter(conversion),
|
|
pre=(
|
|
"do {\n"
|
|
" // block for our 'break' successCode and scope for 'temp' and 'currentValue'\n"
|
|
),
|
|
post="} while(false);\n",
|
|
)
|
|
if member.canHaveMissingValue():
|
|
# Only do the conversion if we have a value
|
|
conversion = CGIfWrapper(conversion, "%s.WasPassed()" % memberLoc)
|
|
conditions = getConditionList(member, "cx", "obj")
|
|
if len(conditions) != 0:
|
|
conversion = CGIfWrapper(conversion, conditions.define())
|
|
return conversion
|
|
|
|
def getMemberTrace(self, member):
|
|
type = member.type
|
|
assert typeNeedsRooting(type)
|
|
memberLoc = self.makeMemberName(member.identifier.name)
|
|
if not member.canHaveMissingValue():
|
|
memberData = memberLoc
|
|
else:
|
|
# The data is inside the Optional<>
|
|
memberData = "%s.Value()" % memberLoc
|
|
|
|
memberName = "%s.%s" % (self.makeClassName(self.dictionary), memberLoc)
|
|
|
|
if type.isObject():
|
|
trace = CGGeneric(
|
|
'JS::UnsafeTraceRoot(trc, %s, "%s");\n' % ("&" + memberData, memberName)
|
|
)
|
|
if type.nullable():
|
|
trace = CGIfWrapper(trace, memberData)
|
|
elif type.isAny():
|
|
trace = CGGeneric(
|
|
'JS::UnsafeTraceRoot(trc, %s, "%s");\n' % ("&" + memberData, memberName)
|
|
)
|
|
elif (
|
|
type.isSequence()
|
|
or type.isDictionary()
|
|
or type.isSpiderMonkeyInterface()
|
|
or type.isUnion()
|
|
or type.isRecord()
|
|
):
|
|
if type.nullable():
|
|
memberNullable = memberData
|
|
memberData = "%s.Value()" % memberData
|
|
if type.isSequence():
|
|
trace = CGGeneric("DoTraceSequence(trc, %s);\n" % memberData)
|
|
elif type.isDictionary():
|
|
trace = CGGeneric("%s.TraceDictionary(trc);\n" % memberData)
|
|
elif type.isUnion():
|
|
trace = CGGeneric("%s.TraceUnion(trc);\n" % memberData)
|
|
elif type.isRecord():
|
|
trace = CGGeneric("TraceRecord(trc, %s);\n" % memberData)
|
|
else:
|
|
assert type.isSpiderMonkeyInterface()
|
|
trace = CGGeneric("%s.TraceSelf(trc);\n" % memberData)
|
|
if type.nullable():
|
|
trace = CGIfWrapper(trace, "!%s.IsNull()" % memberNullable)
|
|
else:
|
|
assert False # unknown type
|
|
|
|
if member.canHaveMissingValue():
|
|
trace = CGIfWrapper(trace, "%s.WasPassed()" % memberLoc)
|
|
|
|
return trace.define()
|
|
|
|
def getMemberInitializer(self, memberInfo):
|
|
"""
|
|
Get the right initializer for the member. Most members don't need one,
|
|
but we need to pre-initialize 'object' that have a default value or are
|
|
required (and hence are not inside Optional), so they're safe to trace
|
|
at all times. And we can optimize a bit for dictionary-typed members.
|
|
"""
|
|
member, _ = memberInfo
|
|
if member.canHaveMissingValue():
|
|
# Allowed missing value means no need to set it up front, since it's
|
|
# inside an Optional and won't get traced until it's actually set
|
|
# up.
|
|
return None
|
|
type = member.type
|
|
if type.isDictionary():
|
|
# When we construct ourselves, we don't want to init our member
|
|
# dictionaries. Either we're being constructed-but-not-initialized
|
|
# ourselves (and then we don't want to init them) or we're about to
|
|
# init ourselves and then we'll init them anyway.
|
|
return CGDictionary.getNonInitializingCtorArg()
|
|
return initializerForType(type)
|
|
|
|
def getMemberSourceDescription(self, member):
|
|
return "'%s' member of %s" % (
|
|
member.identifier.name,
|
|
self.dictionary.identifier.name,
|
|
)
|
|
|
|
@staticmethod
|
|
def makeIdName(name):
|
|
return IDLToCIdentifier(name) + "_id"
|
|
|
|
@staticmethod
|
|
def getNonInitializingCtorArg():
|
|
return "FastDictionaryInitializer()"
|
|
|
|
@staticmethod
|
|
def isDictionaryCopyConstructible(dictionary):
|
|
if dictionary.parent and not CGDictionary.isDictionaryCopyConstructible(
|
|
dictionary.parent
|
|
):
|
|
return False
|
|
return all(isTypeCopyConstructible(m.type) for m in dictionary.members)
|
|
|
|
@staticmethod
|
|
def typeSafeToJSONify(type):
|
|
"""
|
|
Determine whether the given type is safe to convert to JSON. The
|
|
restriction is that this needs to be safe while in a global controlled
|
|
by an adversary, and "safe" means no side-effects when the JS
|
|
representation of this type is converted to JSON. That means that we
|
|
have to be pretty restrictive about what things we can allow. For
|
|
example, "object" is out, because it may have accessor properties on it.
|
|
"""
|
|
if type.nullable():
|
|
# Converting null to JSON is always OK.
|
|
return CGDictionary.typeSafeToJSONify(type.inner)
|
|
|
|
if type.isSequence():
|
|
# Sequences are arrays we create ourselves, with no holes. They
|
|
# should be safe if their contents are safe, as long as we suppress
|
|
# invocation of .toJSON on objects.
|
|
return CGDictionary.typeSafeToJSONify(type.inner)
|
|
|
|
if type.isUnion():
|
|
# OK if everything in it is ok.
|
|
return all(CGDictionary.typeSafeToJSONify(t) for t in type.flatMemberTypes)
|
|
|
|
if type.isDictionary():
|
|
# OK if the dictionary is OK
|
|
return CGDictionary.dictionarySafeToJSONify(type.inner)
|
|
|
|
if type.isString() or type.isEnum():
|
|
# Strings are always OK.
|
|
return True
|
|
|
|
if type.isPrimitive():
|
|
# Primitives (numbers and booleans) are ok, as long as
|
|
# they're not unrestricted float/double.
|
|
return not type.isFloat() or not type.isUnrestricted()
|
|
|
|
if type.isRecord():
|
|
# Records are okay, as long as the value type is.
|
|
# Per spec, only strings are allowed as keys.
|
|
return CGDictionary.typeSafeToJSONify(type.inner)
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def dictionarySafeToJSONify(dictionary):
|
|
# The dictionary itself is OK, so we're good if all our types are.
|
|
return all(CGDictionary.typeSafeToJSONify(m.type) for m in dictionary.members)
|
|
|
|
|
|
class CGRegisterWorkerBindings(CGAbstractMethod):
|
|
def __init__(self, config):
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
None,
|
|
"RegisterWorkerBindings",
|
|
"bool",
|
|
[Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
|
|
)
|
|
self.config = config
|
|
|
|
def definition_body(self):
|
|
descriptors = self.config.getDescriptors(
|
|
hasInterfaceObject=True, isExposedInAnyWorker=True, register=True
|
|
)
|
|
conditions = []
|
|
for desc in descriptors:
|
|
bindingNS = toBindingNamespace(desc.name)
|
|
condition = "!%s::GetConstructorObject(aCx)" % bindingNS
|
|
if desc.isExposedConditionally():
|
|
condition = (
|
|
"%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition
|
|
)
|
|
conditions.append(condition)
|
|
lines = [
|
|
CGIfWrapper(CGGeneric("return false;\n"), condition)
|
|
for condition in conditions
|
|
]
|
|
lines.append(CGGeneric("return true;\n"))
|
|
return CGList(lines, "\n").define()
|
|
|
|
|
|
class CGRegisterWorkerDebuggerBindings(CGAbstractMethod):
|
|
def __init__(self, config):
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
None,
|
|
"RegisterWorkerDebuggerBindings",
|
|
"bool",
|
|
[Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
|
|
)
|
|
self.config = config
|
|
|
|
def definition_body(self):
|
|
descriptors = self.config.getDescriptors(
|
|
hasInterfaceObject=True, isExposedInWorkerDebugger=True, register=True
|
|
)
|
|
conditions = []
|
|
for desc in descriptors:
|
|
bindingNS = toBindingNamespace(desc.name)
|
|
condition = "!%s::GetConstructorObject(aCx)" % bindingNS
|
|
if desc.isExposedConditionally():
|
|
condition = (
|
|
"%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition
|
|
)
|
|
conditions.append(condition)
|
|
lines = [
|
|
CGIfWrapper(CGGeneric("return false;\n"), condition)
|
|
for condition in conditions
|
|
]
|
|
lines.append(CGGeneric("return true;\n"))
|
|
return CGList(lines, "\n").define()
|
|
|
|
|
|
class CGRegisterWorkletBindings(CGAbstractMethod):
|
|
def __init__(self, config):
|
|
CGAbstractMethod.__init__(
|
|
self,
|
|
None,
|
|
"RegisterWorkletBindings",
|
|
"bool",
|
|
[Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
|
|
)
|
|
self.config = config
|
|
|
|
def definition_body(self):
|
|
descriptors = self.config.getDescriptors(
|
|
hasInterfaceObject=True, isExposedInAnyWorklet=True, register=True
|
|
)
|
|
conditions = []
|
|
for desc in descriptors:
|
|
bindingNS = toBindingNamespace(desc.name)
|
|
condition = "!%s::GetConstructorObject(aCx)" % bindingNS
|
|
if desc.isExposedConditionally():
|
|
condition = (
|
|
"%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition
|
|
)
|
|
conditions.append(condition)
|
|
lines = [
|
|
CGIfWrapper(CGGeneric("return false;\n"), condition)
|
|
for condition in conditions
|
|
]
|
|
lines.append(CGGeneric("return true;\n"))
|
|
return CGList(lines, "\n").define()
|
|
|
|
|
|
def getGlobalNames(config):
|
|
names = []
|
|
for desc in config.getDescriptors(registersGlobalNamesOnWindow=True):
|
|
names.append((desc.name, desc))
|
|
names.extend(
|
|
(n.identifier.name, desc) for n in desc.interface.namedConstructors
|
|
)
|
|
names.extend((n, desc) for n in desc.interface.legacyWindowAliases)
|
|
return names
|
|
|
|
|
|
class CGGlobalNames(CGGeneric):
|
|
def __init__(self, config):
|
|
currentOffset = 0
|
|
strings = []
|
|
entries = []
|
|
for name, desc in getGlobalNames(config):
|
|
# Add a string to the list.
|
|
offset = currentOffset
|
|
strings.append('/* %i */ "%s\\0"' % (offset, name))
|
|
currentOffset += len(name) + 1 # Add trailing null.
|
|
|
|
# Generate the entry declaration
|
|
# XXX(nika): mCreate & mEnabled require relocations. If we want to
|
|
# reduce those, we could move them into separate tables.
|
|
nativeEntry = fill(
|
|
"""
|
|
{
|
|
/* mNameOffset */ ${nameOffset}, // "${name}"
|
|
/* mNameLength */ ${nameLength},
|
|
/* mConstructorId */ constructors::id::${realname},
|
|
/* mCreate */ ${realname}_Binding::CreateInterfaceObjects,
|
|
/* mEnabled */ ${enabled}
|
|
}
|
|
""",
|
|
nameOffset=offset,
|
|
nameLength=len(name),
|
|
name=name,
|
|
realname=desc.name,
|
|
enabled=(
|
|
"%s_Binding::ConstructorEnabled" % desc.name
|
|
if desc.isExposedConditionally()
|
|
else "nullptr"
|
|
),
|
|
)
|
|
|
|
entries.append((name, nativeEntry))
|
|
|
|
# Unfortunately, when running tests, we may have no entries.
|
|
# PerfectHash will assert if we give it an empty set of entries, so we
|
|
# just generate a dummy value.
|
|
if len(entries) == 0:
|
|
CGGeneric.__init__(
|
|
self,
|
|
define=dedent(
|
|
"""
|
|
static_assert(false, "No WebIDL global name entries!");
|
|
"""
|
|
),
|
|
)
|
|
return
|
|
|
|
# Build the perfect hash function.
|
|
phf = PerfectHash(entries, GLOBAL_NAMES_PHF_SIZE)
|
|
|
|
# Generate code for the PHF
|
|
phfCodegen = phf.codegen(
|
|
"WebIDLGlobalNameHash::sEntries", "WebIDLNameTableEntry"
|
|
)
|
|
entries = phfCodegen.gen_entries(lambda e: e[1])
|
|
getter = phfCodegen.gen_jslinearstr_getter(
|
|
name="WebIDLGlobalNameHash::GetEntry",
|
|
return_type="const WebIDLNameTableEntry*",
|
|
return_entry=dedent(
|
|
"""
|
|
if (JS_LinearStringEqualsAscii(aKey, sNames + entry.mNameOffset, entry.mNameLength)) {
|
|
return &entry;
|
|
}
|
|
return nullptr;
|
|
"""
|
|
),
|
|
)
|
|
|
|
define = fill(
|
|
"""
|
|
const uint32_t WebIDLGlobalNameHash::sCount = ${count};
|
|
|
|
const char WebIDLGlobalNameHash::sNames[] =
|
|
$*{strings}
|
|
|
|
$*{entries}
|
|
|
|
$*{getter}
|
|
|
|
""",
|
|
count=len(phf.entries),
|
|
strings="\n".join(strings) + ";\n",
|
|
entries=entries,
|
|
getter=getter,
|
|
)
|
|
CGGeneric.__init__(self, define=define)
|
|
|
|
|
|
def dependencySortObjects(objects, dependencyGetter, nameGetter):
|
|
"""
|
|
Sort IDL objects with dependencies on each other such that if A
|
|
depends on B then B will come before A. This is needed for
|
|
declaring C++ classes in the right order, for example. Objects
|
|
that have no dependencies are just sorted by name.
|
|
|
|
objects should be something that can produce a set of objects
|
|
(e.g. a set, iterator, list, etc).
|
|
|
|
dependencyGetter is something that, given an object, should return
|
|
the set of objects it depends on.
|
|
"""
|
|
# XXXbz this will fail if we have two webidl files F1 and F2 such that F1
|
|
# declares an object which depends on an object in F2, and F2 declares an
|
|
# object (possibly a different one!) that depends on an object in F1. The
|
|
# good news is that I expect this to never happen.
|
|
sortedObjects = []
|
|
objects = set(objects)
|
|
while len(objects) != 0:
|
|
# Find the dictionaries that don't depend on anything else
|
|
# anymore and move them over.
|
|
toMove = [o for o in objects if len(dependencyGetter(o) & objects) == 0]
|
|
if len(toMove) == 0:
|
|
raise TypeError(
|
|
"Loop in dependency graph\n" + "\n".join(o.location for o in objects)
|
|
)
|
|
objects = objects - set(toMove)
|
|
sortedObjects.extend(sorted(toMove, key=nameGetter))
|
|
return sortedObjects
|
|
|
|
|
|
class ForwardDeclarationBuilder:
|
|
"""
|
|
Create a canonical representation of a set of namespaced forward
|
|
declarations.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""
|
|
The set of declarations is represented as a tree of nested namespaces.
|
|
Each tree node has a set of declarations |decls| and a dict |children|.
|
|
Each declaration is a pair consisting of the class name and a boolean
|
|
that is true iff the class is really a struct. |children| maps the
|
|
names of inner namespaces to the declarations in that namespace.
|
|
"""
|
|
self.decls = set()
|
|
self.children = {}
|
|
|
|
def _ensureNonTemplateType(self, type):
|
|
if "<" in type:
|
|
# This is a templated type. We don't really know how to
|
|
# forward-declare those, and trying to do it naively is not going to
|
|
# go well (e.g. we may have :: characters inside the type we're
|
|
# templated on!). Just bail out.
|
|
raise TypeError(
|
|
"Attempt to use ForwardDeclarationBuilder on "
|
|
"templated type %s. We don't know how to do that "
|
|
"yet." % type
|
|
)
|
|
|
|
def _listAdd(self, namespaces, name, isStruct=False):
|
|
"""
|
|
Add a forward declaration, where |namespaces| is a list of namespaces.
|
|
|name| should not contain any other namespaces.
|
|
"""
|
|
if namespaces:
|
|
child = self.children.setdefault(namespaces[0], ForwardDeclarationBuilder())
|
|
child._listAdd(namespaces[1:], name, isStruct)
|
|
else:
|
|
assert "::" not in name
|
|
self.decls.add((name, isStruct))
|
|
|
|
def addInMozillaDom(self, name, isStruct=False):
|
|
"""
|
|
Add a forward declaration to the mozilla::dom:: namespace. |name| should not
|
|
contain any other namespaces.
|
|
"""
|
|
self._ensureNonTemplateType(name)
|
|
self._listAdd(["mozilla", "dom"], name, isStruct)
|
|
|
|
def add(self, nativeType, isStruct=False):
|
|
"""
|
|
Add a forward declaration, where |nativeType| is a string containing
|
|
the type and its namespaces, in the usual C++ way.
|
|
"""
|
|
self._ensureNonTemplateType(nativeType)
|
|
components = nativeType.split("::")
|
|
self._listAdd(components[:-1], components[-1], isStruct)
|
|
|
|
def _build(self, atTopLevel):
|
|
"""
|
|
Return a codegenerator for the forward declarations.
|
|
"""
|
|
decls = []
|
|
if self.decls:
|
|
decls.append(
|
|
CGList(
|
|
[
|
|
CGClassForwardDeclare(cname, isStruct)
|
|
for cname, isStruct in sorted(self.decls)
|
|
]
|
|
)
|
|
)
|
|
for namespace, child in sorted(six.iteritems(self.children)):
|
|
decls.append(
|
|
CGNamespace(namespace, child._build(atTopLevel=False), declareOnly=True)
|
|
)
|
|
|
|
cg = CGList(decls, "\n")
|
|
if not atTopLevel and len(decls) + len(self.decls) > 1:
|
|
cg = CGWrapper(cg, pre="\n", post="\n")
|
|
return cg
|
|
|
|
def build(self):
|
|
return self._build(atTopLevel=True)
|
|
|
|
def forwardDeclareForType(self, t, config):
|
|
t = t.unroll()
|
|
if t.isGeckoInterface():
|
|
name = t.inner.identifier.name
|
|
try:
|
|
desc = config.getDescriptor(name)
|
|
self.add(desc.nativeType)
|
|
except NoSuchDescriptorError:
|
|
pass
|
|
|
|
# Note: SpiderMonkey interfaces are typedefs, so can't be
|
|
# forward-declared
|
|
elif t.isPromise():
|
|
self.addInMozillaDom("Promise")
|
|
elif t.isCallback():
|
|
self.addInMozillaDom(t.callback.identifier.name)
|
|
elif t.isDictionary():
|
|
self.addInMozillaDom(t.inner.identifier.name, isStruct=True)
|
|
elif t.isCallbackInterface():
|
|
self.addInMozillaDom(t.inner.identifier.name)
|
|
elif t.isUnion():
|
|
# Forward declare both the owning and non-owning version,
|
|
# since we don't know which one we might want
|
|
self.addInMozillaDom(CGUnionStruct.unionTypeName(t, False))
|
|
self.addInMozillaDom(CGUnionStruct.unionTypeName(t, True))
|
|
elif t.isRecord():
|
|
self.forwardDeclareForType(t.inner, config)
|
|
# Don't need to do anything for void, primitive, string, any or object.
|
|
# There may be some other cases we are missing.
|
|
|
|
|
|
class CGForwardDeclarations(CGWrapper):
|
|
"""
|
|
Code generate the forward declarations for a header file.
|
|
additionalDeclarations is a list of tuples containing a classname and a
|
|
boolean. If the boolean is true we will declare a struct, otherwise we'll
|
|
declare a class.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config,
|
|
descriptors,
|
|
callbacks,
|
|
dictionaries,
|
|
callbackInterfaces,
|
|
additionalDeclarations=[],
|
|
):
|
|
builder = ForwardDeclarationBuilder()
|
|
|
|
# Needed for at least Wrap.
|
|
for d in descriptors:
|
|
# If this is a generated iterator interface, we only create these
|
|
# in the generated bindings, and don't need to forward declare.
|
|
if d.interface.isIteratorInterface():
|
|
continue
|
|
builder.add(d.nativeType)
|
|
if d.interface.isSerializable():
|
|
builder.add("nsIGlobalObject")
|
|
# If we're an interface and we have a maplike/setlike declaration,
|
|
# we'll have helper functions exposed to the native side of our
|
|
# bindings, which will need to show up in the header. If either of
|
|
# our key/value types are interfaces, they'll be passed as
|
|
# arguments to helper functions, and they'll need to be forward
|
|
# declared in the header.
|
|
if d.interface.maplikeOrSetlikeOrIterable:
|
|
if d.interface.maplikeOrSetlikeOrIterable.hasKeyType():
|
|
builder.forwardDeclareForType(
|
|
d.interface.maplikeOrSetlikeOrIterable.keyType, config
|
|
)
|
|
if d.interface.maplikeOrSetlikeOrIterable.hasValueType():
|
|
builder.forwardDeclareForType(
|
|
d.interface.maplikeOrSetlikeOrIterable.valueType, config
|
|
)
|
|
|
|
# We just about always need NativePropertyHooks
|
|
builder.addInMozillaDom("NativePropertyHooks", isStruct=True)
|
|
builder.addInMozillaDom("ProtoAndIfaceCache")
|
|
# Add the atoms cache type, even if we don't need it.
|
|
for d in descriptors:
|
|
# Iterators have native types that are template classes, so
|
|
# creating an 'Atoms' cache type doesn't work for them, and is one
|
|
# of the cases where we don't need it anyways.
|
|
if d.interface.isIteratorInterface():
|
|
continue
|
|
builder.add(d.nativeType + "Atoms", isStruct=True)
|
|
|
|
for callback in callbacks:
|
|
builder.addInMozillaDom(callback.identifier.name)
|
|
for t in getTypesFromCallback(callback):
|
|
builder.forwardDeclareForType(t, config)
|
|
|
|
for d in callbackInterfaces:
|
|
builder.add(d.nativeType)
|
|
builder.add(d.nativeType + "Atoms", isStruct=True)
|
|
for t in getTypesFromDescriptor(d):
|
|
builder.forwardDeclareForType(t, config)
|
|
if d.hasCEReactions():
|
|
builder.addInMozillaDom("DocGroup")
|
|
|
|
for d in dictionaries:
|
|
if len(d.members) > 0:
|
|
builder.addInMozillaDom(d.identifier.name + "Atoms", isStruct=True)
|
|
for t in getTypesFromDictionary(d):
|
|
builder.forwardDeclareForType(t, config)
|
|
|
|
for className, isStruct in additionalDeclarations:
|
|
builder.add(className, isStruct=isStruct)
|
|
|
|
CGWrapper.__init__(self, builder.build())
|
|
|
|
|
|
class CGBindingRoot(CGThing):
|
|
"""
|
|
Root codegen class for binding generation. Instantiate the class, and call
|
|
declare or define to generate header or cpp code (respectively).
|
|
"""
|
|
|
|
def __init__(self, config, prefix, webIDLFile):
|
|
bindingHeaders = dict.fromkeys(("mozilla/dom/NonRefcountedDOMObject.h",), True)
|
|
bindingDeclareHeaders = dict.fromkeys(
|
|
(
|
|
"mozilla/dom/BindingDeclarations.h",
|
|
"mozilla/dom/Nullable.h",
|
|
"mozilla/ErrorResult.h",
|
|
"GeckoProfiler.h",
|
|
),
|
|
True,
|
|
)
|
|
|
|
descriptors = config.getDescriptors(
|
|
webIDLFile=webIDLFile, hasInterfaceOrInterfacePrototypeObject=True
|
|
)
|
|
|
|
unionTypes = UnionsForFile(config, webIDLFile)
|
|
|
|
(
|
|
unionHeaders,
|
|
unionImplheaders,
|
|
unionDeclarations,
|
|
traverseMethods,
|
|
unlinkMethods,
|
|
unionStructs,
|
|
) = UnionTypes(unionTypes, config)
|
|
|
|
bindingDeclareHeaders.update(dict.fromkeys(unionHeaders, True))
|
|
bindingHeaders.update(dict.fromkeys(unionImplheaders, True))
|
|
bindingDeclareHeaders["mozilla/dom/UnionMember.h"] = len(unionStructs) > 0
|
|
bindingDeclareHeaders["mozilla/dom/FakeString.h"] = len(unionStructs) > 0
|
|
# BindingUtils.h is only needed for SetToObject.
|
|
# If it stops being inlined or stops calling CallerSubsumes
|
|
# both this bit and the bit in UnionTypes can be removed.
|
|
bindingDeclareHeaders["mozilla/dom/BindingUtils.h"] = any(
|
|
d.isObject() for t in unionTypes for d in t.flatMemberTypes
|
|
)
|
|
bindingDeclareHeaders["mozilla/dom/IterableIterator.h"] = any(
|
|
d.interface.isIteratorInterface() or d.interface.isIterable()
|
|
for d in descriptors
|
|
)
|
|
|
|
def descriptorHasCrossOriginProperties(desc):
|
|
def hasCrossOriginProperty(m):
|
|
props = memberProperties(m, desc)
|
|
return (
|
|
props.isCrossOriginMethod
|
|
or props.isCrossOriginGetter
|
|
or props.isCrossOriginSetter
|
|
)
|
|
|
|
return any(hasCrossOriginProperty(m) for m in desc.interface.members)
|
|
|
|
bindingDeclareHeaders["mozilla/dom/RemoteObjectProxy.h"] = any(
|
|
descriptorHasCrossOriginProperties(d) for d in descriptors
|
|
)
|
|
bindingDeclareHeaders["jsapi.h"] = any(
|
|
descriptorHasCrossOriginProperties(d) for d in descriptors
|
|
)
|
|
bindingDeclareHeaders["jspubtd.h"] = not bindingDeclareHeaders["jsapi.h"]
|
|
bindingDeclareHeaders["js/RootingAPI.h"] = not bindingDeclareHeaders["jsapi.h"]
|
|
|
|
def descriptorHasIteratorAlias(desc):
|
|
def hasIteratorAlias(m):
|
|
return m.isMethod() and "@@iterator" in m.aliases
|
|
|
|
return any(hasIteratorAlias(m) for m in desc.interface.members)
|
|
|
|
bindingHeaders["js/Symbol.h"] = any(
|
|
descriptorHasIteratorAlias(d) for d in descriptors
|
|
)
|
|
|
|
bindingHeaders["js/shadow/Object.h"] = any(
|
|
d.interface.hasMembersInSlots() for d in descriptors
|
|
)
|
|
|
|
# The symbols supplied by this header are used so ubiquitously it's not
|
|
# worth the effort delineating the exact dependency, if it can't be done
|
|
# *at* the places where their definitions are required.
|
|
bindingHeaders["js/experimental/JitInfo.h"] = True
|
|
|
|
# JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, and
|
|
# JS::SetReservedSlot are also used too many places to restate
|
|
# dependency logic.
|
|
bindingHeaders["js/Object.h"] = True
|
|
|
|
def descriptorDeprecated(desc):
|
|
iface = desc.interface
|
|
return any(
|
|
m.getExtendedAttribute("Deprecated") for m in iface.members + [iface]
|
|
)
|
|
|
|
bindingHeaders["mozilla/dom/Document.h"] = any(
|
|
descriptorDeprecated(d) for d in descriptors
|
|
)
|
|
|
|
bindingHeaders["mozilla/dom/DOMJSProxyHandler.h"] = any(
|
|
d.concrete and d.proxy for d in descriptors
|
|
)
|
|
|
|
bindingHeaders["js/String.h"] = any(
|
|
d.needsMissingPropUseCounters for d in descriptors
|
|
)
|
|
|
|
hasCrossOriginObjects = any(
|
|
d.concrete and d.isMaybeCrossOriginObject() for d in descriptors
|
|
)
|
|
bindingHeaders["mozilla/dom/MaybeCrossOriginObject.h"] = hasCrossOriginObjects
|
|
bindingHeaders["AccessCheck.h"] = hasCrossOriginObjects
|
|
hasCEReactions = any(d.hasCEReactions() for d in descriptors)
|
|
bindingHeaders["mozilla/dom/CustomElementRegistry.h"] = hasCEReactions
|
|
bindingHeaders["mozilla/dom/DocGroup.h"] = hasCEReactions
|
|
|
|
def descriptorHasChromeOnly(desc):
|
|
ctor = desc.interface.ctor()
|
|
|
|
return (
|
|
any(
|
|
isChromeOnly(a) or needsContainsHack(a) or needsCallerType(a)
|
|
for a in desc.interface.members
|
|
)
|
|
or desc.interface.getExtendedAttribute("ChromeOnly") is not None
|
|
or
|
|
# JS-implemented interfaces with an interface object get a
|
|
# chromeonly _create method. And interfaces with an
|
|
# interface object might have a ChromeOnly constructor.
|
|
(
|
|
desc.interface.hasInterfaceObject()
|
|
and (
|
|
desc.interface.isJSImplemented()
|
|
or (ctor and isChromeOnly(ctor))
|
|
)
|
|
)
|
|
)
|
|
|
|
# XXXkhuey ugly hack but this is going away soon.
|
|
bindingHeaders["xpcprivate.h"] = webIDLFile.endswith("EventTarget.webidl")
|
|
|
|
hasThreadChecks = any(d.hasThreadChecks() for d in descriptors)
|
|
bindingHeaders["nsThreadUtils.h"] = hasThreadChecks
|
|
|
|
dictionaries = config.getDictionaries(webIDLFile)
|
|
|
|
def dictionaryHasChromeOnly(dictionary):
|
|
while dictionary:
|
|
if any(isChromeOnly(m) for m in dictionary.members):
|
|
return True
|
|
dictionary = dictionary.parent
|
|
return False
|
|
|
|
def needsNonSystemPrincipal(member):
|
|
return (
|
|
member.getExtendedAttribute("NeedsSubjectPrincipal") == ["NonSystem"]
|
|
or member.getExtendedAttribute("SetterNeedsSubjectPrincipal")
|
|
== ["NonSystem"]
|
|
or member.getExtendedAttribute("GetterNeedsSubjectPrincipal")
|
|
== ["NonSystem"]
|
|
)
|
|
|
|
def descriptorNeedsNonSystemPrincipal(d):
|
|
return any(needsNonSystemPrincipal(m) for m in d.interface.members)
|
|
|
|
def descriptorHasPrefDisabler(desc):
|
|
iface = desc.interface
|
|
return any(
|
|
PropertyDefiner.getControllingCondition(m, desc).hasDisablers()
|
|
for m in iface.members
|
|
if (m.isMethod() or m.isAttr() or m.isConst())
|
|
)
|
|
|
|
def addPrefHeaderForObject(bindingHeaders, obj):
|
|
"""
|
|
obj might be a dictionary member or an interface.
|
|
"""
|
|
if obj is not None:
|
|
pref = PropertyDefiner.getStringAttr(obj, "Pref")
|
|
if pref:
|
|
bindingHeaders[prefHeader(pref)] = True
|
|
|
|
def addPrefHeadersForDictionary(bindingHeaders, dictionary):
|
|
while dictionary:
|
|
for m in dictionary.members:
|
|
addPrefHeaderForObject(bindingHeaders, m)
|
|
dictionary = dictionary.parent
|
|
|
|
for d in dictionaries:
|
|
addPrefHeadersForDictionary(bindingHeaders, d)
|
|
for d in descriptors:
|
|
interface = d.interface
|
|
addPrefHeaderForObject(bindingHeaders, interface)
|
|
addPrefHeaderForObject(bindingHeaders, interface.ctor())
|
|
|
|
bindingHeaders["mozilla/dom/WebIDLPrefs.h"] = any(
|
|
descriptorHasPrefDisabler(d) for d in descriptors
|
|
)
|
|
bindingHeaders["nsContentUtils.h"] = (
|
|
any(descriptorHasChromeOnly(d) for d in descriptors)
|
|
or any(descriptorNeedsNonSystemPrincipal(d) for d in descriptors)
|
|
or any(dictionaryHasChromeOnly(d) for d in dictionaries)
|
|
)
|
|
hasNonEmptyDictionaries = any(len(dict.members) > 0 for dict in dictionaries)
|
|
callbacks = config.getCallbacks(webIDLFile)
|
|
callbackDescriptors = config.getDescriptors(
|
|
webIDLFile=webIDLFile, isCallback=True
|
|
)
|
|
jsImplemented = config.getDescriptors(
|
|
webIDLFile=webIDLFile, isJSImplemented=True
|
|
)
|
|
bindingDeclareHeaders["nsWeakReference.h"] = jsImplemented
|
|
bindingDeclareHeaders["mozilla/dom/PrototypeList.h"] = descriptors
|
|
bindingHeaders["nsIGlobalObject.h"] = jsImplemented
|
|
bindingHeaders["AtomList.h"] = (
|
|
hasNonEmptyDictionaries or jsImplemented or callbackDescriptors
|
|
)
|
|
|
|
def descriptorClearsPropsInSlots(descriptor):
|
|
if not descriptor.wrapperCache:
|
|
return False
|
|
return any(
|
|
m.isAttr() and m.getExtendedAttribute("StoreInSlot")
|
|
for m in descriptor.interface.members
|
|
)
|
|
|
|
bindingHeaders["nsJSUtils.h"] = any(
|
|
descriptorClearsPropsInSlots(d) for d in descriptors
|
|
)
|
|
|
|
# Make sure we can sanely use binding_detail in generated code.
|
|
cgthings = [
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
namespace binding_detail {}; // Just to make sure it's known as a namespace
|
|
using namespace mozilla::dom::binding_detail;
|
|
"""
|
|
)
|
|
)
|
|
]
|
|
|
|
# Do codegen for all the enums
|
|
enums = config.getEnums(webIDLFile)
|
|
cgthings.extend(CGEnum(e) for e in enums)
|
|
|
|
bindingDeclareHeaders["mozilla/Span.h"] = enums
|
|
bindingDeclareHeaders["mozilla/ArrayUtils.h"] = enums
|
|
|
|
hasCode = descriptors or callbackDescriptors or dictionaries or callbacks
|
|
bindingHeaders["mozilla/dom/BindingUtils.h"] = hasCode
|
|
bindingHeaders["mozilla/OwningNonNull.h"] = hasCode
|
|
bindingHeaders["<type_traits>"] = hasCode
|
|
bindingHeaders["mozilla/dom/BindingDeclarations.h"] = not hasCode and enums
|
|
|
|
bindingHeaders["WrapperFactory.h"] = descriptors
|
|
bindingHeaders["mozilla/dom/DOMJSClass.h"] = descriptors
|
|
bindingHeaders["mozilla/dom/ScriptSettings.h"] = dictionaries # AutoJSAPI
|
|
# Ensure we see our enums in the generated .cpp file, for the ToJSValue
|
|
# method body. Also ensure that we see jsapi.h.
|
|
if enums:
|
|
bindingHeaders[CGHeaders.getDeclarationFilename(enums[0])] = True
|
|
bindingHeaders["jsapi.h"] = True
|
|
|
|
# For things that have [UseCounter] or [InstrumentedProps]
|
|
descriptorsHaveUseCounters = any(
|
|
m.getExtendedAttribute("UseCounter")
|
|
for d in descriptors
|
|
for m in d.interface.members
|
|
)
|
|
descriptorsHaveInstrumentedProps = any(
|
|
d.instrumentedProps for d in descriptors if d.concrete
|
|
)
|
|
|
|
bindingHeaders["mozilla/UseCounter.h"] = (
|
|
descriptorsHaveUseCounters or descriptorsHaveInstrumentedProps
|
|
)
|
|
# Make sure to not overwrite existing pref header bits!
|
|
bindingHeaders[prefHeader(MISSING_PROP_PREF)] = (
|
|
bindingHeaders.get(prefHeader(MISSING_PROP_PREF))
|
|
or descriptorsHaveInstrumentedProps
|
|
)
|
|
bindingHeaders["mozilla/dom/SimpleGlobalObject.h"] = any(
|
|
CGDictionary.dictionarySafeToJSONify(d) for d in dictionaries
|
|
)
|
|
bindingHeaders["XrayWrapper.h"] = any(
|
|
d.wantsXrays and d.wantsXrayExpandoClass for d in descriptors
|
|
)
|
|
bindingHeaders["mozilla/dom/XrayExpandoClass.h"] = any(
|
|
d.wantsXrays for d in descriptors
|
|
)
|
|
bindingHeaders["mozilla/dom/StructuredCloneTags.h"] = any(
|
|
d.interface.isSerializable() for d in descriptors
|
|
)
|
|
bindingHeaders["mozilla/Atomics.h"] = any(d.wantsXrays for d in descriptors)
|
|
|
|
for ancestor in (findAncestorWithInstrumentedProps(d) for d in descriptors):
|
|
if not ancestor:
|
|
continue
|
|
bindingHeaders[CGHeaders.getDeclarationFilename(ancestor)] = True
|
|
|
|
cgthings.extend(traverseMethods)
|
|
cgthings.extend(unlinkMethods)
|
|
|
|
# Do codegen for all the dictionaries. We have to be a bit careful
|
|
# here, because we have to generate these in order from least derived
|
|
# to most derived so that class inheritance works out. We also have to
|
|
# generate members before the dictionary that contains them.
|
|
|
|
def getDependenciesFromType(type):
|
|
if type.isDictionary():
|
|
return set([type.unroll().inner])
|
|
if type.isSequence():
|
|
return getDependenciesFromType(type.unroll())
|
|
if type.isUnion():
|
|
return set([type.unroll()])
|
|
return set()
|
|
|
|
def getDependencies(unionTypeOrDictionary):
|
|
if isinstance(unionTypeOrDictionary, IDLDictionary):
|
|
deps = set()
|
|
if unionTypeOrDictionary.parent:
|
|
deps.add(unionTypeOrDictionary.parent)
|
|
for member in unionTypeOrDictionary.members:
|
|
deps |= getDependenciesFromType(member.type)
|
|
return deps
|
|
|
|
assert unionTypeOrDictionary.isType() and unionTypeOrDictionary.isUnion()
|
|
deps = set()
|
|
for member in unionTypeOrDictionary.flatMemberTypes:
|
|
deps |= getDependenciesFromType(member)
|
|
return deps
|
|
|
|
def getName(unionTypeOrDictionary):
|
|
if isinstance(unionTypeOrDictionary, IDLDictionary):
|
|
return unionTypeOrDictionary.identifier.name
|
|
|
|
assert unionTypeOrDictionary.isType() and unionTypeOrDictionary.isUnion()
|
|
return unionTypeOrDictionary.name
|
|
|
|
for t in dependencySortObjects(
|
|
dictionaries + unionStructs, getDependencies, getName
|
|
):
|
|
if t.isDictionary():
|
|
cgthings.append(CGDictionary(t, config))
|
|
else:
|
|
assert t.isUnion()
|
|
cgthings.append(CGUnionStruct(t, config))
|
|
cgthings.append(CGUnionStruct(t, config, True))
|
|
|
|
# Do codegen for all the callbacks.
|
|
cgthings.extend(CGCallbackFunction(c, config) for c in callbacks)
|
|
|
|
cgthings.extend(
|
|
[CGNamespace("binding_detail", CGFastCallback(c)) for c in callbacks]
|
|
)
|
|
|
|
# Do codegen for all the descriptors
|
|
cgthings.extend([CGDescriptor(x) for x in descriptors])
|
|
|
|
# Do codegen for all the callback interfaces.
|
|
cgthings.extend([CGCallbackInterface(x) for x in callbackDescriptors])
|
|
|
|
cgthings.extend(
|
|
[
|
|
CGNamespace("binding_detail", CGFastCallback(x.interface))
|
|
for x in callbackDescriptors
|
|
]
|
|
)
|
|
|
|
# Do codegen for JS implemented classes
|
|
def getParentDescriptor(desc):
|
|
if not desc.interface.parent:
|
|
return set()
|
|
return {desc.getDescriptor(desc.interface.parent.identifier.name)}
|
|
|
|
for x in dependencySortObjects(
|
|
jsImplemented, getParentDescriptor, lambda d: d.interface.identifier.name
|
|
):
|
|
cgthings.append(
|
|
CGCallbackInterface(x, spiderMonkeyInterfacesAreStructs=True)
|
|
)
|
|
cgthings.append(CGJSImplClass(x))
|
|
|
|
# And make sure we have the right number of newlines at the end
|
|
curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, pre="\n"))
|
|
|
|
curr = CGList(
|
|
[
|
|
CGForwardDeclarations(
|
|
config,
|
|
descriptors,
|
|
callbacks,
|
|
dictionaries,
|
|
callbackDescriptors + jsImplemented,
|
|
additionalDeclarations=unionDeclarations,
|
|
),
|
|
curr,
|
|
],
|
|
"\n",
|
|
)
|
|
|
|
# Add header includes.
|
|
bindingHeaders = [
|
|
header for header, include in six.iteritems(bindingHeaders) if include
|
|
]
|
|
bindingDeclareHeaders = [
|
|
header
|
|
for header, include in six.iteritems(bindingDeclareHeaders)
|
|
if include
|
|
]
|
|
|
|
curr = CGHeaders(
|
|
descriptors,
|
|
dictionaries,
|
|
callbacks,
|
|
callbackDescriptors,
|
|
bindingDeclareHeaders,
|
|
bindingHeaders,
|
|
prefix,
|
|
curr,
|
|
config,
|
|
jsImplemented,
|
|
)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard(prefix, curr)
|
|
|
|
# Add the auto-generated comment.
|
|
curr = CGWrapper(
|
|
curr,
|
|
pre=(
|
|
AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT % os.path.basename(webIDLFile)
|
|
),
|
|
)
|
|
|
|
# Store the final result.
|
|
self.root = curr
|
|
|
|
def declare(self):
|
|
return stripTrailingWhitespace(self.root.declare())
|
|
|
|
def define(self):
|
|
return stripTrailingWhitespace(self.root.define())
|
|
|
|
def deps(self):
|
|
return self.root.deps()
|
|
|
|
|
|
class CGNativeMember(ClassMethod):
|
|
def __init__(
|
|
self,
|
|
descriptorProvider,
|
|
member,
|
|
name,
|
|
signature,
|
|
extendedAttrs,
|
|
breakAfter=True,
|
|
passJSBitsAsNeeded=True,
|
|
visibility="public",
|
|
spiderMonkeyInterfacesAreStructs=True,
|
|
variadicIsSequence=False,
|
|
resultNotAddRefed=False,
|
|
virtual=False,
|
|
override=False,
|
|
canRunScript=False,
|
|
):
|
|
"""
|
|
If spiderMonkeyInterfacesAreStructs is false, SpiderMonkey interfaces
|
|
will be passed as JS::Handle<JSObject*>. If it's true they will be
|
|
passed as one of the dom::SpiderMonkeyInterfaceObjectStorage subclasses.
|
|
|
|
If passJSBitsAsNeeded is false, we don't automatically pass in a
|
|
JSContext* or a JSObject* based on the return and argument types. We
|
|
can still pass it based on 'implicitJSContext' annotations.
|
|
"""
|
|
self.descriptorProvider = descriptorProvider
|
|
self.member = member
|
|
self.extendedAttrs = extendedAttrs
|
|
self.resultAlreadyAddRefed = not resultNotAddRefed
|
|
self.passJSBitsAsNeeded = passJSBitsAsNeeded
|
|
self.spiderMonkeyInterfacesAreStructs = spiderMonkeyInterfacesAreStructs
|
|
self.variadicIsSequence = variadicIsSequence
|
|
breakAfterSelf = "\n" if breakAfter else ""
|
|
ClassMethod.__init__(
|
|
self,
|
|
name,
|
|
self.getReturnType(signature[0], False),
|
|
self.getArgs(signature[0], signature[1]),
|
|
static=member.isStatic(),
|
|
# Mark our getters, which are attrs that
|
|
# have a non-void return type, as const.
|
|
const=(
|
|
not member.isStatic() and member.isAttr() and not signature[0].isVoid()
|
|
),
|
|
breakAfterReturnDecl=" ",
|
|
breakAfterSelf=breakAfterSelf,
|
|
visibility=visibility,
|
|
virtual=virtual,
|
|
override=override,
|
|
canRunScript=canRunScript,
|
|
)
|
|
|
|
def getReturnType(self, type, isMember):
|
|
return self.getRetvalInfo(type, isMember)[0]
|
|
|
|
def getRetvalInfo(self, type, isMember):
|
|
"""
|
|
Returns a tuple:
|
|
|
|
The first element is the type declaration for the retval
|
|
|
|
The second element is a default value that can be used on error returns.
|
|
For cases whose behavior depends on isMember, the second element will be
|
|
None if isMember is true.
|
|
|
|
The third element is a template for actually returning a value stored in
|
|
"${declName}" and "${holderName}". This means actually returning it if
|
|
we're not outparam, else assigning to the "retval" outparam. If
|
|
isMember is true, this can be None, since in that case the caller will
|
|
never examine this value.
|
|
"""
|
|
if type.isVoid():
|
|
return "void", "", ""
|
|
if type.isPrimitive() and type.tag() in builtinNames:
|
|
result = CGGeneric(builtinNames[type.tag()])
|
|
defaultReturnArg = "0"
|
|
if type.nullable():
|
|
result = CGTemplatedType("Nullable", result)
|
|
defaultReturnArg = ""
|
|
return (
|
|
result.define(),
|
|
"%s(%s)" % (result.define(), defaultReturnArg),
|
|
"return ${declName};\n",
|
|
)
|
|
if type.isJSString():
|
|
if isMember:
|
|
raise TypeError("JSString not supported as return type member")
|
|
# Outparam
|
|
return "void", "", "aRetVal.set(${declName});\n"
|
|
if type.isDOMString() or type.isUSVString():
|
|
if isMember:
|
|
# No need for a third element in the isMember case
|
|
return "nsString", None, None
|
|
# Outparam
|
|
return "void", "", "aRetVal = ${declName};\n"
|
|
if type.isByteString() or type.isUTF8String():
|
|
if isMember:
|
|
# No need for a third element in the isMember case
|
|
return "nsCString", None, None
|
|
# Outparam
|
|
return "void", "", "aRetVal = ${declName};\n"
|
|
if type.isEnum():
|
|
enumName = type.unroll().inner.identifier.name
|
|
if type.nullable():
|
|
enumName = CGTemplatedType("Nullable", CGGeneric(enumName)).define()
|
|
defaultValue = "%s()" % enumName
|
|
else:
|
|
defaultValue = "%s(0)" % enumName
|
|
return enumName, defaultValue, "return ${declName};\n"
|
|
if type.isGeckoInterface() or type.isPromise():
|
|
if type.isGeckoInterface():
|
|
iface = type.unroll().inner
|
|
result = CGGeneric(
|
|
self.descriptorProvider.getDescriptor(
|
|
iface.identifier.name
|
|
).prettyNativeType
|
|
)
|
|
else:
|
|
result = CGGeneric("Promise")
|
|
if self.resultAlreadyAddRefed:
|
|
if isMember:
|
|
holder = "RefPtr"
|
|
else:
|
|
holder = "already_AddRefed"
|
|
if memberReturnsNewObject(self.member) or isMember:
|
|
warning = ""
|
|
else:
|
|
warning = "// Return a raw pointer here to avoid refcounting, but make sure it's safe (the object should be kept alive by the callee).\n"
|
|
result = CGWrapper(result, pre=("%s%s<" % (warning, holder)), post=">")
|
|
else:
|
|
result = CGWrapper(result, post="*")
|
|
# Since we always force an owning type for callback return values,
|
|
# our ${declName} is an OwningNonNull or RefPtr. So we can just
|
|
# .forget() to get our already_AddRefed.
|
|
return result.define(), "nullptr", "return ${declName}.forget();\n"
|
|
if type.isCallback():
|
|
return (
|
|
"already_AddRefed<%s>" % type.unroll().callback.identifier.name,
|
|
"nullptr",
|
|
"return ${declName}.forget();\n",
|
|
)
|
|
if type.isAny():
|
|
if isMember:
|
|
# No need for a third element in the isMember case
|
|
return "JS::Value", None, None
|
|
# Outparam
|
|
return "void", "", "aRetVal.set(${declName});\n"
|
|
|
|
if type.isObject():
|
|
if isMember:
|
|
# No need for a third element in the isMember case
|
|
return "JSObject*", None, None
|
|
return "void", "", "aRetVal.set(${declName});\n"
|
|
if type.isSpiderMonkeyInterface():
|
|
if isMember:
|
|
# No need for a third element in the isMember case
|
|
return "JSObject*", None, None
|
|
if type.nullable():
|
|
returnCode = (
|
|
"${declName}.IsNull() ? nullptr : ${declName}.Value().Obj()"
|
|
)
|
|
else:
|
|
returnCode = "${declName}.Obj()"
|
|
return "void", "", "aRetVal.set(%s);\n" % returnCode
|
|
if type.isSequence():
|
|
# If we want to handle sequence-of-sequences return values, we're
|
|
# going to need to fix example codegen to not produce nsTArray<void>
|
|
# for the relevant argument...
|
|
assert not isMember
|
|
# Outparam.
|
|
if type.nullable():
|
|
returnCode = dedent(
|
|
"""
|
|
if (${declName}.IsNull()) {
|
|
aRetVal.SetNull();
|
|
} else {
|
|
aRetVal.SetValue() = std::move(${declName}.Value());
|
|
}
|
|
"""
|
|
)
|
|
else:
|
|
returnCode = "aRetVal = std::move(${declName});\n"
|
|
return "void", "", returnCode
|
|
if type.isRecord():
|
|
# If we want to handle record-of-record return values, we're
|
|
# going to need to fix example codegen to not produce record<void>
|
|
# for the relevant argument...
|
|
assert not isMember
|
|
# In this case we convert directly into our outparam to start with
|
|
return "void", "", ""
|
|
if type.isDictionary():
|
|
if isMember:
|
|
# Only the first member of the tuple matters here, but return
|
|
# bogus values for the others in case someone decides to use
|
|
# them.
|
|
return CGDictionary.makeDictionaryName(type.inner), None, None
|
|
# In this case we convert directly into our outparam to start with
|
|
return "void", "", ""
|
|
if type.isUnion():
|
|
if isMember:
|
|
# Only the first member of the tuple matters here, but return
|
|
# bogus values for the others in case someone decides to use
|
|
# them.
|
|
return CGUnionStruct.unionTypeDecl(type, True), None, None
|
|
# In this case we convert directly into our outparam to start with
|
|
return "void", "", ""
|
|
|
|
raise TypeError("Don't know how to declare return value for %s" % type)
|
|
|
|
def getArgs(self, returnType, argList):
|
|
args = [self.getArg(arg) for arg in argList]
|
|
# Now the outparams
|
|
if returnType.isJSString():
|
|
args.append(Argument("JS::MutableHandle<JSString*>", "aRetVal"))
|
|
elif returnType.isDOMString() or returnType.isUSVString():
|
|
args.append(Argument("nsString&", "aRetVal"))
|
|
elif returnType.isByteString() or returnType.isUTF8String():
|
|
args.append(Argument("nsCString&", "aRetVal"))
|
|
elif returnType.isSequence():
|
|
nullable = returnType.nullable()
|
|
if nullable:
|
|
returnType = returnType.inner
|
|
# And now the actual underlying type
|
|
elementDecl = self.getReturnType(returnType.inner, True)
|
|
type = CGTemplatedType("nsTArray", CGGeneric(elementDecl))
|
|
if nullable:
|
|
type = CGTemplatedType("Nullable", type)
|
|
args.append(Argument("%s&" % type.define(), "aRetVal"))
|
|
elif returnType.isRecord():
|
|
nullable = returnType.nullable()
|
|
if nullable:
|
|
returnType = returnType.inner
|
|
# And now the actual underlying type
|
|
elementDecl = self.getReturnType(returnType.inner, True)
|
|
type = CGTemplatedType(
|
|
"Record", [recordKeyDeclType(returnType), CGGeneric(elementDecl)]
|
|
)
|
|
if nullable:
|
|
type = CGTemplatedType("Nullable", type)
|
|
args.append(Argument("%s&" % type.define(), "aRetVal"))
|
|
elif returnType.isDictionary():
|
|
nullable = returnType.nullable()
|
|
if nullable:
|
|
returnType = returnType.inner
|
|
dictType = CGGeneric(CGDictionary.makeDictionaryName(returnType.inner))
|
|
if nullable:
|
|
dictType = CGTemplatedType("Nullable", dictType)
|
|
args.append(Argument("%s&" % dictType.define(), "aRetVal"))
|
|
elif returnType.isUnion():
|
|
args.append(
|
|
Argument(
|
|
"%s&" % CGUnionStruct.unionTypeDecl(returnType, True), "aRetVal"
|
|
)
|
|
)
|
|
elif returnType.isAny():
|
|
args.append(Argument("JS::MutableHandle<JS::Value>", "aRetVal"))
|
|
elif returnType.isObject() or returnType.isSpiderMonkeyInterface():
|
|
args.append(Argument("JS::MutableHandle<JSObject*>", "aRetVal"))
|
|
|
|
# And the nsIPrincipal
|
|
if "needsSubjectPrincipal" in self.extendedAttrs:
|
|
# Cheat and assume self.descriptorProvider is a descriptor
|
|
if self.descriptorProvider.interface.isExposedInAnyWorker():
|
|
args.append(Argument("Maybe<nsIPrincipal*>", "aSubjectPrincipal"))
|
|
elif "needsNonSystemSubjectPrincipal" in self.extendedAttrs:
|
|
args.append(Argument("nsIPrincipal*", "aPrincipal"))
|
|
else:
|
|
args.append(Argument("nsIPrincipal&", "aPrincipal"))
|
|
# And the caller type, if desired.
|
|
if needsCallerType(self.member):
|
|
args.append(Argument("CallerType", "aCallerType"))
|
|
# And the ErrorResult or OOMReporter
|
|
if "infallible" not in self.extendedAttrs:
|
|
# Use aRv so it won't conflict with local vars named "rv"
|
|
args.append(Argument("ErrorResult&", "aRv"))
|
|
elif "canOOM" in self.extendedAttrs:
|
|
args.append(Argument("OOMReporter&", "aRv"))
|
|
|
|
# The legacycaller thisval
|
|
if self.member.isMethod() and self.member.isLegacycaller():
|
|
# If it has an identifier, we can't deal with it yet
|
|
assert self.member.isIdentifierLess()
|
|
args.insert(0, Argument("const JS::Value&", "aThisVal"))
|
|
# And jscontext bits.
|
|
if needCx(
|
|
returnType,
|
|
argList,
|
|
self.extendedAttrs,
|
|
self.passJSBitsAsNeeded,
|
|
self.member.isStatic(),
|
|
):
|
|
args.insert(0, Argument("JSContext*", "cx"))
|
|
if needScopeObject(
|
|
returnType,
|
|
argList,
|
|
self.extendedAttrs,
|
|
self.descriptorProvider.wrapperCache,
|
|
self.passJSBitsAsNeeded,
|
|
self.member.getExtendedAttribute("StoreInSlot"),
|
|
):
|
|
args.insert(1, Argument("JS::Handle<JSObject*>", "obj"))
|
|
# And if we're static, a global
|
|
if self.member.isStatic():
|
|
args.insert(0, Argument("const GlobalObject&", "global"))
|
|
return args
|
|
|
|
def doGetArgType(self, type, optional, isMember):
|
|
"""
|
|
The main work of getArgType. Returns a string type decl, whether this
|
|
is a const ref, as well as whether the type should be wrapped in
|
|
Nullable as needed.
|
|
|
|
isMember can be false or one of the strings "Sequence", "Variadic",
|
|
"Record"
|
|
"""
|
|
if type.isSequence():
|
|
nullable = type.nullable()
|
|
if nullable:
|
|
type = type.inner
|
|
elementType = type.inner
|
|
argType = self.getArgType(elementType, False, "Sequence")[0]
|
|
decl = CGTemplatedType("Sequence", argType)
|
|
return decl.define(), True, True
|
|
|
|
if type.isRecord():
|
|
nullable = type.nullable()
|
|
if nullable:
|
|
type = type.inner
|
|
elementType = type.inner
|
|
argType = self.getArgType(elementType, False, "Record")[0]
|
|
decl = CGTemplatedType("Record", [recordKeyDeclType(type), argType])
|
|
return decl.define(), True, True
|
|
|
|
if type.isUnion():
|
|
# unionTypeDecl will handle nullable types, so return False for
|
|
# auto-wrapping in Nullable
|
|
return CGUnionStruct.unionTypeDecl(type, isMember), True, False
|
|
|
|
if type.isPromise():
|
|
assert not type.nullable()
|
|
if optional or isMember:
|
|
typeDecl = "OwningNonNull<Promise>"
|
|
else:
|
|
typeDecl = "Promise&"
|
|
return (typeDecl, False, False)
|
|
|
|
if type.isGeckoInterface() and not type.isCallbackInterface():
|
|
iface = type.unroll().inner
|
|
if iface.identifier.name == "WindowProxy":
|
|
return "WindowProxyHolder", True, False
|
|
|
|
argIsPointer = type.nullable() or iface.isExternal()
|
|
forceOwningType = iface.isCallback() or isMember
|
|
if argIsPointer:
|
|
if (optional or isMember) and forceOwningType:
|
|
typeDecl = "RefPtr<%s>"
|
|
else:
|
|
typeDecl = "%s*"
|
|
else:
|
|
if optional or isMember:
|
|
if forceOwningType:
|
|
typeDecl = "OwningNonNull<%s>"
|
|
else:
|
|
typeDecl = "NonNull<%s>"
|
|
else:
|
|
typeDecl = "%s&"
|
|
return (
|
|
(
|
|
typeDecl
|
|
% self.descriptorProvider.getDescriptor(
|
|
iface.identifier.name
|
|
).prettyNativeType
|
|
),
|
|
False,
|
|
False,
|
|
)
|
|
|
|
if type.isSpiderMonkeyInterface():
|
|
if not self.spiderMonkeyInterfacesAreStructs:
|
|
return "JS::Handle<JSObject*>", False, False
|
|
|
|
# Unroll for the name, in case we're nullable.
|
|
return type.unroll().name, True, True
|
|
|
|
if type.isJSString():
|
|
if isMember:
|
|
raise TypeError("JSString not supported as member")
|
|
return "JS::Handle<JSString*>", False, False
|
|
|
|
if type.isDOMString() or type.isUSVString():
|
|
if isMember:
|
|
declType = "nsString"
|
|
else:
|
|
declType = "nsAString"
|
|
return declType, True, False
|
|
|
|
if type.isByteString() or type.isUTF8String():
|
|
# TODO(emilio): Maybe bytestrings could benefit from nsAutoCString
|
|
# or such too.
|
|
if type.isUTF8String() and not isMember:
|
|
declType = "nsACString"
|
|
else:
|
|
declType = "nsCString"
|
|
return declType, True, False
|
|
|
|
if type.isEnum():
|
|
return type.unroll().inner.identifier.name, False, True
|
|
|
|
if type.isCallback() or type.isCallbackInterface():
|
|
forceOwningType = optional or isMember
|
|
if type.nullable():
|
|
if forceOwningType:
|
|
declType = "RefPtr<%s>"
|
|
else:
|
|
declType = "%s*"
|
|
else:
|
|
if forceOwningType:
|
|
declType = "OwningNonNull<%s>"
|
|
else:
|
|
declType = "%s&"
|
|
if type.isCallback():
|
|
name = type.unroll().callback.identifier.name
|
|
else:
|
|
name = type.unroll().inner.identifier.name
|
|
return declType % name, False, False
|
|
|
|
if type.isAny():
|
|
# Don't do the rooting stuff for variadics for now
|
|
if isMember:
|
|
declType = "JS::Value"
|
|
else:
|
|
declType = "JS::Handle<JS::Value>"
|
|
return declType, False, False
|
|
|
|
if type.isObject():
|
|
if isMember:
|
|
declType = "JSObject*"
|
|
else:
|
|
declType = "JS::Handle<JSObject*>"
|
|
return declType, False, False
|
|
|
|
if type.isDictionary():
|
|
typeName = CGDictionary.makeDictionaryName(type.inner)
|
|
return typeName, True, True
|
|
|
|
assert type.isPrimitive()
|
|
|
|
return builtinNames[type.tag()], False, True
|
|
|
|
def getArgType(self, type, optional, isMember):
|
|
"""
|
|
Get the type of an argument declaration. Returns the type CGThing, and
|
|
whether this should be a const ref.
|
|
|
|
isMember can be False, "Sequence", or "Variadic"
|
|
"""
|
|
decl, ref, handleNullable = self.doGetArgType(type, optional, isMember)
|
|
decl = CGGeneric(decl)
|
|
if handleNullable and type.nullable():
|
|
decl = CGTemplatedType("Nullable", decl)
|
|
ref = True
|
|
if isMember == "Variadic":
|
|
arrayType = "Sequence" if self.variadicIsSequence else "nsTArray"
|
|
decl = CGTemplatedType(arrayType, decl)
|
|
ref = True
|
|
elif optional:
|
|
# Note: All variadic args claim to be optional, but we can just use
|
|
# empty arrays to represent them not being present.
|
|
decl = CGTemplatedType("Optional", decl)
|
|
ref = True
|
|
return (decl, ref)
|
|
|
|
def getArg(self, arg):
|
|
"""
|
|
Get the full argument declaration for an argument
|
|
"""
|
|
decl, ref = self.getArgType(
|
|
arg.type, arg.canHaveMissingValue(), "Variadic" if arg.variadic else False
|
|
)
|
|
if ref:
|
|
decl = CGWrapper(decl, pre="const ", post="&")
|
|
|
|
return Argument(decl.define(), arg.identifier.name)
|
|
|
|
def arguments(self):
|
|
return self.member.signatures()[0][1]
|
|
|
|
|
|
class CGExampleMethod(CGNativeMember):
|
|
def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
|
|
CGNativeMember.__init__(
|
|
self,
|
|
descriptor,
|
|
method,
|
|
CGSpecializedMethod.makeNativeName(descriptor, method),
|
|
signature,
|
|
descriptor.getExtendedAttributes(method),
|
|
breakAfter=breakAfter,
|
|
variadicIsSequence=True,
|
|
)
|
|
|
|
def declare(self, cgClass):
|
|
assert self.member.isMethod()
|
|
# We skip declaring ourselves if this is a maplike/setlike/iterable
|
|
# method, because those get implemented automatically by the binding
|
|
# machinery, so the implementor of the interface doesn't have to worry
|
|
# about it.
|
|
if self.member.isMaplikeOrSetlikeOrIterableMethod():
|
|
return ""
|
|
return CGNativeMember.declare(self, cgClass)
|
|
|
|
def define(self, cgClass):
|
|
return ""
|
|
|
|
|
|
class CGExampleGetter(CGNativeMember):
|
|
def __init__(self, descriptor, attr):
|
|
CGNativeMember.__init__(
|
|
self,
|
|
descriptor,
|
|
attr,
|
|
CGSpecializedGetter.makeNativeName(descriptor, attr),
|
|
(attr.type, []),
|
|
descriptor.getExtendedAttributes(attr, getter=True),
|
|
)
|
|
|
|
def declare(self, cgClass):
|
|
assert self.member.isAttr()
|
|
# We skip declaring ourselves if this is a maplike/setlike attr (in
|
|
# practice, "size"), because those get implemented automatically by the
|
|
# binding machinery, so the implementor of the interface doesn't have to
|
|
# worry about it.
|
|
if self.member.isMaplikeOrSetlikeAttr():
|
|
return ""
|
|
return CGNativeMember.declare(self, cgClass)
|
|
|
|
def define(self, cgClass):
|
|
return ""
|
|
|
|
|
|
class CGExampleSetter(CGNativeMember):
|
|
def __init__(self, descriptor, attr):
|
|
CGNativeMember.__init__(
|
|
self,
|
|
descriptor,
|
|
attr,
|
|
CGSpecializedSetter.makeNativeName(descriptor, attr),
|
|
(BuiltinTypes[IDLBuiltinType.Types.void], [FakeArgument(attr.type, attr)]),
|
|
descriptor.getExtendedAttributes(attr, setter=True),
|
|
)
|
|
|
|
def define(self, cgClass):
|
|
return ""
|
|
|
|
|
|
class CGBindingImplClass(CGClass):
|
|
"""
|
|
Common codegen for generating a C++ implementation of a WebIDL interface
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
cgMethod,
|
|
cgGetter,
|
|
cgSetter,
|
|
wantGetParent=True,
|
|
wrapMethodName="WrapObject",
|
|
skipStaticMethods=False,
|
|
):
|
|
"""
|
|
cgMethod, cgGetter and cgSetter are classes used to codegen methods,
|
|
getters and setters.
|
|
"""
|
|
self.descriptor = descriptor
|
|
self._deps = descriptor.interface.getDeps()
|
|
|
|
iface = descriptor.interface
|
|
|
|
self.methodDecls = []
|
|
|
|
def appendMethod(m, isConstructor=False):
|
|
sigs = m.signatures()
|
|
for s in sigs[:-1]:
|
|
# Don't put a blank line after overloads, until we
|
|
# get to the last one.
|
|
self.methodDecls.append(
|
|
cgMethod(descriptor, m, s, isConstructor, breakAfter=False)
|
|
)
|
|
self.methodDecls.append(cgMethod(descriptor, m, sigs[-1], isConstructor))
|
|
|
|
if iface.ctor():
|
|
appendMethod(iface.ctor(), isConstructor=True)
|
|
for n in iface.namedConstructors:
|
|
appendMethod(n, isConstructor=True)
|
|
for m in iface.members:
|
|
if m.isMethod():
|
|
if m.isIdentifierLess():
|
|
continue
|
|
if m.isMaplikeOrSetlikeOrIterableMethod():
|
|
# Handled by generated code already
|
|
continue
|
|
if not m.isStatic() or not skipStaticMethods:
|
|
appendMethod(m)
|
|
elif m.isAttr():
|
|
if m.isMaplikeOrSetlikeAttr():
|
|
# Handled by generated code already
|
|
continue
|
|
self.methodDecls.append(cgGetter(descriptor, m))
|
|
if not m.readonly:
|
|
self.methodDecls.append(cgSetter(descriptor, m))
|
|
|
|
# Now do the special operations
|
|
def appendSpecialOperation(name, op):
|
|
if op is None:
|
|
return
|
|
assert len(op.signatures()) == 1
|
|
returnType, args = op.signatures()[0]
|
|
# Make a copy of the args, since we plan to modify them.
|
|
args = list(args)
|
|
if op.isGetter() or op.isDeleter():
|
|
# This is a total hack. The '&' belongs with the
|
|
# type, not the name! But it works, and is simpler
|
|
# than trying to somehow make this pretty.
|
|
args.append(
|
|
FakeArgument(
|
|
BuiltinTypes[IDLBuiltinType.Types.boolean], op, name="&found"
|
|
)
|
|
)
|
|
if name == "Stringifier":
|
|
if op.isIdentifierLess():
|
|
# XXXbz I wish we were consistent about our renaming here.
|
|
name = "Stringify"
|
|
else:
|
|
# We already added this method
|
|
return
|
|
if name == "LegacyCaller":
|
|
if op.isIdentifierLess():
|
|
# XXXbz I wish we were consistent about our renaming here.
|
|
name = "LegacyCall"
|
|
else:
|
|
# We already added this method
|
|
return
|
|
self.methodDecls.append(
|
|
CGNativeMember(
|
|
descriptor,
|
|
op,
|
|
name,
|
|
(returnType, args),
|
|
descriptor.getExtendedAttributes(op),
|
|
)
|
|
)
|
|
|
|
# Sort things by name so we get stable ordering in the output.
|
|
ops = sorted(descriptor.operations.items(), key=lambda x: x[0])
|
|
for name, op in ops:
|
|
appendSpecialOperation(name, op)
|
|
# If we support indexed properties, then we need a Length()
|
|
# method so we know which indices are supported.
|
|
if descriptor.supportsIndexedProperties():
|
|
# But we don't need it if we already have an infallible
|
|
# "length" attribute, which we often do.
|
|
haveLengthAttr = any(
|
|
m
|
|
for m in iface.members
|
|
if m.isAttr()
|
|
and CGSpecializedGetter.makeNativeName(descriptor, m) == "Length"
|
|
)
|
|
if not haveLengthAttr:
|
|
self.methodDecls.append(
|
|
CGNativeMember(
|
|
descriptor,
|
|
FakeMember(),
|
|
"Length",
|
|
(BuiltinTypes[IDLBuiltinType.Types.unsigned_long], []),
|
|
{"infallible": True},
|
|
)
|
|
)
|
|
# And if we support named properties we need to be able to
|
|
# enumerate the supported names.
|
|
if descriptor.supportsNamedProperties():
|
|
self.methodDecls.append(
|
|
CGNativeMember(
|
|
descriptor,
|
|
FakeMember(),
|
|
"GetSupportedNames",
|
|
(
|
|
IDLSequenceType(
|
|
None, BuiltinTypes[IDLBuiltinType.Types.domstring]
|
|
),
|
|
[],
|
|
),
|
|
{"infallible": True},
|
|
)
|
|
)
|
|
|
|
if descriptor.concrete:
|
|
wrapArgs = [
|
|
Argument("JSContext*", "aCx"),
|
|
Argument("JS::Handle<JSObject*>", "aGivenProto"),
|
|
]
|
|
if not descriptor.wrapperCache:
|
|
wrapReturnType = "bool"
|
|
wrapArgs.append(Argument("JS::MutableHandle<JSObject*>", "aReflector"))
|
|
else:
|
|
wrapReturnType = "JSObject*"
|
|
self.methodDecls.insert(
|
|
0,
|
|
ClassMethod(
|
|
wrapMethodName,
|
|
wrapReturnType,
|
|
wrapArgs,
|
|
virtual=descriptor.wrapperCache,
|
|
breakAfterReturnDecl=" ",
|
|
override=descriptor.wrapperCache,
|
|
body=self.getWrapObjectBody(),
|
|
),
|
|
)
|
|
if descriptor.hasCEReactions():
|
|
self.methodDecls.insert(
|
|
0,
|
|
ClassMethod(
|
|
"GetDocGroup",
|
|
"DocGroup*",
|
|
[],
|
|
const=True,
|
|
breakAfterReturnDecl=" ",
|
|
body=self.getGetDocGroupBody(),
|
|
),
|
|
)
|
|
if wantGetParent:
|
|
self.methodDecls.insert(
|
|
0,
|
|
ClassMethod(
|
|
"GetParentObject",
|
|
self.getGetParentObjectReturnType(),
|
|
[],
|
|
const=True,
|
|
breakAfterReturnDecl=" ",
|
|
body=self.getGetParentObjectBody(),
|
|
),
|
|
)
|
|
|
|
# Invoke CGClass.__init__ in any subclasses afterwards to do the actual codegen.
|
|
|
|
def getWrapObjectBody(self):
|
|
return None
|
|
|
|
def getGetParentObjectReturnType(self):
|
|
# The lack of newline before the end of the string is on purpose.
|
|
return dedent(
|
|
"""
|
|
// This should return something that eventually allows finding a
|
|
// path to the global this object is associated with. Most simply,
|
|
// returning an actual global works.
|
|
nsIGlobalObject*"""
|
|
)
|
|
|
|
def getGetParentObjectBody(self):
|
|
return None
|
|
|
|
def getGetDocGroupBody(self):
|
|
return None
|
|
|
|
def deps(self):
|
|
return self._deps
|
|
|
|
|
|
class CGExampleClass(CGBindingImplClass):
|
|
"""
|
|
Codegen for the actual example class implementation for this descriptor
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
CGBindingImplClass.__init__(
|
|
self,
|
|
descriptor,
|
|
CGExampleMethod,
|
|
CGExampleGetter,
|
|
CGExampleSetter,
|
|
wantGetParent=descriptor.wrapperCache,
|
|
)
|
|
|
|
self.parentIface = descriptor.interface.parent
|
|
if self.parentIface:
|
|
self.parentDesc = descriptor.getDescriptor(self.parentIface.identifier.name)
|
|
bases = [ClassBase(self.nativeLeafName(self.parentDesc))]
|
|
else:
|
|
bases = [
|
|
ClassBase(
|
|
"nsISupports /* or NonRefcountedDOMObject if this is a non-refcounted object */"
|
|
)
|
|
]
|
|
if descriptor.wrapperCache:
|
|
bases.append(
|
|
ClassBase(
|
|
"nsWrapperCache /* Change wrapperCache in the binding configuration if you don't want this */"
|
|
)
|
|
)
|
|
|
|
destructorVisibility = "protected"
|
|
if self.parentIface:
|
|
extradeclarations = (
|
|
"public:\n"
|
|
" NS_DECL_ISUPPORTS_INHERITED\n"
|
|
" NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(%s, %s)\n"
|
|
"\n"
|
|
% (
|
|
self.nativeLeafName(descriptor),
|
|
self.nativeLeafName(self.parentDesc),
|
|
)
|
|
)
|
|
else:
|
|
extradeclarations = (
|
|
"public:\n"
|
|
" NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n"
|
|
" NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(%s)\n"
|
|
"\n" % self.nativeLeafName(descriptor)
|
|
)
|
|
|
|
if descriptor.interface.hasChildInterfaces():
|
|
decorators = ""
|
|
else:
|
|
decorators = "final"
|
|
|
|
CGClass.__init__(
|
|
self,
|
|
self.nativeLeafName(descriptor),
|
|
bases=bases,
|
|
constructors=[ClassConstructor([], visibility="public")],
|
|
destructor=ClassDestructor(visibility=destructorVisibility),
|
|
methods=self.methodDecls,
|
|
decorators=decorators,
|
|
extradeclarations=extradeclarations,
|
|
)
|
|
|
|
def define(self):
|
|
# Just override CGClass and do our own thing
|
|
nativeType = self.nativeLeafName(self.descriptor)
|
|
|
|
ctordtor = fill(
|
|
"""
|
|
${nativeType}::${nativeType}()
|
|
{
|
|
// Add |MOZ_COUNT_CTOR(${nativeType});| for a non-refcounted object.
|
|
}
|
|
|
|
${nativeType}::~${nativeType}()
|
|
{
|
|
// Add |MOZ_COUNT_DTOR(${nativeType});| for a non-refcounted object.
|
|
}
|
|
""",
|
|
nativeType=nativeType,
|
|
)
|
|
|
|
if self.parentIface:
|
|
ccImpl = fill(
|
|
"""
|
|
|
|
// Only needed for refcounted objects.
|
|
# error "If you don't have members that need cycle collection,
|
|
# then remove all the cycle collection bits from this
|
|
# implementation and the corresponding header. If you do, you
|
|
# want NS_IMPL_CYCLE_COLLECTION_INHERITED(${nativeType},
|
|
# ${parentType}, your, members, here)"
|
|
NS_IMPL_ADDREF_INHERITED(${nativeType}, ${parentType})
|
|
NS_IMPL_RELEASE_INHERITED(${nativeType}, ${parentType})
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType})
|
|
NS_INTERFACE_MAP_END_INHERITING(${parentType})
|
|
|
|
""",
|
|
nativeType=nativeType,
|
|
parentType=self.nativeLeafName(self.parentDesc),
|
|
)
|
|
else:
|
|
ccImpl = fill(
|
|
"""
|
|
|
|
// Only needed for refcounted objects.
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(${nativeType})
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(${nativeType})
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(${nativeType})
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType})
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
""",
|
|
nativeType=nativeType,
|
|
)
|
|
|
|
classImpl = ccImpl + ctordtor + "\n"
|
|
if self.descriptor.concrete:
|
|
if self.descriptor.wrapperCache:
|
|
reflectorArg = ""
|
|
reflectorPassArg = ""
|
|
returnType = "JSObject*"
|
|
else:
|
|
reflectorArg = ", JS::MutableHandle<JSObject*> aReflector"
|
|
reflectorPassArg = ", aReflector"
|
|
returnType = "bool"
|
|
classImpl += fill(
|
|
"""
|
|
${returnType}
|
|
${nativeType}::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto${reflectorArg})
|
|
{
|
|
return ${ifaceName}_Binding::Wrap(aCx, this, aGivenProto${reflectorPassArg});
|
|
}
|
|
|
|
""",
|
|
returnType=returnType,
|
|
nativeType=nativeType,
|
|
reflectorArg=reflectorArg,
|
|
ifaceName=self.descriptor.name,
|
|
reflectorPassArg=reflectorPassArg,
|
|
)
|
|
|
|
return classImpl
|
|
|
|
@staticmethod
|
|
def nativeLeafName(descriptor):
|
|
return descriptor.nativeType.split("::")[-1]
|
|
|
|
|
|
class CGExampleRoot(CGThing):
|
|
"""
|
|
Root codegen class for example implementation generation. Instantiate the
|
|
class and call declare or define to generate header or cpp code,
|
|
respectively.
|
|
"""
|
|
|
|
def __init__(self, config, interfaceName):
|
|
descriptor = config.getDescriptor(interfaceName)
|
|
|
|
self.root = CGWrapper(CGExampleClass(descriptor), pre="\n", post="\n")
|
|
|
|
self.root = CGNamespace.build(["mozilla", "dom"], self.root)
|
|
|
|
builder = ForwardDeclarationBuilder()
|
|
if descriptor.hasCEReactions():
|
|
builder.addInMozillaDom("DocGroup")
|
|
for member in descriptor.interface.members:
|
|
if not member.isAttr() and not member.isMethod():
|
|
continue
|
|
if member.isStatic():
|
|
builder.addInMozillaDom("GlobalObject")
|
|
if member.isAttr():
|
|
if not member.isMaplikeOrSetlikeAttr():
|
|
builder.forwardDeclareForType(member.type, config)
|
|
else:
|
|
assert member.isMethod()
|
|
if not member.isMaplikeOrSetlikeOrIterableMethod():
|
|
for sig in member.signatures():
|
|
builder.forwardDeclareForType(sig[0], config)
|
|
for arg in sig[1]:
|
|
builder.forwardDeclareForType(arg.type, config)
|
|
|
|
self.root = CGList([builder.build(), self.root], "\n")
|
|
|
|
# Throw in our #includes
|
|
self.root = CGHeaders(
|
|
[],
|
|
[],
|
|
[],
|
|
[],
|
|
[
|
|
"nsWrapperCache.h",
|
|
"nsCycleCollectionParticipant.h",
|
|
"mozilla/Attributes.h",
|
|
"mozilla/ErrorResult.h",
|
|
"mozilla/dom/BindingDeclarations.h",
|
|
"js/TypeDecls.h",
|
|
],
|
|
[
|
|
"mozilla/dom/%s.h" % interfaceName,
|
|
(
|
|
"mozilla/dom/%s"
|
|
% CGHeaders.getDeclarationFilename(descriptor.interface)
|
|
),
|
|
],
|
|
"",
|
|
self.root,
|
|
)
|
|
|
|
# And now some include guards
|
|
self.root = CGIncludeGuard(interfaceName, self.root)
|
|
|
|
# And our license block comes before everything else
|
|
self.root = CGWrapper(
|
|
self.root,
|
|
pre=dedent(
|
|
"""
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* 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/. */
|
|
|
|
"""
|
|
),
|
|
)
|
|
|
|
def declare(self):
|
|
return self.root.declare()
|
|
|
|
def define(self):
|
|
return self.root.define()
|
|
|
|
|
|
def jsImplName(name):
|
|
return name + "JSImpl"
|
|
|
|
|
|
class CGJSImplMember(CGNativeMember):
|
|
"""
|
|
Base class for generating code for the members of the implementation class
|
|
for a JS-implemented WebIDL interface.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
descriptorProvider,
|
|
member,
|
|
name,
|
|
signature,
|
|
extendedAttrs,
|
|
breakAfter=True,
|
|
passJSBitsAsNeeded=True,
|
|
visibility="public",
|
|
variadicIsSequence=False,
|
|
virtual=False,
|
|
override=False,
|
|
):
|
|
CGNativeMember.__init__(
|
|
self,
|
|
descriptorProvider,
|
|
member,
|
|
name,
|
|
signature,
|
|
extendedAttrs,
|
|
breakAfter=breakAfter,
|
|
passJSBitsAsNeeded=passJSBitsAsNeeded,
|
|
visibility=visibility,
|
|
variadicIsSequence=variadicIsSequence,
|
|
virtual=virtual,
|
|
override=override,
|
|
)
|
|
self.body = self.getImpl()
|
|
|
|
def getArgs(self, returnType, argList):
|
|
args = CGNativeMember.getArgs(self, returnType, argList)
|
|
args.append(Argument("JS::Realm*", "aRealm", "nullptr"))
|
|
return args
|
|
|
|
|
|
class CGJSImplMethod(CGJSImplMember):
|
|
"""
|
|
Class for generating code for the methods for a JS-implemented WebIDL
|
|
interface.
|
|
"""
|
|
|
|
def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
|
|
self.signature = signature
|
|
self.descriptor = descriptor
|
|
self.isConstructor = isConstructor
|
|
CGJSImplMember.__init__(
|
|
self,
|
|
descriptor,
|
|
method,
|
|
CGSpecializedMethod.makeNativeName(descriptor, method),
|
|
signature,
|
|
descriptor.getExtendedAttributes(method),
|
|
breakAfter=breakAfter,
|
|
variadicIsSequence=True,
|
|
passJSBitsAsNeeded=False,
|
|
)
|
|
|
|
def getArgs(self, returnType, argList):
|
|
if self.isConstructor:
|
|
# Skip the JS::Compartment bits for constructors; it's handled
|
|
# manually in getImpl. But we do need our aGivenProto argument. We
|
|
# allow it to be omitted if the default proto is desired.
|
|
return CGNativeMember.getArgs(self, returnType, argList) + [
|
|
Argument("JS::Handle<JSObject*>", "aGivenProto", "nullptr")
|
|
]
|
|
return CGJSImplMember.getArgs(self, returnType, argList)
|
|
|
|
def getImpl(self):
|
|
args = self.getArgs(self.signature[0], self.signature[1])
|
|
if not self.isConstructor:
|
|
return "return mImpl->%s(%s);\n" % (
|
|
self.name,
|
|
", ".join(arg.name for arg in args),
|
|
)
|
|
|
|
assert self.descriptor.interface.isJSImplemented()
|
|
if self.name != "Constructor":
|
|
raise TypeError(
|
|
"Named constructors are not supported for JS implemented WebIDL. See bug 851287."
|
|
)
|
|
if len(self.signature[1]) != 0:
|
|
# The first two arguments to the constructor implementation are not
|
|
# arguments to the WebIDL constructor, so don't pass them to
|
|
# __Init(). The last argument is the prototype we're supposed to
|
|
# use, and shouldn't get passed to __Init() either.
|
|
assert args[0].argType == "const GlobalObject&"
|
|
assert args[1].argType == "JSContext*"
|
|
assert args[-1].argType == "JS::Handle<JSObject*>"
|
|
assert args[-1].name == "aGivenProto"
|
|
constructorArgs = [arg.name for arg in args[2:-1]]
|
|
constructorArgs.append("js::GetNonCCWObjectRealm(scopeObj)")
|
|
initCall = fill(
|
|
"""
|
|
// Wrap the object before calling __Init so that __DOM_IMPL__ is available.
|
|
JS::Rooted<JSObject*> scopeObj(cx, global.Get());
|
|
MOZ_ASSERT(js::IsObjectInContextCompartment(scopeObj, cx));
|
|
JS::Rooted<JS::Value> wrappedVal(cx);
|
|
if (!GetOrCreateDOMReflector(cx, impl, &wrappedVal, aGivenProto)) {
|
|
MOZ_ASSERT(JS_IsExceptionPending(cx));
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return nullptr;
|
|
}
|
|
// Initialize the object with the constructor arguments.
|
|
impl->mImpl->__Init(${args});
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
""",
|
|
args=", ".join(constructorArgs),
|
|
)
|
|
else:
|
|
initCall = ""
|
|
return fill(
|
|
"""
|
|
RefPtr<${implClass}> impl =
|
|
ConstructJSImplementation<${implClass}>("${contractId}", global, aRv);
|
|
if (aRv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
$*{initCall}
|
|
return impl.forget();
|
|
""",
|
|
contractId=self.descriptor.interface.getJSImplementation(),
|
|
implClass=self.descriptor.name,
|
|
initCall=initCall,
|
|
)
|
|
|
|
|
|
# We're always fallible
|
|
def callbackGetterName(attr, descriptor):
|
|
return "Get" + MakeNativeName(descriptor.binaryNameFor(attr.identifier.name))
|
|
|
|
|
|
def callbackSetterName(attr, descriptor):
|
|
return "Set" + MakeNativeName(descriptor.binaryNameFor(attr.identifier.name))
|
|
|
|
|
|
class CGJSImplGetter(CGJSImplMember):
|
|
"""
|
|
Class for generating code for the getters of attributes for a JS-implemented
|
|
WebIDL interface.
|
|
"""
|
|
|
|
def __init__(self, descriptor, attr):
|
|
CGJSImplMember.__init__(
|
|
self,
|
|
descriptor,
|
|
attr,
|
|
CGSpecializedGetter.makeNativeName(descriptor, attr),
|
|
(attr.type, []),
|
|
descriptor.getExtendedAttributes(attr, getter=True),
|
|
passJSBitsAsNeeded=False,
|
|
)
|
|
|
|
def getImpl(self):
|
|
callbackArgs = [arg.name for arg in self.getArgs(self.member.type, [])]
|
|
return "return mImpl->%s(%s);\n" % (
|
|
callbackGetterName(self.member, self.descriptorProvider),
|
|
", ".join(callbackArgs),
|
|
)
|
|
|
|
|
|
class CGJSImplSetter(CGJSImplMember):
|
|
"""
|
|
Class for generating code for the setters of attributes for a JS-implemented
|
|
WebIDL interface.
|
|
"""
|
|
|
|
def __init__(self, descriptor, attr):
|
|
CGJSImplMember.__init__(
|
|
self,
|
|
descriptor,
|
|
attr,
|
|
CGSpecializedSetter.makeNativeName(descriptor, attr),
|
|
(BuiltinTypes[IDLBuiltinType.Types.void], [FakeArgument(attr.type, attr)]),
|
|
descriptor.getExtendedAttributes(attr, setter=True),
|
|
passJSBitsAsNeeded=False,
|
|
)
|
|
|
|
def getImpl(self):
|
|
callbackArgs = [
|
|
arg.name
|
|
for arg in self.getArgs(
|
|
BuiltinTypes[IDLBuiltinType.Types.void],
|
|
[FakeArgument(self.member.type, self.member)],
|
|
)
|
|
]
|
|
return "mImpl->%s(%s);\n" % (
|
|
callbackSetterName(self.member, self.descriptorProvider),
|
|
", ".join(callbackArgs),
|
|
)
|
|
|
|
|
|
class CGJSImplClass(CGBindingImplClass):
|
|
def __init__(self, descriptor):
|
|
CGBindingImplClass.__init__(
|
|
self,
|
|
descriptor,
|
|
CGJSImplMethod,
|
|
CGJSImplGetter,
|
|
CGJSImplSetter,
|
|
skipStaticMethods=True,
|
|
)
|
|
|
|
if descriptor.interface.parent:
|
|
parentClass = descriptor.getDescriptor(
|
|
descriptor.interface.parent.identifier.name
|
|
).jsImplParent
|
|
baseClasses = [ClassBase(parentClass)]
|
|
isupportsDecl = "NS_DECL_ISUPPORTS_INHERITED\n"
|
|
ccDecl = "NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(%s, %s)\n" % (
|
|
descriptor.name,
|
|
parentClass,
|
|
)
|
|
constructorBody = dedent(
|
|
"""
|
|
// Make sure we're an nsWrapperCache already
|
|
MOZ_ASSERT(static_cast<nsWrapperCache*>(this));
|
|
"""
|
|
)
|
|
extradefinitions = fill(
|
|
"""
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(${ifaceName}, ${parentClass}, mImpl, mParent)
|
|
NS_IMPL_ADDREF_INHERITED(${ifaceName}, ${parentClass})
|
|
NS_IMPL_RELEASE_INHERITED(${ifaceName}, ${parentClass})
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${ifaceName})
|
|
NS_INTERFACE_MAP_END_INHERITING(${parentClass})
|
|
""",
|
|
ifaceName=self.descriptor.name,
|
|
parentClass=parentClass,
|
|
)
|
|
else:
|
|
baseClasses = [
|
|
ClassBase("nsSupportsWeakReference"),
|
|
ClassBase("nsWrapperCache"),
|
|
]
|
|
isupportsDecl = "NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n"
|
|
ccDecl = (
|
|
"NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(%s)\n" % descriptor.name
|
|
)
|
|
extradefinitions = fill(
|
|
"""
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(${ifaceName})
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(${ifaceName})
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
tmp->ClearWeakReferences();
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(${ifaceName})
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(${ifaceName})
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(${ifaceName})
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(${ifaceName})
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${ifaceName})
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
|
NS_INTERFACE_MAP_END
|
|
""",
|
|
ifaceName=self.descriptor.name,
|
|
)
|
|
|
|
extradeclarations = fill(
|
|
"""
|
|
public:
|
|
$*{isupportsDecl}
|
|
$*{ccDecl}
|
|
|
|
private:
|
|
RefPtr<${jsImplName}> mImpl;
|
|
nsCOMPtr<nsIGlobalObject> mParent;
|
|
|
|
""",
|
|
isupportsDecl=isupportsDecl,
|
|
ccDecl=ccDecl,
|
|
jsImplName=jsImplName(descriptor.name),
|
|
)
|
|
|
|
if descriptor.interface.getExtendedAttribute("WantsEventListenerHooks"):
|
|
# No need to do too much sanity checking here; the
|
|
# generated code will fail to compile if the methods we
|
|
# try to overrid aren't on a superclass.
|
|
self.methodDecls.extend(
|
|
self.getEventHookMethod(parentClass, "EventListenerAdded")
|
|
)
|
|
self.methodDecls.extend(
|
|
self.getEventHookMethod(parentClass, "EventListenerRemoved")
|
|
)
|
|
|
|
if descriptor.interface.hasChildInterfaces():
|
|
decorators = ""
|
|
# We need a protected virtual destructor our subclasses can use
|
|
destructor = ClassDestructor(virtual=True, visibility="protected")
|
|
else:
|
|
decorators = "final"
|
|
destructor = ClassDestructor(virtual=False, visibility="private")
|
|
|
|
baseConstructors = [
|
|
(
|
|
"mImpl(new %s(nullptr, aJSImplObject, aJSImplGlobal, /* aIncumbentGlobal = */ nullptr))"
|
|
% jsImplName(descriptor.name)
|
|
),
|
|
"mParent(aParent)",
|
|
]
|
|
parentInterface = descriptor.interface.parent
|
|
while parentInterface:
|
|
if parentInterface.isJSImplemented():
|
|
baseConstructors.insert(
|
|
0, "%s(aJSImplObject, aJSImplGlobal, aParent)" % parentClass
|
|
)
|
|
break
|
|
parentInterface = parentInterface.parent
|
|
if not parentInterface and descriptor.interface.parent:
|
|
# We only have C++ ancestors, so only pass along the window
|
|
baseConstructors.insert(0, "%s(aParent)" % parentClass)
|
|
|
|
constructor = ClassConstructor(
|
|
[
|
|
Argument("JS::Handle<JSObject*>", "aJSImplObject"),
|
|
Argument("JS::Handle<JSObject*>", "aJSImplGlobal"),
|
|
Argument("nsIGlobalObject*", "aParent"),
|
|
],
|
|
visibility="public",
|
|
baseConstructors=baseConstructors,
|
|
)
|
|
|
|
self.methodDecls.append(
|
|
ClassMethod(
|
|
"_Create",
|
|
"bool",
|
|
JSNativeArguments(),
|
|
static=True,
|
|
body=self.getCreateFromExistingBody(),
|
|
)
|
|
)
|
|
|
|
if (
|
|
descriptor.interface.isJSImplemented()
|
|
and descriptor.interface.maplikeOrSetlikeOrIterable
|
|
and descriptor.interface.maplikeOrSetlikeOrIterable.isMaplike()
|
|
):
|
|
self.methodDecls.append(
|
|
ClassMethod(
|
|
"__OnGet",
|
|
"void",
|
|
[
|
|
Argument("JS::Handle<JS::Value>", "aKey"),
|
|
Argument("JS::Handle<JS::Value>", "aValue"),
|
|
Argument("ErrorResult&", "aRv"),
|
|
],
|
|
body="mImpl->__OnGet(aKey, aValue, aRv);\n",
|
|
)
|
|
)
|
|
|
|
CGClass.__init__(
|
|
self,
|
|
descriptor.name,
|
|
bases=baseClasses,
|
|
constructors=[constructor],
|
|
destructor=destructor,
|
|
methods=self.methodDecls,
|
|
decorators=decorators,
|
|
extradeclarations=extradeclarations,
|
|
extradefinitions=extradefinitions,
|
|
)
|
|
|
|
def getWrapObjectBody(self):
|
|
return fill(
|
|
"""
|
|
JS::Rooted<JSObject*> obj(aCx, ${name}_Binding::Wrap(aCx, this, aGivenProto));
|
|
if (!obj) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Now define it on our chrome object
|
|
JSAutoRealm ar(aCx, mImpl->CallbackGlobalOrNull());
|
|
if (!JS_WrapObject(aCx, &obj)) {
|
|
return nullptr;
|
|
}
|
|
JS::Rooted<JSObject*> callback(aCx, mImpl->CallbackOrNull());
|
|
if (!JS_DefineProperty(aCx, callback, "__DOM_IMPL__", obj, 0)) {
|
|
return nullptr;
|
|
}
|
|
return obj;
|
|
""",
|
|
name=self.descriptor.name,
|
|
)
|
|
|
|
def getGetParentObjectReturnType(self):
|
|
return "nsISupports*"
|
|
|
|
def getGetParentObjectBody(self):
|
|
return "return mParent;\n"
|
|
|
|
def getGetDocGroupBody(self):
|
|
return dedent(
|
|
"""
|
|
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mParent);
|
|
if (!window) {
|
|
return nullptr;
|
|
}
|
|
return window->GetDocGroup();
|
|
"""
|
|
)
|
|
|
|
def getCreateFromExistingBody(self):
|
|
# XXXbz we could try to get parts of this (e.g. the argument
|
|
# conversions) auto-generated by somehow creating an IDLMethod and
|
|
# adding it to our interface, but we'd still need to special-case the
|
|
# implementation slightly to have it not try to forward to the JS
|
|
# object...
|
|
return fill(
|
|
"""
|
|
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
|
if (!args.requireAtLeast(cx, "${ifaceName}._create", 2)) {
|
|
return false;
|
|
}
|
|
BindingCallContext callCx(cx, "${ifaceName}._create");
|
|
if (!args[0].isObject()) {
|
|
return callCx.ThrowErrorMessage<MSG_NOT_OBJECT>("Argument 1");
|
|
}
|
|
if (!args[1].isObject()) {
|
|
return callCx.ThrowErrorMessage<MSG_NOT_OBJECT>("Argument 2");
|
|
}
|
|
|
|
// GlobalObject will go through wrappers as needed for us, and
|
|
// is simpler than the right UnwrapArg incantation.
|
|
GlobalObject global(cx, &args[0].toObject());
|
|
if (global.Failed()) {
|
|
return false;
|
|
}
|
|
nsCOMPtr<nsIGlobalObject> globalHolder = do_QueryInterface(global.GetAsSupports());
|
|
MOZ_ASSERT(globalHolder);
|
|
JS::Rooted<JSObject*> arg(cx, &args[1].toObject());
|
|
JS::Rooted<JSObject*> argGlobal(cx, JS::CurrentGlobalOrNull(cx));
|
|
RefPtr<${implName}> impl = new ${implName}(arg, argGlobal, globalHolder);
|
|
MOZ_ASSERT(js::IsObjectInContextCompartment(arg, cx));
|
|
return GetOrCreateDOMReflector(cx, impl, args.rval());
|
|
""",
|
|
ifaceName=self.descriptor.interface.identifier.name,
|
|
implName=self.descriptor.name,
|
|
)
|
|
|
|
def getEventHookMethod(self, parentClass, methodName):
|
|
body = fill(
|
|
"""
|
|
${parentClass}::${methodName}(aType);
|
|
mImpl->${methodName}(Substring(nsDependentAtomString(aType), 2), IgnoreErrors());
|
|
""",
|
|
parentClass=parentClass,
|
|
methodName=methodName,
|
|
)
|
|
return [
|
|
ClassMethod(
|
|
methodName,
|
|
"void",
|
|
[Argument("nsAtom*", "aType")],
|
|
virtual=True,
|
|
override=True,
|
|
body=body,
|
|
),
|
|
ClassUsingDeclaration(parentClass, methodName),
|
|
]
|
|
|
|
|
|
def isJSImplementedDescriptor(descriptorProvider):
|
|
return (
|
|
isinstance(descriptorProvider, Descriptor)
|
|
and descriptorProvider.interface.isJSImplemented()
|
|
)
|
|
|
|
|
|
class CGCallback(CGClass):
|
|
def __init__(
|
|
self, idlObject, descriptorProvider, baseName, methods, getters=[], setters=[]
|
|
):
|
|
self.baseName = baseName
|
|
self._deps = idlObject.getDeps()
|
|
self.idlObject = idlObject
|
|
self.name = idlObject.identifier.name
|
|
if isJSImplementedDescriptor(descriptorProvider):
|
|
self.name = jsImplName(self.name)
|
|
# For our public methods that needThisHandling we want most of the
|
|
# same args and the same return type as what CallbackMember
|
|
# generates. So we want to take advantage of all its
|
|
# CGNativeMember infrastructure, but that infrastructure can't deal
|
|
# with templates and most especially template arguments. So just
|
|
# cheat and have CallbackMember compute all those things for us.
|
|
realMethods = []
|
|
for method in methods:
|
|
if not isinstance(method, CallbackMember) or not method.needThisHandling:
|
|
realMethods.append(method)
|
|
else:
|
|
realMethods.extend(self.getMethodImpls(method))
|
|
realMethods.append(
|
|
ClassMethod(
|
|
"operator==",
|
|
"bool",
|
|
[Argument("const %s&" % self.name, "aOther")],
|
|
inline=True,
|
|
bodyInHeader=True,
|
|
const=True,
|
|
body=("return %s::operator==(aOther);\n" % baseName),
|
|
)
|
|
)
|
|
CGClass.__init__(
|
|
self,
|
|
self.name,
|
|
bases=[ClassBase(baseName)],
|
|
constructors=self.getConstructors(),
|
|
methods=realMethods + getters + setters,
|
|
)
|
|
|
|
def getConstructors(self):
|
|
if (
|
|
not self.idlObject.isInterface()
|
|
and not self.idlObject._treatNonObjectAsNull
|
|
):
|
|
body = "MOZ_ASSERT(JS::IsCallable(mCallback));\n"
|
|
else:
|
|
# Not much we can assert about it, other than not being null, and
|
|
# CallbackObject does that already.
|
|
body = ""
|
|
return [
|
|
ClassConstructor(
|
|
[
|
|
Argument("JSContext*", "aCx"),
|
|
Argument("JS::Handle<JSObject*>", "aCallback"),
|
|
Argument("JS::Handle<JSObject*>", "aCallbackGlobal"),
|
|
Argument("nsIGlobalObject*", "aIncumbentGlobal"),
|
|
],
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
explicit=True,
|
|
baseConstructors=[
|
|
"%s(aCx, aCallback, aCallbackGlobal, aIncumbentGlobal)"
|
|
% self.baseName,
|
|
],
|
|
body=body,
|
|
),
|
|
ClassConstructor(
|
|
[
|
|
Argument("JSObject*", "aCallback"),
|
|
Argument("JSObject*", "aCallbackGlobal"),
|
|
Argument("const FastCallbackConstructor&", ""),
|
|
],
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
explicit=True,
|
|
baseConstructors=[
|
|
"%s(aCallback, aCallbackGlobal, FastCallbackConstructor())"
|
|
% self.baseName,
|
|
],
|
|
body=body,
|
|
),
|
|
ClassConstructor(
|
|
[
|
|
Argument("JSObject*", "aCallback"),
|
|
Argument("JSObject*", "aCallbackGlobal"),
|
|
Argument("JSObject*", "aAsyncStack"),
|
|
Argument("nsIGlobalObject*", "aIncumbentGlobal"),
|
|
],
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
explicit=True,
|
|
baseConstructors=[
|
|
"%s(aCallback, aCallbackGlobal, aAsyncStack, aIncumbentGlobal)"
|
|
% self.baseName,
|
|
],
|
|
body=body,
|
|
),
|
|
]
|
|
|
|
def getMethodImpls(self, method):
|
|
assert method.needThisHandling
|
|
args = list(method.args)
|
|
# Strip out the BindingCallContext&/JSObject* args
|
|
# that got added.
|
|
assert args[0].name == "cx" and args[0].argType == "BindingCallContext&"
|
|
assert args[1].name == "aThisVal" and args[1].argType == "JS::Handle<JS::Value>"
|
|
args = args[2:]
|
|
|
|
# Now remember which index the ErrorResult argument is at;
|
|
# we'll need this below.
|
|
assert args[-1].name == "aRv" and args[-1].argType == "ErrorResult&"
|
|
rvIndex = len(args) - 1
|
|
assert rvIndex >= 0
|
|
|
|
# Record the names of all the arguments, so we can use them when we call
|
|
# the private method.
|
|
argnames = [arg.name for arg in args]
|
|
argnamesWithThis = ["s.GetCallContext()", "thisValJS"] + argnames
|
|
argnamesWithoutThis = [
|
|
"s.GetCallContext()",
|
|
"JS::UndefinedHandleValue",
|
|
] + argnames
|
|
# Now that we've recorded the argnames for our call to our private
|
|
# method, insert our optional argument for the execution reason.
|
|
args.append(Argument("const char*", "aExecutionReason", "nullptr"))
|
|
|
|
# Make copies of the arg list for the two "without rv" overloads. Note
|
|
# that those don't need aExceptionHandling or aRealm arguments because
|
|
# those would make not sense anyway: the only sane thing to do with
|
|
# exceptions in the "without rv" cases is to report them.
|
|
argsWithoutRv = list(args)
|
|
argsWithoutRv.pop(rvIndex)
|
|
argsWithoutThisAndRv = list(argsWithoutRv)
|
|
|
|
# Add the potional argument for deciding whether the CallSetup should
|
|
# re-throw exceptions on aRv.
|
|
args.append(
|
|
Argument("ExceptionHandling", "aExceptionHandling", "eReportExceptions")
|
|
)
|
|
# And the argument for communicating when exceptions should really be
|
|
# rethrown. In particular, even when aExceptionHandling is
|
|
# eRethrowExceptions they won't get rethrown if aRealm is provided
|
|
# and its principal doesn't subsume either the callback or the
|
|
# exception.
|
|
args.append(Argument("JS::Realm*", "aRealm", "nullptr"))
|
|
# And now insert our template argument.
|
|
argsWithoutThis = list(args)
|
|
args.insert(0, Argument("const T&", "thisVal"))
|
|
argsWithoutRv.insert(0, Argument("const T&", "thisVal"))
|
|
|
|
argnamesWithoutThisAndRv = [arg.name for arg in argsWithoutThisAndRv]
|
|
argnamesWithoutThisAndRv.insert(rvIndex, "IgnoreErrors()")
|
|
# If we just leave things like that, and have no actual arguments in the
|
|
# IDL, we will end up trying to call the templated "without rv" overload
|
|
# with "rv" as the thisVal. That's no good. So explicitly append the
|
|
# aExceptionHandling and aRealm values we need to end up matching the
|
|
# signature of our non-templated "with rv" overload.
|
|
argnamesWithoutThisAndRv.extend(["eReportExceptions", "nullptr"])
|
|
|
|
argnamesWithoutRv = [arg.name for arg in argsWithoutRv]
|
|
# Note that we need to insert at rvIndex + 1, since we inserted a
|
|
# thisVal arg at the start.
|
|
argnamesWithoutRv.insert(rvIndex + 1, "IgnoreErrors()")
|
|
|
|
errorReturn = method.getDefaultRetval()
|
|
|
|
setupCall = fill(
|
|
"""
|
|
MOZ_ASSERT(!aRv.Failed(), "Don't pass an already-failed ErrorResult to a callback!");
|
|
if (!aExecutionReason) {
|
|
aExecutionReason = "${executionReason}";
|
|
}
|
|
CallSetup s(this, aRv, aExecutionReason, aExceptionHandling, aRealm);
|
|
if (!s.GetContext()) {
|
|
MOZ_ASSERT(aRv.Failed());
|
|
return${errorReturn};
|
|
}
|
|
""",
|
|
errorReturn=errorReturn,
|
|
executionReason=method.getPrettyName(),
|
|
)
|
|
|
|
bodyWithThis = fill(
|
|
"""
|
|
$*{setupCall}
|
|
JS::Rooted<JS::Value> thisValJS(s.GetContext());
|
|
if (!ToJSValue(s.GetContext(), thisVal, &thisValJS)) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return${errorReturn};
|
|
}
|
|
return ${methodName}(${callArgs});
|
|
""",
|
|
setupCall=setupCall,
|
|
errorReturn=errorReturn,
|
|
methodName=method.name,
|
|
callArgs=", ".join(argnamesWithThis),
|
|
)
|
|
bodyWithoutThis = fill(
|
|
"""
|
|
$*{setupCall}
|
|
return ${methodName}(${callArgs});
|
|
""",
|
|
setupCall=setupCall,
|
|
errorReturn=errorReturn,
|
|
methodName=method.name,
|
|
callArgs=", ".join(argnamesWithoutThis),
|
|
)
|
|
bodyWithThisWithoutRv = fill(
|
|
"""
|
|
return ${methodName}(${callArgs});
|
|
""",
|
|
methodName=method.name,
|
|
callArgs=", ".join(argnamesWithoutRv),
|
|
)
|
|
bodyWithoutThisAndRv = fill(
|
|
"""
|
|
return ${methodName}(${callArgs});
|
|
""",
|
|
methodName=method.name,
|
|
callArgs=", ".join(argnamesWithoutThisAndRv),
|
|
)
|
|
|
|
return [
|
|
ClassMethod(
|
|
method.name,
|
|
method.returnType,
|
|
args,
|
|
bodyInHeader=True,
|
|
templateArgs=["typename T"],
|
|
body=bodyWithThis,
|
|
canRunScript=method.canRunScript,
|
|
),
|
|
ClassMethod(
|
|
method.name,
|
|
method.returnType,
|
|
argsWithoutThis,
|
|
bodyInHeader=True,
|
|
body=bodyWithoutThis,
|
|
canRunScript=method.canRunScript,
|
|
),
|
|
ClassMethod(
|
|
method.name,
|
|
method.returnType,
|
|
argsWithoutRv,
|
|
bodyInHeader=True,
|
|
templateArgs=["typename T"],
|
|
body=bodyWithThisWithoutRv,
|
|
canRunScript=method.canRunScript,
|
|
),
|
|
ClassMethod(
|
|
method.name,
|
|
method.returnType,
|
|
argsWithoutThisAndRv,
|
|
bodyInHeader=True,
|
|
body=bodyWithoutThisAndRv,
|
|
canRunScript=method.canRunScript,
|
|
),
|
|
method,
|
|
]
|
|
|
|
def deps(self):
|
|
return self._deps
|
|
|
|
|
|
class CGCallbackFunction(CGCallback):
|
|
def __init__(self, callback, descriptorProvider):
|
|
self.callback = callback
|
|
if callback.isConstructor():
|
|
methods = [ConstructCallback(callback, descriptorProvider)]
|
|
else:
|
|
methods = [CallCallback(callback, descriptorProvider)]
|
|
CGCallback.__init__(
|
|
self, callback, descriptorProvider, "CallbackFunction", methods
|
|
)
|
|
|
|
def getConstructors(self):
|
|
return CGCallback.getConstructors(self) + [
|
|
ClassConstructor(
|
|
[Argument("CallbackFunction*", "aOther")],
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
explicit=True,
|
|
baseConstructors=["CallbackFunction(aOther)"],
|
|
)
|
|
]
|
|
|
|
|
|
class CGFastCallback(CGClass):
|
|
def __init__(self, idlObject):
|
|
self._deps = idlObject.getDeps()
|
|
baseName = idlObject.identifier.name
|
|
constructor = ClassConstructor(
|
|
[
|
|
Argument("JSObject*", "aCallback"),
|
|
Argument("JSObject*", "aCallbackGlobal"),
|
|
],
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
explicit=True,
|
|
baseConstructors=[
|
|
"%s(aCallback, aCallbackGlobal, FastCallbackConstructor())" % baseName,
|
|
],
|
|
body="",
|
|
)
|
|
|
|
traceMethod = ClassMethod(
|
|
"Trace",
|
|
"void",
|
|
[Argument("JSTracer*", "aTracer")],
|
|
inline=True,
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
body="%s::Trace(aTracer);\n" % baseName,
|
|
)
|
|
holdMethod = ClassMethod(
|
|
"FinishSlowJSInitIfMoreThanOneOwner",
|
|
"void",
|
|
[Argument("JSContext*", "aCx")],
|
|
inline=True,
|
|
bodyInHeader=True,
|
|
visibility="public",
|
|
body=("%s::FinishSlowJSInitIfMoreThanOneOwner(aCx);\n" % baseName),
|
|
)
|
|
|
|
CGClass.__init__(
|
|
self,
|
|
"Fast%s" % baseName,
|
|
bases=[ClassBase(baseName)],
|
|
constructors=[constructor],
|
|
methods=[traceMethod, holdMethod],
|
|
)
|
|
|
|
def deps(self):
|
|
return self._deps
|
|
|
|
|
|
class CGCallbackInterface(CGCallback):
|
|
def __init__(self, descriptor, spiderMonkeyInterfacesAreStructs=False):
|
|
iface = descriptor.interface
|
|
attrs = [
|
|
m
|
|
for m in iface.members
|
|
if (
|
|
m.isAttr()
|
|
and not m.isStatic()
|
|
and (not m.isMaplikeOrSetlikeAttr() or not iface.isJSImplemented())
|
|
)
|
|
]
|
|
getters = [
|
|
CallbackGetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
|
|
for a in attrs
|
|
]
|
|
setters = [
|
|
CallbackSetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
|
|
for a in attrs
|
|
if not a.readonly
|
|
]
|
|
methods = [
|
|
m
|
|
for m in iface.members
|
|
if (
|
|
m.isMethod()
|
|
and not m.isStatic()
|
|
and not m.isIdentifierLess()
|
|
and (
|
|
not m.isMaplikeOrSetlikeOrIterableMethod()
|
|
or not iface.isJSImplemented()
|
|
)
|
|
)
|
|
]
|
|
methods = [
|
|
CallbackOperation(m, sig, descriptor, spiderMonkeyInterfacesAreStructs)
|
|
for m in methods
|
|
for sig in m.signatures()
|
|
]
|
|
|
|
needInitId = False
|
|
if iface.isJSImplemented() and iface.ctor():
|
|
sigs = descriptor.interface.ctor().signatures()
|
|
if len(sigs) != 1:
|
|
raise TypeError("We only handle one constructor. See bug 869268.")
|
|
methods.append(CGJSImplInitOperation(sigs[0], descriptor))
|
|
needInitId = True
|
|
|
|
needOnGetId = False
|
|
if (
|
|
iface.isJSImplemented()
|
|
and iface.maplikeOrSetlikeOrIterable
|
|
and iface.maplikeOrSetlikeOrIterable.isMaplike()
|
|
):
|
|
methods.append(CGJSImplOnGetOperation(descriptor))
|
|
needOnGetId = True
|
|
|
|
idlist = [
|
|
descriptor.binaryNameFor(m.identifier.name)
|
|
for m in iface.members
|
|
if m.isAttr() or m.isMethod()
|
|
]
|
|
if needInitId:
|
|
idlist.append("__init")
|
|
if needOnGetId:
|
|
idlist.append("__onget")
|
|
|
|
if iface.isJSImplemented() and iface.getExtendedAttribute(
|
|
"WantsEventListenerHooks"
|
|
):
|
|
methods.append(CGJSImplEventHookOperation(descriptor, "eventListenerAdded"))
|
|
methods.append(
|
|
CGJSImplEventHookOperation(descriptor, "eventListenerRemoved")
|
|
)
|
|
idlist.append("eventListenerAdded")
|
|
idlist.append("eventListenerRemoved")
|
|
|
|
if len(idlist) != 0:
|
|
methods.append(initIdsClassMethod(idlist, iface.identifier.name + "Atoms"))
|
|
CGCallback.__init__(
|
|
self,
|
|
iface,
|
|
descriptor,
|
|
"CallbackInterface",
|
|
methods,
|
|
getters=getters,
|
|
setters=setters,
|
|
)
|
|
|
|
|
|
class FakeMember:
|
|
def __init__(self, name=None):
|
|
if name is not None:
|
|
self.identifier = FakeIdentifier(name)
|
|
|
|
def isStatic(self):
|
|
return False
|
|
|
|
def isAttr(self):
|
|
return False
|
|
|
|
def isMethod(self):
|
|
return False
|
|
|
|
def getExtendedAttribute(self, name):
|
|
# Claim to be a [NewObject] so we can avoid the "return a raw pointer"
|
|
# comments CGNativeMember codegen would otherwise stick in.
|
|
if name == "NewObject":
|
|
return True
|
|
return None
|
|
|
|
|
|
class CallbackMember(CGNativeMember):
|
|
# XXXbz It's OK to use CallbackKnownNotGray for wrapScope because
|
|
# CallSetup already handled the unmark-gray bits for us. we don't have
|
|
# anything better to use for 'obj', really...
|
|
def __init__(
|
|
self,
|
|
sig,
|
|
name,
|
|
descriptorProvider,
|
|
needThisHandling,
|
|
rethrowContentException=False,
|
|
spiderMonkeyInterfacesAreStructs=False,
|
|
wrapScope=None,
|
|
canRunScript=False,
|
|
):
|
|
"""
|
|
needThisHandling is True if we need to be able to accept a specified
|
|
thisObj, False otherwise.
|
|
"""
|
|
assert not rethrowContentException or not needThisHandling
|
|
|
|
self.retvalType = sig[0]
|
|
self.originalSig = sig
|
|
args = sig[1]
|
|
self.argCount = len(args)
|
|
if self.argCount > 0:
|
|
# Check for variadic arguments
|
|
lastArg = args[self.argCount - 1]
|
|
if lastArg.variadic:
|
|
self.argCountStr = "(%d - 1) + %s.Length()" % (
|
|
self.argCount,
|
|
lastArg.identifier.name,
|
|
)
|
|
else:
|
|
self.argCountStr = "%d" % self.argCount
|
|
self.needThisHandling = needThisHandling
|
|
# If needThisHandling, we generate ourselves as private and the caller
|
|
# will handle generating public versions that handle the "this" stuff.
|
|
visibility = "private" if needThisHandling else "public"
|
|
self.rethrowContentException = rethrowContentException
|
|
|
|
self.wrapScope = wrapScope
|
|
# We don't care, for callback codegen, whether our original member was
|
|
# a method or attribute or whatnot. Just always pass FakeMember()
|
|
# here.
|
|
CGNativeMember.__init__(
|
|
self,
|
|
descriptorProvider,
|
|
FakeMember(),
|
|
name,
|
|
(self.retvalType, args),
|
|
extendedAttrs={},
|
|
passJSBitsAsNeeded=False,
|
|
visibility=visibility,
|
|
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
|
|
canRunScript=canRunScript,
|
|
)
|
|
# We have to do all the generation of our body now, because
|
|
# the caller relies on us throwing if we can't manage it.
|
|
self.exceptionCode = (
|
|
"aRv.Throw(NS_ERROR_UNEXPECTED);\n" "return%s;\n" % self.getDefaultRetval()
|
|
)
|
|
self.body = self.getImpl()
|
|
|
|
def getImpl(self):
|
|
setupCall = self.getCallSetup()
|
|
declRval = self.getRvalDecl()
|
|
if self.argCount > 0:
|
|
argvDecl = fill(
|
|
"""
|
|
JS::RootedVector<JS::Value> argv(cx);
|
|
if (!argv.resize(${argCount})) {
|
|
// That threw an exception on the JSContext, and our CallSetup will do
|
|
// the right thing with that.
|
|
return${errorReturn};
|
|
}
|
|
""",
|
|
argCount=self.argCountStr,
|
|
errorReturn=self.getDefaultRetval(),
|
|
)
|
|
else:
|
|
# Avoid weird 0-sized arrays
|
|
argvDecl = ""
|
|
convertArgs = self.getArgConversions()
|
|
doCall = self.getCall()
|
|
returnResult = self.getResultConversion()
|
|
|
|
return setupCall + declRval + argvDecl + convertArgs + doCall + returnResult
|
|
|
|
def getResultConversion(self, isDefinitelyObject=False):
|
|
replacements = {
|
|
"val": "rval",
|
|
"holderName": "rvalHolder",
|
|
"declName": "rvalDecl",
|
|
# We actually want to pass in a null scope object here, because
|
|
# wrapping things into our current compartment (that of mCallback)
|
|
# is what we want.
|
|
"obj": "nullptr",
|
|
"passedToJSImpl": "false",
|
|
}
|
|
|
|
if isJSImplementedDescriptor(self.descriptorProvider):
|
|
isCallbackReturnValue = "JSImpl"
|
|
else:
|
|
isCallbackReturnValue = "Callback"
|
|
sourceDescription = "return value of %s" % self.getPrettyName()
|
|
convertType = instantiateJSToNativeConversion(
|
|
getJSToNativeConversionInfo(
|
|
self.retvalType,
|
|
self.descriptorProvider,
|
|
isDefinitelyObject=isDefinitelyObject,
|
|
exceptionCode=self.exceptionCode,
|
|
isCallbackReturnValue=isCallbackReturnValue,
|
|
# Allow returning a callback type that
|
|
# allows non-callable objects.
|
|
allowTreatNonCallableAsNull=True,
|
|
sourceDescription=sourceDescription,
|
|
),
|
|
replacements,
|
|
)
|
|
assignRetval = string.Template(
|
|
self.getRetvalInfo(self.retvalType, False)[2]
|
|
).substitute(replacements)
|
|
type = convertType.define()
|
|
return type + assignRetval
|
|
|
|
def getArgConversions(self):
|
|
# Just reget the arglist from self.originalSig, because our superclasses
|
|
# just have way to many members they like to clobber, so I can't find a
|
|
# safe member name to store it in.
|
|
argConversions = [
|
|
self.getArgConversion(i, arg) for i, arg in enumerate(self.originalSig[1])
|
|
]
|
|
if not argConversions:
|
|
return "\n"
|
|
|
|
# Do them back to front, so our argc modifications will work
|
|
# correctly, because we examine trailing arguments first.
|
|
argConversions.reverse()
|
|
# Wrap each one in a scope so that any locals it has don't leak out, and
|
|
# also so that we can just "break;" for our successCode.
|
|
argConversions = [
|
|
CGWrapper(CGIndenter(CGGeneric(c)), pre="do {\n", post="} while (false);\n")
|
|
for c in argConversions
|
|
]
|
|
if self.argCount > 0:
|
|
argConversions.insert(0, self.getArgcDecl())
|
|
# And slap them together.
|
|
return CGList(argConversions, "\n").define() + "\n"
|
|
|
|
def getArgConversion(self, i, arg):
|
|
argval = arg.identifier.name
|
|
|
|
if arg.variadic:
|
|
argval = argval + "[idx]"
|
|
jsvalIndex = "%d + idx" % i
|
|
else:
|
|
jsvalIndex = "%d" % i
|
|
if arg.canHaveMissingValue():
|
|
argval += ".Value()"
|
|
|
|
if arg.type.isDOMString():
|
|
# XPConnect string-to-JS conversion wants to mutate the string. So
|
|
# let's give it a string it can mutate
|
|
# XXXbz if we try to do a sequence of strings, this will kinda fail.
|
|
result = "mutableStr"
|
|
prepend = "nsString mutableStr(%s);\n" % argval
|
|
else:
|
|
result = argval
|
|
prepend = ""
|
|
|
|
if arg.type.isUnion() and self.wrapScope is None:
|
|
prepend += (
|
|
"JS::Rooted<JSObject*> callbackObj(cx, CallbackKnownNotGray());\n"
|
|
)
|
|
self.wrapScope = "callbackObj"
|
|
|
|
conversion = prepend + wrapForType(
|
|
arg.type,
|
|
self.descriptorProvider,
|
|
{
|
|
"result": result,
|
|
"successCode": "continue;\n" if arg.variadic else "break;\n",
|
|
"jsvalRef": "argv[%s]" % jsvalIndex,
|
|
"jsvalHandle": "argv[%s]" % jsvalIndex,
|
|
"obj": self.wrapScope,
|
|
"returnsNewObject": False,
|
|
"exceptionCode": self.exceptionCode,
|
|
"spiderMonkeyInterfacesAreStructs": self.spiderMonkeyInterfacesAreStructs,
|
|
},
|
|
)
|
|
|
|
if arg.variadic:
|
|
conversion = fill(
|
|
"""
|
|
for (uint32_t idx = 0; idx < ${arg}.Length(); ++idx) {
|
|
$*{conversion}
|
|
}
|
|
break;
|
|
""",
|
|
arg=arg.identifier.name,
|
|
conversion=conversion,
|
|
)
|
|
elif arg.canHaveMissingValue():
|
|
conversion = fill(
|
|
"""
|
|
if (${argName}.WasPassed()) {
|
|
$*{conversion}
|
|
} else if (argc == ${iPlus1}) {
|
|
// This is our current trailing argument; reduce argc
|
|
--argc;
|
|
} else {
|
|
argv[${i}].setUndefined();
|
|
}
|
|
""",
|
|
argName=arg.identifier.name,
|
|
conversion=conversion,
|
|
iPlus1=i + 1,
|
|
i=i,
|
|
)
|
|
return conversion
|
|
|
|
def getDefaultRetval(self):
|
|
default = self.getRetvalInfo(self.retvalType, False)[1]
|
|
if len(default) != 0:
|
|
default = " " + default
|
|
return default
|
|
|
|
def getArgs(self, returnType, argList):
|
|
args = CGNativeMember.getArgs(self, returnType, argList)
|
|
if not self.needThisHandling:
|
|
# Since we don't need this handling, we're the actual method that
|
|
# will be called, so we need an aRethrowExceptions argument.
|
|
if not self.rethrowContentException:
|
|
args.append(Argument("const char*", "aExecutionReason", "nullptr"))
|
|
args.append(
|
|
Argument(
|
|
"ExceptionHandling", "aExceptionHandling", "eReportExceptions"
|
|
)
|
|
)
|
|
args.append(Argument("JS::Realm*", "aRealm", "nullptr"))
|
|
return args
|
|
# We want to allow the caller to pass in a "this" value, as
|
|
# well as a BindingCallContext.
|
|
return [
|
|
Argument("BindingCallContext&", "cx"),
|
|
Argument("JS::Handle<JS::Value>", "aThisVal"),
|
|
] + args
|
|
|
|
def getCallSetup(self):
|
|
if self.needThisHandling:
|
|
# It's been done for us already
|
|
return ""
|
|
callSetup = "CallSetup s(this, aRv"
|
|
if self.rethrowContentException:
|
|
# getArgs doesn't add the aExceptionHandling argument but does add
|
|
# aRealm for us.
|
|
callSetup += (
|
|
', "%s", eRethrowContentExceptions, aRealm, /* aIsJSImplementedWebIDL = */ '
|
|
% self.getPrettyName()
|
|
)
|
|
callSetup += toStringBool(
|
|
isJSImplementedDescriptor(self.descriptorProvider)
|
|
)
|
|
else:
|
|
callSetup += ', "%s", aExceptionHandling, aRealm' % self.getPrettyName()
|
|
callSetup += ");\n"
|
|
return fill(
|
|
"""
|
|
$*{callSetup}
|
|
if (aRv.Failed()) {
|
|
return${errorReturn};
|
|
}
|
|
MOZ_ASSERT(s.GetContext());
|
|
BindingCallContext& cx = s.GetCallContext();
|
|
|
|
""",
|
|
callSetup=callSetup,
|
|
errorReturn=self.getDefaultRetval(),
|
|
)
|
|
|
|
def getArgcDecl(self):
|
|
return CGGeneric("unsigned argc = %s;\n" % self.argCountStr)
|
|
|
|
@staticmethod
|
|
def ensureASCIIName(idlObject):
|
|
type = "attribute" if idlObject.isAttr() else "operation"
|
|
if re.match("[^\x20-\x7E]", idlObject.identifier.name):
|
|
raise SyntaxError(
|
|
'Callback %s name "%s" contains non-ASCII '
|
|
"characters. We can't handle that. %s"
|
|
% (type, idlObject.identifier.name, idlObject.location)
|
|
)
|
|
if re.match('"', idlObject.identifier.name):
|
|
raise SyntaxError(
|
|
"Callback %s name '%s' contains "
|
|
"double-quote character. We can't handle "
|
|
"that. %s" % (type, idlObject.identifier.name, idlObject.location)
|
|
)
|
|
|
|
|
|
class ConstructCallback(CallbackMember):
|
|
def __init__(self, callback, descriptorProvider):
|
|
self.callback = callback
|
|
CallbackMember.__init__(
|
|
self,
|
|
callback.signatures()[0],
|
|
"Construct",
|
|
descriptorProvider,
|
|
needThisHandling=False,
|
|
canRunScript=True,
|
|
)
|
|
|
|
def getRvalDecl(self):
|
|
# Box constructedObj for getJSToNativeConversionInfo().
|
|
return "JS::Rooted<JS::Value> rval(cx);\n"
|
|
|
|
def getCall(self):
|
|
if self.argCount > 0:
|
|
args = "JS::HandleValueArray::subarray(argv, 0, argc)"
|
|
else:
|
|
args = "JS::HandleValueArray::empty()"
|
|
|
|
return fill(
|
|
"""
|
|
JS::Rooted<JS::Value> constructor(cx, JS::ObjectValue(*mCallback));
|
|
JS::Rooted<JSObject*> constructedObj(cx);
|
|
if (!JS::Construct(cx, constructor,
|
|
${args}, &constructedObj)) {
|
|
aRv.NoteJSContextException(cx);
|
|
return${errorReturn};
|
|
}
|
|
rval.setObject(*constructedObj);
|
|
""",
|
|
args=args,
|
|
errorReturn=self.getDefaultRetval(),
|
|
)
|
|
|
|
def getResultConversion(self):
|
|
return CallbackMember.getResultConversion(self, isDefinitelyObject=True)
|
|
|
|
def getPrettyName(self):
|
|
return self.callback.identifier.name
|
|
|
|
|
|
class CallbackMethod(CallbackMember):
|
|
def __init__(
|
|
self,
|
|
sig,
|
|
name,
|
|
descriptorProvider,
|
|
needThisHandling,
|
|
rethrowContentException=False,
|
|
spiderMonkeyInterfacesAreStructs=False,
|
|
canRunScript=False,
|
|
):
|
|
CallbackMember.__init__(
|
|
self,
|
|
sig,
|
|
name,
|
|
descriptorProvider,
|
|
needThisHandling,
|
|
rethrowContentException,
|
|
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
|
|
canRunScript=canRunScript,
|
|
)
|
|
|
|
def getRvalDecl(self):
|
|
return "JS::Rooted<JS::Value> rval(cx);\n"
|
|
|
|
def getCall(self):
|
|
if self.argCount > 0:
|
|
args = "JS::HandleValueArray::subarray(argv, 0, argc)"
|
|
else:
|
|
args = "JS::HandleValueArray::empty()"
|
|
|
|
return fill(
|
|
"""
|
|
$*{declCallable}
|
|
$*{declThis}
|
|
if (${callGuard}!JS::Call(cx, ${thisVal}, callable,
|
|
${args}, &rval)) {
|
|
aRv.NoteJSContextException(cx);
|
|
return${errorReturn};
|
|
}
|
|
""",
|
|
declCallable=self.getCallableDecl(),
|
|
declThis=self.getThisDecl(),
|
|
callGuard=self.getCallGuard(),
|
|
thisVal=self.getThisVal(),
|
|
args=args,
|
|
errorReturn=self.getDefaultRetval(),
|
|
)
|
|
|
|
|
|
class CallCallback(CallbackMethod):
|
|
def __init__(self, callback, descriptorProvider):
|
|
self.callback = callback
|
|
CallbackMethod.__init__(
|
|
self,
|
|
callback.signatures()[0],
|
|
"Call",
|
|
descriptorProvider,
|
|
needThisHandling=True,
|
|
canRunScript=not callback.isRunScriptBoundary(),
|
|
)
|
|
|
|
def getThisDecl(self):
|
|
return ""
|
|
|
|
def getThisVal(self):
|
|
return "aThisVal"
|
|
|
|
def getCallableDecl(self):
|
|
return "JS::Rooted<JS::Value> callable(cx, JS::ObjectValue(*mCallback));\n"
|
|
|
|
def getPrettyName(self):
|
|
return self.callback.identifier.name
|
|
|
|
def getCallGuard(self):
|
|
if self.callback._treatNonObjectAsNull:
|
|
return "JS::IsCallable(mCallback) && "
|
|
return ""
|
|
|
|
|
|
class CallbackOperationBase(CallbackMethod):
|
|
"""
|
|
Common class for implementing various callback operations.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
signature,
|
|
jsName,
|
|
nativeName,
|
|
descriptor,
|
|
singleOperation,
|
|
rethrowContentException=False,
|
|
spiderMonkeyInterfacesAreStructs=False,
|
|
):
|
|
self.singleOperation = singleOperation
|
|
self.methodName = descriptor.binaryNameFor(jsName)
|
|
CallbackMethod.__init__(
|
|
self,
|
|
signature,
|
|
nativeName,
|
|
descriptor,
|
|
singleOperation,
|
|
rethrowContentException,
|
|
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
|
|
)
|
|
|
|
def getThisDecl(self):
|
|
if not self.singleOperation:
|
|
return "JS::Rooted<JS::Value> thisValue(cx, JS::ObjectValue(*mCallback));\n"
|
|
# This relies on getCallableDecl declaring a boolean
|
|
# isCallable in the case when we're a single-operation
|
|
# interface.
|
|
return dedent(
|
|
"""
|
|
JS::Rooted<JS::Value> thisValue(cx, isCallable ? aThisVal.get()
|
|
: JS::ObjectValue(*mCallback));
|
|
"""
|
|
)
|
|
|
|
def getThisVal(self):
|
|
return "thisValue"
|
|
|
|
def getCallableDecl(self):
|
|
getCallableFromProp = fill(
|
|
"""
|
|
${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
|
|
if ((JSID_IS_VOID(*reinterpret_cast<jsid*>(atomsCache)) &&
|
|
!InitIds(cx, atomsCache)) ||
|
|
!GetCallableProperty(cx, atomsCache->${methodAtomName}, &callable)) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return${errorReturn};
|
|
}
|
|
""",
|
|
methodAtomName=CGDictionary.makeIdName(self.methodName),
|
|
atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
|
|
errorReturn=self.getDefaultRetval(),
|
|
)
|
|
if not self.singleOperation:
|
|
return "JS::Rooted<JS::Value> callable(cx);\n" + getCallableFromProp
|
|
return fill(
|
|
"""
|
|
bool isCallable = JS::IsCallable(mCallback);
|
|
JS::Rooted<JS::Value> callable(cx);
|
|
if (isCallable) {
|
|
callable = JS::ObjectValue(*mCallback);
|
|
} else {
|
|
$*{getCallableFromProp}
|
|
}
|
|
""",
|
|
getCallableFromProp=getCallableFromProp,
|
|
)
|
|
|
|
def getCallGuard(self):
|
|
return ""
|
|
|
|
|
|
class CallbackOperation(CallbackOperationBase):
|
|
"""
|
|
Codegen actual WebIDL operations on callback interfaces.
|
|
"""
|
|
|
|
def __init__(self, method, signature, descriptor, spiderMonkeyInterfacesAreStructs):
|
|
self.ensureASCIIName(method)
|
|
self.method = method
|
|
jsName = method.identifier.name
|
|
CallbackOperationBase.__init__(
|
|
self,
|
|
signature,
|
|
jsName,
|
|
MakeNativeName(descriptor.binaryNameFor(jsName)),
|
|
descriptor,
|
|
descriptor.interface.isSingleOperationInterface(),
|
|
rethrowContentException=descriptor.interface.isJSImplemented(),
|
|
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
|
|
)
|
|
|
|
def getPrettyName(self):
|
|
return "%s.%s" % (
|
|
self.descriptorProvider.interface.identifier.name,
|
|
self.method.identifier.name,
|
|
)
|
|
|
|
|
|
class CallbackAccessor(CallbackMember):
|
|
"""
|
|
Shared superclass for CallbackGetter and CallbackSetter.
|
|
"""
|
|
|
|
def __init__(self, attr, sig, name, descriptor, spiderMonkeyInterfacesAreStructs):
|
|
self.ensureASCIIName(attr)
|
|
self.attrName = attr.identifier.name
|
|
CallbackMember.__init__(
|
|
self,
|
|
sig,
|
|
name,
|
|
descriptor,
|
|
needThisHandling=False,
|
|
rethrowContentException=descriptor.interface.isJSImplemented(),
|
|
spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs,
|
|
)
|
|
|
|
def getPrettyName(self):
|
|
return "%s.%s" % (
|
|
self.descriptorProvider.interface.identifier.name,
|
|
self.attrName,
|
|
)
|
|
|
|
|
|
class CallbackGetter(CallbackAccessor):
|
|
def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs):
|
|
CallbackAccessor.__init__(
|
|
self,
|
|
attr,
|
|
(attr.type, []),
|
|
callbackGetterName(attr, descriptor),
|
|
descriptor,
|
|
spiderMonkeyInterfacesAreStructs,
|
|
)
|
|
|
|
def getRvalDecl(self):
|
|
return "JS::Rooted<JS::Value> rval(cx);\n"
|
|
|
|
def getCall(self):
|
|
return fill(
|
|
"""
|
|
JS::Rooted<JSObject *> callback(cx, mCallback);
|
|
${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
|
|
if ((JSID_IS_VOID(*reinterpret_cast<jsid*>(atomsCache))
|
|
&& !InitIds(cx, atomsCache)) ||
|
|
!JS_GetPropertyById(cx, callback, atomsCache->${attrAtomName}, &rval)) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return${errorReturn};
|
|
}
|
|
""",
|
|
atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
|
|
attrAtomName=CGDictionary.makeIdName(
|
|
self.descriptorProvider.binaryNameFor(self.attrName)
|
|
),
|
|
errorReturn=self.getDefaultRetval(),
|
|
)
|
|
|
|
|
|
class CallbackSetter(CallbackAccessor):
|
|
def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs):
|
|
CallbackAccessor.__init__(
|
|
self,
|
|
attr,
|
|
(BuiltinTypes[IDLBuiltinType.Types.void], [FakeArgument(attr.type, attr)]),
|
|
callbackSetterName(attr, descriptor),
|
|
descriptor,
|
|
spiderMonkeyInterfacesAreStructs,
|
|
)
|
|
|
|
def getRvalDecl(self):
|
|
# We don't need an rval
|
|
return ""
|
|
|
|
def getCall(self):
|
|
return fill(
|
|
"""
|
|
MOZ_ASSERT(argv.length() == 1);
|
|
JS::Rooted<JSObject*> callback(cx, CallbackKnownNotGray());
|
|
${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
|
|
if ((JSID_IS_VOID(*reinterpret_cast<jsid*>(atomsCache)) &&
|
|
!InitIds(cx, atomsCache)) ||
|
|
!JS_SetPropertyById(cx, callback, atomsCache->${attrAtomName}, argv[0])) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return${errorReturn};
|
|
}
|
|
""",
|
|
atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
|
|
attrAtomName=CGDictionary.makeIdName(
|
|
self.descriptorProvider.binaryNameFor(self.attrName)
|
|
),
|
|
errorReturn=self.getDefaultRetval(),
|
|
)
|
|
|
|
def getArgcDecl(self):
|
|
return None
|
|
|
|
|
|
class CGJSImplInitOperation(CallbackOperationBase):
|
|
"""
|
|
Codegen the __Init() method used to pass along constructor arguments for JS-implemented WebIDL.
|
|
"""
|
|
|
|
def __init__(self, sig, descriptor):
|
|
assert sig in descriptor.interface.ctor().signatures()
|
|
CallbackOperationBase.__init__(
|
|
self,
|
|
(BuiltinTypes[IDLBuiltinType.Types.void], sig[1]),
|
|
"__init",
|
|
"__Init",
|
|
descriptor,
|
|
singleOperation=False,
|
|
rethrowContentException=True,
|
|
spiderMonkeyInterfacesAreStructs=True,
|
|
)
|
|
|
|
def getPrettyName(self):
|
|
return "__init"
|
|
|
|
|
|
class CGJSImplOnGetOperation(CallbackOperationBase):
|
|
"""
|
|
Codegen the __OnGet() method used to notify the JS impl that a get() is
|
|
happening on a JS-implemented maplike. This method takes two arguments
|
|
(key and value) and returns nothing.
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
CallbackOperationBase.__init__(
|
|
self,
|
|
(
|
|
BuiltinTypes[IDLBuiltinType.Types.void],
|
|
[
|
|
FakeArgument(BuiltinTypes[IDLBuiltinType.Types.any], None, "key"),
|
|
FakeArgument(BuiltinTypes[IDLBuiltinType.Types.any], None, "value"),
|
|
],
|
|
),
|
|
"__onget",
|
|
"__OnGet",
|
|
descriptor,
|
|
singleOperation=False,
|
|
rethrowContentException=True,
|
|
spiderMonkeyInterfacesAreStructs=True,
|
|
)
|
|
|
|
def getPrettyName(self):
|
|
return "__onget"
|
|
|
|
|
|
class CGJSImplEventHookOperation(CallbackOperationBase):
|
|
"""
|
|
Codegen the hooks on a JS impl for adding/removing event listeners.
|
|
"""
|
|
|
|
def __init__(self, descriptor, name):
|
|
self.name = name
|
|
|
|
CallbackOperationBase.__init__(
|
|
self,
|
|
(
|
|
BuiltinTypes[IDLBuiltinType.Types.void],
|
|
[
|
|
FakeArgument(
|
|
BuiltinTypes[IDLBuiltinType.Types.domstring], None, "aType"
|
|
)
|
|
],
|
|
),
|
|
name,
|
|
MakeNativeName(name),
|
|
descriptor,
|
|
singleOperation=False,
|
|
rethrowContentException=False,
|
|
spiderMonkeyInterfacesAreStructs=True,
|
|
)
|
|
|
|
def getPrettyName(self):
|
|
return self.name
|
|
|
|
|
|
def getMaplikeOrSetlikeErrorReturn(helperImpl):
|
|
"""
|
|
Generate return values based on whether a maplike or setlike generated
|
|
method is an interface method (which returns bool) or a helper function
|
|
(which uses ErrorResult).
|
|
"""
|
|
if helperImpl:
|
|
return dedent(
|
|
"""
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return%s;
|
|
"""
|
|
% helperImpl.getDefaultRetval()
|
|
)
|
|
return "return false;\n"
|
|
|
|
|
|
def getMaplikeOrSetlikeBackingObject(descriptor, maplikeOrSetlike, helperImpl=None):
|
|
"""
|
|
Generate code to get/create a JS backing object for a maplike/setlike
|
|
declaration from the declaration slot.
|
|
"""
|
|
func_prefix = maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title()
|
|
ret = fill(
|
|
"""
|
|
JS::Rooted<JSObject*> backingObj(cx);
|
|
bool created = false;
|
|
if (!Get${func_prefix}BackingObject(cx, obj, ${slot}, &backingObj, &created)) {
|
|
$*{errorReturn}
|
|
}
|
|
if (created) {
|
|
PreserveWrapper<${selfType}>(self);
|
|
}
|
|
""",
|
|
slot=memberReservedSlot(maplikeOrSetlike, descriptor),
|
|
func_prefix=func_prefix,
|
|
errorReturn=getMaplikeOrSetlikeErrorReturn(helperImpl),
|
|
selfType=descriptor.nativeType,
|
|
)
|
|
return ret
|
|
|
|
|
|
def getMaplikeOrSetlikeSizeGetterBody(descriptor, attr):
|
|
"""
|
|
Creates the body for the size getter method of maplike/setlike interfaces.
|
|
"""
|
|
# We should only have one declaration attribute currently
|
|
assert attr.identifier.name == "size"
|
|
assert attr.isMaplikeOrSetlikeAttr()
|
|
return fill(
|
|
"""
|
|
$*{getBackingObj}
|
|
uint32_t result = JS::${funcPrefix}Size(cx, backingObj);
|
|
MOZ_ASSERT(!JS_IsExceptionPending(cx));
|
|
args.rval().setNumber(result);
|
|
return true;
|
|
""",
|
|
getBackingObj=getMaplikeOrSetlikeBackingObject(
|
|
descriptor, attr.maplikeOrSetlike
|
|
),
|
|
funcPrefix=attr.maplikeOrSetlike.prefix,
|
|
)
|
|
|
|
|
|
class CGMaplikeOrSetlikeMethodGenerator(CGThing):
|
|
"""
|
|
Creates methods for maplike/setlike interfaces. It is expected that all
|
|
methods will be have a maplike/setlike object attached. Unwrapping/wrapping
|
|
will be taken care of by the usual method generation machinery in
|
|
CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of
|
|
using CGCallGenerator.
|
|
"""
|
|
|
|
def __init__(self, descriptor, maplikeOrSetlike, methodName, helperImpl=None):
|
|
CGThing.__init__(self)
|
|
# True if this will be the body of a C++ helper function.
|
|
self.helperImpl = helperImpl
|
|
self.descriptor = descriptor
|
|
self.maplikeOrSetlike = maplikeOrSetlike
|
|
self.cgRoot = CGList([])
|
|
impl_method_name = methodName
|
|
if impl_method_name[0] == "_":
|
|
# double underscore means this is a js-implemented chrome only rw
|
|
# function. Truncate the double underscore so calling the right
|
|
# underlying JSAPI function still works.
|
|
impl_method_name = impl_method_name[2:]
|
|
self.cgRoot.append(
|
|
CGGeneric(
|
|
getMaplikeOrSetlikeBackingObject(
|
|
self.descriptor, self.maplikeOrSetlike, self.helperImpl
|
|
)
|
|
)
|
|
)
|
|
self.returnStmt = getMaplikeOrSetlikeErrorReturn(self.helperImpl)
|
|
|
|
# Generates required code for the method. Method descriptions included
|
|
# in definitions below. Throw if we don't have a method to fill in what
|
|
# we're looking for.
|
|
try:
|
|
methodGenerator = getattr(self, impl_method_name)
|
|
except AttributeError:
|
|
raise TypeError(
|
|
"Missing %s method definition '%s'"
|
|
% (self.maplikeOrSetlike.maplikeOrSetlikeType, methodName)
|
|
)
|
|
# Method generator returns tuple, containing:
|
|
#
|
|
# - a list of CGThings representing setup code for preparing to call
|
|
# the JS API function
|
|
# - a list of arguments needed for the JS API function we're calling
|
|
# - list of code CGThings needed for return value conversion.
|
|
(setupCode, arguments, setResult) = methodGenerator()
|
|
|
|
# Create the actual method call, and then wrap it with the code to
|
|
# return the value if needed.
|
|
funcName = self.maplikeOrSetlike.prefix + MakeNativeName(impl_method_name)
|
|
# Append the list of setup code CGThings
|
|
self.cgRoot.append(CGList(setupCode))
|
|
# Create the JS API call
|
|
self.cgRoot.append(
|
|
CGWrapper(
|
|
CGGeneric(
|
|
fill(
|
|
"""
|
|
if (!JS::${funcName}(${args})) {
|
|
$*{errorReturn}
|
|
}
|
|
""",
|
|
funcName=funcName,
|
|
args=", ".join(["cx", "backingObj"] + arguments),
|
|
errorReturn=self.returnStmt,
|
|
)
|
|
)
|
|
)
|
|
)
|
|
# Append result conversion
|
|
self.cgRoot.append(CGList(setResult))
|
|
|
|
def mergeTuples(self, a, b):
|
|
"""
|
|
Expecting to take 2 tuples were all elements are lists, append the lists in
|
|
the second tuple to the lists in the first.
|
|
"""
|
|
return tuple([x + y for x, y in zip(a, b)])
|
|
|
|
def appendArgConversion(self, name):
|
|
"""
|
|
Generate code to convert arguments to JS::Values, so they can be
|
|
passed into JSAPI functions.
|
|
"""
|
|
return CGGeneric(
|
|
fill(
|
|
"""
|
|
JS::Rooted<JS::Value> ${name}Val(cx);
|
|
if (!ToJSValue(cx, ${name}, &${name}Val)) {
|
|
$*{errorReturn}
|
|
}
|
|
""",
|
|
name=name,
|
|
errorReturn=self.returnStmt,
|
|
)
|
|
)
|
|
|
|
def appendKeyArgConversion(self):
|
|
"""
|
|
Generates the key argument for methods. Helper functions will use
|
|
a RootedVector<JS::Value>, while interface methods have separate JS::Values.
|
|
"""
|
|
if self.helperImpl:
|
|
return ([], ["argv[0]"], [])
|
|
return ([self.appendArgConversion("arg0")], ["arg0Val"], [])
|
|
|
|
def appendKeyAndValueArgConversion(self):
|
|
"""
|
|
Generates arguments for methods that require a key and value. Helper
|
|
functions will use a RootedVector<JS::Value>, while interface methods have
|
|
separate JS::Values.
|
|
"""
|
|
r = self.appendKeyArgConversion()
|
|
if self.helperImpl:
|
|
return self.mergeTuples(r, ([], ["argv[1]"], []))
|
|
return self.mergeTuples(
|
|
r, ([self.appendArgConversion("arg1")], ["arg1Val"], [])
|
|
)
|
|
|
|
def appendIteratorResult(self):
|
|
"""
|
|
Generate code to output JSObject* return values, needed for functions that
|
|
return iterators. Iterators cannot currently be wrapped via Xrays. If
|
|
something that would return an iterator is called via Xray, fail early.
|
|
"""
|
|
# TODO: Bug 1173651 - Remove check once bug 1023984 is fixed.
|
|
code = CGGeneric(
|
|
dedent(
|
|
"""
|
|
// TODO (Bug 1173651): Xrays currently cannot wrap iterators. Change
|
|
// after bug 1023984 is fixed.
|
|
if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
|
|
JS_ReportErrorASCII(cx, "Xray wrapping of iterators not supported.");
|
|
return false;
|
|
}
|
|
JS::Rooted<JSObject*> result(cx);
|
|
JS::Rooted<JS::Value> v(cx);
|
|
"""
|
|
)
|
|
)
|
|
arguments = "&v"
|
|
setResult = CGGeneric(
|
|
dedent(
|
|
"""
|
|
result = &v.toObject();
|
|
"""
|
|
)
|
|
)
|
|
return ([code], [arguments], [setResult])
|
|
|
|
def appendSelfResult(self):
|
|
"""
|
|
Generate code to return the interface object itself.
|
|
"""
|
|
code = CGGeneric(
|
|
dedent(
|
|
"""
|
|
JS::Rooted<JSObject*> result(cx);
|
|
"""
|
|
)
|
|
)
|
|
setResult = CGGeneric(
|
|
dedent(
|
|
"""
|
|
result = obj;
|
|
"""
|
|
)
|
|
)
|
|
return ([code], [], [setResult])
|
|
|
|
def appendBoolResult(self):
|
|
if self.helperImpl:
|
|
return ([CGGeneric()], ["&aRetVal"], [])
|
|
return ([CGGeneric("bool result;\n")], ["&result"], [])
|
|
|
|
def forEach(self):
|
|
"""
|
|
void forEach(callback c, any thisval);
|
|
|
|
ForEach takes a callback, and a possible value to use as 'this'. The
|
|
callback needs to take value, key, and the interface object
|
|
implementing maplike/setlike. In order to make sure that the third arg
|
|
is our interface object instead of the map/set backing object, we
|
|
create a js function with the callback and original object in its
|
|
storage slots, then use a helper function in BindingUtils to make sure
|
|
the callback is called correctly.
|
|
"""
|
|
assert not self.helperImpl
|
|
code = [
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
// Create a wrapper function.
|
|
JSFunction* func = js::NewFunctionWithReserved(cx, ForEachHandler, 3, 0, nullptr);
|
|
if (!func) {
|
|
return false;
|
|
}
|
|
JS::Rooted<JSObject*> funcObj(cx, JS_GetFunctionObject(func));
|
|
JS::Rooted<JS::Value> funcVal(cx, JS::ObjectValue(*funcObj));
|
|
js::SetFunctionNativeReserved(funcObj, FOREACH_CALLBACK_SLOT,
|
|
JS::ObjectValue(*arg0));
|
|
js::SetFunctionNativeReserved(funcObj, FOREACH_MAPLIKEORSETLIKEOBJ_SLOT,
|
|
JS::ObjectValue(*obj));
|
|
"""
|
|
)
|
|
)
|
|
]
|
|
arguments = ["funcVal", "arg1"]
|
|
return (code, arguments, [])
|
|
|
|
def set(self):
|
|
"""
|
|
object set(key, value);
|
|
|
|
Maplike only function, takes key and sets value to it, returns
|
|
interface object unless being called from a C++ helper.
|
|
"""
|
|
assert self.maplikeOrSetlike.isMaplike()
|
|
r = self.appendKeyAndValueArgConversion()
|
|
if self.helperImpl:
|
|
return r
|
|
return self.mergeTuples(r, self.appendSelfResult())
|
|
|
|
def add(self):
|
|
"""
|
|
object add(value);
|
|
|
|
Setlike only function, adds value to set, returns interface object
|
|
unless being called from a C++ helper
|
|
"""
|
|
assert self.maplikeOrSetlike.isSetlike()
|
|
r = self.appendKeyArgConversion()
|
|
if self.helperImpl:
|
|
return r
|
|
return self.mergeTuples(r, self.appendSelfResult())
|
|
|
|
def get(self):
|
|
"""
|
|
type? get(key);
|
|
|
|
Retrieves a value from a backing object based on the key. Returns value
|
|
if key is in backing object, undefined otherwise.
|
|
"""
|
|
assert self.maplikeOrSetlike.isMaplike()
|
|
r = self.appendKeyArgConversion()
|
|
code = [
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
JS::Rooted<JS::Value> result(cx);
|
|
"""
|
|
)
|
|
)
|
|
]
|
|
arguments = ["&result"]
|
|
if self.descriptor.interface.isJSImplemented():
|
|
callOnGet = [
|
|
CGGeneric(
|
|
dedent(
|
|
"""
|
|
{
|
|
JS::ExposeValueToActiveJS(result);
|
|
ErrorResult onGetResult;
|
|
self->__OnGet(arg0Val, result, onGetResult);
|
|
if (onGetResult.MaybeSetPendingException(cx)) {
|
|
return false;
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
)
|
|
]
|
|
else:
|
|
callOnGet = []
|
|
return self.mergeTuples(r, (code, arguments, callOnGet))
|
|
|
|
def has(self):
|
|
"""
|
|
bool has(key);
|
|
|
|
Check if an entry exists in the backing object. Returns true if value
|
|
exists in backing object, false otherwise.
|
|
"""
|
|
return self.mergeTuples(self.appendKeyArgConversion(), self.appendBoolResult())
|
|
|
|
def keys(self):
|
|
"""
|
|
object keys();
|
|
|
|
Returns new object iterator with all keys from backing object.
|
|
"""
|
|
return self.appendIteratorResult()
|
|
|
|
def values(self):
|
|
"""
|
|
object values();
|
|
|
|
Returns new object iterator with all values from backing object.
|
|
"""
|
|
return self.appendIteratorResult()
|
|
|
|
def entries(self):
|
|
"""
|
|
object entries();
|
|
|
|
Returns new object iterator with all keys and values from backing
|
|
object. Keys will be null for set.
|
|
"""
|
|
return self.appendIteratorResult()
|
|
|
|
def clear(self):
|
|
"""
|
|
void clear();
|
|
|
|
Removes all entries from map/set.
|
|
"""
|
|
return ([], [], [])
|
|
|
|
def delete(self):
|
|
"""
|
|
bool delete(key);
|
|
|
|
Deletes an entry from the backing object. Returns true if value existed
|
|
in backing object, false otherwise.
|
|
"""
|
|
return self.mergeTuples(self.appendKeyArgConversion(), self.appendBoolResult())
|
|
|
|
def define(self):
|
|
return self.cgRoot.define()
|
|
|
|
|
|
class CGMaplikeOrSetlikeHelperFunctionGenerator(CallbackMember):
|
|
"""
|
|
Generates code to allow C++ to perform operations on backing objects. Gets
|
|
a context from the binding wrapper, turns arguments into JS::Values (via
|
|
CallbackMember/CGNativeMember argument conversion), then uses
|
|
CGMaplikeOrSetlikeMethodGenerator to generate the body.
|
|
|
|
"""
|
|
|
|
class HelperFunction(CGAbstractMethod):
|
|
"""
|
|
Generates context retrieval code and rooted JSObject for interface for
|
|
CGMaplikeOrSetlikeMethodGenerator to use
|
|
"""
|
|
|
|
def __init__(self, descriptor, name, args, code, needsBoolReturn=False):
|
|
self.code = code
|
|
CGAbstractMethod.__init__(
|
|
self, descriptor, name, "bool" if needsBoolReturn else "void", args
|
|
)
|
|
|
|
def definition_body(self):
|
|
return self.code
|
|
|
|
def __init__(
|
|
self,
|
|
descriptor,
|
|
maplikeOrSetlike,
|
|
name,
|
|
needsKeyArg=False,
|
|
needsValueArg=False,
|
|
needsBoolReturn=False,
|
|
):
|
|
args = []
|
|
self.maplikeOrSetlike = maplikeOrSetlike
|
|
self.needsBoolReturn = needsBoolReturn
|
|
if needsKeyArg:
|
|
args.append(FakeArgument(maplikeOrSetlike.keyType, None, "aKey"))
|
|
if needsValueArg:
|
|
assert needsKeyArg
|
|
args.append(FakeArgument(maplikeOrSetlike.valueType, None, "aValue"))
|
|
# Run CallbackMember init function to generate argument conversion code.
|
|
# wrapScope is set to 'obj' when generating maplike or setlike helper
|
|
# functions, as we don't have access to the CallbackPreserveColor
|
|
# method.
|
|
CallbackMember.__init__(
|
|
self,
|
|
[BuiltinTypes[IDLBuiltinType.Types.void], args],
|
|
name,
|
|
descriptor,
|
|
False,
|
|
wrapScope="obj",
|
|
)
|
|
# Wrap CallbackMember body code into a CGAbstractMethod to make
|
|
# generation easier.
|
|
self.implMethod = CGMaplikeOrSetlikeHelperFunctionGenerator.HelperFunction(
|
|
descriptor, name, self.args, self.body, needsBoolReturn
|
|
)
|
|
|
|
def getCallSetup(self):
|
|
return dedent(
|
|
"""
|
|
MOZ_ASSERT(self);
|
|
AutoJSAPI jsapi;
|
|
jsapi.Init();
|
|
JSContext* cx = jsapi.cx();
|
|
// It's safe to use UnprivilegedJunkScopeOrWorkerGlobal here because
|
|
// all we want is to wrap into _some_ scope and then unwrap to find
|
|
// the reflector, and wrapping has no side-effects.
|
|
JSAutoRealm tempRealm(cx, UnprivilegedJunkScopeOrWorkerGlobal());
|
|
JS::Rooted<JS::Value> v(cx);
|
|
if(!ToJSValue(cx, self, &v)) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return%s;
|
|
}
|
|
// This is a reflector, but due to trying to name things
|
|
// similarly across method generators, it's called obj here.
|
|
JS::Rooted<JSObject*> obj(cx);
|
|
obj = js::UncheckedUnwrap(&v.toObject(), /* stopAtWindowProxy = */ false);
|
|
JSAutoRealm reflectorRealm(cx, obj);
|
|
"""
|
|
% self.getDefaultRetval()
|
|
)
|
|
|
|
def getArgs(self, returnType, argList):
|
|
# We don't need the context or the value. We'll generate those instead.
|
|
args = CGNativeMember.getArgs(self, returnType, argList)
|
|
# Prepend a pointer to the binding object onto the arguments
|
|
return [Argument(self.descriptorProvider.nativeType + "*", "self")] + args
|
|
|
|
def getResultConversion(self):
|
|
if self.needsBoolReturn:
|
|
return "return aRetVal;\n"
|
|
return "return;\n"
|
|
|
|
def getRvalDecl(self):
|
|
if self.needsBoolReturn:
|
|
return "bool aRetVal;\n"
|
|
return ""
|
|
|
|
def getArgcDecl(self):
|
|
# Don't need argc for anything.
|
|
return None
|
|
|
|
def getDefaultRetval(self):
|
|
if self.needsBoolReturn:
|
|
return " false"
|
|
return ""
|
|
|
|
def getCall(self):
|
|
return CGMaplikeOrSetlikeMethodGenerator(
|
|
self.descriptorProvider,
|
|
self.maplikeOrSetlike,
|
|
self.name.lower(),
|
|
helperImpl=self,
|
|
).define()
|
|
|
|
def getPrettyName(self):
|
|
return self.name
|
|
|
|
def declare(self):
|
|
return self.implMethod.declare()
|
|
|
|
def define(self):
|
|
return self.implMethod.define()
|
|
|
|
|
|
class CGMaplikeOrSetlikeHelperGenerator(CGNamespace):
|
|
"""
|
|
Declares and defines convenience methods for accessing backing objects on
|
|
setlike/maplike interface. Generates function signatures, un/packs
|
|
backing objects from slot, etc.
|
|
"""
|
|
|
|
def __init__(self, descriptor, maplikeOrSetlike):
|
|
self.descriptor = descriptor
|
|
# Since iterables are folded in with maplike/setlike, make sure we've
|
|
# got the right type here.
|
|
assert maplikeOrSetlike.isMaplike() or maplikeOrSetlike.isSetlike()
|
|
self.maplikeOrSetlike = maplikeOrSetlike
|
|
self.namespace = "%sHelpers" % (
|
|
self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title()
|
|
)
|
|
self.helpers = [
|
|
CGMaplikeOrSetlikeHelperFunctionGenerator(
|
|
descriptor, maplikeOrSetlike, "Clear"
|
|
),
|
|
CGMaplikeOrSetlikeHelperFunctionGenerator(
|
|
descriptor,
|
|
maplikeOrSetlike,
|
|
"Delete",
|
|
needsKeyArg=True,
|
|
needsBoolReturn=True,
|
|
),
|
|
CGMaplikeOrSetlikeHelperFunctionGenerator(
|
|
descriptor,
|
|
maplikeOrSetlike,
|
|
"Has",
|
|
needsKeyArg=True,
|
|
needsBoolReturn=True,
|
|
),
|
|
]
|
|
if self.maplikeOrSetlike.isMaplike():
|
|
self.helpers.append(
|
|
CGMaplikeOrSetlikeHelperFunctionGenerator(
|
|
descriptor,
|
|
maplikeOrSetlike,
|
|
"Set",
|
|
needsKeyArg=True,
|
|
needsValueArg=True,
|
|
)
|
|
)
|
|
else:
|
|
assert self.maplikeOrSetlike.isSetlike()
|
|
self.helpers.append(
|
|
CGMaplikeOrSetlikeHelperFunctionGenerator(
|
|
descriptor, maplikeOrSetlike, "Add", needsKeyArg=True
|
|
)
|
|
)
|
|
CGNamespace.__init__(self, self.namespace, CGList(self.helpers))
|
|
|
|
|
|
class CGIterableMethodGenerator(CGGeneric):
|
|
"""
|
|
Creates methods for iterable interfaces. Unwrapping/wrapping
|
|
will be taken care of by the usual method generation machinery in
|
|
CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of
|
|
using CGCallGenerator.
|
|
"""
|
|
|
|
def __init__(self, descriptor, iterable, methodName):
|
|
if methodName == "forEach":
|
|
CGGeneric.__init__(
|
|
self,
|
|
fill(
|
|
"""
|
|
if (!JS::IsCallable(arg0)) {
|
|
cx.ThrowErrorMessage<MSG_NOT_CALLABLE>("Argument 1");
|
|
return false;
|
|
}
|
|
JS::RootedValueArray<3> callArgs(cx);
|
|
callArgs[2].setObject(*obj);
|
|
JS::Rooted<JS::Value> ignoredReturnVal(cx);
|
|
auto GetKeyAtIndex = &${selfType}::GetKeyAtIndex;
|
|
auto GetValueAtIndex = &${selfType}::GetValueAtIndex;
|
|
for (size_t i = 0; i < self->GetIterableLength(); ++i) {
|
|
if (!CallIterableGetter(cx, GetValueAtIndex, self, i,
|
|
callArgs[0])) {
|
|
return false;
|
|
}
|
|
if (!CallIterableGetter(cx, GetKeyAtIndex, self, i,
|
|
callArgs[1])) {
|
|
return false;
|
|
}
|
|
if (!JS::Call(cx, arg1, arg0, JS::HandleValueArray(callArgs),
|
|
&ignoredReturnVal)) {
|
|
return false;
|
|
}
|
|
}
|
|
""",
|
|
ifaceName=descriptor.interface.identifier.name,
|
|
selfType=descriptor.nativeType,
|
|
),
|
|
)
|
|
return
|
|
CGGeneric.__init__(
|
|
self,
|
|
fill(
|
|
"""
|
|
typedef ${iterClass} itrType;
|
|
RefPtr<itrType> result(new itrType(self,
|
|
itrType::IterableIteratorType::${itrMethod},
|
|
&${ifaceName}Iterator_Binding::Wrap));
|
|
""",
|
|
iterClass=iteratorNativeType(descriptor),
|
|
ifaceName=descriptor.interface.identifier.name,
|
|
itrMethod=methodName.title(),
|
|
),
|
|
)
|
|
|
|
|
|
class GlobalGenRoots:
|
|
"""
|
|
Roots for global codegen.
|
|
|
|
To generate code, call the method associated with the target, and then
|
|
call the appropriate define/declare method.
|
|
"""
|
|
|
|
@staticmethod
|
|
def GeneratedAtomList(config):
|
|
# Atom enum
|
|
dictionaries = config.dictionaries
|
|
|
|
structs = []
|
|
|
|
def memberToAtomCacheMember(binaryNameFor, m):
|
|
binaryMemberName = binaryNameFor(m.identifier.name)
|
|
return ClassMember(
|
|
CGDictionary.makeIdName(binaryMemberName),
|
|
"PinnedStringId",
|
|
visibility="public",
|
|
)
|
|
|
|
def buildAtomCacheStructure(idlobj, binaryNameFor, members):
|
|
classMembers = [memberToAtomCacheMember(binaryNameFor, m) for m in members]
|
|
structName = idlobj.identifier.name + "Atoms"
|
|
return (
|
|
structName,
|
|
CGWrapper(
|
|
CGClass(
|
|
structName, bases=None, isStruct=True, members=classMembers
|
|
),
|
|
post="\n",
|
|
),
|
|
)
|
|
|
|
for dict in dictionaries:
|
|
if len(dict.members) == 0:
|
|
continue
|
|
|
|
structs.append(buildAtomCacheStructure(dict, lambda x: x, dict.members))
|
|
|
|
for d in config.getDescriptors(isJSImplemented=True) + config.getDescriptors(
|
|
isCallback=True
|
|
):
|
|
members = [m for m in d.interface.members if m.isAttr() or m.isMethod()]
|
|
if d.interface.isJSImplemented() and d.interface.ctor():
|
|
# We'll have an __init() method.
|
|
members.append(FakeMember("__init"))
|
|
if (
|
|
d.interface.isJSImplemented()
|
|
and d.interface.maplikeOrSetlikeOrIterable
|
|
and d.interface.maplikeOrSetlikeOrIterable.isMaplike()
|
|
):
|
|
# We'll have an __onget() method.
|
|
members.append(FakeMember("__onget"))
|
|
if d.interface.isJSImplemented() and d.interface.getExtendedAttribute(
|
|
"WantsEventListenerHooks"
|
|
):
|
|
members.append(FakeMember("eventListenerAdded"))
|
|
members.append(FakeMember("eventListenerRemoved"))
|
|
if len(members) == 0:
|
|
continue
|
|
|
|
structs.append(
|
|
buildAtomCacheStructure(
|
|
d.interface, lambda x: d.binaryNameFor(x), members
|
|
)
|
|
)
|
|
|
|
structs.sort()
|
|
generatedStructs = [struct for structName, struct in structs]
|
|
structNames = [structName for structName, struct in structs]
|
|
|
|
mainStruct = CGWrapper(
|
|
CGClass(
|
|
"PerThreadAtomCache",
|
|
bases=[ClassBase(structName) for structName in structNames],
|
|
isStruct=True,
|
|
),
|
|
post="\n",
|
|
)
|
|
|
|
structs = CGList(generatedStructs + [mainStruct])
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(structs, pre="\n"))
|
|
curr = CGWrapper(curr, post="\n")
|
|
|
|
# Add include statement for PinnedStringId.
|
|
declareIncludes = ["mozilla/dom/PinnedStringId.h"]
|
|
curr = CGHeaders([], [], [], [], declareIncludes, [], "GeneratedAtomList", curr)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("GeneratedAtomList", curr)
|
|
|
|
# Add the auto-generated comment.
|
|
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def GeneratedEventList(config):
|
|
eventList = CGList([])
|
|
for generatedEvent in config.generatedEvents:
|
|
eventList.append(
|
|
CGGeneric(declare=("GENERATED_EVENT(%s)\n" % generatedEvent))
|
|
)
|
|
return eventList
|
|
|
|
@staticmethod
|
|
def PrototypeList(config):
|
|
|
|
# Prototype ID enum.
|
|
descriptorsWithPrototype = config.getDescriptors(
|
|
hasInterfacePrototypeObject=True
|
|
)
|
|
protos = [d.name for d in descriptorsWithPrototype]
|
|
idEnum = CGNamespacedEnum("id", "ID", ["_ID_Start"] + protos, [0, "_ID_Start"])
|
|
idEnum = CGList([idEnum])
|
|
|
|
def fieldSizeAssert(amount, jitInfoField, message):
|
|
maxFieldValue = (
|
|
"(uint64_t(1) << (sizeof(std::declval<JSJitInfo>().%s) * 8))"
|
|
% jitInfoField
|
|
)
|
|
return CGGeneric(
|
|
define='static_assert(%s < %s, "%s");\n\n'
|
|
% (amount, maxFieldValue, message)
|
|
)
|
|
|
|
idEnum.append(
|
|
fieldSizeAssert("id::_ID_Count", "protoID", "Too many prototypes!")
|
|
)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
idEnum = CGNamespace.build(
|
|
["mozilla", "dom", "prototypes"], CGWrapper(idEnum, pre="\n")
|
|
)
|
|
idEnum = CGWrapper(idEnum, post="\n")
|
|
|
|
curr = CGList(
|
|
[
|
|
CGGeneric(define="#include <stdint.h>\n"),
|
|
CGGeneric(define="#include <type_traits>\n\n"),
|
|
CGGeneric(define='#include "js/experimental/JitInfo.h"\n\n'),
|
|
CGGeneric(define='#include "mozilla/dom/PrototypeList.h"\n\n'),
|
|
idEnum,
|
|
]
|
|
)
|
|
|
|
# Let things know the maximum length of the prototype chain.
|
|
maxMacroName = "MAX_PROTOTYPE_CHAIN_LENGTH"
|
|
maxMacro = CGGeneric(
|
|
declare="#define " + maxMacroName + " " + str(config.maxProtoChainLength)
|
|
)
|
|
curr.append(CGWrapper(maxMacro, post="\n\n"))
|
|
curr.append(
|
|
fieldSizeAssert(
|
|
maxMacroName, "depth", "Some inheritance chain is too long!"
|
|
)
|
|
)
|
|
|
|
# Constructor ID enum.
|
|
constructors = [d.name for d in config.getDescriptors(hasInterfaceObject=True)]
|
|
idEnum = CGNamespacedEnum(
|
|
"id",
|
|
"ID",
|
|
["_ID_Start"] + constructors,
|
|
["prototypes::id::_ID_Count", "_ID_Start"],
|
|
)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
idEnum = CGNamespace.build(
|
|
["mozilla", "dom", "constructors"], CGWrapper(idEnum, pre="\n")
|
|
)
|
|
idEnum = CGWrapper(idEnum, post="\n")
|
|
|
|
curr.append(idEnum)
|
|
|
|
# Named properties object enum.
|
|
namedPropertiesObjects = [
|
|
d.name for d in config.getDescriptors(hasNamedPropertiesObject=True)
|
|
]
|
|
idEnum = CGNamespacedEnum(
|
|
"id",
|
|
"ID",
|
|
["_ID_Start"] + namedPropertiesObjects,
|
|
["constructors::id::_ID_Count", "_ID_Start"],
|
|
)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
idEnum = CGNamespace.build(
|
|
["mozilla", "dom", "namedpropertiesobjects"], CGWrapper(idEnum, pre="\n")
|
|
)
|
|
idEnum = CGWrapper(idEnum, post="\n")
|
|
|
|
curr.append(idEnum)
|
|
|
|
traitsDecls = [
|
|
CGGeneric(
|
|
declare=dedent(
|
|
"""
|
|
template <prototypes::ID PrototypeID>
|
|
struct PrototypeTraits;
|
|
"""
|
|
)
|
|
)
|
|
]
|
|
traitsDecls.extend(CGPrototypeTraitsClass(d) for d in descriptorsWithPrototype)
|
|
|
|
ifaceNamesWithProto = [
|
|
d.interface.identifier.name for d in descriptorsWithPrototype
|
|
]
|
|
traitsDecls.append(
|
|
CGStringTable("NamesOfInterfacesWithProtos", ifaceNamesWithProto)
|
|
)
|
|
|
|
traitsDecl = CGNamespace.build(["mozilla", "dom"], CGList(traitsDecls))
|
|
|
|
curr.append(traitsDecl)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("PrototypeList", curr)
|
|
|
|
# Add the auto-generated comment.
|
|
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def RegisterBindings(config):
|
|
|
|
curr = CGNamespace.build(["mozilla", "dom"], CGGlobalNames(config))
|
|
curr = CGWrapper(curr, post="\n")
|
|
|
|
# Add the includes
|
|
defineIncludes = [
|
|
CGHeaders.getDeclarationFilename(desc.interface)
|
|
for desc in config.getDescriptors(
|
|
hasInterfaceObject=True, isExposedInWindow=True, register=True
|
|
)
|
|
]
|
|
defineIncludes.append("mozilla/dom/WebIDLGlobalNameHash.h")
|
|
defineIncludes.append("mozilla/dom/PrototypeList.h")
|
|
defineIncludes.append("mozilla/PerfectHash.h")
|
|
defineIncludes.append("js/String.h")
|
|
curr = CGHeaders([], [], [], [], [], defineIncludes, "RegisterBindings", curr)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("RegisterBindings", curr)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def RegisterWorkerBindings(config):
|
|
|
|
curr = CGRegisterWorkerBindings(config)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n"))
|
|
curr = CGWrapper(curr, post="\n")
|
|
|
|
# Add the includes
|
|
defineIncludes = [
|
|
CGHeaders.getDeclarationFilename(desc.interface)
|
|
for desc in config.getDescriptors(
|
|
hasInterfaceObject=True, register=True, isExposedInAnyWorker=True
|
|
)
|
|
]
|
|
|
|
curr = CGHeaders(
|
|
[], [], [], [], [], defineIncludes, "RegisterWorkerBindings", curr
|
|
)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("RegisterWorkerBindings", curr)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def RegisterWorkerDebuggerBindings(config):
|
|
|
|
curr = CGRegisterWorkerDebuggerBindings(config)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n"))
|
|
curr = CGWrapper(curr, post="\n")
|
|
|
|
# Add the includes
|
|
defineIncludes = [
|
|
CGHeaders.getDeclarationFilename(desc.interface)
|
|
for desc in config.getDescriptors(
|
|
hasInterfaceObject=True, register=True, isExposedInWorkerDebugger=True
|
|
)
|
|
]
|
|
|
|
curr = CGHeaders(
|
|
[], [], [], [], [], defineIncludes, "RegisterWorkerDebuggerBindings", curr
|
|
)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("RegisterWorkerDebuggerBindings", curr)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def RegisterWorkletBindings(config):
|
|
|
|
curr = CGRegisterWorkletBindings(config)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n"))
|
|
curr = CGWrapper(curr, post="\n")
|
|
|
|
# Add the includes
|
|
defineIncludes = [
|
|
CGHeaders.getDeclarationFilename(desc.interface)
|
|
for desc in config.getDescriptors(
|
|
hasInterfaceObject=True, register=True, isExposedInAnyWorklet=True
|
|
)
|
|
]
|
|
|
|
curr = CGHeaders(
|
|
[], [], [], [], [], defineIncludes, "RegisterWorkletBindings", curr
|
|
)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("RegisterWorkletBindings", curr)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def UnionTypes(config):
|
|
unionTypes = UnionsForFile(config, None)
|
|
(
|
|
includes,
|
|
implincludes,
|
|
declarations,
|
|
traverseMethods,
|
|
unlinkMethods,
|
|
unionStructs,
|
|
) = UnionTypes(unionTypes, config)
|
|
|
|
unions = CGList(
|
|
traverseMethods
|
|
+ unlinkMethods
|
|
+ [CGUnionStruct(t, config) for t in unionStructs]
|
|
+ [CGUnionStruct(t, config, True) for t in unionStructs],
|
|
"\n",
|
|
)
|
|
|
|
includes.add("mozilla/OwningNonNull.h")
|
|
includes.add("mozilla/dom/UnionMember.h")
|
|
includes.add("mozilla/dom/BindingDeclarations.h")
|
|
# BindingUtils.h is only needed for SetToObject.
|
|
# If it stops being inlined or stops calling CallerSubsumes
|
|
# both this bit and the bit in CGBindingRoot can be removed.
|
|
includes.add("mozilla/dom/BindingUtils.h")
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(["mozilla", "dom"], unions)
|
|
|
|
curr = CGWrapper(curr, post="\n")
|
|
|
|
builder = ForwardDeclarationBuilder()
|
|
for className, isStruct in declarations:
|
|
builder.add(className, isStruct=isStruct)
|
|
|
|
curr = CGList([builder.build(), curr], "\n")
|
|
|
|
curr = CGHeaders([], [], [], [], includes, implincludes, "UnionTypes", curr)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("UnionTypes", curr)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def UnionConversions(config):
|
|
unionTypes = []
|
|
for l in six.itervalues(config.unionsPerFilename):
|
|
unionTypes.extend(l)
|
|
unionTypes.sort(key=lambda u: u.name)
|
|
headers, unions = UnionConversions(unionTypes, config)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(["mozilla", "dom"], unions)
|
|
|
|
curr = CGWrapper(curr, post="\n")
|
|
|
|
headers.update(["nsDebug.h", "mozilla/dom/UnionTypes.h"])
|
|
curr = CGHeaders([], [], [], [], headers, [], "UnionConversions", curr)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("UnionConversions", curr)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def WebIDLPrefs(config):
|
|
prefs = set()
|
|
headers = set(["mozilla/dom/WebIDLPrefs.h"])
|
|
for d in config.getDescriptors(hasInterfaceOrInterfacePrototypeObject=True):
|
|
for m in d.interface.members:
|
|
pref = PropertyDefiner.getStringAttr(m, "Pref")
|
|
if pref:
|
|
headers.add(prefHeader(pref))
|
|
prefs.add((pref, prefIdentifier(pref)))
|
|
prefs = sorted(prefs)
|
|
declare = fill(
|
|
"""
|
|
enum class WebIDLPrefIndex : uint8_t {
|
|
NoPref,
|
|
$*{prefs}
|
|
};
|
|
typedef bool (*WebIDLPrefFunc)();
|
|
extern const WebIDLPrefFunc sWebIDLPrefs[${len}];
|
|
""",
|
|
prefs=",\n".join(map(lambda p: "// " + p[0] + "\n" + p[1], prefs)) + "\n",
|
|
len=len(prefs) + 1,
|
|
)
|
|
define = fill(
|
|
"""
|
|
const WebIDLPrefFunc sWebIDLPrefs[] = {
|
|
nullptr,
|
|
$*{prefs}
|
|
};
|
|
""",
|
|
prefs=",\n".join(
|
|
map(lambda p: "// " + p[0] + "\nStaticPrefs::" + p[1], prefs)
|
|
)
|
|
+ "\n",
|
|
)
|
|
prefFunctions = CGGeneric(declare=declare, define=define)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(["mozilla", "dom"], prefFunctions)
|
|
|
|
curr = CGWrapper(curr, post="\n")
|
|
|
|
curr = CGHeaders([], [], [], [], [], headers, "WebIDLPrefs", curr)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("WebIDLPrefs", curr)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
@staticmethod
|
|
def WebIDLSerializable(config):
|
|
# We need a declaration of StructuredCloneTags in the header.
|
|
declareIncludes = set(
|
|
[
|
|
"mozilla/dom/DOMJSClass.h",
|
|
"mozilla/dom/StructuredCloneTags.h",
|
|
"js/TypeDecls.h",
|
|
]
|
|
)
|
|
defineIncludes = set(
|
|
["mozilla/dom/WebIDLSerializable.h", "mozilla/PerfectHash.h"]
|
|
)
|
|
names = list()
|
|
for d in config.getDescriptors(isSerializable=True):
|
|
names.append(d.name)
|
|
defineIncludes.add(CGHeaders.getDeclarationFilename(d.interface))
|
|
|
|
if len(names) == 0:
|
|
# We can't really create a PerfectHash out of this, but also there's
|
|
# not much point to this file if we have no [Serializable] objects.
|
|
# Just spit out an empty file.
|
|
return CGIncludeGuard("WebIDLSerializable", CGGeneric(""))
|
|
|
|
# If we had a lot of serializable things, it might be worth it to use a
|
|
# PerfectHash here, or an array ordered by sctag value and binary
|
|
# search. But setting those up would require knowing in this python
|
|
# code the values of the various SCTAG_DOM_*. We could hardcode them
|
|
# here and add static asserts that the values are right, or switch to
|
|
# code-generating StructuredCloneTags.h or something. But in practice,
|
|
# there's a pretty small number of serializable interfaces, and just
|
|
# doing a linear walk is fine. It's not obviously worse than the
|
|
# if-cascade we used to have. Let's just make sure we notice if we do
|
|
# end up with a lot of serializable things here.
|
|
#
|
|
# Also, in practice it looks like compilers compile this linear walk to
|
|
# an out-of-bounds check followed by a direct index into an array, by
|
|
# basically making a second copy of this array ordered by tag, with the
|
|
# holes filled in. Again, worth checking whether this still happens if
|
|
# we have too many serializable things.
|
|
if len(names) > 20:
|
|
raise TypeError(
|
|
"We now have %s serializable interfaces. "
|
|
"Double-check that the compiler is still "
|
|
"generating a jump table." % len(names)
|
|
)
|
|
|
|
entries = list()
|
|
# Make sure we have stable ordering.
|
|
for name in sorted(names):
|
|
# Strip off trailing newline to make our formatting look right.
|
|
entries.append(
|
|
fill(
|
|
"""
|
|
{
|
|
/* mTag */ ${tag},
|
|
/* mDeserialize */ ${name}_Binding::Deserialize
|
|
}
|
|
""",
|
|
tag=StructuredCloneTag(name),
|
|
name=name,
|
|
)[:-1]
|
|
)
|
|
|
|
declare = dedent(
|
|
"""
|
|
WebIDLDeserializer LookupDeserializer(StructuredCloneTags aTag);
|
|
"""
|
|
)
|
|
define = fill(
|
|
"""
|
|
struct WebIDLSerializableEntry {
|
|
StructuredCloneTags mTag;
|
|
WebIDLDeserializer mDeserialize;
|
|
};
|
|
|
|
static const WebIDLSerializableEntry sEntries[] = {
|
|
$*{entries}
|
|
};
|
|
|
|
WebIDLDeserializer LookupDeserializer(StructuredCloneTags aTag) {
|
|
for (auto& entry : sEntries) {
|
|
if (entry.mTag == aTag) {
|
|
return entry.mDeserialize;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
""",
|
|
entries=",\n".join(entries) + "\n",
|
|
)
|
|
|
|
code = CGGeneric(declare=declare, define=define)
|
|
|
|
# Wrap all of that in our namespaces.
|
|
curr = CGNamespace.build(["mozilla", "dom"], code)
|
|
|
|
curr = CGWrapper(curr, post="\n")
|
|
|
|
curr = CGHeaders(
|
|
[], [], [], [], declareIncludes, defineIncludes, "WebIDLSerializable", curr
|
|
)
|
|
|
|
# Add include guards.
|
|
curr = CGIncludeGuard("WebIDLSerializable", curr)
|
|
|
|
# Done.
|
|
return curr
|
|
|
|
|
|
# Code generator for simple events
|
|
class CGEventGetter(CGNativeMember):
|
|
def __init__(self, descriptor, attr):
|
|
ea = descriptor.getExtendedAttributes(attr, getter=True)
|
|
CGNativeMember.__init__(
|
|
self,
|
|
descriptor,
|
|
attr,
|
|
CGSpecializedGetter.makeNativeName(descriptor, attr),
|
|
(attr.type, []),
|
|
ea,
|
|
resultNotAddRefed=not attr.type.isSequence(),
|
|
)
|
|
self.body = self.getMethodBody()
|
|
|
|
def getArgs(self, returnType, argList):
|
|
if "infallible" not in self.extendedAttrs:
|
|
raise TypeError("Event code generator does not support [Throws]!")
|
|
if "canOOM" in self.extendedAttrs:
|
|
raise TypeError("Event code generator does not support [CanOOM]!")
|
|
if not self.member.isAttr():
|
|
raise TypeError("Event code generator does not support methods")
|
|
if self.member.isStatic():
|
|
raise TypeError("Event code generators does not support static attributes")
|
|
return CGNativeMember.getArgs(self, returnType, argList)
|
|
|
|
def getMethodBody(self):
|
|
type = self.member.type
|
|
memberName = CGDictionary.makeMemberName(self.member.identifier.name)
|
|
if (
|
|
(type.isPrimitive() and type.tag() in builtinNames)
|
|
or type.isEnum()
|
|
or type.isPromise()
|
|
or type.isGeckoInterface()
|
|
):
|
|
return "return " + memberName + ";\n"
|
|
if type.isJSString():
|
|
# https://bugzilla.mozilla.org/show_bug.cgi?id=1580167
|
|
raise TypeError("JSString not supported as member of a generated event")
|
|
if (
|
|
type.isDOMString()
|
|
or type.isByteString()
|
|
or type.isUSVString()
|
|
or type.isUTF8String()
|
|
):
|
|
return "aRetVal = " + memberName + ";\n"
|
|
if type.isSpiderMonkeyInterface() or type.isObject():
|
|
return fill(
|
|
"""
|
|
if (${memberName}) {
|
|
JS::ExposeObjectToActiveJS(${memberName});
|
|
}
|
|
aRetVal.set(${memberName});
|
|
return;
|
|
""",
|
|
memberName=memberName,
|
|
)
|
|
if type.isAny():
|
|
return fill(
|
|
"""
|
|
${selfName}(aRetVal);
|
|
""",
|
|
selfName=self.name,
|
|
)
|
|
if type.isUnion():
|
|
return "aRetVal = " + memberName + ";\n"
|
|
if type.isSequence():
|
|
if type.nullable():
|
|
return (
|
|
"if ("
|
|
+ memberName
|
|
+ ".IsNull()) { aRetVal.SetNull(); } else { aRetVal.SetValue("
|
|
+ memberName
|
|
+ ".Value().Clone()); }\n"
|
|
)
|
|
else:
|
|
return "aRetVal = " + memberName + ".Clone();\n"
|
|
raise TypeError("Event code generator does not support this type!")
|
|
|
|
def declare(self, cgClass):
|
|
if (
|
|
getattr(self.member, "originatingInterface", cgClass.descriptor.interface)
|
|
!= cgClass.descriptor.interface
|
|
):
|
|
return ""
|
|
return CGNativeMember.declare(self, cgClass)
|
|
|
|
def define(self, cgClass):
|
|
if (
|
|
getattr(self.member, "originatingInterface", cgClass.descriptor.interface)
|
|
!= cgClass.descriptor.interface
|
|
):
|
|
return ""
|
|
return CGNativeMember.define(self, cgClass)
|
|
|
|
|
|
class CGEventSetter(CGNativeMember):
|
|
def __init__(self):
|
|
raise TypeError("Event code generator does not support setters!")
|
|
|
|
|
|
class CGEventMethod(CGNativeMember):
|
|
def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
|
|
self.isInit = False
|
|
|
|
CGNativeMember.__init__(
|
|
self,
|
|
descriptor,
|
|
method,
|
|
CGSpecializedMethod.makeNativeName(descriptor, method),
|
|
signature,
|
|
descriptor.getExtendedAttributes(method),
|
|
breakAfter=breakAfter,
|
|
variadicIsSequence=True,
|
|
)
|
|
self.originalArgs = list(self.args)
|
|
|
|
iface = descriptor.interface
|
|
allowed = isConstructor
|
|
if not allowed and iface.getExtendedAttribute("LegacyEventInit"):
|
|
# Allow it, only if it fits the initFooEvent profile exactly
|
|
# We could check the arg types but it's not worth the effort.
|
|
if (
|
|
method.identifier.name == "init" + iface.identifier.name
|
|
and signature[1][0].type.isDOMString()
|
|
and signature[1][1].type.isBoolean()
|
|
and signature[1][2].type.isBoolean()
|
|
and
|
|
# -3 on the left to ignore the type, bubbles, and cancelable parameters
|
|
# -1 on the right to ignore the .trusted property which bleeds through
|
|
# here because it is [Unforgeable].
|
|
len(signature[1]) - 3
|
|
== len([x for x in iface.members if x.isAttr()]) - 1
|
|
):
|
|
allowed = True
|
|
self.isInit = True
|
|
|
|
if not allowed:
|
|
raise TypeError("Event code generator does not support methods!")
|
|
|
|
def getArgs(self, returnType, argList):
|
|
args = [self.getArg(arg) for arg in argList]
|
|
return args
|
|
|
|
def getArg(self, arg):
|
|
decl, ref = self.getArgType(
|
|
arg.type, arg.canHaveMissingValue(), "Variadic" if arg.variadic else False
|
|
)
|
|
if ref:
|
|
decl = CGWrapper(decl, pre="const ", post="&")
|
|
|
|
name = arg.identifier.name
|
|
name = "a" + name[0].upper() + name[1:]
|
|
return Argument(decl.define(), name)
|
|
|
|
def declare(self, cgClass):
|
|
if self.isInit:
|
|
constructorForNativeCaller = ""
|
|
else:
|
|
self.args = list(self.originalArgs)
|
|
self.args.insert(0, Argument("mozilla::dom::EventTarget*", "aOwner"))
|
|
constructorForNativeCaller = CGNativeMember.declare(self, cgClass)
|
|
|
|
self.args = list(self.originalArgs)
|
|
if needCx(None, self.arguments(), [], considerTypes=True, static=True):
|
|
self.args.insert(0, Argument("JSContext*", "aCx"))
|
|
if not self.isInit:
|
|
self.args.insert(0, Argument("const GlobalObject&", "aGlobal"))
|
|
|
|
return constructorForNativeCaller + CGNativeMember.declare(self, cgClass)
|
|
|
|
def defineInit(self, cgClass):
|
|
iface = self.descriptorProvider.interface
|
|
members = ""
|
|
while iface.identifier.name != "Event":
|
|
i = 3 # Skip the boilerplate args: type, bubble,s cancelable.
|
|
for m in iface.members:
|
|
if m.isAttr():
|
|
# We need to initialize all the member variables that do
|
|
# not come from Event.
|
|
if (
|
|
getattr(m, "originatingInterface", iface).identifier.name
|
|
== "Event"
|
|
):
|
|
continue
|
|
name = CGDictionary.makeMemberName(m.identifier.name)
|
|
members += "%s = %s;\n" % (name, self.args[i].name)
|
|
i += 1
|
|
iface = iface.parent
|
|
|
|
self.body = fill(
|
|
"""
|
|
InitEvent(${typeArg}, ${bubblesArg}, ${cancelableArg});
|
|
${members}
|
|
""",
|
|
typeArg=self.args[0].name,
|
|
bubblesArg=self.args[1].name,
|
|
cancelableArg=self.args[2].name,
|
|
members=members,
|
|
)
|
|
|
|
return CGNativeMember.define(self, cgClass)
|
|
|
|
def define(self, cgClass):
|
|
self.args = list(self.originalArgs)
|
|
if self.isInit:
|
|
return self.defineInit(cgClass)
|
|
members = ""
|
|
holdJS = ""
|
|
iface = self.descriptorProvider.interface
|
|
while iface.identifier.name != "Event":
|
|
for m in self.descriptorProvider.getDescriptor(
|
|
iface.identifier.name
|
|
).interface.members:
|
|
if m.isAttr():
|
|
# We initialize all the other member variables in the
|
|
# Constructor except those ones coming from the Event.
|
|
if (
|
|
getattr(
|
|
m, "originatingInterface", cgClass.descriptor.interface
|
|
).identifier.name
|
|
== "Event"
|
|
):
|
|
continue
|
|
name = CGDictionary.makeMemberName(m.identifier.name)
|
|
if m.type.isSequence():
|
|
# For sequences we may not be able to do a simple
|
|
# assignment because the underlying types may not match.
|
|
# For example, the argument can be a
|
|
# Sequence<OwningNonNull<SomeInterface>> while our
|
|
# member is an nsTArray<RefPtr<SomeInterface>>. So
|
|
# use AppendElements, which is actually a template on
|
|
# the incoming type on nsTArray and does the right thing
|
|
# for this case.
|
|
target = name
|
|
source = "%s.%s" % (self.args[1].name, name)
|
|
sequenceCopy = "e->%s.AppendElements(%s);\n"
|
|
if m.type.nullable():
|
|
sequenceCopy = CGIfWrapper(
|
|
CGGeneric(sequenceCopy), "!%s.IsNull()" % source
|
|
).define()
|
|
target += ".SetValue()"
|
|
source += ".Value()"
|
|
members += sequenceCopy % (target, source)
|
|
elif m.type.isSpiderMonkeyInterface():
|
|
srcname = "%s.%s" % (self.args[1].name, name)
|
|
if m.type.nullable():
|
|
members += fill(
|
|
"""
|
|
if (${srcname}.IsNull()) {
|
|
e->${varname} = nullptr;
|
|
} else {
|
|
e->${varname} = ${srcname}.Value().Obj();
|
|
}
|
|
""",
|
|
varname=name,
|
|
srcname=srcname,
|
|
)
|
|
else:
|
|
members += fill(
|
|
"""
|
|
e->${varname}.set(${srcname}.Obj());
|
|
""",
|
|
varname=name,
|
|
srcname=srcname,
|
|
)
|
|
else:
|
|
members += "e->%s = %s.%s;\n" % (name, self.args[1].name, name)
|
|
if (
|
|
m.type.isAny()
|
|
or m.type.isObject()
|
|
or m.type.isSpiderMonkeyInterface()
|
|
):
|
|
holdJS = "mozilla::HoldJSObjects(e.get());\n"
|
|
iface = iface.parent
|
|
|
|
self.body = fill(
|
|
"""
|
|
RefPtr<${nativeType}> e = new ${nativeType}(aOwner);
|
|
bool trusted = e->Init(aOwner);
|
|
e->InitEvent(${eventType}, ${eventInit}.mBubbles, ${eventInit}.mCancelable);
|
|
$*{members}
|
|
e->SetTrusted(trusted);
|
|
e->SetComposed(${eventInit}.mComposed);
|
|
$*{holdJS}
|
|
return e.forget();
|
|
""",
|
|
nativeType=self.descriptorProvider.nativeType.split("::")[-1],
|
|
eventType=self.args[0].name,
|
|
eventInit=self.args[1].name,
|
|
members=members,
|
|
holdJS=holdJS,
|
|
)
|
|
|
|
self.args.insert(0, Argument("mozilla::dom::EventTarget*", "aOwner"))
|
|
constructorForNativeCaller = CGNativeMember.define(self, cgClass) + "\n"
|
|
self.args = list(self.originalArgs)
|
|
self.body = fill(
|
|
"""
|
|
nsCOMPtr<mozilla::dom::EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
|
|
return Constructor(owner, ${arg0}, ${arg1});
|
|
""",
|
|
arg0=self.args[0].name,
|
|
arg1=self.args[1].name,
|
|
)
|
|
if needCx(None, self.arguments(), [], considerTypes=True, static=True):
|
|
self.args.insert(0, Argument("JSContext*", "aCx"))
|
|
self.args.insert(0, Argument("const GlobalObject&", "aGlobal"))
|
|
return constructorForNativeCaller + CGNativeMember.define(self, cgClass)
|
|
|
|
|
|
class CGEventClass(CGBindingImplClass):
|
|
"""
|
|
Codegen for the actual Event class implementation for this descriptor
|
|
"""
|
|
|
|
def __init__(self, descriptor):
|
|
CGBindingImplClass.__init__(
|
|
self,
|
|
descriptor,
|
|
CGEventMethod,
|
|
CGEventGetter,
|
|
CGEventSetter,
|
|
False,
|
|
"WrapObjectInternal",
|
|
)
|
|
members = []
|
|
extraMethods = []
|
|
self.membersNeedingCC = []
|
|
self.membersNeedingTrace = []
|
|
|
|
for m in descriptor.interface.members:
|
|
if (
|
|
getattr(m, "originatingInterface", descriptor.interface)
|
|
!= descriptor.interface
|
|
):
|
|
continue
|
|
|
|
if m.isAttr():
|
|
if m.type.isAny():
|
|
self.membersNeedingTrace.append(m)
|
|
# Add a getter that doesn't need a JSContext. Note that we
|
|
# don't need to do this if our originating interface is not
|
|
# the descriptor's interface, because in that case we
|
|
# wouldn't generate the getter that _does_ need a JSContext
|
|
# either.
|
|
extraMethods.append(
|
|
ClassMethod(
|
|
CGSpecializedGetter.makeNativeName(descriptor, m),
|
|
"void",
|
|
[Argument("JS::MutableHandle<JS::Value>", "aRetVal")],
|
|
const=True,
|
|
body=fill(
|
|
"""
|
|
JS::ExposeValueToActiveJS(${memberName});
|
|
aRetVal.set(${memberName});
|
|
""",
|
|
memberName=CGDictionary.makeMemberName(
|
|
m.identifier.name
|
|
),
|
|
),
|
|
)
|
|
)
|
|
elif m.type.isObject() or m.type.isSpiderMonkeyInterface():
|
|
self.membersNeedingTrace.append(m)
|
|
elif typeNeedsRooting(m.type):
|
|
raise TypeError(
|
|
"Need to implement tracing for event member of type %s" % m.type
|
|
)
|
|
elif idlTypeNeedsCycleCollection(m.type):
|
|
self.membersNeedingCC.append(m)
|
|
|
|
nativeType = self.getNativeTypeForIDLType(m.type).define()
|
|
members.append(
|
|
ClassMember(
|
|
CGDictionary.makeMemberName(m.identifier.name),
|
|
nativeType,
|
|
visibility="private",
|
|
body="body",
|
|
)
|
|
)
|
|
|
|
parent = self.descriptor.interface.parent
|
|
self.parentType = self.descriptor.getDescriptor(
|
|
parent.identifier.name
|
|
).nativeType.split("::")[-1]
|
|
self.nativeType = self.descriptor.nativeType.split("::")[-1]
|
|
|
|
if self.needCC():
|
|
isupportsDecl = fill(
|
|
"""
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(${nativeType}, ${parentType})
|
|
""",
|
|
nativeType=self.nativeType,
|
|
parentType=self.parentType,
|
|
)
|
|
else:
|
|
isupportsDecl = fill(
|
|
"""
|
|
NS_INLINE_DECL_REFCOUNTING_INHERITED(${nativeType}, ${parentType})
|
|
""",
|
|
nativeType=self.nativeType,
|
|
parentType=self.parentType,
|
|
)
|
|
|
|
baseDeclarations = fill(
|
|
"""
|
|
public:
|
|
$*{isupportsDecl}
|
|
|
|
protected:
|
|
virtual ~${nativeType}();
|
|
explicit ${nativeType}(mozilla::dom::EventTarget* aOwner);
|
|
|
|
""",
|
|
isupportsDecl=isupportsDecl,
|
|
nativeType=self.nativeType,
|
|
parentType=self.parentType,
|
|
)
|
|
|
|
className = self.nativeType
|
|
asConcreteTypeMethod = ClassMethod(
|
|
"As%s" % className,
|
|
"%s*" % className,
|
|
[],
|
|
virtual=True,
|
|
body="return this;\n",
|
|
breakAfterReturnDecl=" ",
|
|
override=True,
|
|
)
|
|
extraMethods.append(asConcreteTypeMethod)
|
|
|
|
CGClass.__init__(
|
|
self,
|
|
className,
|
|
bases=[ClassBase(self.parentType)],
|
|
methods=extraMethods + self.methodDecls,
|
|
members=members,
|
|
extradeclarations=baseDeclarations,
|
|
)
|
|
|
|
def getWrapObjectBody(self):
|
|
return (
|
|
"return %s_Binding::Wrap(aCx, this, aGivenProto);\n" % self.descriptor.name
|
|
)
|
|
|
|
def needCC(self):
|
|
return len(self.membersNeedingCC) != 0 or len(self.membersNeedingTrace) != 0
|
|
|
|
def implTraverse(self):
|
|
retVal = ""
|
|
for m in self.membersNeedingCC:
|
|
retVal += (
|
|
" NS_IMPL_CYCLE_COLLECTION_TRAVERSE(%s)\n"
|
|
% CGDictionary.makeMemberName(m.identifier.name)
|
|
)
|
|
return retVal
|
|
|
|
def implUnlink(self):
|
|
retVal = ""
|
|
for m in self.membersNeedingCC:
|
|
retVal += (
|
|
" NS_IMPL_CYCLE_COLLECTION_UNLINK(%s)\n"
|
|
% CGDictionary.makeMemberName(m.identifier.name)
|
|
)
|
|
for m in self.membersNeedingTrace:
|
|
name = CGDictionary.makeMemberName(m.identifier.name)
|
|
if m.type.isAny():
|
|
retVal += " tmp->" + name + ".setUndefined();\n"
|
|
elif m.type.isObject() or m.type.isSpiderMonkeyInterface():
|
|
retVal += " tmp->" + name + " = nullptr;\n"
|
|
else:
|
|
raise TypeError("Unknown traceable member type %s" % m.type)
|
|
return retVal
|
|
|
|
def implTrace(self):
|
|
retVal = ""
|
|
for m in self.membersNeedingTrace:
|
|
retVal += (
|
|
" NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(%s)\n"
|
|
% CGDictionary.makeMemberName(m.identifier.name)
|
|
)
|
|
return retVal
|
|
|
|
def define(self):
|
|
dropJS = ""
|
|
for m in self.membersNeedingTrace:
|
|
member = CGDictionary.makeMemberName(m.identifier.name)
|
|
if m.type.isAny():
|
|
dropJS += member + " = JS::UndefinedValue();\n"
|
|
elif m.type.isObject() or m.type.isSpiderMonkeyInterface():
|
|
dropJS += member + " = nullptr;\n"
|
|
else:
|
|
raise TypeError("Unknown traceable member type %s" % m.type)
|
|
|
|
if dropJS != "":
|
|
dropJS += "mozilla::DropJSObjects(this);\n"
|
|
# Just override CGClass and do our own thing
|
|
ctorParams = (
|
|
"aOwner, nullptr, nullptr" if self.parentType == "Event" else "aOwner"
|
|
)
|
|
|
|
if self.needCC():
|
|
classImpl = fill(
|
|
"""
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(${nativeType})
|
|
|
|
NS_IMPL_ADDREF_INHERITED(${nativeType}, ${parentType})
|
|
NS_IMPL_RELEASE_INHERITED(${nativeType}, ${parentType})
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(${nativeType}, ${parentType})
|
|
$*{traverse}
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(${nativeType}, ${parentType})
|
|
$*{trace}
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(${nativeType}, ${parentType})
|
|
$*{unlink}
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType})
|
|
NS_INTERFACE_MAP_END_INHERITING(${parentType})
|
|
""",
|
|
nativeType=self.nativeType,
|
|
parentType=self.parentType,
|
|
traverse=self.implTraverse(),
|
|
unlink=self.implUnlink(),
|
|
trace=self.implTrace(),
|
|
)
|
|
else:
|
|
classImpl = ""
|
|
|
|
classImpl += fill(
|
|
"""
|
|
|
|
${nativeType}::${nativeType}(mozilla::dom::EventTarget* aOwner)
|
|
: ${parentType}(${ctorParams})
|
|
{
|
|
}
|
|
|
|
${nativeType}::~${nativeType}()
|
|
{
|
|
$*{dropJS}
|
|
}
|
|
|
|
""",
|
|
nativeType=self.nativeType,
|
|
ctorParams=ctorParams,
|
|
parentType=self.parentType,
|
|
dropJS=dropJS,
|
|
)
|
|
|
|
return classImpl + CGBindingImplClass.define(self)
|
|
|
|
def getNativeTypeForIDLType(self, type):
|
|
if type.isPrimitive() and type.tag() in builtinNames:
|
|
nativeType = CGGeneric(builtinNames[type.tag()])
|
|
if type.nullable():
|
|
nativeType = CGTemplatedType("Nullable", nativeType)
|
|
elif type.isEnum():
|
|
nativeType = CGGeneric(type.unroll().inner.identifier.name)
|
|
if type.nullable():
|
|
nativeType = CGTemplatedType("Nullable", nativeType)
|
|
elif type.isJSString():
|
|
nativeType = CGGeneric("JS::Heap<JSString*>")
|
|
elif type.isDOMString() or type.isUSVString():
|
|
nativeType = CGGeneric("nsString")
|
|
elif type.isByteString() or type.isUTF8String():
|
|
nativeType = CGGeneric("nsCString")
|
|
elif type.isPromise():
|
|
nativeType = CGGeneric("RefPtr<Promise>")
|
|
elif type.isGeckoInterface():
|
|
iface = type.unroll().inner
|
|
nativeType = self.descriptor.getDescriptor(iface.identifier.name).nativeType
|
|
# Now trim off unnecessary namespaces
|
|
nativeType = nativeType.split("::")
|
|
if nativeType[0] == "mozilla":
|
|
nativeType.pop(0)
|
|
if nativeType[0] == "dom":
|
|
nativeType.pop(0)
|
|
nativeType = CGWrapper(
|
|
CGGeneric("::".join(nativeType)), pre="RefPtr<", post=">"
|
|
)
|
|
elif type.isAny():
|
|
nativeType = CGGeneric("JS::Heap<JS::Value>")
|
|
elif type.isObject() or type.isSpiderMonkeyInterface():
|
|
nativeType = CGGeneric("JS::Heap<JSObject*>")
|
|
elif type.isUnion():
|
|
nativeType = CGGeneric(CGUnionStruct.unionTypeDecl(type, True))
|
|
elif type.isSequence():
|
|
if type.nullable():
|
|
innerType = type.inner.inner
|
|
else:
|
|
innerType = type.inner
|
|
if (
|
|
not innerType.isPrimitive()
|
|
and not innerType.isEnum()
|
|
and not innerType.isDOMString()
|
|
and not innerType.isByteString()
|
|
and not innerType.isUTF8String()
|
|
and not innerType.isPromise()
|
|
and not innerType.isGeckoInterface()
|
|
):
|
|
raise TypeError(
|
|
"Don't know how to properly manage GC/CC for "
|
|
"event member of type %s" % type
|
|
)
|
|
nativeType = CGTemplatedType(
|
|
"nsTArray", self.getNativeTypeForIDLType(innerType)
|
|
)
|
|
if type.nullable():
|
|
nativeType = CGTemplatedType("Nullable", nativeType)
|
|
else:
|
|
raise TypeError("Don't know how to declare event member of type %s" % type)
|
|
return nativeType
|
|
|
|
|
|
class CGEventRoot(CGThing):
|
|
def __init__(self, config, interfaceName):
|
|
descriptor = config.getDescriptor(interfaceName)
|
|
|
|
self.root = CGWrapper(CGEventClass(descriptor), pre="\n", post="\n")
|
|
|
|
self.root = CGNamespace.build(["mozilla", "dom"], self.root)
|
|
|
|
self.root = CGList(
|
|
[CGClassForwardDeclare("JSContext", isStruct=True), self.root]
|
|
)
|
|
|
|
parent = descriptor.interface.parent.identifier.name
|
|
|
|
# Throw in our #includes
|
|
self.root = CGHeaders(
|
|
[descriptor],
|
|
[],
|
|
[],
|
|
[],
|
|
[
|
|
config.getDescriptor(parent).headerFile,
|
|
"mozilla/Attributes.h",
|
|
"mozilla/ErrorResult.h",
|
|
"mozilla/dom/%sBinding.h" % interfaceName,
|
|
"mozilla/dom/BindingUtils.h",
|
|
],
|
|
[
|
|
"%s.h" % interfaceName,
|
|
"js/GCAPI.h",
|
|
"mozilla/dom/Nullable.h",
|
|
],
|
|
"",
|
|
self.root,
|
|
config,
|
|
)
|
|
|
|
# And now some include guards
|
|
self.root = CGIncludeGuard(interfaceName, self.root)
|
|
|
|
self.root = CGWrapper(
|
|
self.root,
|
|
pre=(
|
|
AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT
|
|
% os.path.basename(descriptor.interface.filename())
|
|
),
|
|
)
|
|
|
|
self.root = CGWrapper(
|
|
self.root,
|
|
pre=dedent(
|
|
"""
|
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* 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/. */
|
|
|
|
"""
|
|
),
|
|
)
|
|
|
|
def declare(self):
|
|
return self.root.declare()
|
|
|
|
def define(self):
|
|
return self.root.define()
|