diff options
Diffstat (limited to 'dom/bindings/Codegen.py')
-rw-r--r-- | dom/bindings/Codegen.py | 17364 |
1 files changed, 17364 insertions, 0 deletions
diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py new file mode 100644 index 000000000..3174c37dd --- /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() |