summaryrefslogtreecommitdiff
path: root/dom/bindings/Codegen.py
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/bindings/Codegen.py
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/bindings/Codegen.py')
-rw-r--r--dom/bindings/Codegen.py17364
1 files changed, 17364 insertions, 0 deletions
diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py
new file mode 100644
index 0000000000..3174c37ddc
--- /dev/null
+++ b/dom/bindings/Codegen.py
@@ -0,0 +1,17364 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Common codegen classes.
+
+import os
+import re
+import string
+import math
+import textwrap
+import functools
+
+from WebIDL import BuiltinTypes, IDLBuiltinType, IDLNullValue, IDLSequenceType, IDLType, IDLAttribute, IDLInterfaceMember, IDLUndefinedValue, IDLEmptySequenceValue, IDLDictionary
+from Configuration import NoSuchDescriptorError, getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback, getAllTypes, Descriptor, MemberIsUnforgeable, iteratorNativeType
+
+AUTOGENERATED_WARNING_COMMENT = \
+ "/* THIS FILE IS AUTOGENERATED BY Codegen.py - DO NOT EDIT */\n\n"
+AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT = \
+ "/* THIS FILE IS AUTOGENERATED FROM %s BY Codegen.py - DO NOT EDIT */\n\n"
+ADDPROPERTY_HOOK_NAME = '_addProperty'
+FINALIZE_HOOK_NAME = '_finalize'
+OBJECT_MOVED_HOOK_NAME = '_objectMoved'
+CONSTRUCT_HOOK_NAME = '_constructor'
+LEGACYCALLER_HOOK_NAME = '_legacycaller'
+HASINSTANCE_HOOK_NAME = '_hasInstance'
+RESOLVE_HOOK_NAME = '_resolve'
+MAY_RESOLVE_HOOK_NAME = '_mayResolve'
+ENUMERATE_HOOK_NAME = '_enumerate'
+ENUM_ENTRY_VARIABLE_NAME = 'strings'
+INSTANCE_RESERVED_SLOTS = 1
+
+
+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):
+ return str(not not arg).lower()
+
+
+def toBindingNamespace(arg):
+ return arg + "Binding"
+
+
+def isTypeCopyConstructible(type):
+ # Nullable and sequence stuff doesn't affect copy-constructibility
+ type = type.unroll()
+ return (type.isPrimitive() or type.isString() or type.isEnum() or
+ (type.isUnion() and
+ CGUnionStruct.isUnionCopyConstructible(type)) or
+ (type.isDictionary() and
+ CGDictionary.isDictionaryCopyConstructible(type.inner)) or
+ # Interface types are only copy-constructible if they're Gecko
+ # interfaces. SpiderMonkey interfaces are not copy-constructible
+ # because of rooting issues.
+ (type.isInterface() and type.isGeckoInterface()))
+
+
+def idlTypeNeedsCycleCollection(type):
+ type = type.unroll() # Takes care of sequences and nullables
+ if ((type.isPrimitive() and type.tag() in builtinNames) or
+ type.isEnum() or
+ type.isString() or
+ type.isAny() or
+ type.isObject() or
+ type.isSpiderMonkeyInterface()):
+ return False
+ elif type.isCallback() or type.isGeckoInterface():
+ return True
+ elif type.isUnion():
+ return any(idlTypeNeedsCycleCollection(t) for t in type.flatMemberTypes)
+ elif type.isMozMap():
+ if idlTypeNeedsCycleCollection(type.inner):
+ raise TypeError("Cycle collection for type %s is not supported" % type)
+ return False
+ elif type.isDictionary():
+ if any(idlTypeNeedsCycleCollection(m.type) for m in type.inner.members):
+ raise TypeError("Cycle collection for type %s is not supported" % type)
+ return False
+ else:
+ raise TypeError("Don't know whether to cycle-collect type %s" % type)
+
+
+def wantsAddProperty(desc):
+ return (desc.concrete and desc.wrapperCache and not desc.isGlobal())
+
+
+# We'll want to insert the indent at the beginnings of lines, but we
+# don't want to indent empty lines. So only indent lines that have a
+# non-newline character on them.
+lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE)
+
+
+def indent(s, indentLevel=2):
+ """
+ Indent C++ code.
+
+ Weird secret feature: this doesn't indent lines that start with # (such as
+ #include lines or #ifdef/#endif).
+ """
+ if s == "":
+ return s
+ return re.sub(lineStartDetector, indentLevel * " ", s)
+
+
+# dedent() and fill() are often called on the same string multiple
+# times. We want to memoize their return values so we don't keep
+# recomputing them all the time.
+def memoize(fn):
+ """
+ Decorator to memoize a function of one argument. The cache just
+ grows without bound.
+ """
+ cache = {}
+
+ @functools.wraps(fn)
+ def wrapper(arg):
+ retval = cache.get(arg)
+ if retval is None:
+ retval = cache[arg] = fn(arg)
+ return retval
+ return wrapper
+
+
+@memoize
+def dedent(s):
+ """
+ Remove all leading whitespace from s, and remove a blank line
+ at the beginning.
+ """
+ if s.startswith('\n'):
+ s = s[1:]
+ return textwrap.dedent(s)
+
+
+# This works by transforming the fill()-template to an equivalent
+# string.Template.
+fill_multiline_substitution_re = re.compile(r"( *)\$\*{(\w+)}(\n)?")
+
+
+find_substitutions = re.compile(r"\${")
+
+
+@memoize
+def compile_fill_template(template):
+ """
+ Helper function for fill(). Given the template string passed to fill(),
+ do the reusable part of template processing and return a pair (t,
+ argModList) that can be used every time fill() is called with that
+ template argument.
+
+ argsModList is list of tuples that represent modifications to be
+ made to args. Each modification has, in order: i) the arg name,
+ ii) the modified name, iii) the indent depth.
+ """
+ t = dedent(template)
+ assert t.endswith("\n") or "\n" not in t
+ argModList = []
+
+ def replace(match):
+ """
+ Replaces a line like ' $*{xyz}\n' with '${xyz_n}',
+ where n is the indent depth, and add a corresponding entry to
+ argModList.
+
+ Note that this needs to close over argModList, so it has to be
+ defined inside compile_fill_template().
+ """
+ indentation, name, nl = match.groups()
+ depth = len(indentation)
+
+ # Check that $*{xyz} appears by itself on a line.
+ prev = match.string[:match.start()]
+ if (prev and not prev.endswith("\n")) or nl is None:
+ raise ValueError("Invalid fill() template: $*{%s} must appear by itself on a line" % name)
+
+ # Now replace this whole line of template with the indented equivalent.
+ modified_name = name + "_" + str(depth)
+ argModList.append((name, modified_name, depth))
+ return "${" + modified_name + "}"
+
+ t = re.sub(fill_multiline_substitution_re, replace, t)
+ if not re.search(find_substitutions, t):
+ raise TypeError("Using fill() when dedent() would do.")
+ return (string.Template(t), argModList)
+
+
+def fill(template, **args):
+ """
+ Convenience function for filling in a multiline template.
+
+ `fill(template, name1=v1, name2=v2)` is a lot like
+ `string.Template(template).substitute({"name1": v1, "name2": v2})`.
+
+ However, it's shorter, and has a few nice features:
+
+ * If `template` is indented, fill() automatically dedents it!
+ This makes code using fill() with Python's multiline strings
+ much nicer to look at.
+
+ * If `template` starts with a blank line, fill() strips it off.
+ (Again, convenient with multiline strings.)
+
+ * fill() recognizes a special kind of substitution
+ of the form `$*{name}`.
+
+ Use this to paste in, and automatically indent, multiple lines.
+ (Mnemonic: The `*` is for "multiple lines").
+
+ A `$*` substitution must appear by itself on a line, with optional
+ preceding indentation (spaces only). The whole line is replaced by the
+ corresponding keyword argument, indented appropriately. If the
+ argument is an empty string, no output is generated, not even a blank
+ line.
+ """
+
+ t, argModList = compile_fill_template(template)
+ # Now apply argModList to args
+ for (name, modified_name, depth) in argModList:
+ if not (args[name] == "" or args[name].endswith("\n")):
+ raise ValueError("Argument %s with value %r is missing a newline" % (name, args[name]))
+ args[modified_name] = indent(args[name], depth)
+
+ return t.substitute(args)
+
+
+class CGThing():
+ """
+ Abstract base class for things that spit out code.
+ """
+ def __init__(self):
+ pass # Nothing for now
+
+ def declare(self):
+ """Produce code for a header file."""
+ assert False # Override me!
+
+ def define(self):
+ """Produce code for a cpp file."""
+ assert False # Override me!
+
+ def deps(self):
+ """Produce the deps for a pp file"""
+ assert False # Override me!
+
+
+class CGStringTable(CGThing):
+ """
+ Generate a string table for the given strings with a function accessor:
+
+ const char *accessorName(unsigned int index) {
+ static const char table[] = "...";
+ static const uint16_t indices = { ... };
+ return &table[indices[index]];
+ }
+
+ This is more efficient than the more natural:
+
+ const char *table[] = {
+ ...
+ };
+
+ The uint16_t indices are smaller than the pointer equivalents, and the
+ string table requires no runtime relocations.
+ """
+ def __init__(self, accessorName, strings):
+ CGThing.__init__(self)
+ self.accessorName = accessorName
+ self.strings = strings
+
+ def declare(self):
+ return "extern const char *%s(unsigned int aIndex);\n" % self.accessorName
+
+ def define(self):
+ table = ' "\\0" '.join('"%s"' % s for s in self.strings)
+ indices = []
+ currentIndex = 0
+ for s in self.strings:
+ indices.append(currentIndex)
+ currentIndex += len(s) + 1 # for the null terminator
+ return fill(
+ """
+ const char *${name}(unsigned int aIndex)
+ {
+ static const char table[] = ${table};
+ static const uint16_t indices[] = { ${indices} };
+ static_assert(${currentIndex} <= UINT16_MAX, "string table overflow!");
+ return &table[indices[aIndex]];
+ }
+ """,
+ name=self.accessorName,
+ table=table,
+ indices=", ".join("%d" % index for index in indices),
+ currentIndex=currentIndex)
+
+
+class CGNativePropertyHooks(CGThing):
+ """
+ Generate a NativePropertyHooks for a given descriptor
+ """
+ def __init__(self, descriptor, properties):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ self.properties = properties
+
+ def declare(self):
+ if not self.descriptor.wantsXrays:
+ return ""
+ return dedent("""
+ // We declare this as an array so that retrieving a pointer to this
+ // binding's property hooks only requires compile/link-time resolvable
+ // address arithmetic. Declaring it as a pointer instead would require
+ // doing a run-time load to fetch a pointer to this binding's property
+ // hooks. And then structures which embedded a pointer to this structure
+ // would require a run-time load for proper initialization, which would
+ // then induce static constructors. Lots of static constructors.
+ extern const NativePropertyHooks sNativePropertyHooks[];
+ """)
+
+ def define(self):
+ if not self.descriptor.wantsXrays:
+ return ""
+ deleteNamedProperty = "nullptr"
+ if self.descriptor.concrete and self.descriptor.proxy:
+ resolveOwnProperty = "ResolveOwnProperty"
+ enumerateOwnProperties = "EnumerateOwnProperties"
+ if self.descriptor.needsXrayNamedDeleterHook():
+ deleteNamedProperty = "DeleteNamedProperty"
+ elif self.descriptor.needsXrayResolveHooks():
+ resolveOwnProperty = "ResolveOwnPropertyViaResolve"
+ enumerateOwnProperties = "EnumerateOwnPropertiesViaGetOwnPropertyNames"
+ else:
+ resolveOwnProperty = "nullptr"
+ enumerateOwnProperties = "nullptr"
+ if self.properties.hasNonChromeOnly():
+ regular = "sNativeProperties.Upcast()"
+ else:
+ regular = "nullptr"
+ if self.properties.hasChromeOnly():
+ chrome = "sChromeOnlyNativeProperties.Upcast()"
+ else:
+ chrome = "nullptr"
+ constructorID = "constructors::id::"
+ if self.descriptor.interface.hasInterfaceObject():
+ constructorID += self.descriptor.name
+ else:
+ constructorID += "_ID_Count"
+ prototypeID = "prototypes::id::"
+ if self.descriptor.interface.hasInterfacePrototypeObject():
+ prototypeID += self.descriptor.name
+ else:
+ prototypeID += "_ID_Count"
+ parentProtoName = self.descriptor.parentPrototypeName
+ parentHooks = (toBindingNamespace(parentProtoName) + "::sNativePropertyHooks"
+ if parentProtoName else 'nullptr')
+
+ if self.descriptor.wantsXrayExpandoClass:
+ expandoClass = "&sXrayExpandoObjectClass"
+ else:
+ expandoClass = "&DefaultXrayExpandoObjectClass"
+
+ return fill(
+ """
+ const NativePropertyHooks sNativePropertyHooks[] = { {
+ ${resolveOwnProperty},
+ ${enumerateOwnProperties},
+ ${deleteNamedProperty},
+ { ${regular}, ${chrome} },
+ ${prototypeID},
+ ${constructorID},
+ ${parentHooks},
+ ${expandoClass}
+ } };
+ """,
+ resolveOwnProperty=resolveOwnProperty,
+ enumerateOwnProperties=enumerateOwnProperties,
+ deleteNamedProperty=deleteNamedProperty,
+ regular=regular,
+ chrome=chrome,
+ prototypeID=prototypeID,
+ constructorID=constructorID,
+ parentHooks=parentHooks,
+ expandoClass=expandoClass)
+
+
+def NativePropertyHooks(descriptor):
+ return "&sEmptyNativePropertyHooks" if not descriptor.wantsXrays else "sNativePropertyHooks"
+
+
+def DOMClass(descriptor):
+ protoList = ['prototypes::id::' + proto for proto in descriptor.prototypeNameChain]
+ # Pad out the list to the right length with _ID_Count so we
+ # guarantee that all the lists are the same length. _ID_Count
+ # is never the ID of any prototype, so it's safe to use as
+ # padding.
+ protoList.extend(['prototypes::id::_ID_Count'] * (descriptor.config.maxProtoChainLength - len(protoList)))
+
+ return fill(
+ """
+ { ${protoChain} },
+ IsBaseOf<nsISupports, ${nativeType} >::value,
+ ${hooks},
+ FindAssociatedGlobalForNative<${nativeType}>::Get,
+ GetProtoObjectHandle,
+ GetCCParticipant<${nativeType}>::Get()
+ """,
+ protoChain=', '.join(protoList),
+ nativeType=descriptor.nativeType,
+ hooks=NativePropertyHooks(descriptor))
+
+
+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 = INSTANCE_RESERVED_SLOTS + self.descriptor.interface.totalMembersInSlots
+ 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.isProbablyShortLivingObject():
+ classFlags += " | JSCLASS_SKIP_NURSERY_FINALIZE"
+ if self.descriptor.interface.getExtendedAttribute("NeedResolve"):
+ resolveHook = RESOLVE_HOOK_NAME
+ mayResolveHook = MAY_RESOLVE_HOOK_NAME
+ enumerateHook = ENUMERATE_HOOK_NAME
+ elif self.descriptor.isGlobal():
+ resolveHook = "mozilla::dom::ResolveGlobal"
+ mayResolveHook = "mozilla::dom::MayResolveGlobal"
+ enumerateHook = "mozilla::dom::EnumerateGlobal"
+ else:
+ resolveHook = "nullptr"
+ mayResolveHook = "nullptr"
+ enumerateHook = "nullptr"
+
+ return fill(
+ """
+ static const js::ClassOps sClassOps = {
+ ${addProperty}, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ ${enumerate}, /* enumerate */
+ ${resolve}, /* resolve */
+ ${mayResolve}, /* mayResolve */
+ ${finalize}, /* finalize */
+ ${call}, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ ${trace}, /* trace */
+ };
+
+ static const js::ClassExtension sClassExtension = {
+ nullptr, /* weakmapKeyDelegateOp */
+ ${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.identifier.name,
+ flags=classFlags,
+ addProperty=ADDPROPERTY_HOOK_NAME if wantsAddProperty(self.descriptor) else 'nullptr',
+ enumerate=enumerateHook,
+ 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):
+ flags = ["JSCLASS_IS_DOMJSCLASS"]
+ # 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")
+ objectMovedHook = OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else 'nullptr'
+ return fill(
+ """
+ static const js::ClassExtension sClassExtension = PROXY_MAKE_EXT(
+ ${objectMoved}
+ );
+
+ static const DOMJSClass sClass = {
+ PROXY_CLASS_WITH_EXT("${name}",
+ ${flags},
+ &sClassExtension),
+ $*{descriptor}
+ };
+ """,
+ name=self.descriptor.interface.identifier.name,
+ flags=" | ".join(flags),
+ objectMoved=objectMovedHook,
+ 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("ArrayClass"):
+ protoGetter = "JS::GetRealmArrayPrototype"
+ elif descriptor.interface.getExtendedAttribute("ExceptionClass"):
+ protoGetter = "JS::GetRealmErrorPrototype"
+ elif descriptor.interface.isIteratorInterface():
+ protoGetter = "JS::GetRealmIteratorPrototype"
+ else:
+ protoGetter = "JS::GetRealmObjectPrototype"
+ protoHandleGetter = None
+ else:
+ prefix = toBindingNamespace(parentProtoName)
+ protoGetter = prefix + "::GetProtoObject"
+ protoHandleGetter = prefix + "::GetProtoObjectHandle"
+
+ return (protoGetter, protoHandleGetter)
+
+
+class CGPrototypeJSClass(CGThing):
+ def __init__(self, descriptor, properties):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ self.properties = properties
+
+ def declare(self):
+ # We're purely for internal consumption
+ return ""
+
+ def define(self):
+ prototypeID, depth = PrototypeIDAndDepth(self.descriptor)
+ slotCount = "DOM_INTERFACE_PROTO_SLOTS_BASE"
+ # Globals handle unforgeables directly in Wrap() instead of
+ # via a holder.
+ if (self.descriptor.hasUnforgeableMembers and
+ not self.descriptor.isGlobal()):
+ slotCount += " + 1 /* slot for the JSObject holding the unforgeable properties */"
+ (protoGetter, _) = InterfacePrototypeObjectProtoGetter(self.descriptor)
+ type = "eGlobalInterfacePrototype" if self.descriptor.isGlobal() else "eInterfacePrototype"
+ return fill(
+ """
+ static const DOMIfaceAndProtoJSClass sPrototypeClass = {
+ {
+ "${name}Prototype",
+ JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
+ JS_NULL_CLASS_OPS,
+ JS_NULL_CLASS_SPEC,
+ JS_NULL_CLASS_EXT,
+ JS_NULL_OBJECT_OPS
+ },
+ ${type},
+ false,
+ ${prototypeID},
+ ${depth},
+ ${hooks},
+ "[object ${name}Prototype]",
+ ${protoGetter}
+ };
+ """,
+ name=self.descriptor.interface.identifier.name,
+ slotCount=slotCount,
+ type=type,
+ hooks=NativePropertyHooks(self.descriptor),
+ prototypeID=prototypeID,
+ depth=depth,
+ protoGetter=protoGetter)
+
+
+def NeedsGeneratedHasInstance(descriptor):
+ assert descriptor.interface.hasInterfaceObject()
+ return descriptor.hasXPConnectImpls or descriptor.interface.isConsequential()
+
+
+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 = "binding_detail::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 = (
+ not NeedsGeneratedHasInstance(self.descriptor) and
+ self.descriptor.interface.hasInterfacePrototypeObject())
+
+ prototypeID, depth = PrototypeIDAndDepth(self.descriptor)
+ slotCount = "DOM_INTERFACE_SLOTS_BASE"
+ if len(self.descriptor.interface.namedConstructors) > 0:
+ slotCount += (" + %i /* slots for the named constructors */" %
+ len(self.descriptor.interface.namedConstructors))
+ (protoGetter, _) = InterfaceObjectProtoGetter(self.descriptor,
+ forXrays=True)
+
+ if ctorname == "ThrowingConstructor":
+ ret = ""
+ classOpsPtr = "&sBoringInterfaceObjectClassClassOps"
+ elif ctorname == "nullptr":
+ ret = ""
+ classOpsPtr = "JS_NULL_CLASS_OPS"
+ else:
+ ret = fill(
+ """
+ static const js::ClassOps sInterfaceObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ nullptr, /* finalize */
+ ${ctorname}, /* call */
+ nullptr, /* hasInstance */
+ ${ctorname}, /* construct */
+ nullptr, /* trace */
+ };
+
+ """,
+ ctorname=ctorname)
+ classOpsPtr = "&sInterfaceObjectClassOps"
+
+ if self.descriptor.interface.isNamespace():
+ classString = self.descriptor.interface.getExtendedAttribute("ClassString")
+ if classString is None:
+ classString = "Object"
+ else:
+ classString = classString[0]
+ toStringResult = "[object %s]" % classString
+ objectOps = "JS_NULL_OBJECT_OPS"
+ else:
+ classString = "Function"
+ toStringResult = ("function %s() {\\n [native code]\\n}" %
+ self.descriptor.interface.identifier.name)
+ # We need non-default ObjectOps so we can actually make
+ # use of our toStringResult.
+ objectOps = "&sInterfaceObjectClassObjectOps"
+
+ ret = ret + fill(
+ """
+ static const DOMIfaceAndProtoJSClass sInterfaceObjectClass = {
+ {
+ "${classString}",
+ JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
+ ${classOpsPtr},
+ JS_NULL_CLASS_SPEC,
+ JS_NULL_CLASS_EXT,
+ ${objectOps}
+ },
+ eInterface,
+ ${needsHasInstance},
+ ${prototypeID},
+ ${depth},
+ ${hooks},
+ "${toStringResult}",
+ ${protoGetter}
+ };
+ """,
+ classString=classString,
+ slotCount=slotCount,
+ classOpsPtr=classOpsPtr,
+ hooks=NativePropertyHooks(self.descriptor),
+ objectOps=objectOps,
+ needsHasInstance=toStringBool(needsHasInstance),
+ prototypeID=prototypeID,
+ depth=depth,
+ toStringResult=toStringResult,
+ 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):
+ const = "const " if isConst else ""
+ pre = "%s%s<" % (const, templateName)
+ ref = "&" if isReference else ""
+ post = ">%s" % ref
+ CGWrapper.__init__(self, child, pre=pre, post=post)
+
+
+class CGNamespace(CGWrapper):
+ def __init__(self, namespace, child, declareOnly=False):
+ pre = "namespace %s {\n" % namespace
+ post = "} // namespace %s\n" % namespace
+ CGWrapper.__init__(self, child, pre=pre, post=post,
+ declareOnly=declareOnly)
+
+ @staticmethod
+ def build(namespaces, child, declareOnly=False):
+ """
+ Static helper method to build multiple wrapped namespaces.
+ """
+ if not namespaces:
+ return CGWrapper(child, declareOnly=declareOnly)
+ inner = CGNamespace.build(namespaces[1:], child, declareOnly=declareOnly)
+ return CGNamespace(namespaces[0], inner, declareOnly=declareOnly)
+
+
+class CGIncludeGuard(CGWrapper):
+ """
+ Generates include guards for a header.
+ """
+ def __init__(self, prefix, child):
+ """|prefix| is the filename without the extension."""
+ define = 'mozilla_dom_%s_h' % prefix
+ CGWrapper.__init__(self, child,
+ declarePre='#ifndef %s\n#define %s\n\n' % (define, define),
+ declarePost='\n#endif // %s\n' % define)
+
+
+class CGHeaders(CGWrapper):
+ """
+ Generates the appropriate include statements.
+ """
+ def __init__(self, descriptors, dictionaries, callbacks,
+ callbackDescriptors,
+ declareIncludes, defineIncludes, prefix, child,
+ config=None, jsImplementedDescriptors=[]):
+ """
+ Builds a set of includes to cover |descriptors|.
+
+ Also includes the files in |declareIncludes| in the header
+ file and the files in |defineIncludes| in the .cpp.
+
+ |prefix| contains the basename of the file that we generate include
+ statements for.
+ """
+
+ # Determine the filenames for which we need headers.
+ interfaceDeps = [d.interface for d in descriptors]
+ ancestors = []
+ for iface in interfaceDeps:
+ if iface.parent:
+ # We're going to need our parent's prototype, to use as the
+ # prototype of our prototype object.
+ ancestors.append(iface.parent)
+ # And if we have an interface object, we'll need the nearest
+ # ancestor with an interface object too, so we can use its
+ # interface object as the proto of our interface object.
+ if iface.hasInterfaceObject():
+ parent = iface.parent
+ while parent and not parent.hasInterfaceObject():
+ parent = parent.parent
+ if parent:
+ ancestors.append(parent)
+ interfaceDeps.extend(ancestors)
+ 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())
+
+ # Grab the includes for checking hasInstance
+ interfacesImplementingSelf = set()
+ for d in descriptors:
+ interfacesImplementingSelf |= d.interface.interfacesImplementingSelf
+ implementationIncludes |= set(self.getDeclarationFilename(i) for i in
+ interfacesImplementingSelf)
+
+ # Grab the includes for the things that involve XPCOM interfaces
+ hasInstanceIncludes = set("nsIDOM" + d.interface.identifier.name + ".h" for d
+ in descriptors if
+ d.interface.hasInterfaceObject() and
+ NeedsGeneratedHasInstance(d) and
+ d.interface.hasInterfacePrototypeObject())
+ if len(hasInstanceIncludes) > 0:
+ hasInstanceIncludes.add("nsContentUtils.h")
+
+ # 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((t, dictionary)):
+ """
+ Add the relevant headers for this type. We use dictionary, if
+ passed, to decide what to do with interface types.
+ """
+ # Dictionaries have members that need to be actually
+ # declared, not just forward-declared.
+ if dictionary:
+ headerSet = declareIncludes
+ else:
+ headerSet = bindingHeaders
+ if t.nullable():
+ # Need to make sure that Nullable as a dictionary
+ # member works.
+ headerSet.add("mozilla/dom/Nullable.h")
+ unrolled = t.unroll()
+ if unrolled.isUnion():
+ headerSet.add(self.getUnionDeclarationFilename(config, unrolled))
+ bindingHeaders.add("mozilla/dom/UnionConversions.h")
+ elif unrolled.isDate():
+ if dictionary or jsImplementedDescriptors:
+ declareIncludes.add("mozilla/dom/Date.h")
+ else:
+ bindingHeaders.add("mozilla/dom/Date.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))
+ 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.isMozMap():
+ if dictionary or jsImplementedDescriptors:
+ declareIncludes.add("mozilla/dom/MozMap.h")
+ else:
+ bindingHeaders.add("mozilla/dom/MozMap.h")
+ # Also add headers for the type the MozMap is
+ # parametrized over, if needed.
+ addHeadersForType((t.inner, dictionary))
+
+ map(addHeadersForType,
+ getAllTypes(descriptors + callbackDescriptors, dictionaries,
+ callbacks))
+
+ # Now make sure we're not trying to include the header from inside itself
+ declareIncludes.discard(prefix + ".h")
+
+ def addHeaderForFunc(func, desc):
+ if func is None:
+ return
+ # Include the right class header, which we can only do
+ # if this is a class member function.
+ if desc is not None and not desc.headerIsDefault:
+ # An explicit header file was provided, assume that we know
+ # what we're doing.
+ return
+
+ if "::" in func:
+ # Strip out the function name and convert "::" to "/"
+ bindingHeaders.add("/".join(func.split("::")[:-1]) + ".h")
+
+ # Now for non-callback descriptors make sure we include any
+ # headers needed by Func declarations and other things like that.
+ for desc in descriptors:
+ # If this is an iterator interface generated for a seperate
+ # iterable interface, skip generating type includes, as we have
+ # what we need in IterableIterator.h
+ if desc.interface.isExternal() or desc.interface.isIteratorInterface():
+ continue
+
+ for m in desc.interface.members:
+ addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), desc)
+ staticTypeOverride = PropertyDefiner.getStringAttr(m, "StaticClassOverride")
+ if staticTypeOverride:
+ bindingHeaders.add("/".join(staticTypeOverride.split("::")) + ".h")
+ # getExtendedAttribute() returns a list, extract the entry.
+ funcList = desc.interface.getExtendedAttribute("Func")
+ if funcList is not None:
+ addHeaderForFunc(funcList[0], desc)
+
+ if desc.interface.maplikeOrSetlikeOrIterable:
+ # We need ToJSValue.h for maplike/setlike type conversions
+ bindingHeaders.add("mozilla/dom/ToJSValue.h")
+ # Add headers for the key and value types of the
+ # maplike/setlike/iterable, since they'll be needed for
+ # convenience functions
+ if desc.interface.maplikeOrSetlikeOrIterable.hasKeyType():
+ addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.keyType,
+ None))
+ if desc.interface.maplikeOrSetlikeOrIterable.hasValueType():
+ addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.valueType,
+ None))
+
+ for d in dictionaries:
+ if d.parent:
+ declareIncludes.add(self.getDeclarationFilename(d.parent))
+ bindingHeaders.add(self.getDeclarationFilename(d))
+ for m in d.members:
+ addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"),
+ None)
+ # No need to worry about Func on members of ancestors, because that
+ # will happen automatically in whatever files those ancestors live
+ # in.
+
+ for c in callbacks:
+ bindingHeaders.add(self.getDeclarationFilename(c))
+
+ for c in callbackDescriptors:
+ bindingHeaders.add(self.getDeclarationFilename(c.interface))
+
+ if len(callbacks) != 0:
+ # We need CallbackFunction to serve as our parent class
+ declareIncludes.add("mozilla/dom/CallbackFunction.h")
+ # And we need ToJSValue.h so we can wrap "this" objects
+ declareIncludes.add("mozilla/dom/ToJSValue.h")
+
+ if len(callbackDescriptors) != 0 or len(jsImplementedDescriptors) != 0:
+ # We need CallbackInterface to serve as our parent class
+ declareIncludes.add("mozilla/dom/CallbackInterface.h")
+ # And we need ToJSValue.h so we can wrap "this" objects
+ declareIncludes.add("mozilla/dom/ToJSValue.h")
+
+ # Also need to include the headers for ancestors of
+ # JS-implemented interfaces.
+ for jsImplemented in jsImplementedDescriptors:
+ jsParent = jsImplemented.interface.parent
+ if jsParent:
+ parentDesc = jsImplemented.getDescriptor(jsParent.identifier.name)
+ declareIncludes.add(parentDesc.jsImplParentHeader)
+
+ # Let the machinery do its thing.
+ def _includeString(includes):
+ return ''.join(['#include "%s"\n' % i for i in includes]) + '\n'
+ CGWrapper.__init__(self, child,
+ declarePre=_includeString(sorted(declareIncludes)),
+ definePre=_includeString(sorted(set(defineIncludes) |
+ bindingIncludes |
+ bindingHeaders |
+ hasInstanceIncludes |
+ 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()
+ f = f.unroll()
+ if f.isInterface():
+ if f.isSpiderMonkeyInterface():
+ headers.add("jsfriendapi.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)
+ 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.isEnum():
+ # Need to see the actual definition of the enum,
+ # unfortunately.
+ headers.add(CGHeaders.getDeclarationFilename(f.inner))
+ 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.isMozMap():
+ headers.add("mozilla/dom/MozMap.h")
+ # And add headers for the type we're parametrized over
+ addHeadersForType(f.inner)
+
+ implheaders.add(CGHeaders.getUnionDeclarationFilename(config, t))
+ for f in t.flatMemberTypes:
+ assert not f.nullable()
+ addHeadersForType(f)
+
+ if idlTypeNeedsCycleCollection(t):
+ declarations.add(("mozilla::dom::%s" % CGUnionStruct.unionTypeName(t, True), False))
+ traverseMethods[name] = CGCycleCollectionTraverseForOwningUnionMethod(t)
+ unlinkMethods[name] = CGCycleCollectionUnlinkForOwningUnionMethod(t)
+
+ # The order of items in CGList is important.
+ # Since the union structs friend the unlinkMethods, the forward-declaration
+ # for these methods should come before the class declaration. Otherwise
+ # some compilers treat the friend declaration as a forward-declaration in
+ # the class scope.
+ return (headers, implheaders, declarations,
+ SortedDictValues(traverseMethods), SortedDictValues(unlinkMethods),
+ SortedDictValues(unionStructs))
+
+
+def UnionConversions(unionTypes, config):
+ """
+ The unionTypes argument should be a list of tuples, each containing two
+ elements: a union type and a descriptor. This is typically the list
+ generated by UnionsForFile.
+
+ Returns a tuple containing a list of headers and a CGThing to declare all
+ union argument conversion helper structs.
+ """
+ headers = set()
+ unionConversions = dict()
+
+ for t in unionTypes:
+ name = str(t)
+ if name not in unionConversions:
+ unionConversions[name] = CGUnionConversionStruct(t, config)
+
+ def addHeadersForType(f):
+ f = f.unroll()
+ if f.isInterface():
+ if f.isSpiderMonkeyInterface():
+ headers.add("jsfriendapi.h")
+ headers.add("mozilla/dom/TypedArray.h")
+ elif f.inner.isExternal():
+ try:
+ typeDesc = config.getDescriptor(f.inner.identifier.name)
+ except NoSuchDescriptorError:
+ return
+ headers.add(typeDesc.headerFile)
+ else:
+ headers.add(CGHeaders.getDeclarationFilename(f.inner))
+ elif f.isDictionary():
+ headers.add(CGHeaders.getDeclarationFilename(f.inner))
+ elif f.isPrimitive():
+ headers.add("mozilla/dom/PrimitiveConversions.h")
+ elif f.isMozMap():
+ headers.add("mozilla/dom/MozMap.h")
+ # And the internal type of the MozMap
+ addHeadersForType(f.inner)
+
+ # We plan to include UnionTypes.h no matter what, so it's
+ # OK if we throw it into the set here.
+ headers.add(CGHeaders.getUnionDeclarationFilename(config, t))
+
+ for f in t.flatMemberTypes:
+ addHeadersForType(f)
+
+ return (headers,
+ CGWrapper(CGList(SortedDictValues(unionConversions), "\n"),
+ post="\n\n"))
+
+
+class Argument():
+ """
+ A class for outputting the type and name of an argument
+ """
+ def __init__(self, argType, name, default=None):
+ self.argType = argType
+ self.name = name
+ self.default = default
+
+ def declare(self):
+ string = self.argType + ' ' + self.name
+ if self.default is not None:
+ string += " = " + self.default
+ return string
+
+ def define(self):
+ return self.argType + ' ' + self.name
+
+
+class CGAbstractMethod(CGThing):
+ """
+ An abstract class for generating code for a method. Subclasses
+ should override definition_body to create the actual code.
+
+ descriptor is the descriptor for the interface the method is associated with
+
+ name is the name of the method as a string
+
+ returnType is the IDLType of the return value
+
+ args is a list of Argument objects
+
+ inline should be True to generate an inline method, whose body is
+ part of the declaration.
+
+ alwaysInline should be True to generate an inline method annotated with
+ MOZ_ALWAYS_INLINE.
+
+ static should be True to generate a static method, which only has
+ a definition.
+
+ If templateArgs is not None it should be a list of strings containing
+ template arguments, and the function will be templatized using those
+ arguments.
+ """
+ def __init__(self, descriptor, name, returnType, args, inline=False, alwaysInline=False, static=False, templateArgs=None):
+ 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
+
+ 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.alwaysInline:
+ decorators.append('MOZ_ALWAYS_INLINE')
+ elif self.inline:
+ decorators.append('inline')
+ if self.static:
+ decorators.append('static')
+ decorators.append(self.returnType)
+ maybeNewline = " " if self.inline else "\n"
+ return ' '.join(decorators) + maybeNewline
+
+ def declare(self):
+ if self.inline:
+ return self._define(True)
+ return "%s%s%s(%s);\n" % (self._template(), self._decorators(), self.name, self._argstring(True))
+
+ def indent_body(self, body):
+ """
+ Indent the code returned by self.definition_body(). Most classes
+ simply indent everything two spaces. This is here for
+ CGRegisterProtos, which needs custom indentation.
+ """
+ return indent(body)
+
+ def _define(self, fromDeclare=False):
+ return (self.definition_prologue(fromDeclare) +
+ self.indent_body(self.definition_body()) +
+ self.definition_epilogue())
+
+ def define(self):
+ return "" if self.inline else self._define()
+
+ def definition_prologue(self, fromDeclare):
+ return "%s%s%s(%s)\n{\n" % (self._template(), self._decorators(),
+ self.name, self._argstring(fromDeclare))
+
+ def definition_epilogue(self):
+ return "}\n"
+
+ def definition_body(self):
+ assert False # Override me!
+
+
+class CGAbstractStaticMethod(CGAbstractMethod):
+ """
+ Abstract base class for codegen of implementation-only (no
+ declaration) static methods.
+ """
+ def __init__(self, descriptor, name, returnType, args):
+ CGAbstractMethod.__init__(self, descriptor, name, returnType, args,
+ inline=False, static=True)
+
+ def declare(self):
+ # We only have implementation
+ return ""
+
+
+class CGAbstractClassHook(CGAbstractStaticMethod):
+ """
+ Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw
+ 'this' unwrapping as it assumes that the unwrapped type is always known.
+ """
+ def __init__(self, descriptor, name, returnType, args):
+ CGAbstractStaticMethod.__init__(self, descriptor, name, returnType,
+ args)
+
+ def definition_body_prologue(self):
+ return ("%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(obj);\n" %
+ (self.descriptor.nativeType, self.descriptor.nativeType))
+
+ def definition_body(self):
+ return self.definition_body_prologue() + self.generate_code()
+
+ def generate_code(self):
+ assert False # Override me!
+
+
+class CGGetJSClassMethod(CGAbstractMethod):
+ def __init__(self, descriptor):
+ CGAbstractMethod.__init__(self, descriptor, 'GetJSClass', 'const JSClass*',
+ [])
+
+ def definition_body(self):
+ return "return sClass.ToJSClass();\n"
+
+
+class CGAddPropertyHook(CGAbstractClassHook):
+ """
+ A hook for addProperty, used to preserve our wrapper from GC.
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'obj'),
+ Argument('JS::Handle<jsid>', 'id'),
+ Argument('JS::Handle<JS::Value>', 'val')]
+ CGAbstractClassHook.__init__(self, descriptor, ADDPROPERTY_HOOK_NAME,
+ 'bool', args)
+
+ def generate_code(self):
+ assert self.descriptor.wrapperCache
+ 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;
+ """)
+
+
+def finalizeHook(descriptor, hookName, freeOp):
+ finalize = ""
+ if descriptor.wrapperCache:
+ finalize += "ClearWrapper(self, self);\n"
+ if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+ finalize += "self->mExpandoAndGeneration.expando = JS::UndefinedValue();\n"
+ if descriptor.isGlobal():
+ finalize += "mozilla::dom::FinalizeGlobal(CastToJSFreeOp(%s), obj);\n" % freeOp
+ 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::FreeOp*', 'fop'), Argument('JSObject*', 'obj')]
+ CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME,
+ 'void', args)
+
+ def generate_code(self):
+ return finalizeHook(self.descriptor, self.name, self.args[0].name).define()
+
+
+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('const JSObject*', 'old')]
+ CGAbstractClassHook.__init__(self, descriptor, OBJECT_MOVED_HOOK_NAME,
+ 'void', args)
+
+ def generate_code(self):
+ assert self.descriptor.wrapperCache
+ return CGIfWrapper(CGGeneric("UpdateWrapper(self, self, obj, old);\n"),
+ "self").define()
+
+
+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):
+ # [ChromeOnly] interfaces may only be constructed by chrome.
+ chromeOnlyCheck = ""
+ if isChromeOnly(self._ctor):
+ chromeOnlyCheck = dedent("""
+ if (!nsContentUtils::ThreadsafeIsCallerChrome()) {
+ return ThrowingConstructor(cx, argc, vp);
+ }
+
+ """)
+
+ # Additionally, we want to throw if a caller does a bareword invocation
+ # of a constructor without |new|. We don't enforce this for chrome in
+ # realease builds to avoid the addon compat fallout of making that
+ # change. See bug 916644.
+ #
+ # Figure out the name of our constructor for error reporting purposes.
+ # For unnamed webidl constructors, identifier.name is "constructor" but
+ # the name JS sees is the interface name; for named constructors
+ # identifier.name is the actual name.
+ name = self._ctor.identifier.name
+ if name != "constructor":
+ ctorName = name
+ else:
+ ctorName = self.descriptor.interface.identifier.name
+
+ preamble = fill(
+ """
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::Rooted<JSObject*> obj(cx, &args.callee());
+ $*{chromeOnlyCheck}
+ if (!args.isConstructing()) {
+ // XXXbz wish I could get the name from the callee instead of
+ // Adding more relocations
+ return ThrowConstructorWithoutNew(cx, "${ctorName}");
+ }
+ JS::Rooted<JSObject*> desiredProto(cx);
+ if (!GetDesiredProto(cx, args, &desiredProto)) {
+ return false;
+ }
+ """,
+ chromeOnlyCheck=chromeOnlyCheck,
+ ctorName=ctorName)
+
+ name = self._ctor.identifier.name
+ nativeName = MakeNativeName(self.descriptor.binaryNameFor(name))
+ callGenerator = CGMethodCall(nativeName, True, self.descriptor,
+ self._ctor, isConstructor=True,
+ constructorName=ctorName)
+ return preamble + "\n" + callGenerator.define()
+
+
+# Encapsulate the constructor in a helper method to share genConstructorBody with CGJSImplMethod.
+class CGConstructNavigatorObject(CGAbstractMethod):
+ """
+ Construct a new JS-implemented WebIDL DOM object, for use on navigator.
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'obj'),
+ Argument('ErrorResult&', 'aRv')]
+ rtype = 'already_AddRefed<%s>' % descriptor.name
+ CGAbstractMethod.__init__(self, descriptor, "ConstructNavigatorObject",
+ rtype, args)
+
+ def definition_body(self):
+ if not self.descriptor.interface.isJSImplemented():
+ raise TypeError("Only JS-implemented classes are currently supported "
+ "on navigator. See bug 856820.")
+
+ return dedent(
+ """
+ GlobalObject global(cx, obj);
+ if (global.Failed()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ """) + genConstructorBody(self.descriptor)
+
+
+def NamedConstructorName(m):
+ return '_' + m.identifier.name
+
+
+class CGNamedConstructors(CGThing):
+ def __init__(self, descriptor):
+ self.descriptor = descriptor
+ CGThing.__init__(self)
+
+ def declare(self):
+ return ""
+
+ def define(self):
+ if len(self.descriptor.interface.namedConstructors) == 0:
+ return ""
+
+ constructorID = "constructors::id::"
+ if self.descriptor.interface.hasInterfaceObject():
+ constructorID += self.descriptor.name
+ else:
+ constructorID += "_ID_Count"
+
+ namedConstructors = ""
+ for n in self.descriptor.interface.namedConstructors:
+ namedConstructors += (
+ "{ \"%s\", { %s, &sNamedConstructorNativePropertyHooks }, %i },\n" %
+ (n.identifier.name, NamedConstructorName(n), methodLength(n)))
+
+ return fill(
+ """
+ const NativePropertyHooks sNamedConstructorNativePropertyHooks = {
+ nullptr,
+ nullptr,
+ nullptr,
+ { nullptr, nullptr },
+ prototypes::id::${name},
+ ${constructorID},
+ nullptr
+ };
+
+ static const NamedConstructor namedConstructors[] = {
+ $*{namedConstructors}
+ { nullptr, { nullptr, nullptr }, 0 }
+ };
+ """,
+ name=self.descriptor.name,
+ constructorID=constructorID,
+ namedConstructors=namedConstructors)
+
+
+class CGHasInstanceHook(CGAbstractStaticMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('unsigned', 'argc'),
+ Argument('JS::Value*', 'vp')]
+ assert descriptor.interface.hasInterfaceObject()
+ assert NeedsGeneratedHasInstance(descriptor)
+ CGAbstractStaticMethod.__init__(self, descriptor, HASINSTANCE_HOOK_NAME,
+ 'bool', args)
+
+ def define(self):
+ return CGAbstractStaticMethod.define(self)
+
+ def definition_body(self):
+ return self.generate_code()
+
+ def generate_code(self):
+ header = dedent("""
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ if (!args.get(0).isObject()) {
+ args.rval().setBoolean(false);
+ return true;
+ }
+
+ JS::Rooted<JSObject*> instance(cx, &args[0].toObject());
+ """)
+ if self.descriptor.interface.hasInterfacePrototypeObject():
+ return (
+ header +
+ fill(
+ """
+
+ static_assert(IsBaseOf<nsISupports, ${nativeType}>::value,
+ "HasInstance only works for nsISupports-based classes.");
+
+ bool ok = InterfaceHasInstance(cx, argc, vp);
+ if (!ok || args.rval().toBoolean()) {
+ return ok;
+ }
+
+ // FIXME Limit this to chrome by checking xpc::AccessCheck::isChrome(obj).
+ nsCOMPtr<nsISupports> native =
+ xpc::UnwrapReflectorToISupports(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false));
+ nsCOMPtr<nsIDOM${name}> qiResult = do_QueryInterface(native);
+ args.rval().setBoolean(!!qiResult);
+ return true;
+ """,
+ nativeType=self.descriptor.nativeType,
+ name=self.descriptor.interface.identifier.name))
+
+ hasInstanceCode = dedent("""
+
+ const DOMJSClass* domClass = GetDOMClass(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false));
+ if (!domClass) {
+ // Not a DOM object, so certainly not an instance of this interface
+ args.rval().setBoolean(false);
+ return true;
+ }
+ """)
+ if self.descriptor.interface.identifier.name == "ChromeWindow":
+ setRval = "args.rval().setBoolean(UnwrapDOMObject<nsGlobalWindow>(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false))->IsChromeWindow())"
+ else:
+ setRval = "args.rval().setBoolean(true)"
+ # Sort interaces implementing self by name so we get stable output.
+ for iface in sorted(self.descriptor.interface.interfacesImplementingSelf,
+ key=lambda iface: iface.identifier.name):
+ hasInstanceCode += fill(
+ """
+
+ if (domClass->mInterfaceChain[PrototypeTraits<prototypes::id::${name}>::Depth] == prototypes::id::${name}) {
+ ${setRval};
+ return true;
+ }
+ """,
+ name=iface.identifier.name,
+ setRval=setRval)
+ hasInstanceCode += ("args.rval().setBoolean(false);\n"
+ "return true;\n")
+ return header + hasInstanceCode
+
+
+def isChromeOnly(m):
+ return m.getExtendedAttribute("ChromeOnly")
+
+
+class MemberCondition:
+ """
+ An object representing the condition for a member to actually be
+ exposed. Any of the arguments can be None. If not
+ None, they should have the following types:
+
+ pref: The name of the preference.
+ func: The name of the function.
+ secureContext: A bool indicating whether a secure context is required.
+ nonExposedGlobals: A set of names of globals. Can be empty, in which case
+ it's treated the same way as None.
+ """
+ def __init__(self, pref=None, func=None, secureContext=False,
+ nonExposedGlobals=None):
+ assert pref is None or isinstance(pref, str)
+ assert func is None or isinstance(func, str)
+ assert isinstance(secureContext, bool)
+ assert nonExposedGlobals is None or isinstance(nonExposedGlobals, set)
+ self.pref = pref
+ self.secureContext = secureContext
+
+ def toFuncPtr(val):
+ if val is None:
+ return "nullptr"
+ return "&" + val
+ self.func = toFuncPtr(func)
+
+ if nonExposedGlobals:
+ # Nonempty set
+ self.nonExposedGlobals = " | ".join(
+ map(lambda g: "GlobalNames::%s" % g,
+ sorted(nonExposedGlobals)))
+ else:
+ self.nonExposedGlobals = "0"
+
+ def __eq__(self, other):
+ return (self.pref == other.pref and self.func == other.func and
+ self.secureContext == other.secureContext and
+ self.nonExposedGlobals == other.nonExposedGlobals)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def hasDisablers(self):
+ return (self.pref is not None or
+ self.secureContext or
+ self.func != "nullptr" or
+ self.nonExposedGlobals != "0")
+
+
+class PropertyDefiner:
+ """
+ A common superclass for defining things on prototype objects.
+
+ Subclasses should implement generateArray to generate the actual arrays of
+ things we're defining. They should also set self.chrome to the list of
+ things only exposed to chrome and self.regular to the list of things exposed
+ to both chrome and web pages.
+ """
+ def __init__(self, descriptor, name):
+ self.descriptor = descriptor
+ self.name = name
+ # self.prefCacheData will store an array of (prefname, bool*)
+ # pairs for our bool var caches. generateArray will fill it
+ # in as needed.
+ self.prefCacheData = []
+
+ 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 __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),
+ self.usedForXrays())
+ if self.hasChromeOnly():
+ str += self.generateArray(self.chrome, self.variableName(True),
+ self.usedForXrays())
+ return str
+
+ @staticmethod
+ def getStringAttr(member, name):
+ attr = member.getExtendedAttribute(name)
+ if attr is None:
+ return None
+ # It's a list of strings
+ assert len(attr) == 1
+ assert attr[0] is not None
+ return attr[0]
+
+ @staticmethod
+ def getControllingCondition(interfaceMember, descriptor):
+ interface = descriptor.interface
+ nonExposureSet = interface.exposureSet - interfaceMember.exposureSet
+
+ return MemberCondition(
+ PropertyDefiner.getStringAttr(interfaceMember,
+ "Pref"),
+ PropertyDefiner.getStringAttr(interfaceMember,
+ "Func"),
+ interfaceMember.getExtendedAttribute("SecureContext") is not None,
+ nonExposureSet)
+
+ def generatePrefableArray(self, array, name, specFormatter, specTerminator,
+ specType, getCondition, getDataTuple, doIdArrays):
+ """
+ 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
+ # So we won't put a specTerminator at the very front of the list:
+ lastCondition = getCondition(array[0], self.descriptor)
+
+ specs = []
+ disablers = []
+ prefableSpecs = []
+
+ disablersTemplate = dedent(
+ """
+ static PrefableDisablers %s_disablers%d = {
+ true, %s, %s, %s
+ };
+ """)
+ prefableWithDisablersTemplate = ' { &%s_disablers%d, &%s_specs[%d] }'
+ prefableWithoutDisablersTemplate = ' { nullptr, &%s_specs[%d] }'
+ prefCacheTemplate = '&%s[%d].disablers->enabled'
+
+ def switchToCondition(props, condition):
+ # Remember the info about where our pref-controlled
+ # booleans live.
+ if condition.pref is not None:
+ props.prefCacheData.append(
+ (condition.pref,
+ prefCacheTemplate % (name, len(prefableSpecs))))
+ # 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),
+ toStringBool(condition.secureContext),
+ condition.nonExposedGlobals,
+ condition.func))
+ else:
+ prefableSpecs.append(prefableWithoutDisablersTemplate %
+ (name, len(specs)))
+
+ switchToCondition(self, lastCondition)
+
+ for member in array:
+ curCondition = getCondition(member, self.descriptor)
+ if lastCondition != curCondition:
+ # Terminate previous list
+ specs.append(specTerminator)
+ # And switch to our new condition
+ switchToCondition(self, curCondition)
+ lastCondition = curCondition
+ # And the actual spec
+ specs.append(specFormatter(getDataTuple(member)))
+ specs.append(specTerminator)
+ prefableSpecs.append(" { nullptr, nullptr }")
+
+ specType = "const " + specType
+ arrays = fill(
+ """
+ static ${specType} ${name}_specs[] = {
+ ${specs}
+ };
+
+ ${disablers}
+ // Can't be const because the pref-enabled boolean needs to be writable
+ static Prefable<${specType}> ${name}[] = {
+ ${prefableSpecs}
+ };
+
+ """,
+ specType=specType,
+ name=name,
+ disablers='\n'.join(disablers),
+ specs=',\n'.join(specs),
+ prefableSpecs=',\n'.join(prefableSpecs))
+ if doIdArrays:
+ arrays += "static jsid %s_ids[%i];\n\n" % (name, len(specs))
+ 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 MakeJSImplClearCachedValueNativeName(member):
+ return "_" + MakeClearCachedValueNativeName(member)
+
+
+def IDLToCIdentifier(name):
+ return name.replace("-", "_")
+
+
+class MethodDefiner(PropertyDefiner):
+ """
+ A class for defining methods on a prototype object.
+ """
+ def __init__(self, descriptor, name, static, unforgeable=False):
+ assert not (static and unforgeable)
+ PropertyDefiner.__init__(self, descriptor, name)
+
+ # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822
+ # We should be able to check for special operations without an
+ # identifier. For now we check if the name starts with __
+
+ # Ignore non-static methods for interfaces without a proto object
+ if descriptor.interface.hasInterfacePrototypeObject() or static:
+ methods = [m for m in descriptor.interface.members if
+ m.isMethod() and m.isStatic() == static and
+ MemberIsUnforgeable(m, descriptor) == unforgeable and
+ not m.isIdentifierLess()]
+ else:
+ methods = []
+ self.chrome = []
+ self.regular = []
+ for m in methods:
+ if m.identifier.name == 'queryInterface':
+ if m.isStatic():
+ raise TypeError("Legacy queryInterface member shouldn't be static")
+ signatures = m.signatures()
+
+ def argTypeIsIID(arg):
+ return arg.type.inner.isExternal() and arg.type.inner.identifier.name == 'IID'
+ if len(signatures) > 1 or len(signatures[0][1]) > 1 or not argTypeIsIID(signatures[0][1][0]):
+ raise TypeError("There should be only one queryInterface method with 1 argument of type IID")
+
+ # Make sure to not stick QueryInterface on abstract interfaces that
+ # have hasXPConnectImpls (like EventTarget). So only put it on
+ # interfaces that are concrete and all of whose ancestors are abstract.
+ def allAncestorsAbstract(iface):
+ if not iface.parent:
+ return True
+ desc = self.descriptor.getDescriptor(iface.parent.identifier.name)
+ if desc.concrete:
+ return False
+ return allAncestorsAbstract(iface.parent)
+ if (not self.descriptor.interface.hasInterfacePrototypeObject() or
+ not self.descriptor.concrete or
+ not allAncestorsAbstract(self.descriptor.interface)):
+ raise TypeError("QueryInterface is only supported on "
+ "interfaces that are concrete and all "
+ "of whose ancestors are abstract: " +
+ self.descriptor.name)
+ condition = "WantsQueryInterface<%s>::Enabled" % descriptor.nativeType
+ self.regular.append({
+ "name": 'QueryInterface',
+ "methodInfo": False,
+ "length": 1,
+ "flags": "0",
+ "condition": MemberCondition(func=condition)
+ })
+ continue
+
+ # Iterable methods should be enumerable, maplike/setlike methods
+ # should not.
+ isMaplikeOrSetlikeMethod = (m.isMaplikeOrSetlikeOrIterableMethod() and
+ (m.maplikeOrSetlikeOrIterable.isMaplike() or
+ m.maplikeOrSetlikeOrIterable.isSetlike()))
+ method = {
+ "name": m.identifier.name,
+ "methodInfo": not m.isStatic(),
+ "length": methodLength(m),
+ # Methods generated for a maplike/setlike declaration are not
+ # enumerable.
+ "flags": "JSPROP_ENUMERATE" if not isMaplikeOrSetlikeMethod else "0",
+ "condition": PropertyDefiner.getControllingCondition(m, descriptor),
+ "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
+ "returnsPromise": m.returnsPromise(),
+ "hasIteratorAlias": "@@iterator" in m.aliases
+ }
+
+ 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": "JSPROP_ENUMERATE",
+ "condition": MemberCondition()
+ })
+
+ if (static and
+ not unforgeable and
+ descriptor.interface.hasInterfaceObject() and
+ NeedsGeneratedHasInstance(descriptor)):
+ self.regular.append({
+ "name": "@@hasInstance",
+ "methodInfo": False,
+ "nativeName": HASINSTANCE_HOOK_NAME,
+ "length": 1,
+ # Flags match those of Function[Symbol.hasInstance]
+ "flags": "JSPROP_READONLY | JSPROP_PERMANENT",
+ "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(m,
+ descriptor)
+ })
+ self.regular.append({
+ "name": "values",
+ "methodInfo": False,
+ "selfHostedName": "ArrayValues",
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE",
+ "condition": PropertyDefiner.getControllingCondition(m,
+ descriptor)
+ })
+ self.regular.append({
+ "name": "entries",
+ "methodInfo": False,
+ "selfHostedName": "ArrayEntries",
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE",
+ "condition": PropertyDefiner.getControllingCondition(m,
+ descriptor)
+ })
+ self.regular.append({
+ "name": "forEach",
+ "methodInfo": False,
+ "selfHostedName": "ArrayForEach",
+ "length": 1,
+ "flags": "JSPROP_ENUMERATE",
+ "condition": PropertyDefiner.getControllingCondition(m,
+ descriptor)
+ })
+
+ if not static:
+ stringifier = descriptor.operations['Stringifier']
+ if (stringifier and
+ unforgeable == MemberIsUnforgeable(stringifier, descriptor)):
+ toStringDesc = {
+ "name": "toString",
+ "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)
+ jsonifier = descriptor.operations['Jsonifier']
+ if (jsonifier and
+ unforgeable == MemberIsUnforgeable(jsonifier, descriptor)):
+ toJSONDesc = {
+ "name": "toJSON",
+ "nativeName": jsonifier.identifier.name,
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE",
+ "condition": PropertyDefiner.getControllingCondition(jsonifier, descriptor)
+ }
+ if isChromeOnly(jsonifier):
+ self.chrome.append(toJSONDesc)
+ else:
+ self.regular.append(toJSONDesc)
+ if (unforgeable and
+ descriptor.interface.getExtendedAttribute("Unforgeable")):
+ # Synthesize our valueOf method
+ self.regular.append({
+ "name": 'valueOf',
+ "nativeName": "UnforgeableValueOf",
+ "methodInfo": False,
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE", # 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()
+ })
+ else:
+ for m in clearableCachedAttrs(descriptor):
+ attrName = MakeNativeName(m.identifier.name)
+ self.chrome.append({
+ "name": "_clearCached%sValue" % attrName,
+ "nativeName": MakeJSImplClearCachedValueNativeName(m),
+ "methodInfo": False,
+ "length": "0",
+ "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()
+
+ def generateArray(self, array, name, doIdArrays):
+ if len(array) == 0:
+ return ""
+
+ def condition(m, d):
+ return m["condition"]
+
+ def flags(m):
+ unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if self.unforgeable else ""
+ return m["flags"] + unforgeable
+
+ def specData(m):
+ 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):
+ # Cast this in case the methodInfo is a
+ # JSTypedMethodJitInfo.
+ jitinfo = ("reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor)
+ if m.get("allowCrossOriginThis", False):
+ if m.get("returnsPromise", False):
+ raise TypeError("%s returns a Promise but should "
+ "be allowed cross-origin?" %
+ accessor)
+ accessor = "genericCrossOriginMethod"
+ elif self.descriptor.needsSpecialGenericOps():
+ if m.get("returnsPromise", False):
+ accessor = "genericPromiseReturningMethod"
+ else:
+ accessor = "genericMethod"
+ elif m.get("returnsPromise", False):
+ accessor = "GenericPromiseReturningBindingMethod"
+ else:
+ accessor = "GenericBindingMethod"
+ else:
+ if m.get("returnsPromise", False):
+ jitinfo = "&%s_methodinfo" % accessor
+ accessor = "StaticMethodPromiseWrapper"
+ else:
+ jitinfo = "nullptr"
+
+ return (m["name"], accessor, jitinfo, m["length"], flags(m), selfHostedName)
+
+ 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
+
+ return self.generatePrefableArray(
+ array, name,
+ formatSpec,
+ ' JS_FS_END',
+ 'JSFunctionSpec',
+ condition, specData, doIdArrays)
+
+
+def IsCrossOriginWritable(attr, descriptor):
+ """
+ Return whether the IDLAttribute in question is cross-origin writable on the
+ interface represented by descriptor. This is needed to handle the fact that
+ some, but not all, interfaces implementing URLUtils want a cross-origin
+ writable .href.
+ """
+ crossOriginWritable = attr.getExtendedAttribute("CrossOriginWritable")
+ if not crossOriginWritable:
+ return False
+ if crossOriginWritable is True:
+ return True
+ assert (isinstance(crossOriginWritable, list) and
+ len(crossOriginWritable) == 1)
+ return crossOriginWritable[0] == descriptor.interface.identifier.name
+
+def isNonExposedNavigatorObjectGetter(attr, descriptor):
+ return (attr.navigatorObjectGetter and
+ not descriptor.getDescriptor(attr.type.inner.identifier.name).register)
+
+class AttrDefiner(PropertyDefiner):
+ def __init__(self, descriptor, name, 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:
+ attributes = [m for m in descriptor.interface.members if
+ m.isAttr() and m.isStatic() == static and
+ MemberIsUnforgeable(m, descriptor) == unforgeable and
+ not isNonExposedNavigatorObjectGetter(m, descriptor)]
+ else:
+ attributes = []
+ self.chrome = [m for m in attributes if isChromeOnly(m)]
+ self.regular = [m for m in attributes if not isChromeOnly(m)]
+ self.static = static
+ self.unforgeable = unforgeable
+
+ 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()
+
+ def generateArray(self, array, name, doIdArrays):
+ if len(array) == 0:
+ return ""
+
+ def flags(attr):
+ unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else ""
+ # Attributes generated as part of a maplike/setlike declaration are
+ # not enumerable.
+ enumerable = " | JSPROP_ENUMERATE" if not attr.isMaplikeOrSetlikeAttr() else ""
+ return ("JSPROP_SHARED" + enumerable + unforgeable)
+
+ def getter(attr):
+ if self.static:
+ accessor = 'get_' + IDLToCIdentifier(attr.identifier.name)
+ jitinfo = "nullptr"
+ else:
+ if attr.hasLenientThis():
+ accessor = "genericLenientGetter"
+ elif attr.getExtendedAttribute("CrossOriginReadable"):
+ accessor = "genericCrossOriginGetter"
+ elif self.descriptor.needsSpecialGenericOps():
+ accessor = "genericGetter"
+ else:
+ accessor = "GenericBindingGetter"
+ jitinfo = ("&%s_getterinfo" %
+ IDLToCIdentifier(attr.identifier.name))
+ return "{ { %s, %s } }" % \
+ (accessor, jitinfo)
+
+ def setter(attr):
+ if (attr.readonly and
+ attr.getExtendedAttribute("PutForwards") is None and
+ attr.getExtendedAttribute("Replaceable") is None and
+ attr.getExtendedAttribute("LenientSetter") is None):
+ return "JSNATIVE_WRAPPER(nullptr)"
+ if self.static:
+ accessor = 'set_' + IDLToCIdentifier(attr.identifier.name)
+ jitinfo = "nullptr"
+ else:
+ if attr.hasLenientThis():
+ accessor = "genericLenientSetter"
+ elif IsCrossOriginWritable(attr, self.descriptor):
+ accessor = "genericCrossOriginSetter"
+ elif self.descriptor.needsSpecialGenericOps():
+ accessor = "genericSetter"
+ else:
+ accessor = "GenericBindingSetter"
+ jitinfo = "&%s_setterinfo" % IDLToCIdentifier(attr.identifier.name)
+ return "{ { %s, %s } }" % \
+ (accessor, jitinfo)
+
+ def specData(attr):
+ return (attr.identifier.name, flags(attr), getter(attr),
+ setter(attr))
+
+ return self.generatePrefableArray(
+ array, name,
+ lambda fields: ' { "%s", %s, { { %s, %s } } }' % fields,
+ ' JS_PS_END',
+ 'JSPropertySpec',
+ PropertyDefiner.getControllingCondition, specData, doIdArrays)
+
+
+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, doIdArrays):
+ if len(array) == 0:
+ return ""
+
+ def specData(const):
+ return (const.identifier.name,
+ convertConstIDLValueToJSVal(const.value))
+
+ return self.generatePrefableArray(
+ array, name,
+ lambda fields: ' { "%s", %s }' % fields,
+ ' { 0, JS::UndefinedValue() }',
+ 'ConstantSpec',
+ PropertyDefiner.getControllingCondition, specData, doIdArrays)
+
+
+class PropertyArrays():
+ def __init__(self, descriptor):
+ self.staticMethods = MethodDefiner(descriptor, "StaticMethods",
+ static=True)
+ self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes",
+ static=True)
+ self.methods = MethodDefiner(descriptor, "Methods", static=False)
+ self.attrs = AttrDefiner(descriptor, "Attributes", static=False)
+ self.unforgeableMethods = MethodDefiner(descriptor, "UnforgeableMethods",
+ static=False, unforgeable=True)
+ self.unforgeableAttrs = AttrDefiner(descriptor, "UnforgeableAttributes",
+ 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 CGNativeProperties(CGList):
+ def __init__(self, descriptor, properties):
+ def generateNativeProperties(name, chrome):
+ def check(p):
+ return p.hasChromeOnly() if chrome else p.hasNonChromeOnly()
+
+ nativePropsInts = []
+ nativePropsTrios = []
+
+ iteratorAliasIndex = -1
+ for index, item in enumerate(properties.methods.regular):
+ if item.get("hasIteratorAlias"):
+ iteratorAliasIndex = index
+ break
+ nativePropsInts.append(CGGeneric(str(iteratorAliasIndex)))
+
+ offset = 0
+ for array in properties.arrayNames():
+ propertyArray = getattr(properties, array)
+ if check(propertyArray):
+ varName = propertyArray.variableName(chrome)
+ bitfields = "true, %d /* %s */" % (offset, varName)
+ offset += 1
+ nativePropsInts.append(CGGeneric(bitfields))
+
+ if propertyArray.usedForXrays():
+ ids = "%(name)s_ids"
+ else:
+ ids = "nullptr"
+ trio = "{ %(name)s, " + ids + ", %(name)s_specs }"
+ trio = trio % {'name': varName}
+ nativePropsTrios.append(CGGeneric(trio))
+ else:
+ bitfields = "false, 0"
+ nativePropsInts.append(CGGeneric(bitfields))
+
+ nativePropsTrios = \
+ [CGWrapper(CGIndenter(CGList(nativePropsTrios, ",\n")),
+ pre='{\n', post='\n}')]
+ nativeProps = nativePropsInts + nativePropsTrios
+ pre = ("static const NativePropertiesN<%d> %s = {\n" %
+ (offset, name))
+ return CGWrapper(CGIndenter(CGList(nativeProps, ",\n")),
+ pre=pre, post="\n};\n")
+
+ 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 CGJsonifyAttributesMethod(CGAbstractMethod):
+ """
+ Generate the JsonifyAttributes method for an interface descriptor
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'aCx'),
+ Argument('JS::Handle<JSObject*>', 'obj'),
+ Argument('%s*' % descriptor.nativeType, 'self'),
+ Argument('JS::Rooted<JSObject*>&', 'aResult')]
+ CGAbstractMethod.__init__(self, descriptor, 'JsonifyAttributes', 'bool', args)
+
+ def definition_body(self):
+ ret = ''
+ interface = self.descriptor.interface
+ for m in interface.members:
+ if m.isAttr() and not m.isStatic() and m.type.isSerializable():
+ ret += fill(
+ """
+ { // scope for "temp"
+ JS::Rooted<JS::Value> temp(aCx);
+ if (!get_${name}(aCx, obj, self, JSJitGetterCallArgs(&temp))) {
+ return false;
+ }
+ if (!JS_DefineProperty(aCx, aResult, "${name}", temp, JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+ """,
+ name=IDLToCIdentifier(m.identifier.name))
+ ret += 'return true;\n'
+ 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):
+ args = [Argument('JSContext*', 'aCx'),
+ Argument('JS::Handle<JSObject*>', 'aGlobal'),
+ Argument('ProtoAndIfaceCache&', 'aProtoAndIfaceCache'),
+ Argument('bool', 'aDefineOnGlobal')]
+ CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args)
+ self.properties = properties
+ self.haveUnscopables = haveUnscopables
+
+ def definition_body(self):
+ (protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter(self.descriptor)
+ if protoHandleGetter is None:
+ parentProtoType = "Rooted"
+ getParentProto = "aCx, " + protoGetter
+ else:
+ parentProtoType = "Handle"
+ getParentProto = protoHandleGetter
+ getParentProto = getParentProto + "(aCx)"
+
+ (protoGetter, protoHandleGetter) = InterfaceObjectProtoGetter(self.descriptor)
+ if protoHandleGetter is None:
+ getConstructorProto = "aCx, " + protoGetter
+ constructorProtoType = "Rooted"
+ else:
+ getConstructorProto = protoHandleGetter
+ constructorProtoType = "Handle"
+ getConstructorProto += "(aCx)"
+
+ needInterfaceObject = self.descriptor.interface.hasInterfaceObject()
+ needInterfacePrototypeObject = self.descriptor.interface.hasInterfacePrototypeObject()
+
+ # if we don't need to create anything, why are we generating this?
+ assert needInterfaceObject or needInterfacePrototypeObject
+
+ getParentProto = fill(
+ """
+ JS::${type}<JSObject*> parentProto(${getParentProto});
+ if (!parentProto) {
+ return;
+ }
+ """,
+ type=parentProtoType,
+ getParentProto=getParentProto)
+
+ getConstructorProto = fill(
+ """
+ JS::${type}<JSObject*> constructorProto(${getConstructorProto});
+ if (!constructorProto) {
+ return;
+ }
+ """,
+ type=constructorProtoType,
+ getConstructorProto=getConstructorProto)
+
+ idsToInit = []
+ # There is no need to init any IDs in bindings that don't want Xrays.
+ if self.descriptor.wantsXrays:
+ for var in self.properties.arrayNames():
+ props = getattr(self.properties, var)
+ # We only have non-chrome ids to init if we have no chrome ids.
+ if props.hasChromeOnly():
+ idsToInit.append(props.variableName(True))
+ if props.hasNonChromeOnly():
+ idsToInit.append(props.variableName(False))
+ if len(idsToInit) > 0:
+ initIdCalls = ["!InitIds(aCx, %s, %s_ids)" % (varname, varname)
+ for varname in idsToInit]
+ idsInitedFlag = CGGeneric("static bool sIdsInited = false;\n")
+ setFlag = CGGeneric("sIdsInited = true;\n")
+ initIdConditionals = [CGIfWrapper(CGGeneric("return;\n"), call)
+ for call in initIdCalls]
+ initIds = CGList([idsInitedFlag,
+ CGIfWrapper(CGList(initIdConditionals + [setFlag]),
+ "!sIdsInited && NS_IsMainThread()")])
+ else:
+ initIds = None
+
+ prefCacheData = []
+ for var in self.properties.arrayNames():
+ props = getattr(self.properties, var)
+ prefCacheData.extend(props.prefCacheData)
+ if len(prefCacheData) != 0:
+ prefCacheData = [
+ CGGeneric('Preferences::AddBoolVarCache(%s, "%s");\n' % (ptr, pref))
+ for pref, ptr in prefCacheData]
+ prefCache = CGWrapper(CGIndenter(CGList(prefCacheData)),
+ pre=("static bool sPrefCachesInited = false;\n"
+ "if (!sPrefCachesInited && NS_IsMainThread()) {\n"
+ " sPrefCachesInited = true;\n"),
+ post="}\n")
+ else:
+ prefCache = None
+
+ if self.descriptor.interface.ctor():
+ constructArgs = methodLength(self.descriptor.interface.ctor())
+ else:
+ constructArgs = 0
+ if len(self.descriptor.interface.namedConstructors) > 0:
+ namedConstructors = "namedConstructors"
+ else:
+ namedConstructors = "nullptr"
+
+ if needInterfacePrototypeObject:
+ protoClass = "&sPrototypeClass.mBase"
+ protoCache = "&aProtoAndIfaceCache.EntrySlotOrCreate(prototypes::id::%s)" % self.descriptor.name
+ parentProto = "parentProto"
+ getParentProto = CGGeneric(getParentProto)
+ else:
+ protoClass = "nullptr"
+ protoCache = "nullptr"
+ parentProto = "nullptr"
+ getParentProto = None
+
+ if needInterfaceObject:
+ interfaceClass = "&sInterfaceObjectClass.mBase"
+ interfaceCache = "&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)" % self.descriptor.name
+ getConstructorProto = CGGeneric(getConstructorProto)
+ constructorProto = "constructorProto"
+ else:
+ # We don't have slots to store the named constructors.
+ assert len(self.descriptor.interface.namedConstructors) == 0
+ interfaceClass = "nullptr"
+ interfaceCache = "nullptr"
+ getConstructorProto = None
+ constructorProto = "nullptr"
+
+ isGlobal = self.descriptor.isGlobal() is not None
+ if self.properties.hasNonChromeOnly():
+ properties = "sNativeProperties.Upcast()"
+ else:
+ properties = "nullptr"
+ if self.properties.hasChromeOnly():
+ chromeProperties = "nsContentUtils::ThreadsafeIsCallerChrome() ? sChromeOnlyNativeProperties.Upcast() : nullptr"
+ else:
+ chromeProperties = "nullptr"
+
+ call = fill(
+ """
+ JS::Heap<JSObject*>* protoCache = ${protoCache};
+ JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
+ dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
+ ${protoClass}, protoCache,
+ ${constructorProto}, ${interfaceClass}, ${constructArgs}, ${namedConstructors},
+ interfaceCache,
+ ${properties},
+ ${chromeProperties},
+ ${name}, aDefineOnGlobal,
+ ${unscopableNames},
+ ${isGlobal});
+ """,
+ protoClass=protoClass,
+ parentProto=parentProto,
+ protoCache=protoCache,
+ constructorProto=constructorProto,
+ interfaceClass=interfaceClass,
+ constructArgs=constructArgs,
+ namedConstructors=namedConstructors,
+ interfaceCache=interfaceCache,
+ properties=properties,
+ chromeProperties=chromeProperties,
+ name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr",
+ unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr",
+ isGlobal=toStringBool(isGlobal))
+
+ # If we fail after here, we must clear interface and prototype caches
+ # using this code: intermediate failure must not expose the interface in
+ # partially-constructed state. Note that every case after here needs an
+ # interface prototype object.
+ failureCode = dedent(
+ """
+ *protoCache = nullptr;
+ if (interfaceCache) {
+ *interfaceCache = nullptr;
+ }
+ return;
+ """)
+
+ aliasedMembers = [m for m in self.descriptor.interface.members if m.isMethod() and m.aliases]
+ if aliasedMembers:
+ assert needInterfacePrototypeObject
+
+ def defineAlias(alias):
+ if alias == "@@iterator":
+ symbolJSID = "SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::iterator))"
+ getSymbolJSID = CGGeneric(fill("JS::Rooted<jsid> iteratorId(aCx, ${symbolJSID});",
+ symbolJSID=symbolJSID))
+ defineFn = "JS_DefinePropertyById"
+ prop = "iteratorId"
+ elif alias.startswith("@@"):
+ raise TypeError("Can't handle any well-known Symbol other than @@iterator")
+ else:
+ getSymbolJSID = None
+ defineFn = "JS_DefineProperty"
+ prop = '"%s"' % alias
+ return CGList([
+ getSymbolJSID,
+ # 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.
+ CGGeneric(fill(
+ """
+ if (!${defineFn}(aCx, proto, ${prop}, aliasedVal, JSPROP_ENUMERATE)) {
+ $*{failureCode}
+ }
+ """,
+ defineFn=defineFn,
+ prop=prop,
+ failureCode=failureCode))
+ ], "\n")
+
+ def defineAliasesFor(m):
+ return CGList([
+ CGGeneric(fill(
+ """
+ if (!JS_GetProperty(aCx, proto, \"${prop}\", &aliasedVal)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ prop=m.identifier.name))
+ ] + [defineAlias(alias) for alias in sorted(m.aliases)])
+
+ defineAliases = CGList([
+ CGGeneric(fill("""
+ // Set up aliases on the interface prototype object we just created.
+ JS::Handle<JSObject*> proto = GetProtoObjectHandle(aCx);
+ if (!proto) {
+ $*{failureCode}
+ }
+
+ """,
+ failureCode=failureCode)),
+ CGGeneric("JS::Rooted<JS::Value> aliasedVal(aCx);\n\n")
+ ] + [defineAliasesFor(m) for m in sorted(aliasedMembers)])
+ else:
+ defineAliases = None
+
+ # Globals handle unforgeables directly in Wrap() instead of
+ # via a holder.
+ if self.descriptor.hasUnforgeableMembers and not self.descriptor.isGlobal():
+ assert needInterfacePrototypeObject
+
+ # We want to use the same JSClass and prototype as the object we'll
+ # end up defining the unforgeable properties on in the end, so that
+ # we can use JS_InitializePropertiesFromCompatibleNativeObject to do
+ # a fast copy. In the case of proxies that's null, because the
+ # expando object is a vanilla object, but in the case of other DOM
+ # objects it's whatever our class is.
+ if self.descriptor.proxy:
+ holderClass = "nullptr"
+ holderProto = "nullptr"
+ else:
+ holderClass = "sClass.ToJSClass()"
+ holderProto = "*protoCache"
+ createUnforgeableHolder = CGGeneric(fill(
+ """
+ JS::Rooted<JSObject*> unforgeableHolder(aCx);
+ {
+ JS::Rooted<JSObject*> holderProto(aCx, ${holderProto});
+ unforgeableHolder = JS_NewObjectWithoutMetadata(aCx, ${holderClass}, holderProto);
+ if (!unforgeableHolder) {
+ $*{failureCode}
+ }
+ }
+ """,
+ holderProto=holderProto,
+ holderClass=holderClass,
+ failureCode=failureCode))
+ defineUnforgeables = InitUnforgeablePropertiesOnHolder(self.descriptor,
+ self.properties,
+ failureCode)
+ createUnforgeableHolder = CGList(
+ [createUnforgeableHolder, defineUnforgeables])
+
+ installUnforgeableHolder = CGGeneric(dedent(
+ """
+ if (*protoCache) {
+ js::SetReservedSlot(*protoCache, DOM_INTERFACE_PROTO_SLOTS_BASE,
+ JS::ObjectValue(*unforgeableHolder));
+ }
+ """))
+
+ unforgeableHolderSetup = CGList(
+ [createUnforgeableHolder, installUnforgeableHolder], "\n")
+ else:
+ unforgeableHolderSetup = None
+
+ if self.descriptor.name == "Promise":
+ speciesSetup = CGGeneric(fill(
+ """
+ #ifndef SPIDERMONKEY_PROMISE
+ JS::Rooted<JSObject*> promiseConstructor(aCx, *interfaceCache);
+ JS::Rooted<jsid> species(aCx,
+ SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::species)));
+ if (!JS_DefinePropertyById(aCx, promiseConstructor, species, JS::UndefinedHandleValue,
+ JSPROP_SHARED, Promise::PromiseSpecies, nullptr)) {
+ $*{failureCode}
+ }
+ #endif // SPIDERMONKEY_PROMISE
+ """,
+ failureCode=failureCode))
+ else:
+ speciesSetup = None
+
+ if (self.descriptor.interface.isOnGlobalProtoChain() and
+ needInterfacePrototypeObject):
+ makeProtoPrototypeImmutable = CGGeneric(fill(
+ """
+ if (*${protoCache}) {
+ bool succeeded;
+ JS::Handle<JSObject*> prot = GetProtoObjectHandle(aCx);
+ if (!JS_SetImmutablePrototype(aCx, prot, &succeeded)) {
+ $*{failureCode}
+ }
+
+ MOZ_ASSERT(succeeded,
+ "making a fresh prototype object's [[Prototype]] "
+ "immutable can internally fail, but it should "
+ "never be unsuccessful");
+ }
+ """,
+ protoCache=protoCache,
+ failureCode=failureCode))
+ else:
+ makeProtoPrototypeImmutable = None
+
+ return CGList(
+ [getParentProto, getConstructorProto, initIds,
+ prefCache, CGGeneric(call), defineAliases, unforgeableHolderSetup,
+ speciesSetup, makeProtoPrototypeImmutable],
+ "\n").define()
+
+
+class CGGetPerInterfaceObject(CGAbstractMethod):
+ """
+ A method for getting a per-interface object (a prototype object or interface
+ constructor object).
+ """
+ def __init__(self, descriptor, name, idPrefix="", extraArgs=[]):
+ args = [Argument('JSContext*', 'aCx')] + extraArgs
+ CGAbstractMethod.__init__(self, descriptor, name,
+ 'JS::Handle<JSObject*>', args)
+ self.id = idPrefix + "id::" + self.descriptor.name
+
+ def definition_body(self):
+ return fill(
+ """
+ /* Make sure our global is sane. Hopefully we can remove this sometime */
+ JSObject* global = JS::CurrentGlobalOrNull(aCx);
+ if (!(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL)) {
+ return nullptr;
+ }
+
+ /* Check to see whether the interface objects are already installed */
+ ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global);
+ if (!protoAndIfaceCache.EntrySlotIfExists(${id})) {
+ JS::Rooted<JSObject*> rootedGlobal(aCx, global);
+ CreateInterfaceObjects(aCx, rootedGlobal, protoAndIfaceCache, aDefineOnGlobal);
+ }
+
+ /*
+ * The object might _still_ be null, but that's OK.
+ *
+ * Calling fromMarkedLocation() is safe because protoAndIfaceCache is
+ * traced by TraceProtoAndIfaceCache() and its contents are never
+ * changed after they have been set.
+ *
+ * Calling address() avoids the read read barrier that does gray
+ * unmarking, but it's not possible for the object to be gray here.
+ */
+
+ const JS::Heap<JSObject*>& entrySlot = protoAndIfaceCache.EntrySlotMustExist(${id});
+ MOZ_ASSERT_IF(entrySlot, !JS::ObjectIsMarkedGray(entrySlot));
+ return JS::Handle<JSObject*>::fromMarkedLocation(entrySlot.address());
+ """,
+ id=self.id)
+
+
+class CGGetProtoObjectHandleMethod(CGGetPerInterfaceObject):
+ """
+ A method for getting the interface prototype object.
+ """
+ def __init__(self, descriptor):
+ CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObjectHandle",
+ "prototypes::")
+
+ def definition_body(self):
+ return dedent("""
+ /* Get the interface prototype object for this class. This will create the
+ object as needed. */
+ bool aDefineOnGlobal = true;
+
+ """) + CGGetPerInterfaceObject.definition_body(self)
+
+
+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(CGGetPerInterfaceObject):
+ """
+ A method for getting the interface constructor object.
+ """
+ def __init__(self, descriptor):
+ CGGetPerInterfaceObject.__init__(
+ self, descriptor, "GetConstructorObjectHandle",
+ "constructors::",
+ extraArgs=[Argument("bool", "aDefineOnGlobal", "true")])
+
+ def definition_body(self):
+ return dedent("""
+ /* Get the interface object for this class. This will create the object as
+ needed. */
+
+ """) + CGGetPerInterfaceObject.definition_body(self)
+
+
+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::GetObjectClass(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::GetObjectClass(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,
+ "Don't know how to resolve the properties of the named properties object for ${nativeType}.");
+ MOZ_ASSERT(clasp->mNativeHooks->mEnumerateOwnProperties,
+ "Don't know how to enumerate the properties of the named properties object for ${nativeType}.");
+ }
+ return namedPropertiesObject.get();
+ """,
+ getParentProto=getParentProto,
+ ifaceName=self.descriptor.name,
+ parentProto=parentProto,
+ nativeType=self.descriptor.nativeType)
+
+
+class CGDefineDOMInterfaceMethod(CGAbstractMethod):
+ """
+ A method for resolve hooks to try to lazily define the interface object for
+ a given interface.
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'aCx'),
+ Argument('JS::Handle<JSObject*>', 'aGlobal'),
+ Argument('JS::Handle<jsid>', 'id'),
+ Argument('bool', 'aDefineOnGlobal')]
+ CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', 'JSObject*', args)
+
+ def definition_body(self):
+ if len(self.descriptor.interface.namedConstructors) > 0:
+ getConstructor = dedent("""
+ JSObject* interfaceObject = GetConstructorObjectHandle(aCx, aDefineOnGlobal);
+ if (!interfaceObject) {
+ return nullptr;
+ }
+ for (unsigned slot = DOM_INTERFACE_SLOTS_BASE; slot < JSCLASS_RESERVED_SLOTS(&sInterfaceObjectClass.mBase); ++slot) {
+ JSObject* constructor = &js::GetReservedSlot(interfaceObject, slot).toObject();
+ if (JS_GetFunctionId(JS_GetObjectFunction(constructor)) == JSID_TO_STRING(id)) {
+ return constructor;
+ }
+ }
+ return interfaceObject;
+ """)
+ else:
+ getConstructor = "return GetConstructorObjectHandle(aCx, aDefineOnGlobal);\n"
+ return getConstructor
+
+
+def getConditionList(idlobj, cxName, objName):
+ """
+ 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.
+ """
+ conditions = []
+ pref = idlobj.getExtendedAttribute("Pref")
+ if pref:
+ assert isinstance(pref, list) and len(pref) == 1
+ conditions.append('Preferences::GetBool("%s")' % pref[0])
+ if idlobj.getExtendedAttribute("ChromeOnly"):
+ conditions.append("nsContentUtils::ThreadsafeIsCallerChrome()")
+ func = idlobj.getExtendedAttribute("Func")
+ if func:
+ assert isinstance(func, list) and len(func) == 1
+ conditions.append("%s(%s, %s)" % (func[0], cxName, objName))
+ if idlobj.getExtendedAttribute("SecureContext"):
+ conditions.append("mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(%s, %s)" % (cxName, objName))
+
+ return CGList((CGGeneric(cond) for cond in conditions), " &&\n")
+
+
+class CGConstructorEnabled(CGAbstractMethod):
+ """
+ A method for testing whether we should be exposing this interface
+ object or navigator property. 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.isExposedOnMainThread():
+ 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::GetObjectClass(aObj)->name;
+ if (${workerCondition}) {
+ return false;
+ }
+ """, workerCondition=workerCondition.define())
+ exposedInWorkerCheck = CGGeneric(exposedInWorkerCheck)
+ if iface.isExposedOnMainThread():
+ 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 CreateBindingJSObject(descriptor, properties):
+ objDecl = "BindingJSObjectCreator<%s> creator(aCx);\n" % descriptor.nativeType
+
+ # We don't always need to root obj, but there are a variety
+ # of cases where we do, so for simplicity, just always root it.
+ if descriptor.proxy:
+ create = dedent(
+ """
+ creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(),
+ proto, aObject, aReflector);
+ if (!aReflector) {
+ return false;
+ }
+
+ """)
+ if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+ create += dedent("""
+ js::SetProxyExtra(aReflector, JSPROXYSLOT_EXPANDO,
+ JS::PrivateValue(&aObject->mExpandoAndGeneration));
+
+ """)
+ else:
+ create = dedent(
+ """
+ creator.CreateObject(aCx, sClass.ToJSClass(), proto, aObject, aReflector);
+ if (!aReflector) {
+ return false;
+ }
+ """)
+ return objDecl + create
+
+
+def InitUnforgeablePropertiesOnHolder(descriptor, properties, failureCode,
+ holderName="unforgeableHolder"):
+ """
+ Define the unforgeable properties on the unforgeable holder for
+ the interface represented by descriptor.
+
+ properties is a PropertyArrays instance.
+
+ """
+ assert (properties.unforgeableAttrs.hasNonChromeOnly() or
+ properties.unforgeableAttrs.hasChromeOnly() or
+ properties.unforgeableMethods.hasNonChromeOnly() or
+ properties.unforgeableMethods.hasChromeOnly())
+
+ unforgeables = []
+
+ defineUnforgeableAttrs = fill(
+ """
+ if (!DefineUnforgeableAttributes(aCx, ${holderName}, %s)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ holderName=holderName)
+ defineUnforgeableMethods = fill(
+ """
+ if (!DefineUnforgeableMethods(aCx, ${holderName}, %s)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ holderName=holderName)
+
+ unforgeableMembers = [
+ (defineUnforgeableAttrs, properties.unforgeableAttrs),
+ (defineUnforgeableMethods, properties.unforgeableMethods)
+ ]
+ for (template, array) in unforgeableMembers:
+ if array.hasNonChromeOnly():
+ unforgeables.append(CGGeneric(template % array.variableName(False)))
+ if array.hasChromeOnly():
+ unforgeables.append(
+ CGIfWrapper(CGGeneric(template % array.variableName(True)),
+ "nsContentUtils::ThreadsafeIsCallerChrome()"))
+
+ if descriptor.interface.getExtendedAttribute("Unforgeable"):
+ # We do our undefined toJSON and toPrimitive here, not as a regular
+ # property because we don't have a concept of value props anywhere in
+ # IDL.
+ unforgeables.append(CGGeneric(fill(
+ """
+ JS::RootedId toPrimitive(aCx,
+ SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::toPrimitive)));
+ if (!JS_DefinePropertyById(aCx, ${holderName}, toPrimitive,
+ JS::UndefinedHandleValue,
+ JSPROP_READONLY | JSPROP_PERMANENT) ||
+ !JS_DefineProperty(aCx, ${holderName}, "toJSON",
+ JS::UndefinedHandleValue,
+ JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode,
+ holderName=holderName)))
+
+ return CGWrapper(CGList(unforgeables), pre="\n")
+
+
+def CopyUnforgeablePropertiesToInstance(descriptor, failureCode):
+ """
+ Copy the unforgeable properties from the unforgeable holder for
+ this interface to the instance object we have.
+ """
+ assert not descriptor.isGlobal();
+
+ if not descriptor.hasUnforgeableMembers:
+ return ""
+
+ copyCode = [
+ CGGeneric(dedent(
+ """
+ // Important: do unforgeable property setup after we have handed
+ // over ownership of the C++ object to obj as needed, so that if
+ // we fail and it ends up GCed it won't have problems in the
+ // finalizer trying to drop its ownership of the C++ object.
+ """))
+ ]
+
+ # For proxies, we want to define on the expando object, not directly on the
+ # reflector, so we can make sure we don't get confused by named getters.
+ if descriptor.proxy:
+ copyCode.append(CGGeneric(fill(
+ """
+ JS::Rooted<JSObject*> expando(aCx,
+ DOMProxyHandler::EnsureExpandoObject(aCx, aReflector));
+ if (!expando) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode)))
+ obj = "expando"
+ else:
+ obj = "aReflector"
+
+ copyCode.append(CGGeneric(fill(
+ """
+ JS::Rooted<JSObject*> unforgeableHolder(aCx,
+ &js::GetReservedSlot(canonicalProto, DOM_INTERFACE_PROTO_SLOTS_BASE).toObject());
+ if (!JS_InitializePropertiesFromCompatibleNativeObject(aCx, ${obj}, unforgeableHolder)) {
+ $*{failureCode}
+ }
+ """,
+ obj=obj,
+ failureCode=failureCode)))
+
+ return CGWrapper(CGList(copyCode), pre="\n").define()
+
+
+def AssertInheritanceChain(descriptor):
+ asserts = ""
+ iface = descriptor.interface
+ while iface:
+ desc = descriptor.getDescriptor(iface.identifier.name)
+ asserts += (
+ "MOZ_ASSERT(static_cast<%s*>(aObject) == \n"
+ " reinterpret_cast<%s*>(aObject),\n"
+ " \"Multiple inheritance for %s is broken.\");\n" %
+ (desc.nativeType, desc.nativeType, desc.nativeType))
+ iface = iface.parent
+ asserts += "MOZ_ASSERT(ToSupportsIsCorrect(aObject));\n"
+ return asserts
+
+
+def InitMemberSlots(descriptor, failureCode):
+ """
+ Initialize member slots on our JS object if we're supposed to have some.
+
+ Note that this is called after the SetWrapper() call in the
+ wrapperCache case, since that can affect how our getters behave
+ and we plan to invoke them here. So if we fail, we need to
+ ClearWrapper.
+ """
+ if not descriptor.interface.hasMembersInSlots():
+ return ""
+ return fill(
+ """
+ if (!UpdateMemberSlots(aCx, aReflector, aObject)) {
+ $*{failureCode}
+ }
+ """,
+ failureCode=failureCode)
+
+
+def SetImmutablePrototype(descriptor, failureCode):
+ if not descriptor.hasNonOrdinaryGetPrototypeOf():
+ return ""
+
+ return fill(
+ """
+ bool succeeded;
+ if (!JS_SetImmutablePrototype(aCx, aReflector, &succeeded)) {
+ ${failureCode}
+ }
+ MOZ_ASSERT(succeeded,
+ "Making a fresh reflector instance have an immutable "
+ "prototype can internally fail, but it should never be "
+ "unsuccessful");
+ """,
+ failureCode=failureCode)
+
+
+def DeclareProto():
+ """
+ Declare the canonicalProto and proto we have for our wrapping operation.
+ """
+ return dedent(
+ """
+ JS::Handle<JSObject*> canonicalProto = GetProtoObjectHandle(aCx);
+ if (!canonicalProto) {
+ return false;
+ }
+ JS::Rooted<JSObject*> proto(aCx);
+ 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::GetObjectCompartment(proto)) {
+ if (!JS_WrapObject(aCx, &proto)) {
+ return false;
+ }
+ }
+ } else {
+ proto = canonicalProto;
+ }
+ """)
+
+
+class CGWrapWithCacheMethod(CGAbstractMethod):
+ """
+ Create a wrapper JSObject for a given native that implements nsWrapperCache.
+
+ properties should be a PropertyArrays instance.
+ """
+ def __init__(self, descriptor, properties):
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ args = [Argument('JSContext*', 'aCx'),
+ Argument(descriptor.nativeType + '*', 'aObject'),
+ Argument('nsWrapperCache*', 'aCache'),
+ Argument('JS::Handle<JSObject*>', 'aGivenProto'),
+ Argument('JS::MutableHandle<JSObject*>', 'aReflector')]
+ CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'bool', args)
+ self.properties = properties
+
+ def definition_body(self):
+ if self.descriptor.proxy:
+ preserveWrapper = dedent(
+ """
+ // For DOM proxies, the only reliable way to preserve the wrapper
+ // is to force creation of the expando object.
+ JS::Rooted<JSObject*> unused(aCx,
+ DOMProxyHandler::EnsureExpandoObject(aCx, aReflector));
+ """)
+ else:
+ preserveWrapper = "PreserveWrapper(aObject);\n"
+
+ failureCode = dedent(
+ """
+ aCache->ReleaseWrapper(aObject);
+ aCache->ClearWrapper();
+ return false;
+ """)
+
+ return fill(
+ """
+ $*{assertInheritance}
+ 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");
+
+ JS::Rooted<JSObject*> global(aCx, FindAssociatedGlobal(aCx, aObject->GetParentObject()));
+ if (!global) {
+ return false;
+ }
+ MOZ_ASSERT(JS_IsGlobalObject(global));
+ MOZ_ASSERT(!JS::ObjectIsMarkedGray(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
+ binding_detail::AssertReflectorHasGivenProto(aCx, aReflector, aGivenProto);
+ #endif // DEBUG
+ return true;
+ }
+
+ JSAutoCompartment ac(aCx, global);
+ $*{declareProto}
+
+ $*{createObject}
+
+ aCache->SetWrapper(aReflector);
+ $*{unforgeable}
+ $*{slots}
+ $*{setImmutablePrototype}
+ 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}
+ }
+
+ return true;
+ """,
+ assertInheritance=AssertInheritanceChain(self.descriptor),
+ declareProto=DeclareProto(),
+ createObject=CreateBindingJSObject(self.descriptor, self.properties),
+ unforgeable=CopyUnforgeablePropertiesToInstance(self.descriptor,
+ failureCode),
+ slots=InitMemberSlots(self.descriptor, failureCode),
+ setImmutablePrototype=SetImmutablePrototype(self.descriptor,
+ failureCode),
+ preserveWrapper=preserveWrapper)
+
+
+class CGWrapMethod(CGAbstractMethod):
+ def __init__(self, descriptor):
+ # XXX can we wrap if we don't have an interface prototype object?
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ args = [Argument('JSContext*', 'aCx'),
+ Argument('T*', 'aObject'),
+ Argument('JS::Handle<JSObject*>', 'aGivenProto')]
+ CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args,
+ inline=True, templateArgs=["class T"])
+
+ def definition_body(self):
+ return dedent("""
+ JS::Rooted<JSObject*> reflector(aCx);
+ return Wrap(aCx, aObject, aObject, aGivenProto, &reflector) ? reflector.get() : nullptr;
+ """)
+
+
+class CGWrapNonWrapperCacheMethod(CGAbstractMethod):
+ """
+ Create a wrapper JSObject for a given native that does not implement
+ nsWrapperCache.
+
+ properties should be a PropertyArrays instance.
+ """
+ def __init__(self, descriptor, properties):
+ # XXX can we wrap if we don't have an interface prototype object?
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ args = [Argument('JSContext*', 'aCx'),
+ Argument(descriptor.nativeType + '*', 'aObject'),
+ Argument('JS::Handle<JSObject*>', 'aGivenProto'),
+ Argument('JS::MutableHandle<JSObject*>', 'aReflector')]
+ CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'bool', args)
+ self.properties = properties
+
+ def definition_body(self):
+ failureCode = "return false;\n"
+
+ return fill(
+ """
+ $*{assertions}
+
+ JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
+ $*{declareProto}
+
+ $*{createObject}
+
+ $*{unforgeable}
+
+ $*{slots}
+
+ $*{setImmutablePrototype}
+ creator.InitializationSucceeded();
+ return true;
+ """,
+ assertions=AssertInheritanceChain(self.descriptor),
+ declareProto=DeclareProto(),
+ createObject=CreateBindingJSObject(self.descriptor, self.properties),
+ unforgeable=CopyUnforgeablePropertiesToInstance(self.descriptor,
+ failureCode),
+ slots=InitMemberSlots(self.descriptor, failureCode),
+ setImmutablePrototype=SetImmutablePrototype(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::CompartmentOptions&', '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::ThreadsafeIsCallerChrome() ? sChromeOnlyNativeProperties.Upcast() : nullptr"
+ else:
+ chromeProperties = "nullptr"
+
+ failureCode = dedent(
+ """
+ aCache->ReleaseWrapper(aObject);
+ aCache->ClearWrapper();
+ return false;
+ """);
+
+ if self.descriptor.hasUnforgeableMembers:
+ unforgeable = InitUnforgeablePropertiesOnHolder(
+ self.descriptor, self.properties, failureCode,
+ "aReflector").define();
+ else:
+ unforgeable = ""
+
+ return fill(
+ """
+ $*{assertions}
+ MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
+ "nsISupports must be on our primary inheritance chain");
+
+ if (!CreateGlobal<${nativeType}, GetProtoObjectHandle>(aCx,
+ aObject,
+ aCache,
+ sClass.ToJSClass(),
+ aOptions,
+ aPrincipal,
+ aInitStandardClasses,
+ aReflector)) {
+ $*{failureCode}
+ }
+
+ // aReflector is a new global, so has a new compartment. Enter it
+ // before doing anything with it.
+ JSAutoCompartment ac(aCx, aReflector);
+
+ if (!DefineProperties(aCx, aReflector, ${properties}, ${chromeProperties})) {
+ $*{failureCode}
+ }
+ $*{unforgeable}
+
+ $*{slots}
+
+ return true;
+ """,
+ assertions=AssertInheritanceChain(self.descriptor),
+ nativeType=self.descriptor.nativeType,
+ properties=properties,
+ chromeProperties=chromeProperties,
+ failureCode=failureCode,
+ unforgeable=unforgeable,
+ slots=InitMemberSlots(self.descriptor, failureCode))
+
+
+class CGUpdateMemberSlotsMethod(CGAbstractStaticMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'aCx'),
+ Argument('JS::Handle<JSObject*>', 'aWrapper'),
+ Argument(descriptor.nativeType + '*', 'aObject')]
+ CGAbstractStaticMethod.__init__(self, descriptor, 'UpdateMemberSlots', 'bool', args)
+
+ def definition_body(self):
+ body = ("JS::Rooted<JS::Value> temp(aCx);\n"
+ "JSJitGetterCallArgs args(&temp);\n")
+ for m in self.descriptor.interface.members:
+ if m.isAttr() and m.getExtendedAttribute("StoreInSlot"):
+ # Skip doing this for the "window" and "self" attributes on the
+ # Window interface, because those can't be gotten safely until
+ # we have hooked it up correctly to the outer window. The
+ # window code handles doing the get itself.
+ if (self.descriptor.interface.identifier.name == "Window" and
+ (m.identifier.name == "window" or m.identifier.name == "self")):
+ continue
+ body += fill(
+ """
+
+ if (!get_${member}(aCx, aWrapper, aObject, args)) {
+ return false;
+ }
+ // Getter handled setting our reserved slots
+ """,
+ 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);
+ JSAutoCompartment ac(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 CGIsPermittedMethod(CGAbstractMethod):
+ """
+ crossOriginGetters/Setters/Methods are sets of names of the relevant members.
+ """
+ def __init__(self, descriptor, crossOriginGetters, crossOriginSetters,
+ crossOriginMethods):
+ self.crossOriginGetters = crossOriginGetters
+ self.crossOriginSetters = crossOriginSetters
+ self.crossOriginMethods = crossOriginMethods
+ args = [Argument("JSFlatString*", "prop"),
+ Argument("char16_t", "propFirstChar"),
+ Argument("bool", "set")]
+ CGAbstractMethod.__init__(self, descriptor, "IsPermitted", "bool", args,
+ inline=True)
+
+ def definition_body(self):
+ allNames = self.crossOriginGetters | self.crossOriginSetters | self.crossOriginMethods
+ readwrite = self.crossOriginGetters & self.crossOriginSetters
+ readonly = (self.crossOriginGetters - self.crossOriginSetters) | self.crossOriginMethods
+ writeonly = self.crossOriginSetters - self.crossOriginGetters
+ cases = {}
+ for name in sorted(allNames):
+ cond = 'JS_FlatStringEqualsAscii(prop, "%s")' % name
+ if name in readonly:
+ cond = "!set && %s" % cond
+ elif name in writeonly:
+ cond = "set && %s" % cond
+ else:
+ assert name in readwrite
+ firstLetter = name[0]
+ case = cases.get(firstLetter, CGList([]))
+ case.append(CGGeneric("if (%s) {\n"
+ " return true;\n"
+ "}\n" % cond))
+ cases[firstLetter] = case
+ caseList = []
+ for firstLetter in sorted(cases.keys()):
+ caseList.append(CGCase("'%s'" % firstLetter, cases[firstLetter]))
+ switch = CGSwitch("propFirstChar", caseList)
+ return switch.define() + "\nreturn false;\n"
+
+
+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.
+
+ If allowCrossOriginObj is True, then we'll first do an
+ UncheckedUnwrap and then operate on the result.
+ """
+ def __init__(self, descriptor, source, mutableSource, target, codeOnFailure,
+ exceptionCode=None, isCallbackReturnValue=False,
+ allowCrossOriginObj=False):
+ self.substitution = {
+ "type": descriptor.nativeType,
+ "protoID": "prototypes::id::" + descriptor.name,
+ "target": target,
+ "codeOnFailure": codeOnFailure,
+ }
+ # Supporting both the "cross origin object" case and the "has
+ # XPConnect impls" case at the same time is a pain, so let's
+ # not do that. That allows us to assume that our source is
+ # always a Handle or MutableHandle.
+ if allowCrossOriginObj and descriptor.hasXPConnectImpls:
+ raise TypeError("Interface %s both allows a cross-origin 'this' "
+ "and has XPConnect impls. We don't support that" %
+ descriptor.name)
+ if allowCrossOriginObj:
+ self.substitution["uncheckedObjDecl"] = fill(
+ """
+ JS::Rooted<JSObject*> maybeUncheckedObj(cx, &${source}.toObject());
+ """,
+ source=source)
+ self.substitution["uncheckedObjGet"] = fill(
+ """
+ if (xpc::WrapperFactory::IsXrayWrapper(maybeUncheckedObj)) {
+ maybeUncheckedObj = js::UncheckedUnwrap(maybeUncheckedObj);
+ } else {
+ maybeUncheckedObj = js::CheckedUnwrap(maybeUncheckedObj);
+ if (!maybeUncheckedObj) {
+ $*{codeOnFailure}
+ }
+ }
+ """,
+ codeOnFailure=(codeOnFailure % { 'securityError': 'true'}))
+ self.substitution["source"] = "maybeUncheckedObj"
+ self.substitution["mutableSource"] = "&maybeUncheckedObj"
+ # No need to set up xpconnectUnwrap, since it won't be
+ # used in the allowCrossOriginObj case.
+ else:
+ self.substitution["uncheckedObjDecl"] = ""
+ self.substitution["uncheckedObjGet"] = ""
+ self.substitution["source"] = source
+ self.substitution["mutableSource"] = mutableSource
+ xpconnectUnwrap = (
+ "nsresult rv = UnwrapXPConnect<${type}>(cx, ${mutableSource}, getter_AddRefs(objPtr));\n")
+
+ if descriptor.hasXPConnectImpls:
+ self.substitution["codeOnFailure"] = string.Template(
+ "RefPtr<${type}> objPtr;\n" +
+ xpconnectUnwrap +
+ "if (NS_FAILED(rv)) {\n"
+ "${indentedCodeOnFailure}"
+ "}\n"
+ "// We should have an object\n"
+ "MOZ_ASSERT(objPtr);\n"
+ "${target} = objPtr;\n"
+ ).substitute(self.substitution,
+ indentedCodeOnFailure=indent(codeOnFailure))
+ elif (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;
+ if (!GetContentGlobalForJSImplementedObject(cx, Callback(), getter_AddRefs(contentGlobal))) {
+ $*{exceptionCode}
+ }
+ JS::Rooted<JSObject*> jsImplSourceObj(cx, &${source}.toObject());
+ ${target} = new ${type}(jsImplSourceObj, 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(
+ """
+ $*{uncheckedObjDecl}
+ {
+ $*{uncheckedObjGet}
+ nsresult rv = UnwrapObject<${protoID}, ${type}>(${mutableSource}, ${target});
+ 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,
+ 'ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s", "%s");\n'
+ '%s' % (sourceDescription, descriptor.interface.identifier.name,
+ exceptionCode),
+ exceptionCode,
+ isCallbackReturnValue)
+
+
+class CGCallbackTempRoot(CGGeneric):
+ def __init__(self, name):
+ define = dedent("""
+ { // Scope for tempRoot
+ JS::Rooted<JSObject*> tempRoot(cx, &${val}.toObject());
+ ${declName} = new %s(cx, tempRoot, mozilla::dom::GetIncumbentGlobal());
+ }
+ """) % name
+ CGGeneric.__init__(self, define=define)
+
+
+def getCallbackConversionInfo(type, idlObject, isMember, isCallbackReturnValue,
+ isOptional):
+ """
+ Returns a tuple containing the declType, declArgs, and basic
+ conversion for the given callback type, with the given callback
+ idl object in the given context (isMember/isCallbackReturnValue/isOptional).
+ """
+ name = idlObject.identifier.name
+
+ # We can't use fast callbacks if isOptional because then we get an
+ # Optional<RootedCallback> thing, which is not transparent to consumers.
+ useFastCallback = (not isMember and not isCallbackReturnValue and
+ not isOptional)
+ if useFastCallback:
+ name = "binding_detail::Fast%s" % name
+
+ 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 = indent(CGCallbackTempRoot(name).define())
+ 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.
+ ${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; in particular it does not
+ end with a ';'
+ """
+ assert defaultValue.type.isDOMString() or defaultValue.type.isByteString()
+ return ("static const %(char_t)s data[] = { %(data)s };\n"
+ "%(method)s(data, ArrayLength(data) - 1)") % {
+ 'char_t': "char" if defaultValue.type.isByteString() else "char16_t",
+ 'method': method,
+ 'data': ", ".join(["'" + char + "'" for char in
+ defaultValue.value] + ["0"])
+ }
+
+
+# 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,
+ treatNullAs="Default",
+ isEnforceRange=False,
+ isClamp=False,
+ isNullOrUndefined=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 know the value
+ isObject() and we have no need to recheck that.
+
+ if isMember is not False, we're being converted from a property of some JS
+ object, not from an actual method argument, so we can't rely on our jsval
+ being rooted or outliving us in any way. Callers can pass "Dictionary",
+ "Variadic", "Sequence", or "OwningUnion" to indicate that the conversion is
+ for something that is a dictionary member, a variadic argument, a sequence,
+ or an owning union respectively.
+
+ If isOptional is true, then we are doing conversion of an optional
+ argument with no default value.
+
+ invalidEnumValueFatal controls whether an invalid enum value conversion
+ attempt will throw (if true) or simply return without doing anything (if
+ false).
+
+ If defaultValue is not None, it's the IDL default value for this conversion
+
+ If isEnforceRange is true, we're converting an integer and throwing if the
+ value is out of range.
+
+ If isClamp is true, we're converting an integer and clamping if the
+ value is out of range.
+
+ If lenientFloatCode is not None, it should be used in cases when
+ we're a non-finite float that's not unrestricted.
+
+ If allowTreatNonCallableAsNull is true, then [TreatNonCallableAsNull] and
+ [TreatNonObjectAsNull] extended attributes on nullable callback functions
+ will be honored.
+
+ If isCallbackReturnValue is "JSImpl" or "Callback", then the declType may be
+ adjusted to make it easier to return from a callback. Since that type is
+ never directly observable by any consumers of the callback code, this is OK.
+ Furthermore, if isCallbackReturnValue is "JSImpl", that affects the behavior
+ of the FailureFatalCastableObjectUnwrapper conversion; this is used for
+ implementing auto-wrapping of JS-implemented return values from a
+ JS-implemented interface.
+
+ sourceDescription is a description of what this JS value represents, to be
+ used in error reporting. Callers should assume that it might get placed in
+ the middle of a sentence. If it ends up at the beginning of a sentence, its
+ first character will be automatically uppercased.
+
+ The return value from this function is a JSToNativeConversionInfo.
+ """
+ # If we have a defaultValue then we're not actually optional for
+ # purposes of what we need to be declared as.
+ assert defaultValue is None or not isOptional
+
+ # Also, we should not have a defaultValue if we know we're an object
+ assert not isDefinitelyObject or defaultValue is None
+
+ # And we can't both be an object and be null or undefined
+ assert not isDefinitelyObject or not isNullOrUndefined
+
+ # 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
+ ('ThrowErrorMessage(cx, MSG_NOT_OBJECT, "%s");\n'
+ '%s' % (firstCap(sourceDescription), exceptionCode)))
+
+ def onFailureBadType(failureCode, typeName):
+ return CGGeneric(
+ failureCode or
+ ('ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s", "%s");\n'
+ '%s' % (firstCap(sourceDescription), typeName, exceptionCode)))
+
+ def onFailureNotCallable(failureCode):
+ return CGGeneric(
+ failureCode or
+ ('ThrowErrorMessage(cx, 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
+ return (
+ "if (${haveValue}) {\n" +
+ indent(template) +
+ "} else {\n" +
+ indent(setDefault) +
+ "}\n")
+
+ # A helper function for wrapping up the template body for
+ # possibly-nullable objecty stuff
+ def wrapObjectTemplate(templateBody, type, codeToSetNull, failureCode=None):
+ if isNullOrUndefined and type.nullable():
+ # Just ignore templateBody and set ourselves to null.
+ # Note that we don't have to worry about default values
+ # here either, since we already examined this value.
+ return codeToSetNull
+
+ if not isDefinitelyObject:
+ # Handle the non-object cases by wrapping up the whole
+ # thing in an if cascade.
+ if type.nullable():
+ elifLine = "} else if (${val}.isNullOrUndefined()) {\n"
+ elifBody = codeToSetNull
+ else:
+ elifLine = ""
+ elifBody = ""
+
+ # Note that $${val} below expands to ${val}. This string is
+ # used as a template later, and val will be filled in then.
+ templateBody = fill(
+ """
+ if ($${val}.isObject()) {
+ $*{templateBody}
+ $*{elifLine}
+ $*{elifBody}
+ } else {
+ $*{failureBody}
+ }
+ """,
+ templateBody=templateBody,
+ elifLine=elifLine,
+ elifBody=elifBody,
+ failureBody=onFailureNotAnObject(failureCode).define())
+
+ if isinstance(defaultValue, IDLNullValue):
+ assert type.nullable() # Parser should enforce this
+ templateBody = handleDefault(templateBody, codeToSetNull)
+ elif isinstance(defaultValue, IDLEmptySequenceValue):
+ # Our caller will handle it
+ pass
+ else:
+ assert defaultValue is None
+
+ return templateBody
+
+ # A helper function for converting things that look like a JSObject*.
+ def handleJSObjectType(type, isMember, failureCode, exceptionCode, sourceDescription):
+ if not isMember:
+ if isOptional:
+ # We have a specialization of Optional that will use a
+ # Rooted for the storage here.
+ declType = CGGeneric("JS::Handle<JSObject*>")
+ else:
+ declType = CGGeneric("JS::Rooted<JSObject*>")
+ declArgs = "cx"
+ else:
+ assert (isMember in
+ ("Sequence", "Variadic", "Dictionary", "OwningUnion", "MozMap"))
+ # 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`.
+ if not isinstance(descriptorProvider, Descriptor) or descriptorProvider.interface.isJSImplemented():
+ templateBody = fill(
+ """
+ if (($${passedToJSImpl}) && !CallerSubsumes($${val})) {
+ ThrowErrorMessage(cx, MSG_PERMISSION_DENIED_TO_PASS_ARG, "${sourceDescription}");
+ $*{exceptionCode}
+ }
+ """,
+ 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 is "":
+ return 1
+ return nestingLevel + 1
+
+ assert not (isEnforceRange and isClamp) # These are mutually exclusive
+
+ if type.isSequence():
+ assert not isEnforceRange and not isClamp
+
+ if failureCode is None:
+ notSequence = ('ThrowErrorMessage(cx, MSG_NOT_SEQUENCE, "%s");\n'
+ "%s" % (firstCap(sourceDescription), exceptionCode))
+ else:
+ notSequence = failureCode
+
+ nullable = type.nullable()
+ # Be very careful not to change "type": we need it later
+ if nullable:
+ elementType = type.inner.inner
+ else:
+ elementType = type.inner
+
+ # We want to use auto arrays if we can, but we have to be careful with
+ # reallocation behavior for arrays. In particular, if we use auto
+ # arrays for sequences and have a sequence of elements which are
+ # themselves sequences or have sequences as members, we have a problem.
+ # In that case, resizing the outermost AutoTArray to the right size
+ # will memmove its elements, but AutoTArrays are not memmovable and
+ # hence will end up with pointers to bogus memory, which is bad. To
+ # deal with this, we typically map WebIDL sequences to our Sequence
+ # type, which is in fact memmovable. The one exception is when we're
+ # passing in a sequence directly as an argument without any sort of
+ # optional or nullable complexity going on. In that situation, we can
+ # use an AutoSequence instead. We have to keep using Sequence in the
+ # nullable and optional cases because we don't want to leak the
+ # AutoSequence type to consumers, which would be unavoidable with
+ # Nullable<AutoSequence> or Optional<AutoSequence>.
+ if isMember or isOptional or nullable or isCallbackReturnValue:
+ sequenceClass = "Sequence"
+ else:
+ sequenceClass = "binding_detail::AutoSequence"
+
+ # XXXbz we can't include the index in the sourceDescription, because
+ # we don't really have a way to pass one in dynamically at runtime...
+ elementInfo = getJSToNativeConversionInfo(
+ elementType, descriptorProvider, isMember="Sequence",
+ exceptionCode=exceptionCode, lenientFloatCode=lenientFloatCode,
+ isCallbackReturnValue=isCallbackReturnValue,
+ sourceDescription="element of %s" % sourceDescription,
+ nestingLevel=incrementNestingLevel())
+ if elementInfo.dealWithOptional:
+ raise TypeError("Shouldn't have optional things in sequences")
+ if elementInfo.holderType is not None:
+ raise TypeError("Shouldn't need holders for sequences")
+
+ typeName = CGTemplatedType(sequenceClass, elementInfo.declType)
+ sequenceType = typeName.define()
+ if nullable:
+ typeName = CGTemplatedType("Nullable", typeName)
+ arrayRef = "${declName}.SetValue()"
+ else:
+ arrayRef = "${declName}"
+
+ elementConversion = string.Template(elementInfo.template).substitute({
+ "val": "temp" + str(nestingLevel),
+ "maybeMutableVal": "&temp" + str(nestingLevel),
+ "declName": "slot" + str(nestingLevel),
+ # We only need holderName here to handle isExternal()
+ # interfaces, which use an internal holder for the
+ # conversion even when forceOwningType ends up true.
+ "holderName": "tempHolder" + str(nestingLevel),
+ "passedToJSImpl": "${passedToJSImpl}"
+ })
+
+ # 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(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,
+ nestingLevel=str(nestingLevel))
+
+ templateBody = wrapObjectTemplate(templateBody, type,
+ "${declName}.SetNull();\n", notSequence)
+ if isinstance(defaultValue, IDLEmptySequenceValue):
+ if type.nullable():
+ codeToSetEmpty = "${declName}.SetValue();\n"
+ else:
+ codeToSetEmpty = "/* Array is already empty; nothing to do */\n"
+ templateBody = handleDefault(templateBody, codeToSetEmpty)
+
+ # Sequence arguments that might contain traceable things need
+ # to get traced
+ if not isMember and typeNeedsRooting(elementType):
+ holderType = CGTemplatedType("SequenceRooter", elementInfo.declType)
+ # If our sequence is nullable, this will set the Nullable to be
+ # not-null, but that's ok because we make an explicit SetNull() call
+ # on it as needed if our JS value is actually null.
+ holderArgs = "cx, &%s" % arrayRef
+ else:
+ holderType = None
+ holderArgs = None
+
+ return JSToNativeConversionInfo(templateBody, declType=typeName,
+ holderType=holderType,
+ dealWithOptional=isOptional,
+ holderArgs=holderArgs)
+
+ if type.isMozMap():
+ assert not isEnforceRange and not isClamp
+ if failureCode is None:
+ notMozMap = ('ThrowErrorMessage(cx, MSG_NOT_OBJECT, "%s");\n'
+ "%s" % (firstCap(sourceDescription), exceptionCode))
+ else:
+ notMozMap = failureCode
+
+ nullable = type.nullable()
+ # Be very careful not to change "type": we need it later
+ if nullable:
+ valueType = type.inner.inner
+ else:
+ valueType = type.inner
+
+ valueInfo = getJSToNativeConversionInfo(
+ valueType, descriptorProvider, isMember="MozMap",
+ exceptionCode=exceptionCode, lenientFloatCode=lenientFloatCode,
+ isCallbackReturnValue=isCallbackReturnValue,
+ sourceDescription="value in %s" % sourceDescription,
+ nestingLevel=incrementNestingLevel())
+ if valueInfo.dealWithOptional:
+ raise TypeError("Shouldn't have optional things in MozMap")
+ if valueInfo.holderType is not None:
+ raise TypeError("Shouldn't need holders for MozMap")
+
+ typeName = CGTemplatedType("MozMap", valueInfo.declType)
+ mozMapType = typeName.define()
+ if nullable:
+ typeName = CGTemplatedType("Nullable", typeName)
+ mozMapRef = "${declName}.SetValue()"
+ else:
+ mozMapRef = "${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}"
+ })
+
+ templateBody = fill(
+ """
+ ${mozMapType} &mozMap = ${mozMapRef};
+
+ JS::Rooted<JSObject*> mozMapObj(cx, &$${val}.toObject());
+ JS::Rooted<JS::IdVector> ids(cx, JS::IdVector(cx));
+ if (!JS_Enumerate(cx, mozMapObj, &ids)) {
+ $*{exceptionCode}
+ }
+ JS::Rooted<JS::Value> propNameValue(cx);
+ JS::Rooted<JS::Value> temp(cx);
+ JS::Rooted<jsid> curId(cx);
+ for (size_t i = 0; i < ids.length(); ++i) {
+ // Make sure we get the value before converting the name, since
+ // getting the value can trigger GC but our name is a dependent
+ // string.
+ curId = ids[i];
+ binding_detail::FakeString propName;
+ bool isSymbol;
+ if (!ConvertIdToString(cx, curId, propName, isSymbol) ||
+ (!isSymbol && !JS_GetPropertyById(cx, mozMapObj, curId, &temp))) {
+ $*{exceptionCode}
+ }
+ if (isSymbol) {
+ continue;
+ }
+
+ ${valueType}* slotPtr = mozMap.AddEntry(propName);
+ if (!slotPtr) {
+ JS_ReportOutOfMemory(cx);
+ $*{exceptionCode}
+ }
+ ${valueType}& slot = *slotPtr;
+ $*{valueConversion}
+ }
+ """,
+ exceptionCode=exceptionCode,
+ mozMapType=mozMapType,
+ mozMapRef=mozMapRef,
+ valueType=valueInfo.declType.define(),
+ valueConversion=valueConversion)
+
+ templateBody = wrapObjectTemplate(templateBody, type,
+ "${declName}.SetNull();\n",
+ notMozMap)
+
+ declType = typeName
+ declArgs = None
+ holderType = None
+ holderArgs = None
+ # MozMap arguments that might contain traceable things need
+ # to get traced
+ if not isMember and isCallbackReturnValue:
+ # Go ahead and just convert directly into our actual return value
+ declType = CGWrapper(declType, post="&")
+ declArgs = "aRetVal"
+ elif not isMember and typeNeedsRooting(valueType):
+ holderType = CGTemplatedType("MozMapRooter", valueInfo.declType)
+ # If our MozMap 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" % mozMapRef
+
+ return JSToNativeConversionInfo(templateBody, declType=declType,
+ declArgs=declArgs,
+ holderType=holderType,
+ dealWithOptional=isOptional,
+ holderArgs=holderArgs)
+
+ if type.isUnion():
+ nullable = type.nullable()
+ if nullable:
+ type = type.inner
+
+ isOwningUnion = isMember or isCallbackReturnValue
+ unionArgumentObj = "${declName}" if isOwningUnion else "${holderName}"
+ if nullable:
+ # If we're owning, we're a Nullable, which hasn't been told it has
+ # a value. Otherwise we're an already-constructed Maybe.
+ unionArgumentObj += ".SetValue()" if isOwningUnion else ".ref()"
+
+ memberTypes = type.flatMemberTypes
+ names = []
+
+ interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes)
+ if len(interfaceMemberTypes) > 0:
+ interfaceObject = []
+ for memberType in interfaceMemberTypes:
+ name = getUnionMemberName(memberType)
+ interfaceObject.append(
+ CGGeneric("(failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext" %
+ (unionArgumentObj, name)))
+ names.append(name)
+ interfaceObject = CGWrapper(CGList(interfaceObject, " ||\n"),
+ pre="done = ", post=";\n\n", reindent=True)
+ else:
+ interfaceObject = None
+
+ sequenceObjectMemberTypes = filter(lambda t: t.isSequence(), memberTypes)
+ if len(sequenceObjectMemberTypes) > 0:
+ assert len(sequenceObjectMemberTypes) == 1
+ name = getUnionMemberName(sequenceObjectMemberTypes[0])
+ sequenceObject = CGGeneric(
+ "done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" %
+ (unionArgumentObj, name))
+ names.append(name)
+ else:
+ sequenceObject = None
+
+ dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes)
+ if len(dateObjectMemberTypes) > 0:
+ assert len(dateObjectMemberTypes) == 1
+ memberType = dateObjectMemberTypes[0]
+ name = getUnionMemberName(memberType)
+ dateObject = CGGeneric("%s.SetTo%s(cx, ${val});\n"
+ "done = true;\n" % (unionArgumentObj, name))
+ dateObject = CGIfWrapper(dateObject, "JS_ObjectIsDate(cx, argObj)")
+ names.append(name)
+ else:
+ dateObject = None
+
+ callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes)
+ if len(callbackMemberTypes) > 0:
+ assert len(callbackMemberTypes) == 1
+ memberType = callbackMemberTypes[0]
+ name = getUnionMemberName(memberType)
+ callbackObject = CGGeneric(
+ "done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" %
+ (unionArgumentObj, name))
+ names.append(name)
+ else:
+ callbackObject = None
+
+ dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes)
+ if len(dictionaryMemberTypes) > 0:
+ assert len(dictionaryMemberTypes) == 1
+ name = getUnionMemberName(dictionaryMemberTypes[0])
+ setDictionary = CGGeneric(
+ "done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" %
+ (unionArgumentObj, name))
+ names.append(name)
+ else:
+ setDictionary = None
+
+ mozMapMemberTypes = filter(lambda t: t.isMozMap(), memberTypes)
+ if len(mozMapMemberTypes) > 0:
+ assert len(mozMapMemberTypes) == 1
+ name = getUnionMemberName(mozMapMemberTypes[0])
+ mozMapObject = CGGeneric(
+ "done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" %
+ (unionArgumentObj, name))
+ names.append(name)
+ else:
+ mozMapObject = None
+
+ objectMemberTypes = filter(lambda t: t.isObject(), memberTypes)
+ if len(objectMemberTypes) > 0:
+ assert len(objectMemberTypes) == 1
+ # Very important to NOT construct a temporary Rooted here, since the
+ # SetToObject call can call a Rooted constructor and we need to keep
+ # stack discipline for Rooted.
+ object = CGGeneric("if (!%s.SetToObject(cx, &${val}.toObject(), ${passedToJSImpl})) {\n"
+ "%s"
+ "}\n"
+ "done = true;\n" % (unionArgumentObj, indent(exceptionCode)))
+ names.append(objectMemberTypes[0].name)
+ else:
+ object = None
+
+ hasObjectTypes = interfaceObject or sequenceObject or dateObject or callbackObject or object or mozMapObject
+ if hasObjectTypes:
+ # "object" is not distinguishable from other types
+ assert not object or not (interfaceObject or sequenceObject or dateObject or callbackObject or mozMapObject)
+ if sequenceObject or dateObject 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, dateObject, 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 dateObject:
+ templateBody.prepend(CGGeneric("JS::Rooted<JSObject*> argObj(cx, &${val}.toObject());\n"))
+
+ if mozMapObject:
+ templateBody = CGList([templateBody,
+ CGIfWrapper(mozMapObject, "!done")])
+
+ templateBody = CGIfWrapper(templateBody, "${val}.isObject()")
+ else:
+ templateBody = CGGeneric()
+
+ if setDictionary:
+ assert not object
+ templateBody = CGList([templateBody,
+ CGIfWrapper(setDictionary, "!done")])
+
+ stringTypes = [t for t in memberTypes if t.isString() or t.isEnum()]
+ numericTypes = [t for t in memberTypes if t.isNumeric()]
+ booleanTypes = [t for t in memberTypes if t.isBoolean()]
+ if stringTypes or numericTypes or booleanTypes:
+ assert len(stringTypes) <= 1
+ assert len(numericTypes) <= 1
+ assert len(booleanTypes) <= 1
+
+ # We will wrap all this stuff in a do { } while (0); so we
+ # can use "break" for flow control.
+ def getStringOrPrimitiveConversion(memberType):
+ name = getUnionMemberName(memberType)
+ return CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n"
+ "break;\n" % (unionArgumentObj, name))
+ other = CGList([])
+ stringConversion = map(getStringOrPrimitiveConversion, stringTypes)
+ numericConversion = map(getStringOrPrimitiveConversion, numericTypes)
+ booleanConversion = map(getStringOrPrimitiveConversion, 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 (0);\n")
+ if hasObjectTypes or setDictionary:
+ other = CGWrapper(CGIndenter(other), "{\n", post="}\n")
+ if object:
+ templateBody = CGElseChain([templateBody, other])
+ else:
+ other = CGWrapper(other, pre="if (!done) ")
+ templateBody = CGList([templateBody, other])
+ else:
+ assert templateBody.define() == ""
+ templateBody = other
+ else:
+ other = None
+
+ templateBody = CGWrapper(templateBody, pre="bool done = false, failed = false, tryNext;\n")
+ throw = CGGeneric(fill(
+ """
+ if (failed) {
+ $*{exceptionCode}
+ }
+ if (!done) {
+ ThrowErrorMessage(cx, MSG_NOT_IN_UNION, "${desc}", "${names}");
+ $*{exceptionCode}
+ }
+ """,
+ exceptionCode=exceptionCode,
+ desc=firstCap(sourceDescription),
+ names=", ".join(names)))
+
+ templateBody = CGWrapper(CGIndenter(CGList([templateBody, throw])), pre="{\n", post="}\n")
+
+ typeName = CGUnionStruct.unionTypeDecl(type, isOwningUnion)
+ argumentTypeName = typeName + "Argument"
+ if nullable:
+ typeName = "Nullable<" + typeName + " >"
+
+ def handleNull(templateBody, setToNullVar, extraConditionForNull=""):
+ nullTest = "%s${val}.isNullOrUndefined()" % extraConditionForNull
+ return CGIfElseWrapper(nullTest,
+ CGGeneric("%s.SetNull();\n" % setToNullVar),
+ templateBody)
+
+ if type.hasNullableType:
+ assert not nullable
+ # Make sure to handle a null default value here
+ if defaultValue and isinstance(defaultValue, IDLNullValue):
+ assert defaultValue.type == type
+ extraConditionForNull = "!(${haveValue}) || "
+ else:
+ extraConditionForNull = ""
+ templateBody = handleNull(templateBody, unionArgumentObj,
+ extraConditionForNull=extraConditionForNull)
+
+ declType = CGGeneric(typeName)
+ if isOwningUnion:
+ holderType = None
+ else:
+ holderType = CGGeneric(argumentTypeName)
+ if nullable:
+ holderType = CGTemplatedType("Maybe", holderType)
+
+ # If we're isOptional and not nullable the normal optional handling will
+ # handle lazy construction of our holder. If we're nullable and not
+ # owning we do it all by hand because we do not want our holder
+ # constructed if we're null. But if we're owning we don't have a
+ # holder anyway, so we can do the normal Optional codepath.
+ declLoc = "${declName}"
+ constructDecl = None
+ if nullable:
+ if isOptional and not isOwningUnion:
+ holderArgs = "${declName}.Value().SetValue()"
+ declType = CGTemplatedType("Optional", declType)
+ constructDecl = CGGeneric("${declName}.Construct();\n")
+ declLoc = "${declName}.Value()"
+ else:
+ holderArgs = "${declName}.SetValue()"
+ if holderType is not None:
+ constructHolder = CGGeneric("${holderName}.emplace(%s);\n" % holderArgs)
+ else:
+ constructHolder = None
+ # Don't need to pass those args when the holder is being constructed
+ holderArgs = None
+ else:
+ holderArgs = "${declName}"
+ constructHolder = None
+
+ if not isMember and isCallbackReturnValue:
+ declType = CGWrapper(declType, post="&")
+ declArgs = "aRetVal"
+ else:
+ declArgs = None
+
+ if defaultValue and not isinstance(defaultValue, IDLNullValue):
+ tag = defaultValue.type.tag()
+
+ if tag in numericSuffixes or tag is IDLType.Tags.bool:
+ defaultStr = getHandleDefault(defaultValue)
+ # Make sure we actually construct the thing inside the nullable.
+ value = declLoc + (".SetValue()" if nullable else "")
+ name = getUnionMemberName(defaultValue.type)
+ default = CGGeneric("%s.RawSetAs%s() = %s;\n" %
+ (value, name, defaultStr))
+ elif isinstance(defaultValue, IDLEmptySequenceValue):
+ name = getUnionMemberName(defaultValue.type)
+ # Make sure we actually construct the thing inside the nullable.
+ value = declLoc + (".SetValue()" if nullable else "")
+ # It's enough to set us to the right type; that will
+ # create an empty array, which is all we need here.
+ default = CGGeneric("%s.RawSetAs%s();\n" %
+ (value, name))
+ elif defaultValue.type.isEnum():
+ name = getUnionMemberName(defaultValue.type)
+ # Make sure we actually construct the thing inside the nullable.
+ value = declLoc + (".SetValue()" if nullable else "")
+ default = CGGeneric(
+ "%s.RawSetAs%s() = %s::%s;\n" %
+ (value, name,
+ defaultValue.type.inner.identifier.name,
+ getEnumValueName(defaultValue.value)))
+ else:
+ default = CGGeneric(
+ handleDefaultStringValue(
+ defaultValue, "%s.SetStringData" % unionArgumentObj) +
+ ";\n")
+
+ templateBody = CGIfElseWrapper("!(${haveValue})", default, templateBody)
+
+ templateBody = CGList([constructHolder, templateBody])
+
+ if nullable:
+ if defaultValue:
+ if isinstance(defaultValue, IDLNullValue):
+ extraConditionForNull = "!(${haveValue}) || "
+ else:
+ extraConditionForNull = "${haveValue} && "
+ else:
+ extraConditionForNull = ""
+ templateBody = handleNull(templateBody, declLoc,
+ extraConditionForNull=extraConditionForNull)
+ elif (not type.hasNullableType and defaultValue and
+ isinstance(defaultValue, IDLNullValue)):
+ 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)))
+ templateBody = CGIfElseWrapper("!(${haveValue})",
+ initDictionaryWithNull,
+ templateBody)
+
+ templateBody = CGList([constructDecl, templateBody])
+
+ return JSToNativeConversionInfo(templateBody.define(),
+ declType=declType,
+ declArgs=declArgs,
+ holderType=holderType,
+ holderArgs=holderArgs,
+ dealWithOptional=isOptional and (not nullable or isOwningUnion))
+
+ if type.isGeckoInterface():
+ assert not isEnforceRange and not isClamp
+
+ 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)
+
+ # 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.
+ #
+ # Finally, promises need to hold a strong ref because that's what
+ # Promise.resolve returns.
+ assert not descriptor.interface.isCallback()
+ isPromise = descriptor.interface.identifier.name == "Promise"
+ forceOwningType = isMember or isCallbackReturnValue or isPromise
+
+ 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 += 'static_assert(IsRefcounted<%s>::value, "We can only store refcounted classes.");' % typeName
+
+ if isPromise:
+ # 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.
+ # This is in theory a case the spec covers but in practice it
+ # really doesn't define behavior here because it doesn't define
+ # what Realm we're in after the callback returns, which is when
+ # the argument conversion happens. We will use the current
+ # compartment, which is the compartment of the callable (which
+ # may itself be a cross-compartment wrapper itself), which makes
+ # as much sense as anything else. In practice, such an API would
+ # once again be providing a Promise to signal completion of an
+ # operation, which would then not be exposed to anyone other than
+ # our own implementation code.
+ # 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. CheckedUnwrap 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>(NS_LITERAL_STRING("${sourceDescription}"));
+ return nullptr;
+ }
+ JSObject* unwrappedVal = js::CheckedUnwrap(&$${val}.toObject());
+ if (!unwrappedVal) {
+ // A slight lie, but not much of one, for a dead object wrapper.
+ aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}"));
+ return nullptr;
+ }
+ globalObj = js::GetGlobalForObjectCrossCompartment(unwrappedVal);
+ """,
+ sourceDescription=sourceDescription)
+ else:
+ getPromiseGlobal = ""
+
+ templateBody = fill(
+ """
+ { // Scope for our GlobalObject, FastErrorResult, JSAutoCompartment,
+ // etc.
+
+ JS::Rooted<JSObject*> globalObj(cx, JS::CurrentGlobalOrNull(cx));
+ $*{getPromiseGlobal}
+ JSAutoCompartment ac(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;
+ #ifdef SPIDERMONKEY_PROMISE
+ nsCOMPtr<nsIGlobalObject> global =
+ do_QueryInterface(promiseGlobal.GetAsSupports());
+ if (!global) {
+ promiseRv.Throw(NS_ERROR_UNEXPECTED);
+ promiseRv.MaybeSetPendingException(cx);
+ $*{exceptionCode}
+ }
+ $${declName} = Promise::Resolve(global, cx, valueToResolve,
+ promiseRv);
+ if (promiseRv.MaybeSetPendingException(cx)) {
+ $*{exceptionCode}
+ }
+ #else
+ JS::Handle<JSObject*> promiseCtor =
+ PromiseBinding::GetConstructorObjectHandle(cx);
+ if (!promiseCtor) {
+ $*{exceptionCode}
+ }
+ JS::Rooted<JS::Value> resolveThisv(cx, JS::ObjectValue(*promiseCtor));
+ JS::Rooted<JS::Value> resolveResult(cx);
+ Promise::Resolve(promiseGlobal, resolveThisv, valueToResolve,
+ &resolveResult, promiseRv);
+ if (promiseRv.MaybeSetPendingException(cx)) {
+ $*{exceptionCode}
+ }
+ nsresult unwrapRv = UNWRAP_OBJECT(Promise, &resolveResult.toObject(), $${declName});
+ if (NS_FAILED(unwrapRv)) { // Quite odd
+ promiseRv.Throw(unwrapRv);
+ promiseRv.MaybeSetPendingException(cx);
+ $*{exceptionCode}
+ }
+ #endif // SPIDERMONKEY_PROMISE
+ }
+ """,
+ getPromiseGlobal=getPromiseGlobal,
+ exceptionCode=exceptionCode)
+ elif not descriptor.interface.isConsequential() and 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:
+ # Either external, or new-binding non-castable. 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 + ">(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"
+
+ if isPromise:
+ if type.nullable():
+ codeToSetNull = "${declName} = nullptr;\n"
+ templateBody = CGIfElseWrapper(
+ "${val}.isNullOrUndefined()",
+ CGGeneric(codeToSetNull),
+ CGGeneric(templateBody)).define()
+ if isinstance(defaultValue, IDLNullValue):
+ templateBody = handleDefault(templateBody, codeToSetNull)
+ else:
+ assert defaultValue is None
+ else:
+ # 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
+ arrayType = CGGeneric(name)
+ declType = arrayType
+ 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())
+ template = wrapObjectTemplate(template, type, "${declName}.SetNull();\n",
+ failureCode)
+ if not isMember:
+ # This is a bit annoying. In a union we don't want to have a
+ # holder, since unions don't support that. But if we're optional we
+ # want to have a holder, so that the callee doesn't see
+ # Optional<RootedTypedArray<ArrayType> >. So do a holder if we're
+ # optional and use a RootedTypedArray otherwise.
+ if isOptional:
+ holderType = CGTemplatedType("TypedArrayRooter", arrayType)
+ # If our typed array 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("RootedTypedArray", 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.isDOMString() or type.isUSVString():
+ assert not isEnforceRange and not isClamp
+
+ 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"
+ nullBehavior = treatAs[treatNullAs]
+
+ def getConversionCode(varName):
+ normalizeCode = ""
+ if type.isUSVString():
+ normalizeCode = "NormalizeUSVString(cx, %s);\n" % varName
+
+ 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)" % varName
+ else:
+ defaultCode = handleDefaultStringValue(defaultValue,
+ "%s.Rebind" % varName)
+ return handleDefault(conversionCode, defaultCode + ";\n")
+
+ if isMember:
+ # Convert directly into the nsString member we have.
+ declType = CGGeneric("nsString")
+ return JSToNativeConversionInfo(
+ getConversionCode("${declName}"),
+ declType=declType,
+ dealWithOptional=isOptional)
+
+ if isOptional:
+ declType = "Optional<nsAString>"
+ holderType = CGGeneric("binding_detail::FakeString")
+ conversionCode = ("%s"
+ "${declName} = &${holderName};\n" %
+ getConversionCode("${holderName}"))
+ else:
+ declType = "binding_detail::FakeString"
+ 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
+
+ nullable = toStringBool(type.nullable())
+
+ conversionCode = fill("""
+ if (!ConvertJSValueToByteString(cx, $${val}, ${nullable}, $${declName})) {
+ $*{exceptionCode}
+ }
+ """,
+ nullable=nullable,
+ exceptionCode=exceptionCode)
+
+ if defaultValue is not None:
+ if isinstance(defaultValue, IDLNullValue):
+ assert(type.nullable())
+ defaultCode = "${declName}.SetIsVoid(true)"
+ else:
+ defaultCode = handleDefaultStringValue(defaultValue,
+ "${declName}.Rebind")
+ conversionCode = handleDefault(conversionCode, defaultCode + ";\n")
+
+ return JSToNativeConversionInfo(
+ conversionCode,
+ declType=CGGeneric("nsCString"),
+ dealWithOptional=isOptional)
+
+ if type.isEnum():
+ assert not isEnforceRange and not isClamp
+
+ 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=firstCap(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
+ 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
+
+ declArgs = None
+ if isMember in ("Variadic", "Sequence", "Dictionary", "MozMap"):
+ # 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`.
+ if not isinstance(descriptorProvider, Descriptor) or descriptorProvider.interface.isJSImplemented():
+ templateBody = fill(
+ """
+ if (($${passedToJSImpl}) && !CallerSubsumes($${val})) {
+ ThrowErrorMessage(cx, MSG_PERMISSION_DENIED_TO_PASS_ARG, "${sourceDescription}");
+ $*{exceptionCode}
+ }
+ """,
+ 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
+ return handleJSObjectType(type, isMember, failureCode, exceptionCode, sourceDescription)
+
+ if type.isDictionary():
+ # There are no nullable dictionaries
+ assert not type.nullable() or isCallbackReturnValue
+ # All optional dictionaries always have default values, so we
+ # should be able to assume not isOptional here.
+ assert not isOptional
+ # In the callback return value case we never have to worry
+ # about a default value; we always have a value.
+ assert not isCallbackReturnValue or defaultValue is None
+
+ typeName = CGDictionary.makeDictionaryName(type.unroll().inner)
+ if not isMember and not isCallbackReturnValue:
+ # Since we're not a member and not nullable or optional, no one will
+ # see our real type, so we can do the fast version of the dictionary
+ # that doesn't pre-initialize members.
+ typeName = "binding_detail::Fast" + typeName
+
+ declType = CGGeneric(typeName)
+
+ # We do manual default value handling here, because we
+ # actually do want a jsval, and we only handle 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, IDLNullValue))
+ val = "(${haveValue}) ? ${val} : JS::NullHandleValue"
+ else:
+ val = "${val}"
+
+ dictLoc = "${declName}"
+ if type.nullable():
+ dictLoc += ".SetValue()"
+
+ conversionCode = fill("""
+ if (!${dictLoc}.Init(cx, ${val}, "${desc}", $${passedToJSImpl})) {
+ $*{exceptionCode}
+ }
+ """,
+ dictLoc=dictLoc,
+ val=val,
+ desc=firstCap(sourceDescription),
+ exceptionCode=exceptionCode)
+
+ if failureCode is not None:
+ if isDefinitelyObject:
+ dictionaryTest = "IsObjectValueConvertibleToDictionary"
+ else:
+ dictionaryTest = "IsConvertibleToDictionary"
+
+ template = fill("""
+ { // scope for isConvertible
+ bool isConvertible;
+ if (!${testConvertible}(cx, ${val}, &isConvertible)) {
+ $*{exceptionCode}
+ }
+ if (!isConvertible) {
+ $*{failureCode}
+ }
+
+ $*{conversionCode}
+ }
+
+ """,
+ testConvertible=dictionaryTest,
+ val=val,
+ exceptionCode=exceptionCode,
+ failureCode=failureCode,
+ conversionCode=conversionCode)
+ else:
+ template = conversionCode
+
+ if type.nullable():
+ declType = CGTemplatedType("Nullable", declType)
+ template = CGIfElseWrapper("${val}.isNullOrUndefined()",
+ CGGeneric("${declName}.SetNull();\n"),
+ CGGeneric(template)).define()
+
+ # Dictionary arguments that might contain traceable things need to get
+ # traced
+ if not isMember and isCallbackReturnValue:
+ # Go ahead and just convert directly into our actual return value
+ declType = CGWrapper(declType, post="&")
+ declArgs = "aRetVal"
+ elif not isMember and typeNeedsRooting(type):
+ declType = CGTemplatedType("RootedDictionary", declType)
+ declArgs = "cx"
+ else:
+ declArgs = None
+
+ return JSToNativeConversionInfo(template, declType=declType,
+ declArgs=declArgs)
+
+ if type.isVoid():
+ assert not isOptional
+ # This one only happens for return values, and its easy: Just
+ # ignore the jsval.
+ return JSToNativeConversionInfo("")
+
+ if type.isDate():
+ assert not isEnforceRange and not isClamp
+
+ declType = CGGeneric("Date")
+ if type.nullable():
+ declType = CGTemplatedType("Nullable", declType)
+ dateVal = "${declName}.SetValue()"
+ else:
+ dateVal = "${declName}"
+
+ if failureCode is None:
+ notDate = ('ThrowErrorMessage(cx, MSG_NOT_DATE, "%s");\n'
+ "%s" % (firstCap(sourceDescription), exceptionCode))
+ else:
+ notDate = failureCode
+
+ conversion = fill(
+ """
+ JS::Rooted<JSObject*> possibleDateObject(cx, &$${val}.toObject());
+ { // scope for isDate
+ bool isDate;
+ if (!JS_ObjectIsDate(cx, possibleDateObject, &isDate)) {
+ $*{exceptionCode}
+ }
+ if (!isDate) {
+ $*{notDate}
+ }
+ if (!${dateVal}.SetTimeStamp(cx, possibleDateObject)) {
+ $*{exceptionCode}
+ }
+ }
+ """,
+ exceptionCode=exceptionCode,
+ dateVal=dateVal,
+ notDate=notDate)
+
+ conversion = wrapObjectTemplate(conversion, type,
+ "${declName}.SetNull();\n", notDate)
+ return JSToNativeConversionInfo(conversion,
+ declType=declType,
+ dealWithOptional=isOptional)
+
+ 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"
+
+ 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
+ template = fill("""
+ if (${nullCondition}) {
+ $${declName}.SetNull();
+ } else if (!ValueToPrimitive<${typeName}, ${conversionBehavior}>(cx, $${val}, &${writeLoc})) {
+ $*{exceptionCode}
+ }
+ """,
+ nullCondition=nullCondition,
+ typeName=typeName,
+ conversionBehavior=conversionBehavior,
+ 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}, &${writeLoc})) {
+ $*{exceptionCode}
+ }
+ """,
+ typeName=typeName,
+ conversionBehavior=conversionBehavior,
+ writeLoc=writeLoc,
+ exceptionCode=exceptionCode)
+ declType = CGGeneric(typeName)
+
+ if type.isFloat() and not type.isUnrestricted():
+ if lenientFloatCode is not None:
+ nonFiniteCode = lenientFloatCode
+ else:
+ nonFiniteCode = ('ThrowErrorMessage(cx, MSG_NOT_FINITE, "%s");\n'
+ "%s" % (firstCap(sourceDescription), exceptionCode))
+
+ # We're appending to an if-block brace, so strip trailing whitespace
+ # and add an extra space before the else.
+ template = template.rstrip()
+ template += fill("""
+ else if (!mozilla::IsFinite(${readLoc})) {
+ $*{nonFiniteCode}
+ }
+ """,
+ readLoc=readLoc,
+ nonFiniteCode=nonFiniteCode)
+
+ if (defaultValue is not None and
+ # We already handled IDLNullValue, so just deal with the other ones
+ not isinstance(defaultValue, IDLNullValue)):
+ tag = defaultValue.type.tag()
+ defaultStr = getHandleDefault(defaultValue)
+ template = CGIfElseWrapper(
+ "${haveValue}",
+ CGGeneric(template),
+ CGGeneric("%s = %s;\n" % (writeLoc, defaultStr))).define()
+
+ 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(true)" if value.value else "JS::BooleanValue(false)"
+ 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,
+ treatNullAs=self.argument.treatNullAs,
+ isEnforceRange=self.argument.enforceRange,
+ isClamp=self.argument.clamp,
+ 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("binding_detail::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()
+
+ # 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) {
+ ${elemType}& slot = *${declName}.AppendElement(mozilla::fallible);
+ """)
+ ).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):
+ # 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
+mozMapWrapLevel = 0
+
+
+def getWrapTemplateForType(type, descriptorProvider, result, successCode,
+ returnsNewObject, exceptionCode, typedArraysAreStructs,
+ 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 typedArraysAreStructs is true, then if the type is a typed array,
+ "result" is one of the dom::TypedArray 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, setter="setString")
+
+ def setObject(value, wrapAsType=None):
+ return _setValue(value, wrapAsType=wrapAsType, setter="setObject")
+
+ def setObjectOrNull(value, wrapAsType=None):
+ return _setValue(value, wrapAsType=wrapAsType, setter="setObjectOrNull")
+
+ def setUint32(value):
+ return _setValue(value, setter="setNumber")
+
+ def setDouble(value):
+ return _setValue("JS_NumberValue(%s)" % value)
+
+ def setBoolean(value):
+ return _setValue(value, setter="setBoolean")
+
+ def _setValue(value, wrapAsType=None, setter="set"):
+ """
+ Returns the code to set the jsval to value.
+
+ If wrapAsType is not None, then will wrap the resulting value using the
+ function that getMaybeWrapValueFuncForType(wrapAsType) returns.
+ Otherwise, no wrapping will be done.
+ """
+ if wrapAsType is None:
+ tail = successCode
+ else:
+ tail = fill(
+ """
+ if (!${maybeWrap}(cx, $${jsvalHandle})) {
+ $*{exceptionCode}
+ }
+ $*{successCode}
+ """,
+ maybeWrap=getMaybeWrapValueFuncForType(wrapAsType),
+ exceptionCode=exceptionCode,
+ successCode=successCode)
+ return ("${jsvalRef}.%s(%s);\n" % (setter, value)) + tail
+
+ def wrapAndSetPtr(wrapCall, failureCode=None):
+ """
+ Returns the code to set the jsval by calling "wrapCall". "failureCode"
+ is the code to run if calling "wrapCall" fails
+ """
+ if failureCode is None:
+ failureCode = exceptionCode
+ return fill(
+ """
+ if (!${wrapCall}) {
+ $*{failureCode}
+ }
+ $*{successCode}
+ """,
+ wrapCall=wrapCall,
+ failureCode=failureCode,
+ successCode=successCode)
+
+ if type is None or type.isVoid():
+ return (setUndefined(), True)
+
+ if (type.isSequence() or type.isMozMap()) and type.nullable():
+ # These are both wrapped in Nullable<>
+ recTemplate, recInfall = getWrapTemplateForType(type.inner, descriptorProvider,
+ "%s.Value()" % result, successCode,
+ returnsNewObject, exceptionCode,
+ typedArraysAreStructs)
+ 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",
+ 'typedArraysAreStructs': typedArraysAreStructs
+ })
+ 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 (0);
+ 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.isMozMap():
+ # Now do non-nullable MozMap. Our success code is just to break to
+ # where we define the property on the object. Note that we bump the
+ # mozMapWrapLevel around this call so that nested MozMap conversions
+ # will use different temp value names.
+ global mozMapWrapLevel
+ valueName = "mozMapValue%d" % mozMapWrapLevel
+ mozMapWrapLevel += 1
+ innerTemplate = wrapForType(
+ type.inner, descriptorProvider,
+ {
+ 'result': valueName,
+ 'successCode': "break;\n",
+ 'jsvalRef': "tmp",
+ 'jsvalHandle': "&tmp",
+ 'returnsNewObject': returnsNewObject,
+ 'exceptionCode': exceptionCode,
+ 'obj': "returnObj",
+ 'typedArraysAreStructs': typedArraysAreStructs
+ })
+ mozMapWrapLevel -= 1
+ code = fill(
+ """
+
+ nsTArray<nsString> keys;
+ ${result}.GetKeys(keys);
+ JS::Rooted<JSObject*> returnObj(cx, JS_NewPlainObject(cx));
+ if (!returnObj) {
+ $*{exceptionCode}
+ }
+ // Scope for 'tmp'
+ {
+ JS::Rooted<JS::Value> tmp(cx);
+ for (size_t idx = 0; idx < keys.Length(); ++idx) {
+ auto& ${valueName} = ${result}.Get(keys[idx]);
+ // 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 (0);
+ if (!JS_DefineUCProperty(cx, returnObj, keys[idx].get(),
+ keys[idx].Length(), tmp,
+ JSPROP_ENUMERATE)) {
+ $*{exceptionCode}
+ }
+ }
+ }
+ $*{set}
+ """,
+ result=result,
+ exceptionCode=exceptionCode,
+ valueName=valueName,
+ innerTemplate=innerTemplate,
+ set=setObject("*returnObj"))
+
+ return (code, False)
+
+ if type.isGeckoInterface() and not type.isCallbackInterface():
+ descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name)
+ if type.nullable():
+ 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:
+ # Hack: the "Promise" interface is OK to return from
+ # non-newobject things even when it's not wrappercached; that
+ # happens when using SpiderMonkey promises, and the WrapObject()
+ # method will just return the existing reflector, which is just
+ # not stored in a wrappercache.
+ if (not returnsNewObject and
+ descriptor.interface.identifier.name != "Promise"):
+ raise MethodNotNewObjectError(descriptor.interface.identifier.name)
+ wrapMethod = "WrapNewBindingNonWrapperCachedObject"
+ wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result
+ if isConstructorRetval:
+ wrapArgs += ", desiredProto"
+ wrap = "%s(%s)" % (wrapMethod, wrapArgs)
+ if not descriptor.hasXPConnectImpls:
+ # Can only fail to wrap as a new-binding object
+ # if they already threw an exception.
+ # XXX Assertion disabled for now, see bug 991271.
+ failed = ("MOZ_ASSERT(true || JS_IsExceptionPending(cx));\n" +
+ exceptionCode)
+ else:
+ if descriptor.notflattened:
+ raise TypeError("%s has XPConnect impls but not flattened; "
+ "fallback won't work correctly" %
+ descriptor.interface.identifier.name)
+ # Try old-style wrapping for bindings which might be XPConnect impls.
+ failed = wrapAndSetPtr("HandleNewBindingWrappingFailure(cx, ${obj}, %s, ${jsvalHandle})" % result)
+ 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.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.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():
+ wrapCode = setObject(
+ "*GetCallbackFromCallbackObject(%(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 typedArraysAreStructs)):
+ # See comments in GetOrCreateDOMReflector explaining why we need
+ # to wrap here.
+ if type.nullable():
+ toValue = "%s"
+ setter = setObjectOrNull
+ head = """if (%s) {
+ JS::ExposeObjectToActiveJS(%s);
+ }
+ """ % (result, result)
+ else:
+ toValue = "*%s"
+ setter = setObject
+ head = "JS::ExposeObjectToActiveJS(%s);\n" % result
+ # NB: setObject{,OrNull}(..., some-object-type) calls JS_WrapValue(), so is fallible
+ return (head + setter(toValue % result, wrapAsType=type), False)
+
+ if not (type.isUnion() or type.isPrimitive() or type.isDictionary() or
+ type.isDate() or
+ (type.isSpiderMonkeyInterface() and typedArraysAreStructs)):
+ raise TypeError("Need to learn to wrap %s" % type)
+
+ if type.nullable():
+ recTemplate, recInfal = getWrapTemplateForType(type.inner, descriptorProvider,
+ "%s.Value()" % result, successCode,
+ returnsNewObject, exceptionCode,
+ typedArraysAreStructs)
+ return ("if (%s.IsNull()) {\n" % result +
+ indent(setNull()) +
+ "}\n" +
+ recTemplate, recInfal)
+
+ if type.isSpiderMonkeyInterface():
+ assert typedArraysAreStructs
+ # 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)
+
+ if type.isDate():
+ return (wrapAndSetPtr("%s.ToDateObject(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('typedArraysAreStructs', 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
+ (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.isMozMap():
+ return typeMatchesLambda(type.inner, func)
+ if type.isUnion():
+ return any(typeMatchesLambda(t, func) for t in
+ type.unroll().flatMemberTypes)
+ if type.isDictionary():
+ return dictionaryMatchesLambda(type.inner, func)
+ return func(type)
+
+
+def dictionaryMatchesLambda(dictionary, func):
+ return (any(typeMatchesLambda(m.type, func) for m in dictionary.members) or
+ (dictionary.parent and dictionaryMatchesLambda(dictionary.parent, func)))
+
+
+# Whenever this is modified, please update CGNativeMember.getRetvalInfo as
+# needed to keep the types compatible.
+def getRetvalDeclarationForType(returnType, descriptorProvider,
+ isMember=False):
+ """
+ Returns a tuple containing five things:
+
+ 1) A CGThing for the type of the return value, or None if there is no need
+ for a return value.
+
+ 2) A value indicating the kind of ourparam to pass the value as. Valid
+ options are None to not pass as an out param at all, "ref" (to pass a
+ reference as an out param), and "ptr" (to pass a pointer as an out
+ param).
+
+ 3) A CGThing for a tracer for the return value, or None if no tracing is
+ needed.
+
+ 4) An argument string to pass to the retval declaration
+ constructor or None if there are no arguments.
+
+ 5) The name of a function that needs to be called with the return value
+ before using it, or None if no function needs to be called.
+ """
+ if returnType is None or returnType.isVoid():
+ # Nothing to declare
+ return None, None, None, None, None
+ if returnType.isPrimitive() and returnType.tag() in builtinNames:
+ result = CGGeneric(builtinNames[returnType.tag()])
+ if returnType.nullable():
+ result = CGTemplatedType("Nullable", result)
+ return result, None, None, None, None
+ if returnType.isDOMString() or returnType.isUSVString():
+ if isMember:
+ return CGGeneric("nsString"), "ref", None, None, None
+ return CGGeneric("DOMString"), "ref", None, None, None
+ if returnType.isByteString():
+ return CGGeneric("nsCString"), "ref", None, None, None
+ if returnType.isEnum():
+ result = CGGeneric(returnType.unroll().inner.identifier.name)
+ if returnType.nullable():
+ result = CGTemplatedType("Nullable", result)
+ return result, None, None, None, None
+ if returnType.isGeckoInterface():
+ result = CGGeneric(descriptorProvider.getDescriptor(
+ returnType.unroll().inner.identifier.name).nativeType)
+ conversion = None
+ if isMember:
+ result = CGGeneric("StrongPtrForMember<%s>::Type" % result.define())
+ else:
+ conversion = CGGeneric("StrongOrRawPtr<%s>" % result.define())
+ 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.isMozMap():
+ nullable = returnType.nullable()
+ if nullable:
+ returnType = returnType.inner
+ result, _, _, _, _ = getRetvalDeclarationForType(returnType.inner,
+ descriptorProvider,
+ isMember="MozMap")
+ # While we have our inner type, set up our rooter, if needed
+ if not isMember and typeNeedsRooting(returnType):
+ rooter = CGGeneric("MozMapRooter<%s> resultRooter(cx, &result);\n" %
+ result.define())
+ else:
+ rooter = None
+ result = CGTemplatedType("MozMap", 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
+ if returnType.isDate():
+ result = CGGeneric("Date")
+ if returnType.nullable():
+ result = CGTemplatedType("Nullable", result)
+ return result, None, None, None, 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)))
+
+
+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.
+
+ needsSubjectPrincipal is a boolean indicating whether the call should
+ receive the subject nsIPrincipal as argument.
+
+ needsCallerType is a boolean indicating whether the call should receive
+ a PrincipalType for the caller.
+
+ isFallible is a boolean indicating whether the call should be fallible.
+
+ resultVar: If the returnType is not void, then the result of the call is
+ stored in a C++ variable named by resultVar. The caller is responsible for
+ declaring the result variable. If the caller doesn't care about the result
+ value, resultVar can be omitted.
+ """
+ def __init__(self, isFallible, needsSubjectPrincipal, needsCallerType,
+ arguments, argsPre, returnType, extendedAttributes, descriptor,
+ nativeMethodName, static, object="self", argsPost=[],
+ resultVar=None):
+ 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.isMozMap():
+ 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()) and not a.type.nullable()) or
+ a.type.isDOMString()):
+ arg = CGWrapper(arg, pre="NonNullHelper(", post=")")
+ args.append(arg)
+
+ needResultDecl = False
+
+ # 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))
+
+ if needsSubjectPrincipal:
+ args.append(CGGeneric("subjectPrincipal"))
+
+ if needsCallerType:
+ args.append(CGGeneric("callerType"))
+
+ if isFallible:
+ args.append(CGGeneric("rv"))
+ args.extend(CGGeneric(arg) for arg in argsPost)
+
+ # Build up our actual call
+ self.cgRoot = CGList([])
+
+ call = CGGeneric(nativeMethodName)
+ if not static:
+ call = CGWrapper(call, pre="%s->" % object)
+ call = CGList([call, CGWrapper(args, pre="(", post=")")])
+ if 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 resultRooter is not None:
+ self.cgRoot.prepend(resultRooter)
+ 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.prepend(CGWrapper(result, post=";\n"))
+ if resultOutParam is None:
+ call = CGWrapper(call, pre=resultVar + " = ")
+ elif result is not None:
+ assert resultOutParam is None
+ call = CGWrapper(call, pre=resultVar + " = ")
+
+ call = CGWrapper(call, post=";\n")
+ self.cgRoot.append(call)
+
+ if needsSubjectPrincipal:
+ getPrincipal = dedent(
+ """
+ JSCompartment* compartment = js::GetContextCompartment(cx);
+ MOZ_ASSERT(compartment);
+ JSPrincipals* principals = JS_GetCompartmentPrincipals(compartment);
+ """)
+
+ if descriptor.interface.isExposedInAnyWorker():
+ self.cgRoot.prepend(CGGeneric(fill(
+ """
+ Maybe<nsIPrincipal*> subjectPrincipal;
+ if (NS_IsMainThread()) {
+ $*{getPrincipal}
+ subjectPrincipal.emplace(nsJSPrincipals::get(principals));
+ }
+ """,
+ getPrincipal=getPrincipal)))
+ else:
+ self.cgRoot.prepend(CGGeneric(fill(
+ """
+ $*{getPrincipal}
+ // Initializing a nonnull is pretty darn annoying...
+ NonNull<nsIPrincipal> subjectPrincipal;
+ subjectPrincipal = static_cast<nsIPrincipal*>(nsJSPrincipals::get(principals));
+ """,
+ getPrincipal=getPrincipal)))
+
+ if needsCallerType:
+ # Note that we do not want to use
+ # IsCallerChrome/ThreadsafeIsCallerChrome directly because those
+ # will pull in the check for UniversalXPConnect, which we ideally
+ # don't want to have in the new thing we're doing here. If not
+ # NS_IsMainThread(), though, we'll go ahead and call
+ # ThreasafeIsCallerChrome(), since that won't mess with
+ # UnivesalXPConnect and we don't want to worry about the right
+ # worker includes here.
+ callerCheck = CGGeneric("callerType = nsContentUtils::IsSystemPrincipal(nsContentUtils::SubjectPrincipal()) ? CallerType::System : CallerType::NonSystem;\n")
+ if descriptor.interface.isExposedInAnyWorker():
+ callerCheck = CGIfElseWrapper(
+ "NS_IsMainThread()",
+ callerCheck,
+ CGGeneric("callerType = nsContentUtils::ThreadsafeIsCallerChrome() ? CallerType::System : CallerType::NonSystem;\n"));
+ self.cgRoot.prepend(callerCheck)
+ self.cgRoot.prepend(CGGeneric("CallerType callerType;\n"))
+
+ if isFallible:
+ self.cgRoot.prepend(CGGeneric("binding_detail::FastErrorResult rv;\n"))
+ self.cgRoot.append(CGGeneric(dedent(
+ """
+ if (MOZ_UNLIKELY(rv.MaybeSetPendingException(cx))) {
+ return false;
+ }
+ """)))
+
+ self.cgRoot.append(CGGeneric("MOZ_ASSERT(!JS_IsExceptionPending(cx));\n"))
+
+ def define(self):
+ return self.cgRoot.define()
+
+
+def getUnionMemberName(type):
+ if type.isGeckoInterface():
+ return type.inner.identifier.name
+ if type.isEnum():
+ return type.inner.identifier.name
+ return type.name
+
+
+class MethodNotNewObjectError(Exception):
+ def __init__(self, typename):
+ self.typename = typename
+
+# 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
+mapWrapLevel = 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.isMozMap():
+ origValue = value
+ origType = type
+ if type.nullable():
+ type = type.inner
+ value = "%s.Value()" % value
+ global mapWrapLevel
+ key = "mapName%d" % mapWrapLevel
+ mapWrapLevel += 1
+ wrapElement = wrapTypeIntoCurrentCompartment(type.inner,
+ "%s.Get(%sKeys[%sIndex])" % (value, key, key))
+ mapWrapLevel -= 1
+ if not wrapElement:
+ return None
+ wrapCode = CGWrapper(CGIndenter(wrapElement),
+ pre=("""
+ nsTArray<nsString> %sKeys;
+ %s.GetKeys(%sKeys);
+ for (uint32_t %sIndex = 0; %sIndex < %sKeys.Length(); ++%sIndex) {
+ """ % (key, value, key, key, key, key, key)),
+ post="}\n")
+ if origType.nullable():
+ wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % origValue)
+ return wrapCode
+
+ if type.isDictionary():
+ assert not type.nullable()
+ myDict = type.inner
+ memberWraps = []
+ while myDict:
+ for member in myDict.members:
+ memberWrap = wrapArgIntoCurrentCompartment(
+ member,
+ "%s.%s" % (value, CGDictionary.makeMemberName(member.identifier.name)))
+ if memberWrap:
+ memberWraps.append(memberWrap)
+ myDict = myDict.parent
+ return CGList(memberWraps) if len(memberWraps) != 0 else None
+
+ if type.isUnion():
+ memberWraps = []
+ if type.nullable():
+ type = type.inner
+ value = "%s.Value()" % value
+ for member in type.flatMemberTypes:
+ memberName = getUnionMemberName(member)
+ memberWrap = wrapTypeIntoCurrentCompartment(
+ member, "%s.GetAs%s()" % (value, memberName))
+ if memberWrap:
+ memberWrap = CGIfWrapper(
+ memberWrap, "%s.Is%s()" % (value, memberName))
+ memberWraps.append(memberWrap)
+ return CGList(memberWraps, "else ") if len(memberWraps) != 0 else None
+
+ if (type.isString() or type.isPrimitive() or type.isEnum() or
+ type.isGeckoInterface() or type.isCallback() or type.isDate()):
+ # 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.
+ """
+ # 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):
+ 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
+ self.extendedAttributes = descriptor.getExtendedAttributes(idlNode,
+ getter=getter,
+ setter=setter)
+ self.arguments = arguments
+ self.argCount = len(arguments)
+ self.isConstructor = isConstructor
+ cgThings = []
+
+ # Here, we check if the current getter, setter, method, interface or
+ # inherited interfaces have the UnsafeInPrerendering extended attribute
+ # and if so, we add a check to make sure it is safe.
+ if (idlNode.getExtendedAttribute("UnsafeInPrerendering") or
+ descriptor.interface.getExtendedAttribute("UnsafeInPrerendering") or
+ any(i.getExtendedAttribute("UnsafeInPrerendering")
+ for i in descriptor.interface.getInheritedInterfaces())):
+ cgThings.append(CGGeneric(dedent(
+ """
+ if (!mozilla::dom::EnforceNotInPrerendering(cx, obj)) {
+ // Return false from the JSNative in order to trigger
+ // an uncatchable exception.
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+ return false;
+ }
+ """)))
+
+ deprecated = (idlNode.getExtendedAttribute("Deprecated") or
+ (idlNode.isStatic() and descriptor.interface.getExtendedAttribute("Deprecated")))
+ if deprecated:
+ cgThings.append(CGGeneric(dedent(
+ """
+ DeprecationWarning(cx, obj, nsIDocument::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")
+
+ # Hack for making Promise.prototype.then work well over Xrays.
+ if (not idlNode.isStatic() and
+ descriptor.name == "Promise" and
+ idlNode.isMethod() and
+ idlNode.identifier.name == "then"):
+ cgThings.append(CGGeneric(dedent(
+ """
+ JS::Rooted<JSObject*> calleeGlobal(cx, xpc::XrayAwareCalleeGlobal(&args.callee()));
+ """)))
+ argsPre.append("calleeGlobal")
+
+ needsUnwrap = False
+ argsPost = []
+ if isConstructor:
+ if descriptor.name == "Promise":
+ # Hack for Promise for now: pass in our desired proto so the
+ # implementation can create the reflector with the right proto.
+ argsPost.append("desiredProto")
+ # Also, we do not want to enter the content compartment when the
+ # Promise constructor is called via Xrays, because we want to
+ # create our callback functions that we will hand to our caller
+ # in the Xray compartment. The reason we want to do that is the
+ # following situation, over Xrays:
+ #
+ # contentWindow.Promise.race([Promise.resolve(5)])
+ #
+ # Ideally this would work. Internally, race() does a
+ # contentWindow.Promise.resolve() on everything in the array.
+ # Per spec, to support subclassing,
+ # contentWindow.Promise.resolve has to do:
+ #
+ # var resolve, reject;
+ # var p = new contentWindow.Promise(function(a, b) {
+ # resolve = a;
+ # reject = b;
+ # });
+ # resolve(arg);
+ # return p;
+ #
+ # where "arg" is, in this case, the chrome-side return value of
+ # Promise.resolve(5). But if the "resolve" function in that
+ # case were created in the content compartment, then calling it
+ # would wrap "arg" in an opaque wrapper, and that function tries
+ # to get .then off the argument, which would throw. So we need
+ # to create the "resolve" function in the chrome compartment,
+ # and hence want to be running the entire Promise constructor
+ # (which creates that function) in the chrome compartment in
+ # this case. So don't set needsUnwrap here.
+ else:
+ needsUnwrap = True
+ needsUnwrappedVar = False
+ unwrappedVar = "obj"
+ elif descriptor.interface.isJSImplemented():
+ if not idlNode.isStatic():
+ needsUnwrap = True
+ needsUnwrappedVar = True
+ argsPost.append("js::GetObjectCompartment(unwrappedObj ? *unwrappedObj : obj)")
+ elif needScopeObject(returnType, arguments, self.extendedAttributes,
+ descriptor.wrapperCache, True,
+ idlNode.getExtendedAttribute("StoreInSlot")):
+ needsUnwrap = True
+ needsUnwrappedVar = True
+ argsPre.append("unwrappedObj ? *unwrappedObj : obj")
+
+ if idlNode.isStatic() and not isConstructor and descriptor.name == "Promise":
+ # Hack for Promise for now: pass in the "this" value to
+ # Promise static methods.
+ argsPre.append("args.thisv()")
+
+ 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()")
+
+ ourName = "%s.%s" % (descriptor.interface.identifier.name,
+ idlNode.identifier.name)
+ if idlNode.isMethod():
+ argDescription = "argument %(index)d of " + ourName
+ elif setter:
+ argDescription = "value being assigned to %s" % ourName
+ 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(
+ """
+ ${obj} = js::CheckedUnwrap(${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<JSAutoCompartment> ac;\n"))
+ xraySteps.append(CGGeneric("ac.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 this is a method that was generated by a maplike/setlike
+ # interface, use the maplike/setlike generator to fill in the body.
+ # Otherwise, use CGCallGenerator to call the native method.
+ if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod():
+ if (idlNode.maplikeOrSetlikeOrIterable.isMaplike() or
+ idlNode.maplikeOrSetlikeOrIterable.isSetlike()):
+ cgThings.append(CGMaplikeOrSetlikeMethodGenerator(descriptor,
+ idlNode.maplikeOrSetlikeOrIterable,
+ idlNode.identifier.name))
+ else:
+ cgThings.append(CGIterableMethodGenerator(descriptor,
+ idlNode.maplikeOrSetlikeOrIterable,
+ idlNode.identifier.name))
+ else:
+ cgThings.append(CGCallGenerator(
+ self.isFallible(),
+ idlNode.getExtendedAttribute('NeedsSubjectPrincipal'),
+ needsCallerType(idlNode),
+ self.getArguments(), argsPre, returnType,
+ self.extendedAttributes, descriptor,
+ nativeMethodName,
+ static, argsPost=argsPost, resultVar=resultVar))
+
+ if useCounterName:
+ # Generate a telemetry call for when [UseCounter] is used.
+ code = "SetDocumentAndPageUseCounter(cx, obj, eUseCounter_%s);\n" % useCounterName
+ cgThings.append(CGGeneric(code))
+
+ self.cgRoot = CGList(cgThings)
+
+ def getArguments(self):
+ return [(a, "arg" + str(i)) for i, a in enumerate(self.arguments)]
+
+ def isFallible(self):
+ return 'infallible' not in self.extendedAttributes
+
+ def wrap_return_value(self):
+ wrapCode = ""
+
+ returnsNewObject = memberReturnsNewObject(self.idlNode)
+ if (returnsNewObject and
+ self.returnType.isGeckoInterface()):
+ wrapCode += dedent(
+ """
+ static_assert(!IsPointer<decltype(result)>::value,
+ "NewObject implies that we need to keep the object alive with a strong reference.");
+ """)
+
+ setSlot = self.idlNode.isAttr() and self.idlNode.slotIndices is not None
+ if 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 setSlot else "obj"
+ }
+ try:
+ wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues)
+ except MethodNotNewObjectError, err:
+ assert not returnsNewObject
+ raise TypeError("%s being returned from non-NewObject method or property %s.%s" %
+ (err.typename,
+ self.descriptor.interface.identifier.name,
+ self.idlNode.identifier.name))
+ if setSlot:
+ # 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::ThreadsafeIsCallerChrome()) {
+ 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:
+ conversionScope = "isXray ? obj : slotStorage"
+ else:
+ conversionScope = "slotStorage"
+
+ wrapCode = fill(
+ """
+ {
+ JS::Rooted<JSObject*> conversionScope(cx, ${conversionScope});
+ JSAutoCompartment ac(cx, conversionScope);
+ do { // block we break out of when done wrapping
+ $*{wrapCode}
+ } while (0);
+ $*{postConversionSteps}
+ }
+ { // And now store things in the compartment of our slotStorage.
+ JSAutoCompartment ac(cx, slotStorage);
+ $*{slotStorageSteps}
+ }
+ // And now make sure args.rval() is in the caller compartment
+ return ${maybeWrap}(cx, args.rval());
+ """,
+ conversionScope=conversionScope,
+ wrapCode=wrapCode,
+ postConversionSteps=postConversionSteps,
+ slotStorageSteps=slotStorageSteps,
+ maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type))
+ return wrapCode
+
+ def define(self):
+ return (self.cgRoot.define() + self.wrap_return_value())
+
+
+class CGSwitch(CGList):
+ """
+ A class to generate code for a switch statement.
+
+ Takes three constructor arguments: an expression, a list of cases,
+ and an optional default.
+
+ Each case is a CGCase. The default is a CGThing for the body of
+ the default case, if any.
+ """
+ def __init__(self, expression, cases, default=None):
+ CGList.__init__(self, [CGIndenter(c) for c in cases])
+ self.prepend(CGGeneric("switch (" + expression + ") {\n"))
+ if default is not None:
+ self.append(
+ CGIndenter(
+ CGWrapper(
+ CGIndenter(default),
+ pre="default: {\n",
+ post=" break;\n}\n")))
+
+ self.append(CGGeneric("}\n"))
+
+
+class CGCase(CGList):
+ """
+ A class to generate code for a case statement.
+
+ Takes three constructor arguments: an expression, a CGThing for
+ the body (allowed to be None if there is no body), and an optional
+ argument (defaulting to False) for whether to fall through.
+ """
+ def __init__(self, expression, body, fallThrough=False):
+ CGList.__init__(self, [])
+ self.append(CGGeneric("case " + expression + ": {\n"))
+ bodyList = CGList([body])
+ if fallThrough:
+ bodyList.append(CGGeneric("MOZ_FALLTHROUGH;\n"))
+ else:
+ bodyList.append(CGGeneric("break;\n"))
+ self.append(CGIndenter(bodyList))
+ self.append(CGGeneric("}\n"))
+
+
+class CGMethodCall(CGThing):
+ """
+ A class to generate selection of a method signature from a set of
+ signatures and generation of a call to that signature.
+ """
+ def __init__(self, nativeMethodName, static, descriptor, method,
+ isConstructor=False, constructorName=None):
+ CGThing.__init__(self)
+
+ if isConstructor:
+ assert constructorName is not None
+ methodName = constructorName
+ else:
+ methodName = "%s.%s" % (descriptor.interface.identifier.name, method.identifier.name)
+ argDesc = "argument %d of " + methodName
+
+ if method.getExtendedAttribute("UseCounter"):
+ useCounterName = methodName.replace(".", "_")
+ else:
+ useCounterName = None
+
+ if method.isStatic():
+ nativeType = descriptor.nativeType
+ staticTypeOverride = PropertyDefiner.getStringAttr(method, "StaticClassOverride")
+ if (staticTypeOverride):
+ nativeType = staticTypeOverride
+ nativeMethodName = "%s::%s" % (nativeType, nativeMethodName)
+
+ def requiredArgCount(signature):
+ arguments = signature[1]
+ if len(arguments) == 0:
+ return 0
+ requiredArgs = len(arguments)
+ while requiredArgs and arguments[requiredArgs-1].optional:
+ requiredArgs -= 1
+ return requiredArgs
+
+ def getPerSignatureCall(signature, argConversionStartsAt=0):
+ return CGPerSignatureCall(signature[0], signature[1],
+ nativeMethodName, static, descriptor,
+ method,
+ argConversionStartsAt=argConversionStartsAt,
+ isConstructor=isConstructor,
+ useCounterName=useCounterName)
+
+ signatures = method.signatures()
+ if len(signatures) == 1:
+ # Special case: we can just do a per-signature method call
+ # here for our one signature and not worry about switching
+ # on anything.
+ signature = signatures[0]
+ self.cgRoot = CGList([getPerSignatureCall(signature)])
+ requiredArgs = requiredArgCount(signature)
+
+ # Skip required arguments check for maplike/setlike interfaces, as
+ # they can have arguments which are not passed, and are treated as
+ # if undefined had been explicitly passed.
+ if requiredArgs > 0 and not method.isMaplikeOrSetlikeOrIterableMethod():
+ code = fill(
+ """
+ if (MOZ_UNLIKELY(args.length() < ${requiredArgs})) {
+ return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${methodName}");
+ }
+ """,
+ requiredArgs=requiredArgs,
+ methodName=methodName)
+ self.cgRoot.prepend(CGGeneric(code))
+ return
+
+ # Need to find the right overload
+ maxArgCount = method.maxArgCount
+ allowedArgCounts = method.allowedArgCounts
+
+ argCountCases = []
+ for argCountIdx, argCount in enumerate(allowedArgCounts):
+ possibleSignatures = method.signaturesForArgCount(argCount)
+
+ # Try to optimize away cases when the next argCount in the list
+ # will have the same code as us; if it does, we can fall through to
+ # that case.
+ if argCountIdx+1 < len(allowedArgCounts):
+ nextPossibleSignatures = method.signaturesForArgCount(allowedArgCounts[argCountIdx+1])
+ else:
+ nextPossibleSignatures = None
+ if possibleSignatures == nextPossibleSignatures:
+ # Same set of signatures means we better have the same
+ # distinguishing index. So we can in fact just fall through to
+ # the next case here.
+ assert (len(possibleSignatures) == 1 or
+ (method.distinguishingIndexForArgCount(argCount) ==
+ method.distinguishingIndexForArgCount(allowedArgCounts[argCountIdx+1])))
+ argCountCases.append(CGCase(str(argCount), None, True))
+ continue
+
+ if len(possibleSignatures) == 1:
+ # easy case!
+ signature = possibleSignatures[0]
+ argCountCases.append(
+ CGCase(str(argCount), getPerSignatureCall(signature)))
+ continue
+
+ distinguishingIndex = method.distinguishingIndexForArgCount(argCount)
+
+ def distinguishingArgument(signature):
+ args = signature[1]
+ if distinguishingIndex < len(args):
+ return args[distinguishingIndex]
+ assert args[-1].variadic
+ return args[-1]
+
+ def distinguishingType(signature):
+ return distinguishingArgument(signature).type
+
+ for sig in possibleSignatures:
+ # We should not have "any" args at distinguishingIndex,
+ # since we have multiple possible signatures remaining,
+ # but "any" is never distinguishable from anything else.
+ assert not distinguishingType(sig).isAny()
+ # We can't handle unions at the distinguishing index.
+ if distinguishingType(sig).isUnion():
+ raise TypeError("No support for unions as distinguishing "
+ "arguments yet: %s" %
+ distinguishingArgument(sig).location)
+ # We don't support variadics as the distinguishingArgument yet.
+ # If you want to add support, consider this case:
+ #
+ # void(long... foo);
+ # void(long bar, Int32Array baz);
+ #
+ # in which we have to convert argument 0 to long before picking
+ # an overload... but all the variadic stuff needs to go into a
+ # single array in case we pick that overload, so we have to have
+ # machinery for converting argument 0 to long and then either
+ # placing it in the variadic bit or not. Or something. We may
+ # be able to loosen this restriction if the variadic arg is in
+ # fact at distinguishingIndex, perhaps. Would need to
+ # double-check.
+ if distinguishingArgument(sig).variadic:
+ raise TypeError("No support for variadics as distinguishing "
+ "arguments yet: %s" %
+ distinguishingArgument(sig).location)
+
+ # Convert all our arguments up to the distinguishing index.
+ # Doesn't matter which of the possible signatures we use, since
+ # they all have the same types up to that point; just use
+ # possibleSignatures[0]
+ caseBody = [CGArgumentConverter(possibleSignatures[0][1][i],
+ i, descriptor,
+ argDesc % (i + 1), method)
+ for i in range(0, distinguishingIndex)]
+
+ # Select the right overload from our set.
+ distinguishingArg = "args[%d]" % distinguishingIndex
+
+ def tryCall(signature, indent, isDefinitelyObject=False,
+ isNullOrUndefined=False):
+ assert not isDefinitelyObject or not isNullOrUndefined
+ assert isDefinitelyObject or isNullOrUndefined
+ if isDefinitelyObject:
+ failureCode = "break;\n"
+ else:
+ failureCode = None
+ type = distinguishingType(signature)
+ # The argument at index distinguishingIndex can't possibly be
+ # unset here, because we've already checked that argc is large
+ # enough that we can examine this argument. But note that we
+ # still want to claim that optional arguments are optional, in
+ # case undefined was passed in.
+ argIsOptional = distinguishingArgument(signature).canHaveMissingValue()
+ testCode = instantiateJSToNativeConversion(
+ getJSToNativeConversionInfo(type, descriptor,
+ failureCode=failureCode,
+ isDefinitelyObject=isDefinitelyObject,
+ isNullOrUndefined=isNullOrUndefined,
+ isOptional=argIsOptional,
+ sourceDescription=(argDesc % (distinguishingIndex + 1))),
+ {
+ "declName": "arg%d" % distinguishingIndex,
+ "holderName": ("arg%d" % distinguishingIndex) + "_holder",
+ "val": distinguishingArg,
+ "obj": "obj",
+ "haveValue": "args.hasDefined(%d)" % distinguishingIndex,
+ "passedToJSImpl": toStringBool(isJSImplementedDescriptor(descriptor))
+ },
+ checkForValue=argIsOptional)
+ caseBody.append(CGIndenter(testCode, indent))
+
+ # If we got this far, we know we unwrapped to the right
+ # C++ type, so just do the call. Start conversion with
+ # distinguishingIndex + 1, since we already converted
+ # distinguishingIndex.
+ caseBody.append(CGIndenter(
+ getPerSignatureCall(signature, distinguishingIndex + 1),
+ indent))
+
+ def hasConditionalConversion(type):
+ """
+ Return whether the argument conversion for this type will be
+ conditional on the type of incoming JS value. For example, for
+ interface types the conversion is conditional on the incoming
+ value being isObject().
+
+ For the types for which this returns false, we do not have to
+ output extra isUndefined() or isNullOrUndefined() cases, because
+ null/undefined values will just fall through into our
+ unconditional conversion.
+ """
+ if type.isString() or type.isEnum():
+ return False
+ if type.isBoolean():
+ distinguishingTypes = (distinguishingType(s) for s in
+ possibleSignatures)
+ return any(t.isString() or t.isEnum() or t.isNumeric()
+ for t in distinguishingTypes)
+ if type.isNumeric():
+ distinguishingTypes = (distinguishingType(s) for s in
+ possibleSignatures)
+ return any(t.isString() or t.isEnum()
+ for t in distinguishingTypes)
+ return True
+
+ def needsNullOrUndefinedCase(type):
+ """
+ Return true if the type needs a special isNullOrUndefined() case
+ """
+ return ((type.nullable() and
+ hasConditionalConversion(type)) or
+ type.isDictionary())
+
+ # First check for undefined and optional distinguishing arguments
+ # and output a special branch for that case. Note that we don't
+ # use distinguishingArgument here because we actualy want to
+ # exclude variadic arguments. Also note that we skip this check if
+ # we plan to output a isNullOrUndefined() special case for this
+ # argument anyway, since that will subsume our isUndefined() check.
+ # This is safe, because there can be at most one nullable
+ # distinguishing argument, so if we're it we'll definitely get
+ # picked up by the nullable handling. Also, we can skip this check
+ # if the argument has an unconditional conversion later on.
+ undefSigs = [s for s in possibleSignatures if
+ distinguishingIndex < len(s[1]) and
+ s[1][distinguishingIndex].optional and
+ hasConditionalConversion(s[1][distinguishingIndex].type) and
+ not needsNullOrUndefinedCase(s[1][distinguishingIndex].type)]
+ # Can't have multiple signatures with an optional argument at the
+ # same index.
+ assert len(undefSigs) < 2
+ if len(undefSigs) > 0:
+ caseBody.append(CGGeneric("if (%s.isUndefined()) {\n" %
+ distinguishingArg))
+ tryCall(undefSigs[0], 2, isNullOrUndefined=True)
+ caseBody.append(CGGeneric("}\n"))
+
+ # Next, check for null or undefined. That means looking for
+ # nullable arguments at the distinguishing index and outputting a
+ # separate branch for them. But if the nullable argument has an
+ # unconditional conversion, we don't need to do that. The reason
+ # for that is that at most one argument at the distinguishing index
+ # is nullable (since two nullable arguments are not
+ # distinguishable), and null/undefined values will always fall
+ # through to the unconditional conversion we have, if any, since
+ # they will fail whatever the conditions on the input value are for
+ # our other conversions.
+ nullOrUndefSigs = [s for s in possibleSignatures
+ if needsNullOrUndefinedCase(distinguishingType(s))]
+ # Can't have multiple nullable types here
+ assert len(nullOrUndefSigs) < 2
+ if len(nullOrUndefSigs) > 0:
+ caseBody.append(CGGeneric("if (%s.isNullOrUndefined()) {\n" %
+ distinguishingArg))
+ tryCall(nullOrUndefSigs[0], 2, isNullOrUndefined=True)
+ caseBody.append(CGGeneric("}\n"))
+
+ # Now check for distinguishingArg being various kinds of objects.
+ # The spec says to check for the following things in order:
+ # 1) A platform object that's not a platform array object, being
+ # passed to an interface or "object" arg.
+ # 2) A Date object being passed to a Date or "object" arg.
+ # 3) A RegExp object being passed to a RegExp or "object" arg.
+ # 4) A callable object being passed to a callback or "object" arg.
+ # 5) An iterable object being passed to a sequence arg.
+ # 6) Any non-Date and non-RegExp 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 typed arrays and arraybuffers) 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 Date
+ objectSigs.extend(s for s in possibleSignatures
+ if distinguishingType(s).isDate())
+
+ # 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 MozMap. There should be only one of these!
+ genericObjectSigs = [
+ s for s in possibleSignatures
+ if (distinguishingType(s).isDictionary() or
+ distinguishingType(s).isMozMap() 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, typed arrays,
+ # sequences, and Dates 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 (0);\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 = filter(filterLambda, possibleSignatures)
+ 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 ThrowErrorMessage(cx, MSG_OVERLOAD_RESOLUTION_FAILED, "%d", "%d", "%s");\n' %
+ (distinguishingIndex + 1, argCount, methodName)))
+
+ 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('return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "%s");\n' %
+ methodName)))
+ 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):
+ 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)
+
+
+class CGNavigatorGetterCall(CGPerSignatureCall):
+ """
+ A class to generate a native object getter call for an IDL getter for a
+ property generated by NavigatorProperty.
+ """
+ def __init__(self, returnType, _, descriptor, attr):
+ nativeMethodName = "%s::ConstructNavigatorObject" % (toBindingNamespace(returnType.inner.identifier.name))
+ CGPerSignatureCall.__init__(self, returnType, [], nativeMethodName,
+ True, descriptor, attr, getter=True)
+
+ def getArguments(self):
+ # The navigator object should be associated with the global of
+ # the navigator it's coming from, which will be the global of
+ # the object whose slot it gets cached in. That's stored in
+ # "slotStorage".
+ return [(FakeArgument(BuiltinTypes[IDLBuiltinType.Types.object],
+ self.idlNode),
+ "slotStorage")]
+
+
+class FakeIdentifier():
+ def __init__(self, name):
+ self.name = name
+
+
+class FakeArgument():
+ """
+ A class that quacks like an IDLArgument. This is used to make
+ setters look like method calls or for special operations.
+ """
+ def __init__(self, type, interfaceMember, name="arg", allowTreatNonCallableAsNull=False):
+ self.type = type
+ self.optional = False
+ self.variadic = False
+ self.defaultValue = None
+ self._allowTreatNonCallableAsNull = allowTreatNonCallableAsNull
+ # For FakeArguments generated by maplike/setlike convenience functions,
+ # we won't have an interfaceMember to pass in.
+ if interfaceMember:
+ self.treatNullAs = interfaceMember.treatNullAs
+ else:
+ self.treatNullAs = "Default"
+ if isinstance(interfaceMember, IDLAttribute):
+ self.enforceRange = interfaceMember.enforceRange
+ self.clamp = interfaceMember.clamp
+ else:
+ self.enforceRange = False
+ self.clamp = False
+
+ self.identifier = FakeIdentifier(name)
+
+ def allowTreatNonCallableAsNull(self):
+ return self._allowTreatNonCallableAsNull
+
+ def canHaveMissingValue(self):
+ return False
+
+
+class CGSetterCall(CGPerSignatureCall):
+ """
+ A class to generate a native object setter call for a particular IDL
+ setter.
+ """
+ def __init__(self, argType, nativeMethodName, descriptor, attr):
+ if attr.getExtendedAttribute("UseCounter"):
+ useCounterName = "%s_%s_setter" % (descriptor.interface.identifier.name,
+ attr.identifier.name)
+ else:
+ useCounterName = None
+ if attr.isStatic():
+ nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName)
+ CGPerSignatureCall.__init__(self, None,
+ [FakeArgument(argType, attr, allowTreatNonCallableAsNull=True)],
+ nativeMethodName, attr.isStatic(),
+ descriptor, attr, setter=True, useCounterName=useCounterName)
+
+ def wrap_return_value(self):
+ attr = self.idlNode
+ if self.descriptor.wrapperCache and attr.slotIndices is not None:
+ if attr.getExtendedAttribute("StoreInSlot"):
+ args = "cx, self"
+ else:
+ args = "self"
+ clearSlot = ("%s(%s);\n" %
+ (MakeClearCachedValueNativeName(self.idlNode), args))
+ else:
+ clearSlot = ""
+
+ # We have no return value
+ return ("\n"
+ "%s"
+ "return true;\n" % clearSlot)
+
+
+class CGAbstractBindingMethod(CGAbstractStaticMethod):
+ """
+ Common class to generate the JSNatives for all our methods, getters, and
+ setters. This will generate the function declaration and unwrap the
+ |this| 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.
+
+ getThisObj should be code for getting a JSObject* for the binding
+ object. If this is None, we will auto-generate code based on
+ descriptor to do the right thing. "" 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.
+
+ If allowCrossOriginThis is true, then this-unwrapping will first do an
+ UncheckedUnwrap and after that operate on the result.
+ """
+ def __init__(self, descriptor, name, args, unwrapFailureCode=None,
+ getThisObj=None,
+ callArgs="JS::CallArgs args = JS::CallArgsFromVp(argc, vp);\n",
+ allowCrossOriginThis=False):
+ CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args)
+
+ if unwrapFailureCode is None:
+ self.unwrapFailureCode = 'return ThrowErrorMessage(cx, MSG_THIS_DOES_NOT_IMPLEMENT_INTERFACE, "Value", "%s");\n' % descriptor.interface.identifier.name
+ else:
+ self.unwrapFailureCode = unwrapFailureCode
+
+ if getThisObj == "":
+ self.getThisObj = None
+ else:
+ if getThisObj is None:
+ if descriptor.interface.isOnGlobalProtoChain():
+ ensureCondition = "!args.thisv().isNullOrUndefined() && !args.thisv().isObject()"
+ getThisObj = "args.thisv().isObject() ? &args.thisv().toObject() : js::GetGlobalForObjectCrossCompartment(&args.callee())"
+ else:
+ ensureCondition = "!args.thisv().isObject()"
+ getThisObj = "&args.thisv().toObject()"
+ unwrapFailureCode = self.unwrapFailureCode % {'securityError': 'false'}
+ ensureThisObj = CGIfWrapper(CGGeneric(unwrapFailureCode),
+ ensureCondition)
+ else:
+ ensureThisObj = None
+ self.getThisObj = CGList(
+ [ensureThisObj,
+ CGGeneric("JS::Rooted<JSObject*> obj(cx, %s);\n" %
+ getThisObj)])
+ self.callArgs = callArgs
+ self.allowCrossOriginThis = allowCrossOriginThis
+
+ 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));
+ """)
+
+ # Our descriptor might claim that we're not castable, simply because
+ # we're someone's consequential interface. But for this-unwrapping, we
+ # know that we're the real deal. So fake a descriptor here for
+ # consumption by CastableObjectUnwrapper.
+ body += str(CastableObjectUnwrapper(
+ self.descriptor,
+ "rootSelf",
+ "&rootSelf",
+ "self",
+ self.unwrapFailureCode,
+ allowCrossOriginObj=self.allowCrossOriginThis))
+
+ 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())
+
+ 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:])
+
+
+class CGGenericMethod(CGAbstractBindingMethod):
+ """
+ A class for generating the C++ code for an IDL method.
+
+ If allowCrossOriginThis is true, then this-unwrapping will first do an
+ UncheckedUnwrap and after that operate on the result.
+ """
+ def __init__(self, descriptor, allowCrossOriginThis=False):
+ unwrapFailureCode = (
+ 'return ThrowInvalidThis(cx, args, %%(securityError)s, "%s");\n' %
+ descriptor.interface.identifier.name)
+ name = "genericCrossOriginMethod" if allowCrossOriginThis else "genericMethod"
+ CGAbstractBindingMethod.__init__(self, descriptor, name,
+ JSNativeArguments(),
+ unwrapFailureCode=unwrapFailureCode,
+ allowCrossOriginThis=allowCrossOriginThis)
+
+ def generate_code(self):
+ return CGGeneric(dedent("""
+ const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ MOZ_ASSERT(info->type() == JSJitInfo::Method);
+ JSJitMethodOp method = info->method;
+ bool ok = method(cx, obj, self, JSJitMethodCallArgs(args));
+ #ifdef DEBUG
+ if (ok) {
+ AssertReturnTypeMatchesJitinfo(info, args.rval());
+ }
+ #endif
+ return ok;
+ """))
+
+
+class CGGenericPromiseReturningMethod(CGAbstractBindingMethod):
+ """
+ A class for generating the C++ code for an IDL method that returns a Promise.
+
+ Does not handle cross-origin this.
+ """
+ def __init__(self, descriptor):
+ unwrapFailureCode = dedent("""
+ ThrowInvalidThis(cx, args, %%(securityError)s, "%s");\n
+ return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
+ args.rval());\n""" %
+ descriptor.interface.identifier.name)
+
+ name = "genericPromiseReturningMethod"
+ customCallArgs = dedent("""
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ // Make sure to save the callee before someone maybe messes with rval().
+ JS::Rooted<JSObject*> callee(cx, &args.callee());
+ """)
+
+ CGAbstractBindingMethod.__init__(self, descriptor, name,
+ JSNativeArguments(),
+ callArgs=customCallArgs,
+ unwrapFailureCode=unwrapFailureCode)
+
+ def generate_code(self):
+ return CGGeneric(dedent("""
+ const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ MOZ_ASSERT(info->type() == JSJitInfo::Method);
+ JSJitMethodOp method = info->method;
+ bool ok = method(cx, obj, self, JSJitMethodCallArgs(args));
+ if (ok) {
+ #ifdef DEBUG
+ AssertReturnTypeMatchesJitinfo(info, args.rval());
+ #endif
+ return true;
+ }
+
+ MOZ_ASSERT(info->returnType() == JSVAL_TYPE_OBJECT);
+ return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
+ args.rval());
+ """))
+
+
+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('%s*' % descriptor.nativeType, 'self'),
+ Argument('const JSJitMethodCallArgs&', 'args')]
+ CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args)
+
+ def definition_body(self):
+ nativeName = CGSpecializedMethod.makeNativeName(self.descriptor,
+ self.method)
+ return CGMethodCall(nativeName, self.method.isStatic(), self.descriptor,
+ self.method).define()
+
+ @staticmethod
+ def makeNativeName(descriptor, method):
+ name = method.identifier.name
+ return MakeNativeName(descriptor.binaryNameFor(name))
+
+
+class CGMethodPromiseWrapper(CGAbstractStaticMethod):
+ """
+ A class for generating a wrapper around another method that will
+ convert exceptions to promises.
+ """
+ def __init__(self, descriptor, methodToWrap):
+ self.method = methodToWrap
+ name = self.makeName(methodToWrap.name)
+ args = list(methodToWrap.args)
+ CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args)
+
+ def definition_body(self):
+ return fill(
+ """
+ // Make sure to save the callee before someone maybe messes
+ // with rval().
+ JS::Rooted<JSObject*> callee(cx, &args.callee());
+ bool ok = ${methodName}(${args});
+ if (ok) {
+ return true;
+ }
+ return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
+ args.rval());
+ """,
+ methodName=self.method.name,
+ args=", ".join(arg.name for arg in self.args))
+
+ @staticmethod
+ def makeName(methodName):
+ return methodName + "_promiseWrapper"
+
+
+class CGJsonifierMethod(CGSpecializedMethod):
+ def __init__(self, descriptor, method):
+ assert method.isJsonifier()
+ CGSpecializedMethod.__init__(self, descriptor, method)
+
+ def definition_body(self):
+ ret = dedent("""
+ JS::Rooted<JSObject*> result(cx, JS_NewPlainObject(cx));
+ if (!result) {
+ return false;
+ }
+ """)
+
+ jsonDescriptors = [self.descriptor]
+ interface = self.descriptor.interface.parent
+ while interface:
+ descriptor = self.descriptor.getDescriptor(interface.identifier.name)
+ if descriptor.operations['Jsonifier']:
+ jsonDescriptors.append(descriptor)
+ interface = interface.parent
+
+ # Iterate the array in reverse: oldest ancestor first
+ for descriptor in jsonDescriptors[::-1]:
+ ret += fill(
+ """
+ if (!${parentclass}::JsonifyAttributes(cx, obj, self, result)) {
+ return false;
+ }
+ """,
+ parentclass=toBindingNamespace(descriptor.name)
+ )
+ ret += ('args.rval().setObject(*result);\n'
+ 'return true;\n')
+ return ret
+
+
+class CGLegacyCallHook(CGAbstractBindingMethod):
+ """
+ Call hook for our object
+ """
+ def __init__(self, descriptor):
+ self._legacycaller = descriptor.operations["LegacyCaller"]
+ # Our "self" is actually the callee in this case, not the thisval.
+ CGAbstractBindingMethod.__init__(
+ self, descriptor, LEGACYCALLER_HOOK_NAME,
+ JSNativeArguments(), getThisObj="&args.callee()")
+
+ def define(self):
+ if not self._legacycaller:
+ return ""
+ return CGAbstractBindingMethod.define(self)
+
+ def generate_code(self):
+ name = self._legacycaller.identifier.name
+ nativeName = MakeNativeName(self.descriptor.binaryNameFor(name))
+ return CGMethodCall(nativeName, False, self.descriptor,
+ self._legacycaller)
+
+
+class CGResolveHook(CGAbstractClassHook):
+ """
+ Resolve hook for objects that have the NeedResolve extended attribute.
+ """
+ def __init__(self, descriptor):
+ assert descriptor.interface.getExtendedAttribute("NeedResolve")
+
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'obj'),
+ Argument('JS::Handle<jsid>', 'id'),
+ Argument('bool*', 'resolvedp')]
+ CGAbstractClassHook.__init__(self, descriptor, RESOLVE_HOOK_NAME,
+ "bool", args)
+
+ def generate_code(self):
+ return dedent("""
+ JS::Rooted<JS::PropertyDescriptor> desc(cx);
+ if (!self->DoResolve(cx, obj, id, &desc)) {
+ return false;
+ }
+ if (!desc.object()) {
+ return true;
+ }
+ // If desc.value() is undefined, then the DoResolve call
+ // has already defined it on the object. Don't try to also
+ // define it.
+ if (!desc.value().isUndefined()) {
+ desc.attributesRef() |= JSPROP_RESOLVING;
+ if (!JS_DefinePropertyById(cx, obj, id, desc)) {
+ return false;
+ }
+ }
+ *resolvedp = true;
+ return true;
+ """)
+
+ def definition_body(self):
+ if self.descriptor.isGlobal():
+ # Resolve standard classes
+ prefix = dedent("""
+ if (!ResolveGlobal(cx, obj, id, resolvedp)) {
+ return false;
+ }
+ if (*resolvedp) {
+ return true;
+ }
+
+ """)
+ else:
+ prefix = ""
+ return prefix + CGAbstractClassHook.definition_body(self)
+
+
+class CGMayResolveHook(CGAbstractStaticMethod):
+ """
+ Resolve hook for objects that have the NeedResolve extended attribute.
+ """
+ def __init__(self, descriptor):
+ assert descriptor.interface.getExtendedAttribute("NeedResolve")
+
+ args = [Argument('const JSAtomState&', 'names'),
+ Argument('jsid', 'id'),
+ Argument('JSObject*', 'maybeObj')]
+ CGAbstractStaticMethod.__init__(self, descriptor, MAY_RESOLVE_HOOK_NAME,
+ "bool", args)
+
+ def definition_body(self):
+ if self.descriptor.isGlobal():
+ # Check whether this would resolve as a standard class.
+ prefix = dedent("""
+ if (MayResolveGlobal(names, id, maybeObj)) {
+ return true;
+ }
+
+ """)
+ else:
+ prefix = ""
+ return (prefix +
+ "return %s::MayResolve(id);\n" % self.descriptor.nativeType)
+
+
+class CGEnumerateHook(CGAbstractBindingMethod):
+ """
+ Enumerate hook for objects with custom hooks.
+ """
+ def __init__(self, descriptor):
+ assert descriptor.interface.getExtendedAttribute("NeedResolve")
+
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'obj')]
+ # Our "self" is actually the "obj" argument in this case, not the thisval.
+ CGAbstractBindingMethod.__init__(
+ self, descriptor, ENUMERATE_HOOK_NAME,
+ args, getThisObj="", callArgs="")
+
+ def generate_code(self):
+ return CGGeneric(dedent("""
+ AutoTArray<nsString, 8> names;
+ binding_detail::FastErrorResult rv;
+ self->GetOwnPropertyNames(cx, names, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return false;
+ }
+ bool dummy;
+ for (uint32_t i = 0; i < names.Length(); ++i) {
+ if (!JS_HasUCProperty(cx, obj, names[i].get(), names[i].Length(), &dummy)) {
+ return false;
+ }
+ }
+ return true;
+ """))
+
+ def definition_body(self):
+ if self.descriptor.isGlobal():
+ # Enumerate standard classes
+ prefix = dedent("""
+ if (!EnumerateGlobal(cx, obj)) {
+ 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)
+
+
+class CGGenericGetter(CGAbstractBindingMethod):
+ """
+ A class for generating the C++ code for an IDL attribute getter.
+ """
+ def __init__(self, descriptor, lenientThis=False, allowCrossOriginThis=False):
+ if lenientThis:
+ name = "genericLenientGetter"
+ unwrapFailureCode = dedent("""
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+ if (!ReportLenientThisUnwrappingFailure(cx, &args.callee())) {
+ return false;
+ }
+ args.rval().set(JS::UndefinedValue());
+ return true;
+ """)
+ else:
+ if allowCrossOriginThis:
+ name = "genericCrossOriginGetter"
+ else:
+ name = "genericGetter"
+ unwrapFailureCode = (
+ 'return ThrowInvalidThis(cx, args, %%(securityError)s, "%s");\n' %
+ descriptor.interface.identifier.name)
+ CGAbstractBindingMethod.__init__(self, descriptor, name, JSNativeArguments(),
+ unwrapFailureCode,
+ allowCrossOriginThis=allowCrossOriginThis)
+
+ def generate_code(self):
+ return CGGeneric(dedent("""
+ const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ MOZ_ASSERT(info->type() == JSJitInfo::Getter);
+ JSJitGetterOp getter = info->getter;
+ bool ok = getter(cx, obj, self, JSJitGetterCallArgs(args));
+ #ifdef DEBUG
+ if (ok) {
+ AssertReturnTypeMatchesJitinfo(info, args.rval());
+ }
+ #endif
+ return ok;
+ """))
+
+
+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('%s*' % descriptor.nativeType, 'self'),
+ Argument('JSJitGetterCallArgs', 'args')
+ ]
+ CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args)
+
+ def definition_body(self):
+ if self.attr.isMaplikeOrSetlikeAttr():
+ # 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 getMaplikeOrSetlikeSizeGetterBody(self.descriptor, self.attr)
+ nativeName = CGSpecializedGetter.makeNativeName(self.descriptor,
+ self.attr)
+ if self.attr.slotIndices is not None:
+ if self.descriptor.hasXPConnectImpls:
+ raise TypeError("Interface '%s' has XPConnect impls, so we "
+ "can't use our slot for property '%s'!" %
+ (self.descriptor.interface.identifier.name,
+ self.attr.identifier.name))
+
+ # 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
+ # (navigator properties, window.document) we want to avoid calling
+ # the getter more than once. In the case of navigator properties
+ # that's because the getter actually creates a new object each time.
+ # In the case of window.document, it's because the getter can start
+ # returning null, which would get hidden in he 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::GetObjectClass(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))
+ else:
+ prefix = ""
+
+ if self.attr.navigatorObjectGetter:
+ cgGetterCall = CGNavigatorGetterCall
+ else:
+ cgGetterCall = CGGetterCall
+ return (prefix +
+ cgGetterCall(self.attr.type, nativeName,
+ self.descriptor, self.attr).define())
+
+ @staticmethod
+ def makeNativeName(descriptor, attr):
+ name = attr.identifier.name
+ nativeName = MakeNativeName(descriptor.binaryNameFor(name))
+ _, resultOutParam, _, _, _ = getRetvalDeclarationForType(attr.type,
+ descriptor)
+ infallible = ('infallible' in
+ descriptor.getExtendedAttributes(attr, getter=True))
+ if resultOutParam or attr.type.nullable() or not infallible:
+ nativeName = "Get" + nativeName
+ return nativeName
+
+
+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)
+
+
+class CGGenericSetter(CGAbstractBindingMethod):
+ """
+ A class for generating the C++ code for an IDL attribute setter.
+ """
+ def __init__(self, descriptor, lenientThis=False, allowCrossOriginThis=False):
+ if lenientThis:
+ name = "genericLenientSetter"
+ unwrapFailureCode = dedent("""
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+ if (!ReportLenientThisUnwrappingFailure(cx, &args.callee())) {
+ return false;
+ }
+ args.rval().set(JS::UndefinedValue());
+ return true;
+ """)
+ else:
+ if allowCrossOriginThis:
+ name = "genericCrossOriginSetter"
+ else:
+ name = "genericSetter"
+ unwrapFailureCode = (
+ 'return ThrowInvalidThis(cx, args, %%(securityError)s, "%s");\n' %
+ descriptor.interface.identifier.name)
+
+ CGAbstractBindingMethod.__init__(self, descriptor, name, JSNativeArguments(),
+ unwrapFailureCode,
+ allowCrossOriginThis=allowCrossOriginThis)
+
+ def generate_code(self):
+ return CGGeneric(fill(
+ """
+ if (args.length() == 0) {
+ return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${name} attribute setter");
+ }
+ const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
+ MOZ_ASSERT(info->type() == JSJitInfo::Setter);
+ JSJitSetterOp setter = info->setter;
+ if (!setter(cx, obj, self, JSJitSetterCallArgs(args))) {
+ return false;
+ }
+ args.rval().setUndefined();
+ #ifdef DEBUG
+ AssertReturnTypeMatchesJitinfo(info, args.rval());
+ #endif
+ return true;
+ """,
+ name=self.descriptor.interface.identifier.name))
+
+
+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('%s*' % descriptor.nativeType, 'self'),
+ Argument('JSJitSetterCallArgs', 'args')]
+ CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args)
+
+ def definition_body(self):
+ nativeName = CGSpecializedSetter.makeNativeName(self.descriptor,
+ self.attr)
+ return CGSetterCall(self.attr.type, nativeName, self.descriptor,
+ self.attr).define()
+
+ @staticmethod
+ def makeNativeName(descriptor, attr):
+ name = attr.identifier.name
+ return "Set" + MakeNativeName(descriptor.binaryNameFor(name))
+
+
+class CGStaticSetter(CGAbstractStaticBindingMethod):
+ """
+ A class for generating the C++ code for an IDL static attribute setter.
+ """
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = 'set_' + IDLToCIdentifier(attr.identifier.name)
+ CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
+
+ def generate_code(self):
+ nativeName = CGSpecializedSetter.makeNativeName(self.descriptor,
+ self.attr)
+ checkForArg = CGGeneric(fill(
+ """
+ if (args.length() == 0) {
+ return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${name} setter");
+ }
+ """,
+ name=self.attr.identifier.name))
+ call = CGSetterCall(self.attr.type, nativeName, self.descriptor,
+ self.attr)
+ return CGList([checkForArg, call])
+
+
+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 ThrowErrorMessage(cx, 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)
+
+
+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)
+
+
+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, nsIDocument::eLenientSetter);
+ return true;
+ """)
+
+
+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=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)
+
+ return fill(
+ """
+ static const JSJitInfo ${infoName} = ${jitInfo};
+ $*{slotAssert}
+ """,
+ infoName=infoName,
+ jitInfo=jitInfoInitializer(False),
+ slotAssert=slotAssert)
+
+ def define(self):
+ if self.member.isAttr():
+ getterinfo = ("%s_getterinfo" %
+ IDLToCIdentifier(self.member.identifier.name))
+ # We need the cast here because JSJitGetterOp has a "void* self"
+ # while we have the right type.
+ getter = ("(JSJitGetterOp)get_%s" %
+ IDLToCIdentifier(self.member.identifier.name))
+ getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True)
+
+ movable = self.mayBeMovable() and getterinfal
+ eliminatable = self.mayBeEliminatable() and getterinfal
+ aliasSet = self.aliasSet()
+
+ getterinfal = getterinfal and infallibleForMember(self.member, self.member.type, self.descriptor)
+ isAlwaysInSlot = self.member.getExtendedAttribute("StoreInSlot")
+ if self.member.slotIndices is not None:
+ assert isAlwaysInSlot or self.member.getExtendedAttribute("Cached")
+ isLazilyCachedInSlot = not isAlwaysInSlot
+ slotIndex = memberReservedSlot(self.member, self.descriptor)
+ # We'll statically assert that this is not too big in
+ # CGUpdateMemberSlotsMethod, in the case when
+ # isAlwaysInSlot is true.
+ else:
+ isLazilyCachedInSlot = False
+ slotIndex = "0"
+
+ result = self.defineJitInfo(getterinfo, getter, "Getter",
+ getterinfal, movable, eliminatable,
+ aliasSet, isAlwaysInSlot,
+ isLazilyCachedInSlot, slotIndex,
+ [self.member.type], None)
+ if (not self.member.readonly or
+ self.member.getExtendedAttribute("PutForwards") is not None or
+ self.member.getExtendedAttribute("Replaceable") is not None or
+ self.member.getExtendedAttribute("LenientSetter") is not None):
+ setterinfo = ("%s_setterinfo" %
+ IDLToCIdentifier(self.member.identifier.name))
+ # Actually a JSJitSetterOp, but JSJitGetterOp is first in the
+ # union.
+ setter = ("(JSJitGetterOp)set_%s" %
+ IDLToCIdentifier(self.member.identifier.name))
+ # Setters are always fallible, since they have to do a typed unwrap.
+ result += self.defineJitInfo(setterinfo, setter, "Setter",
+ False, False, False, "AliasEverything",
+ False, False, "0",
+ [BuiltinTypes[IDLBuiltinType.Types.void]],
+ None)
+ return result
+ if self.member.isMethod():
+ methodinfo = ("%s_methodinfo" %
+ IDLToCIdentifier(self.member.identifier.name))
+ name = CppKeywords.checkMethodName(
+ IDLToCIdentifier(self.member.identifier.name))
+ if self.member.returnsPromise():
+ name = CGMethodPromiseWrapper.makeName(name)
+ # Actually a JSJitMethodOp, but JSJitGetterOp is first in the union.
+ method = ("(JSJitGetterOp)%s" % name)
+
+ # Methods are infallible if they are infallible, have no arguments
+ # to unwrap, and have a return type that's infallible to wrap up for
+ # return.
+ sigs = self.member.signatures()
+ if len(sigs) != 1:
+ # Don't handle overloading. If there's more than one signature,
+ # one of them must take arguments.
+ methodInfal = False
+ args = None
+ movable = False
+ eliminatable = False
+ else:
+ sig = sigs[0]
+ # For methods that affect nothing, it's OK to set movable to our
+ # notion of infallible on the C++ side, without considering
+ # argument conversions, since argument conversions that can
+ # reliably throw would be effectful anyway and the jit doesn't
+ # move effectful things.
+ hasInfallibleImpl = "infallible" in self.descriptor.getExtendedAttributes(self.member)
+ 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
+ # For now, only bother to output args if we're side-effect-free.
+ if self.member.affects == "Nothing":
+ args = sig[1]
+ else:
+ args = None
+
+ aliasSet = self.aliasSet()
+ result = self.defineJitInfo(methodinfo, method, "Method",
+ methodInfal, movable, eliminatable,
+ aliasSet, False, False, "0",
+ [s[0] for s in sigs], args)
+ return result
+ raise TypeError("Illegal member type to CGPropertyJITInfo")
+
+ def mayBeMovable(self):
+ """
+ Returns whether this attribute or method may be movable, just
+ based on Affects/DependsOn annotations.
+ """
+ affects = self.member.affects
+ dependsOn = self.member.dependsOn
+ assert affects in IDLInterfaceMember.AffectsValues
+ assert dependsOn in IDLInterfaceMember.DependsOnValues
+ # Things that are DependsOn=DeviceState are not movable, because we
+ # don't want them coalesced with each other or loop-hoisted, since
+ # their return value can change even if nothing is going on from our
+ # point of view.
+ return (affects == "Nothing" and
+ (dependsOn != "Everything" and dependsOn != "DeviceState"))
+
+ def mayBeEliminatable(self):
+ """
+ Returns whether this attribute or method may be eliminatable, just
+ based on Affects/DependsOn annotations.
+ """
+ # dependsOn shouldn't affect this decision at all, except in jitinfo we
+ # have no way to express "Depends on everything, affects nothing",
+ # because we only have three alias set values: AliasNone ("depends on
+ # nothing, affects nothing"), AliasDOMSets ("depends on DOM sets,
+ # affects nothing"), AliasEverything ("depends on everything, affects
+ # everything"). So the [Affects=Nothing, DependsOn=Everything] case
+ # gets encoded as AliasEverything and defineJitInfo asserts that if our
+ # alias state is AliasEverything then we're not eliminatable (because it
+ # thinks we might have side-effects at that point). Bug 1155796 is
+ # tracking possible solutions for this.
+ affects = self.member.affects
+ dependsOn = self.member.dependsOn
+ assert affects in IDLInterfaceMember.AffectsValues
+ assert dependsOn in IDLInterfaceMember.DependsOnValues
+ return affects == "Nothing" and dependsOn != "Everything"
+
+ def aliasSet(self):
+ """
+ Returns the alias set to store in the jitinfo. This may not be the
+ effective alias set the JIT uses, depending on whether we have enough
+ information about our args to allow the JIT to prove that effectful
+ argument conversions won't happen.
+ """
+ dependsOn = self.member.dependsOn
+ assert dependsOn in IDLInterfaceMember.DependsOnValues
+
+ if dependsOn == "Nothing" or dependsOn == "DeviceState":
+ assert self.member.affects == "Nothing"
+ return "AliasNone"
+
+ if dependsOn == "DOMState":
+ assert self.member.affects == "Nothing"
+ return "AliasDOMSets"
+
+ return "AliasEverything"
+
+ @staticmethod
+ def getJSReturnTypeTag(t):
+ if t.nullable():
+ # Sometimes it might return null, sometimes not
+ return "JSVAL_TYPE_UNKNOWN"
+ if t.isVoid():
+ # No return, every time
+ return "JSVAL_TYPE_UNDEFINED"
+ if t.isSequence():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isMozMap():
+ 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 reduce(CGMemberJITInfo.getSingleReturnType,
+ u.flatMemberTypes, "")
+ if t.isDictionary():
+ return "JSVAL_TYPE_OBJECT"
+ if t.isDate():
+ return "JSVAL_TYPE_OBJECT"
+ if not t.isPrimitive():
+ raise TypeError("No idea what type " + str(t) + " is.")
+ tag = t.tag()
+ if tag == IDLType.Tags.bool:
+ return "JSVAL_TYPE_BOOLEAN"
+ if tag in [IDLType.Tags.int8, IDLType.Tags.uint8,
+ IDLType.Tags.int16, IDLType.Tags.uint16,
+ IDLType.Tags.int32]:
+ return "JSVAL_TYPE_INT32"
+ if tag in [IDLType.Tags.int64, IDLType.Tags.uint64,
+ IDLType.Tags.unrestricted_float, IDLType.Tags.float,
+ IDLType.Tags.unrestricted_double, IDLType.Tags.double]:
+ # These all use JS_NumberValue, which can return int or double.
+ # But TI treats "double" as meaning "int or double", so we're
+ # good to return JSVAL_TYPE_DOUBLE here.
+ return "JSVAL_TYPE_DOUBLE"
+ if tag != IDLType.Tags.uint32:
+ raise TypeError("No idea what type " + str(t) + " is.")
+ # uint32 is sometimes int and sometimes double.
+ return "JSVAL_TYPE_DOUBLE"
+
+ @staticmethod
+ def getSingleReturnType(existingType, t):
+ type = CGMemberJITInfo.getJSReturnTypeTag(t)
+ if existingType == "":
+ # First element of the list; just return its type
+ return type
+
+ if type == existingType:
+ return existingType
+ if ((type == "JSVAL_TYPE_DOUBLE" and
+ existingType == "JSVAL_TYPE_INT32") or
+ (existingType == "JSVAL_TYPE_DOUBLE" and
+ type == "JSVAL_TYPE_INT32")):
+ # Promote INT32 to DOUBLE as needed
+ return "JSVAL_TYPE_DOUBLE"
+ # Different types
+ return "JSVAL_TYPE_UNKNOWN"
+
+ @staticmethod
+ def getJSArgType(t):
+ assert not t.isVoid()
+ if t.nullable():
+ # Sometimes it might return null, sometimes not
+ return "JSJitInfo::ArgType(JSJitInfo::Null | %s)" % CGMemberJITInfo.getJSArgType(t.inner)
+ if t.isSequence():
+ return "JSJitInfo::Object"
+ if t.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)" %
+ reduce(CGMemberJITInfo.getSingleArgType,
+ u.flatMemberTypes, type))
+ if t.isDictionary():
+ return "JSJitInfo::Object"
+ if t.isDate():
+ 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_MISSING, 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):
+ return '_' + 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
+ strings = CGNamespace(
+ self.stringsNamespace(),
+ CGGeneric(declare=("extern const EnumEntry %s[%d];\n" %
+ (ENUM_ENTRY_VARIABLE_NAME, self.nEnumStrings())),
+ 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 declare(self):
+ decl = fill(
+ """
+ enum class ${name} : uint32_t {
+ $*{enums}
+ EndGuard_
+ };
+ """,
+ name=self.enum.identifier.name,
+ enums=",\n".join(map(getEnumValueName, self.enum.values())) + ",\n")
+ strings = CGNamespace(self.stringsNamespace(),
+ CGGeneric(declare="extern const EnumEntry %s[%d];\n"
+ % (ENUM_ENTRY_VARIABLE_NAME, self.nEnumStrings())))
+ 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()
+
+ if type.isSequence() or type.isMozMap():
+ if type.isSequence():
+ wrapperType = "Sequence"
+ else:
+ wrapperType = "MozMap"
+ # We don't use the returned template here, so it's OK to just pass no
+ # sourceDescription.
+ elementInfo = getJSToNativeConversionInfo(type.inner,
+ descriptorProvider,
+ isMember=wrapperType)
+ return CGTemplatedType(wrapperType, elementInfo.declType,
+ 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)
+ # Allow null pointers for old-binding classes.
+ if type.unroll().inner.isExternal():
+ typeName = CGWrapper(typeName, post="*")
+ else:
+ typeName = CGWrapper(typeName, post="&")
+ return typeName
+
+ if type.isSpiderMonkeyInterface():
+ typeName = CGGeneric(type.name)
+ return CGWrapper(typeName, post=" const &")
+
+ if type.isDOMString() or type.isUSVString():
+ return CGGeneric("const nsAString&")
+
+ if type.isByteString():
+ return CGGeneric("const nsCString&")
+
+ if type.isEnum():
+ return CGGeneric(type.inner.identifier.name)
+
+ if type.isCallback():
+ return CGGeneric("%s&" % type.unroll().callback.identifier.name)
+
+ if type.isAny():
+ return CGGeneric("JS::Value")
+
+ if type.isObject():
+ return CGGeneric("JSObject*")
+
+ if type.isDictionary():
+ return CGGeneric("const %s&" % type.inner.identifier.name)
+
+ if not type.isPrimitive():
+ raise TypeError("Need native type for argument type '%s'" % str(type))
+
+ return CGGeneric(builtinNames[type.tag()])
+
+
+def getUnionTypeTemplateVars(unionType, type, descriptorProvider,
+ ownsMembers=False):
+ name = getUnionMemberName(type)
+ holderName = "m" + name + "Holder"
+
+ # By the time tryNextCode is invoked, we're guaranteed the union has been
+ # constructed as some type, since we've been trying to convert into the
+ # corresponding member.
+ prefix = "" if ownsMembers else "mUnion."
+ tryNextCode = ("$*{destroyHolder}\n"
+ "%sDestroy%s();\n"
+ "tryNext = true;\n"
+ "return true;\n" % (prefix, name))
+
+ conversionInfo = getJSToNativeConversionInfo(
+ type, descriptorProvider, failureCode=tryNextCode,
+ isDefinitelyObject=not type.isDictionary(),
+ isMember=("OwningUnion" if ownsMembers else None),
+ sourceDescription="member of %s" % unionType)
+
+ if conversionInfo.holderType is not None:
+ assert not ownsMembers
+ destroyHolder = "%s.reset();\n" % holderName
+ else:
+ destroyHolder = ""
+
+ ctorNeedsCx = conversionInfo.declArgs == "cx"
+ ctorArgs = "cx" if ctorNeedsCx else ""
+
+ structType = conversionInfo.declType.define()
+ externalType = getUnionAccessorSignatureType(type, descriptorProvider).define()
+
+ if type.isObject():
+ if ownsMembers:
+ body = dedent("""
+ MOZ_ASSERT(mType == eUninitialized);
+ mValue.mObject.SetValue(obj);
+ mType = eObject;
+ """)
+ else:
+ body = dedent("""
+ MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized);
+ mUnion.mValue.mObject.SetValue(cx, obj);
+ mUnion.mType = mUnion.eObject;
+ """)
+
+ # It's a bit sketchy to do the security check after setting the value,
+ # but it keeps the code cleaner and lets us avoid rooting |obj| over the
+ # call to CallerSubsumes().
+ body = body + dedent("""
+ if (passedToJSImpl && !CallerSubsumes(obj)) {
+ ThrowErrorMessage(cx, MSG_PERMISSION_DENIED_TO_PASS_ARG, "%s");
+ return false;
+ }
+ return true;
+ """)
+
+ setter = ClassMethod("SetToObject", "bool",
+ [Argument("JSContext*", "cx"),
+ Argument("JSObject*", "obj"),
+ Argument("bool", "passedToJSImpl", default="false")],
+ inline=True, bodyInHeader=True,
+ body=body)
+
+ else:
+ # Important: we need to not have our declName involve
+ # maybe-GCing operations.
+ if conversionInfo.holderType is not None:
+ holderArgs = conversionInfo.holderArgs
+ if holderArgs is None:
+ holderArgs = ""
+ initHolder = "%s.emplace(%s);\n" % (holderName, holderArgs)
+ else:
+ initHolder = ""
+
+ jsConversion = fill(
+ initHolder + conversionInfo.template,
+ val="value",
+ maybeMutableVal="value",
+ declName="memberSlot",
+ holderName=(holderName if ownsMembers else "%s.ref()" % holderName),
+ destroyHolder=destroyHolder,
+ passedToJSImpl="passedToJSImpl")
+
+ jsConversion = fill(
+ """
+ tryNext = false;
+ { // scope for memberSlot
+ ${structType}& memberSlot = RawSetAs${name}(${ctorArgs});
+ $*{jsConversion}
+ }
+ return true;
+ """,
+ structType=structType,
+ name=name,
+ ctorArgs=ctorArgs,
+ jsConversion=jsConversion)
+
+ if ownsMembers:
+ handleType = "JS::Handle<JS::Value>"
+ else:
+ handleType = "JS::MutableHandle<JS::Value>"
+
+ setter = ClassMethod("TrySetTo" + name, "bool",
+ [Argument("JSContext*", "cx"),
+ Argument(handleType, "value"),
+ Argument("bool&", "tryNext"),
+ Argument("bool", "passedToJSImpl", default="false")],
+ inline=not ownsMembers,
+ bodyInHeader=not ownsMembers,
+ body=jsConversion)
+
+ return {
+ "name": name,
+ "structType": structType,
+ "externalType": externalType,
+ "setter": setter,
+ "holderType": conversionInfo.holderType.define() if conversionInfo.holderType else None,
+ "ctorArgs": ctorArgs,
+ "ctorArgList": [Argument("JSContext*", "cx")] if ctorNeedsCx else []
+ }
+
+
+class CGUnionStruct(CGThing):
+ def __init__(self, type, descriptorProvider, ownsMembers=False):
+ CGThing.__init__(self)
+ self.type = type.unroll()
+ self.descriptorProvider = descriptorProvider
+ self.ownsMembers = ownsMembers
+ self.struct = self.getStruct()
+
+ def declare(self):
+ return self.struct.declare()
+
+ def define(self):
+ return self.struct.define()
+
+ def deps(self):
+ return self.type.getDeps()
+
+ def getStruct(self):
+
+ members = [ClassMember("mType", "Type", body="eUninitialized"),
+ ClassMember("mValue", "Value")]
+ ctor = ClassConstructor([], bodyInHeader=True, visibility="public",
+ explicit=True)
+
+ methods = []
+ enumValues = ["eUninitialized"]
+ toJSValCases = [CGCase("eUninitialized", CGGeneric("return false;\n"))]
+ destructorCases = [CGCase("eUninitialized", None)]
+ assignmentCases = [
+ CGCase("eUninitialized",
+ CGGeneric('MOZ_ASSERT(mType == eUninitialized,\n'
+ ' "We need to destroy ourselves?");\n'))]
+ traceCases = []
+ unionValues = []
+ if self.type.hasNullableType:
+ enumValues.append("eNull")
+ methods.append(ClassMethod("IsNull", "bool", [], const=True, inline=True,
+ body="return mType == eNull;\n",
+ bodyInHeader=True))
+ methods.append(ClassMethod("SetNull", "void", [], inline=True,
+ body=("Uninit();\n"
+ "mType = eNull;\n"),
+ bodyInHeader=True))
+ destructorCases.append(CGCase("eNull", None))
+ assignmentCases.append(CGCase("eNull",
+ CGGeneric("MOZ_ASSERT(mType == eUninitialized);\n"
+ "mType = eNull;\n")))
+ toJSValCases.append(CGCase("eNull", CGGeneric("rval.setNull();\n"
+ "return true;\n")))
+
+ hasObjectType = any(t.isObject() for t in self.type.flatMemberTypes)
+ for t in self.type.flatMemberTypes:
+ vars = getUnionTypeTemplateVars(self.type,
+ t, self.descriptorProvider,
+ ownsMembers=self.ownsMembers)
+ if vars["name"] != "Object" or self.ownsMembers:
+ body = fill(
+ """
+ if (mType == e${name}) {
+ return mValue.m${name}.Value();
+ }
+ %s
+ mType = e${name};
+ return mValue.m${name}.SetValue(${ctorArgs});
+ """,
+ **vars)
+
+ # bodyInHeader must be false for return values because they own
+ # their union members and we don't want include headers in
+ # UnionTypes.h just to call Addref/Release
+ methods.append(ClassMethod(
+ "RawSetAs" + vars["name"],
+ vars["structType"] + "&",
+ vars["ctorArgList"],
+ bodyInHeader=not self.ownsMembers,
+ body=body % "MOZ_ASSERT(mType == eUninitialized);"))
+ uninit = "Uninit();"
+ if hasObjectType and not self.ownsMembers:
+ uninit = 'MOZ_ASSERT(mType != eObject, "This will not play well with Rooted");\n' + uninit
+ methods.append(ClassMethod(
+ "SetAs" + vars["name"],
+ vars["structType"] + "&",
+ vars["ctorArgList"],
+ bodyInHeader=not self.ownsMembers,
+ body=body % uninit))
+ if self.ownsMembers:
+ methods.append(vars["setter"])
+ # Provide a SetStringData() method to support string defaults.
+ if t.isByteString():
+ methods.append(
+ ClassMethod("SetStringData", "void",
+ [Argument("const nsCString::char_type*", "aData"),
+ Argument("nsCString::size_type", "aLength")],
+ inline=True, bodyInHeader=True,
+ body="RawSetAs%s().Assign(aData, aLength);\n" % t.name))
+ elif t.isString():
+ methods.append(
+ ClassMethod("SetStringData", "void",
+ [Argument("const nsString::char_type*", "aData"),
+ Argument("nsString::size_type", "aLength")],
+ inline=True, bodyInHeader=True,
+ body="RawSetAs%s().Assign(aData, aLength);\n" % t.name))
+
+ body = fill(
+ """
+ MOZ_ASSERT(Is${name}(), "Wrong type!");
+ mValue.m${name}.Destroy();
+ mType = eUninitialized;
+ """,
+ **vars)
+ methods.append(ClassMethod("Destroy" + vars["name"],
+ "void",
+ [],
+ visibility="private",
+ bodyInHeader=not self.ownsMembers,
+ body=body))
+
+ body = fill("return mType == e${name};\n", **vars)
+ methods.append(ClassMethod("Is" + vars["name"],
+ "bool",
+ [],
+ const=True,
+ bodyInHeader=True,
+ body=body))
+
+ body = fill(
+ """
+ MOZ_ASSERT(Is${name}(), "Wrong type!");
+ return mValue.m${name}.Value();
+ """,
+ **vars)
+ # The non-const version of GetAs* returns our internal type
+ getterReturnType = "%s&" % vars["structType"]
+ methods.append(ClassMethod("GetAs" + vars["name"],
+ getterReturnType,
+ [],
+ bodyInHeader=True,
+ body=body))
+ # The const version of GetAs* returns our internal type
+ # for owning unions, but our external type for non-owning
+ # ones.
+ if self.ownsMembers:
+ getterReturnType = "%s const &" % vars["structType"]
+ else:
+ getterReturnType = vars["externalType"]
+ methods.append(ClassMethod("GetAs" + vars["name"],
+ getterReturnType,
+ [],
+ const=True,
+ bodyInHeader=True,
+ body=body))
+
+ unionValues.append(
+ fill("UnionMember<${structType} > m${name}", **vars))
+ enumValues.append("e" + vars["name"])
+
+ skipToJSVal = False
+ try:
+ toJSValCases.append(
+ CGCase("e" + vars["name"],
+ self.getConversionToJS(vars, t)))
+ except MethodNotNewObjectError:
+ # If we can't have a ToJSVal() because one of our members can
+ # only be returned from [NewObject] methods, then just skip
+ # generating ToJSVal.
+ skipToJSVal = True
+ destructorCases.append(
+ CGCase("e" + vars["name"],
+ CGGeneric("Destroy%s();\n" % vars["name"])))
+ assignmentCases.append(
+ CGCase("e" + vars["name"],
+ CGGeneric("SetAs%s() = aOther.GetAs%s();\n" %
+ (vars["name"], vars["name"]))))
+ if self.ownsMembers and typeNeedsRooting(t):
+ if t.isObject():
+ traceCases.append(
+ CGCase("e" + vars["name"],
+ CGGeneric('JS::UnsafeTraceRoot(trc, %s, "%s");\n' %
+ ("&mValue.m" + vars["name"] + ".Value()",
+ "mValue.m" + vars["name"]))))
+ elif t.isDictionary():
+ traceCases.append(
+ CGCase("e" + vars["name"],
+ CGGeneric("mValue.m%s.Value().TraceDictionary(trc);\n" %
+ vars["name"])))
+ elif t.isSequence():
+ traceCases.append(
+ CGCase("e" + vars["name"],
+ CGGeneric("DoTraceSequence(trc, mValue.m%s.Value());\n" %
+ vars["name"])))
+ elif t.isMozMap():
+ traceCases.append(
+ CGCase("e" + vars["name"],
+ CGGeneric("TraceMozMap(trc, mValue.m%s.Value());\n" %
+ vars["name"])))
+ else:
+ assert t.isSpiderMonkeyInterface()
+ traceCases.append(
+ CGCase("e" + vars["name"],
+ CGGeneric("mValue.m%s.Value().TraceSelf(trc);\n" %
+ vars["name"])))
+
+ dtor = CGSwitch("mType", destructorCases).define()
+
+ methods.append(ClassMethod("Uninit", "void", [],
+ visibility="public", body=dtor,
+ bodyInHeader=not self.ownsMembers,
+ inline=not self.ownsMembers))
+
+ if not skipToJSVal:
+ methods.append(
+ ClassMethod(
+ "ToJSVal",
+ "bool",
+ [
+ Argument("JSContext*", "cx"),
+ Argument("JS::Handle<JSObject*>", "scopeObj"),
+ Argument("JS::MutableHandle<JS::Value>", "rval")
+ ],
+ body=CGSwitch("mType", toJSValCases,
+ default=CGGeneric("return false;\n")).define() + "\nreturn false;\n",
+ const=True))
+
+ constructors = [ctor]
+ selfName = CGUnionStruct.unionTypeName(self.type, self.ownsMembers)
+ if self.ownsMembers:
+ if traceCases:
+ traceBody = CGSwitch("mType", traceCases,
+ default=CGGeneric("")).define()
+ else:
+ traceBody = ""
+ methods.append(ClassMethod("TraceUnion", "void",
+ [Argument("JSTracer*", "trc")],
+ body=traceBody))
+ if CGUnionStruct.isUnionCopyConstructible(self.type):
+ constructors.append(
+ ClassConstructor(
+ [Argument("const %s&" % selfName, "aOther")],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ body="*this = aOther;\n"))
+ methods.append(ClassMethod(
+ "operator=", "void",
+ [Argument("const %s&" % selfName, "aOther")],
+ body=CGSwitch("aOther.mType", assignmentCases).define()))
+ disallowCopyConstruction = False
+ else:
+ disallowCopyConstruction = True
+ else:
+ disallowCopyConstruction = True
+
+ if self.ownsMembers:
+ friend = " friend void ImplCycleCollectionUnlink(%s& aUnion);\n" % CGUnionStruct.unionTypeName(self.type, True)
+ else:
+ friend = " friend class %sArgument;\n" % str(self.type)
+
+ bases = [ClassBase("AllOwningUnionBase")] if self.ownsMembers else []
+ return CGClass(selfName,
+ bases=bases,
+ members=members,
+ constructors=constructors,
+ methods=methods,
+ disallowCopyConstruction=disallowCopyConstruction,
+ extradeclarations=friend,
+ destructor=ClassDestructor(visibility="public",
+ body="Uninit();\n",
+ bodyInHeader=True),
+ enums=[ClassEnum("Type", enumValues, visibility="private")],
+ unions=[ClassUnion("Value", unionValues, visibility="private")])
+
+ def getConversionToJS(self, templateVars, type):
+ 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,
+ "typedArraysAreStructs": True
+ })
+ return CGGeneric(wrapCode)
+
+ @staticmethod
+ def isUnionCopyConstructible(type):
+ return all(isTypeCopyConstructible(t) for t in type.flatMemberTypes)
+
+ @staticmethod
+ def unionTypeName(type, ownsMembers):
+ """
+ Returns a string name for this known union type.
+ """
+ assert type.isUnion() and not type.nullable()
+ return ("Owning" if ownsMembers else "") + type.name
+
+ @staticmethod
+ def unionTypeDecl(type, ownsMembers):
+ """
+ Returns a string for declaring this possibly-nullable union type.
+ """
+ assert type.isUnion()
+ nullable = type.nullable()
+ if nullable:
+ type = type.inner
+ decl = CGGeneric(CGUnionStruct.unionTypeName(type, ownsMembers))
+ if nullable:
+ decl = CGTemplatedType("Nullable", decl)
+ return decl.define()
+
+
+class CGUnionConversionStruct(CGThing):
+ def __init__(self, type, descriptorProvider):
+ CGThing.__init__(self)
+ self.type = type.unroll()
+ self.descriptorProvider = descriptorProvider
+
+ def declare(self):
+
+ structName = str(self.type)
+ members = [ClassMember("mUnion", structName + "&",
+ body="const_cast<%s&>(aUnion)" % structName)]
+ # Argument needs to be a const ref because that's all Maybe<> allows
+ ctor = ClassConstructor([Argument("const %s&" % structName, "aUnion")],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True)
+ methods = []
+
+ if self.type.hasNullableType:
+ methods.append(ClassMethod("SetNull", "bool", [],
+ body=("MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized);\n"
+ "mUnion.mType = mUnion.eNull;\n"
+ "return true;\n"),
+ inline=True, bodyInHeader=True))
+
+ for t in self.type.flatMemberTypes:
+ vars = getUnionTypeTemplateVars(self.type,
+ t, self.descriptorProvider)
+ methods.append(vars["setter"])
+ if vars["name"] != "Object":
+ body = fill(
+ """
+ MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized);
+ mUnion.mType = mUnion.e${name};
+ return mUnion.mValue.m${name}.SetValue(${ctorArgs});
+ """,
+ **vars)
+ methods.append(ClassMethod("RawSetAs" + vars["name"],
+ vars["structType"] + "&",
+ vars["ctorArgList"],
+ bodyInHeader=True,
+ body=body,
+ visibility="private"))
+ # Provide a SetStringData() method to support string defaults.
+ if t.isByteString():
+ methods.append(
+ ClassMethod("SetStringData", "void",
+ [Argument("const nsDependentCString::char_type*", "aData"),
+ Argument("nsDependentCString::size_type", "aLength")],
+ inline=True, bodyInHeader=True,
+ body="RawSetAs%s().Rebind(aData, aLength);\n" % t.name))
+ elif t.isString():
+ methods.append(
+ ClassMethod("SetStringData", "void",
+ [Argument("const nsDependentString::char_type*", "aData"),
+ Argument("nsDependentString::size_type", "aLength")],
+ inline=True, bodyInHeader=True,
+ body="RawSetAs%s().Rebind(aData, aLength);\n" % t.name))
+
+ if vars["holderType"] is not None:
+ holderType = CGTemplatedType("Maybe",
+ CGGeneric(vars["holderType"])).define()
+ members.append(ClassMember("m%sHolder" % vars["name"],
+ holderType))
+
+ return CGClass(structName + "Argument",
+ members=members,
+ constructors=[ctor],
+ methods=methods,
+ disallowCopyConstruction=True).declare()
+
+ def define(self):
+ return ""
+
+ def deps(self):
+ return set()
+
+
+class ClassItem:
+ """ Use with CGClass """
+ def __init__(self, name, visibility):
+ self.name = name
+ self.visibility = visibility
+
+ def declare(self, cgClass):
+ assert False
+
+ def define(self, cgClass):
+ assert False
+
+
+class ClassBase(ClassItem):
+ def __init__(self, name, visibility='public'):
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return '%s %s' % (self.visibility, self.name)
+
+ def define(self, cgClass):
+ # Only in the header
+ return ''
+
+
+class ClassMethod(ClassItem):
+ def __init__(self, name, returnType, args, inline=False, static=False,
+ virtual=False, const=False, bodyInHeader=False,
+ templateArgs=None, visibility='public', body=None,
+ breakAfterReturnDecl="\n",
+ breakAfterSelf="\n", override=False):
+ """
+ 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
+ ClassItem.__init__(self, name, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.inline:
+ decorators.append('inline')
+ if declaring:
+ if self.static:
+ decorators.append('static')
+ if self.virtual:
+ decorators.append('virtual')
+ if decorators:
+ return ' '.join(decorators) + ' '
+ return ''
+
+ def getBody(self):
+ # Override me or pass a string to constructor
+ assert self.body is not None
+ return self.body
+
+ def declare(self, cgClass):
+ templateClause = ('template <%s>\n' % ', '.join(self.templateArgs)
+ if self.bodyInHeader and self.templateArgs else '')
+ args = ', '.join([a.declare() for a in self.args])
+ if self.bodyInHeader:
+ body = indent(self.getBody())
+ body = '\n{\n' + body + '}\n'
+ else:
+ body = ';\n'
+
+ return fill(
+ "${templateClause}${decorators}${returnType}${breakAfterReturnDecl}"
+ "${name}(${args})${const}${override}${body}"
+ "${breakAfterSelf}",
+ templateClause=templateClause,
+ decorators=self.getDecorators(True),
+ returnType=self.returnType,
+ breakAfterReturnDecl=self.breakAfterReturnDecl,
+ name=self.name,
+ args=args,
+ const=' const' if self.const else '',
+ override=' override' if self.override else '',
+ body=body,
+ breakAfterSelf=self.breakAfterSelf)
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ''
+
+ templateArgs = cgClass.templateArgs
+ if templateArgs:
+ if cgClass.templateSpecialization:
+ templateArgs = \
+ templateArgs[len(cgClass.templateSpecialization):]
+
+ if templateArgs:
+ templateClause = \
+ 'template <%s>\n' % ', '.join([str(a) for a in templateArgs])
+ else:
+ templateClause = ''
+
+ return fill(
+ """
+ ${templateClause}${decorators}${returnType}
+ ${className}::${name}(${args})${const}
+ {
+ $*{body}
+ }
+ """,
+ templateClause=templateClause,
+ decorators=self.getDecorators(False),
+ returnType=self.returnType,
+ className=cgClass.getNameString(),
+ name=self.name,
+ args=', '.join([a.define() for a in self.args]),
+ const=' const' if self.const else '',
+ body=self.getBody())
+
+
+class ClassUsingDeclaration(ClassItem):
+ """
+ Used for importing a name from a base class into a CGClass
+
+ baseClass is the name of the base class to import the name from
+
+ name is the name to import
+
+ visibility determines the visibility of the name (public,
+ protected, private), defaults to public.
+ """
+ def __init__(self, baseClass, name, visibility='public'):
+ self.baseClass = baseClass
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return "using %s::%s;\n\n" % (self.baseClass, self.name)
+
+ def define(self, cgClass):
+ return ''
+
+
+class ClassConstructor(ClassItem):
+ """
+ Used for adding a constructor to a CGClass.
+
+ args is a list of Argument objects that are the arguments taken by the
+ constructor.
+
+ inline should be True if the constructor should be marked inline.
+
+ bodyInHeader should be True if the body should be placed in the class
+ declaration in the header.
+
+ visibility determines the visibility of the constructor (public,
+ protected, private), defaults to private.
+
+ explicit should be True if the constructor should be marked explicit.
+
+ baseConstructors is a list of strings containing calls to base constructors,
+ defaults to None.
+
+ body contains a string with the code for the constructor, defaults to empty.
+ """
+ def __init__(self, args, inline=False, bodyInHeader=False,
+ visibility="private", explicit=False, constexpr=False, baseConstructors=None,
+ body=""):
+ assert not (inline and constexpr)
+ assert not (bodyInHeader and constexpr)
+ self.args = args
+ self.inline = inline or bodyInHeader
+ self.bodyInHeader = bodyInHeader or constexpr
+ self.explicit = explicit
+ self.constexpr = constexpr
+ self.baseConstructors = baseConstructors or []
+ self.body = body
+ ClassItem.__init__(self, None, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.explicit:
+ decorators.append('explicit')
+ if self.inline and declaring:
+ decorators.append('inline')
+ if self.constexpr and declaring:
+ decorators.append('constexpr')
+ if decorators:
+ return ' '.join(decorators) + ' '
+ return ''
+
+ def getInitializationList(self, cgClass):
+ items = [str(c) for c in self.baseConstructors]
+ for m in cgClass.members:
+ if not m.static:
+ initialize = m.body
+ if initialize:
+ items.append(m.name + "(" + initialize + ")")
+
+ if len(items) > 0:
+ return '\n : ' + ',\n '.join(items)
+ return ''
+
+ def getBody(self):
+ return self.body
+
+ def declare(self, cgClass):
+ args = ', '.join([a.declare() for a in self.args])
+ if self.bodyInHeader:
+ body = self.getInitializationList(cgClass) + '\n{\n' + indent(self.getBody()) + '}\n'
+ else:
+ body = ';\n'
+
+ return fill(
+ "${decorators}${className}(${args})${body}\n",
+ decorators=self.getDecorators(True),
+ className=cgClass.getNameString(),
+ args=args,
+ body=body)
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ''
+
+ return fill(
+ """
+ ${decorators}
+ ${className}::${className}(${args})${initializationList}
+ {
+ $*{body}
+ }
+ """,
+ decorators=self.getDecorators(False),
+ className=cgClass.getNameString(),
+ args=', '.join([a.define() for a in self.args]),
+ initializationList=self.getInitializationList(cgClass),
+ body=self.getBody())
+
+
+class ClassDestructor(ClassItem):
+ """
+ Used for adding a destructor to a CGClass.
+
+ inline should be True if the destructor should be marked inline.
+
+ bodyInHeader should be True if the body should be placed in the class
+ declaration in the header.
+
+ visibility determines the visibility of the destructor (public,
+ protected, private), defaults to private.
+
+ body contains a string with the code for the destructor, defaults to empty.
+
+ virtual determines whether the destructor is virtual, defaults to False.
+ """
+ def __init__(self, inline=False, bodyInHeader=False,
+ visibility="private", body='', virtual=False):
+ self.inline = inline or bodyInHeader
+ self.bodyInHeader = bodyInHeader
+ self.body = body
+ self.virtual = virtual
+ ClassItem.__init__(self, None, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.virtual and declaring:
+ decorators.append('virtual')
+ if self.inline and declaring:
+ decorators.append('inline')
+ if decorators:
+ return ' '.join(decorators) + ' '
+ return ''
+
+ def getBody(self):
+ return self.body
+
+ def declare(self, cgClass):
+ if self.bodyInHeader:
+ body = '\n{\n' + indent(self.getBody()) + '}\n'
+ else:
+ body = ';\n'
+
+ return fill(
+ "${decorators}~${className}()${body}\n",
+ decorators=self.getDecorators(True),
+ className=cgClass.getNameString(),
+ body=body)
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ''
+ return fill(
+ """
+ ${decorators}
+ ${className}::~${className}()
+ {
+ $*{body}
+ }
+ """,
+ decorators=self.getDecorators(False),
+ className=cgClass.getNameString(),
+ body=self.getBody())
+
+
+class ClassMember(ClassItem):
+ def __init__(self, name, type, visibility="private", static=False,
+ body=None, hasIgnoreInitCheckFlag=False):
+ self.type = type
+ self.static = static
+ self.body = body
+ self.hasIgnoreInitCheckFlag = hasIgnoreInitCheckFlag;
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return '%s%s%s %s;\n' % ('static ' if self.static else '',
+ 'MOZ_INIT_OUTSIDE_CTOR '
+ if self.hasIgnoreInitCheckFlag else '',
+ self.type, self.name)
+
+ def define(self, cgClass):
+ if not self.static:
+ return ''
+ if self.body:
+ body = " = " + self.body
+ else:
+ body = ""
+ return '%s %s::%s%s;\n' % (self.type, cgClass.getNameString(),
+ self.name, body)
+
+
+class ClassTypedef(ClassItem):
+ def __init__(self, name, type, visibility="public"):
+ self.type = type
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return 'typedef %s %s;\n' % (self.type, self.name)
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ''
+
+
+class ClassEnum(ClassItem):
+ def __init__(self, name, entries, values=None, visibility="public"):
+ self.entries = entries
+ self.values = values
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ entries = []
+ for i in range(0, len(self.entries)):
+ if not self.values or i >= len(self.values):
+ entry = '%s' % self.entries[i]
+ else:
+ entry = '%s = %s' % (self.entries[i], self.values[i])
+ entries.append(entry)
+ name = '' if not self.name else ' ' + self.name
+ return 'enum%s\n{\n%s\n};\n' % (name, indent(',\n'.join(entries)))
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ''
+
+
+class ClassUnion(ClassItem):
+ def __init__(self, name, entries, visibility="public"):
+ self.entries = [entry + ";\n" for entry in entries]
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return "union %s\n{\n%s\n};\n" % (self.name, indent(''.join(self.entries)))
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ''
+
+
+class CGClass(CGThing):
+ def __init__(self, name, bases=[], members=[], constructors=[],
+ destructor=None, methods=[],
+ typedefs=[], 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.typedefs = typedefs
+ 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"
+ "void operator=(const %s&) = delete;\n" % (name, name, name))
+
+ disallowedCopyConstructors = [DisallowedCopyConstructor()]
+ else:
+ disallowedCopyConstructors = []
+
+ order = [self.enums, self.unions,
+ self.typedefs, self.members,
+ self.constructors + disallowedCopyConstructors,
+ self.destructors, self.methods]
+
+ lastVisibility = self.defaultVisibility
+ pieces = []
+ for memberList in order:
+ code, lastVisibility = declareMembers(self, memberList, lastVisibility)
+
+ if code:
+ code = code.rstrip() + "\n" # remove extra blank lines at the end
+ pieces.append(code)
+
+ result += '\n'.join(pieces)
+ result += '};\n'
+ result = indent(result, len(self.indent))
+ return result
+
+ def define(self):
+ def defineMembers(cgClass, memberList, itemCount, separator=''):
+ result = ''
+ for member in memberList:
+ if itemCount != 0:
+ result = result + separator
+ definition = member.define(cgClass)
+ if definition:
+ # Member variables would only produce empty lines here.
+ result += definition
+ itemCount += 1
+ return (result, itemCount)
+
+ order = [(self.members, ''), (self.constructors, '\n'),
+ (self.destructors, '\n'), (self.methods, '\n')]
+
+ result = self.extradefinitions
+ itemCount = 0
+ for memberList, separator in order:
+ memberString, itemCount = defineMembers(self, memberList,
+ itemCount, separator)
+ result = result + memberString
+ return result
+
+
+class CGResolveOwnProperty(CGAbstractStaticMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'wrapper'),
+ Argument('JS::Handle<JSObject*>', 'obj'),
+ Argument('JS::Handle<jsid>', 'id'),
+ Argument('JS::MutableHandle<JS::PropertyDescriptor>', 'desc'),
+ ]
+ CGAbstractStaticMethod.__init__(self, descriptor, "ResolveOwnProperty",
+ "bool", args)
+
+ def definition_body(self):
+ return "return js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, wrapper, id, desc);\n"
+
+
+class CGResolveOwnPropertyViaResolve(CGAbstractBindingMethod):
+ """
+ An implementation of Xray ResolveOwnProperty stuff for things that have a
+ resolve hook.
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'wrapper'),
+ Argument('JS::Handle<JSObject*>', 'obj'),
+ Argument('JS::Handle<jsid>', 'id'),
+ Argument('JS::MutableHandle<JS::PropertyDescriptor>', 'desc')]
+ CGAbstractBindingMethod.__init__(self, descriptor,
+ "ResolveOwnPropertyViaResolve",
+ args, getThisObj="",
+ callArgs="")
+
+ def generate_code(self):
+ return CGGeneric(dedent("""
+ {
+ // Since we're dealing with an Xray, do the resolve on the
+ // underlying object first. That gives it a chance to
+ // define properties on the actual object as needed, and
+ // then use the fact that it created the objects as a flag
+ // to avoid re-resolving the properties if someone deletes
+ // them.
+ JSAutoCompartment ac(cx, obj);
+ JS::Rooted<JS::PropertyDescriptor> objDesc(cx);
+ if (!self->DoResolve(cx, obj, id, &objDesc)) {
+ return false;
+ }
+ // If desc.value() is undefined, then the DoResolve call
+ // has already defined the property on the object. Don't
+ // try to also define it.
+ if (objDesc.object() &&
+ !objDesc.value().isUndefined() &&
+ !JS_DefinePropertyById(cx, obj, id, objDesc)) {
+ return false;
+ }
+ }
+ return self->DoResolve(cx, wrapper, id, desc);
+ """))
+
+
+class CGEnumerateOwnProperties(CGAbstractStaticMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'wrapper'),
+ Argument('JS::Handle<JSObject*>', 'obj'),
+ Argument('JS::AutoIdVector&', 'props')]
+ CGAbstractStaticMethod.__init__(self, descriptor,
+ "EnumerateOwnProperties", "bool", args)
+
+ def definition_body(self):
+ return "return js::GetProxyHandler(obj)->ownPropertyKeys(cx, wrapper, props);\n"
+
+
+class CGEnumerateOwnPropertiesViaGetOwnPropertyNames(CGAbstractBindingMethod):
+ """
+ An implementation of Xray EnumerateOwnProperties stuff for things
+ that have a resolve hook.
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'wrapper'),
+ Argument('JS::Handle<JSObject*>', 'obj'),
+ Argument('JS::AutoIdVector&', 'props')]
+ CGAbstractBindingMethod.__init__(self, descriptor,
+ "EnumerateOwnPropertiesViaGetOwnPropertyNames",
+ args, getThisObj="",
+ callArgs="")
+
+ def generate_code(self):
+ return CGGeneric(dedent("""
+ AutoTArray<nsString, 8> names;
+ binding_detail::FastErrorResult rv;
+ self->GetOwnPropertyNames(cx, names, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return false;
+ }
+ // OK to pass null as "proxy" because it's ignored if
+ // shadowPrototypeProperties is true
+ return AppendNamedPropertyIds(cx, nullptr, names, true, props);
+ """))
+
+
+class CGPrototypeTraitsClass(CGClass):
+ def __init__(self, descriptor, indent=''):
+ templateArgs = [Argument('prototypes::ID', 'PrototypeID')]
+ templateSpecialization = ['prototypes::id::' + descriptor.name]
+ enums = [ClassEnum('', ['Depth'],
+ [descriptor.interface.inheritanceDepth()])]
+ CGClass.__init__(self, 'PrototypeTraits', indent=indent,
+ templateArgs=templateArgs,
+ templateSpecialization=templateSpecialization,
+ enums=enums, isStruct=True)
+
+ def deps(self):
+ return set()
+
+
+class CGClassForwardDeclare(CGThing):
+ def __init__(self, name, isStruct=False):
+ CGThing.__init__(self)
+ self.name = name
+ self.isStruct = isStruct
+
+ def declare(self):
+ type = 'struct' if self.isStruct else 'class'
+ return '%s %s;\n' % (type, self.name)
+
+ def define(self):
+ # Header only
+ return ''
+
+ def deps(self):
+ return set()
+
+
+class CGProxySpecialOperation(CGPerSignatureCall):
+ """
+ Base class for classes for calling an indexed or named special operation
+ (don't use this directly, use the derived classes below).
+
+ If checkFound is False, will just assert that the prop is found instead of
+ checking that it is before wrapping the value.
+
+ resultVar: See the docstring for CGCallGenerator.
+
+ foundVar: For getters and deleters, the generated code can also set a bool
+ variable, declared by the caller, if the given indexed or named property
+ already existed. If the caller wants this, it should pass the name of the
+ bool variable as the foundVar keyword argument to the constructor. The
+ caller is responsible for declaring the variable and initializing it to
+ false.
+ """
+ def __init__(self, descriptor, operation, checkFound=True,
+ argumentHandleValue=None, resultVar=None, foundVar=None):
+ self.checkFound = checkFound
+ self.foundVar = foundVar or "found"
+
+ nativeName = MakeNativeName(descriptor.binaryNameFor(operation))
+ operation = descriptor.operations[operation]
+ assert len(operation.signatures()) == 1
+ signature = operation.signatures()[0]
+
+ returnType, arguments = signature
+
+ # We pass len(arguments) as the final argument so that the
+ # CGPerSignatureCall won't do any argument conversion of its own.
+ CGPerSignatureCall.__init__(self, returnType, arguments, nativeName,
+ False, descriptor, operation,
+ len(arguments), resultVar=resultVar)
+
+ if operation.isSetter() or operation.isCreator():
+ # arguments[0] is the index or name of the item that we're setting.
+ argument = arguments[1]
+ info = getJSToNativeConversionInfo(
+ argument.type, descriptor,
+ treatNullAs=argument.treatNullAs,
+ sourceDescription=("value being assigned to %s setter" %
+ descriptor.interface.identifier.name))
+ if argumentHandleValue is None:
+ argumentHandleValue = "desc.value()"
+ rootedValue = fill(
+ """
+ JS::Rooted<JS::Value> rootedValue(cx, ${argumentHandleValue});
+ """,
+ argumentHandleValue = argumentHandleValue)
+ templateValues = {
+ "declName": argument.identifier.name,
+ "holderName": argument.identifier.name + "_holder",
+ "val": argumentHandleValue,
+ "maybeMutableVal": "&rootedValue",
+ "obj": "obj",
+ "passedToJSImpl": "false"
+ }
+ self.cgRoot.prepend(instantiateJSToNativeConversion(info, templateValues))
+ # rootedValue needs to come before the conversion, so we
+ # need to prepend it last.
+ self.cgRoot.prepend(CGGeneric(rootedValue))
+ elif operation.isGetter() or operation.isDeleter():
+ if foundVar is None:
+ self.cgRoot.prepend(CGGeneric("bool found = false;\n"))
+
+ def getArguments(self):
+ args = [(a, a.identifier.name) for a in self.arguments]
+ if self.idlNode.isGetter() or self.idlNode.isDeleter():
+ args.append((FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean],
+ self.idlNode),
+ self.foundVar))
+ return args
+
+ def wrap_return_value(self):
+ if not self.idlNode.isGetter() or self.templateValues is None:
+ return ""
+
+ wrap = CGGeneric(wrapForType(self.returnType, self.descriptor, self.templateValues))
+ if self.checkFound:
+ wrap = CGIfWrapper(wrap, self.foundVar)
+ else:
+ wrap = CGList([CGGeneric("MOZ_ASSERT(" + self.foundVar + ");\n"), wrap])
+ return "\n" + wrap.define()
+
+
+class CGProxyIndexedOperation(CGProxySpecialOperation):
+ """
+ Class to generate a call to an indexed operation.
+
+ If doUnwrap is False, the caller is responsible for making sure a variable
+ named 'self' holds the C++ object somewhere where the code we generate
+ will see it.
+
+ If checkFound is False, will just assert that the prop is found instead of
+ checking that it is before wrapping the value.
+
+ resultVar: See the docstring for CGCallGenerator.
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+ def __init__(self, descriptor, name, doUnwrap=True, checkFound=True,
+ argumentHandleValue=None, resultVar=None, foundVar=None):
+ self.doUnwrap = doUnwrap
+ CGProxySpecialOperation.__init__(self, descriptor, name, checkFound,
+ argumentHandleValue=argumentHandleValue,
+ resultVar=resultVar,
+ foundVar=foundVar)
+
+ def define(self):
+ # Our first argument is the id we're getting.
+ argName = self.arguments[0].identifier.name
+ if argName == "index":
+ # We already have our index in a variable with that name
+ setIndex = ""
+ else:
+ setIndex = "uint32_t %s = index;\n" % argName
+ if self.doUnwrap:
+ unwrap = "%s* self = UnwrapProxy(proxy);\n" % self.descriptor.nativeType
+ else:
+ unwrap = ""
+ return (setIndex + unwrap +
+ CGProxySpecialOperation.define(self))
+
+
+class CGProxyIndexedGetter(CGProxyIndexedOperation):
+ """
+ Class to generate a call to an indexed getter. If templateValues is not None
+ the returned value will be wrapped with wrapForType using templateValues.
+
+ If doUnwrap is False, the caller is responsible for making sure a variable
+ named 'self' holds the C++ object somewhere where the code we generate
+ will see it.
+
+ If checkFound is False, will just assert that the prop is found instead of
+ checking that it is before wrapping the value.
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+ def __init__(self, descriptor, templateValues=None, doUnwrap=True,
+ checkFound=True, foundVar=None):
+ self.templateValues = templateValues
+ CGProxyIndexedOperation.__init__(self, descriptor, 'IndexedGetter',
+ doUnwrap, checkFound, foundVar=foundVar)
+
+
+class CGProxyIndexedPresenceChecker(CGProxyIndexedGetter):
+ """
+ Class to generate a call that checks whether an indexed property exists.
+
+ For now, we just delegate to CGProxyIndexedGetter
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+ def __init__(self, descriptor, foundVar):
+ CGProxyIndexedGetter.__init__(self, descriptor, foundVar=foundVar)
+ self.cgRoot.append(CGGeneric("(void)result;\n"))
+
+
+class CGProxyIndexedSetter(CGProxyIndexedOperation):
+ """
+ Class to generate a call to an indexed setter.
+ """
+ def __init__(self, descriptor, argumentHandleValue=None):
+ CGProxyIndexedOperation.__init__(self, descriptor, 'IndexedSetter',
+ argumentHandleValue=argumentHandleValue)
+
+
+class CGProxyNamedOperation(CGProxySpecialOperation):
+ """
+ Class to generate a call to a named operation.
+
+ 'value' is the jsval to use for the name; None indicates that it should be
+ gotten from the property id.
+
+ resultVar: See the docstring for CGCallGenerator.
+
+ foundVar: See the docstring for CGProxySpecialOperation.
+ """
+ def __init__(self, descriptor, name, value=None, argumentHandleValue=None,
+ resultVar=None, foundVar=None):
+ CGProxySpecialOperation.__init__(self, descriptor, name,
+ argumentHandleValue=argumentHandleValue,
+ resultVar=resultVar,
+ foundVar=foundVar)
+ self.value = value
+
+ 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 += "binding_detail::FakeString %s;\n" % argName
+
+ main = fill(
+ """
+ ${nativeType}* self = UnwrapProxy(proxy);
+ $*{op}
+ """,
+ nativeType=self.descriptor.nativeType,
+ op=CGProxySpecialOperation.define(self))
+
+ 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, argumentHandleValue=None):
+ CGProxyNamedOperation.__init__(self, descriptor, 'NamedSetter',
+ argumentHandleValue=argumentHandleValue)
+
+
+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::GetProxyPrivate(obj).toPrivate());
+ """,
+ type=self.descriptor.nativeType)
+
+
+class CGDOMJSProxyHandler_getOwnPropDescriptor(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'proxy'),
+ Argument('JS::Handle<jsid>', 'id'),
+ Argument('bool', 'ignoreNamedProps'),
+ Argument('JS::MutableHandle<JS::PropertyDescriptor>', 'desc')]
+ ClassMethod.__init__(self, "getOwnPropDescriptor", "bool", args,
+ virtual=True, override=True, const=True)
+ self.descriptor = descriptor
+
+ def getBody(self):
+ indexedGetter = self.descriptor.operations['IndexedGetter']
+ indexedSetter = self.descriptor.operations['IndexedSetter']
+
+ if self.descriptor.supportsIndexedProperties():
+ readonly = toStringBool(indexedSetter is None)
+ fillDescriptor = "FillPropertyDescriptor(desc, proxy, %s);\nreturn true;\n" % readonly
+ templateValues = {
+ 'jsvalRef': 'desc.value()',
+ 'jsvalHandle': 'desc.value()',
+ 'obj': 'proxy',
+ 'successCode': fillDescriptor
+ }
+ getIndexed = fill(
+ """
+ uint32_t index = GetArrayIndexFromId(cx, id);
+ if (IsArrayIndex(index)) {
+ $*{callGetter}
+ }
+
+ """,
+ callGetter=CGProxyIndexedGetter(self.descriptor, templateValues).define())
+ else:
+ getIndexed = ""
+
+ if self.descriptor.supportsNamedProperties():
+ operations = self.descriptor.operations
+ readonly = toStringBool(operations['NamedSetter'] is None)
+ fillDescriptor = (
+ "FillPropertyDescriptor(desc, proxy, %s, %s);\n"
+ "return true;\n" %
+ (readonly,
+ toStringBool(self.descriptor.namedPropertiesEnumerable)))
+ templateValues = {'jsvalRef': 'desc.value()', 'jsvalHandle': 'desc.value()',
+ 'obj': 'proxy', 'successCode': fillDescriptor}
+
+ computeCondition = dedent("""
+ bool hasOnProto;
+ if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
+ return false;
+ }
+ callNamedGetter = !hasOnProto;
+ """)
+ if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+ computeCondition = fill(
+ """
+ if (!isXray) {
+ callNamedGetter = true;
+ } else {
+ $*{hasOnProto}
+ }
+ """,
+ hasOnProto=computeCondition)
+
+ outerCondition = "!ignoreNamedProps"
+ if self.descriptor.supportsIndexedProperties():
+ outerCondition = "!IsArrayIndex(index) && " + outerCondition
+
+ namedGetCode = CGProxyNamedGetter(self.descriptor,
+ templateValues).define()
+ namedGet = fill("""
+ bool callNamedGetter = false;
+ if (${outerCondition}) {
+ $*{computeCondition}
+ }
+ if (callNamedGetter) {
+ $*{namedGetCode}
+ }
+ """,
+ outerCondition=outerCondition,
+ computeCondition=computeCondition,
+ namedGetCode=namedGetCode)
+ namedGet += "\n"
+ else:
+ namedGet = ""
+
+ return fill(
+ """
+ bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);
+ $*{getIndexed}
+ JS::Rooted<JSObject*> expando(cx);
+ if (!isXray && (expando = GetExpandoObject(proxy))) {
+ if (!JS_GetOwnPropertyDescriptorById(cx, expando, id, desc)) {
+ return false;
+ }
+ if (desc.object()) {
+ // Pretend the property lives on the wrapper.
+ desc.object().set(proxy);
+ return true;
+ }
+ }
+
+ $*{namedGet}
+ desc.object().set(nullptr);
+ return true;
+ """,
+ getIndexed=getIndexed,
+ 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*', 'defined')]
+ 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:
+ if self.descriptor.operations['IndexedCreator'] is not indexedSetter:
+ raise TypeError("Can't handle creator that's different from the setter")
+ set += fill(
+ """
+ uint32_t index = GetArrayIndexFromId(cx, id);
+ if (IsArrayIndex(index)) {
+ *defined = true;
+ $*{callSetter}
+ return opresult.succeed();
+ }
+ """,
+ 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(cx, id))) {
+ *defined = true;
+ return opresult.failNoIndexedSetter();
+ }
+ """)
+
+ namedSetter = self.descriptor.operations['NamedSetter']
+ if namedSetter:
+ if self.descriptor.hasUnforgeableMembers:
+ raise TypeError("Can't handle a named setter on an interface "
+ "that has unforgeables. Figure out how that "
+ "should work!")
+ if self.descriptor.operations['NamedCreator'] is not namedSetter:
+ raise TypeError("Can't handle creator that's different from the setter")
+ # If we support indexed properties, we won't get down here for
+ # indices, so we can just do our setter unconditionally here.
+ set += fill(
+ """
+ *defined = true;
+ $*{callSetter}
+
+ return opresult.succeed();
+ """,
+ callSetter=CGProxyNamedSetter(self.descriptor).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(
+ """
+ bool found = false;
+ $*{presenceChecker}
+
+ if (found) {
+ *defined = true;
+ return opresult.failNoNamedSetter();
+ }
+ """,
+ presenceChecker=CGProxyNamedPresenceChecker(self.descriptor, foundVar="found").define())
+ set += ("return mozilla::dom::DOMProxyHandler::defineProperty(%s);\n" %
+ ", ".join(a.name for a in self.args))
+ return set
+
+
+def getDeleterBody(descriptor, type, foundVar=None):
+ """
+ type should be "Named" or "Indexed"
+
+ The possible outcomes:
+ - an error happened (the emitted code returns false)
+ - own property not found (foundVar=false, deleteSucceeded=true)
+ - own property found and deleted (foundVar=true, deleteSucceeded=true)
+ - own property found but can't be deleted (foundVar=true, deleteSucceeded=false)
+ """
+ assert type in ("Named", "Indexed")
+ deleter = descriptor.operations[type + 'Deleter']
+ if deleter:
+ assert type == "Named"
+ assert foundVar is not None
+ if descriptor.hasUnforgeableMembers:
+ raise TypeError("Can't handle a deleter on an interface "
+ "that has unforgeables. Figure out how "
+ "that should work!")
+ # See if the deleter method is fallible.
+ t = deleter.signatures()[0][0]
+ if t.isPrimitive() and not t.nullable() and t.tag() == IDLType.Tags.bool:
+ # The deleter method has a boolean return value. When a
+ # property is found, the return value indicates whether it
+ # was successfully deleted.
+ setDS = fill(
+ """
+ if (!${foundVar}) {
+ deleteSucceeded = true;
+ }
+ """,
+ foundVar=foundVar)
+ else:
+ # No boolean return value: if a property is found,
+ # deleting it always succeeds.
+ setDS = "deleteSucceeded = true;\n"
+
+ body = (CGProxyNamedDeleter(descriptor,
+ resultVar="deleteSucceeded",
+ foundVar=foundVar).define() +
+ setDS)
+ elif getattr(descriptor, "supports%sProperties" % type)():
+ presenceCheckerClass = globals()["CGProxy%sPresenceChecker" % type]
+ foundDecl = ""
+ if foundVar is None:
+ foundVar = "found"
+ foundDecl = "bool found = false;\n"
+ body = fill(
+ """
+ $*{foundDecl}
+ $*{presenceChecker}
+ deleteSucceeded = !${foundVar};
+ """,
+ foundDecl=foundDecl,
+ presenceChecker=presenceCheckerClass(descriptor, foundVar=foundVar).define(),
+ foundVar=foundVar)
+ else:
+ body = None
+ return body
+
+
+class CGDeleteNamedProperty(CGAbstractStaticMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'xray'),
+ Argument('JS::Handle<JSObject*>', 'proxy'),
+ Argument('JS::Handle<jsid>', 'id'),
+ Argument('JS::ObjectOpResult&', 'opresult')]
+ CGAbstractStaticMethod.__init__(self, descriptor, "DeleteNamedProperty",
+ "bool", args)
+
+ def definition_body(self):
+ return fill(
+ """
+ MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(xray));
+ MOZ_ASSERT(js::IsProxy(proxy));
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy));
+ JSAutoCompartment ac(cx, proxy);
+ bool deleteSucceeded;
+ 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");
+
+ """)
+
+ indexedBody = getDeleterBody(self.descriptor, "Indexed")
+ if indexedBody is not None:
+ delete += fill(
+ """
+ uint32_t index = GetArrayIndexFromId(cx, id);
+ if (IsArrayIndex(index)) {
+ bool deleteSucceeded;
+ $*{indexedBody}
+ return deleteSucceeded ? opresult.succeed() : opresult.failCantDelete();
+ }
+ """,
+ indexedBody=indexedBody)
+
+ namedBody = getDeleterBody(self.descriptor, "Named", foundVar="found")
+ if namedBody is not None:
+ delete += dedent(
+ """
+ // Try named delete only if the named property visibility
+ // algorithm says the property is visible.
+ bool tryNamedDelete = true;
+ { // Scope for expando
+ JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
+ if (expando) {
+ bool hasProp;
+ if (!JS_HasPropertyById(cx, expando, id, &hasProp)) {
+ return false;
+ }
+ tryNamedDelete = !hasProp;
+ }
+ }
+ """)
+
+ if not self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+ delete += dedent(
+ """
+ if (tryNamedDelete) {
+ bool hasOnProto;
+ if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
+ return false;
+ }
+ tryNamedDelete = !hasOnProto;
+ }
+ """)
+
+ # We always return above for an index id in the case when we support
+ # indexed properties, so we can just treat the id as a name
+ # unconditionally here.
+ delete += fill(
+ """
+ if (tryNamedDelete) {
+ bool found = false;
+ bool deleteSucceeded;
+ $*{namedBody}
+ if (found) {
+ return deleteSucceeded ? opresult.succeed() : opresult.failCantDelete();
+ }
+ }
+ """,
+ namedBody=namedBody)
+
+ delete += dedent("""
+
+ return dom::DOMProxyHandler::delete_(cx, proxy, id, opresult);
+ """)
+
+ return delete
+
+
+class CGDOMJSProxyHandler_ownPropNames(ClassMethod):
+ def __init__(self, descriptor, ):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'proxy'),
+ Argument('unsigned', 'flags'),
+ Argument('JS::AutoIdVector&', 'props')]
+ ClassMethod.__init__(self, "ownPropNames", "bool", args,
+ virtual=True, override=True, const=True)
+ self.descriptor = descriptor
+
+ def getBody(self):
+ # Per spec, we do indices, then named props, then everything else
+ if self.descriptor.supportsIndexedProperties():
+ addIndices = dedent("""
+
+ uint32_t length = UnwrapProxy(proxy)->Length();
+ MOZ_ASSERT(int32_t(length) >= 0);
+ for (int32_t i = 0; i < int32_t(length); ++i) {
+ if (!props.append(INT_TO_JSID(i))) {
+ return false;
+ }
+ }
+ """)
+ else:
+ addIndices = ""
+
+ if self.descriptor.supportsNamedProperties():
+ if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+ shadow = "!isXray"
+ else:
+ shadow = "false"
+ addNames = fill(
+ """
+ nsTArray<nsString> names;
+ UnwrapProxy(proxy)->GetSupportedNames(names);
+ if (!AppendNamedPropertyIds(cx, proxy, names, ${shadow}, props)) {
+ return false;
+ }
+ """,
+ shadow=shadow)
+ if not self.descriptor.namedPropertiesEnumerable:
+ addNames = CGIfWrapper(CGGeneric(addNames),
+ "flags & JSITER_HIDDEN").define()
+ addNames = "\n" + addNames
+ else:
+ addNames = ""
+
+ return fill(
+ """
+ bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);
+ $*{addIndices}
+ $*{addNames}
+
+ JS::Rooted<JSObject*> expando(cx);
+ if (!isXray && (expando = DOMProxyHandler::GetExpandoObject(proxy)) &&
+ !js::GetPropertyKeys(cx, expando, flags, &props)) {
+ return false;
+ }
+
+ return true;
+ """,
+ addIndices=addIndices,
+ addNames=addNames)
+
+
+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.supportsIndexedProperties():
+ indexed = fill(
+ """
+ uint32_t index = GetArrayIndexFromId(cx, id);
+ if (IsArrayIndex(index)) {
+ bool found = false;
+ $*{presenceChecker}
+
+ *bp = found;
+ return true;
+ }
+
+ """,
+ presenceChecker=CGProxyIndexedPresenceChecker(self.descriptor, foundVar="found").define())
+ else:
+ indexed = ""
+
+ if self.descriptor.supportsNamedProperties():
+ # If we support indexed properties we always return above for index
+ # property names, so no need to check for those here.
+ named = fill(
+ """
+ bool found = false;
+ $*{presenceChecker}
+
+ *bp = found;
+ """,
+ presenceChecker=CGProxyNamedPresenceChecker(self.descriptor, foundVar="found").define())
+ if not self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+ named = fill(
+ """
+ bool hasOnProto;
+ if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) {
+ return false;
+ }
+ if (!hasOnProto) {
+ $*{protoLacksProperty}
+ return true;
+ }
+ """,
+ protoLacksProperty=named)
+ named += "*bp = false;\n"
+ else:
+ named += "\n"
+ else:
+ named = "*bp = false;\n"
+
+ return fill(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+ $*{indexed}
+
+ 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;
+ """,
+ indexed=indexed,
+ 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):
+ getUnforgeableOrExpando = dedent("""
+ { // Scope for expando
+ JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy));
+ if (expando) {
+ bool hasProp;
+ if (!JS_HasPropertyById(cx, expando, id, &hasProp)) {
+ return false;
+ }
+
+ if (hasProp) {
+ // Forward the get to the expando object, but our receiver is whatever our
+ // receiver is.
+ return JS_ForwardGetPropertyTo(cx, expando, id, receiver, vp);
+ }
+ }
+ }
+ """)
+
+ templateValues = {'jsvalRef': 'vp', 'jsvalHandle': 'vp', 'obj': 'proxy'}
+
+ if self.descriptor.supportsIndexedProperties():
+ getIndexedOrExpando = fill(
+ """
+ uint32_t index = GetArrayIndexFromId(cx, 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 = dedent("""
+ bool foundOnPrototype;
+ if (!GetPropertyOnPrototype(cx, proxy, receiver, id, &foundOnPrototype, vp)) {
+ return false;
+ }
+
+ if (foundOnPrototype) {
+ return true;
+ }
+
+ """)
+ if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+ getNamed = getNamed + getOnPrototype
+ else:
+ getNamed = getOnPrototype + getNamed
+
+ return fill(
+ """
+ MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+ $*{indexedOrExpando}
+
+ $*{named}
+ vp.setUndefined();
+ return true;
+ """,
+ indexedOrExpando=getIndexedOrExpando,
+ named=getNamed)
+
+
+class CGDOMJSProxyHandler_setCustom(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'proxy'),
+ Argument('JS::Handle<jsid>', 'id'),
+ Argument('JS::Handle<JS::Value>', 'v'),
+ Argument('bool*', 'done')]
+ ClassMethod.__init__(self, "setCustom", "bool", args, virtual=True, override=True, const=True)
+ self.descriptor = descriptor
+
+ def getBody(self):
+ assertion = ("MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),\n"
+ ' "Should not have a XrayWrapper here");\n')
+
+ # Correctness first. If we have a NamedSetter and [OverrideBuiltins],
+ # always call the NamedSetter and never do anything else.
+ namedSetter = self.descriptor.operations['NamedSetter']
+ if (namedSetter is not None and
+ self.descriptor.interface.getExtendedAttribute('OverrideBuiltins')):
+ # Check assumptions.
+ if self.descriptor.supportsIndexedProperties():
+ raise ValueError("In interface " + self.descriptor.name + ": " +
+ "Can't cope with [OverrideBuiltins] and an indexed getter")
+ if self.descriptor.operations['NamedCreator'] is not namedSetter:
+ raise ValueError("In interface " + self.descriptor.name + ": " +
+ "Can't cope with named setter that is not also a named creator")
+ if self.descriptor.hasUnforgeableMembers:
+ raise ValueError("In interface " + self.descriptor.name + ": " +
+ "Can't cope with [OverrideBuiltins] and unforgeable members")
+
+ callSetter = CGProxyNamedSetter(self.descriptor, argumentHandleValue="v")
+ return (assertion +
+ callSetter.define() +
+ "*done = true;\n"
+ "return true;\n")
+
+ # 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:
+ if self.descriptor.operations['IndexedCreator'] is not indexedSetter:
+ raise ValueError("In interface " + self.descriptor.name + ": " +
+ "Can't cope with indexed setter that is not " +
+ "also an indexed creator")
+ setIndexed = fill(
+ """
+ uint32_t index = GetArrayIndexFromId(cx, id);
+ if (IsArrayIndex(index)) {
+ $*{callSetter}
+ *done = true;
+ return true;
+ }
+
+ """,
+ 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):
+ return 'return "%s";\n' % self.descriptor.name
+
+
+class CGDOMJSProxyHandler_finalizeInBackground(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('const JS::Value&', 'priv')]
+ ClassMethod.__init__(self, "finalizeInBackground", "bool", args,
+ virtual=True, override=True, const=True)
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return "return false;\n"
+
+
+class CGDOMJSProxyHandler_finalize(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSFreeOp*', 'fop'), Argument('JSObject*', 'proxy')]
+ ClassMethod.__init__(self, "finalize", "void", args,
+ virtual=True, override=True, const=True)
+ self.descriptor = descriptor
+
+ def getBody(self):
+ return (("%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(proxy);\n" %
+ (self.descriptor.nativeType, self.descriptor.nativeType)) +
+ finalizeHook(self.descriptor, FINALIZE_HOOK_NAME, self.args[0].name).define())
+
+
+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()
+
+ 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();
+ // 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,
+ get=get)
+
+
+class CGDOMJSProxyHandler_getInstance(ClassMethod):
+ def __init__(self):
+ ClassMethod.__init__(self, "getInstance", "const DOMProxyHandler*", [], static=True)
+
+ def getBody(self):
+ return dedent("""
+ static const DOMProxyHandler instance;
+ return &instance;
+ """)
+
+
+class CGDOMJSProxyHandler_getPrototypeIfOrdinary(ClassMethod):
+ def __init__(self):
+ args = [Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JSObject*>', 'proxy'),
+ Argument('bool*', 'isOrdinary'),
+ Argument('JS::MutableHandle<JSObject*>', 'proto')]
+
+ ClassMethod.__init__(self, "getPrototypeIfOrdinary", "bool", args,
+ virtual=True, override=True, const=True)
+
+ def getBody(self):
+ return dedent("""
+ *isOrdinary = false;
+ return true;
+ """)
+
+
+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(CGClass):
+ def __init__(self, descriptor):
+ assert (descriptor.supportsIndexedProperties() or
+ descriptor.supportsNamedProperties() or
+ descriptor.hasNonOrdinaryGetPrototypeOf())
+ methods = [CGDOMJSProxyHandler_getOwnPropDescriptor(descriptor),
+ CGDOMJSProxyHandler_defineProperty(descriptor),
+ ClassUsingDeclaration("mozilla::dom::DOMProxyHandler",
+ "defineProperty"),
+ CGDOMJSProxyHandler_ownPropNames(descriptor),
+ CGDOMJSProxyHandler_hasOwn(descriptor),
+ CGDOMJSProxyHandler_get(descriptor),
+ CGDOMJSProxyHandler_className(descriptor),
+ CGDOMJSProxyHandler_finalizeInBackground(descriptor),
+ CGDOMJSProxyHandler_finalize(descriptor),
+ CGDOMJSProxyHandler_getInstance(),
+ CGDOMJSProxyHandler_delete(descriptor)]
+ constructors = [
+ ClassConstructor(
+ [],
+ constexpr=True,
+ visibility="public",
+ explicit=True)
+ ]
+
+ if descriptor.supportsIndexedProperties():
+ methods.append(CGDOMJSProxyHandler_getElements(descriptor))
+ if (descriptor.operations['IndexedSetter'] is not None or
+ (descriptor.operations['NamedSetter'] is not None and
+ descriptor.interface.getExtendedAttribute('OverrideBuiltins'))):
+ methods.append(CGDOMJSProxyHandler_setCustom(descriptor))
+ if descriptor.hasNonOrdinaryGetPrototypeOf():
+ methods.append(CGDOMJSProxyHandler_getPrototypeIfOrdinary())
+ if descriptor.operations['LegacyCaller']:
+ methods.append(CGDOMJSProxyHandler_call())
+ methods.append(CGDOMJSProxyHandler_isCallable())
+
+ if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+ parentClass = 'ShadowingDOMProxyHandler'
+ 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.isGenericMethod = False
+ self.isCrossOriginMethod = False
+ self.isPromiseReturningMethod = False
+ self.isGenericGetter = False
+ self.isLenientGetter = False
+ self.isCrossOriginGetter = False
+ self.isGenericSetter = False
+ self.isLenientSetter = False
+ self.isCrossOriginSetter = False
+ self.isJsonifier = False
+
+
+def memberProperties(m, descriptor):
+ props = MemberProperties()
+ if m.isMethod():
+ if m == descriptor.operations['Jsonifier']:
+ props.isGenericMethod = descriptor.needsSpecialGenericOps()
+ props.isJsonifier = True
+ elif (not m.isIdentifierLess() or m == descriptor.operations['Stringifier']):
+ if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
+ if descriptor.needsSpecialGenericOps():
+ if m.returnsPromise():
+ props.isPromiseReturningMethod = True
+ else:
+ props.isGenericMethod = True
+ if m.getExtendedAttribute("CrossOriginCallable"):
+ props.isCrossOriginMethod = True
+ elif m.isAttr():
+ if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
+ if m.hasLenientThis():
+ props.isLenientGetter = True
+ elif m.getExtendedAttribute("CrossOriginReadable"):
+ props.isCrossOriginGetter = True
+ elif descriptor.needsSpecialGenericOps():
+ props.isGenericGetter = True
+ if not m.readonly:
+ if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject():
+ if m.hasLenientThis():
+ props.isLenientSetter = True
+ elif IsCrossOriginWritable(m, descriptor):
+ props.isCrossOriginSetter = True
+ elif descriptor.needsSpecialGenericOps():
+ props.isGenericSetter = True
+ elif m.getExtendedAttribute("PutForwards"):
+ if IsCrossOriginWritable(m, descriptor):
+ props.isCrossOriginSetter = True
+ elif descriptor.needsSpecialGenericOps():
+ props.isGenericSetter = True
+ elif (m.getExtendedAttribute("Replaceable") or
+ m.getExtendedAttribute("LenientSetter")):
+ if descriptor.needsSpecialGenericOps():
+ props.isGenericSetter = True
+
+ return props
+
+
+class CGDescriptor(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+
+ assert not descriptor.concrete or descriptor.interface.hasInterfacePrototypeObject()
+
+ self._deps = descriptor.interface.getDeps()
+
+ cgThings = []
+ cgThings.append(CGGeneric(declare="typedef %s NativeType;\n" %
+ descriptor.nativeType))
+ parent = descriptor.interface.parent
+ if parent:
+ cgThings.append(CGGeneric("static_assert(IsRefcounted<NativeType>::value == IsRefcounted<%s::NativeType>::value,\n"
+ " \"Can't inherit from an interface with a different ownership model.\");\n" %
+ toBindingNamespace(descriptor.parentPrototypeName)))
+
+ # These are set to true if at least one non-static
+ # method/getter/setter or jsonifier exist on the interface.
+ (hasMethod, hasGetter, hasLenientGetter, hasSetter, hasLenientSetter,
+ hasPromiseReturningMethod) = False, False, False, False, False, False
+ jsonifierMethod = None
+ crossOriginMethods, crossOriginGetters, crossOriginSetters = set(), set(), set()
+ unscopableNames = list()
+ for n in descriptor.interface.namedConstructors:
+ cgThings.append(CGClassConstructor(descriptor, n,
+ NamedConstructorName(n)))
+ for m in descriptor.interface.members:
+ if m.isMethod() and m.identifier.name == 'queryInterface':
+ continue
+
+ props = memberProperties(m, descriptor)
+
+ if m.isMethod():
+ if m.getExtendedAttribute("Unscopable"):
+ assert not m.isStatic()
+ unscopableNames.append(m.identifier.name)
+ if props.isJsonifier:
+ jsonifierMethod = 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:
+ crossOriginMethods.add(m.identifier.name)
+ # 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.stringifier:
+ raise TypeError("Stringifier attributes not supported yet. "
+ "See bug 824857.\n"
+ "%s" % m.location)
+ 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():
+ if isNonExposedNavigatorObjectGetter(m, descriptor):
+ continue
+ cgThings.append(CGSpecializedGetter(descriptor, m))
+ if props.isCrossOriginGetter:
+ crossOriginGetters.add(m.identifier.name)
+ 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:
+ crossOriginSetters.add(m.identifier.name)
+ elif m.getExtendedAttribute("PutForwards"):
+ cgThings.append(CGSpecializedForwardingSetter(descriptor, m))
+ if props.isCrossOriginSetter:
+ crossOriginSetters.add(m.identifier.name)
+ elif m.getExtendedAttribute("Replaceable"):
+ cgThings.append(CGSpecializedReplaceableSetter(descriptor, m))
+ elif m.getExtendedAttribute("LenientSetter"):
+ cgThings.append(CGSpecializedLenientSetter(descriptor, m))
+ if (not m.isStatic() and
+ descriptor.interface.hasInterfacePrototypeObject()):
+ cgThings.append(CGMemberJITInfo(descriptor, m))
+
+ hasMethod = hasMethod or props.isGenericMethod
+ hasPromiseReturningMethod = (hasPromiseReturningMethod or
+ props.isPromiseReturningMethod)
+ hasGetter = hasGetter or props.isGenericGetter
+ hasLenientGetter = hasLenientGetter or props.isLenientGetter
+ hasSetter = hasSetter or props.isGenericSetter
+ hasLenientSetter = hasLenientSetter or props.isLenientSetter
+
+ if jsonifierMethod:
+ cgThings.append(CGJsonifyAttributesMethod(descriptor))
+ cgThings.append(CGJsonifierMethod(descriptor, jsonifierMethod))
+ cgThings.append(CGMemberJITInfo(descriptor, jsonifierMethod))
+ if hasMethod:
+ cgThings.append(CGGenericMethod(descriptor))
+ if hasPromiseReturningMethod:
+ cgThings.append(CGGenericPromiseReturningMethod(descriptor))
+ if len(crossOriginMethods):
+ cgThings.append(CGGenericMethod(descriptor,
+ allowCrossOriginThis=True))
+ if hasGetter:
+ cgThings.append(CGGenericGetter(descriptor))
+ if hasLenientGetter:
+ cgThings.append(CGGenericGetter(descriptor, lenientThis=True))
+ if len(crossOriginGetters):
+ cgThings.append(CGGenericGetter(descriptor,
+ allowCrossOriginThis=True))
+ if hasSetter:
+ cgThings.append(CGGenericSetter(descriptor))
+ if hasLenientSetter:
+ cgThings.append(CGGenericSetter(descriptor, lenientThis=True))
+ if len(crossOriginSetters):
+ cgThings.append(CGGenericSetter(descriptor,
+ allowCrossOriginThis=True))
+
+ if descriptor.interface.isNavigatorProperty():
+ cgThings.append(CGConstructNavigatorObject(descriptor))
+
+ 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 descriptor.concrete and descriptor.wrapperCache:
+ cgThings.append(CGClassObjectMovedHook(descriptor))
+
+ # Generate the _ClearCachedFooValue methods before the property arrays that use them.
+ if descriptor.interface.isJSImplemented():
+ for m in clearableCachedAttrs(descriptor):
+ cgThings.append(CGJSImplClearCachedValueMethod(descriptor, m))
+
+ # Need to output our generated hasinstance bits before
+ # PropertyArrays tries to use them.
+ if (descriptor.interface.hasInterfaceObject() and
+ NeedsGeneratedHasInstance(descriptor)):
+ cgThings.append(CGHasInstanceHook(descriptor))
+
+ properties = PropertyArrays(descriptor)
+ cgThings.append(CGGeneric(define=str(properties)))
+ cgThings.append(CGNativeProperties(descriptor, properties))
+
+ if descriptor.interface.hasInterfaceObject():
+ cgThings.append(CGClassConstructor(descriptor,
+ descriptor.interface.ctor()))
+ cgThings.append(CGInterfaceObjectJSClass(descriptor, properties))
+ cgThings.append(CGNamedConstructors(descriptor))
+
+ cgThings.append(CGLegacyCallHook(descriptor))
+ if descriptor.interface.getExtendedAttribute("NeedResolve"):
+ cgThings.append(CGResolveHook(descriptor))
+ cgThings.append(CGMayResolveHook(descriptor))
+ cgThings.append(CGEnumerateHook(descriptor))
+
+ if descriptor.hasNamedPropertiesObject:
+ cgThings.append(CGGetNamedPropertiesObjectMethod(descriptor))
+
+ if descriptor.interface.hasInterfacePrototypeObject():
+ cgThings.append(CGPrototypeJSClass(descriptor, properties))
+
+ if ((descriptor.interface.hasInterfaceObject() or descriptor.interface.isNavigatorProperty()) and
+ not descriptor.interface.isExternal() and
+ descriptor.isExposedConditionally()):
+ cgThings.append(CGConstructorEnabled(descriptor))
+
+ if descriptor.registersGlobalNamesOnWindow:
+ cgThings.append(CGDefineDOMInterfaceMethod(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.concrete:
+ if descriptor.proxy:
+ if descriptor.interface.totalMembersInSlots != 0:
+ raise TypeError("We can't have extra reserved slots for "
+ "proxy interface %s" %
+ descriptor.interface.identifier.name)
+ cgThings.append(CGGeneric(fill(
+ """
+ static_assert(IsBaseOf<nsISupports, ${nativeType} >::value,
+ "We don't support non-nsISupports native classes for "
+ "proxy-based bindings yet");
+
+ """,
+ nativeType=descriptor.nativeType)))
+ if not descriptor.wrapperCache:
+ raise TypeError("We need a wrappercache to support expandos for proxy-based "
+ "bindings (" + descriptor.name + ")")
+ handlerThing = CGDOMJSProxyHandler(descriptor)
+ cgThings.append(CGDOMJSProxyHandlerDeclarer(handlerThing))
+ cgThings.append(CGProxyIsProxy(descriptor))
+ cgThings.append(CGProxyUnwrap(descriptor))
+ cgThings.append(CGDOMJSProxyHandlerDefiner(handlerThing))
+ cgThings.append(CGDOMProxyJSClass(descriptor))
+ else:
+ cgThings.append(CGDOMJSClass(descriptor))
+ cgThings.append(CGGetJSClassMethod(descriptor))
+ if descriptor.interface.hasMembersInSlots():
+ cgThings.append(CGUpdateMemberSlotsMethod(descriptor))
+
+ if descriptor.isGlobal():
+ assert descriptor.wrapperCache
+ cgThings.append(CGWrapGlobalMethod(descriptor, properties))
+ elif descriptor.wrapperCache:
+ cgThings.append(CGWrapWithCacheMethod(descriptor, properties))
+ cgThings.append(CGWrapMethod(descriptor))
+ else:
+ cgThings.append(CGWrapNonWrapperCacheMethod(descriptor,
+ properties))
+
+ # Set up our Xray callbacks as needed. This needs to come
+ # after we have our DOMProxyHandler defined.
+ if descriptor.wantsXrays:
+ if descriptor.concrete and descriptor.proxy:
+ cgThings.append(CGResolveOwnProperty(descriptor))
+ cgThings.append(CGEnumerateOwnProperties(descriptor))
+ if descriptor.needsXrayNamedDeleterHook():
+ cgThings.append(CGDeleteNamedProperty(descriptor))
+ elif descriptor.needsXrayResolveHooks():
+ cgThings.append(CGResolveOwnPropertyViaResolve(descriptor))
+ cgThings.append(CGEnumerateOwnPropertiesViaGetOwnPropertyNames(descriptor))
+ if descriptor.wantsXrayExpandoClass:
+ cgThings.append(CGXrayExpandoJSClass(descriptor))
+
+ # Now that we have our ResolveOwnProperty/EnumerateOwnProperties stuff
+ # done, set up our NativePropertyHooks.
+ cgThings.append(CGNativePropertyHooks(descriptor, properties))
+
+ # If we're not wrappercached, we don't know how to clear our
+ # cached values, since we can't get at the JSObject.
+ if descriptor.wrapperCache:
+ cgThings.extend(CGClearCachedValueMethod(descriptor, m) for
+ m in clearableCachedAttrs(descriptor))
+
+ haveUnscopables = (len(unscopableNames) != 0 and
+ descriptor.interface.hasInterfacePrototypeObject())
+ if haveUnscopables:
+ cgThings.append(
+ CGList([CGGeneric("static const char* const unscopableNames[] = {"),
+ CGIndenter(CGList([CGGeneric('"%s"' % name) for
+ name in unscopableNames] +
+ [CGGeneric("nullptr")], ",\n")),
+ CGGeneric("};\n")], "\n"))
+
+ # CGCreateInterfaceObjectsMethod needs to come after our
+ # CGDOMJSClass and unscopables, if any.
+ cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties,
+ haveUnscopables))
+
+ # CGGetProtoObjectMethod and CGGetConstructorObjectMethod need
+ # to come after CGCreateInterfaceObjectsMethod.
+ if descriptor.interface.hasInterfacePrototypeObject():
+ cgThings.append(CGGetProtoObjectHandleMethod(descriptor))
+ if descriptor.interface.hasChildInterfaces():
+ cgThings.append(CGGetProtoObjectMethod(descriptor))
+ if descriptor.interface.hasInterfaceObject():
+ cgThings.append(CGGetConstructorObjectHandleMethod(descriptor))
+ cgThings.append(CGGetConstructorObjectMethod(descriptor))
+
+ # See whether we need we need to generate an IsPermitted method
+ if crossOriginGetters or crossOriginSetters or crossOriginMethods:
+ cgThings.append(CGIsPermittedMethod(descriptor,
+ crossOriginGetters,
+ crossOriginSetters,
+ crossOriginMethods))
+
+ cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n")
+ cgThings = CGWrapper(cgThings, pre='\n', post='\n')
+ self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name),
+ cgThings),
+ post='\n')
+
+ def declare(self):
+ return self.cgRoot.declare()
+
+ def define(self):
+ return self.cgRoot.define()
+
+ def deps(self):
+ return self._deps
+
+
+class CGNamespacedEnum(CGThing):
+ def __init__(self, namespace, enumName, names, values, comment=""):
+
+ if not values:
+ values = []
+
+ # Account for explicit enum values.
+ entries = []
+ for i in range(0, len(names)):
+ if len(values) > i and values[i] is not None:
+ entry = "%s = %s" % (names[i], values[i])
+ else:
+ entry = names[i]
+ entries.append(entry)
+
+ # Append a Count.
+ entries.append('_' + enumName + '_Count')
+
+ # Indent.
+ entries = [' ' + e for e in entries]
+
+ # Build the enum body.
+ enumstr = comment + 'enum %s : uint16_t\n{\n%s\n};\n' % (enumName, ',\n'.join(entries))
+ curr = CGGeneric(declare=enumstr)
+
+ # Add some whitespace padding.
+ curr = CGWrapper(curr, pre='\n', post='\n')
+
+ # Add the namespace.
+ curr = CGNamespace(namespace, curr)
+
+ # Add the typedef
+ typedef = '\ntypedef %s::%s %s;\n\n' % (namespace, enumName, enumName)
+ curr = CGList([curr, CGGeneric(declare=typedef)])
+
+ # Save the result.
+ self.node = curr
+
+ def declare(self):
+ return self.node.declare()
+
+ def define(self):
+ return ""
+
+
+def initIdsClassMethod(identifiers, atomCacheName):
+ idinit = ['!atomsCache->%s.init(cx, "%s")' %
+ (CGDictionary.makeIdName(id),
+ id)
+ for id in identifiers]
+ idinit.reverse()
+ body = fill(
+ """
+ MOZ_ASSERT(!*reinterpret_cast<jsid**>(atomsCache));
+
+ // Initialize these in reverse order so that any failure leaves the first one
+ // uninitialized.
+ if (${idinit}) {
+ return false;
+ }
+ return true;
+ """,
+ idinit=" ||\n ".join(idinit))
+ return ClassMethod("InitIds", "bool", [
+ Argument("JSContext*", "cx"),
+ Argument("%s*" % atomCacheName, "atomsCache")
+ ], static=True, body=body, visibility="private")
+
+
+class CGDictionary(CGThing):
+ def __init__(self, dictionary, descriptorProvider):
+ self.dictionary = dictionary
+ self.descriptorProvider = descriptorProvider
+ self.needToInitIds = len(dictionary.members) > 0
+ self.memberInfo = [
+ (member,
+ getJSToNativeConversionInfo(
+ member.type,
+ descriptorProvider,
+ isEnforceRange=member.enforceRange,
+ isClamp=member.clamp,
+ isMember="Dictionary",
+ isOptional=member.canHaveMissingValue(),
+ defaultValue=member.defaultValue,
+ sourceDescription=self.getMemberSourceDescription(member)))
+ for member in dictionary.members]
+
+ # If we have a union member containing something in the same
+ # file as us, bail: the C++ includes won't work out.
+ for member in dictionary.members:
+ type = member.type.unroll()
+ if type.isUnion():
+ for t in type.flatMemberTypes:
+ if (t.isDictionary() and
+ CGHeaders.getDeclarationFilename(t.inner) ==
+ CGHeaders.getDeclarationFilename(dictionary)):
+ raise TypeError(
+ "Dictionary contains a union that contains a "
+ "dictionary in the same WebIDL file. This won't "
+ "compile. Move the inner dictionary to a "
+ "different file.\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
+ MOZ_ASSERT_IF(!cx, val.isNull());
+ """)
+
+ if self.needToInitIds:
+ body += fill(
+ """
+ ${dictName}Atoms* atomsCache = nullptr;
+ if (cx) {
+ atomsCache = GetAtomCache<${dictName}Atoms>(cx);
+ if (!*reinterpret_cast<jsid**>(atomsCache) && !InitIds(cx, atomsCache)) {
+ return false;
+ }
+ }
+
+ """,
+ dictName=self.makeClassName(self.dictionary))
+
+ if self.dictionary.parent:
+ body += fill(
+ """
+ // Per spec, we init the parent's members first
+ if (!${dictName}::Init(cx, val)) {
+ return false;
+ }
+
+ """,
+ dictName=self.makeClassName(self.dictionary.parent))
+ else:
+ body += dedent(
+ """
+ { // scope for isConvertible
+ bool isConvertible;
+ if (!IsConvertibleToDictionary(cx, val, &isConvertible)) {
+ return false;
+ }
+ if (!isConvertible) {
+ return ThrowErrorMessage(cx, MSG_NOT_DICTIONARY, sourceDescription);
+ }
+ }
+
+ """)
+
+ memberInits = [self.getMemberConversion(m).define()
+ for m in self.memberInfo]
+ if memberInits:
+ body += fill(
+ """
+ bool isNull = val.isNullOrUndefined();
+ // We only need these if !isNull, in which case we have |cx|.
+ Maybe<JS::Rooted<JSObject *> > object;
+ Maybe<JS::Rooted<JS::Value> > temp;
+ if (!isNull) {
+ MOZ_ASSERT(cx);
+ object.emplace(cx, &val.toObject());
+ temp.emplace(cx);
+ }
+ $*{memberInits}
+ """,
+ memberInits="\n".join(memberInits))
+
+ body += "return true;\n"
+
+ return ClassMethod("Init", "bool", [
+ Argument('JSContext*', 'cx'),
+ Argument('JS::Handle<JS::Value>', 'val'),
+ 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.
+ JSAutoCompartment ac(cx, binding_detail::UnprivilegedJunkScopeOrWorkerGlobal());
+ 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) && !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)
+
+ def assignmentOperator(self):
+ body = CGList([])
+ if self.dictionary.parent:
+ body.append(CGGeneric(
+ "%s::operator=(aOther);\n" %
+ self.makeClassName(self.dictionary.parent)))
+ 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)
+ return ClassMethod(
+ "operator=", "void",
+ [Argument("const %s&" % self.makeClassName(self.dictionary),
+ "aOther")],
+ 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
+ ctors = [
+ ClassConstructor(
+ [],
+ visibility="public",
+ baseConstructors=baseConstructors,
+ body=(
+ "// Safe to pass a null context if we pass a null value\n"
+ "Init(nullptr, JS::NullHandleValue);\n")),
+ 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())
+
+ methods.append(self.initMethod())
+ canBeRepresentedAsJSON = self.dictionarySafeToJSONify(self.dictionary)
+ if canBeRepresentedAsJSON:
+ methods.append(self.initFromJSONMethod())
+ try:
+ methods.append(self.toObjectInternalMethod())
+ if canBeRepresentedAsJSON:
+ methods.append(self.toJSONMethod())
+ except MethodNotNewObjectError:
+ # If we can't have a ToObjectInternal() because one of our members
+ # can only be returned from [NewObject] methods, then just skip
+ # generating ToObjectInternal() and ToJSON (since the latter depens
+ # on the former).
+ pass
+ methods.append(self.traceDictionaryMethod())
+
+ if CGDictionary.isDictionaryCopyConstructible(d):
+ disallowCopyConstruction = False
+ # Note: no base constructors because our operator= will
+ # deal with that.
+ ctors.append(ClassConstructor([Argument("const %s&" % selfName,
+ "aOther")],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ body="*this = aOther;\n"))
+ methods.append(self.assignmentOperator())
+ else:
+ disallowCopyConstruction = True
+
+ 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):
+ """
+ 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
+ replacements = {
+ "val": "temp.ref()",
+ "maybeMutableVal": "temp.ptr()",
+ "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"
+ }
+ # We can't handle having a holderType here
+ assert conversionInfo.holderType is None
+ if conversionInfo.dealWithOptional:
+ replacements["declName"] = "(" + replacements["declName"] + ".Value())"
+ if member.defaultValue:
+ 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"
+ 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 ThrowErrorMessage(cx, 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", "toSource", "toString", "toLocaleString", "valueOf",
+ "watch", "unwatch", "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.
+ 'typedArraysAreStructs': 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(0);\n")
+ if member.canHaveMissingValue():
+ # Only do the conversion if we have a value
+ conversion = CGIfWrapper(conversion, "%s.WasPassed()" % memberLoc)
+ conditions = getConditionList(member, "cx", "obj")
+ if len(conditions) != 0:
+ conversion = CGIfWrapper(conversion, conditions.define())
+ return conversion
+
+ def getMemberTrace(self, member):
+ type = member.type
+ assert typeNeedsRooting(type)
+ memberLoc = self.makeMemberName(member.identifier.name)
+ if not member.canHaveMissingValue():
+ memberData = memberLoc
+ else:
+ # The data is inside the Optional<>
+ memberData = "%s.Value()" % memberLoc
+
+ memberName = "%s.%s" % (self.makeClassName(self.dictionary),
+ memberLoc)
+
+ if type.isObject():
+ trace = CGGeneric('JS::UnsafeTraceRoot(trc, %s, "%s");\n' %
+ ("&"+memberData, memberName))
+ if type.nullable():
+ trace = CGIfWrapper(trace, memberData)
+ elif type.isAny():
+ trace = CGGeneric('JS::UnsafeTraceRoot(trc, %s, "%s");\n' %
+ ("&"+memberData, memberName))
+ elif (type.isSequence() or type.isDictionary() or
+ type.isSpiderMonkeyInterface() or type.isUnion()):
+ 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)
+ else:
+ assert type.isSpiderMonkeyInterface()
+ trace = CGGeneric('%s.TraceSelf(trc);\n' % memberData)
+ if type.nullable():
+ trace = CGIfWrapper(trace, "!%s.IsNull()" % memberNullable)
+ elif type.isMozMap():
+ # If you implement this, add a MozMap<object> to
+ # TestInterfaceJSDictionary and test it in test_bug1036214.html
+ # to make sure we end up with the correct security properties.
+ assert False
+ 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 'any' and 'object' that have a default
+ value, so they're safe to trace at all times.
+ """
+ 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.isAny():
+ return "JS::UndefinedValue()"
+ if type.isObject():
+ return "nullptr"
+ 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 None
+
+ def getMemberSourceDescription(self, member):
+ return ("'%s' member of %s" %
+ (member.identifier.name, self.dictionary.identifier.name))
+
+ @staticmethod
+ def makeIdName(name):
+ return IDLToCIdentifier(name) + "_id"
+
+ @staticmethod
+ def getNonInitializingCtorArg():
+ return "FastDictionaryInitializer()"
+
+ @staticmethod
+ def isDictionaryCopyConstructible(dictionary):
+ if (dictionary.parent and
+ not CGDictionary.isDictionaryCopyConstructible(dictionary.parent)):
+ return False
+ return all(isTypeCopyConstructible(m.type) for m in dictionary.members)
+
+ @staticmethod
+ def typeSafeToJSONify(type):
+ """
+ Determine whether the given type is safe to convert to JSON. The
+ restriction is that this needs to be safe while in a global controlled
+ by an adversary, and "safe" means no side-effects when the JS
+ representation of this type is converted to JSON. That means that we
+ have to be pretty restrictive about what things we can allow. For
+ example, "object" is out, because it may have accessor properties on it.
+ """
+ if type.nullable():
+ # Converting null to JSON is always OK.
+ return CGDictionary.typeSafeToJSONify(type.inner)
+
+ if type.isSequence():
+ # Sequences are arrays we create ourselves, with no holes. They
+ # should be safe if their contents are safe, as long as we suppress
+ # invocation of .toJSON on objects.
+ return CGDictionary.typeSafeToJSONify(type.inner)
+
+ if type.isUnion():
+ # OK if everything in it is ok.
+ return all(CGDictionary.typeSafeToJSONify(t)
+ for t in type.flatMemberTypes)
+
+ if type.isDictionary():
+ # OK if the dictionary is OK
+ return CGDictionary.dictionarySafeToJSONify(type.inner)
+
+ if type.isString() or type.isEnum():
+ # Strings are always OK.
+ return True
+
+ if type.isPrimitive():
+ # Primitives (numbers and booleans) are ok, as long as
+ # they're not unrestricted float/double.
+ return not type.isFloat() or not type.isUnrestricted()
+
+ 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 CGResolveSystemBinding(CGAbstractMethod):
+ def __init__(self, config):
+ CGAbstractMethod.__init__(self, None, 'ResolveSystemBinding', 'bool',
+ [Argument('JSContext*', 'aCx'),
+ Argument('JS::Handle<JSObject*>', 'aObj'),
+ Argument('JS::Handle<jsid>', 'aId'),
+ Argument('bool*', 'aResolvedp')])
+ self.config = config
+
+ def definition_body(self):
+ descriptors = self.config.getDescriptors(hasInterfaceObject=True,
+ isExposedInSystemGlobals=True,
+ register=True)
+
+ def descNameToId(name):
+ return "s%s_id" % name
+ jsidNames = [descNameToId(desc.name) for desc in descriptors]
+ jsidDecls = CGList(CGGeneric("static jsid %s;\n" % name)
+ for name in jsidNames)
+
+ jsidInits = CGList(
+ (CGIfWrapper(
+ CGGeneric("return false;\n"),
+ '!AtomizeAndPinJSString(aCx, %s, "%s")' %
+ (descNameToId(desc.name), desc.interface.identifier.name))
+ for desc in descriptors),
+ "\n")
+ jsidInits.append(CGGeneric("idsInited = true;\n"))
+ jsidInits = CGIfWrapper(jsidInits, "!idsInited")
+ jsidInits = CGList([CGGeneric("static bool idsInited = false;\n"),
+ jsidInits])
+
+ definitions = CGList([], "\n")
+ for desc in descriptors:
+ bindingNS = toBindingNamespace(desc.name)
+ defineCode = "!%s::GetConstructorObject(aCx)" % bindingNS
+ defineCode = CGIfWrapper(CGGeneric("return false;\n"), defineCode)
+ defineCode = CGList([defineCode,
+ CGGeneric("*aResolvedp = true;\n")])
+
+ condition = "JSID_IS_VOID(aId) || aId == %s" % descNameToId(desc.name)
+ if desc.isExposedConditionally():
+ condition = "(%s) && %s::ConstructorEnabled(aCx, aObj)" % (condition, bindingNS)
+
+ definitions.append(CGIfWrapper(defineCode, condition))
+
+ return CGList([CGGeneric("MOZ_ASSERT(NS_IsMainThread());\n"),
+ jsidDecls,
+ jsidInits,
+ definitions,
+ CGGeneric("return true;\n")],
+ "\n").define()
+
+
+def getGlobalNames(config):
+ names = []
+ for desc in config.getDescriptors(registersGlobalNamesOnWindow=True):
+ names.append((desc.name, desc))
+ names.extend((n.identifier.name, desc) for n in desc.interface.namedConstructors)
+ return names
+
+class CGGlobalNamesString(CGGeneric):
+ def __init__(self, config):
+ globalNames = getGlobalNames(config)
+ currentOffset = 0
+ strings = []
+ for (name, _) in globalNames:
+ strings.append('/* %i */ "%s\\0"' % (currentOffset, name))
+ currentOffset += len(name) + 1 # Add trailing null.
+ define = fill("""
+ const uint32_t WebIDLGlobalNameHash::sCount = ${count};
+
+ const char WebIDLGlobalNameHash::sNames[] =
+ $*{strings}
+
+ """,
+ count=len(globalNames),
+ strings="\n".join(strings) + ";\n")
+
+ CGGeneric.__init__(self, define=define)
+
+
+class CGRegisterGlobalNames(CGAbstractMethod):
+ def __init__(self, config):
+ CGAbstractMethod.__init__(self, None, 'RegisterWebIDLGlobalNames',
+ 'void', [])
+ self.config = config
+
+ def definition_body(self):
+ def getCheck(desc):
+ if not desc.isExposedConditionally():
+ return "nullptr"
+ return "%sBinding::ConstructorEnabled" % desc.name
+
+ define = ""
+ currentOffset = 0
+ for (name, desc) in getGlobalNames(self.config):
+ length = len(name)
+ define += "WebIDLGlobalNameHash::Register(%i, %i, %sBinding::DefineDOMInterface, %s);\n" % (currentOffset, length, desc.name, getCheck(desc))
+ currentOffset += length + 1 # Add trailing null.
+ return 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(self.children.iteritems()):
+ decls.append(CGNamespace(namespace, child._build(atTopLevel=False), declareOnly=True))
+
+ cg = CGList(decls, "\n")
+ if not atTopLevel and len(decls) + len(self.decls) > 1:
+ cg = CGWrapper(cg, pre='\n', post='\n')
+ return cg
+
+ def build(self):
+ return self._build(atTopLevel=True)
+
+ def forwardDeclareForType(self, t, config):
+ t = t.unroll()
+ if t.isGeckoInterface():
+ name = t.inner.identifier.name
+ try:
+ desc = config.getDescriptor(name)
+ self.add(desc.nativeType)
+ except NoSuchDescriptorError:
+ pass
+
+ # Note: Spidermonkey interfaces are typedefs, so can't be
+ # forward-declared
+ elif t.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.isMozMap():
+ self.forwardDeclareForType(t.inner, config)
+ # Don't need to do anything for void, primitive, string, any or object.
+ # There may be some other cases we are missing.
+
+
+class CGForwardDeclarations(CGWrapper):
+ """
+ Code generate the forward declarations for a header file.
+ additionalDeclarations is a list of tuples containing a classname and a
+ boolean. If the boolean is true we will declare a struct, otherwise we'll
+ declare a class.
+ """
+ def __init__(self, config, descriptors, callbacks,
+ dictionaries, callbackInterfaces, additionalDeclarations=[]):
+ builder = ForwardDeclarationBuilder()
+
+ # Needed for at least Wrap.
+ for d in descriptors:
+ # If this is a generated iterator interface, we only create these
+ # in the generated bindings, and don't need to forward declare.
+ if d.interface.isIteratorInterface():
+ continue
+ builder.add(d.nativeType)
+ # If we're an interface and we have a maplike/setlike declaration,
+ # we'll have helper functions exposed to the native side of our
+ # bindings, which will need to show up in the header. If either of
+ # our key/value types are interfaces, they'll be passed as
+ # arguments to helper functions, and they'll need to be forward
+ # declared in the header.
+ if d.interface.maplikeOrSetlikeOrIterable:
+ if d.interface.maplikeOrSetlikeOrIterable.hasKeyType():
+ builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.keyType,
+ config)
+ if d.interface.maplikeOrSetlikeOrIterable.hasValueType():
+ builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.valueType,
+ config)
+
+ # We just about always need NativePropertyHooks
+ builder.addInMozillaDom("NativePropertyHooks", isStruct=True)
+ builder.addInMozillaDom("ProtoAndIfaceCache")
+ # Add the atoms cache type, even if we don't need it.
+ for d in descriptors:
+ # Iterators have native types that are template classes, so
+ # creating an 'Atoms' cache type doesn't work for them, and is one
+ # of the cases where we don't need it anyways.
+ if d.interface.isIteratorInterface():
+ continue
+ builder.add(d.nativeType + "Atoms", isStruct=True)
+
+ for callback in callbacks:
+ builder.addInMozillaDom(callback.identifier.name)
+ for t in getTypesFromCallback(callback):
+ builder.forwardDeclareForType(t, config)
+
+ for d in callbackInterfaces:
+ builder.add(d.nativeType)
+ builder.add(d.nativeType + "Atoms", isStruct=True)
+ for t in getTypesFromDescriptor(d):
+ builder.forwardDeclareForType(t, config)
+
+ for d in dictionaries:
+ if len(d.members) > 0:
+ builder.addInMozillaDom(d.identifier.name + "Atoms", isStruct=True)
+ for t in getTypesFromDictionary(d):
+ builder.forwardDeclareForType(t, config)
+
+ for className, isStruct in additionalDeclarations:
+ builder.add(className, isStruct=isStruct)
+
+ CGWrapper.__init__(self, builder.build())
+
+
+class CGBindingRoot(CGThing):
+ """
+ Root codegen class for binding generation. Instantiate the class, and call
+ declare or define to generate header or cpp code (respectively).
+ """
+ def __init__(self, config, prefix, webIDLFile):
+ bindingHeaders = dict.fromkeys((
+ 'mozilla/dom/NonRefcountedDOMObject.h',
+ ),
+ True)
+ bindingDeclareHeaders = dict.fromkeys((
+ 'mozilla/dom/BindingDeclarations.h',
+ 'mozilla/dom/Nullable.h',
+ 'mozilla/ErrorResult.h',
+ ),
+ True)
+
+ descriptors = config.getDescriptors(webIDLFile=webIDLFile,
+ hasInterfaceOrInterfacePrototypeObject=True)
+
+ unionTypes = UnionsForFile(config, webIDLFile)
+
+ (unionHeaders, unionImplheaders, unionDeclarations, traverseMethods,
+ unlinkMethods, unionStructs) = UnionTypes(unionTypes, config)
+
+ bindingDeclareHeaders.update(dict.fromkeys(unionHeaders, True))
+ bindingHeaders.update(dict.fromkeys(unionImplheaders, True))
+ bindingDeclareHeaders["mozilla/dom/UnionMember.h"] = len(unionStructs) > 0
+ bindingDeclareHeaders["mozilla/dom/FakeString.h"] = len(unionStructs) > 0
+ # BindingUtils.h is only needed for SetToObject.
+ # If it stops being inlined or stops calling CallerSubsumes
+ # both this bit and the bit in UnionTypes can be removed.
+ bindingDeclareHeaders["mozilla/dom/BindingUtils.h"] = any(d.isObject() for t in unionTypes
+ for d in t.flatMemberTypes)
+ bindingDeclareHeaders["mozilla/dom/IterableIterator.h"] = any(d.interface.isIteratorInterface() or
+ d.interface.isIterable() for d in descriptors)
+
+ def descriptorHasCrossOriginProperties(desc):
+ def hasCrossOriginProperty(m):
+ props = memberProperties(m, desc)
+ return (props.isCrossOriginMethod or
+ props.isCrossOriginGetter or
+ props.isCrossOriginSetter)
+
+ return any(hasCrossOriginProperty(m) for m in desc.interface.members)
+
+ bindingDeclareHeaders["jsapi.h"] = any(descriptorHasCrossOriginProperties(d) for d in descriptors)
+ bindingDeclareHeaders["jspubtd.h"] = not bindingDeclareHeaders["jsapi.h"]
+ bindingDeclareHeaders["js/RootingAPI.h"] = not bindingDeclareHeaders["jsapi.h"]
+
+ def descriptorRequiresPreferences(desc):
+ iface = desc.interface
+ return any(m.getExtendedAttribute("Pref") for m in iface.members + [iface])
+
+ def descriptorDeprecated(desc):
+ iface = desc.interface
+ return any(m.getExtendedAttribute("Deprecated") for m in iface.members + [iface])
+
+ bindingHeaders["nsIDocument.h"] = any(
+ descriptorDeprecated(d) for d in descriptors)
+ bindingHeaders["mozilla/Preferences.h"] = any(
+ descriptorRequiresPreferences(d) for d in descriptors)
+ bindingHeaders["mozilla/dom/DOMJSProxyHandler.h"] = any(
+ d.concrete and d.proxy for d in descriptors)
+
+ 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)))) or
+ # JS-implemented interfaces with clearable cached
+ # attrs have chromeonly _clearFoo methods.
+ (desc.interface.isJSImplemented() and
+ any(clearableCachedAttrs(desc))))
+
+ # 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
+
+ bindingHeaders["nsContentUtils.h"] = (
+ any(descriptorHasChromeOnly(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
+ bindingHeaders["nsIGlobalObject.h"] = jsImplemented
+ bindingHeaders["AtomList.h"] = hasNonEmptyDictionaries or jsImplemented or callbackDescriptors
+
+ def descriptorClearsPropsInSlots(descriptor):
+ if not descriptor.wrapperCache:
+ return False
+ return any(m.isAttr() and m.getExtendedAttribute("StoreInSlot")
+ for m in descriptor.interface.members)
+ bindingHeaders["nsJSUtils.h"] = any(descriptorClearsPropsInSlots(d) for d in descriptors)
+
+ # Do codegen for all the enums
+ enums = config.getEnums(webIDLFile)
+ cgthings = [CGEnum(e) for e in enums]
+
+ hasCode = (descriptors or callbackDescriptors or dictionaries or
+ callbacks)
+ bindingHeaders["mozilla/dom/BindingUtils.h"] = hasCode
+ bindingHeaders["mozilla/OwningNonNull.h"] = 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]
+ def descriptorRequiresTelemetry(desc):
+ iface = desc.interface
+ return any(m.getExtendedAttribute("UseCounter") for m in iface.members)
+ bindingHeaders["mozilla/UseCounter.h"] = any(
+ descriptorRequiresTelemetry(d) for d in descriptors)
+ bindingHeaders["mozilla/dom/SimpleGlobalObject.h"] = any(
+ CGDictionary.dictionarySafeToJSONify(d) for d in dictionaries)
+ bindingHeaders["XrayWrapper.h"] = any(
+ d.wantsXrays and d.wantsXrayExpandoClass for d in descriptors)
+ bindingHeaders["mozilla/dom/XrayExpandoClass.h"] = any(
+ d.wantsXrays for d in descriptors)
+
+ cgthings.extend(traverseMethods)
+ cgthings.extend(unlinkMethods)
+
+ # Do codegen for all the dictionaries. We have to be a bit careful
+ # here, because we have to generate these in order from least derived
+ # to most derived so that class inheritance works out. We also have to
+ # generate members before the dictionary that contains them.
+
+ def getDependenciesFromType(type):
+ if type.isDictionary():
+ return set([type.unroll().inner])
+ if type.isSequence():
+ return getDependenciesFromType(type.unroll())
+ if type.isUnion():
+ return set([type.unroll()])
+ return set()
+
+ def getDependencies(unionTypeOrDictionary):
+ if isinstance(unionTypeOrDictionary, IDLDictionary):
+ deps = set()
+ if unionTypeOrDictionary.parent:
+ deps.add(unionTypeOrDictionary.parent)
+ for member in unionTypeOrDictionary.members:
+ deps |= getDependenciesFromType(member.type)
+ return deps
+
+ assert unionTypeOrDictionary.isType() and unionTypeOrDictionary.isUnion()
+ deps = set()
+ for member in unionTypeOrDictionary.flatMemberTypes:
+ deps |= getDependenciesFromType(member)
+ return deps
+
+ def getName(unionTypeOrDictionary):
+ if isinstance(unionTypeOrDictionary, IDLDictionary):
+ return unionTypeOrDictionary.identifier.name
+
+ assert unionTypeOrDictionary.isType() and unionTypeOrDictionary.isUnion()
+ return unionTypeOrDictionary.name
+
+ for t in dependencySortObjects(dictionaries + unionStructs, getDependencies, getName):
+ if t.isDictionary():
+ cgthings.append(CGDictionary(t, config))
+ else:
+ assert t.isUnion()
+ cgthings.append(CGUnionStruct(t, config))
+ cgthings.append(CGUnionStruct(t, config, True))
+
+ # Do codegen for all the callbacks.
+ cgthings.extend(CGCallbackFunction(c, config) for c in callbacks)
+
+ cgthings.extend([CGNamespace('binding_detail', CGFastCallback(c))
+ for c in callbacks])
+
+ # Do codegen for all the descriptors
+ cgthings.extend([CGDescriptor(x) for x in descriptors])
+
+ # Do codegen for all the callback interfaces.
+ cgthings.extend([CGCallbackInterface(x) for x in callbackDescriptors])
+
+ cgthings.extend([CGNamespace('binding_detail',
+ CGFastCallback(x.interface))
+ for x in callbackDescriptors])
+
+ # Do codegen for JS implemented classes
+ def getParentDescriptor(desc):
+ if not desc.interface.parent:
+ return set()
+ return {desc.getDescriptor(desc.interface.parent.identifier.name)}
+ for x in dependencySortObjects(jsImplemented, getParentDescriptor,
+ lambda d: d.interface.identifier.name):
+ cgthings.append(CGCallbackInterface(x, typedArraysAreStructs=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 bindingHeaders.iteritems()
+ if include]
+ bindingDeclareHeaders = [header
+ for header, include in bindingDeclareHeaders.iteritems()
+ 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",
+ typedArraysAreStructs=True, variadicIsSequence=False,
+ resultNotAddRefed=False,
+ virtual=False,
+ override=False):
+ """
+ If typedArraysAreStructs is false, typed arrays will be passed as
+ JS::Handle<JSObject*>. If it's true they will be passed as one of the
+ dom::TypedArray 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.typedArraysAreStructs = typedArraysAreStructs
+ self.variadicIsSequence = variadicIsSequence
+ breakAfterSelf = "\n" if breakAfter else ""
+ ClassMethod.__init__(self, name,
+ self.getReturnType(signature[0], False),
+ self.getArgs(signature[0], signature[1]),
+ static=member.isStatic(),
+ # Mark our getters, which are attrs that
+ # have a non-void return type, as const.
+ const=(not member.isStatic() and member.isAttr() and
+ not signature[0].isVoid()),
+ breakAfterReturnDecl=" ",
+ breakAfterSelf=breakAfterSelf,
+ visibility=visibility,
+ virtual=virtual,
+ override=override)
+
+ def getReturnType(self, type, isMember):
+ return self.getRetvalInfo(type, isMember)[0]
+
+ def getRetvalInfo(self, type, isMember):
+ """
+ Returns a tuple:
+
+ The first element is the type declaration for the retval
+
+ The second element is a default value that can be used on error returns.
+ For cases whose behavior depends on isMember, the second element will be
+ None if isMember is true.
+
+ The third element is a template for actually returning a value stored in
+ "${declName}" and "${holderName}". This means actually returning it if
+ we're not outparam, else assigning to the "retval" outparam. If
+ isMember is true, this can be None, since in that case the caller will
+ never examine this value.
+ """
+ if type.isVoid():
+ return "void", "", ""
+ if type.isPrimitive() and type.tag() in builtinNames:
+ result = CGGeneric(builtinNames[type.tag()])
+ defaultReturnArg = "0"
+ if type.nullable():
+ result = CGTemplatedType("Nullable", result)
+ defaultReturnArg = ""
+ return (result.define(),
+ "%s(%s)" % (result.define(), defaultReturnArg),
+ "return ${declName};\n")
+ if type.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():
+ 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():
+ iface = type.unroll().inner
+ result = CGGeneric(self.descriptorProvider.getDescriptor(
+ iface.identifier.name).prettyNativeType)
+ 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().SwapElements(${declName}.Value());
+ }
+ """)
+ else:
+ returnCode = "aRetVal.SwapElements(${declName});\n"
+ return "void", "", returnCode
+ if type.isMozMap():
+ # If we want to handle MozMap-of-MozMap return values, we're
+ # going to need to fix example codegen to not produce MozMap<void>
+ # for the relevant argument...
+ assert not isMember
+ # In this case we convert directly into our outparam to start with
+ return "void", "", ""
+ if type.isDate():
+ result = CGGeneric("Date")
+ if type.nullable():
+ result = CGTemplatedType("Nullable", result)
+ return (result.define(), "%s()" % result.define(),
+ "return ${declName};\n")
+ 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.isDOMString() or returnType.isUSVString():
+ args.append(Argument("nsString&", "aRetVal"))
+ elif returnType.isByteString():
+ 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.isMozMap():
+ nullable = returnType.nullable()
+ if nullable:
+ returnType = returnType.inner
+ # And now the actual underlying type
+ elementDecl = self.getReturnType(returnType.inner, True)
+ type = CGTemplatedType("MozMap", 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 self.member.getExtendedAttribute('NeedsSubjectPrincipal'):
+ # Cheat and assume self.descriptorProvider is a descriptor
+ if self.descriptorProvider.interface.isExposedInAnyWorker():
+ args.append(Argument("Maybe<nsIPrincipal*>", "aSubjectPrincipal"))
+ else:
+ args.append(Argument("nsIPrincipal&", "aPrincipal"))
+ # And the caller type, if desired.
+ if needsCallerType(self.member):
+ args.append(Argument("CallerType", "aCallerType"))
+ # And the ErrorResult
+ if 'infallible' not in self.extendedAttrs:
+ # Use aRv so it won't conflict with local vars named "rv"
+ args.append(Argument("ErrorResult&", "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",
+ "MozMap"
+ """
+ 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.isMozMap():
+ nullable = type.nullable()
+ if nullable:
+ type = type.inner
+ elementType = type.inner
+ argType = self.getArgType(elementType, False, "MozMap")[0]
+ decl = CGTemplatedType("MozMap", 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.isGeckoInterface() and not type.isCallbackInterface():
+ iface = type.unroll().inner
+ argIsPointer = type.nullable() or iface.isExternal()
+ forceOwningType = (iface.isCallback() or isMember or
+ iface.identifier.name == "Promise")
+ 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.typedArraysAreStructs:
+ return "JS::Handle<JSObject*>", False, False
+
+ # Unroll for the name, in case we're nullable.
+ return type.unroll().name, True, True
+
+ if type.isDOMString() or type.isUSVString():
+ if isMember:
+ declType = "nsString"
+ else:
+ declType = "nsAString"
+ return declType, True, False
+
+ if type.isByteString():
+ 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
+
+ if type.isDate():
+ return "Date", False, True
+
+ assert type.isPrimitive()
+
+ return builtinNames[type.tag()], False, True
+
+ def getArgType(self, type, optional, isMember):
+ """
+ Get the type of an argument declaration. Returns the type CGThing, and
+ whether this should be a const ref.
+
+ isMember can be False, "Sequence", or "Variadic"
+ """
+ decl, ref, handleNullable = self.doGetArgType(type, optional, isMember)
+ decl = CGGeneric(decl)
+ if handleNullable and type.nullable():
+ decl = CGTemplatedType("Nullable", decl)
+ ref = True
+ if isMember == "Variadic":
+ arrayType = "Sequence" if self.variadicIsSequence else "nsTArray"
+ decl = CGTemplatedType(arrayType, decl)
+ ref = True
+ elif optional:
+ # Note: All variadic args claim to be optional, but we can just use
+ # empty arrays to represent them not being present.
+ decl = CGTemplatedType("Optional", decl)
+ ref = True
+ return (decl, ref)
+
+ def getArg(self, arg):
+ """
+ Get the full argument declaration for an argument
+ """
+ decl, ref = self.getArgType(arg.type, arg.canHaveMissingValue(),
+ "Variadic" if arg.variadic else False)
+ if ref:
+ decl = CGWrapper(decl, pre="const ", post="&")
+
+ return Argument(decl.define(), arg.identifier.name)
+
+ def arguments(self):
+ return self.member.signatures()[0][1]
+
+
+class CGExampleMethod(CGNativeMember):
+ def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
+ CGNativeMember.__init__(self, descriptor, method,
+ CGSpecializedMethod.makeNativeName(descriptor,
+ method),
+ signature,
+ descriptor.getExtendedAttributes(method),
+ breakAfter=breakAfter,
+ variadicIsSequence=True)
+
+ def declare(self, cgClass):
+ assert self.member.isMethod()
+ # We skip declaring ourselves if this is a maplike/setlike/iterable
+ # method, because those get implemented automatically by the binding
+ # machinery, so the implementor of the interface doesn't have to worry
+ # about it.
+ if self.member.isMaplikeOrSetlikeOrIterableMethod():
+ return ''
+ return CGNativeMember.declare(self, cgClass);
+
+ def define(self, cgClass):
+ return ''
+
+
+class CGExampleGetter(CGNativeMember):
+ def __init__(self, descriptor, attr):
+ CGNativeMember.__init__(self, descriptor, attr,
+ CGSpecializedGetter.makeNativeName(descriptor,
+ attr),
+ (attr.type, []),
+ descriptor.getExtendedAttributes(attr,
+ getter=True))
+
+ def declare(self, cgClass):
+ assert self.member.isAttr()
+ # We skip declaring ourselves if this is a maplike/setlike attr (in
+ # practice, "size"), because those get implemented automatically by the
+ # binding machinery, so the implementor of the interface doesn't have to
+ # worry about it.
+ if self.member.isMaplikeOrSetlikeAttr():
+ return ''
+ return CGNativeMember.declare(self, cgClass);
+
+ def define(self, cgClass):
+ return ''
+
+
+class CGExampleSetter(CGNativeMember):
+ def __init__(self, descriptor, attr):
+ CGNativeMember.__init__(self, descriptor, attr,
+ CGSpecializedSetter.makeNativeName(descriptor,
+ attr),
+ (BuiltinTypes[IDLBuiltinType.Types.void],
+ [FakeArgument(attr.type, attr)]),
+ descriptor.getExtendedAttributes(attr,
+ setter=True))
+
+ def define(self, cgClass):
+ return ''
+
+
+class CGBindingImplClass(CGClass):
+ """
+ Common codegen for generating a C++ implementation of a WebIDL interface
+ """
+ def __init__(self, descriptor, cgMethod, cgGetter, cgSetter, wantGetParent=True, wrapMethodName="WrapObject", skipStaticMethods=False):
+ """
+ cgMethod, cgGetter and cgSetter are classes used to codegen methods,
+ getters and setters.
+ """
+ self.descriptor = descriptor
+ self._deps = descriptor.interface.getDeps()
+
+ iface = descriptor.interface
+
+ self.methodDecls = []
+
+ def appendMethod(m, isConstructor=False):
+ sigs = m.signatures()
+ for s in sigs[:-1]:
+ # Don't put a blank line after overloads, until we
+ # get to the last one.
+ self.methodDecls.append(cgMethod(descriptor, m, s,
+ isConstructor,
+ breakAfter=False))
+ self.methodDecls.append(cgMethod(descriptor, m, sigs[-1],
+ isConstructor))
+
+ if iface.ctor():
+ appendMethod(iface.ctor(), isConstructor=True)
+ for n in iface.namedConstructors:
+ appendMethod(n, isConstructor=True)
+ for m in iface.members:
+ if m.isMethod():
+ if m.isIdentifierLess():
+ continue
+ if not m.isStatic() or not skipStaticMethods:
+ appendMethod(m)
+ elif m.isAttr():
+ 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
+ if name == "IndexedCreator" or name == "NamedCreator":
+ # These are identical to the setters
+ return
+ assert len(op.signatures()) == 1
+ returnType, args = op.signatures()[0]
+ # Make a copy of the args, since we plan to modify them.
+ args = list(args)
+ if op.isGetter() or op.isDeleter():
+ # This is a total hack. The '&' belongs with the
+ # type, not the name! But it works, and is simpler
+ # than trying to somehow make this pretty.
+ args.append(FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean],
+ op, name="&found"))
+ if name == "Stringifier":
+ if op.isIdentifierLess():
+ # XXXbz I wish we were consistent about our renaming here.
+ name = "Stringify"
+ else:
+ # We already added this method
+ return
+ if name == "LegacyCaller":
+ if op.isIdentifierLess():
+ # XXXbz I wish we were consistent about our renaming here.
+ name = "LegacyCall"
+ else:
+ # We already added this method
+ return
+ if name == "Jsonifier":
+ # 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 = descriptor.operations.items()
+ ops.sort(key=lambda x: x[0])
+ for name, op in ops:
+ appendSpecialOperation(name, op)
+ # If we support indexed properties, then we need a Length()
+ # method so we know which indices are supported.
+ if descriptor.supportsIndexedProperties():
+ # But we don't need it if we already have an infallible
+ # "length" attribute, which we often do.
+ haveLengthAttr = any(
+ m for m in iface.members if m.isAttr() and
+ CGSpecializedGetter.makeNativeName(descriptor, m) == "Length")
+ if not haveLengthAttr:
+ self.methodDecls.append(
+ CGNativeMember(descriptor, FakeMember(),
+ "Length",
+ (BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
+ []),
+ {"infallible": True}))
+ # And if we support named properties we need to be able to
+ # enumerate the supported names.
+ if descriptor.supportsNamedProperties():
+ self.methodDecls.append(
+ CGNativeMember(
+ descriptor, FakeMember(),
+ "GetSupportedNames",
+ (IDLSequenceType(None,
+ BuiltinTypes[IDLBuiltinType.Types.domstring]),
+ []),
+ {"infallible": True}))
+
+ 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 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):
+ return ("// TODO: return something sensible here, and change the return type\n"
+ "%s*" % self.descriptor.nativeType.split('::')[-1])
+
+ def getGetParentObjectBody(self):
+ return None
+
+ def deps(self):
+ return self._deps
+
+
+class CGExampleClass(CGBindingImplClass):
+ """
+ Codegen for the actual example class implementation for this descriptor
+ """
+ def __init__(self, descriptor):
+ CGBindingImplClass.__init__(self, descriptor,
+ CGExampleMethod, CGExampleGetter, CGExampleSetter,
+ wantGetParent=descriptor.wrapperCache)
+
+ self.parentIface = descriptor.interface.parent
+ if self.parentIface:
+ self.parentDesc = descriptor.getDescriptor(
+ self.parentIface.identifier.name)
+ bases = [ClassBase(self.nativeLeafName(self.parentDesc))]
+ else:
+ bases = [ClassBase("nsISupports /* or NonRefcountedDOMObject if this is a non-refcounted object */")]
+ if descriptor.wrapperCache:
+ bases.append(ClassBase("nsWrapperCache /* Change wrapperCache in the binding configuration if you don't want this */"))
+
+ destructorVisibility = "protected"
+ if self.parentIface:
+ extradeclarations = (
+ "public:\n"
+ " NS_DECL_ISUPPORTS_INHERITED\n"
+ " NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(%s, %s)\n"
+ "\n" % (self.nativeLeafName(descriptor),
+ self.nativeLeafName(self.parentDesc)))
+ else:
+ extradeclarations = (
+ "public:\n"
+ " NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n"
+ " NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(%s)\n"
+ "\n" % self.nativeLeafName(descriptor))
+
+ if descriptor.interface.hasChildInterfaces():
+ decorators = ""
+ else:
+ decorators = "final"
+
+ CGClass.__init__(self, self.nativeLeafName(descriptor),
+ bases=bases,
+ constructors=[ClassConstructor([],
+ visibility="public")],
+ destructor=ClassDestructor(visibility=destructorVisibility),
+ methods=self.methodDecls,
+ decorators=decorators,
+ extradeclarations=extradeclarations)
+
+ def define(self):
+ # Just override CGClass and do our own thing
+ ctordtor = dedent("""
+ ${nativeType}::${nativeType}()
+ {
+ // Add |MOZ_COUNT_CTOR(${nativeType});| for a non-refcounted object.
+ }
+
+ ${nativeType}::~${nativeType}()
+ {
+ // Add |MOZ_COUNT_DTOR(${nativeType});| for a non-refcounted object.
+ }
+ """)
+
+ if self.parentIface:
+ ccImpl = dedent("""
+
+ // 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})
+
+ """)
+ else:
+ ccImpl = dedent("""
+
+ // 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
+
+ """)
+
+ if self.descriptor.wrapperCache:
+ reflectorArg = ""
+ reflectorPassArg = ""
+ returnType = "JSObject*"
+ else:
+ reflectorArg = ", JS::MutableHandle<JSObject*> aReflector"
+ reflectorPassArg = ", aReflector"
+ returnType = "bool"
+ classImpl = ccImpl + ctordtor + "\n" + dedent("""
+ ${returnType}
+ ${nativeType}::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto${reflectorArg})
+ {
+ return ${ifaceName}Binding::Wrap(aCx, this, aGivenProto${reflectorPassArg});
+ }
+
+ """)
+ return string.Template(classImpl).substitute(
+ ifaceName=self.descriptor.name,
+ nativeType=self.nativeLeafName(self.descriptor),
+ parentType=self.nativeLeafName(self.parentDesc) if self.parentIface else "",
+ returnType=returnType,
+ reflectorArg=reflectorArg,
+ reflectorPassArg=reflectorPassArg)
+
+ @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()
+ for member in descriptor.interface.members:
+ if not member.isAttr() and not member.isMethod():
+ continue
+ if member.isStatic():
+ builder.addInMozillaDom("GlobalObject")
+ if member.isAttr() and 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("JSCompartment*", "aCompartment", "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):
+ virtual = False
+ override = False
+ if (method.identifier.name == "eventListenerWasAdded" or
+ method.identifier.name == "eventListenerWasRemoved"):
+ virtual = True
+ override = 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,
+ virtual=virtual,
+ override=override)
+
+ def getArgs(self, returnType, argList):
+ if self.isConstructor:
+ # Skip the JSCompartment bits for constructors; it's handled
+ # manually in getImpl.
+ return CGNativeMember.getArgs(self, returnType, argList)
+ 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()
+ assert args[0].argType == 'const GlobalObject&'
+ assert args[1].argType == 'JSContext*'
+ constructorArgs = [arg.name for arg in args[2:]]
+ constructorArgs.append("js::GetObjectCompartment(scopeObj)")
+ initCall = fill(
+ """
+ // Wrap the object before calling __Init so that __DOM_IMPL__ is available.
+ JS::Rooted<JSObject*> scopeObj(cx, globalHolder->GetGlobalJSObject());
+ MOZ_ASSERT(js::IsObjectInContextCompartment(scopeObj, cx));
+ JS::Rooted<JS::Value> wrappedVal(cx);
+ if (!GetOrCreateDOMReflector(cx, impl, &wrappedVal)) {
+ //XXX Assertion disabled for now, see bug 991271.
+ MOZ_ASSERT(true || 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 genConstructorBody(self.descriptor, initCall)
+
+
+def genConstructorBody(descriptor, initCall=""):
+ return fill(
+ """
+ JS::Rooted<JSObject*> jsImplObj(cx);
+ nsCOMPtr<nsIGlobalObject> globalHolder =
+ ConstructJSImplementation("${contractId}", global, &jsImplObj, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ // Build the C++ implementation.
+ RefPtr<${implClass}> impl = new ${implClass}(jsImplObj, globalHolder);
+ $*{initCall}
+ return impl.forget();
+ """,
+ contractId=descriptor.interface.getJSImplementation(),
+ implClass=descriptor.name,
+ initCall=initCall)
+
+
+# We're always fallible
+def callbackGetterName(attr, descriptor):
+ return "Get" + MakeNativeName(
+ descriptor.binaryNameFor(attr.identifier.name))
+
+
+def callbackSetterName(attr, descriptor):
+ return "Set" + MakeNativeName(
+ descriptor.binaryNameFor(attr.identifier.name))
+
+
+class CGJSImplClearCachedValueMethod(CGAbstractBindingMethod):
+ def __init__(self, descriptor, attr):
+ if attr.getExtendedAttribute("StoreInSlot"):
+ raise TypeError("[StoreInSlot] is not supported for JS-implemented WebIDL. See bug 1056325.")
+
+ CGAbstractBindingMethod.__init__(self, descriptor,
+ MakeJSImplClearCachedValueNativeName(attr),
+ JSNativeArguments())
+ self.attr = attr
+
+ def generate_code(self):
+ return CGGeneric(fill(
+ """
+ ${bindingNamespace}::${fnName}(self);
+ args.rval().setUndefined();
+ return true;
+ """,
+ bindingNamespace=toBindingNamespace(self.descriptor.name),
+ fnName=MakeClearCachedValueNativeName(self.attr)))
+
+
+class CGJSImplGetter(CGJSImplMember):
+ """
+ Class for generating code for the getters of attributes for a JS-implemented
+ WebIDL interface.
+ """
+ def __init__(self, descriptor, attr):
+ CGJSImplMember.__init__(self, descriptor, attr,
+ CGSpecializedGetter.makeNativeName(descriptor,
+ attr),
+ (attr.type, []),
+ descriptor.getExtendedAttributes(attr,
+ getter=True),
+ passJSBitsAsNeeded=False)
+
+ def getImpl(self):
+ callbackArgs = [arg.name for arg in self.getArgs(self.member.type, [])]
+ return 'return mImpl->%s(%s);\n' % (
+ callbackGetterName(self.member, self.descriptorProvider),
+ ", ".join(callbackArgs))
+
+
+class CGJSImplSetter(CGJSImplMember):
+ """
+ Class for generating code for the setters of attributes for a JS-implemented
+ WebIDL interface.
+ """
+ def __init__(self, descriptor, attr):
+ CGJSImplMember.__init__(self, descriptor, attr,
+ CGSpecializedSetter.makeNativeName(descriptor,
+ attr),
+ (BuiltinTypes[IDLBuiltinType.Types.void],
+ [FakeArgument(attr.type, attr)]),
+ descriptor.getExtendedAttributes(attr,
+ setter=True),
+ passJSBitsAsNeeded=False)
+
+ def getImpl(self):
+ callbackArgs = [arg.name for arg in self.getArgs(BuiltinTypes[IDLBuiltinType.Types.void],
+ [FakeArgument(self.member.type, self.member)])]
+ return 'mImpl->%s(%s);\n' % (
+ callbackSetterName(self.member, self.descriptorProvider),
+ ", ".join(callbackArgs))
+
+
+class CGJSImplClass(CGBindingImplClass):
+ def __init__(self, descriptor):
+ CGBindingImplClass.__init__(self, descriptor, CGJSImplMethod, CGJSImplGetter, CGJSImplSetter, skipStaticMethods=True)
+
+ if descriptor.interface.parent:
+ parentClass = descriptor.getDescriptor(
+ descriptor.interface.parent.identifier.name).jsImplParent
+ baseClasses = [ClassBase(parentClass)]
+ isupportsDecl = "NS_DECL_ISUPPORTS_INHERITED\n"
+ ccDecl = ("NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(%s, %s)\n" %
+ (descriptor.name, parentClass))
+ constructorBody = dedent("""
+ // Make sure we're an nsWrapperCache already
+ MOZ_ASSERT(static_cast<nsWrapperCache*>(this));
+ // And that our ancestor has not called SetIsNotDOMBinding()
+ MOZ_ASSERT(IsDOMBinding());
+ """)
+ 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_INHERITED(${ifaceName})
+ NS_INTERFACE_MAP_END_INHERITING(${parentClass})
+ """,
+ ifaceName=self.descriptor.name,
+ parentClass=parentClass)
+ else:
+ baseClasses = [ClassBase("nsSupportsWeakReference"),
+ ClassBase("nsWrapperCache")]
+ isupportsDecl = "NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n"
+ ccDecl = ("NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(%s)\n" %
+ descriptor.name)
+ extradefinitions = fill(
+ """
+ NS_IMPL_CYCLE_COLLECTION_CLASS(${ifaceName})
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(${ifaceName})
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->ClearWeakReferences();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(${ifaceName})
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+ NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(${ifaceName})
+ NS_IMPL_CYCLE_COLLECTING_ADDREF(${ifaceName})
+ NS_IMPL_CYCLE_COLLECTING_RELEASE(${ifaceName})
+ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${ifaceName})
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_END
+ """,
+ ifaceName=self.descriptor.name)
+
+ extradeclarations = fill(
+ """
+ public:
+ $*{isupportsDecl}
+ $*{ccDecl}
+
+ private:
+ RefPtr<${jsImplName}> mImpl;
+ nsCOMPtr<nsISupports> mParent;
+
+ """,
+ isupportsDecl=isupportsDecl,
+ ccDecl=ccDecl,
+ jsImplName=jsImplName(descriptor.name))
+
+ 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, /* aIncumbentGlobal = */ nullptr))" %
+ jsImplName(descriptor.name)),
+ "mParent(aParent)"]
+ parentInterface = descriptor.interface.parent
+ while parentInterface:
+ if parentInterface.isJSImplemented():
+ baseConstructors.insert(
+ 0, "%s(aJSImplObject, 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("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
+ JSAutoCompartment ac(aCx, mImpl->Callback());
+ if (!JS_WrapObject(aCx, &obj)) {
+ return nullptr;
+ }
+ if (!JS_DefineProperty(aCx, mImpl->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 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.length() < 2) {
+ return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${ifaceName}._create");
+ }
+ if (!args[0].isObject()) {
+ return ThrowErrorMessage(cx, MSG_NOT_OBJECT, "Argument 1 of ${ifaceName}._create");
+ }
+ if (!args[1].isObject()) {
+ return ThrowErrorMessage(cx, MSG_NOT_OBJECT, "Argument 2 of ${ifaceName}._create");
+ }
+
+ // 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());
+ RefPtr<${implName}> impl = new ${implName}(arg, globalHolder);
+ MOZ_ASSERT(js::IsObjectInContextCompartment(arg, cx));
+ return GetOrCreateDOMReflector(cx, impl, args.rval());
+ """,
+ ifaceName=self.descriptor.interface.identifier.name,
+ implName=self.descriptor.name)
+
+
+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("nsIGlobalObject*", "aIncumbentGlobal")],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ baseConstructors=[
+ "%s(aCx, aCallback, aIncumbentGlobal)" % self.baseName,
+ ],
+ body=body),
+ ClassConstructor(
+ [Argument("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aCallback"),
+ Argument("nsIGlobalObject*", "aIncumbentGlobal"),
+ Argument("const FastCallbackConstructor&", "")],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ baseConstructors=[
+ "%s(aCx, aCallback, aIncumbentGlobal, FastCallbackConstructor())" % self.baseName,
+ ],
+ body=body),
+ ClassConstructor(
+ [Argument("JS::Handle<JSObject*>", "aCallback"),
+ Argument("JS::Handle<JSObject*>", "aAsyncStack"),
+ Argument("nsIGlobalObject*", "aIncumbentGlobal")],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ baseConstructors=[
+ "%s(aCallback, aAsyncStack, aIncumbentGlobal)" % self.baseName,
+ ],
+ body=body)]
+
+ def getMethodImpls(self, method):
+ assert method.needThisHandling
+ args = list(method.args)
+ # Strip out the JSContext*/JSObject* args
+ # that got added.
+ assert args[0].name == "cx" and args[0].argType == "JSContext*"
+ 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.GetContext()", "thisValJS"] + argnames
+ argnamesWithoutThis = ["s.GetContext()", "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 aCompartment 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 aCompartment is provided
+ # and its principal doesn't subsume either the callback or the
+ # exception.
+ args.append(Argument("JSCompartment*", "aCompartment", "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, "rv");
+ # 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 aCompartment 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, "rv")
+
+ errorReturn = method.getDefaultRetval()
+
+ setupCall = fill(
+ """
+ if (!aExecutionReason) {
+ aExecutionReason = "${executionReason}";
+ }
+ CallSetup s(this, aRv, aExecutionReason, aExceptionHandling, aCompartment);
+ 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(
+ """
+ IgnoredErrorResult rv;
+ return ${methodName}(${callArgs});
+ """,
+ methodName=method.name,
+ callArgs=", ".join(argnamesWithoutRv))
+ bodyWithoutThisAndRv = fill(
+ """
+ IgnoredErrorResult rv;
+ return ${methodName}(${callArgs});
+ """,
+ methodName=method.name,
+ callArgs=", ".join(argnamesWithoutThisAndRv))
+
+ return [ClassMethod(method.name, method.returnType, args,
+ bodyInHeader=True,
+ templateArgs=["typename T"],
+ body=bodyWithThis),
+ ClassMethod(method.name, method.returnType, argsWithoutThis,
+ bodyInHeader=True,
+ body=bodyWithoutThis),
+ ClassMethod(method.name, method.returnType, argsWithoutRv,
+ bodyInHeader=True,
+ templateArgs=["typename T"],
+ body=bodyWithThisWithoutRv),
+ ClassMethod(method.name, method.returnType, argsWithoutThisAndRv,
+ bodyInHeader=True,
+ body=bodyWithoutThisAndRv),
+ method]
+
+ def deps(self):
+ return self._deps
+
+
+class CGCallbackFunction(CGCallback):
+ def __init__(self, callback, descriptorProvider):
+ self.callback = callback
+ CGCallback.__init__(self, callback, descriptorProvider,
+ "CallbackFunction",
+ methods=[CallCallback(callback, descriptorProvider)])
+
+ 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("JSContext*", "aCx"),
+ Argument("JS::Handle<JSObject*>", "aCallback"),
+ Argument("nsIGlobalObject*", "aIncumbentGlobal")],
+ bodyInHeader=True,
+ visibility="public",
+ explicit=True,
+ baseConstructors=[
+ "%s(aCx, aCallback, aIncumbentGlobal, FastCallbackConstructor())" %
+ baseName,
+ ],
+ body="")
+
+ traceMethod = ClassMethod("Trace", "void",
+ [Argument("JSTracer*", "aTracer")],
+ inline=True,
+ bodyInHeader=True,
+ visibility="public",
+ body="%s::Trace(aTracer);\n" % baseName)
+ holdMethod = ClassMethod("HoldJSObjectsIfMoreThanOneOwner", "void",
+ [],
+ inline=True,
+ bodyInHeader=True,
+ visibility="public",
+ body=(
+ "%s::HoldJSObjectsIfMoreThanOneOwner();\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, typedArraysAreStructs=False):
+ iface = descriptor.interface
+ attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()]
+ getters = [CallbackGetter(a, descriptor, typedArraysAreStructs)
+ for a in attrs]
+ setters = [CallbackSetter(a, descriptor, typedArraysAreStructs)
+ 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()]
+ methods = [CallbackOperation(m, sig, descriptor, typedArraysAreStructs)
+ for m in methods for sig in m.signatures()]
+ 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))
+ if any(m.isAttr() or m.isMethod() for m in iface.members) or (iface.isJSImplemented() and iface.ctor()):
+ methods.append(initIdsClassMethod([descriptor.binaryNameFor(m.identifier.name)
+ for m in iface.members
+ if m.isAttr() or m.isMethod()] +
+ (["__init"] if iface.isJSImplemented() and iface.ctor() else []),
+ iface.identifier.name + "Atoms"))
+ CGCallback.__init__(self, iface, descriptor, "CallbackInterface",
+ methods, getters=getters, setters=setters)
+
+
+class FakeMember():
+ def __init__(self, name=None):
+ self.treatNullAs = "Default"
+ 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, typedArraysAreStructs=False,
+ wrapScope='CallbackKnownNotGray()'):
+ """
+ needThisHandling is True if we need to be able to accept a specified
+ thisObj, False otherwise.
+ """
+ assert not rethrowContentException or not needThisHandling
+
+ self.retvalType = sig[0]
+ self.originalSig = sig
+ args = sig[1]
+ self.argCount = len(args)
+ if self.argCount > 0:
+ # Check for variadic arguments
+ lastArg = args[self.argCount-1]
+ if lastArg.variadic:
+ self.argCountStr = ("(%d - 1) + %s.Length()" %
+ (self.argCount, lastArg.identifier.name))
+ else:
+ self.argCountStr = "%d" % self.argCount
+ self.needThisHandling = needThisHandling
+ # If needThisHandling, we generate ourselves as private and the caller
+ # will handle generating public versions that handle the "this" stuff.
+ visibility = "private" if needThisHandling else "public"
+ self.rethrowContentException = rethrowContentException
+
+ self.wrapScope = wrapScope
+ # We don't care, for callback codegen, whether our original member was
+ # a method or attribute or whatnot. Just always pass FakeMember()
+ # here.
+ CGNativeMember.__init__(self, descriptorProvider, FakeMember(),
+ name, (self.retvalType, args),
+ extendedAttrs={},
+ passJSBitsAsNeeded=False,
+ visibility=visibility,
+ typedArraysAreStructs=typedArraysAreStructs)
+ # We have to do all the generation of our body now, because
+ # the caller relies on us throwing if we can't manage it.
+ self.exceptionCode = ("aRv.Throw(NS_ERROR_UNEXPECTED);\n"
+ "return%s;\n" % self.getDefaultRetval())
+ self.body = self.getImpl()
+
+ def getImpl(self):
+ setupCall = self.getCallSetup()
+ declRval = self.getRvalDecl()
+ if self.argCount > 0:
+ argvDecl = fill(
+ """
+ JS::AutoValueVector argv(cx);
+ if (!argv.resize(${argCount})) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return${errorReturn};
+ }
+ """,
+ argCount=self.argCountStr,
+ errorReturn=self.getDefaultRetval())
+ else:
+ # Avoid weird 0-sized arrays
+ argvDecl = ""
+ convertArgs = self.getArgConversions()
+ doCall = self.getCall()
+ returnResult = self.getResultConversion()
+
+ return setupCall + declRval + argvDecl + convertArgs + doCall + returnResult
+
+ def getResultConversion(self):
+ replacements = {
+ "val": "rval",
+ "holderName": "rvalHolder",
+ "declName": "rvalDecl",
+ # We actually want to pass in a null scope object here, because
+ # wrapping things into our current compartment (that of mCallback)
+ # is what we want.
+ "obj": "nullptr",
+ "passedToJSImpl": "false"
+ }
+
+ if isJSImplementedDescriptor(self.descriptorProvider):
+ isCallbackReturnValue = "JSImpl"
+ else:
+ isCallbackReturnValue = "Callback"
+ sourceDescription = "return value of %s" % self.getPrettyName()
+ convertType = instantiateJSToNativeConversion(
+ getJSToNativeConversionInfo(self.retvalType,
+ self.descriptorProvider,
+ exceptionCode=self.exceptionCode,
+ isCallbackReturnValue=isCallbackReturnValue,
+ # Allow returning a callback type that
+ # allows non-callable objects.
+ allowTreatNonCallableAsNull=True,
+ sourceDescription=sourceDescription),
+ replacements)
+ assignRetval = string.Template(
+ self.getRetvalInfo(self.retvalType,
+ False)[2]).substitute(replacements)
+ type = convertType.define()
+ return type + assignRetval
+
+ def getArgConversions(self):
+ # Just reget the arglist from self.originalSig, because our superclasses
+ # just have way to many members they like to clobber, so I can't find a
+ # safe member name to store it in.
+ argConversions = [self.getArgConversion(i, arg)
+ for i, arg in enumerate(self.originalSig[1])]
+ if not argConversions:
+ return "\n"
+
+ # Do them back to front, so our argc modifications will work
+ # correctly, because we examine trailing arguments first.
+ argConversions.reverse()
+ # Wrap each one in a scope so that any locals it has don't leak out, and
+ # also so that we can just "break;" for our successCode.
+ argConversions = [CGWrapper(CGIndenter(CGGeneric(c)),
+ pre="do {\n",
+ post="} while (0);\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 = ""
+
+ try:
+ conversion = prepend + wrapForType(
+ arg.type, self.descriptorProvider,
+ {
+ 'result': result,
+ 'successCode': "continue;\n" if arg.variadic else "break;\n",
+ 'jsvalRef': "argv[%s]" % jsvalIndex,
+ 'jsvalHandle': "argv[%s]" % jsvalIndex,
+ 'obj': self.wrapScope,
+ 'returnsNewObject': False,
+ 'exceptionCode': self.exceptionCode,
+ 'typedArraysAreStructs': self.typedArraysAreStructs
+ })
+ except MethodNotNewObjectError as err:
+ raise TypeError("%s being passed as an argument to %s but is not "
+ "wrapper cached, so can't be reliably converted to "
+ "a JS object." %
+ (err.typename, self.getPrettyName()))
+ 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("JSCompartment*", "aCompartment", "nullptr"))
+ return args
+ # We want to allow the caller to pass in a "this" value, as
+ # well as a JSContext.
+ return [Argument("JSContext*", "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
+ # aCompartment for us.
+ callSetup += ', "%s", eRethrowContentExceptions, aCompartment, /* aIsJSImplementedWebIDL = */ ' % self.getPrettyName()
+ callSetup += toStringBool(isJSImplementedDescriptor(self.descriptorProvider))
+ else:
+ callSetup += ', "%s", aExceptionHandling, aCompartment' % self.getPrettyName()
+ callSetup += ");\n"
+ return fill(
+ """
+ $*{callSetup}
+ JSContext* cx = s.GetContext();
+ if (!cx) {
+ MOZ_ASSERT(aRv.Failed());
+ return${errorReturn};
+ }
+ """,
+ 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 CallbackMethod(CallbackMember):
+ def __init__(self, sig, name, descriptorProvider, needThisHandling,
+ rethrowContentException=False, typedArraysAreStructs=False):
+ CallbackMember.__init__(self, sig, name, descriptorProvider,
+ needThisHandling, rethrowContentException,
+ typedArraysAreStructs=typedArraysAreStructs)
+
+ def getRvalDecl(self):
+ return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
+
+ def getCall(self):
+ if self.argCount > 0:
+ args = "JS::HandleValueArray::subarray(argv, 0, argc)"
+ else:
+ args = "JS::HandleValueArray::empty()"
+
+ return fill(
+ """
+ $*{declCallable}
+ $*{declThis}
+ if (${callGuard}!JS::Call(cx, ${thisVal}, callable,
+ ${args}, &rval)) {
+ aRv.NoteJSContextException(cx);
+ return${errorReturn};
+ }
+ """,
+ declCallable=self.getCallableDecl(),
+ declThis=self.getThisDecl(),
+ callGuard=self.getCallGuard(),
+ thisVal=self.getThisVal(),
+ args=args,
+ errorReturn=self.getDefaultRetval())
+
+
+class CallCallback(CallbackMethod):
+ def __init__(self, callback, descriptorProvider):
+ self.callback = callback
+ CallbackMethod.__init__(self, callback.signatures()[0], "Call",
+ descriptorProvider, needThisHandling=True)
+
+ 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,
+ typedArraysAreStructs=False):
+ self.singleOperation = singleOperation
+ self.methodName = descriptor.binaryNameFor(jsName)
+ CallbackMethod.__init__(self, signature, nativeName, descriptor,
+ singleOperation, rethrowContentException,
+ typedArraysAreStructs=typedArraysAreStructs)
+
+ 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) && !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, typedArraysAreStructs):
+ self.ensureASCIIName(method)
+ self.method = method
+ jsName = method.identifier.name
+ CallbackOperationBase.__init__(self, signature,
+ jsName,
+ MakeNativeName(descriptor.binaryNameFor(jsName)),
+ descriptor, descriptor.interface.isSingleOperationInterface(),
+ rethrowContentException=descriptor.interface.isJSImplemented(),
+ typedArraysAreStructs=typedArraysAreStructs)
+
+ 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, typedArraysAreStructs):
+ self.ensureASCIIName(attr)
+ self.attrName = attr.identifier.name
+ CallbackMember.__init__(self, sig, name, descriptor,
+ needThisHandling=False,
+ rethrowContentException=descriptor.interface.isJSImplemented(),
+ typedArraysAreStructs=typedArraysAreStructs)
+
+ def getPrettyName(self):
+ return "%s.%s" % (self.descriptorProvider.interface.identifier.name,
+ self.attrName)
+
+
+class CallbackGetter(CallbackAccessor):
+ def __init__(self, attr, descriptor, typedArraysAreStructs):
+ CallbackAccessor.__init__(self, attr,
+ (attr.type, []),
+ callbackGetterName(attr, descriptor),
+ descriptor,
+ typedArraysAreStructs)
+
+ def getRvalDecl(self):
+ return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
+
+ def getCall(self):
+ return fill(
+ """
+ JS::Rooted<JSObject *> callback(cx, mCallback);
+ ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
+ if ((!*reinterpret_cast<jsid**>(atomsCache) && !InitIds(cx, atomsCache)) ||
+ !JS_GetPropertyById(cx, callback, atomsCache->${attrAtomName}, &rval)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return${errorReturn};
+ }
+ """,
+ atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
+ attrAtomName=CGDictionary.makeIdName(self.descriptorProvider.binaryNameFor(self.attrName)),
+ errorReturn=self.getDefaultRetval())
+
+
+class CallbackSetter(CallbackAccessor):
+ def __init__(self, attr, descriptor, typedArraysAreStructs):
+ CallbackAccessor.__init__(self, attr,
+ (BuiltinTypes[IDLBuiltinType.Types.void],
+ [FakeArgument(attr.type, attr)]),
+ callbackSetterName(attr, descriptor),
+ descriptor, typedArraysAreStructs)
+
+ def getRvalDecl(self):
+ # We don't need an rval
+ return ""
+
+ def getCall(self):
+ return fill(
+ """
+ MOZ_ASSERT(argv.length() == 1);
+ ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx);
+ if ((!*reinterpret_cast<jsid**>(atomsCache) && !InitIds(cx, atomsCache)) ||
+ !JS_SetPropertyById(cx, CallbackKnownNotGray(), atomsCache->${attrAtomName}, argv[0])) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return${errorReturn};
+ }
+ """,
+ atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms",
+ attrAtomName=CGDictionary.makeIdName(self.descriptorProvider.binaryNameFor(self.attrName)),
+ errorReturn=self.getDefaultRetval())
+
+ def getArgcDecl(self):
+ return None
+
+
+class CGJSImplInitOperation(CallbackOperationBase):
+ """
+ Codegen the __Init() method used to pass along constructor arguments for JS-implemented WebIDL.
+ """
+ def __init__(self, sig, descriptor):
+ assert sig in descriptor.interface.ctor().signatures()
+ CallbackOperationBase.__init__(self, (BuiltinTypes[IDLBuiltinType.Types.void], sig[1]),
+ "__init", "__Init", descriptor,
+ singleOperation=False,
+ rethrowContentException=True,
+ typedArraysAreStructs=True)
+
+ def getPrettyName(self):
+ return "__init"
+
+
+def getMaplikeOrSetlikeErrorReturn(helperImpl):
+ """
+ Generate return values based on whether a maplike or setlike generated
+ method is an interface method (which returns bool) or a helper function
+ (which uses ErrorResult).
+ """
+ if helperImpl:
+ return dedent(
+ """
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return%s;
+ """ % helperImpl.getDefaultRetval())
+ return "return false;\n"
+
+
+def getMaplikeOrSetlikeBackingObject(descriptor, maplikeOrSetlike, helperImpl=None):
+ """
+ Generate code to get/create a JS backing object for a maplike/setlike
+ declaration from the declaration slot.
+ """
+ func_prefix = maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title()
+ ret = fill(
+ """
+ JS::Rooted<JSObject*> backingObj(cx);
+ bool created = false;
+ if (!Get${func_prefix}BackingObject(cx, obj, ${slot}, &backingObj, &created)) {
+ $*{errorReturn}
+ }
+ if (created) {
+ PreserveWrapper<${selfType}>(self);
+ }
+ """,
+ slot=memberReservedSlot(maplikeOrSetlike, descriptor),
+ func_prefix=func_prefix,
+ errorReturn=getMaplikeOrSetlikeErrorReturn(helperImpl),
+ selfType=descriptor.nativeType)
+ return ret
+
+
+def getMaplikeOrSetlikeSizeGetterBody(descriptor, attr):
+ """
+ Creates the body for the size getter method of maplike/setlike interfaces.
+ """
+ # We should only have one declaration attribute currently
+ assert attr.identifier.name == "size"
+ assert attr.isMaplikeOrSetlikeAttr()
+ return fill(
+ """
+ $*{getBackingObj}
+ uint32_t result = JS::${funcPrefix}Size(cx, backingObj);
+ MOZ_ASSERT(!JS_IsExceptionPending(cx));
+ args.rval().setNumber(result);
+ return true;
+ """,
+ getBackingObj=getMaplikeOrSetlikeBackingObject(descriptor,
+ attr.maplikeOrSetlike),
+ funcPrefix=attr.maplikeOrSetlike.prefix)
+
+
+class CGMaplikeOrSetlikeMethodGenerator(CGThing):
+ """
+ Creates methods for maplike/setlike interfaces. It is expected that all
+ methods will be have a maplike/setlike object attached. Unwrapping/wrapping
+ will be taken care of by the usual method generation machinery in
+ CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of
+ using CGCallGenerator.
+ """
+ def __init__(self, descriptor, maplikeOrSetlike, methodName,
+ helperImpl=None):
+ CGThing.__init__(self)
+ # True if this will be the body of a C++ helper function.
+ self.helperImpl = helperImpl
+ self.descriptor = descriptor
+ self.maplikeOrSetlike = maplikeOrSetlike
+ self.cgRoot = CGList([])
+ impl_method_name = methodName
+ if impl_method_name[0] == "_":
+ # double underscore means this is a js-implemented chrome only rw
+ # function. Truncate the double underscore so calling the right
+ # underlying JSAPI function still works.
+ impl_method_name = impl_method_name[2:]
+ self.cgRoot.append(CGGeneric(
+ getMaplikeOrSetlikeBackingObject(self.descriptor,
+ self.maplikeOrSetlike,
+ self.helperImpl)))
+ self.returnStmt = getMaplikeOrSetlikeErrorReturn(self.helperImpl)
+
+ # Generates required code for the method. Method descriptions included
+ # in definitions below. Throw if we don't have a method to fill in what
+ # we're looking for.
+ try:
+ methodGenerator = getattr(self, impl_method_name)
+ except AttributeError:
+ raise TypeError("Missing %s method definition '%s'" %
+ (self.maplikeOrSetlike.maplikeOrSetlikeType,
+ methodName))
+ # Method generator returns tuple, containing:
+ #
+ # - a list of CGThings representing setup code for preparing to call
+ # the JS API function
+ # - a list of arguments needed for the JS API function we're calling
+ # - list of code CGThings needed for return value conversion.
+ (setupCode, arguments, setResult) = methodGenerator()
+
+ # Create the actual method call, and then wrap it with the code to
+ # return the value if needed.
+ funcName = (self.maplikeOrSetlike.prefix +
+ MakeNativeName(impl_method_name))
+ # Append the list of setup code CGThings
+ self.cgRoot.append(CGList(setupCode))
+ # Create the JS API call
+ self.cgRoot.append(CGWrapper(
+ CGGeneric(fill(
+ """
+ if (!JS::${funcName}(${args})) {
+ $*{errorReturn}
+ }
+ """,
+ funcName=funcName,
+ args=", ".join(["cx", "backingObj"] + arguments),
+ errorReturn=self.returnStmt))))
+ # Append result conversion
+ self.cgRoot.append(CGList(setResult))
+
+ def mergeTuples(self, a, b):
+ """
+ Expecting to take 2 tuples were all elements are lists, append the lists in
+ the second tuple to the lists in the first.
+ """
+ return tuple([x + y for x, y in zip(a, b)])
+
+ def appendArgConversion(self, name):
+ """
+ Generate code to convert arguments to JS::Values, so they can be
+ passed into JSAPI functions.
+ """
+ return CGGeneric(fill(
+ """
+ JS::Rooted<JS::Value> ${name}Val(cx);
+ if (!ToJSValue(cx, ${name}, &${name}Val)) {
+ $*{errorReturn}
+ }
+ """,
+ name=name,
+ errorReturn=self.returnStmt))
+
+ def appendKeyArgConversion(self):
+ """
+ Generates the key argument for methods. Helper functions will use
+ an AutoValueVector, while interface methods have seperate 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 an AutoValueVector, while interface methods have
+ seperate JS::Values.
+ """
+ r = self.appendKeyArgConversion()
+ if self.helperImpl:
+ return self.mergeTuples(r, ([], ["argv[1]"], []))
+ return self.mergeTuples(r, ([self.appendArgConversion("arg1")],
+ ["arg1Val"],
+ []))
+
+ def appendIteratorResult(self):
+ """
+ Generate code to output JSObject* return values, needed for functions that
+ return iterators. Iterators cannot currently be wrapped via Xrays. If
+ something that would return an iterator is called via Xray, fail early.
+ """
+ # TODO: Bug 1173651 - Remove check once bug 1023984 is fixed.
+ code = CGGeneric(dedent(
+ """
+ // TODO (Bug 1173651): Xrays currently cannot wrap iterators. Change
+ // after bug 1023984 is fixed.
+ if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
+ JS_ReportErrorASCII(cx, "Xray wrapping of iterators not supported.");
+ return false;
+ }
+ JS::Rooted<JSObject*> result(cx);
+ JS::Rooted<JS::Value> v(cx);
+ """))
+ arguments = "&v"
+ setResult = CGGeneric(dedent(
+ """
+ result = &v.toObject();
+ """))
+ return ([code], [arguments], [setResult])
+
+ def appendSelfResult(self):
+ """
+ Generate code to return the interface object itself.
+ """
+ code = CGGeneric(dedent(
+ """
+ JS::Rooted<JSObject*> result(cx);
+ """))
+ setResult = CGGeneric(dedent(
+ """
+ result = obj;
+ """))
+ return ([code], [], [setResult])
+
+ def appendBoolResult(self):
+ if self.helperImpl:
+ return ([CGGeneric()], ["&aRetVal"], [])
+ return ([CGGeneric("bool result;\n")], ["&result"], [])
+
+ def forEach(self):
+ """
+ void forEach(callback c, any thisval);
+
+ ForEach takes a callback, and a possible value to use as 'this'. The
+ callback needs to take value, key, and the interface object
+ implementing maplike/setlike. In order to make sure that the third arg
+ is our interface object instead of the map/set backing object, we
+ create a js function with the callback and original object in its
+ storage slots, then use a helper function in BindingUtils to make sure
+ the callback is called correctly.
+ """
+ assert(not self.helperImpl)
+ code = [CGGeneric(dedent(
+ """
+ // Create a wrapper function.
+ JSFunction* func = js::NewFunctionWithReserved(cx, ForEachHandler, 3, 0, nullptr);
+ if (!func) {
+ return false;
+ }
+ JS::Rooted<JSObject*> funcObj(cx, JS_GetFunctionObject(func));
+ JS::Rooted<JS::Value> funcVal(cx, JS::ObjectValue(*funcObj));
+ js::SetFunctionNativeReserved(funcObj, FOREACH_CALLBACK_SLOT,
+ JS::ObjectValue(*arg0));
+ js::SetFunctionNativeReserved(funcObj, FOREACH_MAPLIKEORSETLIKEOBJ_SLOT,
+ JS::ObjectValue(*obj));
+ """))]
+ arguments = ["funcVal", "arg1"]
+ return (code, arguments, [])
+
+ def set(self):
+ """
+ object set(key, value);
+
+ Maplike only function, takes key and sets value to it, returns
+ interface object unless being called from a C++ helper.
+ """
+ assert self.maplikeOrSetlike.isMaplike()
+ r = self.appendKeyAndValueArgConversion()
+ if self.helperImpl:
+ return r
+ return self.mergeTuples(r, self.appendSelfResult())
+
+ def add(self):
+ """
+ object add(value);
+
+ Setlike only function, adds value to set, returns interface object
+ unless being called from a C++ helper
+ """
+ assert self.maplikeOrSetlike.isSetlike()
+ r = self.appendKeyArgConversion()
+ if self.helperImpl:
+ return r
+ return self.mergeTuples(r, self.appendSelfResult())
+
+ def get(self):
+ """
+ type? get(key);
+
+ Retrieves a value from a backing object based on the key. Returns value
+ if key is in backing object, undefined otherwise.
+ """
+ assert self.maplikeOrSetlike.isMaplike()
+ r = self.appendKeyArgConversion()
+ code = [CGGeneric(dedent(
+ """
+ JS::Rooted<JS::Value> result(cx);
+ """))]
+ arguments = ["&result"]
+ 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 CGMaplikeOrSetlikeHelperFunctionGenerator(CallbackMember):
+ """
+ Generates code to allow C++ to perform operations on backing objects. Gets
+ a context from the binding wrapper, turns arguments into JS::Values (via
+ CallbackMember/CGNativeMember argument conversion), then uses
+ CGMaplikeOrSetlikeMethodGenerator to generate the body.
+
+ """
+
+ class HelperFunction(CGAbstractMethod):
+ """
+ Generates context retrieval code and rooted JSObject for interface for
+ CGMaplikeOrSetlikeMethodGenerator to use
+ """
+ def __init__(self, descriptor, name, args, code, needsBoolReturn=False):
+ self.code = code
+ CGAbstractMethod.__init__(self, descriptor, name,
+ "bool" if needsBoolReturn else "void",
+ args)
+
+ def definition_body(self):
+ return self.code
+
+ def __init__(self, descriptor, maplikeOrSetlike, name, needsKeyArg=False,
+ needsValueArg=False, needsBoolReturn=False):
+ args = []
+ self.maplikeOrSetlike = maplikeOrSetlike
+ self.needsBoolReturn = needsBoolReturn
+ if needsKeyArg:
+ args.append(FakeArgument(maplikeOrSetlike.keyType, None, 'aKey'))
+ if needsValueArg:
+ assert needsKeyArg
+ args.append(FakeArgument(maplikeOrSetlike.valueType, None, 'aValue'))
+ # Run CallbackMember init function to generate argument conversion code.
+ # wrapScope is set to 'obj' when generating maplike or setlike helper
+ # functions, as we don't have access to the CallbackPreserveColor
+ # method.
+ CallbackMember.__init__(self,
+ [BuiltinTypes[IDLBuiltinType.Types.void], args],
+ name, descriptor, False,
+ wrapScope='obj')
+ # Wrap CallbackMember body code into a CGAbstractMethod to make
+ # generation easier.
+ self.implMethod = CGMaplikeOrSetlikeHelperFunctionGenerator.HelperFunction(
+ descriptor, name, self.args, self.body, needsBoolReturn)
+
+ def getCallSetup(self):
+ return dedent(
+ """
+ MOZ_ASSERT(self);
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ // It's safe to use UnprivilegedJunkScopeOrWorkerGlobal here because
+ // all we want is to wrap into _some_ scope and then unwrap to find
+ // the reflector, and wrapping has no side-effects.
+ JSAutoCompartment tempCompartment(cx, binding_detail::UnprivilegedJunkScopeOrWorkerGlobal());
+ JS::Rooted<JS::Value> v(cx);
+ if(!ToJSValue(cx, self, &v)) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return%s;
+ }
+ // This is a reflector, but due to trying to name things
+ // similarly across method generators, it's called obj here.
+ JS::Rooted<JSObject*> obj(cx);
+ obj = js::UncheckedUnwrap(&v.toObject(), /* stopAtWindowProxy = */ false);
+ JSAutoCompartment reflectorCompartment(cx, obj);
+ """ % self.getDefaultRetval())
+
+ def getArgs(self, returnType, argList):
+ # We don't need the context or the value. We'll generate those instead.
+ args = CGNativeMember.getArgs(self, returnType, argList)
+ # Prepend a pointer to the binding object onto the arguments
+ return [Argument(self.descriptorProvider.nativeType + "*", "self")] + args
+
+ def getResultConversion(self):
+ if self.needsBoolReturn:
+ return "return aRetVal;\n"
+ return "return;\n"
+
+ def getRvalDecl(self):
+ if self.needsBoolReturn:
+ return "bool aRetVal;\n"
+ return ""
+
+ def getArgcDecl(self):
+ # Don't need argc for anything.
+ return None
+
+ def getDefaultRetval(self):
+ if self.needsBoolReturn:
+ return " false"
+ return ""
+
+ def getCall(self):
+ return CGMaplikeOrSetlikeMethodGenerator(self.descriptorProvider,
+ self.maplikeOrSetlike,
+ self.name.lower(),
+ helperImpl=self).define()
+
+ def getPrettyName(self):
+ return self.name
+
+ def declare(self):
+ return self.implMethod.declare()
+
+ def define(self):
+ return self.implMethod.define()
+
+
+class CGMaplikeOrSetlikeHelperGenerator(CGNamespace):
+ """
+ Declares and defines convenience methods for accessing backing objects on
+ setlike/maplike interface. Generates function signatures, un/packs
+ backing objects from slot, etc.
+ """
+ def __init__(self, descriptor, maplikeOrSetlike):
+ self.descriptor = descriptor
+ # Since iterables are folded in with maplike/setlike, make sure we've
+ # got the right type here.
+ assert maplikeOrSetlike.isMaplike() or maplikeOrSetlike.isSetlike()
+ self.maplikeOrSetlike = maplikeOrSetlike
+ self.namespace = "%sHelpers" % (self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title())
+ self.helpers = [
+ CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
+ maplikeOrSetlike,
+ "Clear"),
+ CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
+ maplikeOrSetlike,
+ "Delete",
+ needsKeyArg=True,
+ needsBoolReturn=True),
+ CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
+ maplikeOrSetlike,
+ "Has",
+ needsKeyArg=True,
+ needsBoolReturn=True)]
+ if self.maplikeOrSetlike.isMaplike():
+ self.helpers.append(
+ CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
+ maplikeOrSetlike,
+ "Set",
+ needsKeyArg=True,
+ needsValueArg=True))
+ else:
+ assert(self.maplikeOrSetlike.isSetlike())
+ self.helpers.append(
+ CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
+ maplikeOrSetlike,
+ "Add",
+ needsKeyArg=True))
+ CGNamespace.__init__(self, self.namespace, CGList(self.helpers))
+
+
+class CGIterableMethodGenerator(CGGeneric):
+ """
+ Creates methods for iterable interfaces. Unwrapping/wrapping
+ will be taken care of by the usual method generation machinery in
+ CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of
+ using CGCallGenerator.
+ """
+ def __init__(self, descriptor, iterable, methodName):
+ if methodName == "forEach":
+ CGGeneric.__init__(self, fill(
+ """
+ if (!JS::IsCallable(arg0)) {
+ ThrowErrorMessage(cx, MSG_NOT_CALLABLE, "Argument 1 of ${ifaceName}.forEach");
+ return false;
+ }
+ JS::AutoValueArray<3> callArgs(cx);
+ callArgs[2].setObject(*obj);
+ JS::Rooted<JS::Value> ignoredReturnVal(cx);
+ for (size_t i = 0; i < self->GetIterableLength(); ++i) {
+ if (!ToJSValue(cx, self->GetValueAtIndex(i), callArgs[0])) {
+ return false;
+ }
+ if (!ToJSValue(cx, self->GetKeyAtIndex(i), callArgs[1])) {
+ return false;
+ }
+ if (!JS::Call(cx, arg1, arg0, JS::HandleValueArray(callArgs),
+ &ignoredReturnVal)) {
+ return false;
+ }
+ }
+ """,
+ ifaceName=descriptor.interface.identifier.name))
+ return
+ CGGeneric.__init__(self, fill(
+ """
+ typedef ${iterClass} itrType;
+ RefPtr<itrType> result(new itrType(self,
+ itrType::IterableIteratorType::${itrMethod},
+ &${ifaceName}IteratorBinding::Wrap));
+ """,
+ iterClass=iteratorNativeType(descriptor),
+ ifaceName=descriptor.interface.identifier.name,
+ itrMethod=methodName.title()))
+
+
+class GlobalGenRoots():
+ """
+ Roots for global codegen.
+
+ To generate code, call the method associated with the target, and then
+ call the appropriate define/declare method.
+ """
+
+ @staticmethod
+ def GeneratedAtomList(config):
+ # Atom enum
+ dictionaries = config.dictionaries
+
+ structs = []
+
+ def memberToAtomCacheMember(binaryNameFor, m):
+ binaryMemberName = binaryNameFor(m.identifier.name)
+ return ClassMember(CGDictionary.makeIdName(binaryMemberName),
+ "PinnedStringId", visibility="public")
+
+ def buildAtomCacheStructure(idlobj, binaryNameFor, members):
+ classMembers = [memberToAtomCacheMember(binaryNameFor, m)
+ for m in members]
+ structName = idlobj.identifier.name + "Atoms"
+ return (structName,
+ CGWrapper(CGClass(structName,
+ bases=None,
+ isStruct=True,
+ members=classMembers), post='\n'))
+
+ for dict in dictionaries:
+ if len(dict.members) == 0:
+ continue
+
+ structs.append(buildAtomCacheStructure(dict, lambda x: x, dict.members))
+
+ for d in (config.getDescriptors(isJSImplemented=True) +
+ config.getDescriptors(isCallback=True)):
+ members = [m for m in d.interface.members if m.isAttr() or m.isMethod()]
+ if d.interface.isJSImplemented() and d.interface.ctor():
+ # We'll have an __init() method.
+ members.append(FakeMember('__init'))
+ if len(members) == 0:
+ continue
+
+ structs.append(buildAtomCacheStructure(d.interface,
+ lambda x: d.binaryNameFor(x),
+ members))
+
+ structs.sort()
+ generatedStructs = [struct for structName, struct in structs]
+ structNames = [structName for structName, struct in structs]
+
+ mainStruct = CGWrapper(CGClass("PerThreadAtomCache",
+ bases=[ClassBase(structName) for structName in structNames],
+ isStruct=True),
+ post='\n')
+
+ structs = CGList(generatedStructs + [mainStruct])
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(['mozilla', 'dom'],
+ CGWrapper(structs, pre='\n'))
+ curr = CGWrapper(curr, post='\n')
+
+ # Add include statement for PinnedStringId.
+ declareIncludes = ['mozilla/dom/BindingUtils.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(((JSJitInfo*)nullptr)->%s) * 8))" % jitInfoField
+ return CGGeneric(declare="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\n"),
+ idEnum])
+
+ # Let things know the maximum length of the prototype chain.
+ maxMacroName = "MAX_PROTOTYPE_CHAIN_LENGTH"
+ maxMacro = CGGeneric(declare="#define " + maxMacroName + " " + str(config.maxProtoChainLength))
+ curr.append(CGWrapper(maxMacro, post='\n\n'))
+ curr.append(fieldSizeAssert(maxMacroName, "depth",
+ "Some inheritance chain is too long!"))
+
+ # Constructor ID enum.
+ constructors = [d.name for d in config.getDescriptors(hasInterfaceObject=True)]
+ idEnum = CGNamespacedEnum('id', 'ID', ['_ID_Start'] + constructors,
+ ['prototypes::id::_ID_Count', '_ID_Start'])
+
+ # Wrap all of that in our namespaces.
+ idEnum = CGNamespace.build(['mozilla', 'dom', 'constructors'],
+ CGWrapper(idEnum, pre='\n'))
+ idEnum = CGWrapper(idEnum, post='\n')
+
+ curr.append(idEnum)
+
+ # Named properties object enum.
+ namedPropertiesObjects = [d.name for d in config.getDescriptors(hasNamedPropertiesObject=True)]
+ idEnum = CGNamespacedEnum('id', 'ID', ['_ID_Start'] + namedPropertiesObjects,
+ ['constructors::id::_ID_Count', '_ID_Start'])
+
+ # Wrap all of that in our namespaces.
+ idEnum = CGNamespace.build(['mozilla', 'dom', 'namedpropertiesobjects'],
+ CGWrapper(idEnum, pre='\n'))
+ idEnum = CGWrapper(idEnum, post='\n')
+
+ curr.append(idEnum)
+
+ traitsDecls = [CGGeneric(declare=dedent("""
+ template <prototypes::ID PrototypeID>
+ struct PrototypeTraits;
+ """))]
+ traitsDecls.extend(CGPrototypeTraitsClass(d) for d in descriptorsWithPrototype)
+
+ ifaceNamesWithProto = [d.interface.identifier.name
+ for d in descriptorsWithPrototype]
+ traitsDecls.append(CGStringTable("NamesOfInterfacesWithProtos",
+ ifaceNamesWithProto))
+
+ traitsDecl = CGNamespace.build(['mozilla', 'dom'],
+ CGList(traitsDecls))
+
+ curr.append(traitsDecl)
+
+ # Add include guards.
+ curr = CGIncludeGuard('PrototypeList', curr)
+
+ # Add the auto-generated comment.
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def RegisterBindings(config):
+
+ curr = CGList([CGGlobalNamesString(config), CGRegisterGlobalNames(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,
+ isExposedInWindow=True,
+ register=True)]
+ defineIncludes.append('mozilla/dom/WebIDLGlobalNameHash.h')
+ defineIncludes.extend([CGHeaders.getDeclarationFilename(desc.interface)
+ for desc in config.getDescriptors(isNavigatorProperty=True,
+ register=True)])
+ 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 ResolveSystemBinding(config):
+
+ curr = CGResolveSystemBinding(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,
+ isExposedInSystemGlobals=True)]
+ defineIncludes.append("nsThreadUtils.h") # For NS_IsMainThread
+ defineIncludes.append("js/Id.h") # For jsid
+ defineIncludes.append("mozilla/dom/BindingUtils.h") # AtomizeAndPinJSString
+
+ curr = CGHeaders([], [], [], [], [], defineIncludes,
+ 'ResolveSystemBinding', curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard('ResolveSystemBinding', curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def UnionTypes(config):
+ unionTypes = UnionsForFile(config, None)
+ (includes, implincludes, declarations,
+ traverseMethods, unlinkMethods,
+ unionStructs) = UnionTypes(unionTypes, config)
+
+ unions = CGList(traverseMethods +
+ unlinkMethods +
+ [CGUnionStruct(t, config) for t in unionStructs] +
+ [CGUnionStruct(t, config, True) for t in unionStructs],
+ "\n")
+
+ includes.add("mozilla/OwningNonNull.h")
+ includes.add("mozilla/dom/UnionMember.h")
+ includes.add("mozilla/dom/BindingDeclarations.h")
+ # BindingUtils.h is only needed for SetToObject.
+ # If it stops being inlined or stops calling CallerSubsumes
+ # both this bit and the bit in CGBindingRoot can be removed.
+ includes.add("mozilla/dom/BindingUtils.h")
+ implincludes.add("mozilla/dom/PrimitiveConversions.h")
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(['mozilla', 'dom'], unions)
+
+ curr = CGWrapper(curr, post='\n')
+
+ builder = ForwardDeclarationBuilder()
+ for className, isStruct in declarations:
+ builder.add(className, isStruct=isStruct)
+
+ curr = CGList([builder.build(), curr], "\n")
+
+ curr = CGHeaders([], [], [], [], includes, implincludes, 'UnionTypes',
+ curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard('UnionTypes', curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def UnionConversions(config):
+ unionTypes = []
+ for l in config.unionsPerFilename.itervalues():
+ unionTypes.extend(l)
+ unionTypes.sort(key=lambda u: u.name)
+ headers, unions = UnionConversions(unionTypes,
+ config)
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(['mozilla', 'dom'], unions)
+
+ curr = CGWrapper(curr, post='\n')
+
+ headers.update(["nsDebug.h", "mozilla/dom/UnionTypes.h"])
+ curr = CGHeaders([], [], [], [], headers, [], 'UnionConversions', curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard('UnionConversions', curr)
+
+ # Done.
+ return curr
+
+
+# Code generator for simple events
+class CGEventGetter(CGNativeMember):
+ def __init__(self, descriptor, attr):
+ ea = descriptor.getExtendedAttributes(attr, getter=True)
+ CGNativeMember.__init__(self, descriptor, attr,
+ CGSpecializedGetter.makeNativeName(descriptor,
+ attr),
+ (attr.type, []),
+ ea,
+ resultNotAddRefed=not attr.type.isSequence())
+ self.body = self.getMethodBody()
+
+ def getArgs(self, returnType, argList):
+ if 'infallible' not in self.extendedAttrs:
+ raise TypeError("Event code generator does not support [Throws]!")
+ if 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.isGeckoInterface():
+ return "return " + memberName + ";\n"
+ if type.isDOMString() or type.isByteString() or type.isUSVString():
+ 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():
+ return "aRetVal = " + memberName + ";\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(filter(lambda x: x.isAttr(), iface.members)) - 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"))
+ self.args.append(Argument('ErrorResult&', 'aRv'))
+ 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"))
+ self.args.append(Argument('ErrorResult&', 'aRv'))
+ 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 = []
+ for m in descriptor.interface.members:
+ if m.isAttr():
+ if m.type.isAny():
+ # 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))))
+ if getattr(m, "originatingInterface",
+ descriptor.interface) != descriptor.interface:
+ continue
+ 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]
+ baseDeclarations = fill(
+ """
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(${nativeType}, ${parentType})
+ protected:
+ virtual ~${nativeType}();
+ explicit ${nativeType}(mozilla::dom::EventTarget* aOwner);
+
+ """,
+ nativeType=self.descriptor.nativeType.split('::')[-1],
+ parentType=self.parentType)
+
+ className = descriptor.nativeType.split('::')[-1]
+ 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 %sBinding::Wrap(aCx, this, aGivenProto);\n" % self.descriptor.name
+
+ def implTraverse(self):
+ retVal = ""
+ for m in self.descriptor.interface.members:
+ # Unroll the type so we pick up sequences of interfaces too.
+ if m.isAttr() and idlTypeNeedsCycleCollection(m.type):
+ retVal += (" NS_IMPL_CYCLE_COLLECTION_TRAVERSE(" +
+ CGDictionary.makeMemberName(m.identifier.name) +
+ ")\n")
+ return retVal
+
+ def implUnlink(self):
+ retVal = ""
+ for m in self.descriptor.interface.members:
+ if m.isAttr():
+ name = CGDictionary.makeMemberName(m.identifier.name)
+ # Unroll the type so we pick up sequences of interfaces too.
+ if idlTypeNeedsCycleCollection(m.type):
+ retVal += " NS_IMPL_CYCLE_COLLECTION_UNLINK(" + name + ")\n"
+ elif m.type.isAny():
+ retVal += " tmp->" + name + ".setUndefined();\n"
+ elif m.type.isObject() or m.type.isSpiderMonkeyInterface():
+ retVal += " tmp->" + name + " = nullptr;\n"
+ return retVal
+
+ def implTrace(self):
+ retVal = ""
+ for m in self.descriptor.interface.members:
+ if m.isAttr():
+ name = CGDictionary.makeMemberName(m.identifier.name)
+ if m.type.isAny() or m.type.isObject() or m.type.isSpiderMonkeyInterface():
+ retVal += " NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(" + name + ")\n"
+ elif typeNeedsRooting(m.type):
+ raise TypeError("Need to implement tracing for event "
+ "member of type %s" % m.type)
+ return retVal
+
+ def define(self):
+ dropJS = ""
+ for m in self.descriptor.interface.members:
+ if m.isAttr():
+ member = CGDictionary.makeMemberName(m.identifier.name)
+ if m.type.isAny():
+ dropJS += member + " = JS::UndefinedValue();\n"
+ elif m.type.isObject() or m.type.isSpiderMonkeyInterface():
+ dropJS += member + " = nullptr;\n"
+ if dropJS != "":
+ dropJS += "mozilla::DropJSObjects(this);\n"
+ # Just override CGClass and do our own thing
+ nativeType = self.descriptor.nativeType.split('::')[-1]
+ ctorParams = ("aOwner, nullptr, nullptr" if self.parentType == "Event"
+ else "aOwner")
+
+ 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_INHERITED(${nativeType})
+ NS_INTERFACE_MAP_END_INHERITING(${parentType})
+
+ ${nativeType}::${nativeType}(mozilla::dom::EventTarget* aOwner)
+ : ${parentType}(${ctorParams})
+ {
+ }
+
+ ${nativeType}::~${nativeType}()
+ {
+ $*{dropJS}
+ }
+
+ """,
+ ifaceName=self.descriptor.name,
+ nativeType=nativeType,
+ ctorParams=ctorParams,
+ parentType=self.parentType,
+ traverse=self.implTraverse(),
+ unlink=self.implUnlink(),
+ trace=self.implTrace(),
+ 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.isDOMString() or type.isUSVString():
+ nativeType = CGGeneric("nsString")
+ elif type.isByteString():
+ nativeType = CGGeneric("nsCString")
+ 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.isGeckoInterface()):
+ raise TypeError("Don't know how to properly manage GC/CC for "
+ "event member of type %s" %
+ type)
+ nativeType = CGTemplatedType(
+ "nsTArray",
+ self.getNativeTypeForIDLType(innerType))
+ if type.nullable():
+ nativeType = CGTemplatedType("Nullable", nativeType)
+ else:
+ raise TypeError("Don't know how to declare event member of type %s" %
+ type)
+ return nativeType
+
+
+class CGEventRoot(CGThing):
+ def __init__(self, config, interfaceName):
+ descriptor = config.getDescriptor(interfaceName)
+
+ self.root = CGWrapper(CGEventClass(descriptor),
+ pre="\n", post="\n")
+
+ self.root = CGNamespace.build(["mozilla", "dom"], self.root)
+
+ self.root = CGList([CGClassForwardDeclare("JSContext", isStruct=True),
+ self.root])
+
+ parent = descriptor.interface.parent.identifier.name
+
+ # Throw in our #includes
+ self.root = CGHeaders(
+ [descriptor],
+ [],
+ [],
+ [],
+ [
+ config.getDescriptor(parent).headerFile,
+ "mozilla/Attributes.h",
+ "mozilla/ErrorResult.h",
+ "mozilla/dom/%sBinding.h" % interfaceName,
+ 'mozilla/dom/BindingUtils.h',
+ ],
+ [
+ "%s.h" % interfaceName,
+ "js/GCAPI.h",
+ 'mozilla/dom/Nullable.h',
+ ],
+ "", self.root, config)
+
+ # And now some include guards
+ self.root = CGIncludeGuard(interfaceName, self.root)
+
+ self.root = CGWrapper(
+ self.root,
+ pre=(AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT %
+ os.path.basename(descriptor.interface.filename())))
+
+ self.root = CGWrapper(self.root, pre=dedent("""
+ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+ /* vim:set ts=2 sw=2 sts=2 et cindent: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ """))
+
+ def declare(self):
+ return self.root.declare()
+
+ def define(self):
+ return self.root.define()