summaryrefslogtreecommitdiffstats
path: root/dom/bindings/Codegen.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/bindings/Codegen.py24278
1 files changed, 24278 insertions, 0 deletions
diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py
new file mode 100644
index 0000000000..809a680d70
--- /dev/null
+++ b/dom/bindings/Codegen.py
@@ -0,0 +1,24278 @@
+# 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 functools
+import math
+import os
+import re
+import string
+import textwrap
+
+import six
+from Configuration import (
+ Descriptor,
+ MemberIsLegacyUnforgeable,
+ NoSuchDescriptorError,
+ getAllTypes,
+ getTypesFromCallback,
+ getTypesFromDescriptor,
+ getTypesFromDictionary,
+)
+from perfecthash import PerfectHash
+from WebIDL import (
+ BuiltinTypes,
+ IDLAttribute,
+ IDLBuiltinType,
+ IDLDefaultDictionaryValue,
+ IDLDictionary,
+ IDLEmptySequenceValue,
+ IDLInterfaceMember,
+ IDLNullValue,
+ IDLSequenceType,
+ IDLType,
+ IDLUndefinedValue,
+)
+
+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.isUndefined()
+ or 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.isUndefined()
+ 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
+
+ if type.isUndefined():
+ # 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
+ # 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()
+ or type.isObservableArray()
+ ):
+ # 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 [LegacyTreatNonObjectAsNull] case already when
+ # unwrapping nullables.
+ return True
+ if type.isUnion():
+ # Can throw if a type not in the union is passed in.
+ return True
+ 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 function accessor for a WebIDL string table, using the existing
+ concatenated names string and mapping indexes to offsets in that string:
+
+ const char *accessorName(unsigned int index) {
+ static const uint16_t offsets = { ... };
+ return BindingName(offsets[index]);
+ }
+
+ This is more efficient than the more natural:
+
+ const char *table[] = {
+ ...
+ };
+
+ The uint16_t offsets are smaller than the pointer equivalents, and the
+ concatenated string 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 "const char *%s(unsigned int aIndex);\n" % self.accessorName
+
+ def define(self):
+ offsets = []
+ for s in self.strings:
+ offsets.append(BindingNamesOffsetEnum(s))
+ return fill(
+ """
+ ${static}const char *${name}(unsigned int aIndex)
+ {
+ static const BindingNamesOffset offsets[] = {
+ $*{offsets}
+ };
+ return BindingName(offsets[aIndex]);
+ }
+ """,
+ static="static " if self.static else "",
+ name=self.accessorName,
+ offsets="".join("BindingNamesOffset::%s,\n" % o for o in offsets),
+ )
+
+
+class CGNativePropertyHooks(CGThing):
+ """
+ Generate a NativePropertyHooks for a given descriptor
+ """
+
+ def __init__(self, descriptor, properties):
+ CGThing.__init__(self)
+ assert descriptor.wantsXrays
+ self.descriptor = descriptor
+ self.properties = properties
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ deleteNamedProperty = "nullptr"
+ if (
+ self.descriptor.concrete
+ and self.descriptor.proxy
+ and not self.descriptor.isMaybeCrossOriginObject()
+ ):
+ resolveOwnProperty = "binding_detail::ResolveOwnProperty"
+ enumerateOwnProperties = "binding_detail::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"
+
+ if self.descriptor.wantsXrayExpandoClass:
+ expandoClass = "&sXrayExpandoObjectClass"
+ else:
+ expandoClass = "&DefaultXrayExpandoObjectClass"
+
+ return fill(
+ """
+ bool sNativePropertiesInited = false;
+ const NativePropertyHooks sNativePropertyHooks = {
+ ${resolveOwnProperty},
+ ${enumerateOwnProperties},
+ ${deleteNamedProperty},
+ { ${regular}, ${chrome}, &sNativePropertiesInited },
+ ${prototypeID},
+ ${constructorID},
+ ${expandoClass}
+ };
+ """,
+ resolveOwnProperty=resolveOwnProperty,
+ enumerateOwnProperties=enumerateOwnProperties,
+ deleteNamedProperty=deleteNamedProperty,
+ regular=regular,
+ chrome=chrome,
+ prototypeID=prototypeID,
+ constructorID=constructorID,
+ 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"
+
+ if descriptor.hasOrdinaryObjectPrototype:
+ getProto = "JS::GetRealmObjectPrototypeHandle"
+ else:
+ getProto = "GetProtoObjectHandle"
+
+ return fill(
+ """
+ { ${protoChain} },
+ std::is_base_of_v<nsISupports, ${nativeType}>,
+ ${hooks},
+ FindAssociatedGlobalForNative<${nativeType}>::Get,
+ ${getProto},
+ GetCCParticipant<${nativeType}>::Get(),
+ ${serializer},
+ ${wrapperCacheGetter}
+ """,
+ protoChain=", ".join(protoList),
+ nativeType=descriptor.nativeType,
+ hooks=NativePropertyHooks(descriptor),
+ serializer=serializer,
+ wrapperCacheGetter=wrapperCacheGetter,
+ getProto=getProto,
+ )
+
+
+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, /* 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"
+ elif descriptor.interface.isAsyncIteratorInterface():
+ protoGetter = "JS::GetRealmAsyncIteratorPrototype"
+ 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.hasLegacyUnforgeableMembers
+ 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.legacyFactoryFunctions) > 0:
+ slotCount += " + %i /* slots for the legacy factory functions */" % len(
+ self.descriptor.interface.legacyFactoryFunctions
+ )
+ (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 */
+ ${ctorname}, /* construct */
+ nullptr, /* trace */
+ };
+
+ """,
+ ctorname=ctorname,
+ )
+ classOpsPtr = "&sInterfaceObjectClassOps"
+
+ if self.descriptor.interface.isNamespace():
+ classString = self.descriptor.interface.getExtendedAttribute("ClassString")
+ if classString is None:
+ classString = self.descriptor.interface.identifier.name
+ 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}
+ },
+ ${type},
+ ${needsHasInstance},
+ ${prototypeID},
+ ${depth},
+ ${hooks},
+ ${funToString},
+ ${protoGetter}
+ };
+ """,
+ classString=classString,
+ slotCount=slotCount,
+ classOpsPtr=classOpsPtr,
+ hooks=NativePropertyHooks(self.descriptor),
+ objectOps=objectOps,
+ type="eNamespace"
+ if self.descriptor.interface.isNamespace()
+ else "eInterface",
+ 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(CGThing):
+ """
+ Generates namespace block that wraps other CGThings.
+ """
+
+ def __init__(self, namespace, child):
+ CGThing.__init__(self)
+ self.child = child
+ self.pre = "namespace %s {\n" % namespace
+ self.post = "} // namespace %s\n" % namespace
+
+ def declare(self):
+ decl = self.child.declare()
+ if len(decl.strip()) == 0:
+ return ""
+ return self.pre + decl + self.post
+
+ def define(self):
+ defn = self.child.define()
+ if len(defn.strip()) == 0:
+ return ""
+ return self.pre + defn + self.post
+
+ def deps(self):
+ return self.child.deps()
+
+ @staticmethod
+ def build(namespaces, child):
+ """
+ Static helper method to build multiple wrapped namespaces.
+ """
+ if not namespaces:
+ return CGWrapper(child)
+ return CGNamespace("::".join(namespaces), child)
+
+
+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(typeAndPossibleOriginType):
+ """
+ Add the relevant headers for this type. We use its origin type, if
+ passed, to decide what to do with interface types.
+ """
+ t, originType = typeAndPossibleOriginType
+ isFromDictionary = originType and originType.isDictionary()
+ isFromCallback = originType and originType.isCallback()
+ # Dictionaries have members that need to be actually
+ # declared, not just forward-declared.
+ # Callbacks have nullable union arguments that need to be actually
+ # declared, not just forward-declared.
+ if isFromDictionary:
+ headerSet = declareIncludes
+ elif isFromCallback and t.nullable() and t.isUnion():
+ 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 or observable arrays outside unions need
+ # ForOfIterator.h; only functions that return, and attributes that
+ # are, sequences or observable arrays 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() or unrolled.isObservableArray():
+ bindingHeaders.add("js/Array.h")
+ bindingHeaders.add("js/ForOfIterator.h")
+ if unrolled.isObservableArray():
+ bindingHeaders.add("mozilla/dom/ObservableArrayProxyHandler.h")
+ else:
+ break
+ unrolled = unrolled.inner
+ if unrolled.isUnion():
+ headerSet.add(self.getUnionDeclarationFilename(config, unrolled))
+ for t in unrolled.flatMemberTypes:
+ addHeadersForType((t, None))
+ 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
+ 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 isFromDictionary 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, originType if isFromDictionary else None))
+
+ 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 or an async 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()
+ or desc.interface.isAsyncIteratorInterface()
+ ):
+ 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")
+ if typeNeedsRooting(f):
+ headers.add("mozilla/dom/RootedSequence.h")
+ f = f.unroll()
+ if idlTypeNeedsCallContext(f):
+ implheaders.add("mozilla/dom/BindingCallContext.h")
+ if f.isPromise():
+ headers.add("mozilla/dom/Promise.h")
+ # We need ToJSValue to do the Promise to JS conversion.
+ headers.add("mozilla/dom/ToJSValue.h")
+ elif f.isInterface():
+ if f.isSpiderMonkeyInterface():
+ headers.add("js/RootingAPI.h")
+ headers.add("js/Value.h")
+ 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)
+ # And if it needs rooting, we need RootedRecord too
+ if typeNeedsRooting(f):
+ headers.add("mozilla/dom/RootedRecord.h")
+
+ 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),
+ )
+
+
+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.
+
+ signatureOnly should be True to only declare the signature (either in
+ the header, or if static is True in the cpp file).
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ name,
+ returnType,
+ args,
+ inline=False,
+ alwaysInline=False,
+ static=False,
+ templateArgs=None,
+ canRunScript=False,
+ signatureOnly=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
+ self.signatureOnly = signatureOnly
+
+ 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 signature(self):
+ return "%s%s%s(%s);\n" % (
+ self._template(),
+ self._decorators(),
+ self.name,
+ self._argstring(True),
+ )
+
+ def declare(self):
+ if self.static:
+ return ""
+ if self.inline:
+ return self._define(True)
+ return self.signature()
+
+ 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):
+ if self.signatureOnly:
+ if self.static:
+ # self.static makes us not output anything in the header, so output the signature here.
+ return self.signature()
+ return ""
+ return "" if (self.inline and not self.static) 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,
+ )
+
+
+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 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, gcx, obj):
+ finalize = "JS::SetReservedSlot(%s, DOM_OBJECT_SLOT, JS::UndefinedValue());\n" % obj
+ if descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ 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,
+ )
+ for m in descriptor.interface.members:
+ if m.isAttr() and m.type.isObservableArray():
+ finalize += fill(
+ """
+ {
+ JS::Value val = JS::GetReservedSlot(obj, ${slot});
+ if (!val.isUndefined()) {
+ JSObject* obj = &val.toObject();
+ js::SetProxyReservedSlot(obj, OBSERVABLE_ARRAY_DOM_INTERFACE_SLOT, JS::UndefinedValue());
+ }
+ }
+ """,
+ slot=memberReservedSlot(m, descriptor),
+ )
+ if descriptor.wrapperCache:
+ finalize += "ClearWrapper(self, self, %s);\n" % obj
+ if descriptor.isGlobal():
+ finalize += "mozilla::dom::FinalizeGlobal(%s, %s);\n" % (gcx, 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("JS::GCContext*", "gcx"), 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, True))
+ 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 LegacyFactoryFunctionName(m):
+ return "_" + m.identifier.name
+
+
+class CGLegacyFactoryFunctions(CGThing):
+ def __init__(self, descriptor):
+ self.descriptor = descriptor
+ CGThing.__init__(self)
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ if len(self.descriptor.interface.legacyFactoryFunctions) == 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.legacyFactoryFunctions:
+ namedConstructors += (
+ '{ "%s", { %s, &sLegacyFactoryFunctionNativePropertyHooks }, %i },\n'
+ % (n.identifier.name, LegacyFactoryFunctionName(n), methodLength(n))
+ )
+
+ return fill(
+ """
+ bool sLegacyFactoryFunctionNativePropertiesInited = true;
+ const NativePropertyHooks sLegacyFactoryFunctionNativePropertyHooks = {
+ nullptr,
+ nullptr,
+ nullptr,
+ { nullptr, nullptr, &sLegacyFactoryFunctionNativePropertiesInited },
+ prototypes::id::${name},
+ ${constructorID},
+ nullptr
+ };
+
+ static const LegacyFactoryFunction 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.
+ trial: The name of the origin trial.
+ """
+
+ def __init__(
+ self,
+ pref=None,
+ func=None,
+ secureContext=False,
+ nonExposedGlobals=None,
+ trial=None,
+ ):
+ assert pref is None or isinstance(pref, str)
+ assert func is None or isinstance(func, str)
+ assert trial is None or isinstance(trial, 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"
+
+ if trial:
+ self.trial = "OriginTrial::" + trial
+ else:
+ self.trial = "OriginTrial(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
+ and self.trial == other.trial
+ )
+
+ 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"
+ or self.trial != "OriginTrial(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
+
+ trial = PropertyDefiner.getStringAttr(interfaceMember, "Trial")
+ if trial and interface.identifier.name in ["Window", "Document"]:
+ raise TypeError(
+ "[Trial] not yet supported for %s.%s, see bug 1757935"
+ % (interface.identifier.name, interfaceMember.identifier.name)
+ )
+
+ return MemberCondition(
+ PropertyDefiner.getStringAttr(interfaceMember, "Pref"),
+ PropertyDefiner.getStringAttr(interfaceMember, "Func"),
+ interfaceMember.getExtendedAttribute("SecureContext") is not None,
+ nonExposureSet,
+ trial,
+ )
+
+ @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, %s
+ };
+ """
+ )
+ prefableWithDisablersTemplate = " { &%s_disablers%d, &%s_specs[%d] }"
+ prefableWithoutDisablersTemplate = " { nullptr, &%s_specs[%d] }"
+
+ 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,
+ condition.nonExposedGlobals,
+ toStringBool(condition.secureContext),
+ condition.trial,
+ 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(
+ """
+ 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 MemberIsLegacyUnforgeable(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 == MemberIsLegacyUnforgeable(
+ 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(
+ "LegacyUnforgeable"
+ ):
+ # 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 MemberIsLegacyUnforgeable(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 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):
+ return PropertyDefiner.getControllingCondition(m["attr"], d)
+
+ @staticmethod
+ def specData(entry, descriptor, static=False, crossOriginOnly=False):
+ 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" % (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.hasLegacyLenientThis():
+ if attr.getExtendedAttribute("CrossOriginReadable"):
+ raise TypeError(
+ "Can't handle lenient cross-origin "
+ "readable attribute %s.%s"
+ % (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("LegacyLenientSetter") 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.hasLegacyLenientThis():
+ 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):
+ 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, static
+ ):
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aGlobal"),
+ Argument("ProtoAndIfaceCache&", "aProtoAndIfaceCache"),
+ Argument("bool", "aDefineOnGlobal"),
+ ]
+ CGAbstractMethod.__init__(
+ self, descriptor, "CreateInterfaceObjects", "void", args, static=static
+ )
+ 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,
+ )
+
+ if self.descriptor.interface.ctor():
+ constructArgs = methodLength(self.descriptor.interface.ctor())
+ isConstructorChromeOnly = isChromeOnly(self.descriptor.interface.ctor())
+ else:
+ constructArgs = 0
+ isConstructorChromeOnly = False
+ if len(self.descriptor.interface.legacyFactoryFunctions) > 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 legacy factory functions.
+ assert len(self.descriptor.interface.legacyFactoryFunctions) == 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"
+
+ # We use getClassName here. This should be the right thing to pass as
+ # the name argument to CreateInterfaceObjects. This is generally the
+ # interface identifier, except for the synthetic interfaces created for
+ # the default iterator objects. If needInterfaceObject is true then
+ # we'll use the name to install a property on the global object, so
+ # there shouldn't be any spaces in the name.
+ name = self.descriptor.interface.getClassName()
+ assert not (needInterfaceObject and " " in name)
+
+ call = fill(
+ """
+ JS::Heap<JSObject*>* protoCache = ${protoCache};
+ JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
+ dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
+ ${protoClass}, protoCache,
+ ${constructorProto}, ${interfaceClass}, ${constructArgs}, ${isConstructorChromeOnly}, ${namedConstructors},
+ interfaceCache,
+ ${properties},
+ ${chromeProperties},
+ "${name}", aDefineOnGlobal,
+ ${unscopableNames},
+ ${isGlobal},
+ ${legacyWindowAliases},
+ ${isNamespace});
+ """,
+ protoClass=protoClass,
+ parentProto=parentProto,
+ protoCache=protoCache,
+ constructorProto=constructorProto,
+ interfaceClass=interfaceClass,
+ constructArgs=constructArgs,
+ isConstructorChromeOnly=toStringBool(isConstructorChromeOnly),
+ namedConstructors=namedConstructors,
+ interfaceCache=interfaceCache,
+ properties=properties,
+ chromeProperties=chromeProperties,
+ name=name,
+ unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr",
+ isGlobal=toStringBool(isGlobal),
+ legacyWindowAliases="legacyWindowAliases"
+ if self.haveLegacyWindowAliases
+ else "nullptr",
+ isNamespace=toStringBool(self.descriptor.interface.isNamespace()),
+ )
+
+ # 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;
+ """
+ )
+
+ needProtoVar = False
+
+ 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" or alias == "@@asyncIterator":
+ name = alias[2:]
+
+ symbolJSID = (
+ "JS::GetWellKnownSymbolKey(aCx, JS::SymbolCode::%s)" % name
+ )
+ prop = "%sId" % name
+ getSymbolJSID = CGGeneric(
+ fill(
+ "JS::Rooted<jsid> ${prop}(aCx, ${symbolJSID});",
+ prop=prop,
+ symbolJSID=symbolJSID,
+ )
+ )
+ defineFn = "JS_DefinePropertyById"
+ enumFlags = "0" # Not enumerable, per spec.
+ elif alias.startswith("@@"):
+ raise TypeError(
+ "Can't handle any well-known Symbol other than @@iterator and @@asyncIterator"
+ )
+ 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(
+ dedent(
+ """
+ // Set up aliases on the interface prototype object we just created.
+ """
+ )
+ ),
+ CGGeneric("JS::Rooted<JS::Value> aliasedVal(aCx);\n\n"),
+ ]
+ + [
+ defineAliasesFor(m)
+ for m in sorted(aliasedMembers, key=lambda m: m.identifier.name)
+ ]
+ )
+ needProtoVar = True
+ else:
+ defineAliases = None
+
+ # Globals handle unforgeables directly in Wrap() instead of
+ # via a holder.
+ if (
+ self.descriptor.hasLegacyUnforgeableMembers
+ 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 = "proto"
+ needProtoVar = True
+ createUnforgeableHolder = CGGeneric(
+ fill(
+ """
+ JS::Rooted<JSObject*> unforgeableHolder(
+ aCx, 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
+
+ # FIXME Unclear whether this is needed for hasOrdinaryObjectPrototype
+ if (
+ self.descriptor.interface.isOnGlobalProtoChain()
+ and needInterfacePrototypeObject
+ and not self.descriptor.hasOrdinaryObjectPrototype
+ ):
+ makeProtoPrototypeImmutable = CGGeneric(
+ fill(
+ """
+ {
+ bool succeeded;
+ if (!JS_SetImmutablePrototype(aCx, proto, &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,
+ )
+ )
+ needProtoVar = True
+ else:
+ makeProtoPrototypeImmutable = None
+
+ if needProtoVar:
+ defineProtoVar = CGGeneric(
+ fill(
+ """
+ JS::AssertObjectIsNotGray(*protoCache);
+ JS::Handle<JSObject*> proto = JS::Handle<JSObject*>::fromMarkedLocation(protoCache->address());
+ if (!proto) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ )
+ )
+ else:
+ defineProtoVar = None
+ return CGList(
+ [
+ getParentProto,
+ getConstructorProto,
+ CGGeneric(call),
+ defineProtoVar,
+ defineAliases,
+ unforgeableHolderSetup,
+ makeProtoPrototypeImmutable,
+ ],
+ "\n",
+ ).define()
+
+
+class CGGetProtoObjectHandleMethod(CGAbstractMethod):
+ """
+ A method for getting the interface prototype object.
+ """
+
+ def __init__(self, descriptor, static, signatureOnly=False):
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "GetProtoObjectHandle",
+ "JS::Handle<JSObject*>",
+ [Argument("JSContext*", "aCx")],
+ inline=True,
+ static=static,
+ signatureOnly=signatureOnly,
+ )
+
+ 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))
+ trial = idlobj.getExtendedAttribute("Trial")
+ if trial:
+ assert isinstance(trial, list) and len(trial) == 1
+ conditions.append(
+ "OriginTrials::IsEnabled(%s, %s, OriginTrial::%s)"
+ % (cxName, objName, trial[0])
+ )
+ 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):
+ 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("LegacyOverrideBuiltIns"):
+ 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 (!DefineLegacyUnforgeableAttributes(aCx, ${holderName}, %s)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ holderName=holderName,
+ )
+ defineUnforgeableMethods = fill(
+ """
+ if (!DefineLegacyUnforgeableMethods(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("LegacyUnforgeable"):
+ # 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::Rooted<JS::PropertyKey> toPrimitive(aCx,
+ JS::GetWellKnownSymbolKey(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.hasLegacyUnforgeableMembers:
+ 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):
+ # We can skip the reinterpret_cast check for the descriptor's nativeType
+ # if aObject is a pointer of that type.
+ asserts = fill(
+ """
+ static_assert(std::is_same_v<decltype(aObject), ${nativeType}*>);
+ """,
+ nativeType=descriptor.nativeType,
+ )
+ iface = descriptor.interface
+ while iface.parent:
+ iface = iface.parent
+ 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)
+ )
+ 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, noGivenProto=False):
+ """
+ Declare the canonicalProto and proto we have for our wrapping operation.
+ """
+ getCanonical = dedent(
+ """
+ JS::Handle<JSObject*> ${canonicalProto} = GetProtoObjectHandle(aCx);
+ if (!${canonicalProto}) {
+ return false;
+ }
+ """
+ )
+
+ if noGivenProto:
+ return fill(getCanonical, canonicalProto="proto")
+
+ getCanonical = fill(getCanonical, canonicalProto="canonicalProto")
+
+ preamble = getCanonical + dedent(
+ """
+ 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.
+ """
+
+ def __init__(self, descriptor):
+ 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)
+
+ 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),
+ 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.
+ """
+
+ def __init__(self, descriptor, static=False, signatureOnly=False):
+ # XXX can we wrap if we don't have an interface prototype object?
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ self.noGivenProto = (
+ descriptor.interface.isIteratorInterface()
+ or descriptor.interface.isAsyncIteratorInterface()
+ )
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument(descriptor.nativeType + "*", "aObject"),
+ ]
+ if not self.noGivenProto:
+ args.append(Argument("JS::Handle<JSObject*>", "aGivenProto"))
+ args.append(Argument("JS::MutableHandle<JSObject*>", "aReflector"))
+ CGAbstractMethod.__init__(
+ self,
+ descriptor,
+ "Wrap",
+ "bool",
+ args,
+ static=static,
+ signatureOnly=signatureOnly,
+ )
+
+ def definition_body(self):
+ failureCode = "return false;\n"
+
+ declareProto = DeclareProto(self.descriptor, noGivenProto=self.noGivenProto)
+ if self.noGivenProto:
+ assertGivenProto = ""
+ else:
+ assertGivenProto = dedent(
+ """
+ MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
+ """
+ )
+ return fill(
+ """
+ $*{assertions}
+ $*{assertGivenProto}
+
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ $*{declareProto}
+
+ $*{createObject}
+
+ $*{unforgeable}
+
+ $*{slots}
+
+ creator.InitializationSucceeded();
+ return true;
+ """,
+ assertions=AssertInheritanceChain(self.descriptor),
+ assertGivenProto=assertGivenProto,
+ declareProto=declareProto,
+ createObject=CreateBindingJSObject(self.descriptor),
+ 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.hasLegacyUnforgeableMembers:
+ unforgeable = InitUnforgeablePropertiesOnHolder(
+ self.descriptor, self.properties, failureCode, "aReflector"
+ ).define()
+ else:
+ unforgeable = ""
+
+ if self.descriptor.hasOrdinaryObjectPrototype:
+ getProto = "JS::GetRealmObjectPrototypeHandle"
+ else:
+ getProto = "GetProtoObjectHandle"
+ return fill(
+ """
+ $*{assertions}
+ MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
+ "nsISupports must be on our primary inheritance chain");
+
+ if (!CreateGlobal<${nativeType}, ${getProto}>(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,
+ getProto=getProto,
+ 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(
+ """
+ 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 or isMember == "Union")
+ 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", "Union", or "OwningUnion" to indicate that the conversion
+ is for something that is a dictionary member, a variadic argument, a sequence,
+ an union, or an owning union respectively.
+ XXX Once we swtich *Rooter to Rooted* for Record and Sequence type entirely,
+ we could remove "Union" from isMember.
+
+ 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
+ [LegacyTreatNonObjectAsNull] 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 onFailureIsLarge():
+ return CGGeneric(
+ 'cx.ThrowErrorMessage<MSG_TYPEDARRAY_IS_LARGE>("%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 or isMember == "Union":
+ 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() or type.isObservableArray():
+ assert not isEnforceRange and not isClamp and not isAllowShared
+
+ if failureCode is None:
+ notSequence = (
+ 'cx.ThrowErrorMessage<MSG_CONVERSION_ERROR>("%s", "%s");\n'
+ "%s"
+ % (
+ firstCap(sourceDescription),
+ "sequence" if type.isSequence() else "observable array",
+ 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 and isMember != "Union")
+ 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 isMember == "Union" and typeNeedsRooting(type):
+ assert not nullable
+ typeName = CGTemplatedType(
+ "binding_detail::RootedAutoSequence", elementInfo.declType
+ )
+ elif nullable:
+ typeName = CGTemplatedType("Nullable", typeName)
+
+ if nullable:
+ 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)
+
+ declArgs = None
+ holderType = None
+ holderArgs = None
+ # Sequence arguments that might contain traceable things need
+ # to get traced
+ if typeNeedsRooting(elementType):
+ if not isMember:
+ 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
+ elif isMember == "Union":
+ declArgs = "cx"
+
+ return JSToNativeConversionInfo(
+ templateBody,
+ declType=typeName,
+ declArgs=declArgs,
+ 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 isMember == "Union" and typeNeedsRooting(type):
+ assert not nullable
+ declType = CGTemplatedType(
+ "RootedRecord", [recordKeyDeclType(recordType), valueInfo.declType]
+ )
+ elif nullable:
+ declType = CGTemplatedType("Nullable", declType)
+
+ if nullable:
+ 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<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx);
+ if (!JS_GetOwnPropertyDescriptorById(cx, recordObj, curId,
+ &desc)) {
+ $*{exceptionCode}
+ }
+
+ if (desc.isNothing() || !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 typeNeedsRooting(valueType):
+ if not isMember:
+ 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
+ elif isMember == "Union":
+ declArgs = "cx"
+
+ 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 and isMember != "Union") or isCallbackReturnValue
+ unionArgumentObj = "${declName}"
+ if nullable:
+ if isOptional and not isOwningUnion:
+ unionArgumentObj += ".Value()"
+ # 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()"
+
+ templateBody = CGIfWrapper(
+ CGGeneric(exceptionCode),
+ '!%s.Init(cx, ${val}, "%s", ${passedToJSImpl})'
+ % (unionArgumentObj, firstCap(sourceDescription)),
+ )
+
+ 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
+ templateBody = CGIfElseWrapper(
+ "!(${haveValue})",
+ CGGeneric("%s.SetNull();\n" % unionArgumentObj),
+ templateBody,
+ )
+
+ typeName = CGUnionStruct.unionTypeDecl(type, isOwningUnion)
+ argumentTypeName = typeName + "Argument"
+ if nullable:
+ typeName = "Nullable<" + typeName + " >"
+
+ 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:
+ declType = CGTemplatedType("Optional", declType)
+ constructDecl = CGGeneric("${declName}.Construct();\n")
+ declLoc = "${declName}.Value()"
+
+ 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 "")
+ if not isOwningUnion and typeNeedsRooting(defaultValue.type):
+ ctorArgs = "cx"
+ else:
+ ctorArgs = ""
+ # 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(%s);\n" % (value, name, ctorArgs))
+ 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)
+
+ if nullable:
+ assert not type.hasNullableType
+ if defaultValue:
+ if isinstance(defaultValue, IDLNullValue):
+ extraConditionForNull = "!(${haveValue}) || "
+ else:
+ extraConditionForNull = "(${haveValue}) && "
+ else:
+ extraConditionForNull = ""
+
+ hasUndefinedType = any(t.isUndefined() for t in type.flatMemberTypes)
+ assert not hasUndefinedType or defaultValue is None
+
+ nullTest = (
+ "${val}.isNull()" if hasUndefinedType else "${val}.isNullOrUndefined()"
+ )
+ templateBody = CGIfElseWrapper(
+ extraConditionForNull + nullTest,
+ CGGeneric("%s.SetNull();\n" % declLoc),
+ templateBody,
+ )
+ 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,
+ 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 and isMember != "Union") 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 type.isBufferSource():
+ if type.isArrayBuffer():
+ isSharedMethod = "JS::IsSharedArrayBufferObject"
+ isLargeMethod = "JS::IsLargeArrayBufferMaybeShared"
+ else:
+ assert type.isArrayBufferView() or type.isTypedArray()
+ isSharedMethod = "JS::IsArrayBufferViewShared"
+ isLargeMethod = "JS::IsLargeArrayBufferView"
+ if not isAllowShared:
+ template += fill(
+ """
+ if (${isSharedMethod}(${objRef}.Obj())) {
+ $*{badType}
+ }
+ """,
+ isSharedMethod=isSharedMethod,
+ objRef=objRef,
+ badType=onFailureIsShared().define(),
+ )
+ # For now reject large (> 2 GB) ArrayBuffers and ArrayBufferViews.
+ # Supporting this will require changing dom::TypedArray and
+ # consumers.
+ template += fill(
+ """
+ if (${isLargeMethod}(${objRef}.Obj())) {
+ $*{badType}
+ }
+ """,
+ isLargeMethod=isLargeMethod,
+ objRef=objRef,
+ badType=onFailureIsLarge().define(),
+ )
+ template = wrapObjectTemplate(
+ template, type, "${declName}.SetNull();\n", failureCode
+ )
+ if not isMember or isMember == "Union":
+ # 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.legacyNullToEmptyString:
+ 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 and isMember != "Union":
+ # 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 or isMember == "Union") 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 or isMember == "Union") and isCallbackReturnValue:
+ # Go ahead and just convert directly into our actual return value
+ declType = CGWrapper(declType, post="&")
+ declArgs = "aRetVal"
+ elif (not isMember or isMember == "Union") and typeNeedsRooting(type):
+ declType = CGTemplatedType("RootedDictionary", declType)
+ declArgs = "cx"
+ else:
+ declArgs = None
+
+ return JSToNativeConversionInfo(
+ template, declType=declType, declArgs=declArgs, dealWithOptional=isOptional
+ )
+
+ if type.isUndefined():
+ 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 (!std::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.isUndefined():
+ 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 type.isObservableArray():
+ # This first argument isn't used at all for now, the attribute getter
+ # for ObservableArray type are generated in getObservableArrayGetterBody
+ # instead.
+ return "", 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.isUndefined():
+ # 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():
+ if isMember:
+ return CGGeneric("nsCString"), "ref", None, None, None
+ return CGGeneric("nsAutoCString"), "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.
+
+ needsErrorResult is a boolean indicating whether the call should be
+ fallible and thus needs ErrorResult parameter.
+
+ 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,
+ needsErrorResult,
+ 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 needsErrorResult:
+ 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.isUndefined() 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 needsErrorResult 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.isUndefined()
+ or 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, DeprecatedOperations::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.identifier.name,
+ self.getArgumentNames(),
+ )
+ )
+ elif idlNode.isAttr() and idlNode.type.isObservableArray():
+ assert setter
+ cgThings.append(CGObservableArraySetterGenerator(descriptor, idlNode))
+ 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
+
+ if idlNode.isMethod() and idlNode.getExtendedAttribute("WebExtensionStub"):
+ [
+ nativeMethodName,
+ argsPre,
+ args,
+ ] = self.processWebExtensionStubAttribute(idlNode, cgThings)
+ else:
+ args = self.getArguments()
+
+ cgThings.append(
+ CGCallGenerator(
+ self.needsErrorResult(),
+ needsCallerType(idlNode),
+ isChromeOnly(idlNode),
+ args,
+ 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 getArgumentNames(self):
+ return ["arg" + str(i) for i in range(len(self.arguments))]
+
+ def getArguments(self):
+ return list(zip(self.arguments, self.getArgumentNames()))
+
+ def processWebExtensionStubAttribute(self, idlNode, cgThings):
+ nativeMethodName = "CallWebExtMethod"
+ stubNameSuffix = idlNode.getExtendedAttribute("WebExtensionStub")
+ if isinstance(stubNameSuffix, list):
+ nativeMethodName += stubNameSuffix[0]
+
+ argsLength = len(self.getArguments())
+ singleVariadicArg = argsLength == 1 and self.getArguments()[0][0].variadic
+
+ # If the method signature does only include a single variadic arguments,
+ # then `arg0` is already a Sequence of JS values and we can pass that
+ # to the WebExtensions Stub method as is.
+ if singleVariadicArg:
+ argsPre = [
+ "cx",
+ 'u"%s"_ns' % idlNode.identifier.name,
+ "Constify(%s)" % "arg0",
+ ]
+ args = []
+ return [nativeMethodName, argsPre, args]
+
+ argsPre = [
+ "cx",
+ 'u"%s"_ns' % idlNode.identifier.name,
+ "Constify(%s)" % "args_sequence",
+ ]
+ args = []
+
+ # Determine the maximum number of elements of the js values sequence argument,
+ # skipping the last optional callback argument if any:
+ #
+ # if this WebExtensions API method does expect a last optional callback argument,
+ # then it is the callback parameter supported for chrome-compatibility
+ # reasons, and we want it as a separate argument passed to the WebExtension
+ # stub method and skip it from the js values sequence including all other
+ # arguments.
+ maxArgsSequenceLen = argsLength
+ if argsLength > 0:
+ lastArg = self.getArguments()[argsLength - 1]
+ isCallback = lastArg[0].type.tag() == IDLType.Tags.callback
+ if isCallback and lastArg[0].optional:
+ argsPre.append(
+ "MOZ_KnownLive(NonNullHelper(Constify(%s)))" % lastArg[1]
+ )
+ maxArgsSequenceLen = argsLength - 1
+
+ cgThings.append(
+ CGGeneric(
+ dedent(
+ fill(
+ """
+ // Collecting all args js values into the single sequence argument
+ // passed to the webextensions stub method.
+ //
+ // NOTE: The stub method will receive the original non-normalized js values,
+ // but those arguments will still be normalized on the main thread by the
+ // WebExtensions API request handler using the same JSONSchema defnition
+ // used by the non-webIDL webextensions API bindings.
+ AutoSequence<JS::Value> args_sequence;
+ SequenceRooter<JS::Value> args_sequence_holder(cx, &args_sequence);
+
+ // maximum number of arguments expected by the WebExtensions API method
+ // excluding the last optional chrome-compatible callback argument (which
+ // is being passed to the stub method as a separate additional argument).
+ uint32_t maxArgsSequenceLen = ${maxArgsSequenceLen};
+
+ uint32_t sequenceArgsLen = args.length() <= maxArgsSequenceLen ?
+ args.length() : maxArgsSequenceLen;
+
+ if (sequenceArgsLen > 0) {
+ if (!args_sequence.SetCapacity(sequenceArgsLen, mozilla::fallible)) {
+ JS_ReportOutOfMemory(cx);
+ return false;
+ }
+ for (uint32_t argIdx = 0; argIdx < sequenceArgsLen; ++argIdx) {
+ // OK to do infallible append here, since we ensured capacity already.
+ JS::Value& slot = *args_sequence.AppendElement();
+ slot = args[argIdx];
+ }
+ }
+ """,
+ maxArgsSequenceLen=maxArgsSequenceLen,
+ )
+ )
+ )
+ )
+
+ return [nativeMethodName, argsPre, args]
+
+ def needsErrorResult(self):
+ return "needsErrorResult" 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="}\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 for whether add a break, add fallthrough annotation or add nothing
+ (defaulting to add a break).
+ """
+
+ ADD_BREAK = 0
+ ADD_FALLTHROUGH = 1
+ DONT_ADD_BREAK = 2
+
+ def __init__(self, expression, body, breakOrFallthrough=ADD_BREAK):
+ CGList.__init__(self, [])
+
+ assert (
+ breakOrFallthrough == CGCase.ADD_BREAK
+ or breakOrFallthrough == CGCase.ADD_FALLTHROUGH
+ or breakOrFallthrough == CGCase.DONT_ADD_BREAK
+ )
+
+ self.append(CGGeneric("case " + expression + ": {\n"))
+ bodyList = CGList([body])
+ if breakOrFallthrough == CGCase.ADD_FALLTHROUGH:
+ bodyList.append(CGGeneric("[[fallthrough]];\n"))
+ elif breakOrFallthrough == CGCase.ADD_BREAK:
+ 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(".", "_").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, CGCase.ADD_FALLTHROUGH)
+ )
+ 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:
+ #
+ # undefined(long... foo);
+ # undefined(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, 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, allowTreatNonCallableAsNull=True)],
+ nativeMethodName,
+ attr.isStatic(),
+ descriptor,
+ attr,
+ setter=True,
+ useCounterName=useCounterName,
+ )
+
+ def wrap_return_value(self):
+ attr = self.idlNode
+ clearSlot = ""
+ if self.descriptor.wrapperCache and attr.slotIndices is not None:
+ if attr.getExtendedAttribute("StoreInSlot"):
+ clearSlot = "%s(cx, self);\n" % MakeClearCachedValueNativeName(
+ self.idlNode
+ )
+ elif attr.getExtendedAttribute("Cached"):
+ clearSlot = "%s(self);\n" % MakeClearCachedValueNativeName(self.idlNode)
+
+ # 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 legacy factory functions
+ # 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-undefined signatures would require us to deal with remote proxies for the
+ # return value here.
+ if not signature[0].isUndefined():
+ raise TypeError(
+ "We don't support a method marked as CrossOriginCallable "
+ "with non-undefined 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, method.isStatic()))
+
+
+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, False))
+ 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<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx);
+ if (!self->DoResolve(cx, obj, id, &desc)) {
+ return false;
+ }
+ if (desc.isNothing()) {
+ 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.
+ MOZ_ASSERT(desc->isDataDescriptor());
+ if (!desc->value().isUndefined()) {
+ JS::Rooted<JS::PropertyDescriptor> defineDesc(cx, *desc);
+ defineDesc.setResolving(true);
+ if (!JS_DefinePropertyById(cx, obj, id, defineDesc)) {
+ 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
+ )
+
+ if self.attr.type.isObservableArray():
+ assert not self.attr.getExtendedAttribute("CrossOriginReadable")
+ # If the attribute is observableArray, due to having to unpack the
+ # backing object from the slot, this requires its own generator.
+ return prefix + getObservableArrayGetterBody(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]
+ if "needsErrorResult" not in extendedAttributes:
+ extendedAttributes.append("needsErrorResult")
+ 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, attr.isStatic()))
+ _, resultOutParam, _, _, _ = getRetvalDeclarationForType(attr.type, descriptor)
+ extendedAttrs = descriptor.getExtendedAttributes(attr, getter=True)
+ canFail = "needsErrorResult" 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",
+ self.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, attr.isStatic()))
+
+
+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, DeprecatedOperations::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 = "needsErrorResult" not 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")
+ or self.member.type.isObservableArray()
+ )
+ 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("LegacyLenientSetter") 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.undefined]],
+ 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 = "needsErrorResult" not 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.isUndefined():
+ # 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 t.isObservableArray():
+ 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.isUndefined()
+ 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, isMember=False):
+ assert not type.isUndefined()
+ assert not isMember or isMember in ("Union", "OwningUnion")
+
+ ownsMembers = isMember == "OwningUnion"
+ 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.
+ tryNextCode = fill(
+ """
+ Destroy${name}();
+ tryNext = true;
+ return true;
+ """,
+ name=name,
+ )
+
+ sourceDescription = "%s branch of %s" % (type.prettyName(), unionType.prettyName())
+
+ conversionInfo = getJSToNativeConversionInfo(
+ type,
+ descriptorProvider,
+ failureCode=tryNextCode,
+ isDefinitelyObject=not type.isDictionary(),
+ isMember=isMember,
+ sourceDescription=sourceDescription,
+ )
+
+ ctorNeedsCx = conversionInfo.declArgs == "cx"
+ ctorArgs = "cx" if ctorNeedsCx else ""
+
+ structType = conversionInfo.declType.define()
+ externalType = getUnionAccessorSignatureType(type, descriptorProvider).define()
+
+ if type.isObject():
+ if ownsMembers:
+ setValue = "mValue.mObject.SetValue(obj);"
+ else:
+ setValue = "mValue.mObject.SetValue(cx, obj);"
+
+ body = fill(
+ """
+ MOZ_ASSERT(mType == eUninitialized);
+ ${setValue}
+ mType = eObject;
+ """,
+ setValue=setValue,
+ )
+
+ # 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.
+ jsConversion = fill(
+ conversionInfo.template,
+ val="value",
+ maybeMutableVal="value",
+ declName="memberSlot",
+ holderName=(holderName if ownsMembers else "%s.ref()" % holderName),
+ 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,
+ )
+
+ needCallContext = idlTypeNeedsCallContext(type)
+ if needCallContext:
+ cxType = "BindingCallContext&"
+ else:
+ cxType = "JSContext*"
+ setters = [
+ ClassMethod(
+ "TrySetTo" + name,
+ "bool",
+ [
+ Argument(cxType, "cx"),
+ Argument("JS::Handle<JS::Value>", "value"),
+ Argument("bool&", "tryNext"),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ visibility="private",
+ 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("JS::Handle<JS::Value>", "value"),
+ Argument("bool&", "tryNext"),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ visibility="private",
+ body=shimBody,
+ )
+ )
+
+ return {
+ "name": name,
+ "structType": structType,
+ "externalType": externalType,
+ "setters": setters,
+ "ctorArgs": ctorArgs,
+ "ctorArgList": [Argument("JSContext*", "cx")] if ctorNeedsCx else [],
+ }
+
+
+def getUnionConversionTemplate(type):
+ assert type.isUnion()
+ assert not type.nullable()
+
+ 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 = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext"
+ % name
+ )
+ )
+ prettyNames.append(memberType.prettyName())
+ interfaceObject = CGWrapper(
+ CGList(interfaceObject, " ||\n"),
+ pre="done = ",
+ post=";\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 = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
+ % 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 = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
+ % 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 = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
+ % 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 = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n"
+ % 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 (!SetToObject(cx, &${val}.toObject(), ${passedToJSImpl})) {\n"
+ " return false;\n"
+ "}\n"
+ "done = true;\n"
+ )
+ 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 = !TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n"
+ "break;\n" % 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) {
+ return false;
+ }
+ if (!done) {
+ cx.ThrowErrorMessage<MSG_NOT_IN_UNION>(sourceDescription, "${names}");
+ return false;
+ }
+ """,
+ names=", ".join(prettyNames),
+ )
+ )
+
+ templateBody = CGList([templateBody, throw])
+
+ hasUndefinedType = any(t.isUndefined() for t in memberTypes)
+ elseChain = []
+
+ # The spec does this before anything else, but we do it after checking
+ # for null in the case of a nullable union. In practice this shouldn't
+ # make a difference, but it makes things easier because we first need to
+ # call Construct on our Maybe<...>, before we can set the union type to
+ # undefined, and we do that below after checking for null (see the
+ # 'if nullable:' block below).
+ if hasUndefinedType:
+ elseChain.append(
+ CGIfWrapper(
+ CGGeneric("SetUndefined();\n"),
+ "${val}.isUndefined()",
+ )
+ )
+
+ if type.hasNullableType:
+ nullTest = (
+ "${val}.isNull()" if hasUndefinedType else "${val}.isNullOrUndefined()"
+ )
+ elseChain.append(
+ CGIfWrapper(
+ CGGeneric("SetNull();\n"),
+ nullTest,
+ )
+ )
+
+ if len(elseChain) > 0:
+ elseChain.append(CGWrapper(CGIndenter(templateBody), pre="{\n", post="}\n"))
+ templateBody = CGElseChain(elseChain)
+
+ return templateBody
+
+
+def getUnionInitMethods(type, isOwningUnion=False):
+ assert type.isUnion()
+
+ template = getUnionConversionTemplate(type).define()
+
+ replacements = {
+ "val": "value",
+ "passedToJSImpl": "passedToJSImpl",
+ }
+
+ initBody = fill(
+ """
+ MOZ_ASSERT(mType == eUninitialized);
+
+ $*{conversion}
+ return true;
+ """,
+ conversion=string.Template(template).substitute(replacements),
+ )
+
+ return [
+ ClassMethod(
+ "Init",
+ "bool",
+ [
+ Argument("BindingCallContext&", "cx"),
+ Argument("JS::Handle<JS::Value>", "value"),
+ Argument("const char*", "sourceDescription", default='"Value"'),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ visibility="public",
+ body=initBody,
+ ),
+ ClassMethod(
+ "Init",
+ "bool",
+ [
+ Argument("JSContext*", "cx_"),
+ Argument("JS::Handle<JS::Value>", "value"),
+ Argument("const char*", "sourceDescription", default='"Value"'),
+ Argument("bool", "passedToJSImpl", default="false"),
+ ],
+ visibility="public",
+ body=dedent(
+ """
+ BindingCallContext cx(cx_, nullptr);
+ return Init(cx, value, sourceDescription, passedToJSImpl);
+ """
+ ),
+ ),
+ ]
+
+
+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", "TypeOrUninit", body="eUninitialized"),
+ ClassMember("mValue", "Value"),
+ ]
+ ctor = ClassConstructor(
+ [], bodyInHeader=True, visibility="public", explicit=True
+ )
+
+ methods = []
+ enumValues = ["eUninitialized"]
+ toJSValCases = [
+ CGCase(
+ "eUninitialized", CGGeneric("return false;\n"), CGCase.DONT_ADD_BREAK
+ )
+ ]
+ destructorCases = [CGCase("eUninitialized", None)]
+ assignmentCase = CGCase(
+ "eUninitialized",
+ CGGeneric(
+ "MOZ_ASSERT(mType == eUninitialized,\n"
+ ' "We need to destroy ourselves?");\n'
+ ),
+ )
+ assignmentCases = [assignmentCase]
+ moveCases = [assignmentCase]
+ traceCases = []
+ unionValues = []
+
+ def addSpecialType(typename):
+ enumValue = "e" + typename
+ enumValues.append(enumValue)
+ methods.append(
+ ClassMethod(
+ "Is" + typename,
+ "bool",
+ [],
+ const=True,
+ inline=True,
+ body="return mType == %s;\n" % enumValue,
+ bodyInHeader=True,
+ )
+ )
+ methods.append(
+ ClassMethod(
+ "Set" + typename,
+ "void",
+ [],
+ inline=True,
+ body=fill(
+ """
+ Uninit();
+ mType = ${enumValue};
+ """,
+ enumValue=enumValue,
+ ),
+ bodyInHeader=True,
+ )
+ )
+ destructorCases.append(CGCase(enumValue, None))
+ assignmentCase = CGCase(
+ enumValue,
+ CGGeneric(
+ fill(
+ """
+ MOZ_ASSERT(mType == eUninitialized);
+ mType = ${enumValue};
+ """,
+ enumValue=enumValue,
+ )
+ ),
+ )
+ assignmentCases.append(assignmentCase)
+ moveCases.append(assignmentCase)
+ toJSValCases.append(
+ CGCase(
+ enumValue,
+ CGGeneric(
+ fill(
+ """
+ rval.set${typename}();
+ return true;
+ """,
+ typename=typename,
+ )
+ ),
+ CGCase.DONT_ADD_BREAK,
+ )
+ )
+
+ if self.type.hasNullableType:
+ addSpecialType("Null")
+
+ hasObjectType = any(t.isObject() for t in self.type.flatMemberTypes)
+ skipToJSVal = False
+ for t in self.type.flatMemberTypes:
+ if t.isUndefined():
+ addSpecialType("Undefined")
+ continue
+
+ vars = getUnionTypeTemplateVars(
+ self.type,
+ t,
+ self.descriptorProvider,
+ isMember="OwningUnion" if self.ownsMembers else "Union",
+ )
+ if vars["setters"]:
+ methods.extend(vars["setters"])
+ uninit = "Uninit();"
+ if hasObjectType and not self.ownsMembers:
+ uninit = (
+ 'MOZ_ASSERT(mType != eObject, "This will not play well with Rooted");\n'
+ + uninit
+ )
+ if not t.isObject() 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);",
+ )
+ )
+ methods.append(
+ ClassMethod(
+ "SetAs" + vars["name"],
+ vars["structType"] + "&",
+ vars["ctorArgList"],
+ bodyInHeader=not self.ownsMembers,
+ body=body % uninit,
+ )
+ )
+
+ # 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("return mType == e${name};\n", **vars)
+ methods.append(
+ ClassMethod(
+ "Is" + vars["name"],
+ "bool",
+ [],
+ const=True,
+ bodyInHeader=True,
+ body=body,
+ )
+ )
+
+ body = fill(
+ """
+ MOZ_RELEASE_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(
+ """
+ MOZ_RELEASE_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))
+ destructorCases.append(
+ CGCase("e" + vars["name"], CGGeneric("Destroy%s();\n" % vars["name"]))
+ )
+
+ enumValues.append("e" + vars["name"])
+
+ conversionToJS = self.getConversionToJS(vars, t)
+ if conversionToJS:
+ toJSValCases.append(
+ CGCase("e" + vars["name"], conversionToJS, CGCase.DONT_ADD_BREAK)
+ )
+ else:
+ skipToJSVal = True
+
+ assignmentCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ "SetAs%s() = aOther.GetAs%s();\n" % (vars["name"], vars["name"])
+ ),
+ )
+ )
+ moveCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ "mType = e%s;\n" % vars["name"]
+ + "mValue.m%s.SetValue(std::move(aOther.mValue.m%s.Value()));\n"
+ % (vars["name"], vars["name"])
+ ),
+ )
+ )
+ if self.ownsMembers and typeNeedsRooting(t):
+ if t.isObject():
+ traceCases.append(
+ CGCase(
+ "e" + vars["name"],
+ CGGeneric(
+ 'JS::TraceRoot(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.extend(getUnionInitMethods(self.type, isOwningUnion=self.ownsMembers))
+ 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(),
+ const=True,
+ )
+ )
+
+ constructors = [ctor]
+ selfName = CGUnionStruct.unionTypeName(self.type, self.ownsMembers)
+ if self.ownsMembers:
+ if traceCases:
+ traceBody = CGSwitch(
+ "mType", traceCases, default=CGGeneric("")
+ ).define()
+ methods.append(
+ ClassMethod(
+ "TraceUnion",
+ "void",
+ [Argument("JSTracer*", "trc")],
+ body=traceBody,
+ )
+ )
+
+ op_body = CGList([])
+ op_body.append(CGSwitch("aOther.mType", moveCases))
+ constructors.append(
+ ClassConstructor(
+ [Argument("%s&&" % selfName, "aOther")],
+ visibility="public",
+ body=op_body.define(),
+ )
+ )
+
+ methods.append(
+ ClassMethod(
+ "operator=",
+ "%s&" % selfName,
+ [Argument("%s&&" % selfName, "aOther")],
+ body="this->~%s();\nnew (this) %s (std::move(aOther));\nreturn *this;\n"
+ % (selfName, selfName),
+ )
+ )
+
+ body = dedent(
+ """
+ MOZ_RELEASE_ASSERT(mType != eUninitialized);
+ return static_cast<Type>(mType);
+ """
+ )
+ methods.append(
+ ClassMethod(
+ "GetType",
+ "Type",
+ [],
+ bodyInHeader=True,
+ body=body,
+ const=True,
+ )
+ )
+
+ 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 and idlTypeNeedsCycleCollection(self.type):
+ friend = (
+ " friend void ImplCycleCollectionUnlink(%s& aUnion);\n"
+ % CGUnionStruct.unionTypeName(self.type, True)
+ )
+ else:
+ friend = ""
+
+ enumValuesNoUninit = [x for x in enumValues if x != "eUninitialized"]
+
+ bases = [ClassBase("AllOwningUnionBase")] if self.ownsMembers else []
+ enums = [
+ ClassEnum("TypeOrUninit", enumValues, visibility="private"),
+ ClassEnum(
+ "Type",
+ enumValuesNoUninit,
+ visibility="public",
+ enumClass=True,
+ values=["TypeOrUninit::" + x for x in enumValuesNoUninit],
+ ),
+ ]
+ 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=enums,
+ 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 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.
+
+ default should be True if the definition of the constructor should be
+ `= default;`.
+
+ 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,
+ default=False,
+ visibility="private",
+ explicit=False,
+ constexpr=False,
+ baseConstructors=None,
+ body="",
+ ):
+ assert not (inline and constexpr)
+ assert not (bodyInHeader and constexpr)
+ assert not (default and body)
+ self.args = args
+ self.inline = inline or bodyInHeader
+ self.bodyInHeader = bodyInHeader or constexpr or default
+ self.default = default
+ self.explicit = explicit
+ self.constexpr = constexpr
+ self.baseConstructors = baseConstructors or []
+ self.body = body
+ ClassItem.__init__(self, None, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if declaring:
+ if self.explicit:
+ decorators.append("explicit")
+ if self.inline:
+ decorators.append("inline")
+ if self.constexpr:
+ 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:
+ if self.default:
+ body = " = default;\n"
+ else:
+ 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", enumClass=False
+ ):
+ self.entries = entries
+ self.values = values
+ self.enumClass = enumClass
+ 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)
+
+ decl = ["enum"]
+ self.enumClass and decl.append("class")
+ self.name and decl.append(self.name)
+ decl = " ".join(decl)
+
+ return "%s\n{\n%s\n};\n" % (decl, 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 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<Maybe<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<mozilla::Maybe<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.isSome() &&
+ !objDesc->value().isUndefined()) {
+ JS::Rooted<JS::PropertyDescriptor> defineDesc(cx, *objDesc);
+ if (!JS_DefinePropertyById(cx, obj, id, defineDesc)) {
+ return false;
+ }
+ }
+ }
+ return self->DoResolve(cx, wrapper, id, desc);
+ """
+ )
+ )
+
+
+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, False))
+ 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.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}() && id.isAtom()) {
+ 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}() && id.isAtom());
+ 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(id.toAtom());
+ // 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<Maybe<JS::PropertyDescriptor>>", "desc"),
+ ]
+ ClassMethod.__init__(
+ self,
+ "getOwnPropDescriptor",
+ "bool",
+ args,
+ virtual=True,
+ override=True,
+ const=True,
+ )
+ self.descriptor = descriptor
+
+ def getBody(self):
+ 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():
+ attributes = [
+ "JS::PropertyAttribute::Configurable",
+ "JS::PropertyAttribute::Enumerable",
+ ]
+ if indexedSetter is not None:
+ attributes.append("JS::PropertyAttribute::Writable")
+ setDescriptor = (
+ "desc.set(mozilla::Some(JS::PropertyDescriptor::Data(value, { %s })));\nreturn true;\n"
+ % ", ".join(attributes)
+ )
+ templateValues = {
+ "jsvalRef": "value",
+ "jsvalHandle": "&value",
+ "obj": "proxy",
+ "successCode": setDescriptor,
+ }
+ getIndexed = fill(
+ """
+ uint32_t index = GetArrayIndexFromId(id);
+ if (IsArrayIndex(index)) {
+ JS::Rooted<JS::Value> value(cx);
+ $*{callGetter}
+ }
+
+ """,
+ callGetter=CGProxyIndexedGetter(
+ self.descriptor, templateValues
+ ).define(),
+ )
+ else:
+ getIndexed = ""
+
+ missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor)
+
+ if self.descriptor.supportsNamedProperties():
+ operations = self.descriptor.operations
+ attributes = ["JS::PropertyAttribute::Configurable"]
+ if self.descriptor.namedPropertiesEnumerable:
+ attributes.append("JS::PropertyAttribute::Enumerable")
+ if operations["NamedSetter"] is not None:
+ attributes.append("JS::PropertyAttribute::Writable")
+ setDescriptor = (
+ "desc.set(mozilla::Some(JS::PropertyDescriptor::Data(value, { %s })));\nreturn true;\n"
+ % ", ".join(attributes)
+ )
+ templateValues = {
+ "jsvalRef": "value",
+ "jsvalHandle": "&value",
+ "obj": "proxy",
+ "successCode": setDescriptor,
+ }
+
+ computeCondition = dedent(
+ """
+ bool hasOnProto;
+ if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
+ return false;
+ }
+ callNamedGetter = !hasOnProto;
+ """
+ )
+ if self.descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ 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) {
+ JS::Rooted<JS::Value> value(cx);
+ $*{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.isSome()) {
+ return true;
+ }
+ }
+
+ $*{namedGet}
+ desc.reset();
+ 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.hasLegacyUnforgeableMembers:
+ 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.hasLegacyUnforgeableMembers:
+ 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(
+ "LegacyOverrideBuiltIns"
+ ):
+ 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(JS::PropertyKey::Int(i))) {
+ return false;
+ }
+ }
+ """,
+ callerType=callerType,
+ )
+ else:
+ addIndices = ""
+
+ if self.descriptor.supportsNamedProperties():
+ if self.descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ 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(
+ "LegacyOverrideBuiltIns"
+ ):
+ 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):
+ missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor)
+
+ getUnforgeableOrExpando = dedent(
+ """
+ bool expandoHasProp = false;
+ { // 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, ${receiver}, vp)) {
+ return false;
+ }
+ }
+ }
+ }
+ """
+ )
+
+ getOnPrototype = dedent(
+ """
+ bool foundOnPrototype;
+ if (!GetPropertyOnPrototype(cx, proxy, ${receiver}, id, &foundOnPrototype, vp)) {
+ return false;
+ }
+ """
+ )
+
+ if self.descriptor.isMaybeCrossOriginObject():
+ # We can't handle these for cross-origin objects
+ assert not self.descriptor.supportsIndexedProperties()
+ assert not self.descriptor.supportsNamedProperties()
+
+ return fill(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return CrossOriginGet(cx, proxy, receiver, id, vp);
+ }
+
+ $*{missingPropUseCounters}
+ { // Scope for the JSAutoRealm accessing expando and prototype.
+ JSAutoRealm ar(cx, proxy);
+ JS::Rooted<JS::Value> wrappedReceiver(cx, receiver);
+ if (!MaybeWrapValue(cx, &wrappedReceiver)) {
+ return false;
+ }
+ JS_MarkCrossZoneId(cx, id);
+
+ $*{getUnforgeableOrExpando}
+ if (!expandoHasProp) {
+ $*{getOnPrototype}
+ if (!foundOnPrototype) {
+ MOZ_ASSERT(vp.isUndefined());
+ return true;
+ }
+ }
+ }
+
+ return MaybeWrapValue(cx, vp);
+ """,
+ missingPropUseCounters=missingPropUseCountersForDescriptor(
+ self.descriptor
+ ),
+ getUnforgeableOrExpando=fill(
+ getUnforgeableOrExpando, receiver="wrappedReceiver"
+ ),
+ getOnPrototype=fill(getOnPrototype, receiver="wrappedReceiver"),
+ )
+
+ templateValues = {"jsvalRef": "vp", "jsvalHandle": "vp", "obj": "proxy"}
+
+ getUnforgeableOrExpando = fill(
+ getUnforgeableOrExpando, receiver="receiver"
+ ) + dedent(
+ """
+
+ if (expandoHasProp) {
+ return true;
+ }
+ """
+ )
+ if self.descriptor.supportsIndexedProperties():
+ 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():
+ getNamed = CGProxyNamedGetter(self.descriptor, templateValues)
+ if self.descriptor.supportsIndexedProperties():
+ getNamed = CGIfWrapper(getNamed, "!IsArrayIndex(index)")
+ getNamed = getNamed.define() + "\n"
+ else:
+ getNamed = ""
+
+ getOnPrototype = fill(getOnPrototype, receiver="receiver") + dedent(
+ """
+
+ if (foundOnPrototype) {
+ return true;
+ }
+
+ MOZ_ASSERT(vp.isUndefined());
+ """
+ )
+
+ if self.descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"):
+ getNamedOrOnPrototype = getNamed + getOnPrototype
+ else:
+ getNamedOrOnPrototype = getOnPrototype + getNamed
+
+ return fill(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+ $*{missingPropUseCounters}
+ $*{indexedOrExpando}
+
+ $*{namedOrOnPropotype}
+ return true;
+ """,
+ missingPropUseCounters=missingPropUseCounters,
+ indexedOrExpando=getIndexedOrExpando,
+ namedOrOnPropotype=getNamedOrOnPrototype,
+ )
+
+
+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 [LegacyOverrideBuiltIns],
+ # always call the NamedSetter and never do anything else.
+ namedSetter = self.descriptor.operations["NamedSetter"]
+ if namedSetter is not None and self.descriptor.interface.getExtendedAttribute(
+ "LegacyOverrideBuiltIns"
+ ):
+ # Check assumptions.
+ if self.descriptor.supportsIndexedProperties():
+ raise ValueError(
+ "In interface "
+ + self.descriptor.name
+ + ": "
+ + "Can't cope with [LegacyOverrideBuiltIns] and an indexed getter"
+ )
+ if self.descriptor.hasLegacyUnforgeableMembers:
+ raise ValueError(
+ "In interface "
+ + self.descriptor.name
+ + ": "
+ + "Can't cope with [LegacyOverrideBuiltIns] 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("JS::GCContext*", "gcx"), 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 CGJSProxyHandler_getInstance(ClassMethod):
+ def __init__(self, type):
+ self.type = type
+ ClassMethod.__init__(
+ self, "getInstance", "const %s*" % self.type, [], static=True
+ )
+
+ def getBody(self):
+ return fill(
+ """
+ static const ${type} instance;
+ return &instance;
+ """,
+ type=self.type,
+ )
+
+
+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<Maybe<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.isSome()) {
+ 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),
+ CGJSProxyHandler_getInstance("DOMProxyHandler"),
+ 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("LegacyOverrideBuiltIns")
+ ):
+ 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("LegacyOverrideBuiltIns"):
+ 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(
+ "LegacyLenientSetter"
+ ):
+ 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()
+
+ iteratorCGThings = None
+ if (
+ descriptor.interface.isIterable()
+ and descriptor.interface.maplikeOrSetlikeOrIterable.isPairIterator()
+ ) or descriptor.interface.isAsyncIterable():
+ # We need the Wrap function when using the [Async]IterableIterator type, so we want to declare it before we need it. We don't really want to expose it in the header file, so we make it static too.
+ iteratorCGThings = []
+ itr_iface = (
+ descriptor.interface.maplikeOrSetlikeOrIterable.iteratorType.inner
+ )
+ iteratorDescriptor = descriptor.getDescriptor(itr_iface.identifier.name)
+ iteratorCGThings.append(
+ CGWrapNonWrapperCacheMethod(
+ iteratorDescriptor, static=True, signatureOnly=True
+ )
+ )
+ iteratorCGThings = CGList(
+ (CGIndenter(t, declareOnly=True) for t in iteratorCGThings), "\n"
+ )
+ iteratorCGThings = CGWrapper(iteratorCGThings, pre="\n", post="\n")
+ iteratorCGThings = CGWrapper(
+ CGNamespace(
+ toBindingNamespace(iteratorDescriptor.name), iteratorCGThings
+ ),
+ post="\n",
+ )
+
+ cgThings = []
+
+ isIteratorInterface = (
+ descriptor.interface.isIteratorInterface()
+ or descriptor.interface.isAsyncIteratorInterface()
+ )
+ if not isIteratorInterface:
+ 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.legacyFactoryFunctions:
+ cgThings.append(
+ CGClassConstructor(descriptor, n, LegacyFactoryFunctionName(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.type.isObservableArray():
+ cgThings.append(
+ CGObservableArrayProxyHandlerGenerator(descriptor, m)
+ )
+ cgThings.append(CGObservableArrayHelperGenerator(descriptor, m))
+ 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("LegacyLenientSetter"):
+ # XXX In this case, we need to add an include for mozilla/dom/Document.h to the generated cpp file.
+ 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)
+ )
+
+ # Declare our DOMProxyHandler.
+ if descriptor.concrete and 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))
+
+ # 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 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 descriptor.interface.hasInterfaceObject():
+ cgThings.append(CGClassConstructor(descriptor, descriptor.interface.ctor()))
+ cgThings.append(CGInterfaceObjectJSClass(descriptor, properties))
+ cgThings.append(CGLegacyFactoryFunctions(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))
+
+ # CGDOMProxyJSClass/CGDOMJSClass need GetProtoObjectHandle, but we don't want to export it for the iterator interfaces, so declare it here.
+ if isIteratorInterface:
+ cgThings.append(
+ CGGetProtoObjectHandleMethod(
+ descriptor, static=True, signatureOnly=True
+ )
+ )
+
+ if descriptor.proxy:
+ cgThings.append(CGDOMJSProxyHandlerDefiner(handlerThing))
+ cgThings.append(CGDOMProxyJSClass(descriptor))
+ else:
+ cgThings.append(CGDOMJSClass(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))
+ cgThings.append(CGWrapMethod(descriptor))
+ else:
+ cgThings.append(
+ CGWrapNonWrapperCacheMethod(descriptor, static=isIteratorInterface)
+ )
+
+ # 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,
+ static=isIteratorInterface,
+ )
+ )
+
+ # CGGetProtoObjectMethod and CGGetConstructorObjectMethod need
+ # to come after CGCreateInterfaceObjectsMethod.
+ if (
+ descriptor.interface.hasInterfacePrototypeObject()
+ and not descriptor.hasOrdinaryObjectPrototype
+ ):
+ cgThings.append(
+ CGGetProtoObjectHandleMethod(descriptor, static=isIteratorInterface)
+ )
+ if descriptor.interface.hasChildInterfaces():
+ assert not isIteratorInterface
+ 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")
+ cgThings = CGWrapper(
+ CGNamespace(toBindingNamespace(descriptor.name), cgThings), post="\n"
+ )
+ self.cgRoot = CGList([iteratorCGThings, cgThings], "\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(reinterpret_cast<jsid*>(atomsCache)->isVoid());
+
+ // 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 (reinterpret_cast<jsid*>(atomsCache)->isVoid() &&
+ !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_CONVERSION_ERROR>(sourceDescription, "dictionary");
+ }
+
+ """
+ )
+
+ 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 (reinterpret_cast<jsid*>(atomsCache)->isVoid() &&
+ !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
+
+ ctors.append(
+ ClassConstructor(
+ [Argument("%s&&" % selfName, "aOther")],
+ default=True,
+ visibility="public",
+ baseConstructors=baseConstructors,
+ )
+ )
+
+ 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::TraceRoot(trc, %s, "%s");\n' % ("&" + memberData, memberName)
+ )
+ if type.nullable():
+ trace = CGIfWrapper(trace, memberData)
+ elif type.isAny():
+ trace = CGGeneric(
+ 'JS::TraceRoot(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.isUndefined() or 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()
+
+
+class CGRegisterShadowRealmBindings(CGAbstractMethod):
+ def __init__(self, config):
+ CGAbstractMethod.__init__(
+ self,
+ None,
+ "RegisterShadowRealmBindings",
+ "bool",
+ [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")],
+ )
+ self.config = config
+
+ def definition_body(self):
+ descriptors = self.config.getDescriptors(
+ hasInterfaceObject=True, isExposedInShadowRealms=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 BindingNamesOffsetEnum(name):
+ return CppKeywords.checkMethodName(name.replace(" ", "_"))
+
+
+class CGGlobalNames(CGGeneric):
+ def __init__(self, names):
+ """
+ names is expected to be a list of tuples of the name and the descriptor it refers to.
+ """
+
+ strings = []
+ entries = []
+ for name, desc in names:
+ # 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 */ BindingNamesOffset::${nameOffset},
+ /* mNameLength */ ${nameLength},
+ /* mConstructorId */ constructors::id::${realname},
+ /* mCreate */ ${realname}_Binding::CreateInterfaceObjects,
+ /* mEnabled */ ${enabled}
+ }
+ """,
+ nameOffset=BindingNamesOffsetEnum(name),
+ 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, BindingName(entry.mNameOffset), entry.mNameLength)) {
+ return &entry;
+ }
+ return nullptr;
+ """
+ ),
+ )
+
+ define = fill(
+ """
+ const uint32_t WebIDLGlobalNameHash::sCount = ${count};
+
+ $*{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)))
+
+ 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()
+ or d.interface.isAsyncIteratorInterface()
+ ):
+ 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
+ )
+
+ for m in d.interface.members:
+ if m.isAttr() and m.type.isObservableArray():
+ builder.forwardDeclareForType(m.type, config)
+
+ # We just about always need NativePropertyHooks
+ builder.addInMozillaDom("NativePropertyHooks", isStruct=True)
+ builder.addInMozillaDom("ProtoAndIfaceCache")
+
+ 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())
+
+
+def dependencySortDictionariesAndUnionsAndCallbacks(types):
+ 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()])
+ if type.isRecord():
+ return set([type.unroll().inner])
+ if type.isCallback():
+ return set([type.unroll()])
+ return set()
+
+ def getDependencies(unionTypeOrDictionaryOrCallback):
+ if isinstance(unionTypeOrDictionaryOrCallback, IDLDictionary):
+ deps = set()
+ if unionTypeOrDictionaryOrCallback.parent:
+ deps.add(unionTypeOrDictionaryOrCallback.parent)
+ for member in unionTypeOrDictionaryOrCallback.members:
+ deps |= getDependenciesFromType(member.type)
+ return deps
+
+ if (
+ unionTypeOrDictionaryOrCallback.isType()
+ and unionTypeOrDictionaryOrCallback.isUnion()
+ ):
+ deps = set()
+ for member in unionTypeOrDictionaryOrCallback.flatMemberTypes:
+ deps |= getDependenciesFromType(member)
+ return deps
+
+ assert unionTypeOrDictionaryOrCallback.isCallback()
+ return set()
+
+ def getName(unionTypeOrDictionaryOrCallback):
+ if isinstance(unionTypeOrDictionaryOrCallback, IDLDictionary):
+ return unionTypeOrDictionaryOrCallback.identifier.name
+
+ if (
+ unionTypeOrDictionaryOrCallback.isType()
+ and unionTypeOrDictionaryOrCallback.isUnion()
+ ):
+ return unionTypeOrDictionaryOrCallback.name
+
+ assert unionTypeOrDictionaryOrCallback.isCallback()
+ return unionTypeOrDictionaryOrCallback.identifier.name
+
+ return dependencySortObjects(types, getDependencies, getName)
+
+
+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", "MainThreadUtils.h"), True
+ )
+ bindingDeclareHeaders = dict.fromkeys(
+ (
+ "mozilla/dom/BindingDeclarations.h",
+ "mozilla/dom/Nullable.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
+ )
+ bindingHeaders["mozilla/dom/IterableIterator.h"] = any(
+ (
+ d.interface.isIteratorInterface()
+ and d.interface.maplikeOrSetlikeOrIterable.isPairIterator()
+ )
+ or d.interface.isAsyncIteratorInterface()
+ or d.interface.isIterable()
+ or d.interface.isAsyncIterable()
+ for d in descriptors
+ )
+
+ def memberNeedsSubjectPrincipal(d, m):
+ if m.isAttr():
+ return (
+ "needsSubjectPrincipal" in d.getExtendedAttributes(m, getter=True)
+ ) or (
+ not m.readonly
+ and "needsSubjectPrincipal"
+ in d.getExtendedAttributes(m, setter=True)
+ )
+ return m.isMethod() and "needsSubjectPrincipal" in d.getExtendedAttributes(
+ m
+ )
+
+ if any(
+ memberNeedsSubjectPrincipal(d, m)
+ for d in descriptors
+ for m in d.interface.members
+ ):
+ bindingHeaders["mozilla/BasePrincipal.h"] = True
+ bindingHeaders["nsJSPrincipals.h"] = True
+
+ # The conditions for which we generate profiler labels are fairly
+ # complicated. The check below is a little imprecise to make it simple.
+ # It includes the profiler header in all cases where it is necessary and
+ # generates only a few false positives.
+ bindingHeaders["mozilla/ProfilerLabels.h"] = any(
+ # constructor profiler label
+ d.interface.legacyFactoryFunctions
+ or (d.interface.hasInterfaceObject() and d.interface.ctor())
+ or any(
+ # getter/setter profiler labels
+ m.isAttr()
+ # method profiler label
+ or m.isMethod()
+ for m in d.interface.members
+ )
+ 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)
+
+ def descriptorHasObservableArrayTypes(desc):
+ def hasObservableArrayTypes(m):
+ return m.isAttr() and m.type.isObservableArray()
+
+ return any(hasObservableArrayTypes(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)
+ or descriptorHasObservableArrayTypes(d)
+ for d in descriptors
+ )
+ bindingDeclareHeaders["js/TypeDecls.h"] = not bindingDeclareHeaders["jsapi.h"]
+ bindingDeclareHeaders["js/RootingAPI.h"] = not bindingDeclareHeaders["jsapi.h"]
+
+ # JS::IsCallable
+ bindingDeclareHeaders["js/CallAndConstruct.h"] = True
+
+ def descriptorHasIteratorAlias(desc):
+ def hasIteratorAlias(m):
+ return m.isMethod() and (
+ ("@@iterator" in m.aliases) or ("@@asyncIterator" 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
+
+ # JS::IsCallable, JS::Call, JS::Construct
+ bindingHeaders["js/CallAndConstruct.h"] = True
+
+ # JS_IsExceptionPending
+ bindingHeaders["js/Exception.h"] = True
+
+ # JS::Map{Clear, Delete, Has, Get, Set}
+ bindingHeaders["js/MapAndSet.h"] = True
+
+ # JS_DefineElement, JS_DefineProperty, JS_DefinePropertyById,
+ # JS_DefineUCProperty, JS_ForwardGetPropertyTo, JS_GetProperty,
+ # JS_GetPropertyById, JS_HasPropertyById, JS_SetProperty,
+ # JS_SetPropertyById
+ bindingHeaders["js/PropertyAndElement.h"] = True
+
+ # JS_GetOwnPropertyDescriptorById
+ bindingHeaders["js/PropertyDescriptor.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["mozilla/dom/ProxyHandlerUtils.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
+ )
+
+ if callbackDescriptors:
+ bindingDeclareHeaders["mozilla/ErrorResult.h"] = True
+
+ 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] or [Trial]
+ for d in descriptors:
+ if d.concrete:
+ if d.instrumentedProps:
+ bindingHeaders["mozilla/UseCounter.h"] = True
+ if d.needsMissingPropUseCounters:
+ bindingHeaders[prefHeader(MISSING_PROP_PREF)] = True
+ if d.interface.isSerializable():
+ bindingHeaders["mozilla/dom/StructuredCloneTags.h"] = True
+ if d.wantsXrays:
+ bindingHeaders["mozilla/Atomics.h"] = True
+ bindingHeaders["mozilla/dom/XrayExpandoClass.h"] = True
+ if d.wantsXrayExpandoClass:
+ bindingHeaders["XrayWrapper.h"] = True
+ for m in d.interface.members:
+ if m.getExtendedAttribute("UseCounter"):
+ bindingHeaders["mozilla/UseCounter.h"] = True
+ if m.getExtendedAttribute("Trial"):
+ bindingHeaders["mozilla/OriginTrials.h"] = True
+
+ bindingHeaders["mozilla/dom/SimpleGlobalObject.h"] = any(
+ CGDictionary.dictionarySafeToJSONify(d) for d in dictionaries
+ )
+
+ 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.
+
+ for t in dependencySortDictionariesAndUnionsAndCallbacks(
+ dictionaries + unionStructs + callbacks
+ ):
+ if t.isDictionary():
+ cgthings.append(CGDictionary(t, config))
+ elif t.isUnion():
+ cgthings.append(CGUnionStruct(t, config))
+ cgthings.append(CGUnionStruct(t, config, True))
+ else:
+ assert t.isCallback()
+ cgthings.append(CGCallbackFunction(t, config))
+ cgthings.append(CGNamespace("binding_detail", CGFastCallback(t)))
+
+ # 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].isUndefined()
+ ),
+ 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.isUndefined():
+ 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 "needsErrorResult" 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.undefined],
+ [FakeArgument(attr.type)],
+ ),
+ 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.legacyFactoryFunctions:
+ 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() or m.type.isObservableArray():
+ # 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], 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], []),
+ [],
+ ),
+ )
+ # 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]
+ ),
+ [],
+ ),
+ [],
+ )
+ )
+
+ 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 CGExampleObservableArrayCallback(CGNativeMember):
+ def __init__(self, descriptor, attr, callbackName):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ CGNativeMember.__init__(
+ self,
+ descriptor,
+ attr,
+ self.makeNativeName(attr, callbackName),
+ (
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ [
+ FakeArgument(attr.type.inner, "aValue"),
+ FakeArgument(
+ BuiltinTypes[IDLBuiltinType.Types.unsigned_long], "aIndex"
+ ),
+ ],
+ ),
+ ["needsErrorResult"],
+ )
+
+ def declare(self, cgClass):
+ assert self.member.isAttr()
+ assert self.member.type.isObservableArray()
+ return CGNativeMember.declare(self, cgClass)
+
+ def define(self, cgClass):
+ return ""
+
+ @staticmethod
+ def makeNativeName(attr, callbackName):
+ assert attr.isAttr()
+ nativeName = MakeNativeName(attr.identifier.name)
+ return "On" + callbackName + nativeName
+
+
+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_WRAPPERCACHE_CLASS(%s)\n"
+ "\n" % self.nativeLeafName(descriptor)
+ )
+
+ if descriptor.interface.hasChildInterfaces():
+ decorators = ""
+ else:
+ decorators = "final"
+
+ for m in descriptor.interface.members:
+ if m.isAttr() and m.type.isObservableArray():
+ self.methodDecls.append(
+ CGExampleObservableArrayCallback(descriptor, m, "Set")
+ )
+ self.methodDecls.append(
+ CGExampleObservableArrayCallback(descriptor, m, "Delete")
+ )
+
+ 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, attr.isStatic())
+ )
+
+
+def callbackSetterName(attr, descriptor):
+ return "Set" + MakeNativeName(
+ descriptor.binaryNameFor(attr.identifier.name, attr.isStatic())
+ )
+
+
+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.undefined],
+ [FakeArgument(attr.type)],
+ ),
+ descriptor.getExtendedAttributes(attr, setter=True),
+ passJSBitsAsNeeded=False,
+ )
+
+ def getImpl(self):
+ callbackArgs = [
+ arg.name
+ for arg in self.getArgs(
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ [FakeArgument(self.member.type)],
+ )
+ ]
+ 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,
+ )
+ 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_WRAPPERCACHE_CLASS(%s)\n" % descriptor.name
+ )
+ extradefinitions = fill(
+ """
+ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_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_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(),
+ )
+ )
+
+ 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
+
+ idlist = [
+ descriptor.binaryNameFor(m.identifier.name, m.isStatic())
+ for m in iface.members
+ if m.isAttr() or m.isMethod()
+ ]
+ if needInitId:
+ idlist.append("__init")
+
+ 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,
+ passJSBitsAsNeeded=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=["needsErrorResult"],
+ passJSBitsAsNeeded=passJSBitsAsNeeded,
+ 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.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})) {
+ $*{failureCode}
+ return${errorReturn};
+ }
+ """,
+ argCount=self.argCountStr,
+ failureCode=self.getArgvDeclFailureCode(),
+ errorReturn=self.getDefaultRetval(),
+ )
+ else:
+ # Avoid weird 0-sized arrays
+ argvDecl = ""
+ convertArgs = self.getArgConversions()
+ doCall = self.getCall()
+ returnResult = self.getResultConversion()
+
+ body = declRval + argvDecl + convertArgs + doCall
+ if self.needsScopeBody():
+ body = "{\n" + indent(body) + "}\n"
+ return setupCall + body + returnResult
+
+ def needsScopeBody(self):
+ return False
+
+ def getArgvDeclFailureCode(self):
+ return dedent(
+ """
+ // That threw an exception on the JSContext, and our CallSetup will do
+ // the right thing with that.
+ """
+ )
+
+ def getExceptionCode(self, forResult):
+ return fill(
+ """
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return${defaultRetval};
+ """,
+ defaultRetval=self.getDefaultRetval(),
+ )
+
+ def getResultConversion(
+ self, val="rval", failureCode=None, isDefinitelyObject=False, exceptionCode=None
+ ):
+ replacements = {
+ "val": val,
+ "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,
+ failureCode=failureCode,
+ isDefinitelyObject=isDefinitelyObject,
+ exceptionCode=exceptionCode or self.getExceptionCode(forResult=True),
+ 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 = ""
+
+ wrapScope = self.wrapScope
+ if arg.type.isUnion() and wrapScope is None:
+ prepend += (
+ "JS::Rooted<JSObject*> callbackObj(cx, CallbackKnownNotGray());\n"
+ )
+ 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": wrapScope,
+ "returnsNewObject": False,
+ "exceptionCode": self.getExceptionCode(forResult=False),
+ "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 getNoteCallFailed(self):
+ return fill(
+ """
+ aRv.NoteJSContextException(cx);
+ return${errorReturn};
+ """,
+ errorReturn=self.getDefaultRetval(),
+ )
+
+ 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)) {
+ $*{noteError}
+ }
+ """,
+ declCallable=self.getCallableDecl(),
+ declThis=self.getThisDecl(),
+ callGuard=self.getCallGuard(),
+ thisVal=self.getThisVal(),
+ args=args,
+ noteError=self.getNoteCallFailed(),
+ )
+
+
+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 getNoteCallFailed(self):
+ if self.retvalType.isPromise():
+ return dedent(
+ """
+ // Convert exception to a rejected promise.
+ // See https://heycam.github.io/webidl/#call-a-user-objects-operation
+ // step 12 and step 15.5.
+ return CreateRejectedPromiseFromThrownException(cx, aRv);
+ """
+ )
+ return CallbackMethod.getNoteCallFailed(self)
+
+ def getExceptionCode(self, forResult):
+ # If the result value is a promise, and conversion
+ # to the promise throws an exception we shouldn't
+ # try to convert that exception to a promise again.
+ if self.retvalType.isPromise() and not forResult:
+ return dedent(
+ """
+ // Convert exception to a rejected promise.
+ // See https://heycam.github.io/webidl/#call-a-user-objects-operation
+ // step 10 and step 15.5.
+ return CreateRejectedPromiseFromThrownException(cx, aRv);
+ """
+ )
+ return CallbackMethod.getExceptionCode(self, forResult)
+
+ 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, False)
+ 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 ((reinterpret_cast<jsid*>(atomsCache)->isVoid() &&
+ !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, False)),
+ 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 ((reinterpret_cast<jsid*>(atomsCache)->isVoid()
+ && !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, False)
+ ),
+ errorReturn=self.getDefaultRetval(),
+ )
+
+
+class CallbackSetter(CallbackAccessor):
+ def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs):
+ CallbackAccessor.__init__(
+ self,
+ attr,
+ (
+ BuiltinTypes[IDLBuiltinType.Types.undefined],
+ [FakeArgument(attr.type)],
+ ),
+ 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 ((reinterpret_cast<jsid*>(atomsCache)->isVoid() &&
+ !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, False)
+ ),
+ 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.undefined], sig[1]),
+ "__init",
+ "__Init",
+ descriptor,
+ singleOperation=False,
+ rethrowContentException=True,
+ spiderMonkeyInterfacesAreStructs=True,
+ )
+
+ def getPrettyName(self):
+ return "__init"
+
+
+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.undefined],
+ [FakeArgument(BuiltinTypes[IDLBuiltinType.Types.domstring], "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,
+ needsValueTypeReturn=False,
+ 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
+ code = dedent(
+ """
+ if (!JS::${funcName}(${args})) {
+ $*{errorReturn}
+ }
+ """
+ )
+
+ if needsValueTypeReturn:
+ assert self.helperImpl and impl_method_name == "get"
+ code += fill(
+ """
+ if (result.isUndefined()) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return${retval};
+ }
+ """,
+ retval=self.helperImpl.getDefaultRetval(),
+ )
+
+ self.cgRoot.append(
+ CGWrapper(
+ CGGeneric(
+ fill(
+ code,
+ 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("bool retVal;\n")], ["&retVal"], [])
+ 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 = []
+ # We don't need to create the result variable because it'll be created elsewhere
+ # for JSObject Get method
+ if not self.helperImpl or not self.helperImpl.needsScopeBody():
+ code = [
+ CGGeneric(
+ dedent(
+ """
+ JS::Rooted<JS::Value> result(cx);
+ """
+ )
+ )
+ ]
+
+ arguments = ["&result"]
+ return self.mergeTuples(r, (code, arguments, []))
+
+ 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 CGHelperFunctionGenerator(CallbackMember):
+ """
+ Generates code to allow C++ to perform operations. Gets a context from the
+ binding wrapper, turns arguments into JS::Values (via
+ CallbackMember/CGNativeMember argument conversion), then uses
+ getCall to generate the body for getting result, and maybe convert the
+ result into return type (via CallbackMember/CGNativeMember result
+ conversion)
+ """
+
+ class HelperFunction(CGAbstractMethod):
+ """
+ Generates context retrieval code and rooted JSObject for interface for
+ method generator to use
+ """
+
+ def __init__(self, descriptor, name, args, code, returnType):
+ self.code = code
+ CGAbstractMethod.__init__(self, descriptor, name, returnType, args)
+
+ def definition_body(self):
+ return self.code
+
+ def __init__(
+ self,
+ descriptor,
+ name,
+ args,
+ returnType=BuiltinTypes[IDLBuiltinType.Types.undefined],
+ needsResultConversion=True,
+ ):
+ assert returnType.isType()
+ self.needsResultConversion = needsResultConversion
+
+ # 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,
+ [returnType, args],
+ name,
+ descriptor,
+ False,
+ wrapScope="obj",
+ passJSBitsAsNeeded=typeNeedsCx(returnType),
+ )
+
+ # Wrap CallbackMember body code into a CGAbstractMethod to make
+ # generation easier.
+ self.implMethod = CGHelperFunctionGenerator.HelperFunction(
+ descriptor, name, self.args, self.body, self.returnType
+ )
+
+ def getCallSetup(self):
+ # If passJSBitsAsNeeded is true, it means the caller will provide a
+ # JSContext, so we don't need to create JSContext and enter
+ # UnprivilegedJunkScopeOrWorkerGlobal here.
+ code = "MOZ_ASSERT(self);\n"
+ if not self.passJSBitsAsNeeded:
+ code += dedent(
+ """
+ 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.
+ JSObject* scope = UnprivilegedJunkScopeOrWorkerGlobal(fallible);
+ if (!scope) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return%s;
+ }
+ JSAutoRealm tempRealm(cx, scope);
+ """
+ % self.getDefaultRetval()
+ )
+
+ code += dedent(
+ """
+ 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);
+ """
+ % self.getDefaultRetval()
+ )
+
+ # We'd like wrap the inner code in a scope such that the code can use the
+ # same realm. So here we are creating the result variable outside of the
+ # scope.
+ if self.needsScopeBody():
+ code += "JS::Rooted<JS::Value> result(cx);\n"
+
+ return code
+
+ 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 needsScopeBody(self):
+ return self.passJSBitsAsNeeded
+
+ def getArgvDeclFailureCode(self):
+ return "aRv.Throw(NS_ERROR_UNEXPECTED);\n"
+
+ def getResultConversion(self):
+ if self.needsResultConversion:
+ code = ""
+ if self.needsScopeBody():
+ code = dedent(
+ """
+ if (!JS_WrapValue(cx, &result)) {
+ aRv.NoteJSContextException(cx);
+ return;
+ }
+ """
+ )
+
+ failureCode = dedent("aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n")
+
+ exceptionCode = None
+ if self.retvalType.isPrimitive():
+ exceptionCode = dedent(
+ "aRv.NoteJSContextException(cx);\nreturn%s;\n"
+ % self.getDefaultRetval()
+ )
+
+ return code + CallbackMember.getResultConversion(
+ self,
+ "result",
+ failureCode=failureCode,
+ isDefinitelyObject=True,
+ exceptionCode=exceptionCode,
+ )
+
+ assignRetval = string.Template(
+ self.getRetvalInfo(self.retvalType, False)[2]
+ ).substitute(
+ {
+ "declName": "retVal",
+ }
+ )
+ return assignRetval
+
+ def getRvalDecl(self):
+ # hack to make sure we put JSAutoRealm inside the body scope
+ return "JSAutoRealm reflectorRealm(cx, obj);\n"
+
+ def getArgcDecl(self):
+ # Don't need argc for anything.
+ return None
+
+ def getCall(self):
+ assert False # Override me!
+
+ def getPrettyName(self):
+ return self.name
+
+ def declare(self):
+ return self.implMethod.declare()
+
+ def define(self):
+ return self.implMethod.define()
+
+
+class CGMaplikeOrSetlikeHelperFunctionGenerator(CGHelperFunctionGenerator):
+ """
+ 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.
+ """
+
+ def __init__(
+ self,
+ descriptor,
+ maplikeOrSetlike,
+ name,
+ needsKeyArg=False,
+ needsValueArg=False,
+ needsValueTypeReturn=False,
+ needsBoolReturn=False,
+ needsResultConversion=True,
+ ):
+ self.maplikeOrSetlike = maplikeOrSetlike
+ self.needsValueTypeReturn = needsValueTypeReturn
+
+ args = []
+ if needsKeyArg:
+ args.append(FakeArgument(maplikeOrSetlike.keyType, "aKey"))
+ if needsValueArg:
+ assert needsKeyArg
+ assert not needsValueTypeReturn
+ args.append(FakeArgument(maplikeOrSetlike.valueType, "aValue"))
+
+ returnType = BuiltinTypes[IDLBuiltinType.Types.undefined]
+ if needsBoolReturn:
+ returnType = BuiltinTypes[IDLBuiltinType.Types.boolean]
+ elif needsValueTypeReturn:
+ returnType = maplikeOrSetlike.valueType
+
+ CGHelperFunctionGenerator.__init__(
+ self,
+ descriptor,
+ name,
+ args,
+ returnType,
+ needsResultConversion,
+ )
+
+ def getCall(self):
+ return CGMaplikeOrSetlikeMethodGenerator(
+ self.descriptorProvider,
+ self.maplikeOrSetlike,
+ self.name.lower(),
+ self.needsValueTypeReturn,
+ helperImpl=self,
+ ).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,
+ needsResultConversion=False,
+ ),
+ CGMaplikeOrSetlikeHelperFunctionGenerator(
+ descriptor,
+ maplikeOrSetlike,
+ "Has",
+ needsKeyArg=True,
+ needsBoolReturn=True,
+ needsResultConversion=False,
+ ),
+ ]
+ if self.maplikeOrSetlike.isMaplike():
+ self.helpers.append(
+ CGMaplikeOrSetlikeHelperFunctionGenerator(
+ descriptor,
+ maplikeOrSetlike,
+ "Set",
+ needsKeyArg=True,
+ needsValueArg=True,
+ )
+ )
+ self.helpers.append(
+ CGMaplikeOrSetlikeHelperFunctionGenerator(
+ descriptor,
+ maplikeOrSetlike,
+ "Get",
+ needsKeyArg=True,
+ needsValueTypeReturn=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, methodName, args):
+ if methodName == "forEach":
+ assert len(args) == 2
+
+ 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
+
+ if descriptor.interface.isIterable():
+ assert descriptor.interface.maplikeOrSetlikeOrIterable.isPairIterator()
+ assert len(args) == 0
+
+ wrap = f"{descriptor.interface.identifier.name}Iterator_Binding::Wrap"
+ iterClass = f"mozilla::dom::binding_detail::WrappableIterableIterator<{descriptor.nativeType}, &{wrap}>"
+ else:
+ needReturnMethod = toStringBool(
+ descriptor.interface.maplikeOrSetlikeOrIterable.getExtendedAttribute(
+ "GenerateReturnMethod"
+ )
+ is not None
+ )
+ wrap = f"{descriptor.interface.identifier.name}AsyncIterator_Binding::Wrap"
+ iterClass = f"mozilla::dom::binding_detail::WrappableAsyncIterableIterator<{descriptor.nativeType}, {needReturnMethod}, &{wrap}>"
+
+ createIterator = fill(
+ """
+ typedef ${iterClass} itrType;
+ RefPtr<itrType> result(new itrType(self,
+ itrType::IteratorType::${itrMethod}));
+ """,
+ iterClass=iterClass,
+ itrMethod=methodName.title(),
+ )
+
+ if descriptor.interface.isAsyncIterable():
+ args.append("initError")
+ createIterator = fill(
+ """
+ $*{createIterator}
+ {
+ ErrorResult initError;
+ self->InitAsyncIteratorData(result->Data(), itrType::IteratorType::${itrMethod}, ${args});
+ if (initError.MaybeSetPendingException(cx, "Asynchronous iterator initialization steps for ${ifaceName} failed")) {
+ return false;
+ }
+ }
+ """,
+ createIterator=createIterator,
+ itrMethod=methodName.title(),
+ args=", ".join(args),
+ ifaceName=descriptor.interface.identifier.name,
+ )
+
+ CGGeneric.__init__(self, createIterator)
+
+
+def getObservableArrayBackingObject(descriptor, attr, errorReturn="return false;\n"):
+ """
+ Generate code to get/create a JS backing list for an observableArray attribute
+ from the declaration slot.
+ """
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+
+ # GetObservableArrayBackingObject may return a wrapped object for Xrays, so
+ # when we create it we need to unwrap it to store the interface in the
+ # reserved slot.
+ return fill(
+ """
+ JS::Rooted<JSObject*> backingObj(cx);
+ bool created = false;
+ if (!GetObservableArrayBackingObject(cx, obj, ${slot},
+ &backingObj, &created, ${namespace}::ObservableArrayProxyHandler::getInstance(),
+ self)) {
+ $*{errorReturn}
+ }
+ if (created) {
+ PreserveWrapper(self);
+ }
+ """,
+ namespace=toBindingNamespace(MakeNativeName(attr.identifier.name)),
+ slot=memberReservedSlot(attr, descriptor),
+ errorReturn=errorReturn,
+ selfType=descriptor.nativeType,
+ )
+
+
+def getObservableArrayGetterBody(descriptor, attr):
+ """
+ Creates the body for the getter method of an observableArray attribute.
+ """
+ assert attr.type.isObservableArray()
+ return fill(
+ """
+ $*{getBackingObj}
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+ args.rval().setObject(*backingObj);
+ return true;
+ """,
+ getBackingObj=getObservableArrayBackingObject(descriptor, attr),
+ )
+
+
+class CGObservableArrayProxyHandler_callback(ClassMethod):
+ """
+ Base class for declaring and defining the hook methods for ObservableArrayProxyHandler
+ subclasses to get the interface native object from backing object and calls
+ its On{Set|Delete}* callback.
+
+ * 'callbackType': "Set" or "Delete".
+ * 'invalidTypeFatal' (optional): If True, we don't expect the type
+ conversion would fail, so generate the
+ assertion code if type conversion fails.
+ """
+
+ def __init__(
+ self, descriptor, attr, name, args, callbackType, invalidTypeFatal=False
+ ):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ assert callbackType in ["Set", "Delete"]
+ self.descriptor = descriptor
+ self.attr = attr
+ self.innertype = attr.type.inner
+ self.callbackType = callbackType
+ self.invalidTypeFatal = invalidTypeFatal
+ ClassMethod.__init__(
+ self,
+ name,
+ "bool",
+ args,
+ visibility="protected",
+ virtual=True,
+ override=True,
+ const=True,
+ )
+
+ def preConversion(self):
+ """
+ The code to run before the conversion steps.
+ """
+ return ""
+
+ def preCallback(self):
+ """
+ The code to run before calling the callback.
+ """
+ return ""
+
+ def postCallback(self):
+ """
+ The code to run after calling the callback, all subclasses should override
+ this to generate the return values.
+ """
+ assert False # Override me!
+
+ def getBody(self):
+ exceptionCode = (
+ fill(
+ """
+ MOZ_ASSERT_UNREACHABLE("The item in ObservableArray backing list is not ${innertype}?");
+ return false;
+ """,
+ innertype=self.innertype,
+ )
+ if self.invalidTypeFatal
+ else None
+ )
+ convertType = instantiateJSToNativeConversion(
+ getJSToNativeConversionInfo(
+ self.innertype,
+ self.descriptor,
+ sourceDescription="Element in ObservableArray backing list",
+ exceptionCode=exceptionCode,
+ ),
+ {
+ "declName": "decl",
+ "holderName": "holder",
+ "val": "aValue",
+ },
+ )
+ callbackArgs = ["decl", "aIndex", "rv"]
+ if typeNeedsCx(self.innertype):
+ callbackArgs.insert(0, "cx")
+ return fill(
+ """
+ MOZ_ASSERT(IsObservableArrayProxy(aProxy));
+ $*{preConversion}
+
+ BindingCallContext cx(aCx, "ObservableArray ${name}");
+ $*{convertType}
+
+ $*{preCallback}
+ JS::Value val = js::GetProxyReservedSlot(aProxy, OBSERVABLE_ARRAY_DOM_INTERFACE_SLOT);
+ auto* interface = static_cast<${ifaceType}*>(val.toPrivate());
+ MOZ_ASSERT(interface);
+
+ ErrorResult rv;
+ MOZ_KnownLive(interface)->${methodName}(${callbackArgs});
+ $*{postCallback}
+ """,
+ preConversion=self.preConversion(),
+ name=self.name,
+ convertType=convertType.define(),
+ preCallback=self.preCallback(),
+ ifaceType=self.descriptor.nativeType,
+ methodName="On%s%s"
+ % (self.callbackType, MakeNativeName(self.attr.identifier.name)),
+ callbackArgs=", ".join(callbackArgs),
+ postCallback=self.postCallback(),
+ )
+
+
+class CGObservableArrayProxyHandler_OnDeleteItem(
+ CGObservableArrayProxyHandler_callback
+):
+ """
+ Declares and defines the hook methods for ObservableArrayProxyHandler
+ subclasses to get the interface native object from backing object and calls
+ its OnDelete* callback.
+ """
+
+ def __init__(self, descriptor, attr):
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aProxy"),
+ Argument("JS::Handle<JS::Value>", "aValue"),
+ Argument("uint32_t", "aIndex"),
+ ]
+ CGObservableArrayProxyHandler_callback.__init__(
+ self,
+ descriptor,
+ attr,
+ "OnDeleteItem",
+ args,
+ "Delete",
+ True,
+ )
+
+ def postCallback(self):
+ return dedent(
+ """
+ return !rv.MaybeSetPendingException(cx);
+ """
+ )
+
+
+class CGObservableArrayProxyHandler_SetIndexedValue(
+ CGObservableArrayProxyHandler_callback
+):
+ """
+ Declares and defines the hook methods for ObservableArrayProxyHandler
+ subclasses to run the setting the indexed value steps.
+ """
+
+ def __init__(self, descriptor, attr):
+ args = [
+ Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aProxy"),
+ Argument("JS::Handle<JSObject*>", "aBackingList"),
+ Argument("uint32_t", "aIndex"),
+ Argument("JS::Handle<JS::Value>", "aValue"),
+ Argument("JS::ObjectOpResult&", "aResult"),
+ ]
+ CGObservableArrayProxyHandler_callback.__init__(
+ self,
+ descriptor,
+ attr,
+ "SetIndexedValue",
+ args,
+ "Set",
+ )
+
+ def preConversion(self):
+ return dedent(
+ """
+ uint32_t oldLen;
+ if (!JS::GetArrayLength(aCx, aBackingList, &oldLen)) {
+ return false;
+ }
+
+ if (aIndex > oldLen) {
+ return aResult.failBadIndex();
+ }
+ """
+ )
+
+ def preCallback(self):
+ return dedent(
+ """
+ if (aIndex < oldLen) {
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_GetElement(aCx, aBackingList, aIndex, &value)) {
+ return false;
+ }
+
+ if (!OnDeleteItem(aCx, aProxy, value, aIndex)) {
+ return false;
+ }
+ }
+
+ """
+ )
+
+ def postCallback(self):
+ return dedent(
+ """
+ if (rv.MaybeSetPendingException(cx)) {
+ return false;
+ }
+
+ if (!JS_SetElement(aCx, aBackingList, aIndex, aValue)) {
+ return false;
+ }
+
+ return aResult.succeed();
+ """
+ )
+
+
+class CGObservableArrayProxyHandler(CGThing):
+ """
+ A class for declaring a ObservableArrayProxyHandler.
+ """
+
+ def __init__(self, descriptor, attr):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ methods = [
+ # The item stored in backing object should always be converted successfully.
+ CGObservableArrayProxyHandler_OnDeleteItem(descriptor, attr),
+ CGObservableArrayProxyHandler_SetIndexedValue(descriptor, attr),
+ CGJSProxyHandler_getInstance("ObservableArrayProxyHandler"),
+ ]
+ parentClass = "mozilla::dom::ObservableArrayProxyHandler"
+ self.proxyHandler = CGClass(
+ "ObservableArrayProxyHandler",
+ bases=[ClassBase(parentClass)],
+ constructors=[],
+ methods=methods,
+ )
+
+ def declare(self):
+ # Our class declaration should happen when we're defining
+ return ""
+
+ def define(self):
+ return self.proxyHandler.declare() + "\n" + self.proxyHandler.define()
+
+
+class CGObservableArrayProxyHandlerGenerator(CGNamespace):
+ """
+ Declares and defines convenience methods for accessing backing list objects
+ for observable array attribute. Generates function signatures, un/packs
+ backing list objects from slot, etc.
+ """
+
+ def __init__(self, descriptor, attr):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ namespace = toBindingNamespace(MakeNativeName(attr.identifier.name))
+ proxyHandler = CGObservableArrayProxyHandler(descriptor, attr)
+ CGNamespace.__init__(self, namespace, proxyHandler)
+
+
+class CGObservableArraySetterGenerator(CGGeneric):
+ """
+ Creates setter for an observableArray attributes.
+ """
+
+ def __init__(self, descriptor, attr):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ getBackingObject = getObservableArrayBackingObject(descriptor, attr)
+ setElement = dedent(
+ """
+ if (!JS_SetElement(cx, backingObj, i, val)) {
+ return false;
+ }
+ """
+ )
+ conversion = wrapForType(
+ attr.type.inner,
+ descriptor,
+ {
+ "result": "arg0.ElementAt(i)",
+ "successCode": setElement,
+ "jsvalRef": "val",
+ "jsvalHandle": "&val",
+ },
+ )
+ CGGeneric.__init__(
+ self,
+ fill(
+ """
+ if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
+ JS_ReportErrorASCII(cx, "Accessing from Xray wrapper is not supported.");
+ return false;
+ }
+
+ ${getBackingObject}
+ const ObservableArrayProxyHandler* handler = GetObservableArrayProxyHandler(backingObj);
+ if (!handler->SetLength(cx, backingObj, 0)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> val(cx);
+ for (size_t i = 0; i < arg0.Length(); i++) {
+ $*{conversion}
+ }
+ """,
+ conversion=conversion,
+ getBackingObject=getBackingObject,
+ ),
+ )
+
+
+class CGObservableArrayHelperFunctionGenerator(CGHelperFunctionGenerator):
+ """
+ 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
+ MethodBodyGenerator to generate the body.
+ """
+
+ class MethodBodyGenerator(CGThing):
+ """
+ Creates methods body for observable array attribute. 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,
+ attr,
+ methodName,
+ helperGenerator,
+ needsIndexArg,
+ ):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+
+ CGThing.__init__(self)
+ self.helperGenerator = helperGenerator
+ self.cgRoot = CGList([])
+
+ self.cgRoot.append(
+ CGGeneric(
+ getObservableArrayBackingObject(
+ descriptor,
+ attr,
+ dedent(
+ """
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return%s;
+ """
+ % helperGenerator.getDefaultRetval()
+ ),
+ )
+ )
+ )
+
+ # 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, methodName)
+ except AttributeError:
+ raise TypeError(
+ "Missing observable array method definition '%s'" % methodName
+ )
+ # Method generator returns tuple, containing:
+ #
+ # - a list of CGThings representing setup code for preparing to call
+ # the JS API function
+ # - JS API function name
+ # - a list of arguments needed for the JS API function we're calling
+ # - a list of CGThings representing code needed before return.
+ (setupCode, funcName, arguments, returnCode) = methodGenerator()
+
+ # Append the list of setup code CGThings
+ self.cgRoot.append(CGList(setupCode))
+ # Create the JS API call
+ if needsIndexArg:
+ arguments.insert(0, "aIndex")
+ self.cgRoot.append(
+ CGWrapper(
+ CGGeneric(
+ fill(
+ """
+ aRv.MightThrowJSException();
+ if (!${funcName}(${args})) {
+ aRv.StealExceptionFromJSContext(cx);
+ return${retval};
+ }
+ """,
+ funcName=funcName,
+ args=", ".join(["cx", "backingObj"] + arguments),
+ retval=helperGenerator.getDefaultRetval(),
+ )
+ )
+ )
+ )
+ # Append code before return
+ self.cgRoot.append(CGList(returnCode))
+
+ def elementat(self):
+ setupCode = []
+ if not self.helperGenerator.needsScopeBody():
+ setupCode.append(CGGeneric("JS::Rooted<JS::Value> result(cx);\n"))
+ returnCode = [
+ CGGeneric(
+ fill(
+ """
+ if (result.isUndefined()) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return${retval};
+ }
+ """,
+ retval=self.helperGenerator.getDefaultRetval(),
+ )
+ )
+ ]
+ return (setupCode, "JS_GetElement", ["&result"], returnCode)
+
+ def replaceelementat(self):
+ setupCode = [
+ CGGeneric(
+ fill(
+ """
+ uint32_t length;
+ aRv.MightThrowJSException();
+ if (!JS::GetArrayLength(cx, backingObj, &length)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return${retval};
+ }
+ if (aIndex > length) {
+ aRv.ThrowRangeError("Invalid index");
+ return${retval};
+ }
+ """,
+ retval=self.helperGenerator.getDefaultRetval(),
+ )
+ )
+ ]
+ return (setupCode, "JS_SetElement", ["argv[0]"], [])
+
+ def appendelement(self):
+ setupCode = [
+ CGGeneric(
+ fill(
+ """
+ uint32_t length;
+ aRv.MightThrowJSException();
+ if (!JS::GetArrayLength(cx, backingObj, &length)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return${retval};
+ }
+ """,
+ retval=self.helperGenerator.getDefaultRetval(),
+ )
+ )
+ ]
+ return (setupCode, "JS_SetElement", ["length", "argv[0]"], [])
+
+ def removelastelement(self):
+ setupCode = [
+ CGGeneric(
+ fill(
+ """
+ uint32_t length;
+ aRv.MightThrowJSException();
+ if (!JS::GetArrayLength(cx, backingObj, &length)) {
+ aRv.StealExceptionFromJSContext(cx);
+ return${retval};
+ }
+ if (length == 0) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return${retval};
+ }
+ """,
+ retval=self.helperGenerator.getDefaultRetval(),
+ )
+ )
+ ]
+ return (setupCode, "JS::SetArrayLength", ["length - 1"], [])
+
+ def length(self):
+ return (
+ [CGGeneric("uint32_t retVal;\n")],
+ "JS::GetArrayLength",
+ ["&retVal"],
+ [],
+ )
+
+ def define(self):
+ return self.cgRoot.define()
+
+ def __init__(
+ self,
+ descriptor,
+ attr,
+ name,
+ returnType=BuiltinTypes[IDLBuiltinType.Types.undefined],
+ needsResultConversion=True,
+ needsIndexArg=False,
+ needsValueArg=False,
+ ):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+ self.attr = attr
+ self.needsIndexArg = needsIndexArg
+
+ args = []
+ if needsValueArg:
+ args.append(FakeArgument(attr.type.inner, "aValue"))
+
+ CGHelperFunctionGenerator.__init__(
+ self,
+ descriptor,
+ name,
+ args,
+ returnType,
+ needsResultConversion,
+ )
+
+ def getArgs(self, returnType, argList):
+ if self.needsIndexArg:
+ argList = [
+ FakeArgument(BuiltinTypes[IDLBuiltinType.Types.unsigned_long], "aIndex")
+ ] + argList
+ return CGHelperFunctionGenerator.getArgs(self, returnType, argList)
+
+ def getCall(self):
+ return CGObservableArrayHelperFunctionGenerator.MethodBodyGenerator(
+ self.descriptorProvider,
+ self.attr,
+ self.name.lower(),
+ self,
+ self.needsIndexArg,
+ ).define()
+
+
+class CGObservableArrayHelperGenerator(CGNamespace):
+ """
+ Declares and defines convenience methods for accessing backing object for
+ observable array type. Generates function signatures, un/packs
+ backing objects from slot, etc.
+ """
+
+ def __init__(self, descriptor, attr):
+ assert attr.isAttr()
+ assert attr.type.isObservableArray()
+
+ namespace = "%sHelpers" % MakeNativeName(attr.identifier.name)
+ helpers = [
+ CGObservableArrayHelperFunctionGenerator(
+ descriptor,
+ attr,
+ "ElementAt",
+ returnType=attr.type.inner,
+ needsIndexArg=True,
+ ),
+ CGObservableArrayHelperFunctionGenerator(
+ descriptor,
+ attr,
+ "ReplaceElementAt",
+ needsIndexArg=True,
+ needsValueArg=True,
+ ),
+ CGObservableArrayHelperFunctionGenerator(
+ descriptor,
+ attr,
+ "AppendElement",
+ needsValueArg=True,
+ ),
+ CGObservableArrayHelperFunctionGenerator(
+ descriptor,
+ attr,
+ "RemoveLastElement",
+ ),
+ CGObservableArrayHelperFunctionGenerator(
+ descriptor,
+ attr,
+ "Length",
+ returnType=BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
+ needsResultConversion=False,
+ ),
+ ]
+ CGNamespace.__init__(self, namespace, CGList(helpers, "\n"))
+
+
+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)
+ 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 m: m.identifier.name, 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.getExtendedAttribute(
+ "WantsEventListenerHooks"
+ ):
+ members.append(FakeMember("eventListenerAdded"))
+ members.append(FakeMember("eventListenerRemoved"))
+ if len(members) == 0:
+ continue
+
+ structs.append(
+ buildAtomCacheStructure(
+ d.interface,
+ lambda m: d.binaryNameFor(m.identifier.name, m.isStatic()),
+ 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/BindingNames.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.getClassName() 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 BindingNames(config):
+ declare = fill(
+ """
+ enum class BindingNamesOffset : uint16_t {
+ $*{enumValues}
+ };
+
+ namespace binding_detail {
+ extern const char sBindingNames[];
+ } // namespace binding_detail
+
+ MOZ_ALWAYS_INLINE const char* BindingName(BindingNamesOffset aOffset) {
+ return binding_detail::sBindingNames + static_cast<size_t>(aOffset);
+ }
+ """,
+ enumValues="".join(
+ "%s = %i,\n" % (BindingNamesOffsetEnum(n), o)
+ for (n, o) in config.namesStringOffsets
+ ),
+ )
+ define = fill(
+ """
+ namespace binding_detail {
+
+ const char sBindingNames[] = {
+ $*{namesString}
+ };
+
+ } // namespace binding_detail
+
+ // Making this enum bigger than a uint16_t has consequences on the size
+ // of some structs (eg. WebIDLNameTableEntry) and tables. We should try
+ // to avoid that.
+ static_assert(EnumTypeFitsWithin<BindingNamesOffset, uint16_t>::value,
+ "Size increase");
+ """,
+ namesString=' "\\0"\n'.join(
+ '/* %5i */ "%s"' % (o, n) for (n, o) in config.namesStringOffsets
+ )
+ + "\n",
+ )
+
+ curr = CGGeneric(declare=declare, define=define)
+ curr = CGWrapper(curr, pre="\n", post="\n")
+
+ curr = CGNamespace.build(["mozilla", "dom"], curr)
+ curr = CGWrapper(curr, post="\n")
+
+ curr = CGHeaders(
+ [],
+ [],
+ [],
+ [],
+ ["<stddef.h>", "<stdint.h>", "mozilla/Attributes.h"],
+ ["mozilla/dom/BindingNames.h", "mozilla/EnumTypeTraits.h"],
+ "BindingNames",
+ curr,
+ )
+
+ # Add include guards.
+ curr = CGIncludeGuard("BindingNames", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def RegisterBindings(config):
+
+ curr = CGNamespace.build(
+ ["mozilla", "dom"], CGGlobalNames(config.windowGlobalNames)
+ )
+ 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/BindingNames.h")
+ 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 RegisterShadowRealmBindings(config):
+
+ curr = CGRegisterShadowRealmBindings(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, isExposedInShadowRealms=True
+ )
+ ]
+
+ curr = CGHeaders(
+ [], [], [], [], [], defineIncludes, "RegisterShadowRealmBindings", curr
+ )
+
+ # Add include guards.
+ curr = CGIncludeGuard("RegisterShadowRealmBindings", curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def UnionTypes(config):
+ unionTypes = UnionsForFile(config, None)
+ (
+ includes,
+ implincludes,
+ declarations,
+ traverseMethods,
+ unlinkMethods,
+ unionStructs,
+ ) = UnionTypes(unionTypes, config)
+
+ unionStructs = dependencySortDictionariesAndUnionsAndCallbacks(unionStructs)
+
+ 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 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 "needsErrorResult" 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):
+ for m in self.membersNeedingTrace:
+ if not (
+ m.type.isAny() or m.type.isObject() or m.type.isSpiderMonkeyInterface()
+ ):
+ raise TypeError("Unknown traceable member type %s" % m.type)
+
+ if len(self.membersNeedingTrace) > 0:
+ dropJS = "mozilla::DropJSObjects(this);\n"
+ else:
+ dropJS = ""
+ # 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/dom/%sBinding.h" % interfaceName,
+ "mozilla/dom/BindingUtils.h",
+ ],
+ [
+ "%s.h" % interfaceName,
+ "js/GCAPI.h",
+ "mozilla/HoldDropJSObjects.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()