diff options
Diffstat (limited to '')
-rw-r--r-- | dom/bindings/Codegen.py | 24278 |
1 files changed, 24278 insertions, 0 deletions
diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py new file mode 100644 index 0000000000..809a680d70 --- /dev/null +++ b/dom/bindings/Codegen.py @@ -0,0 +1,24278 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Common codegen classes. + +import functools +import math +import os +import re +import string +import textwrap + +import six +from Configuration import ( + Descriptor, + MemberIsLegacyUnforgeable, + NoSuchDescriptorError, + getAllTypes, + getTypesFromCallback, + getTypesFromDescriptor, + getTypesFromDictionary, +) +from perfecthash import PerfectHash +from WebIDL import ( + BuiltinTypes, + IDLAttribute, + IDLBuiltinType, + IDLDefaultDictionaryValue, + IDLDictionary, + IDLEmptySequenceValue, + IDLInterfaceMember, + IDLNullValue, + IDLSequenceType, + IDLType, + IDLUndefinedValue, +) + +AUTOGENERATED_WARNING_COMMENT = ( + "/* THIS FILE IS AUTOGENERATED BY Codegen.py - DO NOT EDIT */\n\n" +) +AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT = ( + "/* THIS FILE IS AUTOGENERATED FROM %s BY Codegen.py - DO NOT EDIT */\n\n" +) +ADDPROPERTY_HOOK_NAME = "_addProperty" +GETWRAPPERCACHE_HOOK_NAME = "_getWrapperCache" +FINALIZE_HOOK_NAME = "_finalize" +OBJECT_MOVED_HOOK_NAME = "_objectMoved" +CONSTRUCT_HOOK_NAME = "_constructor" +LEGACYCALLER_HOOK_NAME = "_legacycaller" +RESOLVE_HOOK_NAME = "_resolve" +MAY_RESOLVE_HOOK_NAME = "_mayResolve" +NEW_ENUMERATE_HOOK_NAME = "_newEnumerate" +ENUM_ENTRY_VARIABLE_NAME = "strings" +INSTANCE_RESERVED_SLOTS = 1 + +# This size is arbitrary. It is a power of 2 to make using it as a modulo +# operand cheap, and is usually around 1/3-1/5th of the set size (sometimes +# smaller for very large sets). +GLOBAL_NAMES_PHF_SIZE = 256 + + +def memberReservedSlot(member, descriptor): + return ( + "(DOM_INSTANCE_RESERVED_SLOTS + %d)" + % member.slotIndices[descriptor.interface.identifier.name] + ) + + +def memberXrayExpandoReservedSlot(member, descriptor): + return ( + "(xpc::JSSLOT_EXPANDO_COUNT + %d)" + % member.slotIndices[descriptor.interface.identifier.name] + ) + + +def mayUseXrayExpandoSlots(descriptor, attr): + assert not attr.getExtendedAttribute("NewObject") + # For attributes whose type is a Gecko interface we always use + # slots on the reflector for caching. Also, for interfaces that + # don't want Xrays we obviously never use the Xray expando slot. + return descriptor.wantsXrays and not attr.type.isGeckoInterface() + + +def toStringBool(arg): + """ + Converts IDL/Python Boolean (True/False) to C++ Boolean (true/false) + """ + return str(not not arg).lower() + + +def toBindingNamespace(arg): + return arg + "_Binding" + + +def isTypeCopyConstructible(type): + # Nullable and sequence stuff doesn't affect copy-constructibility + type = type.unroll() + return ( + type.isUndefined() + or type.isPrimitive() + or type.isString() + or type.isEnum() + or (type.isUnion() and CGUnionStruct.isUnionCopyConstructible(type)) + or ( + type.isDictionary() + and CGDictionary.isDictionaryCopyConstructible(type.inner) + ) + or + # Interface types are only copy-constructible if they're Gecko + # interfaces. SpiderMonkey interfaces are not copy-constructible + # because of rooting issues. + (type.isInterface() and type.isGeckoInterface()) + ) + + +class CycleCollectionUnsupported(TypeError): + def __init__(self, message): + TypeError.__init__(self, message) + + +def idlTypeNeedsCycleCollection(type): + type = type.unroll() # Takes care of sequences and nullables + if ( + (type.isPrimitive() and type.tag() in builtinNames) + or type.isUndefined() + or type.isEnum() + or type.isString() + or type.isAny() + or type.isObject() + or type.isSpiderMonkeyInterface() + ): + return False + elif type.isCallback() or type.isPromise() or type.isGeckoInterface(): + return True + elif type.isUnion(): + return any(idlTypeNeedsCycleCollection(t) for t in type.flatMemberTypes) + elif type.isRecord(): + if idlTypeNeedsCycleCollection(type.inner): + raise CycleCollectionUnsupported( + "Cycle collection for type %s is not supported" % type + ) + return False + elif type.isDictionary(): + return CGDictionary.dictionaryNeedsCycleCollection(type.inner) + else: + raise CycleCollectionUnsupported( + "Don't know whether to cycle-collect type %s" % type + ) + + +def idlTypeNeedsCallContext(type, descriptor=None, allowTreatNonCallableAsNull=False): + """ + Returns whether the given type needs error reporting via a + BindingCallContext for JS-to-C++ conversions. This will happen when the + conversion can throw an exception due to logic in the IDL spec or + Gecko-specific security checks. In particular, a type needs a + BindingCallContext if and only if the JS-to-C++ conversion for that type can + end up calling ThrowErrorMessage. + + For some types this depends on the descriptor (e.g. because we do certain + checks only for some kinds of interfaces). + + The allowTreatNonCallableAsNull optimization is there so we can avoid + generating an unnecessary BindingCallContext for all the event handler + attribute setters. + + """ + while True: + if type.isSequence(): + # Sequences can always throw "not an object" + return True + if type.nullable(): + # treatNonObjectAsNull() and treatNonCallableAsNull() are + # only sane things to test on nullable types, so do that now. + if ( + allowTreatNonCallableAsNull + and type.isCallback() + and (type.treatNonObjectAsNull() or type.treatNonCallableAsNull()) + ): + # This can't throw. so never needs a method description. + return False + type = type.inner + else: + break + + if type.isUndefined(): + # Clearly doesn't need a method description; we can only get here from + # CGHeaders trying to decide whether to include the method description + # header. + return False + # The float check needs to come before the isPrimitive() check, + # because floats are primitives too. + if type.isFloat(): + # Floats can throw if restricted. + return not type.isUnrestricted() + if type.isPrimitive() and type.tag() in builtinNames: + # Numbers can throw if enforcing range. + return type.hasEnforceRange() + if type.isEnum(): + # Can throw on invalid value. + return True + if type.isString(): + # Can throw if it's a ByteString + return type.isByteString() + if type.isAny(): + # JS-implemented interfaces do extra security checks so need a + # method description here. If we have no descriptor, this + # might be JS-implemented thing, so it will do the security + # check and we need the method description. + return not descriptor or descriptor.interface.isJSImplemented() + if type.isPromise(): + # JS-to-Promise conversion won't cause us to throw any + # specific exceptions, so does not need a method description. + return False + if ( + type.isObject() + or type.isInterface() + or type.isCallback() + or type.isDictionary() + or type.isRecord() + or type.isObservableArray() + ): + # These can all throw if a primitive is passed in, at the very least. + # There are some rare cases when we know we have an object, but those + # are not worth the complexity of optimizing for. + # + # Note that we checked the [LegacyTreatNonObjectAsNull] case already when + # unwrapping nullables. + return True + if type.isUnion(): + # Can throw if a type not in the union is passed in. + return True + raise TypeError("Don't know whether type '%s' needs a method description" % type) + + +# TryPreserveWrapper uses the addProperty hook to preserve the wrapper of +# non-nsISupports cycle collected objects, so if wantsAddProperty is changed +# to not cover that case then TryPreserveWrapper will need to be changed. +def wantsAddProperty(desc): + return desc.concrete and desc.wrapperCache and not desc.isGlobal() + + +def wantsGetWrapperCache(desc): + return ( + desc.concrete and desc.wrapperCache and not desc.isGlobal() and not desc.proxy + ) + + +# We'll want to insert the indent at the beginnings of lines, but we +# don't want to indent empty lines. So only indent lines that have a +# non-newline character on them. +lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE) + + +def indent(s, indentLevel=2): + """ + Indent C++ code. + + Weird secret feature: this doesn't indent lines that start with # (such as + #include lines or #ifdef/#endif). + """ + if s == "": + return s + return re.sub(lineStartDetector, indentLevel * " ", s) + + +# dedent() and fill() are often called on the same string multiple +# times. We want to memoize their return values so we don't keep +# recomputing them all the time. +def memoize(fn): + """ + Decorator to memoize a function of one argument. The cache just + grows without bound. + """ + cache = {} + + @functools.wraps(fn) + def wrapper(arg): + retval = cache.get(arg) + if retval is None: + retval = cache[arg] = fn(arg) + return retval + + return wrapper + + +@memoize +def dedent(s): + """ + Remove all leading whitespace from s, and remove a blank line + at the beginning. + """ + if s.startswith("\n"): + s = s[1:] + return textwrap.dedent(s) + + +# This works by transforming the fill()-template to an equivalent +# string.Template. +fill_multiline_substitution_re = re.compile(r"( *)\$\*{(\w+)}(\n)?") + + +find_substitutions = re.compile(r"\${") + + +@memoize +def compile_fill_template(template): + """ + Helper function for fill(). Given the template string passed to fill(), + do the reusable part of template processing and return a pair (t, + argModList) that can be used every time fill() is called with that + template argument. + + argsModList is list of tuples that represent modifications to be + made to args. Each modification has, in order: i) the arg name, + ii) the modified name, iii) the indent depth. + """ + t = dedent(template) + assert t.endswith("\n") or "\n" not in t + argModList = [] + + def replace(match): + """ + Replaces a line like ' $*{xyz}\n' with '${xyz_n}', + where n is the indent depth, and add a corresponding entry to + argModList. + + Note that this needs to close over argModList, so it has to be + defined inside compile_fill_template(). + """ + indentation, name, nl = match.groups() + depth = len(indentation) + + # Check that $*{xyz} appears by itself on a line. + prev = match.string[: match.start()] + if (prev and not prev.endswith("\n")) or nl is None: + raise ValueError( + "Invalid fill() template: $*{%s} must appear by itself on a line" % name + ) + + # Now replace this whole line of template with the indented equivalent. + modified_name = name + "_" + str(depth) + argModList.append((name, modified_name, depth)) + return "${" + modified_name + "}" + + t = re.sub(fill_multiline_substitution_re, replace, t) + if not re.search(find_substitutions, t): + raise TypeError("Using fill() when dedent() would do.") + return (string.Template(t), argModList) + + +def fill(template, **args): + """ + Convenience function for filling in a multiline template. + + `fill(template, name1=v1, name2=v2)` is a lot like + `string.Template(template).substitute({"name1": v1, "name2": v2})`. + + However, it's shorter, and has a few nice features: + + * If `template` is indented, fill() automatically dedents it! + This makes code using fill() with Python's multiline strings + much nicer to look at. + + * If `template` starts with a blank line, fill() strips it off. + (Again, convenient with multiline strings.) + + * fill() recognizes a special kind of substitution + of the form `$*{name}`. + + Use this to paste in, and automatically indent, multiple lines. + (Mnemonic: The `*` is for "multiple lines"). + + A `$*` substitution must appear by itself on a line, with optional + preceding indentation (spaces only). The whole line is replaced by the + corresponding keyword argument, indented appropriately. If the + argument is an empty string, no output is generated, not even a blank + line. + """ + + t, argModList = compile_fill_template(template) + # Now apply argModList to args + for (name, modified_name, depth) in argModList: + if not (args[name] == "" or args[name].endswith("\n")): + raise ValueError( + "Argument %s with value %r is missing a newline" % (name, args[name]) + ) + args[modified_name] = indent(args[name], depth) + + return t.substitute(args) + + +class CGThing: + """ + Abstract base class for things that spit out code. + """ + + def __init__(self): + pass # Nothing for now + + def declare(self): + """Produce code for a header file.""" + assert False # Override me! + + def define(self): + """Produce code for a cpp file.""" + assert False # Override me! + + def deps(self): + """Produce the deps for a pp file""" + assert False # Override me! + + +class CGStringTable(CGThing): + """ + Generate a function accessor for a WebIDL string table, using the existing + concatenated names string and mapping indexes to offsets in that string: + + const char *accessorName(unsigned int index) { + static const uint16_t offsets = { ... }; + return BindingName(offsets[index]); + } + + This is more efficient than the more natural: + + const char *table[] = { + ... + }; + + The uint16_t offsets are smaller than the pointer equivalents, and the + concatenated string requires no runtime relocations. + """ + + def __init__(self, accessorName, strings, static=False): + CGThing.__init__(self) + self.accessorName = accessorName + self.strings = strings + self.static = static + + def declare(self): + if self.static: + return "" + return "const char *%s(unsigned int aIndex);\n" % self.accessorName + + def define(self): + offsets = [] + for s in self.strings: + offsets.append(BindingNamesOffsetEnum(s)) + return fill( + """ + ${static}const char *${name}(unsigned int aIndex) + { + static const BindingNamesOffset offsets[] = { + $*{offsets} + }; + return BindingName(offsets[aIndex]); + } + """, + static="static " if self.static else "", + name=self.accessorName, + offsets="".join("BindingNamesOffset::%s,\n" % o for o in offsets), + ) + + +class CGNativePropertyHooks(CGThing): + """ + Generate a NativePropertyHooks for a given descriptor + """ + + def __init__(self, descriptor, properties): + CGThing.__init__(self) + assert descriptor.wantsXrays + self.descriptor = descriptor + self.properties = properties + + def declare(self): + return "" + + def define(self): + deleteNamedProperty = "nullptr" + if ( + self.descriptor.concrete + and self.descriptor.proxy + and not self.descriptor.isMaybeCrossOriginObject() + ): + resolveOwnProperty = "binding_detail::ResolveOwnProperty" + enumerateOwnProperties = "binding_detail::EnumerateOwnProperties" + if self.descriptor.needsXrayNamedDeleterHook(): + deleteNamedProperty = "DeleteNamedProperty" + elif self.descriptor.needsXrayResolveHooks(): + resolveOwnProperty = "ResolveOwnPropertyViaResolve" + enumerateOwnProperties = "EnumerateOwnPropertiesViaGetOwnPropertyNames" + else: + resolveOwnProperty = "nullptr" + enumerateOwnProperties = "nullptr" + if self.properties.hasNonChromeOnly(): + regular = "sNativeProperties.Upcast()" + else: + regular = "nullptr" + if self.properties.hasChromeOnly(): + chrome = "sChromeOnlyNativeProperties.Upcast()" + else: + chrome = "nullptr" + constructorID = "constructors::id::" + if self.descriptor.interface.hasInterfaceObject(): + constructorID += self.descriptor.name + else: + constructorID += "_ID_Count" + prototypeID = "prototypes::id::" + if self.descriptor.interface.hasInterfacePrototypeObject(): + prototypeID += self.descriptor.name + else: + prototypeID += "_ID_Count" + + if self.descriptor.wantsXrayExpandoClass: + expandoClass = "&sXrayExpandoObjectClass" + else: + expandoClass = "&DefaultXrayExpandoObjectClass" + + return fill( + """ + bool sNativePropertiesInited = false; + const NativePropertyHooks sNativePropertyHooks = { + ${resolveOwnProperty}, + ${enumerateOwnProperties}, + ${deleteNamedProperty}, + { ${regular}, ${chrome}, &sNativePropertiesInited }, + ${prototypeID}, + ${constructorID}, + ${expandoClass} + }; + """, + resolveOwnProperty=resolveOwnProperty, + enumerateOwnProperties=enumerateOwnProperties, + deleteNamedProperty=deleteNamedProperty, + regular=regular, + chrome=chrome, + prototypeID=prototypeID, + constructorID=constructorID, + expandoClass=expandoClass, + ) + + +def NativePropertyHooks(descriptor): + return ( + "&sEmptyNativePropertyHooks" + if not descriptor.wantsXrays + else "&sNativePropertyHooks" + ) + + +def DOMClass(descriptor): + protoList = ["prototypes::id::" + proto for proto in descriptor.prototypeNameChain] + # Pad out the list to the right length with _ID_Count so we + # guarantee that all the lists are the same length. _ID_Count + # is never the ID of any prototype, so it's safe to use as + # padding. + protoList.extend( + ["prototypes::id::_ID_Count"] + * (descriptor.config.maxProtoChainLength - len(protoList)) + ) + + if descriptor.interface.isSerializable(): + serializer = "Serialize" + else: + serializer = "nullptr" + + if wantsGetWrapperCache(descriptor): + wrapperCacheGetter = GETWRAPPERCACHE_HOOK_NAME + else: + wrapperCacheGetter = "nullptr" + + if descriptor.hasOrdinaryObjectPrototype: + getProto = "JS::GetRealmObjectPrototypeHandle" + else: + getProto = "GetProtoObjectHandle" + + return fill( + """ + { ${protoChain} }, + std::is_base_of_v<nsISupports, ${nativeType}>, + ${hooks}, + FindAssociatedGlobalForNative<${nativeType}>::Get, + ${getProto}, + GetCCParticipant<${nativeType}>::Get(), + ${serializer}, + ${wrapperCacheGetter} + """, + protoChain=", ".join(protoList), + nativeType=descriptor.nativeType, + hooks=NativePropertyHooks(descriptor), + serializer=serializer, + wrapperCacheGetter=wrapperCacheGetter, + getProto=getProto, + ) + + +def InstanceReservedSlots(descriptor): + slots = INSTANCE_RESERVED_SLOTS + descriptor.interface.totalMembersInSlots + if descriptor.isMaybeCrossOriginObject(): + # We need a slot for the cross-origin holder too. + if descriptor.interface.hasChildInterfaces(): + raise TypeError( + "We don't support non-leaf cross-origin interfaces " + "like %s" % descriptor.interface.identifier.name + ) + slots += 1 + return slots + + +class CGDOMJSClass(CGThing): + """ + Generate a DOMJSClass for a given descriptor + """ + + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def declare(self): + return "" + + def define(self): + callHook = ( + LEGACYCALLER_HOOK_NAME + if self.descriptor.operations["LegacyCaller"] + else "nullptr" + ) + objectMovedHook = ( + OBJECT_MOVED_HOOK_NAME if self.descriptor.wrapperCache else "nullptr" + ) + slotCount = InstanceReservedSlots(self.descriptor) + classFlags = "JSCLASS_IS_DOMJSCLASS | JSCLASS_FOREGROUND_FINALIZE | " + if self.descriptor.isGlobal(): + classFlags += ( + "JSCLASS_DOM_GLOBAL | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS)" + ) + traceHook = "JS_GlobalObjectTraceHook" + reservedSlots = "JSCLASS_GLOBAL_APPLICATION_SLOTS" + else: + classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount + traceHook = "nullptr" + reservedSlots = slotCount + if self.descriptor.interface.hasProbablyShortLivingWrapper(): + if not self.descriptor.wrapperCache: + raise TypeError( + "Need a wrapper cache to support nursery " + "allocation of DOM objects" + ) + classFlags += " | JSCLASS_SKIP_NURSERY_FINALIZE" + + if self.descriptor.interface.getExtendedAttribute("NeedResolve"): + resolveHook = RESOLVE_HOOK_NAME + mayResolveHook = MAY_RESOLVE_HOOK_NAME + newEnumerateHook = NEW_ENUMERATE_HOOK_NAME + elif self.descriptor.isGlobal(): + resolveHook = "mozilla::dom::ResolveGlobal" + mayResolveHook = "mozilla::dom::MayResolveGlobal" + newEnumerateHook = "mozilla::dom::EnumerateGlobal" + else: + resolveHook = "nullptr" + mayResolveHook = "nullptr" + newEnumerateHook = "nullptr" + + return fill( + """ + static const JSClassOps sClassOps = { + ${addProperty}, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* enumerate */ + ${newEnumerate}, /* newEnumerate */ + ${resolve}, /* resolve */ + ${mayResolve}, /* mayResolve */ + ${finalize}, /* finalize */ + ${call}, /* call */ + nullptr, /* construct */ + ${trace}, /* trace */ + }; + + static const js::ClassExtension sClassExtension = { + ${objectMoved} /* objectMovedOp */ + }; + + static const DOMJSClass sClass = { + { "${name}", + ${flags}, + &sClassOps, + JS_NULL_CLASS_SPEC, + &sClassExtension, + JS_NULL_OBJECT_OPS + }, + $*{descriptor} + }; + static_assert(${instanceReservedSlots} == DOM_INSTANCE_RESERVED_SLOTS, + "Must have the right minimal number of reserved slots."); + static_assert(${reservedSlots} >= ${slotCount}, + "Must have enough reserved slots."); + """, + name=self.descriptor.interface.getClassName(), + flags=classFlags, + addProperty=ADDPROPERTY_HOOK_NAME + if wantsAddProperty(self.descriptor) + else "nullptr", + newEnumerate=newEnumerateHook, + resolve=resolveHook, + mayResolve=mayResolveHook, + finalize=FINALIZE_HOOK_NAME, + call=callHook, + trace=traceHook, + objectMoved=objectMovedHook, + descriptor=DOMClass(self.descriptor), + instanceReservedSlots=INSTANCE_RESERVED_SLOTS, + reservedSlots=reservedSlots, + slotCount=slotCount, + ) + + +class CGDOMProxyJSClass(CGThing): + """ + Generate a DOMJSClass for a given proxy descriptor + """ + + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def declare(self): + return "" + + def define(self): + slotCount = InstanceReservedSlots(self.descriptor) + # We need one reserved slot (DOM_OBJECT_SLOT). + flags = ["JSCLASS_IS_DOMJSCLASS", "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount] + # We don't use an IDL annotation for JSCLASS_EMULATES_UNDEFINED because + # we don't want people ever adding that to any interface other than + # HTMLAllCollection. So just hardcode it here. + if self.descriptor.interface.identifier.name == "HTMLAllCollection": + flags.append("JSCLASS_EMULATES_UNDEFINED") + return fill( + """ + static const DOMJSClass sClass = { + PROXY_CLASS_DEF("${name}", + ${flags}), + $*{descriptor} + }; + """, + name=self.descriptor.interface.identifier.name, + flags=" | ".join(flags), + descriptor=DOMClass(self.descriptor), + ) + + +class CGXrayExpandoJSClass(CGThing): + """ + Generate a JSClass for an Xray expando object. This is only + needed if we have members in slots (for [Cached] or [StoreInSlot] + stuff). + """ + + def __init__(self, descriptor): + assert descriptor.interface.totalMembersInSlots != 0 + assert descriptor.wantsXrays + assert descriptor.wantsXrayExpandoClass + CGThing.__init__(self) + self.descriptor = descriptor + + def declare(self): + return "" + + def define(self): + return fill( + """ + // This may allocate too many slots, because we only really need + // slots for our non-interface-typed members that we cache. But + // allocating slots only for those would make the slot index + // computations much more complicated, so let's do this the simple + // way for now. + DEFINE_XRAY_EXPANDO_CLASS(static, sXrayExpandoObjectClass, ${memberSlots}); + """, + memberSlots=self.descriptor.interface.totalMembersInSlots, + ) + + +def PrototypeIDAndDepth(descriptor): + prototypeID = "prototypes::id::" + if descriptor.interface.hasInterfacePrototypeObject(): + prototypeID += descriptor.interface.identifier.name + depth = "PrototypeTraits<%s>::Depth" % prototypeID + else: + prototypeID += "_ID_Count" + depth = "0" + return (prototypeID, depth) + + +def InterfacePrototypeObjectProtoGetter(descriptor): + """ + Returns a tuple with two elements: + + 1) The name of the function to call to get the prototype to use for the + interface prototype object as a JSObject*. + + 2) The name of the function to call to get the prototype to use for the + interface prototype object as a JS::Handle<JSObject*> or None if no + such function exists. + """ + parentProtoName = descriptor.parentPrototypeName + if descriptor.hasNamedPropertiesObject: + protoGetter = "GetNamedPropertiesObject" + protoHandleGetter = None + elif parentProtoName is None: + if descriptor.interface.getExtendedAttribute("ExceptionClass"): + protoGetter = "JS::GetRealmErrorPrototype" + elif descriptor.interface.isIteratorInterface(): + protoGetter = "JS::GetRealmIteratorPrototype" + elif descriptor.interface.isAsyncIteratorInterface(): + protoGetter = "JS::GetRealmAsyncIteratorPrototype" + else: + protoGetter = "JS::GetRealmObjectPrototype" + protoHandleGetter = None + else: + prefix = toBindingNamespace(parentProtoName) + protoGetter = prefix + "::GetProtoObject" + protoHandleGetter = prefix + "::GetProtoObjectHandle" + + return (protoGetter, protoHandleGetter) + + +class CGPrototypeJSClass(CGThing): + def __init__(self, descriptor, properties): + CGThing.__init__(self) + self.descriptor = descriptor + self.properties = properties + + def declare(self): + # We're purely for internal consumption + return "" + + def define(self): + prototypeID, depth = PrototypeIDAndDepth(self.descriptor) + slotCount = "DOM_INTERFACE_PROTO_SLOTS_BASE" + # Globals handle unforgeables directly in Wrap() instead of + # via a holder. + if ( + self.descriptor.hasLegacyUnforgeableMembers + and not self.descriptor.isGlobal() + ): + slotCount += ( + " + 1 /* slot for the JSObject holding the unforgeable properties */" + ) + (protoGetter, _) = InterfacePrototypeObjectProtoGetter(self.descriptor) + type = ( + "eGlobalInterfacePrototype" + if self.descriptor.isGlobal() + else "eInterfacePrototype" + ) + return fill( + """ + static const DOMIfaceAndProtoJSClass sPrototypeClass = { + { + "${name}Prototype", + JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}), + JS_NULL_CLASS_OPS, + JS_NULL_CLASS_SPEC, + JS_NULL_CLASS_EXT, + JS_NULL_OBJECT_OPS + }, + ${type}, + false, + ${prototypeID}, + ${depth}, + ${hooks}, + nullptr, + ${protoGetter} + }; + """, + name=self.descriptor.interface.getClassName(), + slotCount=slotCount, + type=type, + hooks=NativePropertyHooks(self.descriptor), + prototypeID=prototypeID, + depth=depth, + protoGetter=protoGetter, + ) + + +def InterfaceObjectProtoGetter(descriptor, forXrays=False): + """ + Returns a tuple with two elements: + + 1) The name of the function to call to get the prototype to use for the + interface object as a JSObject*. + + 2) The name of the function to call to get the prototype to use for the + interface prototype as a JS::Handle<JSObject*> or None if no such + function exists. + """ + parentInterface = descriptor.interface.parent + if parentInterface: + assert not descriptor.interface.isNamespace() + parentIfaceName = parentInterface.identifier.name + parentDesc = descriptor.getDescriptor(parentIfaceName) + prefix = toBindingNamespace(parentDesc.name) + protoGetter = prefix + "::GetConstructorObject" + protoHandleGetter = prefix + "::GetConstructorObjectHandle" + elif descriptor.interface.isNamespace(): + if forXrays or not descriptor.interface.getExtendedAttribute("ProtoObjectHack"): + protoGetter = "JS::GetRealmObjectPrototype" + else: + protoGetter = "GetHackedNamespaceProtoObject" + protoHandleGetter = None + else: + protoGetter = "JS::GetRealmFunctionPrototype" + protoHandleGetter = None + return (protoGetter, protoHandleGetter) + + +class CGInterfaceObjectJSClass(CGThing): + def __init__(self, descriptor, properties): + CGThing.__init__(self) + self.descriptor = descriptor + self.properties = properties + + def declare(self): + # We're purely for internal consumption + return "" + + def define(self): + if self.descriptor.interface.ctor(): + assert not self.descriptor.interface.isNamespace() + ctorname = CONSTRUCT_HOOK_NAME + elif self.descriptor.interface.isNamespace(): + ctorname = "nullptr" + else: + ctorname = "ThrowingConstructor" + needsHasInstance = self.descriptor.interface.hasInterfacePrototypeObject() + + prototypeID, depth = PrototypeIDAndDepth(self.descriptor) + slotCount = "DOM_INTERFACE_SLOTS_BASE" + if len(self.descriptor.interface.legacyFactoryFunctions) > 0: + slotCount += " + %i /* slots for the legacy factory functions */" % len( + self.descriptor.interface.legacyFactoryFunctions + ) + (protoGetter, _) = InterfaceObjectProtoGetter(self.descriptor, forXrays=True) + + if ctorname == "ThrowingConstructor": + ret = "" + classOpsPtr = "&sBoringInterfaceObjectClassClassOps" + elif ctorname == "nullptr": + ret = "" + classOpsPtr = "JS_NULL_CLASS_OPS" + else: + ret = fill( + """ + static const JSClassOps sInterfaceObjectClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* enumerate */ + nullptr, /* newEnumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* finalize */ + ${ctorname}, /* call */ + ${ctorname}, /* construct */ + nullptr, /* trace */ + }; + + """, + ctorname=ctorname, + ) + classOpsPtr = "&sInterfaceObjectClassOps" + + if self.descriptor.interface.isNamespace(): + classString = self.descriptor.interface.getExtendedAttribute("ClassString") + if classString is None: + classString = self.descriptor.interface.identifier.name + else: + classString = classString[0] + funToString = "nullptr" + objectOps = "JS_NULL_OBJECT_OPS" + else: + classString = "Function" + funToString = ( + '"function %s() {\\n [native code]\\n}"' + % self.descriptor.interface.identifier.name + ) + # We need non-default ObjectOps so we can actually make + # use of our funToString. + objectOps = "&sInterfaceObjectClassObjectOps" + + ret = ret + fill( + """ + static const DOMIfaceAndProtoJSClass sInterfaceObjectClass = { + { + "${classString}", + JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}), + ${classOpsPtr}, + JS_NULL_CLASS_SPEC, + JS_NULL_CLASS_EXT, + ${objectOps} + }, + ${type}, + ${needsHasInstance}, + ${prototypeID}, + ${depth}, + ${hooks}, + ${funToString}, + ${protoGetter} + }; + """, + classString=classString, + slotCount=slotCount, + classOpsPtr=classOpsPtr, + hooks=NativePropertyHooks(self.descriptor), + objectOps=objectOps, + type="eNamespace" + if self.descriptor.interface.isNamespace() + else "eInterface", + needsHasInstance=toStringBool(needsHasInstance), + prototypeID=prototypeID, + depth=depth, + funToString=funToString, + protoGetter=protoGetter, + ) + return ret + + +class CGList(CGThing): + """ + Generate code for a list of GCThings. Just concatenates them together, with + an optional joiner string. "\n" is a common joiner. + """ + + def __init__(self, children, joiner=""): + CGThing.__init__(self) + # Make a copy of the kids into a list, because if someone passes in a + # generator we won't be able to both declare and define ourselves, or + # define ourselves more than once! + self.children = list(children) + self.joiner = joiner + + def append(self, child): + self.children.append(child) + + def prepend(self, child): + self.children.insert(0, child) + + def extend(self, kids): + self.children.extend(kids) + + def join(self, iterable): + return self.joiner.join(s for s in iterable if len(s) > 0) + + def declare(self): + return self.join( + child.declare() for child in self.children if child is not None + ) + + def define(self): + return self.join(child.define() for child in self.children if child is not None) + + def deps(self): + deps = set() + for child in self.children: + if child is None: + continue + deps = deps.union(child.deps()) + return deps + + def __len__(self): + return len(self.children) + + +class CGGeneric(CGThing): + """ + A class that spits out a fixed string into the codegen. Can spit out a + separate string for the declaration too. + """ + + def __init__(self, define="", declare=""): + self.declareText = declare + self.defineText = define + + def declare(self): + return self.declareText + + def define(self): + return self.defineText + + def deps(self): + return set() + + +class CGIndenter(CGThing): + """ + A class that takes another CGThing and generates code that indents that + CGThing by some number of spaces. The default indent is two spaces. + """ + + def __init__(self, child, indentLevel=2, declareOnly=False): + assert isinstance(child, CGThing) + CGThing.__init__(self) + self.child = child + self.indentLevel = indentLevel + self.declareOnly = declareOnly + + def declare(self): + return indent(self.child.declare(), self.indentLevel) + + def define(self): + defn = self.child.define() + if self.declareOnly: + return defn + else: + return indent(defn, self.indentLevel) + + +class CGWrapper(CGThing): + """ + Generic CGThing that wraps other CGThings with pre and post text. + """ + + def __init__( + self, + child, + pre="", + post="", + declarePre=None, + declarePost=None, + definePre=None, + definePost=None, + declareOnly=False, + defineOnly=False, + reindent=False, + ): + CGThing.__init__(self) + self.child = child + self.declarePre = declarePre or pre + self.declarePost = declarePost or post + self.definePre = definePre or pre + self.definePost = definePost or post + self.declareOnly = declareOnly + self.defineOnly = defineOnly + self.reindent = reindent + + def declare(self): + if self.defineOnly: + return "" + decl = self.child.declare() + if self.reindent: + decl = self.reindentString(decl, self.declarePre) + return self.declarePre + decl + self.declarePost + + def define(self): + if self.declareOnly: + return "" + defn = self.child.define() + if self.reindent: + defn = self.reindentString(defn, self.definePre) + return self.definePre + defn + self.definePost + + @staticmethod + def reindentString(stringToIndent, widthString): + # We don't use lineStartDetector because we don't want to + # insert whitespace at the beginning of our _first_ line. + # Use the length of the last line of width string, in case + # it is a multiline string. + lastLineWidth = len(widthString.splitlines()[-1]) + return stripTrailingWhitespace( + stringToIndent.replace("\n", "\n" + (" " * lastLineWidth)) + ) + + def deps(self): + return self.child.deps() + + +class CGIfWrapper(CGList): + def __init__(self, child, condition): + CGList.__init__( + self, + [ + CGWrapper( + CGGeneric(condition), pre="if (", post=") {\n", reindent=True + ), + CGIndenter(child), + CGGeneric("}\n"), + ], + ) + + +class CGIfElseWrapper(CGList): + def __init__(self, condition, ifTrue, ifFalse): + CGList.__init__( + self, + [ + CGWrapper( + CGGeneric(condition), pre="if (", post=") {\n", reindent=True + ), + CGIndenter(ifTrue), + CGGeneric("} else {\n"), + CGIndenter(ifFalse), + CGGeneric("}\n"), + ], + ) + + +class CGElseChain(CGThing): + """ + Concatenate if statements in an if-else-if-else chain. + """ + + def __init__(self, children): + self.children = [c for c in children if c is not None] + + def declare(self): + assert False + + def define(self): + if not self.children: + return "" + s = self.children[0].define() + assert s.endswith("\n") + for child in self.children[1:]: + code = child.define() + assert code.startswith("if") or code.startswith("{") + assert code.endswith("\n") + s = s.rstrip() + " else " + code + return s + + +class CGTemplatedType(CGWrapper): + def __init__(self, templateName, child, isConst=False, isReference=False): + if isinstance(child, list): + child = CGList(child, ", ") + const = "const " if isConst else "" + pre = "%s%s<" % (const, templateName) + ref = "&" if isReference else "" + post = ">%s" % ref + CGWrapper.__init__(self, child, pre=pre, post=post) + + +class CGNamespace(CGThing): + """ + Generates namespace block that wraps other CGThings. + """ + + def __init__(self, namespace, child): + CGThing.__init__(self) + self.child = child + self.pre = "namespace %s {\n" % namespace + self.post = "} // namespace %s\n" % namespace + + def declare(self): + decl = self.child.declare() + if len(decl.strip()) == 0: + return "" + return self.pre + decl + self.post + + def define(self): + defn = self.child.define() + if len(defn.strip()) == 0: + return "" + return self.pre + defn + self.post + + def deps(self): + return self.child.deps() + + @staticmethod + def build(namespaces, child): + """ + Static helper method to build multiple wrapped namespaces. + """ + if not namespaces: + return CGWrapper(child) + return CGNamespace("::".join(namespaces), child) + + +class CGIncludeGuard(CGWrapper): + """ + Generates include guards for a header. + """ + + def __init__(self, prefix, child): + """|prefix| is the filename without the extension.""" + define = "mozilla_dom_%s_h" % prefix + CGWrapper.__init__( + self, + child, + declarePre="#ifndef %s\n#define %s\n\n" % (define, define), + declarePost="\n#endif // %s\n" % define, + ) + + +class CGHeaders(CGWrapper): + """ + Generates the appropriate include statements. + """ + + def __init__( + self, + descriptors, + dictionaries, + callbacks, + callbackDescriptors, + declareIncludes, + defineIncludes, + prefix, + child, + config=None, + jsImplementedDescriptors=[], + ): + """ + Builds a set of includes to cover |descriptors|. + + Also includes the files in |declareIncludes| in the header + file and the files in |defineIncludes| in the .cpp. + + |prefix| contains the basename of the file that we generate include + statements for. + """ + + # Determine the filenames for which we need headers. + interfaceDeps = [d.interface for d in descriptors] + ancestors = [] + for iface in interfaceDeps: + if iface.parent: + # We're going to need our parent's prototype, to use as the + # prototype of our prototype object. + ancestors.append(iface.parent) + # And if we have an interface object, we'll need the nearest + # ancestor with an interface object too, so we can use its + # interface object as the proto of our interface object. + if iface.hasInterfaceObject(): + parent = iface.parent + while parent and not parent.hasInterfaceObject(): + parent = parent.parent + if parent: + ancestors.append(parent) + interfaceDeps.extend(ancestors) + + # Include parent interface headers needed for default toJSON code. + jsonInterfaceParents = [] + for desc in descriptors: + if not desc.hasDefaultToJSON: + continue + parent = desc.interface.parent + while parent: + parentDesc = desc.getDescriptor(parent.identifier.name) + if parentDesc.hasDefaultToJSON: + jsonInterfaceParents.append(parentDesc.interface) + parent = parent.parent + interfaceDeps.extend(jsonInterfaceParents) + + bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps) + + # Grab all the implementation declaration files we need. + implementationIncludes = set( + d.headerFile for d in descriptors if d.needsHeaderInclude() + ) + + # Now find all the things we'll need as arguments because we + # need to wrap or unwrap them. + bindingHeaders = set() + declareIncludes = set(declareIncludes) + + def addHeadersForType(typeAndPossibleOriginType): + """ + Add the relevant headers for this type. We use its origin type, if + passed, to decide what to do with interface types. + """ + t, originType = typeAndPossibleOriginType + isFromDictionary = originType and originType.isDictionary() + isFromCallback = originType and originType.isCallback() + # Dictionaries have members that need to be actually + # declared, not just forward-declared. + # Callbacks have nullable union arguments that need to be actually + # declared, not just forward-declared. + if isFromDictionary: + headerSet = declareIncludes + elif isFromCallback and t.nullable() and t.isUnion(): + headerSet = declareIncludes + else: + headerSet = bindingHeaders + # Strip off outer layers and add headers they might require. (This + # is conservative: only nullable non-pointer types need Nullable.h; + # only sequences or observable arrays outside unions need + # ForOfIterator.h; only functions that return, and attributes that + # are, sequences or observable arrays in interfaces need Array.h, &c.) + unrolled = t + while True: + if idlTypeNeedsCallContext(unrolled): + bindingHeaders.add("mozilla/dom/BindingCallContext.h") + if unrolled.nullable(): + headerSet.add("mozilla/dom/Nullable.h") + elif unrolled.isSequence() or unrolled.isObservableArray(): + bindingHeaders.add("js/Array.h") + bindingHeaders.add("js/ForOfIterator.h") + if unrolled.isObservableArray(): + bindingHeaders.add("mozilla/dom/ObservableArrayProxyHandler.h") + else: + break + unrolled = unrolled.inner + if unrolled.isUnion(): + headerSet.add(self.getUnionDeclarationFilename(config, unrolled)) + for t in unrolled.flatMemberTypes: + addHeadersForType((t, None)) + elif unrolled.isPromise(): + # See comment in the isInterface() case for why we add + # Promise.h to headerSet, not bindingHeaders. + headerSet.add("mozilla/dom/Promise.h") + # We need ToJSValue to do the Promise to JS conversion. + bindingHeaders.add("mozilla/dom/ToJSValue.h") + elif unrolled.isInterface(): + if unrolled.isSpiderMonkeyInterface(): + bindingHeaders.add("jsfriendapi.h") + if jsImplementedDescriptors: + # Since we can't forward-declare typed array types + # (because they're typedefs), we have to go ahead and + # just include their header if we need to have functions + # taking references to them declared in that header. + headerSet = declareIncludes + headerSet.add("mozilla/dom/TypedArray.h") + else: + try: + typeDesc = config.getDescriptor(unrolled.inner.identifier.name) + except NoSuchDescriptorError: + return + # Dictionaries with interface members rely on the + # actual class definition of that interface member + # being visible in the binding header, because they + # store them in RefPtr and have inline + # constructors/destructors. + # + # XXXbz maybe dictionaries with interface members + # should just have out-of-line constructors and + # destructors? + headerSet.add(typeDesc.headerFile) + elif unrolled.isDictionary(): + headerSet.add(self.getDeclarationFilename(unrolled.inner)) + # And if it needs rooting, we need RootedDictionary too + if typeNeedsRooting(unrolled): + headerSet.add("mozilla/dom/RootedDictionary.h") + elif unrolled.isCallback(): + headerSet.add(self.getDeclarationFilename(unrolled.callback)) + elif unrolled.isFloat() and not unrolled.isUnrestricted(): + # Restricted floats are tested for finiteness + bindingHeaders.add("mozilla/FloatingPoint.h") + bindingHeaders.add("mozilla/dom/PrimitiveConversions.h") + elif unrolled.isEnum(): + filename = self.getDeclarationFilename(unrolled.inner) + declareIncludes.add(filename) + elif unrolled.isPrimitive(): + bindingHeaders.add("mozilla/dom/PrimitiveConversions.h") + elif unrolled.isRecord(): + if isFromDictionary or jsImplementedDescriptors: + declareIncludes.add("mozilla/dom/Record.h") + else: + bindingHeaders.add("mozilla/dom/Record.h") + # Also add headers for the type the record is + # parametrized over, if needed. + addHeadersForType((t.inner, originType if isFromDictionary else None)) + + for t in getAllTypes( + descriptors + callbackDescriptors, dictionaries, callbacks + ): + addHeadersForType(t) + + def addHeaderForFunc(func, desc): + if func is None: + return + # Include the right class header, which we can only do + # if this is a class member function. + if desc is not None and not desc.headerIsDefault: + # An explicit header file was provided, assume that we know + # what we're doing. + return + + if "::" in func: + # Strip out the function name and convert "::" to "/" + bindingHeaders.add("/".join(func.split("::")[:-1]) + ".h") + + # Now for non-callback descriptors make sure we include any + # headers needed by Func declarations and other things like that. + for desc in descriptors: + # If this is an iterator or an async iterator interface generated + # for a separate iterable interface, skip generating type includes, + # as we have what we need in IterableIterator.h + if ( + desc.interface.isIteratorInterface() + or desc.interface.isAsyncIteratorInterface() + ): + continue + + for m in desc.interface.members: + addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), desc) + staticTypeOverride = PropertyDefiner.getStringAttr( + m, "StaticClassOverride" + ) + if staticTypeOverride: + bindingHeaders.add("/".join(staticTypeOverride.split("::")) + ".h") + # getExtendedAttribute() returns a list, extract the entry. + funcList = desc.interface.getExtendedAttribute("Func") + if funcList is not None: + addHeaderForFunc(funcList[0], desc) + + if desc.interface.maplikeOrSetlikeOrIterable: + # We need ToJSValue.h for maplike/setlike type conversions + bindingHeaders.add("mozilla/dom/ToJSValue.h") + # Add headers for the key and value types of the + # maplike/setlike/iterable, since they'll be needed for + # convenience functions + if desc.interface.maplikeOrSetlikeOrIterable.hasKeyType(): + addHeadersForType( + (desc.interface.maplikeOrSetlikeOrIterable.keyType, None) + ) + if desc.interface.maplikeOrSetlikeOrIterable.hasValueType(): + addHeadersForType( + (desc.interface.maplikeOrSetlikeOrIterable.valueType, None) + ) + + for d in dictionaries: + if d.parent: + declareIncludes.add(self.getDeclarationFilename(d.parent)) + bindingHeaders.add(self.getDeclarationFilename(d)) + for m in d.members: + addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func"), None) + # No need to worry about Func on members of ancestors, because that + # will happen automatically in whatever files those ancestors live + # in. + + for c in callbacks: + bindingHeaders.add(self.getDeclarationFilename(c)) + + for c in callbackDescriptors: + bindingHeaders.add(self.getDeclarationFilename(c.interface)) + + if len(callbacks) != 0: + # We need CallbackFunction to serve as our parent class + declareIncludes.add("mozilla/dom/CallbackFunction.h") + # And we need ToJSValue.h so we can wrap "this" objects + declareIncludes.add("mozilla/dom/ToJSValue.h") + + if len(callbackDescriptors) != 0 or len(jsImplementedDescriptors) != 0: + # We need CallbackInterface to serve as our parent class + declareIncludes.add("mozilla/dom/CallbackInterface.h") + # And we need ToJSValue.h so we can wrap "this" objects + declareIncludes.add("mozilla/dom/ToJSValue.h") + + # Also need to include the headers for ancestors of + # JS-implemented interfaces. + for jsImplemented in jsImplementedDescriptors: + jsParent = jsImplemented.interface.parent + if jsParent: + parentDesc = jsImplemented.getDescriptor(jsParent.identifier.name) + declareIncludes.add(parentDesc.jsImplParentHeader) + + # Now make sure we're not trying to include the header from inside itself + declareIncludes.discard(prefix + ".h") + + # Let the machinery do its thing. + def _includeString(includes): + def headerName(include): + # System headers are specified inside angle brackets. + if include.startswith("<"): + return include + # Non-system headers need to be placed in quotes. + return '"%s"' % include + + return "".join(["#include %s\n" % headerName(i) for i in includes]) + "\n" + + CGWrapper.__init__( + self, + child, + declarePre=_includeString(sorted(declareIncludes)), + definePre=_includeString( + sorted( + set(defineIncludes) + | bindingIncludes + | bindingHeaders + | implementationIncludes + ) + ), + ) + + @staticmethod + def getDeclarationFilename(decl): + # Use our local version of the header, not the exported one, so that + # test bindings, which don't export, will work correctly. + basename = os.path.basename(decl.filename()) + return basename.replace(".webidl", "Binding.h") + + @staticmethod + def getUnionDeclarationFilename(config, unionType): + assert unionType.isUnion() + assert unionType.unroll() == unionType + # If a union is "defined" in multiple files, it goes in UnionTypes.h. + if len(config.filenamesPerUnion[unionType.name]) > 1: + return "mozilla/dom/UnionTypes.h" + # If a union is defined by a built-in typedef, it also goes in + # UnionTypes.h. + assert len(config.filenamesPerUnion[unionType.name]) == 1 + if "<unknown>" in config.filenamesPerUnion[unionType.name]: + return "mozilla/dom/UnionTypes.h" + return CGHeaders.getDeclarationFilename(unionType) + + +def SortedDictValues(d): + """ + Returns a list of values from the dict sorted by key. + """ + return [v for k, v in sorted(d.items())] + + +def UnionsForFile(config, webIDLFile): + """ + Returns a list of union types for all union types that are only used in + webIDLFile. If webIDLFile is None this will return the list of tuples for + union types that are used in more than one WebIDL file. + """ + return config.unionsPerFilename.get(webIDLFile, []) + + +def UnionTypes(unionTypes, config): + """ + The unionTypes argument should be a list of union types. This is typically + the list generated by UnionsForFile. + + Returns a tuple containing a set of header filenames to include in + the header for the types in unionTypes, a set of header filenames to + include in the implementation file for the types in unionTypes, a set + of tuples containing a type declaration and a boolean if the type is a + struct for member types of the union, a list of traverse methods, + unlink methods and a list of union types. These last three lists only + contain unique union types. + """ + + headers = set() + implheaders = set() + declarations = set() + unionStructs = dict() + traverseMethods = dict() + unlinkMethods = dict() + + for t in unionTypes: + name = str(t) + if name not in unionStructs: + unionStructs[name] = t + + def addHeadersForType(f): + if f.nullable(): + headers.add("mozilla/dom/Nullable.h") + isSequence = f.isSequence() + if isSequence: + # Dealing with sequences requires for-of-compatible + # iteration. + implheaders.add("js/ForOfIterator.h") + # Sequences can always throw "not an object" exceptions. + implheaders.add("mozilla/dom/BindingCallContext.h") + if typeNeedsRooting(f): + headers.add("mozilla/dom/RootedSequence.h") + f = f.unroll() + if idlTypeNeedsCallContext(f): + implheaders.add("mozilla/dom/BindingCallContext.h") + if f.isPromise(): + headers.add("mozilla/dom/Promise.h") + # We need ToJSValue to do the Promise to JS conversion. + headers.add("mozilla/dom/ToJSValue.h") + elif f.isInterface(): + if f.isSpiderMonkeyInterface(): + headers.add("js/RootingAPI.h") + headers.add("js/Value.h") + headers.add("mozilla/dom/TypedArray.h") + else: + try: + typeDesc = config.getDescriptor(f.inner.identifier.name) + except NoSuchDescriptorError: + return + if typeDesc.interface.isCallback() or isSequence: + # Callback interfaces always use strong refs, so + # we need to include the right header to be able + # to Release() in our inlined code. + # + # Similarly, sequences always contain strong + # refs, so we'll need the header to handler + # those. + headers.add(typeDesc.headerFile) + elif typeDesc.interface.identifier.name == "WindowProxy": + # In UnionTypes.h we need to see the declaration of the + # WindowProxyHolder that we use to store the WindowProxy, so + # we have its sizeof and know how big to make our union. + headers.add(typeDesc.headerFile) + else: + declarations.add((typeDesc.nativeType, False)) + implheaders.add(typeDesc.headerFile) + elif f.isDictionary(): + # For a dictionary, we need to see its declaration in + # UnionTypes.h so we have its sizeof and know how big to + # make our union. + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + # And if it needs rooting, we need RootedDictionary too + if typeNeedsRooting(f): + headers.add("mozilla/dom/RootedDictionary.h") + elif f.isFloat() and not f.isUnrestricted(): + # Restricted floats are tested for finiteness + implheaders.add("mozilla/FloatingPoint.h") + implheaders.add("mozilla/dom/PrimitiveConversions.h") + elif f.isEnum(): + # Need to see the actual definition of the enum, + # unfortunately. + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + elif f.isPrimitive(): + implheaders.add("mozilla/dom/PrimitiveConversions.h") + elif f.isCallback(): + # Callbacks always use strong refs, so we need to include + # the right header to be able to Release() in our inlined + # code. + headers.add(CGHeaders.getDeclarationFilename(f.callback)) + elif f.isRecord(): + headers.add("mozilla/dom/Record.h") + # And add headers for the type we're parametrized over + addHeadersForType(f.inner) + # And if it needs rooting, we need RootedRecord too + if typeNeedsRooting(f): + headers.add("mozilla/dom/RootedRecord.h") + + implheaders.add(CGHeaders.getUnionDeclarationFilename(config, t)) + for f in t.flatMemberTypes: + assert not f.nullable() + addHeadersForType(f) + + if idlTypeNeedsCycleCollection(t): + declarations.add( + ("mozilla::dom::%s" % CGUnionStruct.unionTypeName(t, True), False) + ) + traverseMethods[name] = CGCycleCollectionTraverseForOwningUnionMethod(t) + unlinkMethods[name] = CGCycleCollectionUnlinkForOwningUnionMethod(t) + + # The order of items in CGList is important. + # Since the union structs friend the unlinkMethods, the forward-declaration + # for these methods should come before the class declaration. Otherwise + # some compilers treat the friend declaration as a forward-declaration in + # the class scope. + return ( + headers, + implheaders, + declarations, + SortedDictValues(traverseMethods), + SortedDictValues(unlinkMethods), + SortedDictValues(unionStructs), + ) + + +class Argument: + """ + A class for outputting the type and name of an argument + """ + + def __init__(self, argType, name, default=None): + self.argType = argType + self.name = name + self.default = default + + def declare(self): + string = self.argType + " " + self.name + if self.default is not None: + string += " = " + self.default + return string + + def define(self): + return self.argType + " " + self.name + + +class CGAbstractMethod(CGThing): + """ + An abstract class for generating code for a method. Subclasses + should override definition_body to create the actual code. + + descriptor is the descriptor for the interface the method is associated with + + name is the name of the method as a string + + returnType is the IDLType of the return value + + args is a list of Argument objects + + inline should be True to generate an inline method, whose body is + part of the declaration. + + alwaysInline should be True to generate an inline method annotated with + MOZ_ALWAYS_INLINE. + + static should be True to generate a static method, which only has + a definition. + + If templateArgs is not None it should be a list of strings containing + template arguments, and the function will be templatized using those + arguments. + + canRunScript should be True to generate a MOZ_CAN_RUN_SCRIPT annotation. + + signatureOnly should be True to only declare the signature (either in + the header, or if static is True in the cpp file). + """ + + def __init__( + self, + descriptor, + name, + returnType, + args, + inline=False, + alwaysInline=False, + static=False, + templateArgs=None, + canRunScript=False, + signatureOnly=False, + ): + CGThing.__init__(self) + self.descriptor = descriptor + self.name = name + self.returnType = returnType + self.args = args + self.inline = inline + self.alwaysInline = alwaysInline + self.static = static + self.templateArgs = templateArgs + self.canRunScript = canRunScript + self.signatureOnly = signatureOnly + + def _argstring(self, declare): + return ", ".join([a.declare() if declare else a.define() for a in self.args]) + + def _template(self): + if self.templateArgs is None: + return "" + return "template <%s>\n" % ", ".join(self.templateArgs) + + def _decorators(self): + decorators = [] + if self.canRunScript: + decorators.append("MOZ_CAN_RUN_SCRIPT") + if self.alwaysInline: + decorators.append("MOZ_ALWAYS_INLINE") + elif self.inline: + decorators.append("inline") + if self.static: + decorators.append("static") + decorators.append(self.returnType) + maybeNewline = " " if self.inline else "\n" + return " ".join(decorators) + maybeNewline + + def signature(self): + return "%s%s%s(%s);\n" % ( + self._template(), + self._decorators(), + self.name, + self._argstring(True), + ) + + def declare(self): + if self.static: + return "" + if self.inline: + return self._define(True) + return self.signature() + + def indent_body(self, body): + """ + Indent the code returned by self.definition_body(). Most classes + simply indent everything two spaces. This is here for + CGRegisterProtos, which needs custom indentation. + """ + return indent(body) + + def _define(self, fromDeclare=False): + return ( + self.definition_prologue(fromDeclare) + + self.indent_body(self.definition_body()) + + self.definition_epilogue() + ) + + def define(self): + if self.signatureOnly: + if self.static: + # self.static makes us not output anything in the header, so output the signature here. + return self.signature() + return "" + return "" if (self.inline and not self.static) else self._define() + + def definition_prologue(self, fromDeclare): + error_reporting_label = self.error_reporting_label() + if error_reporting_label: + # We're going to want a BindingCallContext. Rename our JSContext* + # arg accordingly. + i = 0 + while i < len(self.args): + arg = self.args[i] + if arg.argType == "JSContext*": + cxname = arg.name + self.args[i] = Argument(arg.argType, "cx_", arg.default) + break + i += 1 + if i == len(self.args): + raise TypeError("Must have a JSContext* to create a BindingCallContext") + + prologue = "%s%s%s(%s)\n{\n" % ( + self._template(), + self._decorators(), + self.name, + self._argstring(fromDeclare), + ) + if error_reporting_label: + prologue += indent( + fill( + """ + BindingCallContext ${cxname}(cx_, "${label}"); + """, + cxname=cxname, + label=error_reporting_label, + ) + ) + + profiler_label = self.auto_profiler_label() + if profiler_label: + prologue += indent(profiler_label) + "\n" + + return prologue + + def definition_epilogue(self): + return "}\n" + + def definition_body(self): + assert False # Override me! + + """ + Override this method to return a pair of (descriptive string, name of a + JSContext* variable) in order to generate a profiler label for this method. + """ + + def auto_profiler_label(self): + return None # Override me! + + """ + Override this method to return a string to be used as the label for a + BindingCallContext. If this does not return None, one of the arguments of + this method must be of type 'JSContext*'. Its name will be replaced with + 'cx_' and a BindingCallContext named 'cx' will be instantiated with the + given label. + """ + + def error_reporting_label(self): + return None # Override me! + + +class CGAbstractStaticMethod(CGAbstractMethod): + """ + Abstract base class for codegen of implementation-only (no + declaration) static methods. + """ + + def __init__(self, descriptor, name, returnType, args, canRunScript=False): + CGAbstractMethod.__init__( + self, + descriptor, + name, + returnType, + args, + inline=False, + static=True, + canRunScript=canRunScript, + ) + + +class CGAbstractClassHook(CGAbstractStaticMethod): + """ + Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw + 'this' unwrapping as it assumes that the unwrapped type is always known. + """ + + def __init__(self, descriptor, name, returnType, args): + CGAbstractStaticMethod.__init__(self, descriptor, name, returnType, args) + + def definition_body_prologue(self): + return "%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(obj);\n" % ( + self.descriptor.nativeType, + self.descriptor.nativeType, + ) + + def definition_body(self): + return self.definition_body_prologue() + self.generate_code() + + def generate_code(self): + assert False # Override me! + + +class CGAddPropertyHook(CGAbstractClassHook): + """ + A hook for addProperty, used to preserve our wrapper from GC. + """ + + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "obj"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::Handle<JS::Value>", "val"), + ] + CGAbstractClassHook.__init__( + self, descriptor, ADDPROPERTY_HOOK_NAME, "bool", args + ) + + def generate_code(self): + assert self.descriptor.wrapperCache + # This hook is also called by TryPreserveWrapper on non-nsISupports + # cycle collected objects, so if addProperty is ever changed to do + # anything more or less than preserve the wrapper, TryPreserveWrapper + # will need to be changed. + return dedent( + """ + // We don't want to preserve if we don't have a wrapper, and we + // obviously can't preserve if we're not initialized. + if (self && self->GetWrapperPreserveColor()) { + PreserveWrapper(self); + } + return true; + """ + ) + + +class CGGetWrapperCacheHook(CGAbstractClassHook): + """ + A hook for GetWrapperCache, used by HasReleasedWrapper to get the + nsWrapperCache pointer for a non-nsISupports object. + """ + + def __init__(self, descriptor): + args = [Argument("JS::Handle<JSObject*>", "obj")] + CGAbstractClassHook.__init__( + self, descriptor, GETWRAPPERCACHE_HOOK_NAME, "nsWrapperCache*", args + ) + + def generate_code(self): + assert self.descriptor.wrapperCache + return dedent( + """ + return self; + """ + ) + + +def finalizeHook(descriptor, hookName, gcx, obj): + finalize = "JS::SetReservedSlot(%s, DOM_OBJECT_SLOT, JS::UndefinedValue());\n" % obj + if descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"): + finalize += fill( + """ + // Either our proxy created an expando object or not. If it did, + // then we would have preserved ourselves, and hence if we're going + // away so is our C++ object and we should reset its expando value. + // It's possible that in this situation the C++ object's reflector + // pointer has been nulled out, but if not it's pointing to us. If + // our proxy did _not_ create an expando object then it's possible + // that we're no longer the reflector for our C++ object (and + // incremental finalization is finally getting to us), and that in + // the meantime the new reflector has created an expando object. + // In that case we do NOT want to clear the expando pointer in the + // C++ object. + // + // It's important to do this before we ClearWrapper, of course. + JSObject* reflector = self->GetWrapperMaybeDead(); + if (!reflector || reflector == ${obj}) { + self->mExpandoAndGeneration.expando = JS::UndefinedValue(); + } + """, + obj=obj, + ) + for m in descriptor.interface.members: + if m.isAttr() and m.type.isObservableArray(): + finalize += fill( + """ + { + JS::Value val = JS::GetReservedSlot(obj, ${slot}); + if (!val.isUndefined()) { + JSObject* obj = &val.toObject(); + js::SetProxyReservedSlot(obj, OBSERVABLE_ARRAY_DOM_INTERFACE_SLOT, JS::UndefinedValue()); + } + } + """, + slot=memberReservedSlot(m, descriptor), + ) + if descriptor.wrapperCache: + finalize += "ClearWrapper(self, self, %s);\n" % obj + if descriptor.isGlobal(): + finalize += "mozilla::dom::FinalizeGlobal(%s, %s);\n" % (gcx, obj) + finalize += fill( + """ + if (size_t mallocBytes = BindingJSObjectMallocBytes(self)) { + JS::RemoveAssociatedMemory(${obj}, mallocBytes, + JS::MemoryUse::DOMBinding); + } + """, + obj=obj, + ) + finalize += "AddForDeferredFinalization<%s>(self);\n" % descriptor.nativeType + return CGIfWrapper(CGGeneric(finalize), "self") + + +class CGClassFinalizeHook(CGAbstractClassHook): + """ + A hook for finalize, used to release our native object. + """ + + def __init__(self, descriptor): + args = [Argument("JS::GCContext*", "gcx"), Argument("JSObject*", "obj")] + CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME, "void", args) + + def generate_code(self): + return finalizeHook( + self.descriptor, self.name, self.args[0].name, self.args[1].name + ).define() + + +def objectMovedHook(descriptor, hookName, obj, old): + assert descriptor.wrapperCache + return fill( + """ + if (self) { + UpdateWrapper(self, self, ${obj}, ${old}); + } + + return 0; + """, + obj=obj, + old=old, + ) + + +class CGClassObjectMovedHook(CGAbstractClassHook): + """ + A hook for objectMovedOp, used to update the wrapper cache when an object it + is holding moves. + """ + + def __init__(self, descriptor): + args = [Argument("JSObject*", "obj"), Argument("JSObject*", "old")] + CGAbstractClassHook.__init__( + self, descriptor, OBJECT_MOVED_HOOK_NAME, "size_t", args + ) + + def generate_code(self): + return objectMovedHook( + self.descriptor, self.name, self.args[0].name, self.args[1].name + ) + + +def JSNativeArguments(): + return [ + Argument("JSContext*", "cx"), + Argument("unsigned", "argc"), + Argument("JS::Value*", "vp"), + ] + + +class CGClassConstructor(CGAbstractStaticMethod): + """ + JS-visible constructor for our objects + """ + + def __init__(self, descriptor, ctor, name=CONSTRUCT_HOOK_NAME): + CGAbstractStaticMethod.__init__( + self, descriptor, name, "bool", JSNativeArguments() + ) + self._ctor = ctor + + def define(self): + if not self._ctor: + return "" + return CGAbstractStaticMethod.define(self) + + def definition_body(self): + return self.generate_code() + + def generate_code(self): + if self._ctor.isHTMLConstructor(): + # We better have a prototype object. Otherwise our proto + # id won't make sense. + assert self.descriptor.interface.hasInterfacePrototypeObject() + # We also better have a constructor object, if this is + # getting called! + assert self.descriptor.interface.hasInterfaceObject() + # We can't just pass null for the CreateInterfaceObjects callback, + # because our newTarget might be in a different compartment, in + # which case we'll need to look up constructor objects in that + # compartment. + return fill( + """ + return HTMLConstructor(cx, argc, vp, + constructors::id::${name}, + prototypes::id::${name}, + CreateInterfaceObjects); + """, + name=self.descriptor.name, + ) + + # If the interface is already SecureContext, notify getConditionList to skip that check, + # because the constructor won't be exposed in non-secure contexts to start with. + alreadySecureContext = self.descriptor.interface.getExtendedAttribute( + "SecureContext" + ) + + # We want to throw if any of the conditions returned by getConditionList are false. + conditionsCheck = "" + rawConditions = getRawConditionList( + self._ctor, "cx", "obj", alreadySecureContext + ) + if len(rawConditions) > 0: + notConditions = " ||\n".join("!" + cond for cond in rawConditions) + failedCheckAction = CGGeneric("return ThrowingConstructor(cx, argc, vp);\n") + conditionsCheck = ( + CGIfWrapper(failedCheckAction, notConditions).define() + "\n" + ) + + # Additionally, we want to throw if a caller does a bareword invocation + # of a constructor without |new|. + ctorName = GetConstructorNameForReporting(self.descriptor, self._ctor) + + preamble = fill( + """ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> obj(cx, &args.callee()); + $*{conditionsCheck} + if (!args.isConstructing()) { + return ThrowConstructorWithoutNew(cx, "${ctorName}"); + } + + JS::Rooted<JSObject*> desiredProto(cx); + if (!GetDesiredProto(cx, args, + prototypes::id::${name}, + CreateInterfaceObjects, + &desiredProto)) { + return false; + } + """, + conditionsCheck=conditionsCheck, + ctorName=ctorName, + name=self.descriptor.name, + ) + + name = self._ctor.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNameFor(name, True)) + callGenerator = CGMethodCall( + nativeName, True, self.descriptor, self._ctor, isConstructor=True + ) + return preamble + "\n" + callGenerator.define() + + def auto_profiler_label(self): + return fill( + """ + AUTO_PROFILER_LABEL_DYNAMIC_FAST( + "${ctorName}", "constructor", DOM, cx, + uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); + """, + ctorName=GetConstructorNameForReporting(self.descriptor, self._ctor), + ) + + def error_reporting_label(self): + return CGSpecializedMethod.error_reporting_label_helper( + self.descriptor, self._ctor, isConstructor=True + ) + + +def LegacyFactoryFunctionName(m): + return "_" + m.identifier.name + + +class CGLegacyFactoryFunctions(CGThing): + def __init__(self, descriptor): + self.descriptor = descriptor + CGThing.__init__(self) + + def declare(self): + return "" + + def define(self): + if len(self.descriptor.interface.legacyFactoryFunctions) == 0: + return "" + + constructorID = "constructors::id::" + if self.descriptor.interface.hasInterfaceObject(): + constructorID += self.descriptor.name + else: + constructorID += "_ID_Count" + + namedConstructors = "" + for n in self.descriptor.interface.legacyFactoryFunctions: + namedConstructors += ( + '{ "%s", { %s, &sLegacyFactoryFunctionNativePropertyHooks }, %i },\n' + % (n.identifier.name, LegacyFactoryFunctionName(n), methodLength(n)) + ) + + return fill( + """ + bool sLegacyFactoryFunctionNativePropertiesInited = true; + const NativePropertyHooks sLegacyFactoryFunctionNativePropertyHooks = { + nullptr, + nullptr, + nullptr, + { nullptr, nullptr, &sLegacyFactoryFunctionNativePropertiesInited }, + prototypes::id::${name}, + ${constructorID}, + nullptr + }; + + static const LegacyFactoryFunction namedConstructors[] = { + $*{namedConstructors} + { nullptr, { nullptr, nullptr }, 0 } + }; + """, + name=self.descriptor.name, + constructorID=constructorID, + namedConstructors=namedConstructors, + ) + + +def isChromeOnly(m): + return m.getExtendedAttribute("ChromeOnly") + + +def prefIdentifier(pref): + return pref.replace(".", "_").replace("-", "_") + + +def prefHeader(pref): + return "mozilla/StaticPrefs_%s.h" % pref.partition(".")[0] + + +class MemberCondition: + """ + An object representing the condition for a member to actually be + exposed. Any of the arguments can be None. If not + None, they should have the following types: + + pref: The name of the preference. + func: The name of the function. + secureContext: A bool indicating whether a secure context is required. + nonExposedGlobals: A set of names of globals. Can be empty, in which case + it's treated the same way as None. + trial: The name of the origin trial. + """ + + def __init__( + self, + pref=None, + func=None, + secureContext=False, + nonExposedGlobals=None, + trial=None, + ): + assert pref is None or isinstance(pref, str) + assert func is None or isinstance(func, str) + assert trial is None or isinstance(trial, str) + assert isinstance(secureContext, bool) + assert nonExposedGlobals is None or isinstance(nonExposedGlobals, set) + self.pref = pref + if self.pref: + identifier = prefIdentifier(self.pref) + self.prefFuncIndex = "WebIDLPrefIndex::" + identifier + else: + self.prefFuncIndex = "WebIDLPrefIndex::NoPref" + + self.secureContext = secureContext + + def toFuncPtr(val): + if val is None: + return "nullptr" + return "&" + val + + self.func = toFuncPtr(func) + + if nonExposedGlobals: + # Nonempty set + self.nonExposedGlobals = " | ".join( + map(lambda g: "GlobalNames::%s" % g, sorted(nonExposedGlobals)) + ) + else: + self.nonExposedGlobals = "0" + + if trial: + self.trial = "OriginTrial::" + trial + else: + self.trial = "OriginTrial(0)" + + def __eq__(self, other): + return ( + self.pref == other.pref + and self.func == other.func + and self.secureContext == other.secureContext + and self.nonExposedGlobals == other.nonExposedGlobals + and self.trial == other.trial + ) + + def __ne__(self, other): + return not self.__eq__(other) + + def hasDisablers(self): + return ( + self.pref is not None + or self.secureContext + or self.func != "nullptr" + or self.nonExposedGlobals != "0" + or self.trial != "OriginTrial(0)" + ) + + +class PropertyDefiner: + """ + A common superclass for defining things on prototype objects. + + Subclasses should implement generateArray to generate the actual arrays of + things we're defining. They should also set self.chrome to the list of + things only exposed to chrome and self.regular to the list of things exposed + to both chrome and web pages. + """ + + def __init__(self, descriptor, name): + self.descriptor = descriptor + self.name = name + + def hasChromeOnly(self): + return len(self.chrome) > 0 + + def hasNonChromeOnly(self): + return len(self.regular) > 0 + + def variableName(self, chrome): + if chrome: + if self.hasChromeOnly(): + return "sChrome" + self.name + else: + if self.hasNonChromeOnly(): + return "s" + self.name + return "nullptr" + + def usedForXrays(self): + return self.descriptor.wantsXrays + + def length(self, chrome): + return len(self.chrome) if chrome else len(self.regular) + + def __str__(self): + # We only need to generate id arrays for things that will end + # up used via ResolveProperty or EnumerateProperties. + str = self.generateArray(self.regular, self.variableName(False)) + if self.hasChromeOnly(): + str += self.generateArray(self.chrome, self.variableName(True)) + return str + + @staticmethod + def getStringAttr(member, name): + attr = member.getExtendedAttribute(name) + if attr is None: + return None + # It's a list of strings + assert len(attr) == 1 + assert attr[0] is not None + return attr[0] + + @staticmethod + def getControllingCondition(interfaceMember, descriptor): + interface = descriptor.interface + nonExposureSet = interface.exposureSet - interfaceMember.exposureSet + + trial = PropertyDefiner.getStringAttr(interfaceMember, "Trial") + if trial and interface.identifier.name in ["Window", "Document"]: + raise TypeError( + "[Trial] not yet supported for %s.%s, see bug 1757935" + % (interface.identifier.name, interfaceMember.identifier.name) + ) + + return MemberCondition( + PropertyDefiner.getStringAttr(interfaceMember, "Pref"), + PropertyDefiner.getStringAttr(interfaceMember, "Func"), + interfaceMember.getExtendedAttribute("SecureContext") is not None, + nonExposureSet, + trial, + ) + + @staticmethod + def generatePrefableArrayValues( + array, + descriptor, + specFormatter, + specTerminator, + getCondition, + getDataTuple, + switchToCondition=None, + ): + """ + This method generates an array of spec entries for interface members. It returns + a tuple containing the array of spec entries and the maximum of the number of + spec entries per condition. + + array is an array of interface members. + + descriptor is the descriptor for the interface that array contains members of. + + specFormatter is a function that takes a single argument, a tuple, + and returns a string, a spec array entry. + + specTerminator is a terminator for the spec array (inserted every time + our controlling pref changes and at the end of the array). + + getCondition is a callback function that takes an array entry and + returns the corresponding MemberCondition. + + getDataTuple is a callback function that takes an array entry and + returns a tuple suitable to be passed to specFormatter. + + switchToCondition is a function that takes a MemberCondition and an array of + previously generated spec entries. If None is passed for this function then all + the interface members should return the same value from getCondition. + """ + + def unsupportedSwitchToCondition(condition, specs): + # If no specs have been added yet then this is just the first call to + # switchToCondition that we call to avoid putting a specTerminator at the + # front of the list. + if len(specs) == 0: + return + raise "Not supported" + + if switchToCondition is None: + switchToCondition = unsupportedSwitchToCondition + + specs = [] + numSpecsInCurPrefable = 0 + maxNumSpecsInPrefable = 0 + + # So we won't put a specTerminator at the very front of the list: + lastCondition = getCondition(array[0], descriptor) + + switchToCondition(lastCondition, specs) + + for member in array: + curCondition = getCondition(member, descriptor) + if lastCondition != curCondition: + # Terminate previous list + specs.append(specTerminator) + if numSpecsInCurPrefable > maxNumSpecsInPrefable: + maxNumSpecsInPrefable = numSpecsInCurPrefable + numSpecsInCurPrefable = 0 + # And switch to our new condition + switchToCondition(curCondition, specs) + lastCondition = curCondition + # And the actual spec + specs.append(specFormatter(getDataTuple(member, descriptor))) + numSpecsInCurPrefable += 1 + if numSpecsInCurPrefable > maxNumSpecsInPrefable: + maxNumSpecsInPrefable = numSpecsInCurPrefable + specs.append(specTerminator) + + return (specs, maxNumSpecsInPrefable) + + def generatePrefableArray( + self, + array, + name, + specFormatter, + specTerminator, + specType, + getCondition, + getDataTuple, + ): + """ + This method generates our various arrays. + + array is an array of interface members as passed to generateArray + + name is the name as passed to generateArray + + specFormatter is a function that takes a single argument, a tuple, + and returns a string, a spec array entry + + specTerminator is a terminator for the spec array (inserted every time + our controlling pref changes and at the end of the array) + + specType is the actual typename of our spec + + getCondition is a callback function that takes an array entry and + returns the corresponding MemberCondition. + + getDataTuple is a callback function that takes an array entry and + returns a tuple suitable to be passed to specFormatter. + """ + + # We want to generate a single list of specs, but with specTerminator + # inserted at every point where the pref name controlling the member + # changes. That will make sure the order of the properties as exposed + # on the interface and interface prototype objects does not change when + # pref control is added to members while still allowing us to define all + # the members in the smallest number of JSAPI calls. + assert len(array) != 0 + + disablers = [] + prefableSpecs = [] + + disablersTemplate = dedent( + """ + static const PrefableDisablers %s_disablers%d = { + %s, %s, %s, %s, %s + }; + """ + ) + prefableWithDisablersTemplate = " { &%s_disablers%d, &%s_specs[%d] }" + prefableWithoutDisablersTemplate = " { nullptr, &%s_specs[%d] }" + + def switchToCondition(condition, specs): + # Set up pointers to the new sets of specs inside prefableSpecs + if condition.hasDisablers(): + prefableSpecs.append( + prefableWithDisablersTemplate % (name, len(specs), name, len(specs)) + ) + disablers.append( + disablersTemplate + % ( + name, + len(specs), + condition.prefFuncIndex, + condition.nonExposedGlobals, + toStringBool(condition.secureContext), + condition.trial, + condition.func, + ) + ) + else: + prefableSpecs.append( + prefableWithoutDisablersTemplate % (name, len(specs)) + ) + + specs, maxNumSpecsInPrefable = self.generatePrefableArrayValues( + array, + self.descriptor, + specFormatter, + specTerminator, + getCondition, + getDataTuple, + switchToCondition, + ) + prefableSpecs.append(" { nullptr, nullptr }") + + specType = "const " + specType + arrays = fill( + """ + static ${specType} ${name}_specs[] = { + ${specs} + }; + + ${disablers} + static const Prefable<${specType}> ${name}[] = { + ${prefableSpecs} + }; + + """, + specType=specType, + name=name, + disablers="\n".join(disablers), + specs=",\n".join(specs), + prefableSpecs=",\n".join(prefableSpecs), + ) + + if self.usedForXrays(): + arrays = fill( + """ + $*{arrays} + static_assert(${numPrefableSpecs} <= 1ull << NUM_BITS_PROPERTY_INFO_PREF_INDEX, + "We have a prefable index that is >= (1 << NUM_BITS_PROPERTY_INFO_PREF_INDEX)"); + static_assert(${maxNumSpecsInPrefable} <= 1ull << NUM_BITS_PROPERTY_INFO_SPEC_INDEX, + "We have a spec index that is >= (1 << NUM_BITS_PROPERTY_INFO_SPEC_INDEX)"); + + """, + arrays=arrays, + # Minus 1 because there's a list terminator in prefableSpecs. + numPrefableSpecs=len(prefableSpecs) - 1, + maxNumSpecsInPrefable=maxNumSpecsInPrefable, + ) + + return arrays + + +# The length of a method is the minimum of the lengths of the +# argument lists of all its overloads. +def overloadLength(arguments): + i = len(arguments) + while i > 0 and arguments[i - 1].optional: + i -= 1 + return i + + +def methodLength(method): + signatures = method.signatures() + return min(overloadLength(arguments) for retType, arguments in signatures) + + +def clearableCachedAttrs(descriptor): + return ( + m + for m in descriptor.interface.members + if m.isAttr() and + # Constants should never need clearing! + m.dependsOn != "Nothing" and m.slotIndices is not None + ) + + +def MakeClearCachedValueNativeName(member): + return "ClearCached%sValue" % MakeNativeName(member.identifier.name) + + +def IDLToCIdentifier(name): + return name.replace("-", "_") + + +def EnumerabilityFlags(member): + if member.getExtendedAttribute("NonEnumerable"): + return "0" + return "JSPROP_ENUMERATE" + + +class MethodDefiner(PropertyDefiner): + """ + A class for defining methods on a prototype object. + """ + + def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False): + assert not (static and unforgeable) + PropertyDefiner.__init__(self, descriptor, name) + + # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822 + # We should be able to check for special operations without an + # identifier. For now we check if the name starts with __ + + # Ignore non-static methods for interfaces without a proto object + if descriptor.interface.hasInterfacePrototypeObject() or static: + methods = [ + m + for m in descriptor.interface.members + if m.isMethod() + and m.isStatic() == static + and MemberIsLegacyUnforgeable(m, descriptor) == unforgeable + and ( + not crossOriginOnly or m.getExtendedAttribute("CrossOriginCallable") + ) + and not m.isIdentifierLess() + and not m.getExtendedAttribute("Unexposed") + ] + else: + methods = [] + self.chrome = [] + self.regular = [] + for m in methods: + method = self.methodData(m, descriptor) + + if m.isStatic(): + method["nativeName"] = CppKeywords.checkMethodName( + IDLToCIdentifier(m.identifier.name) + ) + + if isChromeOnly(m): + self.chrome.append(method) + else: + self.regular.append(method) + + # TODO: Once iterable is implemented, use tiebreak rules instead of + # failing. Also, may be more tiebreak rules to implement once spec bug + # is resolved. + # https://www.w3.org/Bugs/Public/show_bug.cgi?id=28592 + def hasIterator(methods, regular): + return any("@@iterator" in m.aliases for m in methods) or any( + "@@iterator" == r["name"] for r in regular + ) + + # Check whether we need to output an @@iterator due to having an indexed + # getter. We only do this while outputting non-static and + # non-unforgeable methods, since the @@iterator function will be + # neither. + if not static and not unforgeable and descriptor.supportsIndexedProperties(): + if hasIterator(methods, self.regular): + raise TypeError( + "Cannot have indexed getter/attr on " + "interface %s with other members " + "that generate @@iterator, such as " + "maplike/setlike or aliased functions." + % self.descriptor.interface.identifier.name + ) + self.regular.append( + { + "name": "@@iterator", + "methodInfo": False, + "selfHostedName": "$ArrayValues", + "length": 0, + "flags": "0", # Not enumerable, per spec. + "condition": MemberCondition(), + } + ) + + # Generate the keys/values/entries aliases for value iterables. + maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable + if ( + not static + and not unforgeable + and maplikeOrSetlikeOrIterable + and maplikeOrSetlikeOrIterable.isIterable() + and maplikeOrSetlikeOrIterable.isValueIterator() + ): + # Add our keys/values/entries/forEach + self.regular.append( + { + "name": "keys", + "methodInfo": False, + "selfHostedName": "ArrayKeys", + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition( + maplikeOrSetlikeOrIterable, descriptor + ), + } + ) + self.regular.append( + { + "name": "values", + "methodInfo": False, + "selfHostedName": "$ArrayValues", + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition( + maplikeOrSetlikeOrIterable, descriptor + ), + } + ) + self.regular.append( + { + "name": "entries", + "methodInfo": False, + "selfHostedName": "ArrayEntries", + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition( + maplikeOrSetlikeOrIterable, descriptor + ), + } + ) + self.regular.append( + { + "name": "forEach", + "methodInfo": False, + "selfHostedName": "ArrayForEach", + "length": 1, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition( + maplikeOrSetlikeOrIterable, descriptor + ), + } + ) + + if not static: + stringifier = descriptor.operations["Stringifier"] + if stringifier and unforgeable == MemberIsLegacyUnforgeable( + stringifier, descriptor + ): + toStringDesc = { + "name": GetWebExposedName(stringifier, descriptor), + "nativeName": stringifier.identifier.name, + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition( + stringifier, descriptor + ), + } + if isChromeOnly(stringifier): + self.chrome.append(toStringDesc) + else: + self.regular.append(toStringDesc) + if unforgeable and descriptor.interface.getExtendedAttribute( + "LegacyUnforgeable" + ): + # Synthesize our valueOf method + self.regular.append( + { + "name": "valueOf", + "selfHostedName": "Object_valueOf", + "methodInfo": False, + "length": 0, + "flags": "0", # readonly/permanent added automatically. + "condition": MemberCondition(), + } + ) + + if descriptor.interface.isJSImplemented(): + if static: + if descriptor.interface.hasInterfaceObject(): + self.chrome.append( + { + "name": "_create", + "nativeName": ("%s::_Create" % descriptor.name), + "methodInfo": False, + "length": 2, + "flags": "0", + "condition": MemberCondition(), + } + ) + + self.unforgeable = unforgeable + + if static: + if not descriptor.interface.hasInterfaceObject(): + # static methods go on the interface object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + else: + if not descriptor.interface.hasInterfacePrototypeObject(): + # non-static methods go on the interface prototype object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + + @staticmethod + def methodData(m, descriptor, overrideFlags=None): + return { + "name": m.identifier.name, + "methodInfo": not m.isStatic(), + "length": methodLength(m), + "flags": EnumerabilityFlags(m) + if (overrideFlags is None) + else overrideFlags, + "condition": PropertyDefiner.getControllingCondition(m, descriptor), + "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"), + "returnsPromise": m.returnsPromise(), + "hasIteratorAlias": "@@iterator" in m.aliases, + } + + @staticmethod + def formatSpec(fields): + if fields[0].startswith("@@"): + fields = (fields[0][2:],) + fields[1:] + return " JS_SYM_FNSPEC(%s, %s, %s, %s, %s, %s)" % fields + return ' JS_FNSPEC("%s", %s, %s, %s, %s, %s)' % fields + + @staticmethod + def specData(m, descriptor, unforgeable=False): + def flags(m, unforgeable): + unforgeable = " | JSPROP_PERMANENT | JSPROP_READONLY" if unforgeable else "" + return m["flags"] + unforgeable + + if "selfHostedName" in m: + selfHostedName = '"%s"' % m["selfHostedName"] + assert not m.get("methodInfo", True) + accessor = "nullptr" + jitinfo = "nullptr" + else: + selfHostedName = "nullptr" + # When defining symbols, function name may not match symbol name + methodName = m.get("methodName", m["name"]) + accessor = m.get("nativeName", IDLToCIdentifier(methodName)) + if m.get("methodInfo", True): + if m.get("returnsPromise", False): + exceptionPolicy = "ConvertExceptionsToPromises" + else: + exceptionPolicy = "ThrowExceptions" + + # Cast this in case the methodInfo is a + # JSTypedMethodJitInfo. + jitinfo = ( + "reinterpret_cast<const JSJitInfo*>(&%s_methodinfo)" % accessor + ) + if m.get("allowCrossOriginThis", False): + accessor = ( + "(GenericMethod<CrossOriginThisPolicy, %s>)" % exceptionPolicy + ) + elif descriptor.interface.hasDescendantWithCrossOriginMembers: + accessor = ( + "(GenericMethod<MaybeCrossOriginObjectThisPolicy, %s>)" + % exceptionPolicy + ) + elif descriptor.interface.isOnGlobalProtoChain(): + accessor = ( + "(GenericMethod<MaybeGlobalThisPolicy, %s>)" % exceptionPolicy + ) + else: + accessor = "(GenericMethod<NormalThisPolicy, %s>)" % exceptionPolicy + else: + if m.get("returnsPromise", False): + jitinfo = "&%s_methodinfo" % accessor + accessor = "StaticMethodPromiseWrapper" + else: + jitinfo = "nullptr" + + return ( + m["name"], + accessor, + jitinfo, + m["length"], + flags(m, unforgeable), + selfHostedName, + ) + + @staticmethod + def condition(m, d): + return m["condition"] + + def generateArray(self, array, name): + if len(array) == 0: + return "" + + return self.generatePrefableArray( + array, + name, + self.formatSpec, + " JS_FS_END", + "JSFunctionSpec", + self.condition, + functools.partial(self.specData, unforgeable=self.unforgeable), + ) + + +class AttrDefiner(PropertyDefiner): + def __init__(self, descriptor, name, crossOriginOnly, static, unforgeable=False): + assert not (static and unforgeable) + PropertyDefiner.__init__(self, descriptor, name) + self.name = name + # Ignore non-static attributes for interfaces without a proto object + if descriptor.interface.hasInterfacePrototypeObject() or static: + idlAttrs = [ + m + for m in descriptor.interface.members + if m.isAttr() + and m.isStatic() == static + and MemberIsLegacyUnforgeable(m, descriptor) == unforgeable + and ( + not crossOriginOnly + or m.getExtendedAttribute("CrossOriginReadable") + or m.getExtendedAttribute("CrossOriginWritable") + ) + ] + else: + idlAttrs = [] + + attributes = [] + for attr in idlAttrs: + attributes.extend(self.attrData(attr, unforgeable)) + self.chrome = [m for m in attributes if isChromeOnly(m["attr"])] + self.regular = [m for m in attributes if not isChromeOnly(m["attr"])] + self.static = static + + if static: + if not descriptor.interface.hasInterfaceObject(): + # static attributes go on the interface object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + else: + if not descriptor.interface.hasInterfacePrototypeObject(): + # non-static attributes go on the interface prototype object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + + @staticmethod + def attrData(attr, unforgeable=False, overrideFlags=None): + if overrideFlags is None: + permanent = " | JSPROP_PERMANENT" if unforgeable else "" + flags = EnumerabilityFlags(attr) + permanent + else: + flags = overrideFlags + return ( + {"name": name, "attr": attr, "flags": flags} + for name in [attr.identifier.name] + attr.bindingAliases + ) + + @staticmethod + def condition(m, d): + return PropertyDefiner.getControllingCondition(m["attr"], d) + + @staticmethod + def specData(entry, descriptor, static=False, crossOriginOnly=False): + def getter(attr): + if crossOriginOnly and not attr.getExtendedAttribute("CrossOriginReadable"): + return "nullptr, nullptr" + if static: + if attr.type.isPromise(): + raise TypeError( + "Don't know how to handle " + "static Promise-returning " + "attribute %s.%s" % (descriptor.name, attr.identifier.name) + ) + accessor = "get_" + IDLToCIdentifier(attr.identifier.name) + jitinfo = "nullptr" + else: + if attr.type.isPromise(): + exceptionPolicy = "ConvertExceptionsToPromises" + else: + exceptionPolicy = "ThrowExceptions" + + if attr.hasLegacyLenientThis(): + if attr.getExtendedAttribute("CrossOriginReadable"): + raise TypeError( + "Can't handle lenient cross-origin " + "readable attribute %s.%s" + % (descriptor.name, attr.identifier.name) + ) + if descriptor.interface.hasDescendantWithCrossOriginMembers: + accessor = ( + "GenericGetter<MaybeCrossOriginObjectLenientThisPolicy, %s>" + % exceptionPolicy + ) + else: + accessor = ( + "GenericGetter<LenientThisPolicy, %s>" % exceptionPolicy + ) + elif attr.getExtendedAttribute("CrossOriginReadable"): + accessor = ( + "GenericGetter<CrossOriginThisPolicy, %s>" % exceptionPolicy + ) + elif descriptor.interface.hasDescendantWithCrossOriginMembers: + accessor = ( + "GenericGetter<MaybeCrossOriginObjectThisPolicy, %s>" + % exceptionPolicy + ) + elif descriptor.interface.isOnGlobalProtoChain(): + accessor = ( + "GenericGetter<MaybeGlobalThisPolicy, %s>" % exceptionPolicy + ) + else: + accessor = "GenericGetter<NormalThisPolicy, %s>" % exceptionPolicy + jitinfo = "&%s_getterinfo" % IDLToCIdentifier(attr.identifier.name) + return "%s, %s" % (accessor, jitinfo) + + def setter(attr): + if ( + attr.readonly + and attr.getExtendedAttribute("PutForwards") is None + and attr.getExtendedAttribute("Replaceable") is None + and attr.getExtendedAttribute("LegacyLenientSetter") is None + ): + return "nullptr, nullptr" + if crossOriginOnly and not attr.getExtendedAttribute("CrossOriginWritable"): + return "nullptr, nullptr" + if static: + accessor = "set_" + IDLToCIdentifier(attr.identifier.name) + jitinfo = "nullptr" + else: + if attr.hasLegacyLenientThis(): + if attr.getExtendedAttribute("CrossOriginWritable"): + raise TypeError( + "Can't handle lenient cross-origin " + "writable attribute %s.%s" + % (descriptor.name, attr.identifier.name) + ) + if descriptor.interface.hasDescendantWithCrossOriginMembers: + accessor = ( + "GenericSetter<MaybeCrossOriginObjectLenientThisPolicy>" + ) + else: + accessor = "GenericSetter<LenientThisPolicy>" + elif attr.getExtendedAttribute("CrossOriginWritable"): + accessor = "GenericSetter<CrossOriginThisPolicy>" + elif descriptor.interface.hasDescendantWithCrossOriginMembers: + accessor = "GenericSetter<MaybeCrossOriginObjectThisPolicy>" + elif descriptor.interface.isOnGlobalProtoChain(): + accessor = "GenericSetter<MaybeGlobalThisPolicy>" + else: + accessor = "GenericSetter<NormalThisPolicy>" + jitinfo = "&%s_setterinfo" % IDLToCIdentifier(attr.identifier.name) + return "%s, %s" % (accessor, jitinfo) + + name, attr, flags = entry["name"], entry["attr"], entry["flags"] + return (name, flags, getter(attr), setter(attr)) + + @staticmethod + def formatSpec(fields): + return ' JSPropertySpec::nativeAccessors("%s", %s, %s, %s)' % fields + + def generateArray(self, array, name): + if len(array) == 0: + return "" + + return self.generatePrefableArray( + array, + name, + self.formatSpec, + " JS_PS_END", + "JSPropertySpec", + self.condition, + functools.partial(self.specData, static=self.static), + ) + + +class ConstDefiner(PropertyDefiner): + """ + A class for definining constants on the interface object + """ + + def __init__(self, descriptor, name): + PropertyDefiner.__init__(self, descriptor, name) + self.name = name + constants = [m for m in descriptor.interface.members if m.isConst()] + self.chrome = [m for m in constants if isChromeOnly(m)] + self.regular = [m for m in constants if not isChromeOnly(m)] + + def generateArray(self, array, name): + if len(array) == 0: + return "" + + def specData(const, descriptor): + return (const.identifier.name, convertConstIDLValueToJSVal(const.value)) + + return self.generatePrefableArray( + array, + name, + lambda fields: ' { "%s", %s }' % fields, + " { 0, JS::UndefinedValue() }", + "ConstantSpec", + PropertyDefiner.getControllingCondition, + specData, + ) + + +class PropertyArrays: + def __init__(self, descriptor, crossOriginOnly=False): + self.staticMethods = MethodDefiner( + descriptor, "StaticMethods", crossOriginOnly, static=True + ) + self.staticAttrs = AttrDefiner( + descriptor, "StaticAttributes", crossOriginOnly, static=True + ) + self.methods = MethodDefiner( + descriptor, "Methods", crossOriginOnly, static=False + ) + self.attrs = AttrDefiner( + descriptor, "Attributes", crossOriginOnly, static=False + ) + self.unforgeableMethods = MethodDefiner( + descriptor, + "UnforgeableMethods", + crossOriginOnly, + static=False, + unforgeable=True, + ) + self.unforgeableAttrs = AttrDefiner( + descriptor, + "UnforgeableAttributes", + crossOriginOnly, + static=False, + unforgeable=True, + ) + self.consts = ConstDefiner(descriptor, "Constants") + + @staticmethod + def arrayNames(): + return [ + "staticMethods", + "staticAttrs", + "methods", + "attrs", + "unforgeableMethods", + "unforgeableAttrs", + "consts", + ] + + def hasChromeOnly(self): + return any(getattr(self, a).hasChromeOnly() for a in self.arrayNames()) + + def hasNonChromeOnly(self): + return any(getattr(self, a).hasNonChromeOnly() for a in self.arrayNames()) + + def __str__(self): + define = "" + for array in self.arrayNames(): + define += str(getattr(self, array)) + return define + + +class CGConstDefinition(CGThing): + """ + Given a const member of an interface, return the C++ static const definition + for the member. Should be part of the interface namespace in the header + file. + """ + + def __init__(self, member): + assert ( + member.isConst() + and member.value.type.isPrimitive() + and not member.value.type.nullable() + ) + + name = CppKeywords.checkMethodName(IDLToCIdentifier(member.identifier.name)) + tag = member.value.type.tag() + value = member.value.value + if tag == IDLType.Tags.bool: + value = toStringBool(member.value.value) + self.const = "static const %s %s = %s;" % (builtinNames[tag], name, value) + + def declare(self): + return self.const + + def define(self): + return "" + + def deps(self): + return [] + + +class CGNativeProperties(CGList): + def __init__(self, descriptor, properties): + def generateNativeProperties(name, chrome): + def check(p): + return p.hasChromeOnly() if chrome else p.hasNonChromeOnly() + + nativePropsInts = [] + nativePropsPtrs = [] + nativePropsDuos = [] + + duosOffset = 0 + idsOffset = 0 + for array in properties.arrayNames(): + propertyArray = getattr(properties, array) + if check(propertyArray): + varName = propertyArray.variableName(chrome) + bitfields = "true, %d /* %s */" % (duosOffset, varName) + duosOffset += 1 + nativePropsInts.append(CGGeneric(bitfields)) + + if propertyArray.usedForXrays(): + ids = "&%s_propertyInfos[%d]" % (name, idsOffset) + idsOffset += propertyArray.length(chrome) + else: + ids = "nullptr" + duo = "{ %s, %s }" % (varName, ids) + nativePropsDuos.append(CGGeneric(duo)) + else: + bitfields = "false, 0" + nativePropsInts.append(CGGeneric(bitfields)) + + iteratorAliasIndex = -1 + for index, item in enumerate(properties.methods.regular): + if item.get("hasIteratorAlias"): + iteratorAliasIndex = index + break + nativePropsInts.append(CGGeneric(str(iteratorAliasIndex))) + + nativePropsDuos = [ + CGWrapper( + CGIndenter(CGList(nativePropsDuos, ",\n")), pre="{\n", post="\n}" + ) + ] + + pre = "static const NativePropertiesN<%d> %s = {\n" % (duosOffset, name) + post = "\n};\n" + if descriptor.wantsXrays: + pre = fill( + """ + static uint16_t ${name}_sortedPropertyIndices[${size}]; + static PropertyInfo ${name}_propertyInfos[${size}]; + + $*{pre} + """, + name=name, + size=idsOffset, + pre=pre, + ) + if iteratorAliasIndex > 0: + # The iteratorAliasMethodIndex is a signed integer, so the + # max value it can store is 2^(nbits-1)-1. + post = fill( + """ + $*{post} + static_assert(${iteratorAliasIndex} < 1ull << (CHAR_BIT * sizeof(${name}.iteratorAliasMethodIndex) - 1), + "We have an iterator alias index that is oversized"); + """, + post=post, + iteratorAliasIndex=iteratorAliasIndex, + name=name, + ) + post = fill( + """ + $*{post} + static_assert(${propertyInfoCount} < 1ull << (CHAR_BIT * sizeof(${name}.propertyInfoCount)), + "We have a property info count that is oversized"); + """, + post=post, + propertyInfoCount=idsOffset, + name=name, + ) + nativePropsInts.append(CGGeneric("%d" % idsOffset)) + nativePropsPtrs.append(CGGeneric("%s_sortedPropertyIndices" % name)) + else: + nativePropsInts.append(CGGeneric("0")) + nativePropsPtrs.append(CGGeneric("nullptr")) + nativeProps = nativePropsInts + nativePropsPtrs + nativePropsDuos + return CGWrapper(CGIndenter(CGList(nativeProps, ",\n")), pre=pre, post=post) + + nativeProperties = [] + if properties.hasNonChromeOnly(): + nativeProperties.append( + generateNativeProperties("sNativeProperties", False) + ) + if properties.hasChromeOnly(): + nativeProperties.append( + generateNativeProperties("sChromeOnlyNativeProperties", True) + ) + + CGList.__init__(self, nativeProperties, "\n") + + def declare(self): + return "" + + def define(self): + return CGList.define(self) + + +class CGCollectJSONAttributesMethod(CGAbstractMethod): + """ + Generate the CollectJSONAttributes method for an interface descriptor + """ + + def __init__(self, descriptor, toJSONMethod): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "obj"), + Argument("%s*" % descriptor.nativeType, "self"), + Argument("JS::Rooted<JSObject*>&", "result"), + ] + CGAbstractMethod.__init__( + self, descriptor, "CollectJSONAttributes", "bool", args, canRunScript=True + ) + self.toJSONMethod = toJSONMethod + + def definition_body(self): + ret = "" + interface = self.descriptor.interface + toJSONCondition = PropertyDefiner.getControllingCondition( + self.toJSONMethod, self.descriptor + ) + needUnwrappedObj = False + for m in interface.members: + if m.isAttr() and not m.isStatic() and m.type.isJSONType(): + getAndDefine = fill( + """ + JS::Rooted<JS::Value> temp(cx); + if (!get_${name}(cx, obj, self, JSJitGetterCallArgs(&temp))) { + return false; + } + if (!JS_DefineProperty(cx, result, "${name}", temp, JSPROP_ENUMERATE)) { + return false; + } + """, + name=IDLToCIdentifier(m.identifier.name), + ) + # Make sure we don't include things which are supposed to be + # disabled. Things that either don't have disablers or whose + # disablers match the disablers for our toJSON method can't + # possibly be disabled, but other things might be. + condition = PropertyDefiner.getControllingCondition(m, self.descriptor) + if condition.hasDisablers() and condition != toJSONCondition: + needUnwrappedObj = True + ret += fill( + """ + // This is unfortunately a linear scan through sAttributes, but we + // only do it for things which _might_ be disabled, which should + // help keep the performance problems down. + if (IsGetterEnabled(cx, unwrappedObj, (JSJitGetterOp)get_${name}, sAttributes)) { + $*{getAndDefine} + } + """, + name=IDLToCIdentifier(m.identifier.name), + getAndDefine=getAndDefine, + ) + else: + ret += fill( + """ + { // scope for "temp" + $*{getAndDefine} + } + """, + getAndDefine=getAndDefine, + ) + ret += "return true;\n" + + if needUnwrappedObj: + # If we started allowing cross-origin objects here, we'd need to + # use CheckedUnwrapDynamic and figure out whether it makes sense. + # But in practice no one is trying to add toJSON methods to those, + # so let's just guard against it. + assert not self.descriptor.isMaybeCrossOriginObject() + ret = fill( + """ + JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrapStatic(obj)); + if (!unwrappedObj) { + // How did that happen? We managed to get called with that + // object as "this"! Just give up on sanity. + return false; + } + + $*{ret} + """, + ret=ret, + ) + + return ret + + +class CGCreateInterfaceObjectsMethod(CGAbstractMethod): + """ + Generate the CreateInterfaceObjects method for an interface descriptor. + + properties should be a PropertyArrays instance. + """ + + def __init__( + self, descriptor, properties, haveUnscopables, haveLegacyWindowAliases, static + ): + args = [ + Argument("JSContext*", "aCx"), + Argument("JS::Handle<JSObject*>", "aGlobal"), + Argument("ProtoAndIfaceCache&", "aProtoAndIfaceCache"), + Argument("bool", "aDefineOnGlobal"), + ] + CGAbstractMethod.__init__( + self, descriptor, "CreateInterfaceObjects", "void", args, static=static + ) + self.properties = properties + self.haveUnscopables = haveUnscopables + self.haveLegacyWindowAliases = haveLegacyWindowAliases + + def definition_body(self): + (protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter( + self.descriptor + ) + if protoHandleGetter is None: + parentProtoType = "Rooted" + getParentProto = "aCx, " + protoGetter + else: + parentProtoType = "Handle" + getParentProto = protoHandleGetter + getParentProto = getParentProto + "(aCx)" + + (protoGetter, protoHandleGetter) = InterfaceObjectProtoGetter(self.descriptor) + if protoHandleGetter is None: + getConstructorProto = "aCx, " + protoGetter + constructorProtoType = "Rooted" + else: + getConstructorProto = protoHandleGetter + constructorProtoType = "Handle" + getConstructorProto += "(aCx)" + + needInterfaceObject = self.descriptor.interface.hasInterfaceObject() + needInterfacePrototypeObject = ( + self.descriptor.interface.hasInterfacePrototypeObject() + ) + + # if we don't need to create anything, why are we generating this? + assert needInterfaceObject or needInterfacePrototypeObject + + getParentProto = fill( + """ + JS::${type}<JSObject*> parentProto(${getParentProto}); + if (!parentProto) { + return; + } + """, + type=parentProtoType, + getParentProto=getParentProto, + ) + + getConstructorProto = fill( + """ + JS::${type}<JSObject*> constructorProto(${getConstructorProto}); + if (!constructorProto) { + return; + } + """, + type=constructorProtoType, + getConstructorProto=getConstructorProto, + ) + + if self.descriptor.interface.ctor(): + constructArgs = methodLength(self.descriptor.interface.ctor()) + isConstructorChromeOnly = isChromeOnly(self.descriptor.interface.ctor()) + else: + constructArgs = 0 + isConstructorChromeOnly = False + if len(self.descriptor.interface.legacyFactoryFunctions) > 0: + namedConstructors = "namedConstructors" + else: + namedConstructors = "nullptr" + + if needInterfacePrototypeObject: + protoClass = "&sPrototypeClass.mBase" + protoCache = ( + "&aProtoAndIfaceCache.EntrySlotOrCreate(prototypes::id::%s)" + % self.descriptor.name + ) + parentProto = "parentProto" + getParentProto = CGGeneric(getParentProto) + else: + protoClass = "nullptr" + protoCache = "nullptr" + parentProto = "nullptr" + getParentProto = None + + if needInterfaceObject: + interfaceClass = "&sInterfaceObjectClass.mBase" + interfaceCache = ( + "&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)" + % self.descriptor.name + ) + getConstructorProto = CGGeneric(getConstructorProto) + constructorProto = "constructorProto" + else: + # We don't have slots to store the legacy factory functions. + assert len(self.descriptor.interface.legacyFactoryFunctions) == 0 + interfaceClass = "nullptr" + interfaceCache = "nullptr" + getConstructorProto = None + constructorProto = "nullptr" + + isGlobal = self.descriptor.isGlobal() is not None + if self.properties.hasNonChromeOnly(): + properties = "sNativeProperties.Upcast()" + else: + properties = "nullptr" + if self.properties.hasChromeOnly(): + chromeProperties = "sChromeOnlyNativeProperties.Upcast()" + else: + chromeProperties = "nullptr" + + # We use getClassName here. This should be the right thing to pass as + # the name argument to CreateInterfaceObjects. This is generally the + # interface identifier, except for the synthetic interfaces created for + # the default iterator objects. If needInterfaceObject is true then + # we'll use the name to install a property on the global object, so + # there shouldn't be any spaces in the name. + name = self.descriptor.interface.getClassName() + assert not (needInterfaceObject and " " in name) + + call = fill( + """ + JS::Heap<JSObject*>* protoCache = ${protoCache}; + JS::Heap<JSObject*>* interfaceCache = ${interfaceCache}; + dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto}, + ${protoClass}, protoCache, + ${constructorProto}, ${interfaceClass}, ${constructArgs}, ${isConstructorChromeOnly}, ${namedConstructors}, + interfaceCache, + ${properties}, + ${chromeProperties}, + "${name}", aDefineOnGlobal, + ${unscopableNames}, + ${isGlobal}, + ${legacyWindowAliases}, + ${isNamespace}); + """, + protoClass=protoClass, + parentProto=parentProto, + protoCache=protoCache, + constructorProto=constructorProto, + interfaceClass=interfaceClass, + constructArgs=constructArgs, + isConstructorChromeOnly=toStringBool(isConstructorChromeOnly), + namedConstructors=namedConstructors, + interfaceCache=interfaceCache, + properties=properties, + chromeProperties=chromeProperties, + name=name, + unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr", + isGlobal=toStringBool(isGlobal), + legacyWindowAliases="legacyWindowAliases" + if self.haveLegacyWindowAliases + else "nullptr", + isNamespace=toStringBool(self.descriptor.interface.isNamespace()), + ) + + # If we fail after here, we must clear interface and prototype caches + # using this code: intermediate failure must not expose the interface in + # partially-constructed state. Note that every case after here needs an + # interface prototype object. + failureCode = dedent( + """ + *protoCache = nullptr; + if (interfaceCache) { + *interfaceCache = nullptr; + } + return; + """ + ) + + needProtoVar = False + + aliasedMembers = [ + m for m in self.descriptor.interface.members if m.isMethod() and m.aliases + ] + if aliasedMembers: + assert needInterfacePrototypeObject + + def defineAlias(alias): + if alias == "@@iterator" or alias == "@@asyncIterator": + name = alias[2:] + + symbolJSID = ( + "JS::GetWellKnownSymbolKey(aCx, JS::SymbolCode::%s)" % name + ) + prop = "%sId" % name + getSymbolJSID = CGGeneric( + fill( + "JS::Rooted<jsid> ${prop}(aCx, ${symbolJSID});", + prop=prop, + symbolJSID=symbolJSID, + ) + ) + defineFn = "JS_DefinePropertyById" + enumFlags = "0" # Not enumerable, per spec. + elif alias.startswith("@@"): + raise TypeError( + "Can't handle any well-known Symbol other than @@iterator and @@asyncIterator" + ) + else: + getSymbolJSID = None + defineFn = "JS_DefineProperty" + prop = '"%s"' % alias + # XXX If we ever create non-enumerable properties that can + # be aliased, we should consider making the aliases + # match the enumerability of the property being aliased. + enumFlags = "JSPROP_ENUMERATE" + return CGList( + [ + getSymbolJSID, + CGGeneric( + fill( + """ + if (!${defineFn}(aCx, proto, ${prop}, aliasedVal, ${enumFlags})) { + $*{failureCode} + } + """, + defineFn=defineFn, + prop=prop, + enumFlags=enumFlags, + failureCode=failureCode, + ) + ), + ], + "\n", + ) + + def defineAliasesFor(m): + return CGList( + [ + CGGeneric( + fill( + """ + if (!JS_GetProperty(aCx, proto, \"${prop}\", &aliasedVal)) { + $*{failureCode} + } + """, + failureCode=failureCode, + prop=m.identifier.name, + ) + ) + ] + + [defineAlias(alias) for alias in sorted(m.aliases)] + ) + + defineAliases = CGList( + [ + CGGeneric( + dedent( + """ + // Set up aliases on the interface prototype object we just created. + """ + ) + ), + CGGeneric("JS::Rooted<JS::Value> aliasedVal(aCx);\n\n"), + ] + + [ + defineAliasesFor(m) + for m in sorted(aliasedMembers, key=lambda m: m.identifier.name) + ] + ) + needProtoVar = True + else: + defineAliases = None + + # Globals handle unforgeables directly in Wrap() instead of + # via a holder. + if ( + self.descriptor.hasLegacyUnforgeableMembers + and not self.descriptor.isGlobal() + ): + assert needInterfacePrototypeObject + + # We want to use the same JSClass and prototype as the object we'll + # end up defining the unforgeable properties on in the end, so that + # we can use JS_InitializePropertiesFromCompatibleNativeObject to do + # a fast copy. In the case of proxies that's null, because the + # expando object is a vanilla object, but in the case of other DOM + # objects it's whatever our class is. + if self.descriptor.proxy: + holderClass = "nullptr" + holderProto = "nullptr" + else: + holderClass = "sClass.ToJSClass()" + holderProto = "proto" + needProtoVar = True + createUnforgeableHolder = CGGeneric( + fill( + """ + JS::Rooted<JSObject*> unforgeableHolder( + aCx, JS_NewObjectWithoutMetadata(aCx, ${holderClass}, ${holderProto})); + if (!unforgeableHolder) { + $*{failureCode} + } + """, + holderProto=holderProto, + holderClass=holderClass, + failureCode=failureCode, + ) + ) + defineUnforgeables = InitUnforgeablePropertiesOnHolder( + self.descriptor, self.properties, failureCode + ) + createUnforgeableHolder = CGList( + [createUnforgeableHolder, defineUnforgeables] + ) + + installUnforgeableHolder = CGGeneric( + dedent( + """ + if (*protoCache) { + JS::SetReservedSlot(*protoCache, DOM_INTERFACE_PROTO_SLOTS_BASE, + JS::ObjectValue(*unforgeableHolder)); + } + """ + ) + ) + + unforgeableHolderSetup = CGList( + [createUnforgeableHolder, installUnforgeableHolder], "\n" + ) + else: + unforgeableHolderSetup = None + + # FIXME Unclear whether this is needed for hasOrdinaryObjectPrototype + if ( + self.descriptor.interface.isOnGlobalProtoChain() + and needInterfacePrototypeObject + and not self.descriptor.hasOrdinaryObjectPrototype + ): + makeProtoPrototypeImmutable = CGGeneric( + fill( + """ + { + bool succeeded; + if (!JS_SetImmutablePrototype(aCx, proto, &succeeded)) { + $*{failureCode} + } + + MOZ_ASSERT(succeeded, + "making a fresh prototype object's [[Prototype]] " + "immutable can internally fail, but it should " + "never be unsuccessful"); + } + """, + protoCache=protoCache, + failureCode=failureCode, + ) + ) + needProtoVar = True + else: + makeProtoPrototypeImmutable = None + + if needProtoVar: + defineProtoVar = CGGeneric( + fill( + """ + JS::AssertObjectIsNotGray(*protoCache); + JS::Handle<JSObject*> proto = JS::Handle<JSObject*>::fromMarkedLocation(protoCache->address()); + if (!proto) { + $*{failureCode} + } + """, + failureCode=failureCode, + ) + ) + else: + defineProtoVar = None + return CGList( + [ + getParentProto, + getConstructorProto, + CGGeneric(call), + defineProtoVar, + defineAliases, + unforgeableHolderSetup, + makeProtoPrototypeImmutable, + ], + "\n", + ).define() + + +class CGGetProtoObjectHandleMethod(CGAbstractMethod): + """ + A method for getting the interface prototype object. + """ + + def __init__(self, descriptor, static, signatureOnly=False): + CGAbstractMethod.__init__( + self, + descriptor, + "GetProtoObjectHandle", + "JS::Handle<JSObject*>", + [Argument("JSContext*", "aCx")], + inline=True, + static=static, + signatureOnly=signatureOnly, + ) + + def definition_body(self): + return fill( + """ + /* Get the interface prototype object for this class. This will create the + object as needed. */ + return GetPerInterfaceObjectHandle(aCx, prototypes::id::${name}, + &CreateInterfaceObjects, + /* aDefineOnGlobal = */ true); + + """, + name=self.descriptor.name, + ) + + +class CGGetProtoObjectMethod(CGAbstractMethod): + """ + A method for getting the interface prototype object. + """ + + def __init__(self, descriptor): + CGAbstractMethod.__init__( + self, + descriptor, + "GetProtoObject", + "JSObject*", + [Argument("JSContext*", "aCx")], + ) + + def definition_body(self): + return "return GetProtoObjectHandle(aCx);\n" + + +class CGGetConstructorObjectHandleMethod(CGAbstractMethod): + """ + A method for getting the interface constructor object. + """ + + def __init__(self, descriptor): + CGAbstractMethod.__init__( + self, + descriptor, + "GetConstructorObjectHandle", + "JS::Handle<JSObject*>", + [ + Argument("JSContext*", "aCx"), + Argument("bool", "aDefineOnGlobal", "true"), + ], + inline=True, + ) + + def definition_body(self): + return fill( + """ + /* Get the interface object for this class. This will create the object as + needed. */ + + return GetPerInterfaceObjectHandle(aCx, constructors::id::${name}, + &CreateInterfaceObjects, + aDefineOnGlobal); + """, + name=self.descriptor.name, + ) + + +class CGGetConstructorObjectMethod(CGAbstractMethod): + """ + A method for getting the interface constructor object. + """ + + def __init__(self, descriptor): + CGAbstractMethod.__init__( + self, + descriptor, + "GetConstructorObject", + "JSObject*", + [Argument("JSContext*", "aCx")], + ) + + def definition_body(self): + return "return GetConstructorObjectHandle(aCx);\n" + + +class CGGetNamedPropertiesObjectMethod(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [Argument("JSContext*", "aCx")] + CGAbstractStaticMethod.__init__( + self, descriptor, "GetNamedPropertiesObject", "JSObject*", args + ) + + def definition_body(self): + parentProtoName = self.descriptor.parentPrototypeName + if parentProtoName is None: + getParentProto = "" + parentProto = "nullptr" + else: + getParentProto = fill( + """ + JS::Rooted<JSObject*> parentProto(aCx, ${parent}::GetProtoObjectHandle(aCx)); + if (!parentProto) { + return nullptr; + } + """, + parent=toBindingNamespace(parentProtoName), + ) + parentProto = "parentProto" + return fill( + """ + /* Make sure our global is sane. Hopefully we can remove this sometime */ + JSObject* global = JS::CurrentGlobalOrNull(aCx); + if (!(JS::GetClass(global)->flags & JSCLASS_DOM_GLOBAL)) { + return nullptr; + } + + /* Check to see whether the named properties object has already been created */ + ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global); + + JS::Heap<JSObject*>& namedPropertiesObject = protoAndIfaceCache.EntrySlotOrCreate(namedpropertiesobjects::id::${ifaceName}); + if (!namedPropertiesObject) { + $*{getParentProto} + namedPropertiesObject = ${nativeType}::CreateNamedPropertiesObject(aCx, ${parentProto}); + DebugOnly<const DOMIfaceAndProtoJSClass*> clasp = + DOMIfaceAndProtoJSClass::FromJSClass(JS::GetClass(namedPropertiesObject)); + MOZ_ASSERT(clasp->mType == eNamedPropertiesObject, + "Expected ${nativeType}::CreateNamedPropertiesObject to return a named properties object"); + MOZ_ASSERT(clasp->mNativeHooks, + "The named properties object for ${nativeType} should have NativePropertyHooks."); + MOZ_ASSERT(!clasp->mNativeHooks->mResolveOwnProperty, + "Shouldn't resolve the properties of the named properties object for ${nativeType} for Xrays."); + MOZ_ASSERT(!clasp->mNativeHooks->mEnumerateOwnProperties, + "Shouldn't enumerate the properties of the named properties object for ${nativeType} for Xrays."); + } + return namedPropertiesObject.get(); + """, + getParentProto=getParentProto, + ifaceName=self.descriptor.name, + parentProto=parentProto, + nativeType=self.descriptor.nativeType, + ) + + +def getRawConditionList(idlobj, cxName, objName, ignoreSecureContext=False): + """ + Get the list of conditions for idlobj (to be used in "is this enabled" + checks). This will be returned as a CGList with " &&\n" as the separator, + for readability. + + objName is the name of the object that we're working with, because some of + our test functions want that. + + ignoreSecureContext is used only for constructors in which the WebIDL interface + itself is already marked as [SecureContext]. There is no need to do the work twice. + """ + conditions = [] + pref = idlobj.getExtendedAttribute("Pref") + if pref: + assert isinstance(pref, list) and len(pref) == 1 + conditions.append("StaticPrefs::%s()" % prefIdentifier(pref[0])) + if isChromeOnly(idlobj): + conditions.append("nsContentUtils::ThreadsafeIsSystemCaller(%s)" % cxName) + func = idlobj.getExtendedAttribute("Func") + if func: + assert isinstance(func, list) and len(func) == 1 + conditions.append("%s(%s, %s)" % (func[0], cxName, objName)) + trial = idlobj.getExtendedAttribute("Trial") + if trial: + assert isinstance(trial, list) and len(trial) == 1 + conditions.append( + "OriginTrials::IsEnabled(%s, %s, OriginTrial::%s)" + % (cxName, objName, trial[0]) + ) + if not ignoreSecureContext and idlobj.getExtendedAttribute("SecureContext"): + conditions.append( + "mozilla::dom::IsSecureContextOrObjectIsFromSecureContext(%s, %s)" + % (cxName, objName) + ) + return conditions + + +def getConditionList(idlobj, cxName, objName, ignoreSecureContext=False): + """ + Get the list of conditions from getRawConditionList + See comment on getRawConditionList above for more info about arguments. + + The return value is a possibly-empty conjunctive CGList of conditions. + """ + conditions = getRawConditionList(idlobj, cxName, objName, ignoreSecureContext) + return CGList((CGGeneric(cond) for cond in conditions), " &&\n") + + +class CGConstructorEnabled(CGAbstractMethod): + """ + A method for testing whether we should be exposing this interface object. + This can perform various tests depending on what conditions are specified + on the interface. + """ + + def __init__(self, descriptor): + CGAbstractMethod.__init__( + self, + descriptor, + "ConstructorEnabled", + "bool", + [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")], + ) + + def definition_body(self): + body = CGList([], "\n") + + iface = self.descriptor.interface + + if not iface.isExposedInWindow(): + exposedInWindowCheck = dedent( + """ + MOZ_ASSERT(!NS_IsMainThread(), "Why did we even get called?"); + """ + ) + body.append(CGGeneric(exposedInWindowCheck)) + + if iface.isExposedInSomeButNotAllWorkers(): + workerGlobals = sorted(iface.getWorkerExposureSet()) + workerCondition = CGList( + ( + CGGeneric('strcmp(name, "%s")' % workerGlobal) + for workerGlobal in workerGlobals + ), + " && ", + ) + exposedInWorkerCheck = fill( + """ + const char* name = JS::GetClass(aObj)->name; + if (${workerCondition}) { + return false; + } + """, + workerCondition=workerCondition.define(), + ) + exposedInWorkerCheck = CGGeneric(exposedInWorkerCheck) + if iface.isExposedInWindow(): + exposedInWorkerCheck = CGIfWrapper( + exposedInWorkerCheck, "!NS_IsMainThread()" + ) + body.append(exposedInWorkerCheck) + + conditions = getConditionList(iface, "aCx", "aObj") + + # We should really have some conditions + assert len(body) or len(conditions) + + conditionsWrapper = "" + if len(conditions): + conditionsWrapper = CGWrapper( + conditions, pre="return ", post=";\n", reindent=True + ) + else: + conditionsWrapper = CGGeneric("return true;\n") + + body.append(conditionsWrapper) + return body.define() + + +def StructuredCloneTag(name): + return "SCTAG_DOM_%s" % name.upper() + + +class CGSerializer(CGAbstractStaticMethod): + """ + Implementation of serialization for things marked [Serializable]. + This gets stored in our DOMJSClass, so it can be static. + + The caller is expected to pass in the object whose DOMJSClass it + used to get the serializer. + """ + + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "aCx"), + Argument("JSStructuredCloneWriter*", "aWriter"), + Argument("JS::Handle<JSObject*>", "aObj"), + ] + CGAbstractStaticMethod.__init__(self, descriptor, "Serialize", "bool", args) + + def definition_body(self): + return fill( + """ + MOZ_ASSERT(IsDOMObject(aObj), "Non-DOM object passed"); + MOZ_ASSERT(GetDOMClass(aObj)->mSerializer == &Serialize, + "Wrong object passed"); + return JS_WriteUint32Pair(aWriter, ${tag}, 0) && + UnwrapDOMObject<${type}>(aObj)->WriteStructuredClone(aCx, aWriter); + """, + tag=StructuredCloneTag(self.descriptor.name), + type=self.descriptor.nativeType, + ) + + +class CGDeserializer(CGAbstractMethod): + """ + Implementation of deserialization for things marked [Serializable]. + This will need to be accessed from WebIDLSerializable, so can't be static. + """ + + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "aCx"), + Argument("nsIGlobalObject*", "aGlobal"), + Argument("JSStructuredCloneReader*", "aReader"), + ] + CGAbstractMethod.__init__(self, descriptor, "Deserialize", "JSObject*", args) + + def definition_body(self): + # WrapObject has different signatures depending on whether + # the object is wrappercached. + if self.descriptor.wrapperCache: + wrapCall = dedent( + """ + result = obj->WrapObject(aCx, nullptr); + if (!result) { + return nullptr; + } + """ + ) + else: + wrapCall = dedent( + """ + if (!obj->WrapObject(aCx, nullptr, &result)) { + return nullptr; + } + """ + ) + + return fill( + """ + // Protect the result from a moving GC in ~RefPtr + JS::Rooted<JSObject*> result(aCx); + { // Scope for the RefPtr + RefPtr<${type}> obj = ${type}::ReadStructuredClone(aCx, aGlobal, aReader); + if (!obj) { + return nullptr; + } + $*{wrapCall} + } + return result; + """, + type=self.descriptor.nativeType, + wrapCall=wrapCall, + ) + + +def CreateBindingJSObject(descriptor): + objDecl = "BindingJSObjectCreator<%s> creator(aCx);\n" % descriptor.nativeType + + # We don't always need to root obj, but there are a variety + # of cases where we do, so for simplicity, just always root it. + if descriptor.proxy: + if descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"): + assert not descriptor.isMaybeCrossOriginObject() + create = dedent( + """ + aObject->mExpandoAndGeneration.expando.setUndefined(); + JS::Rooted<JS::Value> expandoValue(aCx, JS::PrivateValue(&aObject->mExpandoAndGeneration)); + creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(), + proto, /* aLazyProto = */ false, aObject, + expandoValue, aReflector); + """ + ) + else: + if descriptor.isMaybeCrossOriginObject(): + proto = "nullptr" + lazyProto = "true" + else: + proto = "proto" + lazyProto = "false" + create = fill( + """ + creator.CreateProxyObject(aCx, &sClass.mBase, DOMProxyHandler::getInstance(), + ${proto}, /* aLazyProto = */ ${lazyProto}, + aObject, JS::UndefinedHandleValue, aReflector); + """, + proto=proto, + lazyProto=lazyProto, + ) + else: + create = dedent( + """ + creator.CreateObject(aCx, sClass.ToJSClass(), proto, aObject, aReflector); + """ + ) + return ( + objDecl + + create + + dedent( + """ + if (!aReflector) { + return false; + } + """ + ) + ) + + +def InitUnforgeablePropertiesOnHolder( + descriptor, properties, failureCode, holderName="unforgeableHolder" +): + """ + Define the unforgeable properties on the unforgeable holder for + the interface represented by descriptor. + + properties is a PropertyArrays instance. + + """ + assert ( + properties.unforgeableAttrs.hasNonChromeOnly() + or properties.unforgeableAttrs.hasChromeOnly() + or properties.unforgeableMethods.hasNonChromeOnly() + or properties.unforgeableMethods.hasChromeOnly() + ) + + unforgeables = [] + + defineUnforgeableAttrs = fill( + """ + if (!DefineLegacyUnforgeableAttributes(aCx, ${holderName}, %s)) { + $*{failureCode} + } + """, + failureCode=failureCode, + holderName=holderName, + ) + defineUnforgeableMethods = fill( + """ + if (!DefineLegacyUnforgeableMethods(aCx, ${holderName}, %s)) { + $*{failureCode} + } + """, + failureCode=failureCode, + holderName=holderName, + ) + + unforgeableMembers = [ + (defineUnforgeableAttrs, properties.unforgeableAttrs), + (defineUnforgeableMethods, properties.unforgeableMethods), + ] + for (template, array) in unforgeableMembers: + if array.hasNonChromeOnly(): + unforgeables.append(CGGeneric(template % array.variableName(False))) + if array.hasChromeOnly(): + unforgeables.append( + CGIfWrapper( + CGGeneric(template % array.variableName(True)), + "nsContentUtils::ThreadsafeIsSystemCaller(aCx)", + ) + ) + + if descriptor.interface.getExtendedAttribute("LegacyUnforgeable"): + # We do our undefined toPrimitive here, not as a regular property + # because we don't have a concept of value props anywhere in IDL. + unforgeables.append( + CGGeneric( + fill( + """ + JS::Rooted<JS::PropertyKey> toPrimitive(aCx, + JS::GetWellKnownSymbolKey(aCx, JS::SymbolCode::toPrimitive)); + if (!JS_DefinePropertyById(aCx, ${holderName}, toPrimitive, + JS::UndefinedHandleValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { + $*{failureCode} + } + """, + failureCode=failureCode, + holderName=holderName, + ) + ) + ) + + return CGWrapper(CGList(unforgeables), pre="\n") + + +def CopyUnforgeablePropertiesToInstance(descriptor, failureCode): + """ + Copy the unforgeable properties from the unforgeable holder for + this interface to the instance object we have. + """ + assert not descriptor.isGlobal() + + if not descriptor.hasLegacyUnforgeableMembers: + return "" + + copyCode = [ + CGGeneric( + dedent( + """ + // Important: do unforgeable property setup after we have handed + // over ownership of the C++ object to obj as needed, so that if + // we fail and it ends up GCed it won't have problems in the + // finalizer trying to drop its ownership of the C++ object. + """ + ) + ) + ] + + # For proxies, we want to define on the expando object, not directly on the + # reflector, so we can make sure we don't get confused by named getters. + if descriptor.proxy: + copyCode.append( + CGGeneric( + fill( + """ + JS::Rooted<JSObject*> expando(aCx, + DOMProxyHandler::EnsureExpandoObject(aCx, aReflector)); + if (!expando) { + $*{failureCode} + } + """, + failureCode=failureCode, + ) + ) + ) + obj = "expando" + else: + obj = "aReflector" + + copyCode.append( + CGGeneric( + fill( + """ + JS::Rooted<JSObject*> unforgeableHolder(aCx, + &JS::GetReservedSlot(canonicalProto, DOM_INTERFACE_PROTO_SLOTS_BASE).toObject()); + if (!JS_InitializePropertiesFromCompatibleNativeObject(aCx, ${obj}, unforgeableHolder)) { + $*{failureCode} + } + """, + obj=obj, + failureCode=failureCode, + ) + ) + ) + + return CGWrapper(CGList(copyCode), pre="\n").define() + + +def AssertInheritanceChain(descriptor): + # We can skip the reinterpret_cast check for the descriptor's nativeType + # if aObject is a pointer of that type. + asserts = fill( + """ + static_assert(std::is_same_v<decltype(aObject), ${nativeType}*>); + """, + nativeType=descriptor.nativeType, + ) + iface = descriptor.interface + while iface.parent: + iface = iface.parent + desc = descriptor.getDescriptor(iface.identifier.name) + asserts += ( + "MOZ_ASSERT(static_cast<%s*>(aObject) == \n" + " reinterpret_cast<%s*>(aObject),\n" + ' "Multiple inheritance for %s is broken.");\n' + % (desc.nativeType, desc.nativeType, desc.nativeType) + ) + asserts += "MOZ_ASSERT(ToSupportsIsCorrect(aObject));\n" + return asserts + + +def InitMemberSlots(descriptor, failureCode): + """ + Initialize member slots on our JS object if we're supposed to have some. + + Note that this is called after the SetWrapper() call in the + wrapperCache case, since that can affect how our getters behave + and we plan to invoke them here. So if we fail, we need to + ClearWrapper. + """ + if not descriptor.interface.hasMembersInSlots(): + return "" + return fill( + """ + if (!UpdateMemberSlots(aCx, aReflector, aObject)) { + $*{failureCode} + } + """, + failureCode=failureCode, + ) + + +def DeclareProto(descriptor, noGivenProto=False): + """ + Declare the canonicalProto and proto we have for our wrapping operation. + """ + getCanonical = dedent( + """ + JS::Handle<JSObject*> ${canonicalProto} = GetProtoObjectHandle(aCx); + if (!${canonicalProto}) { + return false; + } + """ + ) + + if noGivenProto: + return fill(getCanonical, canonicalProto="proto") + + getCanonical = fill(getCanonical, canonicalProto="canonicalProto") + + preamble = getCanonical + dedent( + """ + JS::Rooted<JSObject*> proto(aCx); + """ + ) + if descriptor.isMaybeCrossOriginObject(): + return preamble + dedent( + """ + MOZ_ASSERT(!aGivenProto, + "Shouldn't have constructors on cross-origin objects"); + // Set proto to canonicalProto to avoid preserving our wrapper if + // we don't have to. + proto = canonicalProto; + """ + ) + + return preamble + dedent( + """ + if (aGivenProto) { + proto = aGivenProto; + // Unfortunately, while aGivenProto was in the compartment of aCx + // coming in, we changed compartments to that of "parent" so may need + // to wrap the proto here. + if (js::GetContextCompartment(aCx) != JS::GetCompartment(proto)) { + if (!JS_WrapObject(aCx, &proto)) { + return false; + } + } + } else { + proto = canonicalProto; + } + """ + ) + + +class CGWrapWithCacheMethod(CGAbstractMethod): + """ + Create a wrapper JSObject for a given native that implements nsWrapperCache. + """ + + def __init__(self, descriptor): + assert descriptor.interface.hasInterfacePrototypeObject() + args = [ + Argument("JSContext*", "aCx"), + Argument(descriptor.nativeType + "*", "aObject"), + Argument("nsWrapperCache*", "aCache"), + Argument("JS::Handle<JSObject*>", "aGivenProto"), + Argument("JS::MutableHandle<JSObject*>", "aReflector"), + ] + CGAbstractMethod.__init__(self, descriptor, "Wrap", "bool", args) + + def definition_body(self): + failureCode = dedent( + """ + aCache->ReleaseWrapper(aObject); + aCache->ClearWrapper(); + return false; + """ + ) + + if self.descriptor.proxy: + finalize = "DOMProxyHandler::getInstance()->finalize" + else: + finalize = FINALIZE_HOOK_NAME + + return fill( + """ + static_assert(!std::is_base_of_v<NonRefcountedDOMObject, ${nativeType}>, + "Shouldn't have wrappercached things that are not refcounted."); + $*{assertInheritance} + MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx)); + MOZ_ASSERT(!aCache->GetWrapper(), + "You should probably not be using Wrap() directly; use " + "GetOrCreateDOMReflector instead"); + + MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache), + "nsISupports must be on our primary inheritance chain"); + + // If the wrapper cache contains a dead reflector then finalize that + // now, ensuring that the finalizer for the old reflector always + // runs before the new reflector is created and attached. This + // avoids the awkward situation where there are multiple reflector + // objects that contain pointers to the same native. + + if (JSObject* oldReflector = aCache->GetWrapperMaybeDead()) { + ${finalize}(nullptr /* unused */, oldReflector); + MOZ_ASSERT(!aCache->GetWrapperMaybeDead()); + } + + JS::Rooted<JSObject*> global(aCx, FindAssociatedGlobal(aCx, aObject->GetParentObject())); + if (!global) { + return false; + } + MOZ_ASSERT(JS_IsGlobalObject(global)); + JS::AssertObjectIsNotGray(global); + + // That might have ended up wrapping us already, due to the wonders + // of XBL. Check for that, and bail out as needed. + aReflector.set(aCache->GetWrapper()); + if (aReflector) { + #ifdef DEBUG + AssertReflectorHasGivenProto(aCx, aReflector, aGivenProto); + #endif // DEBUG + return true; + } + + JSAutoRealm ar(aCx, global); + $*{declareProto} + + $*{createObject} + + aCache->SetWrapper(aReflector); + $*{unforgeable} + $*{slots} + creator.InitializationSucceeded(); + + MOZ_ASSERT(aCache->GetWrapperPreserveColor() && + aCache->GetWrapperPreserveColor() == aReflector); + // If proto != canonicalProto, we have to preserve our wrapper; + // otherwise we won't be able to properly recreate it later, since + // we won't know what proto to use. Note that we don't check + // aGivenProto here, since it's entirely possible (and even + // somewhat common) to have a non-null aGivenProto which is the + // same as canonicalProto. + if (proto != canonicalProto) { + PreserveWrapper(aObject); + } + + return true; + """, + nativeType=self.descriptor.nativeType, + assertInheritance=AssertInheritanceChain(self.descriptor), + declareProto=DeclareProto(self.descriptor), + createObject=CreateBindingJSObject(self.descriptor), + unforgeable=CopyUnforgeablePropertiesToInstance( + self.descriptor, failureCode + ), + slots=InitMemberSlots(self.descriptor, failureCode), + finalize=finalize, + ) + + +class CGWrapMethod(CGAbstractMethod): + def __init__(self, descriptor): + # XXX can we wrap if we don't have an interface prototype object? + assert descriptor.interface.hasInterfacePrototypeObject() + args = [ + Argument("JSContext*", "aCx"), + Argument("T*", "aObject"), + Argument("JS::Handle<JSObject*>", "aGivenProto"), + ] + CGAbstractMethod.__init__( + self, + descriptor, + "Wrap", + "JSObject*", + args, + inline=True, + templateArgs=["class T"], + ) + + def definition_body(self): + return dedent( + """ + JS::Rooted<JSObject*> reflector(aCx); + return Wrap(aCx, aObject, aObject, aGivenProto, &reflector) ? reflector.get() : nullptr; + """ + ) + + +class CGWrapNonWrapperCacheMethod(CGAbstractMethod): + """ + Create a wrapper JSObject for a given native that does not implement + nsWrapperCache. + """ + + def __init__(self, descriptor, static=False, signatureOnly=False): + # XXX can we wrap if we don't have an interface prototype object? + assert descriptor.interface.hasInterfacePrototypeObject() + self.noGivenProto = ( + descriptor.interface.isIteratorInterface() + or descriptor.interface.isAsyncIteratorInterface() + ) + args = [ + Argument("JSContext*", "aCx"), + Argument(descriptor.nativeType + "*", "aObject"), + ] + if not self.noGivenProto: + args.append(Argument("JS::Handle<JSObject*>", "aGivenProto")) + args.append(Argument("JS::MutableHandle<JSObject*>", "aReflector")) + CGAbstractMethod.__init__( + self, + descriptor, + "Wrap", + "bool", + args, + static=static, + signatureOnly=signatureOnly, + ) + + def definition_body(self): + failureCode = "return false;\n" + + declareProto = DeclareProto(self.descriptor, noGivenProto=self.noGivenProto) + if self.noGivenProto: + assertGivenProto = "" + else: + assertGivenProto = dedent( + """ + MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx)); + """ + ) + return fill( + """ + $*{assertions} + $*{assertGivenProto} + + JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); + $*{declareProto} + + $*{createObject} + + $*{unforgeable} + + $*{slots} + + creator.InitializationSucceeded(); + return true; + """, + assertions=AssertInheritanceChain(self.descriptor), + assertGivenProto=assertGivenProto, + declareProto=declareProto, + createObject=CreateBindingJSObject(self.descriptor), + unforgeable=CopyUnforgeablePropertiesToInstance( + self.descriptor, failureCode + ), + slots=InitMemberSlots(self.descriptor, failureCode), + ) + + +class CGWrapGlobalMethod(CGAbstractMethod): + """ + Create a wrapper JSObject for a global. The global must implement + nsWrapperCache. + + properties should be a PropertyArrays instance. + """ + + def __init__(self, descriptor, properties): + assert descriptor.interface.hasInterfacePrototypeObject() + args = [ + Argument("JSContext*", "aCx"), + Argument(descriptor.nativeType + "*", "aObject"), + Argument("nsWrapperCache*", "aCache"), + Argument("JS::RealmOptions&", "aOptions"), + Argument("JSPrincipals*", "aPrincipal"), + Argument("bool", "aInitStandardClasses"), + Argument("JS::MutableHandle<JSObject*>", "aReflector"), + ] + CGAbstractMethod.__init__(self, descriptor, "Wrap", "bool", args) + self.descriptor = descriptor + self.properties = properties + + def definition_body(self): + if self.properties.hasNonChromeOnly(): + properties = "sNativeProperties.Upcast()" + else: + properties = "nullptr" + if self.properties.hasChromeOnly(): + chromeProperties = "nsContentUtils::ThreadsafeIsSystemCaller(aCx) ? sChromeOnlyNativeProperties.Upcast() : nullptr" + else: + chromeProperties = "nullptr" + + failureCode = dedent( + """ + aCache->ReleaseWrapper(aObject); + aCache->ClearWrapper(); + return false; + """ + ) + + if self.descriptor.hasLegacyUnforgeableMembers: + unforgeable = InitUnforgeablePropertiesOnHolder( + self.descriptor, self.properties, failureCode, "aReflector" + ).define() + else: + unforgeable = "" + + if self.descriptor.hasOrdinaryObjectPrototype: + getProto = "JS::GetRealmObjectPrototypeHandle" + else: + getProto = "GetProtoObjectHandle" + return fill( + """ + $*{assertions} + MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache), + "nsISupports must be on our primary inheritance chain"); + + if (!CreateGlobal<${nativeType}, ${getProto}>(aCx, + aObject, + aCache, + sClass.ToJSClass(), + aOptions, + aPrincipal, + aInitStandardClasses, + aReflector)) { + $*{failureCode} + } + + // aReflector is a new global, so has a new realm. Enter it + // before doing anything with it. + JSAutoRealm ar(aCx, aReflector); + + if (!DefineProperties(aCx, aReflector, ${properties}, ${chromeProperties})) { + $*{failureCode} + } + $*{unforgeable} + + $*{slots} + + return true; + """, + assertions=AssertInheritanceChain(self.descriptor), + nativeType=self.descriptor.nativeType, + getProto=getProto, + properties=properties, + chromeProperties=chromeProperties, + failureCode=failureCode, + unforgeable=unforgeable, + slots=InitMemberSlots(self.descriptor, failureCode), + ) + + +class CGUpdateMemberSlotsMethod(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "aCx"), + Argument("JS::Handle<JSObject*>", "aWrapper"), + Argument(descriptor.nativeType + "*", "aObject"), + ] + CGAbstractStaticMethod.__init__( + self, descriptor, "UpdateMemberSlots", "bool", args + ) + + def definition_body(self): + body = "JS::Rooted<JS::Value> temp(aCx);\n" "JSJitGetterCallArgs args(&temp);\n" + for m in self.descriptor.interface.members: + if m.isAttr() and m.getExtendedAttribute("StoreInSlot"): + # Skip doing this for the "window" and "self" attributes on the + # Window interface, because those can't be gotten safely until + # we have hooked it up correctly to the outer window. The + # window code handles doing the get itself. + if self.descriptor.interface.identifier.name == "Window" and ( + m.identifier.name == "window" or m.identifier.name == "self" + ): + continue + body += fill( + """ + + static_assert(${slot} < JS::shadow::Object::MAX_FIXED_SLOTS, + "Not enough fixed slots to fit '${interface}.${member}. Ion's visitGetDOMMemberV/visitGetDOMMemberT assume StoreInSlot things are all in fixed slots."); + if (!get_${member}(aCx, aWrapper, aObject, args)) { + return false; + } + // Getter handled setting our reserved slots + """, + slot=memberReservedSlot(m, self.descriptor), + interface=self.descriptor.interface.identifier.name, + member=m.identifier.name, + ) + + body += "\nreturn true;\n" + return body + + +class CGClearCachedValueMethod(CGAbstractMethod): + def __init__(self, descriptor, member): + self.member = member + # If we're StoreInSlot, we'll need to call the getter + if member.getExtendedAttribute("StoreInSlot"): + args = [Argument("JSContext*", "aCx")] + returnType = "bool" + else: + args = [] + returnType = "void" + args.append(Argument(descriptor.nativeType + "*", "aObject")) + name = MakeClearCachedValueNativeName(member) + CGAbstractMethod.__init__(self, descriptor, name, returnType, args) + + def definition_body(self): + slotIndex = memberReservedSlot(self.member, self.descriptor) + if self.member.getExtendedAttribute("StoreInSlot"): + # We have to root things and save the old value in case + # regetting fails, so we can restore it. + declObj = "JS::Rooted<JSObject*> obj(aCx);\n" + noopRetval = " true" + saveMember = ( + "JS::Rooted<JS::Value> oldValue(aCx, JS::GetReservedSlot(obj, %s));\n" + % slotIndex + ) + regetMember = fill( + """ + JS::Rooted<JS::Value> temp(aCx); + JSJitGetterCallArgs args(&temp); + JSAutoRealm ar(aCx, obj); + if (!get_${name}(aCx, obj, aObject, args)) { + JS::SetReservedSlot(obj, ${slotIndex}, oldValue); + return false; + } + return true; + """, + name=self.member.identifier.name, + slotIndex=slotIndex, + ) + else: + declObj = "JSObject* obj;\n" + noopRetval = "" + saveMember = "" + regetMember = "" + + if self.descriptor.wantsXrays: + clearXrayExpandoSlots = fill( + """ + xpc::ClearXrayExpandoSlots(obj, ${xraySlotIndex}); + """, + xraySlotIndex=memberXrayExpandoReservedSlot( + self.member, self.descriptor + ), + ) + else: + clearXrayExpandoSlots = "" + + return fill( + """ + $*{declObj} + obj = aObject->GetWrapper(); + if (!obj) { + return${noopRetval}; + } + $*{saveMember} + JS::SetReservedSlot(obj, ${slotIndex}, JS::UndefinedValue()); + $*{clearXrayExpandoSlots} + $*{regetMember} + """, + declObj=declObj, + noopRetval=noopRetval, + saveMember=saveMember, + slotIndex=slotIndex, + clearXrayExpandoSlots=clearXrayExpandoSlots, + regetMember=regetMember, + ) + + +class CGCrossOriginProperties(CGThing): + def __init__(self, descriptor): + attrs = [] + chromeOnlyAttrs = [] + methods = [] + chromeOnlyMethods = [] + for m in descriptor.interface.members: + if m.isAttr() and ( + m.getExtendedAttribute("CrossOriginReadable") + or m.getExtendedAttribute("CrossOriginWritable") + ): + if m.isStatic(): + raise TypeError( + "Don't know how to deal with static method %s" + % m.identifier.name + ) + if PropertyDefiner.getControllingCondition( + m, descriptor + ).hasDisablers(): + raise TypeError( + "Don't know how to deal with disabler for %s" + % m.identifier.name + ) + if len(m.bindingAliases) > 0: + raise TypeError( + "Don't know how to deal with aliases for %s" % m.identifier.name + ) + if m.getExtendedAttribute("ChromeOnly") is not None: + chromeOnlyAttrs.extend(AttrDefiner.attrData(m, overrideFlags="0")) + else: + attrs.extend(AttrDefiner.attrData(m, overrideFlags="0")) + elif m.isMethod() and m.getExtendedAttribute("CrossOriginCallable"): + if m.isStatic(): + raise TypeError( + "Don't know how to deal with static method %s" + % m.identifier.name + ) + if PropertyDefiner.getControllingCondition( + m, descriptor + ).hasDisablers(): + raise TypeError( + "Don't know how to deal with disabler for %s" + % m.identifier.name + ) + if len(m.aliases) > 0: + raise TypeError( + "Don't know how to deal with aliases for %s" % m.identifier.name + ) + if m.getExtendedAttribute("ChromeOnly") is not None: + chromeOnlyMethods.append( + MethodDefiner.methodData( + m, descriptor, overrideFlags="JSPROP_READONLY" + ) + ) + else: + methods.append( + MethodDefiner.methodData( + m, descriptor, overrideFlags="JSPROP_READONLY" + ) + ) + + if len(attrs) > 0: + self.attributeSpecs, _ = PropertyDefiner.generatePrefableArrayValues( + attrs, + descriptor, + AttrDefiner.formatSpec, + " JS_PS_END\n", + AttrDefiner.condition, + functools.partial(AttrDefiner.specData, crossOriginOnly=True), + ) + else: + self.attributeSpecs = [" JS_PS_END\n"] + if len(methods) > 0: + self.methodSpecs, _ = PropertyDefiner.generatePrefableArrayValues( + methods, + descriptor, + MethodDefiner.formatSpec, + " JS_FS_END\n", + MethodDefiner.condition, + MethodDefiner.specData, + ) + else: + self.methodSpecs = [" JS_FS_END\n"] + + if len(chromeOnlyAttrs) > 0: + ( + self.chromeOnlyAttributeSpecs, + _, + ) = PropertyDefiner.generatePrefableArrayValues( + chromeOnlyAttrs, + descriptor, + AttrDefiner.formatSpec, + " JS_PS_END\n", + AttrDefiner.condition, + functools.partial(AttrDefiner.specData, crossOriginOnly=True), + ) + else: + self.chromeOnlyAttributeSpecs = [] + if len(chromeOnlyMethods) > 0: + self.chromeOnlyMethodSpecs, _ = PropertyDefiner.generatePrefableArrayValues( + chromeOnlyMethods, + descriptor, + MethodDefiner.formatSpec, + " JS_FS_END\n", + MethodDefiner.condition, + MethodDefiner.specData, + ) + else: + self.chromeOnlyMethodSpecs = [] + + def declare(self): + return dedent( + """ + extern const CrossOriginProperties sCrossOriginProperties; + """ + ) + + def define(self): + def defineChromeOnly(name, specs, specType): + if len(specs) == 0: + return ("", "nullptr") + name = "sChromeOnlyCrossOrigin" + name + define = fill( + """ + static const ${specType} ${name}[] = { + $*{specs} + }; + """, + specType=specType, + name=name, + specs=",\n".join(specs), + ) + return (define, name) + + chromeOnlyAttributes = defineChromeOnly( + "Attributes", self.chromeOnlyAttributeSpecs, "JSPropertySpec" + ) + chromeOnlyMethods = defineChromeOnly( + "Methods", self.chromeOnlyMethodSpecs, "JSFunctionSpec" + ) + return fill( + """ + static const JSPropertySpec sCrossOriginAttributes[] = { + $*{attributeSpecs} + }; + static const JSFunctionSpec sCrossOriginMethods[] = { + $*{methodSpecs} + }; + $*{chromeOnlyAttributeSpecs} + $*{chromeOnlyMethodSpecs} + const CrossOriginProperties sCrossOriginProperties = { + sCrossOriginAttributes, + sCrossOriginMethods, + ${chromeOnlyAttributes}, + ${chromeOnlyMethods} + }; + """, + attributeSpecs=",\n".join(self.attributeSpecs), + methodSpecs=",\n".join(self.methodSpecs), + chromeOnlyAttributeSpecs=chromeOnlyAttributes[0], + chromeOnlyMethodSpecs=chromeOnlyMethods[0], + chromeOnlyAttributes=chromeOnlyAttributes[1], + chromeOnlyMethods=chromeOnlyMethods[1], + ) + + +class CGCycleCollectionTraverseForOwningUnionMethod(CGAbstractMethod): + """ + ImplCycleCollectionUnlink for owning union type. + """ + + def __init__(self, type): + self.type = type + args = [ + Argument("nsCycleCollectionTraversalCallback&", "aCallback"), + Argument("%s&" % CGUnionStruct.unionTypeName(type, True), "aUnion"), + Argument("const char*", "aName"), + Argument("uint32_t", "aFlags", "0"), + ] + CGAbstractMethod.__init__( + self, None, "ImplCycleCollectionTraverse", "void", args + ) + + def deps(self): + return self.type.getDeps() + + def definition_body(self): + memberNames = [ + getUnionMemberName(t) + for t in self.type.flatMemberTypes + if idlTypeNeedsCycleCollection(t) + ] + assert memberNames + + conditionTemplate = "aUnion.Is%s()" + functionCallTemplate = ( + 'ImplCycleCollectionTraverse(aCallback, aUnion.GetAs%s(), "m%s", aFlags);\n' + ) + + ifStaments = ( + CGIfWrapper(CGGeneric(functionCallTemplate % (m, m)), conditionTemplate % m) + for m in memberNames + ) + + return CGElseChain(ifStaments).define() + + +class CGCycleCollectionUnlinkForOwningUnionMethod(CGAbstractMethod): + """ + ImplCycleCollectionUnlink for owning union type. + """ + + def __init__(self, type): + self.type = type + args = [Argument("%s&" % CGUnionStruct.unionTypeName(type, True), "aUnion")] + CGAbstractMethod.__init__(self, None, "ImplCycleCollectionUnlink", "void", args) + + def deps(self): + return self.type.getDeps() + + def definition_body(self): + return "aUnion.Uninit();\n" + + +builtinNames = { + IDLType.Tags.bool: "bool", + IDLType.Tags.int8: "int8_t", + IDLType.Tags.int16: "int16_t", + IDLType.Tags.int32: "int32_t", + IDLType.Tags.int64: "int64_t", + IDLType.Tags.uint8: "uint8_t", + IDLType.Tags.uint16: "uint16_t", + IDLType.Tags.uint32: "uint32_t", + IDLType.Tags.uint64: "uint64_t", + IDLType.Tags.unrestricted_float: "float", + IDLType.Tags.float: "float", + IDLType.Tags.unrestricted_double: "double", + IDLType.Tags.double: "double", +} + +numericSuffixes = { + IDLType.Tags.int8: "", + IDLType.Tags.uint8: "", + IDLType.Tags.int16: "", + IDLType.Tags.uint16: "", + IDLType.Tags.int32: "", + IDLType.Tags.uint32: "U", + IDLType.Tags.int64: "LL", + IDLType.Tags.uint64: "ULL", + IDLType.Tags.unrestricted_float: "F", + IDLType.Tags.float: "F", + IDLType.Tags.unrestricted_double: "", + IDLType.Tags.double: "", +} + + +def numericValue(t, v): + if t == IDLType.Tags.unrestricted_double or t == IDLType.Tags.unrestricted_float: + typeName = builtinNames[t] + if v == float("inf"): + return "mozilla::PositiveInfinity<%s>()" % typeName + if v == float("-inf"): + return "mozilla::NegativeInfinity<%s>()" % typeName + if math.isnan(v): + return "mozilla::UnspecifiedNaN<%s>()" % typeName + return "%s%s" % (v, numericSuffixes[t]) + + +class CastableObjectUnwrapper: + """ + A class for unwrapping an object stored in a JS Value (or + MutableHandle<Value> or Handle<Value>) named by the "source" and + "mutableSource" arguments based on the passed-in descriptor and storing it + in a variable called by the name in the "target" argument. The "source" + argument should be able to produce a Value or Handle<Value>; the + "mutableSource" argument should be able to produce a MutableHandle<Value> + + codeOnFailure is the code to run if unwrapping fails. + + If isCallbackReturnValue is "JSImpl" and our descriptor is also + JS-implemented, fall back to just creating the right object if what we + have isn't one already. + """ + + def __init__( + self, + descriptor, + source, + mutableSource, + target, + codeOnFailure, + exceptionCode=None, + isCallbackReturnValue=False, + ): + self.substitution = { + "type": descriptor.nativeType, + "protoID": "prototypes::id::" + descriptor.name, + "target": target, + "codeOnFailure": codeOnFailure, + "source": source, + "mutableSource": mutableSource, + } + + if isCallbackReturnValue == "JSImpl" and descriptor.interface.isJSImplemented(): + exceptionCode = exceptionCode or codeOnFailure + self.substitution["codeOnFailure"] = fill( + """ + // Be careful to not wrap random DOM objects here, even if + // they're wrapped in opaque security wrappers for some reason. + // XXXbz Wish we could check for a JS-implemented object + // that already has a content reflection... + if (!IsDOMObject(js::UncheckedUnwrap(&${source}.toObject()))) { + nsCOMPtr<nsIGlobalObject> contentGlobal; + JS::Rooted<JSObject*> callback(cx, CallbackOrNull()); + if (!callback || + !GetContentGlobalForJSImplementedObject(cx, callback, getter_AddRefs(contentGlobal))) { + $*{exceptionCode} + } + JS::Rooted<JSObject*> jsImplSourceObj(cx, &${source}.toObject()); + MOZ_RELEASE_ASSERT(!js::IsWrapper(jsImplSourceObj), + "Don't return JS implementations from other compartments"); + JS::Rooted<JSObject*> jsImplSourceGlobal(cx, JS::GetNonCCWObjectGlobal(jsImplSourceObj)); + ${target} = new ${type}(jsImplSourceObj, jsImplSourceGlobal, contentGlobal); + } else { + $*{codeOnFailure} + } + """, + exceptionCode=exceptionCode, + **self.substitution, + ) + else: + self.substitution["codeOnFailure"] = codeOnFailure + + def __str__(self): + substitution = self.substitution.copy() + substitution["codeOnFailure"] %= { + "securityError": "rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO" + } + return fill( + """ + { + // Our JSContext should be in the right global to do unwrapping in. + nsresult rv = UnwrapObject<${protoID}, ${type}>(${mutableSource}, ${target}, cx); + if (NS_FAILED(rv)) { + $*{codeOnFailure} + } + } + """, + **substitution, + ) + + +class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper): + """ + As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails + """ + + def __init__( + self, + descriptor, + source, + mutableSource, + target, + exceptionCode, + isCallbackReturnValue, + sourceDescription, + ): + CastableObjectUnwrapper.__init__( + self, + descriptor, + source, + mutableSource, + target, + 'cx.ThrowErrorMessage<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("%s", "%s");\n' + "%s" + % (sourceDescription, descriptor.interface.identifier.name, exceptionCode), + exceptionCode, + isCallbackReturnValue, + ) + + +def getCallbackConversionInfo( + type, idlObject, isMember, isCallbackReturnValue, isOptional +): + """ + Returns a tuple containing the declType, declArgs, and basic + conversion for the given callback type, with the given callback + idl object in the given context (isMember/isCallbackReturnValue/isOptional). + """ + name = idlObject.identifier.name + + # We can't use fast callbacks if isOptional because then we get an + # Optional<RootedCallback> thing, which is not transparent to consumers. + useFastCallback = ( + (not isMember or isMember == "Union") + and not isCallbackReturnValue + and not isOptional + ) + if useFastCallback: + name = "binding_detail::Fast%s" % name + rootArgs = "" + args = "&${val}.toObject(), JS::CurrentGlobalOrNull(cx)" + else: + rootArgs = dedent( + """ + JS::Rooted<JSObject*> tempRoot(cx, &${val}.toObject()); + JS::Rooted<JSObject*> tempGlobalRoot(cx, JS::CurrentGlobalOrNull(cx)); + """ + ) + args = "cx, tempRoot, tempGlobalRoot, GetIncumbentGlobal()" + + if type.nullable() or isCallbackReturnValue: + declType = CGGeneric("RefPtr<%s>" % name) + else: + declType = CGGeneric("OwningNonNull<%s>" % name) + + if useFastCallback: + declType = CGTemplatedType("RootedCallback", declType) + declArgs = "cx" + else: + declArgs = None + + conversion = fill( + """ + { // scope for tempRoot and tempGlobalRoot if needed + $*{rootArgs} + $${declName} = new ${name}(${args}); + } + """, + rootArgs=rootArgs, + name=name, + args=args, + ) + return (declType, declArgs, conversion) + + +class JSToNativeConversionInfo: + """ + An object representing information about a JS-to-native conversion. + """ + + def __init__( + self, + template, + declType=None, + holderType=None, + dealWithOptional=False, + declArgs=None, + holderArgs=None, + ): + """ + template: A string representing the conversion code. This will have + template substitution performed on it as follows: + + ${val} is a handle to the JS::Value in question + ${maybeMutableVal} May be a mutable handle to the JS::Value in + question. This is only OK to use if ${val} is + known to not be undefined. + ${holderName} replaced by the holder's name, if any + ${declName} replaced by the declaration's name + ${haveValue} replaced by an expression that evaluates to a boolean + for whether we have a JS::Value. Only used when + defaultValue is not None or when True is passed for + checkForValue to instantiateJSToNativeConversion. + This expression may not be already-parenthesized, so if + you use it with && or || make sure to put parens + around it. + ${passedToJSImpl} replaced by an expression that evaluates to a boolean + for whether this value is being passed to a JS- + implemented interface. + + declType: A CGThing representing the native C++ type we're converting + to. This is allowed to be None if the conversion code is + supposed to be used as-is. + + holderType: A CGThing representing the type of a "holder" which will + hold a possible reference to the C++ thing whose type we + returned in declType, or None if no such holder is needed. + + dealWithOptional: A boolean indicating whether the caller has to do + optional-argument handling. This should only be set + to true if the JS-to-native conversion is being done + for an optional argument or dictionary member with no + default value and if the returned template expects + both declType and holderType to be wrapped in + Optional<>, with ${declName} and ${holderName} + adjusted to point to the Value() of the Optional, and + Construct() calls to be made on the Optional<>s as + needed. + + declArgs: If not None, the arguments to pass to the ${declName} + constructor. These will have template substitution performed + on them so you can use things like ${val}. This is a + single string, not a list of strings. + + holderArgs: If not None, the arguments to pass to the ${holderName} + constructor. These will have template substitution + performed on them so you can use things like ${val}. + This is a single string, not a list of strings. + + ${declName} must be in scope before the code from 'template' is entered. + + If holderType is not None then ${holderName} must be in scope before + the code from 'template' is entered. + """ + assert isinstance(template, str) + assert declType is None or isinstance(declType, CGThing) + assert holderType is None or isinstance(holderType, CGThing) + self.template = template + self.declType = declType + self.holderType = holderType + self.dealWithOptional = dealWithOptional + self.declArgs = declArgs + self.holderArgs = holderArgs + + +def getHandleDefault(defaultValue): + tag = defaultValue.type.tag() + if tag in numericSuffixes: + # Some numeric literals require a suffix to compile without warnings + return numericValue(tag, defaultValue.value) + assert tag == IDLType.Tags.bool + return toStringBool(defaultValue.value) + + +def handleDefaultStringValue(defaultValue, method): + """ + Returns a string which ends up calling 'method' with a (char_t*, length) + pair that sets this string default value. This string is suitable for + passing as the second argument of handleDefault. + """ + assert ( + defaultValue.type.isDOMString() + or defaultValue.type.isUSVString() + or defaultValue.type.isUTF8String() + or defaultValue.type.isByteString() + ) + # There shouldn't be any non-ASCII or embedded nulls in here; if + # it ever sneaks in we will need to think about how to properly + # represent that in the C++. + assert all(ord(c) < 128 and ord(c) > 0 for c in defaultValue.value) + if defaultValue.type.isByteString() or defaultValue.type.isUTF8String(): + prefix = "" + else: + prefix = "u" + return fill( + """ + ${method}(${prefix}"${value}"); + """, + method=method, + prefix=prefix, + value=defaultValue.value, + ) + + +def recordKeyType(recordType): + assert recordType.keyType.isString() + if recordType.keyType.isByteString() or recordType.keyType.isUTF8String(): + return "nsCString" + return "nsString" + + +def recordKeyDeclType(recordType): + return CGGeneric(recordKeyType(recordType)) + + +def initializerForType(type): + """ + Get the right initializer for the given type for a data location where we + plan to then initialize it from a JS::Value. Some types need to always be + initialized even before we start the JS::Value-to-IDL-value conversion. + + Returns a string or None if no initialization is needed. + """ + if type.isObject(): + return "nullptr" + # We could probably return CGDictionary.getNonInitializingCtorArg() for the + # dictionary case, but code outside DictionaryBase subclasses can't use + # that, so we can't do it across the board. + return None + + +# If this function is modified, modify CGNativeMember.getArg and +# CGNativeMember.getRetvalInfo accordingly. The latter cares about the decltype +# and holdertype we end up using, because it needs to be able to return the code +# that will convert those to the actual return value of the callback function. +def getJSToNativeConversionInfo( + type, + descriptorProvider, + failureCode=None, + isDefinitelyObject=False, + isMember=False, + isOptional=False, + invalidEnumValueFatal=True, + defaultValue=None, + isNullOrUndefined=False, + isKnownMissing=False, + exceptionCode=None, + lenientFloatCode=None, + allowTreatNonCallableAsNull=False, + isCallbackReturnValue=False, + sourceDescription="value", + nestingLevel="", +): + """ + Get a template for converting a JS value to a native object based on the + given type and descriptor. If failureCode is given, then we're actually + testing whether we can convert the argument to the desired type. That + means that failures to convert due to the JS value being the wrong type of + value need to use failureCode instead of throwing exceptions. Failures to + convert that are due to JS exceptions (from toString or valueOf methods) or + out of memory conditions need to throw exceptions no matter what + failureCode is. However what actually happens when throwing an exception + can be controlled by exceptionCode. The only requirement on that is that + exceptionCode must end up doing a return, and every return from this + function must happen via exceptionCode if exceptionCode is not None. + + If isDefinitelyObject is True, that means we have a value and the value + tests true for isObject(), so we have no need to recheck that. + + If isNullOrUndefined is True, that means we have a value and the value + tests true for isNullOrUndefined(), so we have no need to recheck that. + + If isKnownMissing is True, that means that we are known-missing, and for + cases when we have a default value we only need to output the default value. + + if isMember is not False, we're being converted from a property of some JS + object, not from an actual method argument, so we can't rely on our jsval + being rooted or outliving us in any way. Callers can pass "Dictionary", + "Variadic", "Sequence", "Union", or "OwningUnion" to indicate that the conversion + is for something that is a dictionary member, a variadic argument, a sequence, + an union, or an owning union respectively. + XXX Once we swtich *Rooter to Rooted* for Record and Sequence type entirely, + we could remove "Union" from isMember. + + If isOptional is true, then we are doing conversion of an optional + argument with no default value. + + invalidEnumValueFatal controls whether an invalid enum value conversion + attempt will throw (if true) or simply return without doing anything (if + false). + + If defaultValue is not None, it's the IDL default value for this conversion + + If isEnforceRange is true, we're converting an integer and throwing if the + value is out of range. + + If isClamp is true, we're converting an integer and clamping if the + value is out of range. + + If isAllowShared is false, we're converting a buffer source and throwing if + it is a SharedArrayBuffer or backed by a SharedArrayBuffer. + + If lenientFloatCode is not None, it should be used in cases when + we're a non-finite float that's not unrestricted. + + If allowTreatNonCallableAsNull is true, then [TreatNonCallableAsNull] and + [LegacyTreatNonObjectAsNull] extended attributes on nullable callback functions + will be honored. + + If isCallbackReturnValue is "JSImpl" or "Callback", then the declType may be + adjusted to make it easier to return from a callback. Since that type is + never directly observable by any consumers of the callback code, this is OK. + Furthermore, if isCallbackReturnValue is "JSImpl", that affects the behavior + of the FailureFatalCastableObjectUnwrapper conversion; this is used for + implementing auto-wrapping of JS-implemented return values from a + JS-implemented interface. + + sourceDescription is a description of what this JS value represents, to be + used in error reporting. Callers should assume that it might get placed in + the middle of a sentence. If it ends up at the beginning of a sentence, its + first character will be automatically uppercased. + + The return value from this function is a JSToNativeConversionInfo. + """ + # If we have a defaultValue then we're not actually optional for + # purposes of what we need to be declared as. + assert defaultValue is None or not isOptional + + # Also, we should not have a defaultValue if we know we're an object + assert not isDefinitelyObject or defaultValue is None + + # And we can't both be an object and be null or undefined + assert not isDefinitelyObject or not isNullOrUndefined + + isClamp = type.hasClamp() + isEnforceRange = type.hasEnforceRange() + isAllowShared = type.hasAllowShared() + + # If exceptionCode is not set, we'll just rethrow the exception we got. + # Note that we can't just set failureCode to exceptionCode, because setting + # failureCode will prevent pending exceptions from being set in cases when + # they really should be! + if exceptionCode is None: + exceptionCode = "return false;\n" + + # Unfortunately, .capitalize() on a string will lowercase things inside the + # string, which we do not want. + def firstCap(string): + return string[0].upper() + string[1:] + + # Helper functions for dealing with failures due to the JS value being the + # wrong type of value + def onFailureNotAnObject(failureCode): + return CGGeneric( + failureCode + or ( + 'cx.ThrowErrorMessage<MSG_NOT_OBJECT>("%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode) + ) + ) + + def onFailureBadType(failureCode, typeName): + return CGGeneric( + failureCode + or ( + 'cx.ThrowErrorMessage<MSG_DOES_NOT_IMPLEMENT_INTERFACE>("%s", "%s");\n' + "%s" % (firstCap(sourceDescription), typeName, exceptionCode) + ) + ) + + # It's a failure in the committed-to conversion, not a failure to match up + # to a type, so we don't want to use failureCode in here. We want to just + # throw an exception unconditionally. + def onFailureIsShared(): + return CGGeneric( + 'cx.ThrowErrorMessage<MSG_TYPEDARRAY_IS_SHARED>("%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode) + ) + + def onFailureIsLarge(): + return CGGeneric( + 'cx.ThrowErrorMessage<MSG_TYPEDARRAY_IS_LARGE>("%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode) + ) + + def onFailureNotCallable(failureCode): + return CGGeneric( + failureCode + or ( + 'cx.ThrowErrorMessage<MSG_NOT_CALLABLE>("%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode) + ) + ) + + # A helper function for handling default values. Takes a template + # body and the C++ code to set the default value and wraps the + # given template body in handling for the default value. + def handleDefault(template, setDefault): + if defaultValue is None: + return template + if isKnownMissing: + return fill( + """ + { + // scope for any temporaries our default value setting needs. + $*{setDefault} + } + """, + setDefault=setDefault, + ) + return fill( + """ + if ($${haveValue}) { + $*{templateBody} + } else { + $*{setDefault} + } + """, + templateBody=template, + setDefault=setDefault, + ) + + # A helper function for wrapping up the template body for + # possibly-nullable objecty stuff + def wrapObjectTemplate(templateBody, type, codeToSetNull, failureCode=None): + if isNullOrUndefined and type.nullable(): + # Just ignore templateBody and set ourselves to null. + # Note that we don't have to worry about default values + # here either, since we already examined this value. + return codeToSetNull + + if not isDefinitelyObject: + # Handle the non-object cases by wrapping up the whole + # thing in an if cascade. + if type.nullable(): + elifLine = "} else if (${val}.isNullOrUndefined()) {\n" + elifBody = codeToSetNull + else: + elifLine = "" + elifBody = "" + + # Note that $${val} below expands to ${val}. This string is + # used as a template later, and val will be filled in then. + templateBody = fill( + """ + if ($${val}.isObject()) { + $*{templateBody} + $*{elifLine} + $*{elifBody} + } else { + $*{failureBody} + } + """, + templateBody=templateBody, + elifLine=elifLine, + elifBody=elifBody, + failureBody=onFailureNotAnObject(failureCode).define(), + ) + + if isinstance(defaultValue, IDLNullValue): + assert type.nullable() # Parser should enforce this + templateBody = handleDefault(templateBody, codeToSetNull) + elif isinstance(defaultValue, IDLEmptySequenceValue): + # Our caller will handle it + pass + else: + assert defaultValue is None + + return templateBody + + # A helper function for converting things that look like a JSObject*. + def handleJSObjectType( + type, isMember, failureCode, exceptionCode, sourceDescription + ): + if not isMember or isMember == "Union": + if isOptional: + # We have a specialization of Optional that will use a + # Rooted for the storage here. + declType = CGGeneric("JS::Handle<JSObject*>") + else: + declType = CGGeneric("JS::Rooted<JSObject*>") + declArgs = "cx" + else: + assert isMember in ( + "Sequence", + "Variadic", + "Dictionary", + "OwningUnion", + "Record", + ) + # We'll get traced by the sequence or dictionary or union tracer + declType = CGGeneric("JSObject*") + declArgs = None + templateBody = "${declName} = &${val}.toObject();\n" + + # For JS-implemented APIs, we refuse to allow passing objects that the + # API consumer does not subsume. The extra parens around + # ($${passedToJSImpl}) suppress unreachable code warnings when + # $${passedToJSImpl} is the literal `false`. But Apple is shipping a + # buggy clang (clang 3.9) in Xcode 8.3, so there even the parens are not + # enough. So we manually disable some warnings in clang. + if ( + not isinstance(descriptorProvider, Descriptor) + or descriptorProvider.interface.isJSImplemented() + ): + templateBody = ( + fill( + """ + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunreachable-code" + #pragma clang diagnostic ignored "-Wunreachable-code-return" + #endif // __clang__ + if (($${passedToJSImpl}) && !CallerSubsumes($${val})) { + cx.ThrowErrorMessage<MSG_PERMISSION_DENIED_TO_PASS_ARG>("${sourceDescription}"); + $*{exceptionCode} + } + #ifdef __clang__ + #pragma clang diagnostic pop + #endif // __clang__ + """, + sourceDescription=sourceDescription, + exceptionCode=exceptionCode, + ) + + templateBody + ) + + setToNullCode = "${declName} = nullptr;\n" + template = wrapObjectTemplate(templateBody, type, setToNullCode, failureCode) + return JSToNativeConversionInfo( + template, declType=declType, dealWithOptional=isOptional, declArgs=declArgs + ) + + def incrementNestingLevel(): + if nestingLevel == "": + return 1 + return nestingLevel + 1 + + assert not (isEnforceRange and isClamp) # These are mutually exclusive + + if type.isSequence() or type.isObservableArray(): + assert not isEnforceRange and not isClamp and not isAllowShared + + if failureCode is None: + notSequence = ( + 'cx.ThrowErrorMessage<MSG_CONVERSION_ERROR>("%s", "%s");\n' + "%s" + % ( + firstCap(sourceDescription), + "sequence" if type.isSequence() else "observable array", + exceptionCode, + ) + ) + else: + notSequence = failureCode + + nullable = type.nullable() + # Be very careful not to change "type": we need it later + if nullable: + elementType = type.inner.inner + else: + elementType = type.inner + + # We want to use auto arrays if we can, but we have to be careful with + # reallocation behavior for arrays. In particular, if we use auto + # arrays for sequences and have a sequence of elements which are + # themselves sequences or have sequences as members, we have a problem. + # In that case, resizing the outermost AutoTArray to the right size + # will memmove its elements, but AutoTArrays are not memmovable and + # hence will end up with pointers to bogus memory, which is bad. To + # deal with this, we typically map WebIDL sequences to our Sequence + # type, which is in fact memmovable. The one exception is when we're + # passing in a sequence directly as an argument without any sort of + # optional or nullable complexity going on. In that situation, we can + # use an AutoSequence instead. We have to keep using Sequence in the + # nullable and optional cases because we don't want to leak the + # AutoSequence type to consumers, which would be unavoidable with + # Nullable<AutoSequence> or Optional<AutoSequence>. + if ( + (isMember and isMember != "Union") + or isOptional + or nullable + or isCallbackReturnValue + ): + sequenceClass = "Sequence" + else: + sequenceClass = "binding_detail::AutoSequence" + + # XXXbz we can't include the index in the sourceDescription, because + # we don't really have a way to pass one in dynamically at runtime... + elementInfo = getJSToNativeConversionInfo( + elementType, + descriptorProvider, + isMember="Sequence", + exceptionCode=exceptionCode, + lenientFloatCode=lenientFloatCode, + isCallbackReturnValue=isCallbackReturnValue, + sourceDescription="element of %s" % sourceDescription, + nestingLevel=incrementNestingLevel(), + ) + if elementInfo.dealWithOptional: + raise TypeError("Shouldn't have optional things in sequences") + if elementInfo.holderType is not None: + raise TypeError("Shouldn't need holders for sequences") + + typeName = CGTemplatedType(sequenceClass, elementInfo.declType) + sequenceType = typeName.define() + + if isMember == "Union" and typeNeedsRooting(type): + assert not nullable + typeName = CGTemplatedType( + "binding_detail::RootedAutoSequence", elementInfo.declType + ) + elif nullable: + typeName = CGTemplatedType("Nullable", typeName) + + if nullable: + arrayRef = "${declName}.SetValue()" + else: + arrayRef = "${declName}" + + elementConversion = string.Template(elementInfo.template).substitute( + { + "val": "temp" + str(nestingLevel), + "maybeMutableVal": "&temp" + str(nestingLevel), + "declName": "slot" + str(nestingLevel), + # We only need holderName here to handle isExternal() + # interfaces, which use an internal holder for the + # conversion even when forceOwningType ends up true. + "holderName": "tempHolder" + str(nestingLevel), + "passedToJSImpl": "${passedToJSImpl}", + } + ) + + elementInitializer = initializerForType(elementType) + if elementInitializer is None: + elementInitializer = "" + else: + elementInitializer = elementInitializer + ", " + + # NOTE: Keep this in sync with variadic conversions as needed + templateBody = fill( + """ + JS::ForOfIterator iter${nestingLevel}(cx); + if (!iter${nestingLevel}.init($${val}, JS::ForOfIterator::AllowNonIterable)) { + $*{exceptionCode} + } + if (!iter${nestingLevel}.valueIsIterable()) { + $*{notSequence} + } + ${sequenceType} &arr${nestingLevel} = ${arrayRef}; + JS::Rooted<JS::Value> temp${nestingLevel}(cx); + while (true) { + bool done${nestingLevel}; + if (!iter${nestingLevel}.next(&temp${nestingLevel}, &done${nestingLevel})) { + $*{exceptionCode} + } + if (done${nestingLevel}) { + break; + } + ${elementType}* slotPtr${nestingLevel} = arr${nestingLevel}.AppendElement(${elementInitializer}mozilla::fallible); + if (!slotPtr${nestingLevel}) { + JS_ReportOutOfMemory(cx); + $*{exceptionCode} + } + ${elementType}& slot${nestingLevel} = *slotPtr${nestingLevel}; + $*{elementConversion} + } + """, + exceptionCode=exceptionCode, + notSequence=notSequence, + sequenceType=sequenceType, + arrayRef=arrayRef, + elementType=elementInfo.declType.define(), + elementConversion=elementConversion, + elementInitializer=elementInitializer, + nestingLevel=str(nestingLevel), + ) + + templateBody = wrapObjectTemplate( + templateBody, type, "${declName}.SetNull();\n", notSequence + ) + if isinstance(defaultValue, IDLEmptySequenceValue): + if type.nullable(): + codeToSetEmpty = "${declName}.SetValue();\n" + else: + codeToSetEmpty = ( + "/* ${declName} array is already empty; nothing to do */\n" + ) + templateBody = handleDefault(templateBody, codeToSetEmpty) + + declArgs = None + holderType = None + holderArgs = None + # Sequence arguments that might contain traceable things need + # to get traced + if typeNeedsRooting(elementType): + if not isMember: + holderType = CGTemplatedType("SequenceRooter", elementInfo.declType) + # If our sequence is nullable, this will set the Nullable to be + # not-null, but that's ok because we make an explicit SetNull() call + # on it as needed if our JS value is actually null. + holderArgs = "cx, &%s" % arrayRef + elif isMember == "Union": + declArgs = "cx" + + return JSToNativeConversionInfo( + templateBody, + declType=typeName, + declArgs=declArgs, + holderType=holderType, + dealWithOptional=isOptional, + holderArgs=holderArgs, + ) + + if type.isRecord(): + assert not isEnforceRange and not isClamp and not isAllowShared + if failureCode is None: + notRecord = 'cx.ThrowErrorMessage<MSG_NOT_OBJECT>("%s");\n' "%s" % ( + firstCap(sourceDescription), + exceptionCode, + ) + else: + notRecord = failureCode + + nullable = type.nullable() + # Be very careful not to change "type": we need it later + if nullable: + recordType = type.inner + else: + recordType = type + valueType = recordType.inner + + valueInfo = getJSToNativeConversionInfo( + valueType, + descriptorProvider, + isMember="Record", + exceptionCode=exceptionCode, + lenientFloatCode=lenientFloatCode, + isCallbackReturnValue=isCallbackReturnValue, + sourceDescription="value in %s" % sourceDescription, + nestingLevel=incrementNestingLevel(), + ) + if valueInfo.dealWithOptional: + raise TypeError("Shouldn't have optional things in record") + if valueInfo.holderType is not None: + raise TypeError("Shouldn't need holders for record") + + declType = CGTemplatedType( + "Record", [recordKeyDeclType(recordType), valueInfo.declType] + ) + typeName = declType.define() + + if isMember == "Union" and typeNeedsRooting(type): + assert not nullable + declType = CGTemplatedType( + "RootedRecord", [recordKeyDeclType(recordType), valueInfo.declType] + ) + elif nullable: + declType = CGTemplatedType("Nullable", declType) + + if nullable: + recordRef = "${declName}.SetValue()" + else: + recordRef = "${declName}" + + valueConversion = string.Template(valueInfo.template).substitute( + { + "val": "temp", + "maybeMutableVal": "&temp", + "declName": "slot", + # We only need holderName here to handle isExternal() + # interfaces, which use an internal holder for the + # conversion even when forceOwningType ends up true. + "holderName": "tempHolder", + "passedToJSImpl": "${passedToJSImpl}", + } + ) + + keyType = recordKeyType(recordType) + if recordType.keyType.isJSString(): + raise TypeError( + "Have do deal with JSString record type, but don't know how" + ) + if recordType.keyType.isByteString() or recordType.keyType.isUTF8String(): + hashKeyType = "nsCStringHashKey" + if recordType.keyType.isByteString(): + keyConversionFunction = "ConvertJSValueToByteString" + else: + keyConversionFunction = "ConvertJSValueToString" + + else: + hashKeyType = "nsStringHashKey" + if recordType.keyType.isDOMString(): + keyConversionFunction = "ConvertJSValueToString" + else: + assert recordType.keyType.isUSVString() + keyConversionFunction = "ConvertJSValueToUSVString" + + templateBody = fill( + """ + auto& recordEntries = ${recordRef}.Entries(); + + JS::Rooted<JSObject*> recordObj(cx, &$${val}.toObject()); + JS::RootedVector<jsid> ids(cx); + if (!js::GetPropertyKeys(cx, recordObj, + JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &ids)) { + $*{exceptionCode} + } + if (!recordEntries.SetCapacity(ids.length(), mozilla::fallible)) { + JS_ReportOutOfMemory(cx); + $*{exceptionCode} + } + JS::Rooted<JS::Value> propNameValue(cx); + JS::Rooted<JS::Value> temp(cx); + JS::Rooted<jsid> curId(cx); + JS::Rooted<JS::Value> idVal(cx); + // Use a hashset to keep track of ids seen, to avoid + // introducing nasty O(N^2) behavior scanning for them all the + // time. Ideally we'd use a data structure with O(1) lookup + // _and_ ordering for the MozMap, but we don't have one lying + // around. + nsTHashtable<${hashKeyType}> idsSeen; + for (size_t i = 0; i < ids.length(); ++i) { + curId = ids[i]; + + JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx); + if (!JS_GetOwnPropertyDescriptorById(cx, recordObj, curId, + &desc)) { + $*{exceptionCode} + } + + if (desc.isNothing() || !desc->enumerable()) { + continue; + } + + idVal = js::IdToValue(curId); + ${keyType} propName; + // This will just throw if idVal is a Symbol, like the spec says + // to do. + if (!${keyConversionFunction}(cx, idVal, "key of ${sourceDescription}", propName)) { + $*{exceptionCode} + } + + if (!JS_GetPropertyById(cx, recordObj, curId, &temp)) { + $*{exceptionCode} + } + + ${typeName}::EntryType* entry; + if (!idsSeen.EnsureInserted(propName)) { + // Find the existing entry. + auto idx = recordEntries.IndexOf(propName); + MOZ_ASSERT(idx != recordEntries.NoIndex, + "Why is it not found?"); + // Now blow it away to make it look like it was just added + // to the array, because it's not obvious that it's + // safe to write to its already-initialized mValue via our + // normal codegen conversions. For example, the value + // could be a union and this would change its type, but + // codegen assumes we won't do that. + entry = recordEntries.ReconstructElementAt(idx); + } else { + // Safe to do an infallible append here, because we did a + // SetCapacity above to the right capacity. + entry = recordEntries.AppendElement(); + } + entry->mKey = propName; + ${valueType}& slot = entry->mValue; + $*{valueConversion} + } + """, + exceptionCode=exceptionCode, + recordRef=recordRef, + hashKeyType=hashKeyType, + keyType=keyType, + keyConversionFunction=keyConversionFunction, + sourceDescription=sourceDescription, + typeName=typeName, + valueType=valueInfo.declType.define(), + valueConversion=valueConversion, + ) + + templateBody = wrapObjectTemplate( + templateBody, type, "${declName}.SetNull();\n", notRecord + ) + + declArgs = None + holderType = None + holderArgs = None + # record arguments that might contain traceable things need + # to get traced + if not isMember and isCallbackReturnValue: + # Go ahead and just convert directly into our actual return value + declType = CGWrapper(declType, post="&") + declArgs = "aRetVal" + elif typeNeedsRooting(valueType): + if not isMember: + holderType = CGTemplatedType( + "RecordRooter", [recordKeyDeclType(recordType), valueInfo.declType] + ) + # If our record is nullable, this will set the Nullable to be + # not-null, but that's ok because we make an explicit SetNull() call + # on it as needed if our JS value is actually null. + holderArgs = "cx, &%s" % recordRef + elif isMember == "Union": + declArgs = "cx" + + return JSToNativeConversionInfo( + templateBody, + declType=declType, + declArgs=declArgs, + holderType=holderType, + dealWithOptional=isOptional, + holderArgs=holderArgs, + ) + + if type.isUnion(): + nullable = type.nullable() + if nullable: + type = type.inner + + isOwningUnion = (isMember and isMember != "Union") or isCallbackReturnValue + unionArgumentObj = "${declName}" + if nullable: + if isOptional and not isOwningUnion: + unionArgumentObj += ".Value()" + # If we're owning, we're a Nullable, which hasn't been told it has + # a value. Otherwise we're an already-constructed Maybe. + unionArgumentObj += ".SetValue()" + + templateBody = CGIfWrapper( + CGGeneric(exceptionCode), + '!%s.Init(cx, ${val}, "%s", ${passedToJSImpl})' + % (unionArgumentObj, firstCap(sourceDescription)), + ) + + if type.hasNullableType: + assert not nullable + # Make sure to handle a null default value here + if defaultValue and isinstance(defaultValue, IDLNullValue): + assert defaultValue.type == type + templateBody = CGIfElseWrapper( + "!(${haveValue})", + CGGeneric("%s.SetNull();\n" % unionArgumentObj), + templateBody, + ) + + typeName = CGUnionStruct.unionTypeDecl(type, isOwningUnion) + argumentTypeName = typeName + "Argument" + if nullable: + typeName = "Nullable<" + typeName + " >" + + declType = CGGeneric(typeName) + if isOwningUnion: + holderType = None + else: + holderType = CGGeneric(argumentTypeName) + if nullable: + holderType = CGTemplatedType("Maybe", holderType) + + # If we're isOptional and not nullable the normal optional handling will + # handle lazy construction of our holder. If we're nullable and not + # owning we do it all by hand because we do not want our holder + # constructed if we're null. But if we're owning we don't have a + # holder anyway, so we can do the normal Optional codepath. + declLoc = "${declName}" + constructDecl = None + if nullable: + if isOptional and not isOwningUnion: + declType = CGTemplatedType("Optional", declType) + constructDecl = CGGeneric("${declName}.Construct();\n") + declLoc = "${declName}.Value()" + + if not isMember and isCallbackReturnValue: + declType = CGWrapper(declType, post="&") + declArgs = "aRetVal" + else: + declArgs = None + + if ( + defaultValue + and not isinstance(defaultValue, IDLNullValue) + and not isinstance(defaultValue, IDLDefaultDictionaryValue) + ): + tag = defaultValue.type.tag() + + if tag in numericSuffixes or tag is IDLType.Tags.bool: + defaultStr = getHandleDefault(defaultValue) + # Make sure we actually construct the thing inside the nullable. + value = declLoc + (".SetValue()" if nullable else "") + name = getUnionMemberName(defaultValue.type) + default = CGGeneric( + "%s.RawSetAs%s() = %s;\n" % (value, name, defaultStr) + ) + elif isinstance(defaultValue, IDLEmptySequenceValue): + name = getUnionMemberName(defaultValue.type) + # Make sure we actually construct the thing inside the nullable. + value = declLoc + (".SetValue()" if nullable else "") + if not isOwningUnion and typeNeedsRooting(defaultValue.type): + ctorArgs = "cx" + else: + ctorArgs = "" + # It's enough to set us to the right type; that will + # create an empty array, which is all we need here. + default = CGGeneric("%s.RawSetAs%s(%s);\n" % (value, name, ctorArgs)) + elif defaultValue.type.isEnum(): + name = getUnionMemberName(defaultValue.type) + # Make sure we actually construct the thing inside the nullable. + value = declLoc + (".SetValue()" if nullable else "") + default = CGGeneric( + "%s.RawSetAs%s() = %s::%s;\n" + % ( + value, + name, + defaultValue.type.inner.identifier.name, + getEnumValueName(defaultValue.value), + ) + ) + else: + default = CGGeneric( + handleDefaultStringValue( + defaultValue, "%s.SetStringLiteral" % unionArgumentObj + ) + ) + + templateBody = CGIfElseWrapper("!(${haveValue})", default, templateBody) + + if nullable: + assert not type.hasNullableType + if defaultValue: + if isinstance(defaultValue, IDLNullValue): + extraConditionForNull = "!(${haveValue}) || " + else: + extraConditionForNull = "(${haveValue}) && " + else: + extraConditionForNull = "" + + hasUndefinedType = any(t.isUndefined() for t in type.flatMemberTypes) + assert not hasUndefinedType or defaultValue is None + + nullTest = ( + "${val}.isNull()" if hasUndefinedType else "${val}.isNullOrUndefined()" + ) + templateBody = CGIfElseWrapper( + extraConditionForNull + nullTest, + CGGeneric("%s.SetNull();\n" % declLoc), + templateBody, + ) + elif ( + not type.hasNullableType + and defaultValue + and isinstance(defaultValue, IDLDefaultDictionaryValue) + ): + assert type.hasDictionaryType() + assert defaultValue.type.isDictionary() + if not isOwningUnion and typeNeedsRooting(defaultValue.type): + ctorArgs = "cx" + else: + ctorArgs = "" + initDictionaryWithNull = CGIfWrapper( + CGGeneric("return false;\n"), + ( + '!%s.RawSetAs%s(%s).Init(cx, JS::NullHandleValue, "Member of %s")' + % ( + declLoc, + getUnionMemberName(defaultValue.type), + ctorArgs, + type.prettyName(), + ) + ), + ) + templateBody = CGIfElseWrapper( + "!(${haveValue})", initDictionaryWithNull, templateBody + ) + + templateBody = CGList([constructDecl, templateBody]) + + return JSToNativeConversionInfo( + templateBody.define(), + declType=declType, + declArgs=declArgs, + dealWithOptional=isOptional and (not nullable or isOwningUnion), + ) + + if type.isPromise(): + assert not type.nullable() + assert defaultValue is None + + # We always have to hold a strong ref to Promise here, because + # Promise::resolve returns an addrefed thing. + argIsPointer = isCallbackReturnValue + if argIsPointer: + declType = CGGeneric("RefPtr<Promise>") + else: + declType = CGGeneric("OwningNonNull<Promise>") + + # Per spec, what we're supposed to do is take the original + # Promise.resolve and call it with the original Promise as this + # value to make a Promise out of whatever value we actually have + # here. The question is which global we should use. There are + # several cases to consider: + # + # 1) Normal call to API with a Promise argument. This is a case the + # spec covers, and we should be using the current Realm's + # Promise. That means the current compartment. + # 2) Call to API with a Promise argument over Xrays. In practice, + # this sort of thing seems to be used for giving an API + # implementation a way to wait for conclusion of an asyc + # operation, _not_ to expose the Promise to content code. So we + # probably want to allow callers to use such an API in a + # "natural" way, by passing chrome-side promises; indeed, that + # may be all that the caller has to represent their async + # operation. That means we really need to do the + # Promise.resolve() in the caller (chrome) compartment: if we do + # it in the content compartment, we will try to call .then() on + # the chrome promise while in the content compartment, which will + # throw and we'll just get a rejected Promise. Note that this is + # also the reason why a caller who has a chrome Promise + # representing an async operation can't itself convert it to a + # content-side Promise (at least not without some serious + # gyrations). + # 3) Promise return value from a callback or callback interface. + # Per spec, this should use the Realm of the callback object. In + # our case, that's the compartment of the underlying callback, + # not the current compartment (which may be the compartment of + # some cross-compartment wrapper around said callback). + # 4) Return value from a JS-implemented interface. In this case we + # have a problem. Our current compartment is the compartment of + # the JS implementation. But if the JS implementation returned + # a page-side Promise (which is a totally sane thing to do, and + # in fact the right thing to do given that this return value is + # going right to content script) then we don't want to + # Promise.resolve with our current compartment Promise, because + # that will wrap it up in a chrome-side Promise, which is + # decidedly _not_ what's desired here. So in that case we + # should really unwrap the return value and use the global of + # the result. CheckedUnwrapStatic should be good enough for that; + # if it fails, then we're failing unwrap while in a + # system-privileged compartment, so presumably we have a dead + # object wrapper. Just error out. Do NOT fall back to using + # the current compartment instead: that will return a + # system-privileged rejected (because getting .then inside + # resolve() failed) Promise to the caller, which they won't be + # able to touch. That's not helpful. If we error out, on the + # other hand, they will get a content-side rejected promise. + # Same thing if the value returned is not even an object. + if isCallbackReturnValue == "JSImpl": + # Case 4 above. Note that globalObj defaults to the current + # compartment global. Note that we don't use $*{exceptionCode} + # here because that will try to aRv.Throw(NS_ERROR_UNEXPECTED) + # which we don't really want here. + assert exceptionCode == "aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n" + getPromiseGlobal = fill( + """ + if (!$${val}.isObject()) { + aRv.ThrowTypeError<MSG_NOT_OBJECT>("${sourceDescription}"); + return nullptr; + } + JSObject* unwrappedVal = js::CheckedUnwrapStatic(&$${val}.toObject()); + if (!unwrappedVal) { + // A slight lie, but not much of one, for a dead object wrapper. + aRv.ThrowTypeError<MSG_NOT_OBJECT>("${sourceDescription}"); + return nullptr; + } + globalObj = JS::GetNonCCWObjectGlobal(unwrappedVal); + """, + sourceDescription=sourceDescription, + ) + elif isCallbackReturnValue == "Callback": + getPromiseGlobal = dedent( + """ + // We basically want our entry global here. Play it safe + // and use GetEntryGlobal() to get it, with whatever + // principal-clamping it ends up doing. + globalObj = GetEntryGlobal()->GetGlobalJSObject(); + """ + ) + else: + getPromiseGlobal = dedent( + """ + globalObj = JS::CurrentGlobalOrNull(cx); + """ + ) + + templateBody = fill( + """ + { // Scope for our GlobalObject, FastErrorResult, JSAutoRealm, + // etc. + + JS::Rooted<JSObject*> globalObj(cx); + $*{getPromiseGlobal} + JSAutoRealm ar(cx, globalObj); + GlobalObject promiseGlobal(cx, globalObj); + if (promiseGlobal.Failed()) { + $*{exceptionCode} + } + + JS::Rooted<JS::Value> valueToResolve(cx, $${val}); + if (!JS_WrapValue(cx, &valueToResolve)) { + $*{exceptionCode} + } + binding_detail::FastErrorResult promiseRv; + nsCOMPtr<nsIGlobalObject> global = + do_QueryInterface(promiseGlobal.GetAsSupports()); + if (!global) { + promiseRv.Throw(NS_ERROR_UNEXPECTED); + MOZ_ALWAYS_TRUE(promiseRv.MaybeSetPendingException(cx)); + $*{exceptionCode} + } + $${declName} = Promise::Resolve(global, cx, valueToResolve, + promiseRv); + if (promiseRv.MaybeSetPendingException(cx)) { + $*{exceptionCode} + } + } + """, + getPromiseGlobal=getPromiseGlobal, + exceptionCode=exceptionCode, + ) + + return JSToNativeConversionInfo( + templateBody, declType=declType, dealWithOptional=isOptional + ) + + if type.isGeckoInterface(): + assert not isEnforceRange and not isClamp and not isAllowShared + + descriptor = descriptorProvider.getDescriptor( + type.unroll().inner.identifier.name + ) + + assert descriptor.nativeType != "JSObject" + + if descriptor.interface.isCallback(): + (declType, declArgs, conversion) = getCallbackConversionInfo( + type, descriptor.interface, isMember, isCallbackReturnValue, isOptional + ) + template = wrapObjectTemplate( + conversion, type, "${declName} = nullptr;\n", failureCode + ) + return JSToNativeConversionInfo( + template, + declType=declType, + declArgs=declArgs, + dealWithOptional=isOptional, + ) + + if descriptor.interface.identifier.name == "WindowProxy": + declType = CGGeneric("mozilla::dom::WindowProxyHolder") + if type.nullable(): + declType = CGTemplatedType("Nullable", declType) + windowProxyHolderRef = "${declName}.SetValue()" + else: + windowProxyHolderRef = "${declName}" + + failureCode = onFailureBadType( + failureCode, descriptor.interface.identifier.name + ).define() + templateBody = fill( + """ + JS::Rooted<JSObject*> source(cx, &$${val}.toObject()); + if (NS_FAILED(UnwrapWindowProxyArg(cx, source, ${windowProxyHolderRef}))) { + $*{onFailure} + } + """, + windowProxyHolderRef=windowProxyHolderRef, + onFailure=failureCode, + ) + templateBody = wrapObjectTemplate( + templateBody, type, "${declName}.SetNull();\n", failureCode + ) + return JSToNativeConversionInfo( + templateBody, declType=declType, dealWithOptional=isOptional + ) + + # This is an interface that we implement as a concrete class + # or an XPCOM interface. + + # Allow null pointers for nullable types and old-binding classes, and + # use an RefPtr or raw pointer for callback return values to make + # them easier to return. + argIsPointer = ( + type.nullable() or type.unroll().inner.isExternal() or isCallbackReturnValue + ) + + # Sequence and dictionary members, as well as owning unions (which can + # appear here as return values in JS-implemented interfaces) have to + # hold a strong ref to the thing being passed down. Those all set + # isMember. + # + # Also, callback return values always end up addrefing anyway, so there + # is no point trying to avoid it here and it makes other things simpler + # since we can assume the return value is a strong ref. + assert not descriptor.interface.isCallback() + forceOwningType = (isMember and isMember != "Union") or isCallbackReturnValue + + typeName = descriptor.nativeType + typePtr = typeName + "*" + + # Compute a few things: + # - declType is the type we want to return as the first element of our + # tuple. + # - holderType is the type we want to return as the third element + # of our tuple. + + # Set up some sensible defaults for these things insofar as we can. + holderType = None + if argIsPointer: + if forceOwningType: + declType = "RefPtr<" + typeName + ">" + else: + declType = typePtr + else: + if forceOwningType: + declType = "OwningNonNull<" + typeName + ">" + else: + declType = "NonNull<" + typeName + ">" + + templateBody = "" + if forceOwningType: + templateBody += fill( + """ + static_assert(IsRefcounted<${typeName}>::value, "We can only store refcounted classes."); + """, + typeName=typeName, + ) + + if not descriptor.interface.isExternal(): + if failureCode is not None: + templateBody += str( + CastableObjectUnwrapper( + descriptor, + "${val}", + "${maybeMutableVal}", + "${declName}", + failureCode, + ) + ) + else: + templateBody += str( + FailureFatalCastableObjectUnwrapper( + descriptor, + "${val}", + "${maybeMutableVal}", + "${declName}", + exceptionCode, + isCallbackReturnValue, + firstCap(sourceDescription), + ) + ) + else: + # External interface. We always have a holder for these, because we + # don't actually know whether we have to addref when unwrapping or not. + # So we just pass an getter_AddRefs(RefPtr) to XPConnect and if we'll + # need a release it'll put a non-null pointer in there. + if forceOwningType: + # Don't return a holderType in this case; our declName + # will just own stuff. + templateBody += "RefPtr<" + typeName + "> ${holderName};\n" + else: + holderType = "RefPtr<" + typeName + ">" + templateBody += ( + "JS::Rooted<JSObject*> source(cx, &${val}.toObject());\n" + + "if (NS_FAILED(UnwrapArg<" + + typeName + + ">(cx, source, getter_AddRefs(${holderName})))) {\n" + ) + templateBody += CGIndenter( + onFailureBadType(failureCode, descriptor.interface.identifier.name) + ).define() + templateBody += "}\n" "MOZ_ASSERT(${holderName});\n" + + # And store our value in ${declName} + templateBody += "${declName} = ${holderName};\n" + + # Just pass failureCode, not onFailureBadType, here, so we'll report + # the thing as not an object as opposed to not implementing whatever + # our interface is. + templateBody = wrapObjectTemplate( + templateBody, type, "${declName} = nullptr;\n", failureCode + ) + + declType = CGGeneric(declType) + if holderType is not None: + holderType = CGGeneric(holderType) + return JSToNativeConversionInfo( + templateBody, + declType=declType, + holderType=holderType, + dealWithOptional=isOptional, + ) + + if type.isSpiderMonkeyInterface(): + assert not isEnforceRange and not isClamp + name = type.unroll().name # unroll() because it may be nullable + interfaceType = CGGeneric(name) + declType = interfaceType + if type.nullable(): + declType = CGTemplatedType("Nullable", declType) + objRef = "${declName}.SetValue()" + else: + objRef = "${declName}" + + # Again, this is a bit strange since we are actually building a + # template string here. ${objRef} and $*{badType} below are filled in + # right now; $${val} expands to ${val}, to be filled in later. + template = fill( + """ + if (!${objRef}.Init(&$${val}.toObject())) { + $*{badType} + } + """, + objRef=objRef, + badType=onFailureBadType(failureCode, type.name).define(), + ) + if type.isBufferSource(): + if type.isArrayBuffer(): + isSharedMethod = "JS::IsSharedArrayBufferObject" + isLargeMethod = "JS::IsLargeArrayBufferMaybeShared" + else: + assert type.isArrayBufferView() or type.isTypedArray() + isSharedMethod = "JS::IsArrayBufferViewShared" + isLargeMethod = "JS::IsLargeArrayBufferView" + if not isAllowShared: + template += fill( + """ + if (${isSharedMethod}(${objRef}.Obj())) { + $*{badType} + } + """, + isSharedMethod=isSharedMethod, + objRef=objRef, + badType=onFailureIsShared().define(), + ) + # For now reject large (> 2 GB) ArrayBuffers and ArrayBufferViews. + # Supporting this will require changing dom::TypedArray and + # consumers. + template += fill( + """ + if (${isLargeMethod}(${objRef}.Obj())) { + $*{badType} + } + """, + isLargeMethod=isLargeMethod, + objRef=objRef, + badType=onFailureIsLarge().define(), + ) + template = wrapObjectTemplate( + template, type, "${declName}.SetNull();\n", failureCode + ) + if not isMember or isMember == "Union": + # This is a bit annoying. In a union we don't want to have a + # holder, since unions don't support that. But if we're optional we + # want to have a holder, so that the callee doesn't see + # Optional<RootedSpiderMonkeyInterface<InterfaceType>>. So do a + # holder if we're optional and use a RootedSpiderMonkeyInterface + # otherwise. + if isOptional: + holderType = CGTemplatedType( + "SpiderMonkeyInterfaceRooter", interfaceType + ) + # If our SpiderMonkey interface is nullable, this will set the + # Nullable to be not-null, but that's ok because we make an + # explicit SetNull() call on it as needed if our JS value is + # actually null. XXXbz Because "Maybe" takes const refs for + # constructor arguments, we can't pass a reference here; have + # to pass a pointer. + holderArgs = "cx, &%s" % objRef + declArgs = None + else: + holderType = None + holderArgs = None + declType = CGTemplatedType("RootedSpiderMonkeyInterface", declType) + declArgs = "cx" + else: + holderType = None + holderArgs = None + declArgs = None + return JSToNativeConversionInfo( + template, + declType=declType, + holderType=holderType, + dealWithOptional=isOptional, + declArgs=declArgs, + holderArgs=holderArgs, + ) + + if type.isJSString(): + assert not isEnforceRange and not isClamp and not isAllowShared + if type.nullable(): + raise TypeError("Nullable JSString not supported") + + declArgs = "cx" + if isMember: + raise TypeError("JSString not supported as member") + else: + declType = "JS::Rooted<JSString*>" + + if isOptional: + raise TypeError("JSString not supported as optional") + templateBody = fill( + """ + if (!($${declName} = ConvertJSValueToJSString(cx, $${val}))) { + $*{exceptionCode} + } + """, + exceptionCode=exceptionCode, + ) + + if defaultValue is not None: + assert not isinstance(defaultValue, IDLNullValue) + defaultCode = fill( + """ + static const char data[] = { ${data} }; + $${declName} = JS_NewStringCopyN(cx, data, ArrayLength(data) - 1); + if (!$${declName}) { + $*{exceptionCode} + } + """, + data=", ".join( + ["'" + char + "'" for char in defaultValue.value] + ["0"] + ), + exceptionCode=exceptionCode, + ) + + templateBody = handleDefault(templateBody, defaultCode) + return JSToNativeConversionInfo( + templateBody, declType=CGGeneric(declType), declArgs=declArgs + ) + + if type.isDOMString() or type.isUSVString() or type.isUTF8String(): + assert not isEnforceRange and not isClamp and not isAllowShared + + treatAs = { + "Default": "eStringify", + "EmptyString": "eEmpty", + "Null": "eNull", + } + if type.nullable(): + # For nullable strings null becomes a null string. + treatNullAs = "Null" + # For nullable strings undefined also becomes a null string. + undefinedBehavior = "eNull" + else: + undefinedBehavior = "eStringify" + if type.legacyNullToEmptyString: + treatNullAs = "EmptyString" + else: + treatNullAs = "Default" + nullBehavior = treatAs[treatNullAs] + + def getConversionCode(varName): + normalizeCode = "" + if type.isUSVString(): + normalizeCode = fill( + """ + if (!NormalizeUSVString(${var})) { + JS_ReportOutOfMemory(cx); + $*{exceptionCode} + } + """, + var=varName, + exceptionCode=exceptionCode, + ) + + conversionCode = fill( + """ + if (!ConvertJSValueToString(cx, $${val}, ${nullBehavior}, ${undefinedBehavior}, ${varName})) { + $*{exceptionCode} + } + $*{normalizeCode} + """, + nullBehavior=nullBehavior, + undefinedBehavior=undefinedBehavior, + varName=varName, + exceptionCode=exceptionCode, + normalizeCode=normalizeCode, + ) + + if defaultValue is None: + return conversionCode + + if isinstance(defaultValue, IDLNullValue): + assert type.nullable() + defaultCode = "%s.SetIsVoid(true);\n" % varName + else: + defaultCode = handleDefaultStringValue( + defaultValue, "%s.AssignLiteral" % varName + ) + return handleDefault(conversionCode, defaultCode) + + if isMember and isMember != "Union": + # Convert directly into the ns[C]String member we have. + if type.isUTF8String(): + declType = "nsCString" + else: + declType = "nsString" + return JSToNativeConversionInfo( + getConversionCode("${declName}"), + declType=CGGeneric(declType), + dealWithOptional=isOptional, + ) + + if isOptional: + if type.isUTF8String(): + declType = "Optional<nsACString>" + holderType = CGGeneric("binding_detail::FakeString<char>") + else: + declType = "Optional<nsAString>" + holderType = CGGeneric("binding_detail::FakeString<char16_t>") + conversionCode = "%s" "${declName} = &${holderName};\n" % getConversionCode( + "${holderName}" + ) + else: + if type.isUTF8String(): + declType = "binding_detail::FakeString<char>" + else: + declType = "binding_detail::FakeString<char16_t>" + holderType = None + conversionCode = getConversionCode("${declName}") + + # No need to deal with optional here; we handled it already + return JSToNativeConversionInfo( + conversionCode, declType=CGGeneric(declType), holderType=holderType + ) + + if type.isByteString(): + assert not isEnforceRange and not isClamp and not isAllowShared + + nullable = toStringBool(type.nullable()) + + conversionCode = fill( + """ + if (!ConvertJSValueToByteString(cx, $${val}, ${nullable}, "${sourceDescription}", $${declName})) { + $*{exceptionCode} + } + """, + nullable=nullable, + sourceDescription=sourceDescription, + exceptionCode=exceptionCode, + ) + + if defaultValue is not None: + if isinstance(defaultValue, IDLNullValue): + assert type.nullable() + defaultCode = "${declName}.SetIsVoid(true);\n" + else: + defaultCode = handleDefaultStringValue( + defaultValue, "${declName}.AssignLiteral" + ) + conversionCode = handleDefault(conversionCode, defaultCode) + + return JSToNativeConversionInfo( + conversionCode, declType=CGGeneric("nsCString"), dealWithOptional=isOptional + ) + + if type.isEnum(): + assert not isEnforceRange and not isClamp and not isAllowShared + + enumName = type.unroll().inner.identifier.name + declType = CGGeneric(enumName) + if type.nullable(): + declType = CGTemplatedType("Nullable", declType) + declType = declType.define() + enumLoc = "${declName}.SetValue()" + else: + enumLoc = "${declName}" + declType = declType.define() + + if invalidEnumValueFatal: + handleInvalidEnumValueCode = "MOZ_ASSERT(index >= 0);\n" + else: + # invalidEnumValueFatal is false only for attributes. So we won't + # have a non-default exceptionCode here unless attribute "arg + # conversion" code starts passing in an exceptionCode. At which + # point we'll need to figure out what that even means. + assert exceptionCode == "return false;\n" + handleInvalidEnumValueCode = dedent( + """ + if (index < 0) { + return true; + } + """ + ) + + template = fill( + """ + { + int index; + if (!FindEnumStringIndex<${invalidEnumValueFatal}>(cx, $${val}, ${values}, "${enumtype}", "${sourceDescription}", &index)) { + $*{exceptionCode} + } + $*{handleInvalidEnumValueCode} + ${enumLoc} = static_cast<${enumtype}>(index); + } + """, + enumtype=enumName, + values=enumName + "Values::" + ENUM_ENTRY_VARIABLE_NAME, + invalidEnumValueFatal=toStringBool(invalidEnumValueFatal), + handleInvalidEnumValueCode=handleInvalidEnumValueCode, + exceptionCode=exceptionCode, + enumLoc=enumLoc, + sourceDescription=sourceDescription, + ) + + setNull = "${declName}.SetNull();\n" + + if type.nullable(): + template = CGIfElseWrapper( + "${val}.isNullOrUndefined()", CGGeneric(setNull), CGGeneric(template) + ).define() + + if defaultValue is not None: + if isinstance(defaultValue, IDLNullValue): + assert type.nullable() + template = handleDefault(template, setNull) + else: + assert defaultValue.type.tag() == IDLType.Tags.domstring + template = handleDefault( + template, + ( + "%s = %s::%s;\n" + % (enumLoc, enumName, getEnumValueName(defaultValue.value)) + ), + ) + return JSToNativeConversionInfo( + template, declType=CGGeneric(declType), dealWithOptional=isOptional + ) + + if type.isCallback(): + assert not isEnforceRange and not isClamp and not isAllowShared + assert not type.treatNonCallableAsNull() or type.nullable() + assert not type.treatNonObjectAsNull() or type.nullable() + assert not type.treatNonObjectAsNull() or not type.treatNonCallableAsNull() + + callback = type.unroll().callback + name = callback.identifier.name + (declType, declArgs, conversion) = getCallbackConversionInfo( + type, callback, isMember, isCallbackReturnValue, isOptional + ) + + if allowTreatNonCallableAsNull and type.treatNonCallableAsNull(): + haveCallable = "JS::IsCallable(&${val}.toObject())" + if not isDefinitelyObject: + haveCallable = "${val}.isObject() && " + haveCallable + if defaultValue is not None: + assert isinstance(defaultValue, IDLNullValue) + haveCallable = "(${haveValue}) && " + haveCallable + template = ( + ("if (%s) {\n" % haveCallable) + conversion + "} else {\n" + " ${declName} = nullptr;\n" + "}\n" + ) + elif allowTreatNonCallableAsNull and type.treatNonObjectAsNull(): + if not isDefinitelyObject: + haveObject = "${val}.isObject()" + if defaultValue is not None: + assert isinstance(defaultValue, IDLNullValue) + haveObject = "(${haveValue}) && " + haveObject + template = CGIfElseWrapper( + haveObject, + CGGeneric(conversion), + CGGeneric("${declName} = nullptr;\n"), + ).define() + else: + template = conversion + else: + template = wrapObjectTemplate( + "if (JS::IsCallable(&${val}.toObject())) {\n" + + conversion + + "} else {\n" + + indent(onFailureNotCallable(failureCode).define()) + + "}\n", + type, + "${declName} = nullptr;\n", + failureCode, + ) + return JSToNativeConversionInfo( + template, declType=declType, declArgs=declArgs, dealWithOptional=isOptional + ) + + if type.isAny(): + assert not isEnforceRange and not isClamp and not isAllowShared + + declArgs = None + if isMember in ("Variadic", "Sequence", "Dictionary", "Record"): + # Rooting is handled by the sequence and dictionary tracers. + declType = "JS::Value" + else: + assert not isMember + declType = "JS::Rooted<JS::Value>" + declArgs = "cx" + + assert not isOptional + templateBody = "${declName} = ${val};\n" + + # For JS-implemented APIs, we refuse to allow passing objects that the + # API consumer does not subsume. The extra parens around + # ($${passedToJSImpl}) suppress unreachable code warnings when + # $${passedToJSImpl} is the literal `false`. But Apple is shipping a + # buggy clang (clang 3.9) in Xcode 8.3, so there even the parens are not + # enough. So we manually disable some warnings in clang. + if ( + not isinstance(descriptorProvider, Descriptor) + or descriptorProvider.interface.isJSImplemented() + ): + templateBody = ( + fill( + """ + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunreachable-code" + #pragma clang diagnostic ignored "-Wunreachable-code-return" + #endif // __clang__ + if (($${passedToJSImpl}) && !CallerSubsumes($${val})) { + cx.ThrowErrorMessage<MSG_PERMISSION_DENIED_TO_PASS_ARG>("${sourceDescription}"); + $*{exceptionCode} + } + #ifdef __clang__ + #pragma clang diagnostic pop + #endif // __clang__ + """, + sourceDescription=sourceDescription, + exceptionCode=exceptionCode, + ) + + templateBody + ) + + # We may not have a default value if we're being converted for + # a setter, say. + if defaultValue: + if isinstance(defaultValue, IDLNullValue): + defaultHandling = "${declName} = JS::NullValue();\n" + else: + assert isinstance(defaultValue, IDLUndefinedValue) + defaultHandling = "${declName} = JS::UndefinedValue();\n" + templateBody = handleDefault(templateBody, defaultHandling) + return JSToNativeConversionInfo( + templateBody, declType=CGGeneric(declType), declArgs=declArgs + ) + + if type.isObject(): + assert not isEnforceRange and not isClamp and not isAllowShared + return handleJSObjectType( + type, isMember, failureCode, exceptionCode, sourceDescription + ) + + if type.isDictionary(): + # There are no nullable dictionary-typed arguments or dictionary-typed + # dictionary members. + assert ( + not type.nullable() + or isCallbackReturnValue + or (isMember and isMember != "Dictionary") + ) + # All optional dictionary-typed arguments always have default values, + # but dictionary-typed dictionary members can be optional. + assert not isOptional or isMember == "Dictionary" + # In the callback return value case we never have to worry + # about a default value; we always have a value. + assert not isCallbackReturnValue or defaultValue is None + + typeName = CGDictionary.makeDictionaryName(type.unroll().inner) + if (not isMember or isMember == "Union") and not isCallbackReturnValue: + # Since we're not a member and not nullable or optional, no one will + # see our real type, so we can do the fast version of the dictionary + # that doesn't pre-initialize members. + typeName = "binding_detail::Fast" + typeName + + declType = CGGeneric(typeName) + + # We do manual default value handling here, because we actually do want + # a jsval, and we only handle the default-dictionary case (which we map + # into initialization with the JS value `null`) anyway + # NOTE: if isNullOrUndefined or isDefinitelyObject are true, + # we know we have a value, so we don't have to worry about the + # default value. + if ( + not isNullOrUndefined + and not isDefinitelyObject + and defaultValue is not None + ): + assert isinstance(defaultValue, IDLDefaultDictionaryValue) + # Initializing from JS null does the right thing to give + # us a default-initialized dictionary. + val = "(${haveValue}) ? ${val} : JS::NullHandleValue" + else: + val = "${val}" + + dictLoc = "${declName}" + if type.nullable(): + dictLoc += ".SetValue()" + + if type.unroll().inner.needsConversionFromJS: + args = "cx, %s, " % val + else: + # We can end up in this case if a dictionary that does not need + # conversion from JS has a dictionary-typed member with a default + # value of {}. + args = "" + conversionCode = fill( + """ + if (!${dictLoc}.Init(${args}"${desc}", $${passedToJSImpl})) { + $*{exceptionCode} + } + """, + dictLoc=dictLoc, + args=args, + desc=firstCap(sourceDescription), + exceptionCode=exceptionCode, + ) + + if failureCode is not None: + # This means we're part of an overload or union conversion, and + # should simply skip stuff if our value is not convertible to + # dictionary, instead of trying and throwing. If we're either + # isDefinitelyObject or isNullOrUndefined then we're convertible to + # dictionary and don't need to check here. + if isDefinitelyObject or isNullOrUndefined: + template = conversionCode + else: + template = fill( + """ + if (!IsConvertibleToDictionary(${val})) { + $*{failureCode} + } + $*{conversionCode} + """, + val=val, + failureCode=failureCode, + conversionCode=conversionCode, + ) + else: + template = conversionCode + + if type.nullable(): + declType = CGTemplatedType("Nullable", declType) + template = CGIfElseWrapper( + "${val}.isNullOrUndefined()", + CGGeneric("${declName}.SetNull();\n"), + CGGeneric(template), + ).define() + + # Dictionary arguments that might contain traceable things need to get + # traced + if (not isMember or isMember == "Union") and isCallbackReturnValue: + # Go ahead and just convert directly into our actual return value + declType = CGWrapper(declType, post="&") + declArgs = "aRetVal" + elif (not isMember or isMember == "Union") and typeNeedsRooting(type): + declType = CGTemplatedType("RootedDictionary", declType) + declArgs = "cx" + else: + declArgs = None + + return JSToNativeConversionInfo( + template, declType=declType, declArgs=declArgs, dealWithOptional=isOptional + ) + + if type.isUndefined(): + assert not isOptional + # This one only happens for return values, and its easy: Just + # ignore the jsval. + return JSToNativeConversionInfo("") + + if not type.isPrimitive(): + raise TypeError("Need conversion for argument type '%s'" % str(type)) + + typeName = builtinNames[type.tag()] + + conversionBehavior = "eDefault" + if isEnforceRange: + assert type.isInteger() + conversionBehavior = "eEnforceRange" + elif isClamp: + assert type.isInteger() + conversionBehavior = "eClamp" + + alwaysNull = False + if type.nullable(): + declType = CGGeneric("Nullable<" + typeName + ">") + writeLoc = "${declName}.SetValue()" + readLoc = "${declName}.Value()" + nullCondition = "${val}.isNullOrUndefined()" + if defaultValue is not None and isinstance(defaultValue, IDLNullValue): + nullCondition = "!(${haveValue}) || " + nullCondition + if isKnownMissing: + alwaysNull = True + template = dedent( + """ + ${declName}.SetNull(); + """ + ) + if not alwaysNull: + template = fill( + """ + if (${nullCondition}) { + $${declName}.SetNull(); + } else if (!ValueToPrimitive<${typeName}, ${conversionBehavior}>(cx, $${val}, "${sourceDescription}", &${writeLoc})) { + $*{exceptionCode} + } + """, + nullCondition=nullCondition, + typeName=typeName, + conversionBehavior=conversionBehavior, + sourceDescription=firstCap(sourceDescription), + writeLoc=writeLoc, + exceptionCode=exceptionCode, + ) + else: + assert defaultValue is None or not isinstance(defaultValue, IDLNullValue) + writeLoc = "${declName}" + readLoc = writeLoc + template = fill( + """ + if (!ValueToPrimitive<${typeName}, ${conversionBehavior}>(cx, $${val}, "${sourceDescription}", &${writeLoc})) { + $*{exceptionCode} + } + """, + typeName=typeName, + conversionBehavior=conversionBehavior, + sourceDescription=firstCap(sourceDescription), + writeLoc=writeLoc, + exceptionCode=exceptionCode, + ) + declType = CGGeneric(typeName) + + if type.isFloat() and not type.isUnrestricted() and not alwaysNull: + if lenientFloatCode is not None: + nonFiniteCode = lenientFloatCode + else: + nonFiniteCode = 'cx.ThrowErrorMessage<MSG_NOT_FINITE>("%s");\n' "%s" % ( + firstCap(sourceDescription), + exceptionCode, + ) + + # We're appending to an if-block brace, so strip trailing whitespace + # and add an extra space before the else. + template = template.rstrip() + template += fill( + """ + else if (!std::isfinite(${readLoc})) { + $*{nonFiniteCode} + } + """, + readLoc=readLoc, + nonFiniteCode=nonFiniteCode, + ) + + if ( + defaultValue is not None + and + # We already handled IDLNullValue, so just deal with the other ones + not isinstance(defaultValue, IDLNullValue) + ): + tag = defaultValue.type.tag() + defaultStr = getHandleDefault(defaultValue) + template = handleDefault(template, "%s = %s;\n" % (writeLoc, defaultStr)) + + return JSToNativeConversionInfo( + template, declType=declType, dealWithOptional=isOptional + ) + + +def instantiateJSToNativeConversion(info, replacements, checkForValue=False): + """ + Take a JSToNativeConversionInfo as returned by getJSToNativeConversionInfo + and a set of replacements as required by the strings in such an object, and + generate code to convert into stack C++ types. + + If checkForValue is True, then the conversion will get wrapped in + a check for ${haveValue}. + """ + templateBody, declType, holderType, dealWithOptional = ( + info.template, + info.declType, + info.holderType, + info.dealWithOptional, + ) + + if dealWithOptional and not checkForValue: + raise TypeError("Have to deal with optional things, but don't know how") + if checkForValue and declType is None: + raise TypeError( + "Need to predeclare optional things, so they will be " + "outside the check for big enough arg count!" + ) + + # We can't precompute our holder constructor arguments, since + # those might depend on ${declName}, which we change below. Just + # compute arguments at the point when we need them as we go. + def getArgsCGThing(args): + return CGGeneric(string.Template(args).substitute(replacements)) + + result = CGList([]) + # Make a copy of "replacements" since we may be about to start modifying it + replacements = dict(replacements) + originalDeclName = replacements["declName"] + if declType is not None: + if dealWithOptional: + replacements["declName"] = "%s.Value()" % originalDeclName + declType = CGTemplatedType("Optional", declType) + declCtorArgs = None + elif info.declArgs is not None: + declCtorArgs = CGWrapper(getArgsCGThing(info.declArgs), pre="(", post=")") + else: + declCtorArgs = None + result.append( + CGList( + [ + declType, + CGGeneric(" "), + CGGeneric(originalDeclName), + declCtorArgs, + CGGeneric(";\n"), + ] + ) + ) + + originalHolderName = replacements["holderName"] + if holderType is not None: + if dealWithOptional: + replacements["holderName"] = "%s.ref()" % originalHolderName + holderType = CGTemplatedType("Maybe", holderType) + holderCtorArgs = None + elif info.holderArgs is not None: + holderCtorArgs = CGWrapper( + getArgsCGThing(info.holderArgs), pre="(", post=")" + ) + else: + holderCtorArgs = None + result.append( + CGList( + [ + holderType, + CGGeneric(" "), + CGGeneric(originalHolderName), + holderCtorArgs, + CGGeneric(";\n"), + ] + ) + ) + + if "maybeMutableVal" not in replacements: + replacements["maybeMutableVal"] = replacements["val"] + + conversion = CGGeneric(string.Template(templateBody).substitute(replacements)) + + if checkForValue: + if dealWithOptional: + declConstruct = CGIndenter( + CGGeneric( + "%s.Construct(%s);\n" + % ( + originalDeclName, + getArgsCGThing(info.declArgs).define() if info.declArgs else "", + ) + ) + ) + if holderType is not None: + holderConstruct = CGIndenter( + CGGeneric( + "%s.emplace(%s);\n" + % ( + originalHolderName, + getArgsCGThing(info.holderArgs).define() + if info.holderArgs + else "", + ) + ) + ) + else: + holderConstruct = None + else: + declConstruct = None + holderConstruct = None + + conversion = CGList( + [ + CGGeneric( + string.Template("if (${haveValue}) {\n").substitute(replacements) + ), + declConstruct, + holderConstruct, + CGIndenter(conversion), + CGGeneric("}\n"), + ] + ) + + result.append(conversion) + return result + + +def convertConstIDLValueToJSVal(value): + if isinstance(value, IDLNullValue): + return "JS::NullValue()" + if isinstance(value, IDLUndefinedValue): + return "JS::UndefinedValue()" + tag = value.type.tag() + if tag in [ + IDLType.Tags.int8, + IDLType.Tags.uint8, + IDLType.Tags.int16, + IDLType.Tags.uint16, + IDLType.Tags.int32, + ]: + return "JS::Int32Value(%s)" % (value.value) + if tag == IDLType.Tags.uint32: + return "JS::NumberValue(%sU)" % (value.value) + if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]: + return "JS::CanonicalizedDoubleValue(%s)" % numericValue(tag, value.value) + if tag == IDLType.Tags.bool: + return "JS::BooleanValue(%s)" % (toStringBool(value.value)) + if tag in [IDLType.Tags.float, IDLType.Tags.double]: + return "JS::CanonicalizedDoubleValue(%s)" % (value.value) + raise TypeError("Const value of unhandled type: %s" % value.type) + + +class CGArgumentConverter(CGThing): + """ + A class that takes an IDL argument object and its index in the + argument list and generates code to unwrap the argument to the + right native type. + + argDescription is a description of the argument for error-reporting + purposes. Callers should assume that it might get placed in the middle of a + sentence. If it ends up at the beginning of a sentence, its first character + will be automatically uppercased. + """ + + def __init__( + self, + argument, + index, + descriptorProvider, + argDescription, + member, + invalidEnumValueFatal=True, + lenientFloatCode=None, + ): + CGThing.__init__(self) + self.argument = argument + self.argDescription = argDescription + assert not argument.defaultValue or argument.optional + + replacer = {"index": index, "argc": "args.length()"} + self.replacementVariables = { + "declName": "arg%d" % index, + "holderName": ("arg%d" % index) + "_holder", + "obj": "obj", + "passedToJSImpl": toStringBool( + isJSImplementedDescriptor(descriptorProvider) + ), + } + # If we have a method generated by the maplike/setlike portion of an + # interface, arguments can possibly be undefined, but will need to be + # converted to the key/value type of the backing object. In this case, + # use .get() instead of direct access to the argument. This won't + # matter for iterable since generated functions for those interface + # don't take arguments. + if member.isMethod() and member.isMaplikeOrSetlikeOrIterableMethod(): + self.replacementVariables["val"] = string.Template( + "args.get(${index})" + ).substitute(replacer) + self.replacementVariables["maybeMutableVal"] = string.Template( + "args[${index}]" + ).substitute(replacer) + else: + self.replacementVariables["val"] = string.Template( + "args[${index}]" + ).substitute(replacer) + haveValueCheck = string.Template("args.hasDefined(${index})").substitute( + replacer + ) + self.replacementVariables["haveValue"] = haveValueCheck + self.descriptorProvider = descriptorProvider + if self.argument.canHaveMissingValue(): + self.argcAndIndex = replacer + else: + self.argcAndIndex = None + self.invalidEnumValueFatal = invalidEnumValueFatal + self.lenientFloatCode = lenientFloatCode + + def define(self): + typeConversion = getJSToNativeConversionInfo( + self.argument.type, + self.descriptorProvider, + isOptional=(self.argcAndIndex is not None and not self.argument.variadic), + invalidEnumValueFatal=self.invalidEnumValueFatal, + defaultValue=self.argument.defaultValue, + lenientFloatCode=self.lenientFloatCode, + isMember="Variadic" if self.argument.variadic else False, + allowTreatNonCallableAsNull=self.argument.allowTreatNonCallableAsNull(), + sourceDescription=self.argDescription, + ) + + if not self.argument.variadic: + return instantiateJSToNativeConversion( + typeConversion, self.replacementVariables, self.argcAndIndex is not None + ).define() + + # Variadic arguments get turned into a sequence. + if typeConversion.dealWithOptional: + raise TypeError("Shouldn't have optional things in variadics") + if typeConversion.holderType is not None: + raise TypeError("Shouldn't need holders for variadics") + + replacer = dict(self.argcAndIndex, **self.replacementVariables) + replacer["seqType"] = CGTemplatedType( + "AutoSequence", typeConversion.declType + ).define() + if typeNeedsRooting(self.argument.type): + rooterDecl = ( + "SequenceRooter<%s> ${holderName}(cx, &${declName});\n" + % typeConversion.declType.define() + ) + else: + rooterDecl = "" + replacer["elemType"] = typeConversion.declType.define() + + replacer["elementInitializer"] = initializerForType(self.argument.type) or "" + + # NOTE: Keep this in sync with sequence conversions as needed + variadicConversion = string.Template( + "${seqType} ${declName};\n" + + rooterDecl + + dedent( + """ + if (${argc} > ${index}) { + if (!${declName}.SetCapacity(${argc} - ${index}, mozilla::fallible)) { + JS_ReportOutOfMemory(cx); + return false; + } + for (uint32_t variadicArg = ${index}; variadicArg < ${argc}; ++variadicArg) { + // OK to do infallible append here, since we ensured capacity already. + ${elemType}& slot = *${declName}.AppendElement(${elementInitializer}); + """ + ) + ).substitute(replacer) + + val = string.Template("args[variadicArg]").substitute(replacer) + variadicConversion += indent( + string.Template(typeConversion.template).substitute( + { + "val": val, + "maybeMutableVal": val, + "declName": "slot", + # We only need holderName here to handle isExternal() + # interfaces, which use an internal holder for the + # conversion even when forceOwningType ends up true. + "holderName": "tempHolder", + # Use the same ${obj} as for the variadic arg itself + "obj": replacer["obj"], + "passedToJSImpl": toStringBool( + isJSImplementedDescriptor(self.descriptorProvider) + ), + } + ), + 4, + ) + + variadicConversion += " }\n" "}\n" + return variadicConversion + + +def getMaybeWrapValueFuncForType(type): + if type.isJSString(): + return "MaybeWrapStringValue" + # Callbacks might actually be DOM objects; nothing prevents a page from + # doing that. + if type.isCallback() or type.isCallbackInterface() or type.isObject(): + if type.nullable(): + return "MaybeWrapObjectOrNullValue" + return "MaybeWrapObjectValue" + # SpiderMonkey interfaces are never DOM objects. Neither are sequences or + # dictionaries, since those are always plain JS objects. + if type.isSpiderMonkeyInterface() or type.isDictionary() or type.isSequence(): + if type.nullable(): + return "MaybeWrapNonDOMObjectOrNullValue" + return "MaybeWrapNonDOMObjectValue" + if type.isAny(): + return "MaybeWrapValue" + + # For other types, just go ahead an fall back on MaybeWrapValue for now: + # it's always safe to do, and shouldn't be particularly slow for any of + # them + return "MaybeWrapValue" + + +sequenceWrapLevel = 0 +recordWrapLevel = 0 + + +def getWrapTemplateForType( + type, + descriptorProvider, + result, + successCode, + returnsNewObject, + exceptionCode, + spiderMonkeyInterfacesAreStructs, + isConstructorRetval=False, +): + """ + Reflect a C++ value stored in "result", of IDL type "type" into JS. The + "successCode" is the code to run once we have successfully done the + conversion and must guarantee that execution of the conversion template + stops once the successCode has executed (e.g. by doing a 'return', or by + doing a 'break' if the entire conversion template is inside a block that + the 'break' will exit). + + If spiderMonkeyInterfacesAreStructs is true, then if the type is a + SpiderMonkey interface, "result" is one of the + dom::SpiderMonkeyInterfaceObjectStorage subclasses, not a JSObject*. + + The resulting string should be used with string.Template. It + needs the following keys when substituting: + + jsvalHandle: something that can be passed to methods taking a + JS::MutableHandle<JS::Value>. This can be a + JS::MutableHandle<JS::Value> or a JS::Rooted<JS::Value>*. + jsvalRef: something that can have .address() called on it to get a + JS::Value* and .set() called on it to set it to a JS::Value. + This can be a JS::MutableHandle<JS::Value> or a + JS::Rooted<JS::Value>. + obj: a JS::Handle<JSObject*>. + + Returns (templateString, infallibility of conversion template) + """ + if successCode is None: + successCode = "return true;\n" + + def setUndefined(): + return _setValue("", setter="setUndefined") + + def setNull(): + return _setValue("", setter="setNull") + + def setInt32(value): + return _setValue(value, setter="setInt32") + + def setString(value): + return _setValue(value, wrapAsType=type, setter="setString") + + def setObject(value, wrapAsType=None): + return _setValue(value, wrapAsType=wrapAsType, setter="setObject") + + def setObjectOrNull(value, wrapAsType=None): + return _setValue(value, wrapAsType=wrapAsType, setter="setObjectOrNull") + + def setUint32(value): + return _setValue(value, setter="setNumber") + + def setDouble(value): + return _setValue("JS_NumberValue(%s)" % value) + + def setBoolean(value): + return _setValue(value, setter="setBoolean") + + def _setValue(value, wrapAsType=None, setter="set"): + """ + Returns the code to set the jsval to value. + + If wrapAsType is not None, then will wrap the resulting value using the + function that getMaybeWrapValueFuncForType(wrapAsType) returns. + Otherwise, no wrapping will be done. + """ + if wrapAsType is None: + tail = successCode + else: + tail = fill( + """ + if (!${maybeWrap}(cx, $${jsvalHandle})) { + $*{exceptionCode} + } + $*{successCode} + """, + maybeWrap=getMaybeWrapValueFuncForType(wrapAsType), + exceptionCode=exceptionCode, + successCode=successCode, + ) + return ("${jsvalRef}.%s(%s);\n" % (setter, value)) + tail + + def wrapAndSetPtr(wrapCall, failureCode=None): + """ + Returns the code to set the jsval by calling "wrapCall". "failureCode" + is the code to run if calling "wrapCall" fails + """ + if failureCode is None: + failureCode = exceptionCode + return fill( + """ + if (!${wrapCall}) { + $*{failureCode} + } + $*{successCode} + """, + wrapCall=wrapCall, + failureCode=failureCode, + successCode=successCode, + ) + + if type is None or type.isUndefined(): + return (setUndefined(), True) + + if (type.isSequence() or type.isRecord()) and type.nullable(): + # These are both wrapped in Nullable<> + recTemplate, recInfall = getWrapTemplateForType( + type.inner, + descriptorProvider, + "%s.Value()" % result, + successCode, + returnsNewObject, + exceptionCode, + spiderMonkeyInterfacesAreStructs, + ) + code = fill( + """ + + if (${result}.IsNull()) { + $*{setNull} + } + $*{recTemplate} + """, + result=result, + setNull=setNull(), + recTemplate=recTemplate, + ) + return code, recInfall + + if type.isSequence(): + # Now do non-nullable sequences. Our success code is just to break to + # where we set the element in the array. Note that we bump the + # sequenceWrapLevel around this call so that nested sequence conversions + # will use different iteration variables. + global sequenceWrapLevel + index = "sequenceIdx%d" % sequenceWrapLevel + sequenceWrapLevel += 1 + innerTemplate = wrapForType( + type.inner, + descriptorProvider, + { + "result": "%s[%s]" % (result, index), + "successCode": "break;\n", + "jsvalRef": "tmp", + "jsvalHandle": "&tmp", + "returnsNewObject": returnsNewObject, + "exceptionCode": exceptionCode, + "obj": "returnArray", + "spiderMonkeyInterfacesAreStructs": spiderMonkeyInterfacesAreStructs, + }, + ) + sequenceWrapLevel -= 1 + code = fill( + """ + + uint32_t length = ${result}.Length(); + JS::Rooted<JSObject*> returnArray(cx, JS::NewArrayObject(cx, length)); + if (!returnArray) { + $*{exceptionCode} + } + // Scope for 'tmp' + { + JS::Rooted<JS::Value> tmp(cx); + for (uint32_t ${index} = 0; ${index} < length; ++${index}) { + // Control block to let us common up the JS_DefineElement calls when there + // are different ways to succeed at wrapping the object. + do { + $*{innerTemplate} + } while (false); + if (!JS_DefineElement(cx, returnArray, ${index}, tmp, + JSPROP_ENUMERATE)) { + $*{exceptionCode} + } + } + } + $*{set} + """, + result=result, + exceptionCode=exceptionCode, + index=index, + innerTemplate=innerTemplate, + set=setObject("*returnArray"), + ) + + return (code, False) + + if type.isRecord(): + # Now do non-nullable record. Our success code is just to break to + # where we define the property on the object. Note that we bump the + # recordWrapLevel around this call so that nested record conversions + # will use different temp value names. + global recordWrapLevel + valueName = "recordValue%d" % recordWrapLevel + recordWrapLevel += 1 + innerTemplate = wrapForType( + type.inner, + descriptorProvider, + { + "result": valueName, + "successCode": "break;\n", + "jsvalRef": "tmp", + "jsvalHandle": "&tmp", + "returnsNewObject": returnsNewObject, + "exceptionCode": exceptionCode, + "obj": "returnObj", + "spiderMonkeyInterfacesAreStructs": spiderMonkeyInterfacesAreStructs, + }, + ) + recordWrapLevel -= 1 + if type.keyType.isByteString(): + # There is no length-taking JS_DefineProperty. So to keep + # things sane with embedded nulls, we want to byte-inflate + # to an nsAString. The only byte-inflation function we + # have around is AppendASCIItoUTF16, which luckily doesn't + # assert anything about the input being ASCII. + expandedKeyDecl = "NS_ConvertASCIItoUTF16 expandedKey(entry.mKey);\n" + keyName = "expandedKey" + elif type.keyType.isUTF8String(): + # We do the same as above for utf8 strings. We could do better if + # we had a DefineProperty API that takes utf-8 property names. + expandedKeyDecl = "NS_ConvertUTF8toUTF16 expandedKey(entry.mKey);\n" + keyName = "expandedKey" + else: + expandedKeyDecl = "" + keyName = "entry.mKey" + + code = fill( + """ + + JS::Rooted<JSObject*> returnObj(cx, JS_NewPlainObject(cx)); + if (!returnObj) { + $*{exceptionCode} + } + // Scope for 'tmp' + { + JS::Rooted<JS::Value> tmp(cx); + for (auto& entry : ${result}.Entries()) { + auto& ${valueName} = entry.mValue; + // Control block to let us common up the JS_DefineUCProperty calls when there + // are different ways to succeed at wrapping the value. + do { + $*{innerTemplate} + } while (false); + $*{expandedKeyDecl} + if (!JS_DefineUCProperty(cx, returnObj, + ${keyName}.BeginReading(), + ${keyName}.Length(), tmp, + JSPROP_ENUMERATE)) { + $*{exceptionCode} + } + } + } + $*{set} + """, + result=result, + exceptionCode=exceptionCode, + valueName=valueName, + innerTemplate=innerTemplate, + expandedKeyDecl=expandedKeyDecl, + keyName=keyName, + set=setObject("*returnObj"), + ) + + return (code, False) + + if type.isPromise(): + assert not type.nullable() + # The use of ToJSValue here is a bit annoying because the Promise + # version is not inlined. But we can't put an inline version in either + # ToJSValue.h or BindingUtils.h, because Promise.h includes ToJSValue.h + # and that includes BindingUtils.h, so we'd get an include loop if + # either of those headers included Promise.h. And trying to write the + # conversion by hand here is pretty annoying because we have to handle + # the various RefPtr, rawptr, NonNull, etc cases, which ToJSValue will + # handle for us. So just eat the cost of the function call. + return (wrapAndSetPtr("ToJSValue(cx, %s, ${jsvalHandle})" % result), False) + + if type.isGeckoInterface() and not type.isCallbackInterface(): + descriptor = descriptorProvider.getDescriptor( + type.unroll().inner.identifier.name + ) + if type.nullable(): + if descriptor.interface.identifier.name == "WindowProxy": + template, infal = getWrapTemplateForType( + type.inner, + descriptorProvider, + "%s.Value()" % result, + successCode, + returnsNewObject, + exceptionCode, + spiderMonkeyInterfacesAreStructs, + ) + return ( + "if (%s.IsNull()) {\n" % result + + indent(setNull()) + + "}\n" + + template, + infal, + ) + + wrappingCode = "if (!%s) {\n" % (result) + indent(setNull()) + "}\n" + else: + wrappingCode = "" + + if not descriptor.interface.isExternal(): + if descriptor.wrapperCache: + wrapMethod = "GetOrCreateDOMReflector" + wrapArgs = "cx, %s, ${jsvalHandle}" % result + else: + wrapMethod = "WrapNewBindingNonWrapperCachedObject" + wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result + if isConstructorRetval: + wrapArgs += ", desiredProto" + wrap = "%s(%s)" % (wrapMethod, wrapArgs) + # Can only fail to wrap as a new-binding object if they already + # threw an exception. + failed = "MOZ_ASSERT(JS_IsExceptionPending(cx));\n" + exceptionCode + else: + if descriptor.notflattened: + getIID = "&NS_GET_IID(%s), " % descriptor.nativeType + else: + getIID = "" + wrap = "WrapObject(cx, %s, %s${jsvalHandle})" % (result, getIID) + failed = None + + wrappingCode += wrapAndSetPtr(wrap, failed) + return (wrappingCode, False) + + if type.isJSString(): + return (setString(result), False) + + if type.isDOMString() or type.isUSVString(): + if type.nullable(): + return ( + wrapAndSetPtr("xpc::StringToJsval(cx, %s, ${jsvalHandle})" % result), + False, + ) + else: + return ( + wrapAndSetPtr( + "xpc::NonVoidStringToJsval(cx, %s, ${jsvalHandle})" % result + ), + False, + ) + + if type.isByteString(): + if type.nullable(): + return ( + wrapAndSetPtr("ByteStringToJsval(cx, %s, ${jsvalHandle})" % result), + False, + ) + else: + return ( + wrapAndSetPtr( + "NonVoidByteStringToJsval(cx, %s, ${jsvalHandle})" % result + ), + False, + ) + + if type.isUTF8String(): + if type.nullable(): + return ( + wrapAndSetPtr("UTF8StringToJsval(cx, %s, ${jsvalHandle})" % result), + False, + ) + else: + return ( + wrapAndSetPtr( + "NonVoidUTF8StringToJsval(cx, %s, ${jsvalHandle})" % result + ), + False, + ) + + if type.isEnum(): + if type.nullable(): + resultLoc = "%s.Value()" % result + else: + resultLoc = result + conversion = fill( + """ + if (!ToJSValue(cx, ${result}, $${jsvalHandle})) { + $*{exceptionCode} + } + $*{successCode} + """, + result=resultLoc, + exceptionCode=exceptionCode, + successCode=successCode, + ) + + if type.nullable(): + conversion = CGIfElseWrapper( + "%s.IsNull()" % result, CGGeneric(setNull()), CGGeneric(conversion) + ).define() + return conversion, False + + if type.isCallback() or type.isCallbackInterface(): + # Callbacks can store null if we nuked the compartments their + # objects lived in. + wrapCode = setObjectOrNull( + "GetCallbackFromCallbackObject(cx, %(result)s)", wrapAsType=type + ) + if type.nullable(): + wrapCode = ( + "if (%(result)s) {\n" + + indent(wrapCode) + + "} else {\n" + + indent(setNull()) + + "}\n" + ) + wrapCode = wrapCode % {"result": result} + return wrapCode, False + + if type.isAny(): + # See comments in GetOrCreateDOMReflector explaining why we need + # to wrap here. + # NB: _setValue(..., type-that-is-any) calls JS_WrapValue(), so is fallible + head = "JS::ExposeValueToActiveJS(%s);\n" % result + return (head + _setValue(result, wrapAsType=type), False) + + if type.isObject() or ( + type.isSpiderMonkeyInterface() and not spiderMonkeyInterfacesAreStructs + ): + # See comments in GetOrCreateDOMReflector explaining why we need + # to wrap here. + if type.nullable(): + toValue = "%s" + setter = setObjectOrNull + head = """if (%s) { + JS::ExposeObjectToActiveJS(%s); + } + """ % ( + result, + result, + ) + else: + toValue = "*%s" + setter = setObject + head = "JS::ExposeObjectToActiveJS(%s);\n" % result + # NB: setObject{,OrNull}(..., some-object-type) calls JS_WrapValue(), so is fallible + return (head + setter(toValue % result, wrapAsType=type), False) + + if type.isObservableArray(): + # This first argument isn't used at all for now, the attribute getter + # for ObservableArray type are generated in getObservableArrayGetterBody + # instead. + return "", False + + if not ( + type.isUnion() + or type.isPrimitive() + or type.isDictionary() + or (type.isSpiderMonkeyInterface() and spiderMonkeyInterfacesAreStructs) + ): + raise TypeError("Need to learn to wrap %s" % type) + + if type.nullable(): + recTemplate, recInfal = getWrapTemplateForType( + type.inner, + descriptorProvider, + "%s.Value()" % result, + successCode, + returnsNewObject, + exceptionCode, + spiderMonkeyInterfacesAreStructs, + ) + return ( + "if (%s.IsNull()) {\n" % result + indent(setNull()) + "}\n" + recTemplate, + recInfal, + ) + + if type.isSpiderMonkeyInterface(): + assert spiderMonkeyInterfacesAreStructs + # See comments in GetOrCreateDOMReflector explaining why we need + # to wrap here. + # NB: setObject(..., some-object-type) calls JS_WrapValue(), so is fallible + return (setObject("*%s.Obj()" % result, wrapAsType=type), False) + + if type.isUnion(): + return (wrapAndSetPtr("%s.ToJSVal(cx, ${obj}, ${jsvalHandle})" % result), False) + + if type.isDictionary(): + return ( + wrapAndSetPtr("%s.ToObjectInternal(cx, ${jsvalHandle})" % result), + False, + ) + + tag = type.tag() + + if tag in [ + IDLType.Tags.int8, + IDLType.Tags.uint8, + IDLType.Tags.int16, + IDLType.Tags.uint16, + IDLType.Tags.int32, + ]: + return (setInt32("int32_t(%s)" % result), True) + + elif tag in [ + IDLType.Tags.int64, + IDLType.Tags.uint64, + IDLType.Tags.unrestricted_float, + IDLType.Tags.float, + IDLType.Tags.unrestricted_double, + IDLType.Tags.double, + ]: + # XXXbz will cast to double do the "even significand" thing that webidl + # calls for for 64-bit ints? Do we care? + return (setDouble("double(%s)" % result), True) + + elif tag == IDLType.Tags.uint32: + return (setUint32(result), True) + + elif tag == IDLType.Tags.bool: + return (setBoolean(result), True) + + else: + raise TypeError("Need to learn to wrap primitive: %s" % type) + + +def wrapForType(type, descriptorProvider, templateValues): + """ + Reflect a C++ value of IDL type "type" into JS. TemplateValues is a dict + that should contain: + + * 'jsvalRef': something that can have .address() called on it to get a + JS::Value* and .set() called on it to set it to a JS::Value. + This can be a JS::MutableHandle<JS::Value> or a + JS::Rooted<JS::Value>. + * 'jsvalHandle': something that can be passed to methods taking a + JS::MutableHandle<JS::Value>. This can be a + JS::MutableHandle<JS::Value> or a JS::Rooted<JS::Value>*. + * 'obj' (optional): the name of the variable that contains the JSObject to + use as a scope when wrapping, if not supplied 'obj' + will be used as the name + * 'result' (optional): the name of the variable in which the C++ value is + stored, if not supplied 'result' will be used as + the name + * 'successCode' (optional): the code to run once we have successfully + done the conversion, if not supplied 'return + true;' will be used as the code. The + successCode must ensure that once it runs no + more of the conversion template will be + executed (e.g. by doing a 'return' or 'break' + as appropriate). + * 'returnsNewObject' (optional): If true, we're wrapping for the return + value of a [NewObject] method. Assumed + false if not set. + * 'exceptionCode' (optional): Code to run when a JS exception is thrown. + The default is "return false;". The code + passed here must return. + * 'isConstructorRetval' (optional): If true, we're wrapping a constructor + return value. + """ + wrap = getWrapTemplateForType( + type, + descriptorProvider, + templateValues.get("result", "result"), + templateValues.get("successCode", None), + templateValues.get("returnsNewObject", False), + templateValues.get("exceptionCode", "return false;\n"), + templateValues.get("spiderMonkeyInterfacesAreStructs", False), + isConstructorRetval=templateValues.get("isConstructorRetval", False), + )[0] + + defaultValues = {"obj": "obj"} + return string.Template(wrap).substitute(defaultValues, **templateValues) + + +def infallibleForMember(member, type, descriptorProvider): + """ + Determine the fallibility of changing a C++ value of IDL type "type" into + JS for the given attribute. Apart from returnsNewObject, all the defaults + are used, since the fallbility does not change based on the boolean values, + and the template will be discarded. + + CURRENT ASSUMPTIONS: + We assume that successCode for wrapping up return values cannot contain + failure conditions. + """ + return getWrapTemplateForType( + type, + descriptorProvider, + "result", + None, + memberReturnsNewObject(member), + "return false;\n", + False, + )[1] + + +def leafTypeNeedsCx(type, retVal): + return ( + type.isAny() + or type.isObject() + or type.isJSString() + or (retVal and type.isSpiderMonkeyInterface()) + ) + + +def leafTypeNeedsScopeObject(type, retVal): + return retVal and type.isSpiderMonkeyInterface() + + +def leafTypeNeedsRooting(type): + return leafTypeNeedsCx(type, False) or type.isSpiderMonkeyInterface() + + +def typeNeedsRooting(type): + return typeMatchesLambda(type, lambda t: leafTypeNeedsRooting(t)) + + +def typeNeedsCx(type, retVal=False): + return typeMatchesLambda(type, lambda t: leafTypeNeedsCx(t, retVal)) + + +def typeNeedsScopeObject(type, retVal=False): + return typeMatchesLambda(type, lambda t: leafTypeNeedsScopeObject(t, retVal)) + + +def typeMatchesLambda(type, func): + if type is None: + return False + if type.nullable(): + return typeMatchesLambda(type.inner, func) + if type.isSequence() or type.isRecord(): + return typeMatchesLambda(type.inner, func) + if type.isUnion(): + return any(typeMatchesLambda(t, func) for t in type.unroll().flatMemberTypes) + if type.isDictionary(): + return dictionaryMatchesLambda(type.inner, func) + return func(type) + + +def dictionaryMatchesLambda(dictionary, func): + return any(typeMatchesLambda(m.type, func) for m in dictionary.members) or ( + dictionary.parent and dictionaryMatchesLambda(dictionary.parent, func) + ) + + +# Whenever this is modified, please update CGNativeMember.getRetvalInfo as +# needed to keep the types compatible. +def getRetvalDeclarationForType(returnType, descriptorProvider, isMember=False): + """ + Returns a tuple containing five things: + + 1) A CGThing for the type of the return value, or None if there is no need + for a return value. + + 2) A value indicating the kind of ourparam to pass the value as. Valid + options are None to not pass as an out param at all, "ref" (to pass a + reference as an out param), and "ptr" (to pass a pointer as an out + param). + + 3) A CGThing for a tracer for the return value, or None if no tracing is + needed. + + 4) An argument string to pass to the retval declaration + constructor or None if there are no arguments. + + 5) The name of a function that needs to be called with the return value + before using it, or None if no function needs to be called. + """ + if returnType is None or returnType.isUndefined(): + # Nothing to declare + return None, None, None, None, None + if returnType.isPrimitive() and returnType.tag() in builtinNames: + result = CGGeneric(builtinNames[returnType.tag()]) + if returnType.nullable(): + result = CGTemplatedType("Nullable", result) + return result, None, None, None, None + if returnType.isJSString(): + if isMember: + raise TypeError("JSString not supported as return type member") + return CGGeneric("JS::Rooted<JSString*>"), "ptr", None, "cx", None + if returnType.isDOMString() or returnType.isUSVString(): + if isMember: + return CGGeneric("nsString"), "ref", None, None, None + return CGGeneric("DOMString"), "ref", None, None, None + if returnType.isByteString() or returnType.isUTF8String(): + if isMember: + return CGGeneric("nsCString"), "ref", None, None, None + return CGGeneric("nsAutoCString"), "ref", None, None, None + if returnType.isEnum(): + result = CGGeneric(returnType.unroll().inner.identifier.name) + if returnType.nullable(): + result = CGTemplatedType("Nullable", result) + return result, None, None, None, None + if returnType.isGeckoInterface() or returnType.isPromise(): + if returnType.isGeckoInterface(): + typeName = returnType.unroll().inner.identifier.name + if typeName == "WindowProxy": + result = CGGeneric("WindowProxyHolder") + if returnType.nullable(): + result = CGTemplatedType("Nullable", result) + return result, None, None, None, None + + typeName = descriptorProvider.getDescriptor(typeName).nativeType + else: + typeName = "Promise" + if isMember: + conversion = None + result = CGGeneric("StrongPtrForMember<%s>" % typeName) + else: + conversion = CGGeneric("StrongOrRawPtr<%s>" % typeName) + result = CGGeneric("auto") + return result, None, None, None, conversion + if returnType.isCallback(): + name = returnType.unroll().callback.identifier.name + return CGGeneric("RefPtr<%s>" % name), None, None, None, None + if returnType.isAny(): + if isMember: + return CGGeneric("JS::Value"), None, None, None, None + return CGGeneric("JS::Rooted<JS::Value>"), "ptr", None, "cx", None + if returnType.isObject() or returnType.isSpiderMonkeyInterface(): + if isMember: + return CGGeneric("JSObject*"), None, None, None, None + return CGGeneric("JS::Rooted<JSObject*>"), "ptr", None, "cx", None + if returnType.isSequence(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + result, _, _, _, _ = getRetvalDeclarationForType( + returnType.inner, descriptorProvider, isMember="Sequence" + ) + # While we have our inner type, set up our rooter, if needed + if not isMember and typeNeedsRooting(returnType): + rooter = CGGeneric( + "SequenceRooter<%s > resultRooter(cx, &result);\n" % result.define() + ) + else: + rooter = None + result = CGTemplatedType("nsTArray", result) + if nullable: + result = CGTemplatedType("Nullable", result) + return result, "ref", rooter, None, None + if returnType.isRecord(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + result, _, _, _, _ = getRetvalDeclarationForType( + returnType.inner, descriptorProvider, isMember="Record" + ) + # While we have our inner type, set up our rooter, if needed + if not isMember and typeNeedsRooting(returnType): + rooter = CGGeneric( + "RecordRooter<%s> resultRooter(cx, &result);\n" + % ("nsString, " + result.define()) + ) + else: + rooter = None + result = CGTemplatedType("Record", [recordKeyDeclType(returnType), result]) + if nullable: + result = CGTemplatedType("Nullable", result) + return result, "ref", rooter, None, None + if returnType.isDictionary(): + nullable = returnType.nullable() + dictName = CGDictionary.makeDictionaryName(returnType.unroll().inner) + result = CGGeneric(dictName) + if not isMember and typeNeedsRooting(returnType): + if nullable: + result = CGTemplatedType("NullableRootedDictionary", result) + else: + result = CGTemplatedType("RootedDictionary", result) + resultArgs = "cx" + else: + if nullable: + result = CGTemplatedType("Nullable", result) + resultArgs = None + return result, "ref", None, resultArgs, None + if returnType.isUnion(): + result = CGGeneric(CGUnionStruct.unionTypeName(returnType.unroll(), True)) + if not isMember and typeNeedsRooting(returnType): + if returnType.nullable(): + result = CGTemplatedType("NullableRootedUnion", result) + else: + result = CGTemplatedType("RootedUnion", result) + resultArgs = "cx" + else: + if returnType.nullable(): + result = CGTemplatedType("Nullable", result) + resultArgs = None + return result, "ref", None, resultArgs, None + raise TypeError("Don't know how to declare return value for %s" % returnType) + + +def needCx(returnType, arguments, extendedAttributes, considerTypes, static=False): + return ( + not static + and considerTypes + and ( + typeNeedsCx(returnType, True) or any(typeNeedsCx(a.type) for a in arguments) + ) + or "implicitJSContext" in extendedAttributes + ) + + +def needScopeObject( + returnType, arguments, extendedAttributes, isWrapperCached, considerTypes, isMember +): + """ + isMember should be true if we're dealing with an attribute + annotated as [StoreInSlot]. + """ + return ( + considerTypes + and not isWrapperCached + and ( + (not isMember and typeNeedsScopeObject(returnType, True)) + or any(typeNeedsScopeObject(a.type) for a in arguments) + ) + ) + + +def callerTypeGetterForDescriptor(descriptor): + if descriptor.interface.isExposedInAnyWorker(): + systemCallerGetter = "nsContentUtils::ThreadsafeIsSystemCaller" + else: + systemCallerGetter = "nsContentUtils::IsSystemCaller" + return "%s(cx) ? CallerType::System : CallerType::NonSystem" % systemCallerGetter + + +class CGCallGenerator(CGThing): + """ + A class to generate an actual call to a C++ object. Assumes that the C++ + object is stored in a variable whose name is given by the |object| argument. + + needsCallerType is a boolean indicating whether the call should receive + a PrincipalType for the caller. + + needsErrorResult is a boolean indicating whether the call should be + fallible and thus needs ErrorResult parameter. + + resultVar: If the returnType is not void, then the result of the call is + stored in a C++ variable named by resultVar. The caller is responsible for + declaring the result variable. If the caller doesn't care about the result + value, resultVar can be omitted. + + context: The context string to pass to MaybeSetPendingException. + """ + + def __init__( + self, + needsErrorResult, + needsCallerType, + isChromeOnly, + arguments, + argsPre, + returnType, + extendedAttributes, + descriptor, + nativeMethodName, + static, + object="self", + argsPost=[], + resultVar=None, + context="nullptr", + ): + CGThing.__init__(self) + + ( + result, + resultOutParam, + resultRooter, + resultArgs, + resultConversion, + ) = getRetvalDeclarationForType(returnType, descriptor) + + args = CGList([CGGeneric(arg) for arg in argsPre], ", ") + for a, name in arguments: + arg = CGGeneric(name) + + # Now constify the things that need it + def needsConst(a): + if a.type.isDictionary(): + return True + if a.type.isSequence(): + return True + if a.type.isRecord(): + return True + # isObject() types are always a JS::Rooted, whether + # nullable or not, and it turns out a const JS::Rooted + # is not very helpful at all (in particular, it won't + # even convert to a JS::Handle). + # XXX bz Well, why not??? + if a.type.nullable() and not a.type.isObject(): + return True + if a.type.isString(): + return True + if a.canHaveMissingValue(): + # This will need an Optional or it's a variadic; + # in both cases it should be const. + return True + if a.type.isUnion(): + return True + if a.type.isSpiderMonkeyInterface(): + return True + return False + + if needsConst(a): + arg = CGWrapper(arg, pre="Constify(", post=")") + # And convert NonNull<T> to T& + if ( + (a.type.isGeckoInterface() or a.type.isCallback() or a.type.isPromise()) + and not a.type.nullable() + ) or a.type.isDOMString(): + arg = CGWrapper(arg, pre="NonNullHelper(", post=")") + + # If it's a refcounted object, let the static analysis know it's + # alive for the duration of the call. + if a.type.isGeckoInterface() or a.type.isCallback(): + arg = CGWrapper(arg, pre="MOZ_KnownLive(", post=")") + + args.append(arg) + + needResultDecl = False + + # Build up our actual call + self.cgRoot = CGList([]) + + # Return values that go in outparams go here + if resultOutParam is not None: + if resultVar is None: + needResultDecl = True + resultVar = "result" + if resultOutParam == "ref": + args.append(CGGeneric(resultVar)) + else: + assert resultOutParam == "ptr" + args.append(CGGeneric("&" + resultVar)) + + needsSubjectPrincipal = "needsSubjectPrincipal" in extendedAttributes + if needsSubjectPrincipal: + needsNonSystemPrincipal = ( + "needsNonSystemSubjectPrincipal" in extendedAttributes + ) + if needsNonSystemPrincipal: + checkPrincipal = dedent( + """ + if (principal->IsSystemPrincipal()) { + principal = nullptr; + } + """ + ) + else: + checkPrincipal = "" + + getPrincipal = fill( + """ + JS::Realm* realm = js::GetContextRealm(cx); + MOZ_ASSERT(realm); + JSPrincipals* principals = JS::GetRealmPrincipals(realm); + nsIPrincipal* principal = nsJSPrincipals::get(principals); + ${checkPrincipal} + """, + checkPrincipal=checkPrincipal, + ) + + if descriptor.interface.isExposedInAnyWorker(): + self.cgRoot.append( + CGGeneric( + fill( + """ + Maybe<nsIPrincipal*> subjectPrincipal; + if (NS_IsMainThread()) { + $*{getPrincipal} + subjectPrincipal.emplace(principal); + } + """, + getPrincipal=getPrincipal, + ) + ) + ) + subjectPrincipalArg = "subjectPrincipal" + else: + if needsNonSystemPrincipal: + principalType = "nsIPrincipal*" + subjectPrincipalArg = "subjectPrincipal" + else: + principalType = "NonNull<nsIPrincipal>" + subjectPrincipalArg = "NonNullHelper(subjectPrincipal)" + + self.cgRoot.append( + CGGeneric( + fill( + """ + ${principalType} subjectPrincipal; + { + $*{getPrincipal} + subjectPrincipal = principal; + } + """, + principalType=principalType, + getPrincipal=getPrincipal, + ) + ) + ) + + args.append(CGGeneric("MOZ_KnownLive(%s)" % subjectPrincipalArg)) + + if needsCallerType: + if isChromeOnly: + args.append(CGGeneric("SystemCallerGuarantee()")) + else: + args.append(CGGeneric(callerTypeGetterForDescriptor(descriptor))) + + canOOM = "canOOM" in extendedAttributes + if needsErrorResult: + args.append(CGGeneric("rv")) + elif canOOM: + args.append(CGGeneric("OOMReporter::From(rv)")) + args.extend(CGGeneric(arg) for arg in argsPost) + + call = CGGeneric(nativeMethodName) + if not static: + call = CGWrapper(call, pre="%s->" % object) + call = CGList([call, CGWrapper(args, pre="(", post=")")]) + if returnType is None or returnType.isUndefined() or resultOutParam is not None: + assert resultConversion is None + call = CGList( + [ + CGWrapper( + call, + pre=( + "// NOTE: This assert does NOT call the function.\n" + "static_assert(std::is_void_v<decltype(" + ), + post=')>, "Should be returning void here");', + ), + call, + ], + "\n", + ) + elif resultConversion is not None: + call = CGList([resultConversion, CGWrapper(call, pre="(", post=")")]) + if resultVar is None and result is not None: + needResultDecl = True + resultVar = "result" + + if needResultDecl: + if resultArgs is not None: + resultArgsStr = "(%s)" % resultArgs + else: + resultArgsStr = "" + result = CGWrapper(result, post=(" %s%s" % (resultVar, resultArgsStr))) + if resultOutParam is None and resultArgs is None: + call = CGList([result, CGWrapper(call, pre="(", post=")")]) + else: + self.cgRoot.append(CGWrapper(result, post=";\n")) + if resultOutParam is None: + call = CGWrapper(call, pre=resultVar + " = ") + if resultRooter is not None: + self.cgRoot.append(resultRooter) + elif result is not None: + assert resultOutParam is None + call = CGWrapper(call, pre=resultVar + " = ") + + call = CGWrapper(call, post=";\n") + self.cgRoot.append(call) + + if needsErrorResult or canOOM: + self.cgRoot.prepend(CGGeneric("FastErrorResult rv;\n")) + self.cgRoot.append( + CGGeneric( + fill( + """ + if (MOZ_UNLIKELY(rv.MaybeSetPendingException(cx, ${context}))) { + return false; + } + """, + context=context, + ) + ) + ) + + self.cgRoot.append(CGGeneric("MOZ_ASSERT(!JS_IsExceptionPending(cx));\n")) + + def define(self): + return self.cgRoot.define() + + +def getUnionMemberName(type): + # Promises can't be in unions, because they're not distinguishable + # from anything else. + assert not type.isPromise() + if type.isGeckoInterface(): + return type.inner.identifier.name + if type.isEnum(): + return type.inner.identifier.name + return type.name + + +# A counter for making sure that when we're wrapping up things in +# nested sequences we don't use the same variable name to iterate over +# different sequences. +sequenceWrapLevel = 0 +recordWrapLevel = 0 + + +def wrapTypeIntoCurrentCompartment(type, value, isMember=True): + """ + Take the thing named by "value" and if it contains "any", + "object", or spidermonkey-interface types inside return a CGThing + that will wrap them into the current compartment. + """ + if type.isAny(): + assert not type.nullable() + if isMember: + value = "JS::MutableHandle<JS::Value>::fromMarkedLocation(&%s)" % value + else: + value = "&" + value + return CGGeneric( + "if (!JS_WrapValue(cx, %s)) {\n" " return false;\n" "}\n" % value + ) + + if type.isObject(): + if isMember: + value = "JS::MutableHandle<JSObject*>::fromMarkedLocation(&%s)" % value + else: + value = "&" + value + return CGGeneric( + "if (!JS_WrapObject(cx, %s)) {\n" " return false;\n" "}\n" % value + ) + + if type.isSpiderMonkeyInterface(): + origValue = value + if type.nullable(): + value = "%s.Value()" % value + wrapCode = CGGeneric( + "if (!%s.WrapIntoNewCompartment(cx)) {\n" " return false;\n" "}\n" % value + ) + if type.nullable(): + wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % origValue) + return wrapCode + + if type.isSequence(): + origValue = value + origType = type + if type.nullable(): + type = type.inner + value = "%s.Value()" % value + global sequenceWrapLevel + index = "indexName%d" % sequenceWrapLevel + sequenceWrapLevel += 1 + wrapElement = wrapTypeIntoCurrentCompartment( + type.inner, "%s[%s]" % (value, index) + ) + sequenceWrapLevel -= 1 + if not wrapElement: + return None + wrapCode = CGWrapper( + CGIndenter(wrapElement), + pre=( + "for (uint32_t %s = 0; %s < %s.Length(); ++%s) {\n" + % (index, index, value, index) + ), + post="}\n", + ) + if origType.nullable(): + wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % origValue) + return wrapCode + + if type.isRecord(): + origType = type + if type.nullable(): + type = type.inner + recordRef = "%s.Value()" % value + else: + recordRef = value + global recordWrapLevel + entryRef = "mapEntry%d" % recordWrapLevel + recordWrapLevel += 1 + wrapElement = wrapTypeIntoCurrentCompartment(type.inner, "%s.mValue" % entryRef) + recordWrapLevel -= 1 + if not wrapElement: + return None + wrapCode = CGWrapper( + CGIndenter(wrapElement), + pre=("for (auto& %s : %s.Entries()) {\n" % (entryRef, recordRef)), + post="}\n", + ) + if origType.nullable(): + wrapCode = CGIfWrapper(wrapCode, "!%s.IsNull()" % value) + return wrapCode + + if type.isDictionary(): + assert not type.nullable() + myDict = type.inner + memberWraps = [] + while myDict: + for member in myDict.members: + memberWrap = wrapArgIntoCurrentCompartment( + member, + "%s.%s" + % (value, CGDictionary.makeMemberName(member.identifier.name)), + ) + if memberWrap: + memberWraps.append(memberWrap) + myDict = myDict.parent + return CGList(memberWraps) if len(memberWraps) != 0 else None + + if type.isUnion(): + memberWraps = [] + if type.nullable(): + type = type.inner + value = "%s.Value()" % value + for member in type.flatMemberTypes: + memberName = getUnionMemberName(member) + memberWrap = wrapTypeIntoCurrentCompartment( + member, "%s.GetAs%s()" % (value, memberName) + ) + if memberWrap: + memberWrap = CGIfWrapper(memberWrap, "%s.Is%s()" % (value, memberName)) + memberWraps.append(memberWrap) + return CGList(memberWraps, "else ") if len(memberWraps) != 0 else None + + if ( + type.isUndefined() + or type.isString() + or type.isPrimitive() + or type.isEnum() + or type.isGeckoInterface() + or type.isCallback() + or type.isPromise() + ): + # All of these don't need wrapping. + return None + + raise TypeError( + "Unknown type; we don't know how to wrap it in constructor " + "arguments: %s" % type + ) + + +def wrapArgIntoCurrentCompartment(arg, value, isMember=True): + """ + As wrapTypeIntoCurrentCompartment but handles things being optional + """ + origValue = value + isOptional = arg.canHaveMissingValue() + if isOptional: + value = value + ".Value()" + wrap = wrapTypeIntoCurrentCompartment(arg.type, value, isMember) + if wrap and isOptional: + wrap = CGIfWrapper(wrap, "%s.WasPassed()" % origValue) + return wrap + + +def needsContainsHack(m): + return m.getExtendedAttribute("ReturnValueNeedsContainsHack") + + +def needsCallerType(m): + return m.getExtendedAttribute("NeedsCallerType") + + +class CGPerSignatureCall(CGThing): + """ + This class handles the guts of generating code for a particular + call signature. A call signature consists of four things: + + 1) A return type, which can be None to indicate that there is no + actual return value (e.g. this is an attribute setter) or an + IDLType if there's an IDL type involved (including |void|). + 2) An argument list, which is allowed to be empty. + 3) A name of a native method to call. + 4) Whether or not this method is static. Note that this only controls how + the method is called (|self->nativeMethodName(...)| vs + |nativeMethodName(...)|). + + We also need to know whether this is a method or a getter/setter + to do error reporting correctly. + + The idlNode parameter can be either a method or an attr. We can query + |idlNode.identifier| in both cases, so we can be agnostic between the two. + + dontSetSlot should be set to True if the value should not be cached in a + slot (even if the attribute is marked as StoreInSlot or Cached in the + WebIDL). + """ + + # XXXbz For now each entry in the argument list is either an + # IDLArgument or a FakeArgument, but longer-term we may want to + # have ways of flagging things like JSContext* or optional_argc in + # there. + + def __init__( + self, + returnType, + arguments, + nativeMethodName, + static, + descriptor, + idlNode, + argConversionStartsAt=0, + getter=False, + setter=False, + isConstructor=False, + useCounterName=None, + resultVar=None, + objectName="obj", + dontSetSlot=False, + extendedAttributes=None, + ): + assert idlNode.isMethod() == (not getter and not setter) + assert idlNode.isAttr() == (getter or setter) + # Constructors are always static + assert not isConstructor or static + + CGThing.__init__(self) + self.returnType = returnType + self.descriptor = descriptor + self.idlNode = idlNode + if extendedAttributes is None: + extendedAttributes = descriptor.getExtendedAttributes( + idlNode, getter=getter, setter=setter + ) + self.extendedAttributes = extendedAttributes + self.arguments = arguments + self.argCount = len(arguments) + self.isConstructor = isConstructor + self.setSlot = ( + not dontSetSlot and idlNode.isAttr() and idlNode.slotIndices is not None + ) + cgThings = [] + + deprecated = idlNode.getExtendedAttribute("Deprecated") or ( + idlNode.isStatic() + and descriptor.interface.getExtendedAttribute("Deprecated") + ) + if deprecated: + cgThings.append( + CGGeneric( + dedent( + """ + DeprecationWarning(cx, obj, DeprecatedOperations::e%s); + """ + % deprecated[0] + ) + ) + ) + + lenientFloatCode = None + if idlNode.getExtendedAttribute("LenientFloat") is not None and ( + setter or idlNode.isMethod() + ): + cgThings.append( + CGGeneric( + dedent( + """ + bool foundNonFiniteFloat = false; + """ + ) + ) + ) + lenientFloatCode = "foundNonFiniteFloat = true;\n" + + argsPre = [] + if idlNode.isStatic(): + # If we're a constructor, "obj" may not be a function, so calling + # XrayAwareCalleeGlobal() on it is not safe. Of course in the + # constructor case either "obj" is an Xray or we're already in the + # content compartment, not the Xray compartment, so just + # constructing the GlobalObject from "obj" is fine. + if isConstructor: + objForGlobalObject = "obj" + else: + objForGlobalObject = "xpc::XrayAwareCalleeGlobal(obj)" + cgThings.append( + CGGeneric( + fill( + """ + GlobalObject global(cx, ${obj}); + if (global.Failed()) { + return false; + } + + """, + obj=objForGlobalObject, + ) + ) + ) + argsPre.append("global") + + # For JS-implemented interfaces we do not want to base the + # needsCx decision on the types involved, just on our extended + # attributes. Also, JSContext is not needed for the static case + # since GlobalObject already contains the context. + needsCx = needCx( + returnType, + arguments, + self.extendedAttributes, + not descriptor.interface.isJSImplemented(), + static, + ) + if needsCx: + argsPre.append("cx") + + needsUnwrap = False + argsPost = [] + runConstructorInCallerCompartment = descriptor.interface.getExtendedAttribute( + "RunConstructorInCallerCompartment" + ) + if isConstructor and not runConstructorInCallerCompartment: + needsUnwrap = True + needsUnwrappedVar = False + unwrappedVar = "obj" + if descriptor.interface.isJSImplemented(): + # We need the desired proto in our constructor, because the + # constructor will actually construct our reflector. + argsPost.append("desiredProto") + elif descriptor.interface.isJSImplemented(): + if not idlNode.isStatic(): + needsUnwrap = True + needsUnwrappedVar = True + argsPost.append( + "(unwrappedObj ? js::GetNonCCWObjectRealm(*unwrappedObj) : js::GetContextRealm(cx))" + ) + elif needScopeObject( + returnType, + arguments, + self.extendedAttributes, + descriptor.wrapperCache, + True, + idlNode.getExtendedAttribute("StoreInSlot"), + ): + # If we ever end up with APIs like this on cross-origin objects, + # figure out how the CheckedUnwrapDynamic bits should work. Chances + # are, just calling it with "cx" is fine... For now, though, just + # assert that it does not matter. + assert not descriptor.isMaybeCrossOriginObject() + # The scope object should always be from the relevant + # global. Make sure to unwrap it as needed. + cgThings.append( + CGGeneric( + dedent( + """ + JS::Rooted<JSObject*> unwrappedObj(cx, js::CheckedUnwrapStatic(obj)); + // Caller should have ensured that "obj" can be unwrapped already. + MOZ_DIAGNOSTIC_ASSERT(unwrappedObj); + """ + ) + ) + ) + argsPre.append("unwrappedObj") + + if needsUnwrap and needsUnwrappedVar: + # We cannot assign into obj because it's a Handle, not a + # MutableHandle, so we need a separate Rooted. + cgThings.append(CGGeneric("Maybe<JS::Rooted<JSObject*> > unwrappedObj;\n")) + unwrappedVar = "unwrappedObj.ref()" + + if idlNode.isMethod() and idlNode.isLegacycaller(): + # If we can have legacycaller with identifier, we can't + # just use the idlNode to determine whether we're + # generating code for the legacycaller or not. + assert idlNode.isIdentifierLess() + # Pass in our thisVal + argsPre.append("args.thisv()") + + if idlNode.isMethod(): + argDescription = "argument %(index)d" + elif setter: + argDescription = "value being assigned" + else: + assert self.argCount == 0 + + if needsUnwrap: + # It's very important that we construct our unwrappedObj, if we need + # to do it, before we might start setting up Rooted things for our + # arguments, so that we don't violate the stack discipline Rooted + # depends on. + cgThings.append( + CGGeneric("bool objIsXray = xpc::WrapperFactory::IsXrayWrapper(obj);\n") + ) + if needsUnwrappedVar: + cgThings.append( + CGIfWrapper( + CGGeneric("unwrappedObj.emplace(cx, obj);\n"), "objIsXray" + ) + ) + + for i in range(argConversionStartsAt, self.argCount): + cgThings.append( + CGArgumentConverter( + arguments[i], + i, + self.descriptor, + argDescription % {"index": i + 1}, + idlNode, + invalidEnumValueFatal=not setter, + lenientFloatCode=lenientFloatCode, + ) + ) + + # Now that argument processing is done, enforce the LenientFloat stuff + if lenientFloatCode: + if setter: + foundNonFiniteFloatBehavior = "return true;\n" + else: + assert idlNode.isMethod() + foundNonFiniteFloatBehavior = dedent( + """ + args.rval().setUndefined(); + return true; + """ + ) + cgThings.append( + CGGeneric( + fill( + """ + if (foundNonFiniteFloat) { + $*{returnSteps} + } + """, + returnSteps=foundNonFiniteFloatBehavior, + ) + ) + ) + + if needsUnwrap: + # Something depends on having the unwrapped object, so unwrap it now. + xraySteps = [] + # XXXkhuey we should be able to MOZ_ASSERT that ${obj} is + # not null. + xraySteps.append( + CGGeneric( + fill( + """ + // Since our object is an Xray, we can just CheckedUnwrapStatic: + // we know Xrays have no dynamic unwrap behavior. + ${obj} = js::CheckedUnwrapStatic(${obj}); + if (!${obj}) { + return false; + } + """, + obj=unwrappedVar, + ) + ) + ) + if isConstructor: + # If we're called via an xray, we need to enter the underlying + # object's compartment and then wrap up all of our arguments into + # that compartment as needed. This is all happening after we've + # already done the conversions from JS values to WebIDL (C++) + # values, so we only need to worry about cases where there are 'any' + # or 'object' types, or other things that we represent as actual + # JSAPI types, present. Effectively, we're emulating a + # CrossCompartmentWrapper, but working with the C++ types, not the + # original list of JS::Values. + cgThings.append(CGGeneric("Maybe<JSAutoRealm> ar;\n")) + xraySteps.append(CGGeneric("ar.emplace(cx, obj);\n")) + xraySteps.append( + CGGeneric( + dedent( + """ + if (!JS_WrapObject(cx, &desiredProto)) { + return false; + } + """ + ) + ) + ) + xraySteps.extend( + wrapArgIntoCurrentCompartment(arg, argname, isMember=False) + for arg, argname in self.getArguments() + ) + + cgThings.append(CGIfWrapper(CGList(xraySteps), "objIsXray")) + + if idlNode.getExtendedAttribute("CEReactions") is not None and not getter: + cgThings.append( + CGGeneric( + dedent( + """ + Maybe<AutoCEReaction> ceReaction; + DocGroup* docGroup = self->GetDocGroup(); + if (docGroup) { + ceReaction.emplace(docGroup->CustomElementReactionsStack(), cx); + } + """ + ) + ) + ) + + # If this is a method that was generated by a maplike/setlike + # interface, use the maplike/setlike generator to fill in the body. + # Otherwise, use CGCallGenerator to call the native method. + if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod(): + if ( + idlNode.maplikeOrSetlikeOrIterable.isMaplike() + or idlNode.maplikeOrSetlikeOrIterable.isSetlike() + ): + cgThings.append( + CGMaplikeOrSetlikeMethodGenerator( + descriptor, + idlNode.maplikeOrSetlikeOrIterable, + idlNode.identifier.name, + ) + ) + else: + cgThings.append( + CGIterableMethodGenerator( + descriptor, + idlNode.identifier.name, + self.getArgumentNames(), + ) + ) + elif idlNode.isAttr() and idlNode.type.isObservableArray(): + assert setter + cgThings.append(CGObservableArraySetterGenerator(descriptor, idlNode)) + else: + context = GetLabelForErrorReporting(descriptor, idlNode, isConstructor) + if getter: + context = context + " getter" + elif setter: + context = context + " setter" + # Callee expects a quoted string for the context if + # there's a context. + context = '"%s"' % context + + if idlNode.isMethod() and idlNode.getExtendedAttribute("WebExtensionStub"): + [ + nativeMethodName, + argsPre, + args, + ] = self.processWebExtensionStubAttribute(idlNode, cgThings) + else: + args = self.getArguments() + + cgThings.append( + CGCallGenerator( + self.needsErrorResult(), + needsCallerType(idlNode), + isChromeOnly(idlNode), + args, + argsPre, + returnType, + self.extendedAttributes, + descriptor, + nativeMethodName, + static, + # We know our "self" must be being kept alive; otherwise we have + # a serious problem. In common cases it's just an argument and + # we're MOZ_CAN_RUN_SCRIPT, but in some cases it's on the stack + # and being kept alive via references from JS. + object="MOZ_KnownLive(self)", + argsPost=argsPost, + resultVar=resultVar, + context=context, + ) + ) + + if useCounterName: + # Generate a telemetry call for when [UseCounter] is used. + windowCode = fill( + """ + SetUseCounter(obj, eUseCounter_${useCounterName}); + """, + useCounterName=useCounterName, + ) + workerCode = fill( + """ + SetUseCounter(UseCounterWorker::${useCounterName}); + """, + useCounterName=useCounterName, + ) + code = "" + if idlNode.isExposedInWindow() and idlNode.isExposedInAnyWorker(): + code += fill( + """ + if (NS_IsMainThread()) { + ${windowCode} + } else { + ${workerCode} + } + """, + windowCode=windowCode, + workerCode=workerCode, + ) + elif idlNode.isExposedInWindow(): + code += windowCode + elif idlNode.isExposedInAnyWorker(): + code += workerCode + + cgThings.append(CGGeneric(code)) + + self.cgRoot = CGList(cgThings) + + def getArgumentNames(self): + return ["arg" + str(i) for i in range(len(self.arguments))] + + def getArguments(self): + return list(zip(self.arguments, self.getArgumentNames())) + + def processWebExtensionStubAttribute(self, idlNode, cgThings): + nativeMethodName = "CallWebExtMethod" + stubNameSuffix = idlNode.getExtendedAttribute("WebExtensionStub") + if isinstance(stubNameSuffix, list): + nativeMethodName += stubNameSuffix[0] + + argsLength = len(self.getArguments()) + singleVariadicArg = argsLength == 1 and self.getArguments()[0][0].variadic + + # If the method signature does only include a single variadic arguments, + # then `arg0` is already a Sequence of JS values and we can pass that + # to the WebExtensions Stub method as is. + if singleVariadicArg: + argsPre = [ + "cx", + 'u"%s"_ns' % idlNode.identifier.name, + "Constify(%s)" % "arg0", + ] + args = [] + return [nativeMethodName, argsPre, args] + + argsPre = [ + "cx", + 'u"%s"_ns' % idlNode.identifier.name, + "Constify(%s)" % "args_sequence", + ] + args = [] + + # Determine the maximum number of elements of the js values sequence argument, + # skipping the last optional callback argument if any: + # + # if this WebExtensions API method does expect a last optional callback argument, + # then it is the callback parameter supported for chrome-compatibility + # reasons, and we want it as a separate argument passed to the WebExtension + # stub method and skip it from the js values sequence including all other + # arguments. + maxArgsSequenceLen = argsLength + if argsLength > 0: + lastArg = self.getArguments()[argsLength - 1] + isCallback = lastArg[0].type.tag() == IDLType.Tags.callback + if isCallback and lastArg[0].optional: + argsPre.append( + "MOZ_KnownLive(NonNullHelper(Constify(%s)))" % lastArg[1] + ) + maxArgsSequenceLen = argsLength - 1 + + cgThings.append( + CGGeneric( + dedent( + fill( + """ + // Collecting all args js values into the single sequence argument + // passed to the webextensions stub method. + // + // NOTE: The stub method will receive the original non-normalized js values, + // but those arguments will still be normalized on the main thread by the + // WebExtensions API request handler using the same JSONSchema defnition + // used by the non-webIDL webextensions API bindings. + AutoSequence<JS::Value> args_sequence; + SequenceRooter<JS::Value> args_sequence_holder(cx, &args_sequence); + + // maximum number of arguments expected by the WebExtensions API method + // excluding the last optional chrome-compatible callback argument (which + // is being passed to the stub method as a separate additional argument). + uint32_t maxArgsSequenceLen = ${maxArgsSequenceLen}; + + uint32_t sequenceArgsLen = args.length() <= maxArgsSequenceLen ? + args.length() : maxArgsSequenceLen; + + if (sequenceArgsLen > 0) { + if (!args_sequence.SetCapacity(sequenceArgsLen, mozilla::fallible)) { + JS_ReportOutOfMemory(cx); + return false; + } + for (uint32_t argIdx = 0; argIdx < sequenceArgsLen; ++argIdx) { + // OK to do infallible append here, since we ensured capacity already. + JS::Value& slot = *args_sequence.AppendElement(); + slot = args[argIdx]; + } + } + """, + maxArgsSequenceLen=maxArgsSequenceLen, + ) + ) + ) + ) + + return [nativeMethodName, argsPre, args] + + def needsErrorResult(self): + return "needsErrorResult" in self.extendedAttributes + + def wrap_return_value(self): + wrapCode = "" + + returnsNewObject = memberReturnsNewObject(self.idlNode) + if returnsNewObject and ( + self.returnType.isGeckoInterface() or self.returnType.isPromise() + ): + wrapCode += dedent( + """ + static_assert(!std::is_pointer_v<decltype(result)>, + "NewObject implies that we need to keep the object alive with a strong reference."); + """ + ) + + if self.setSlot: + # For attributes in slots, we want to do some + # post-processing once we've wrapped them. + successCode = "break;\n" + else: + successCode = None + + resultTemplateValues = { + "jsvalRef": "args.rval()", + "jsvalHandle": "args.rval()", + "returnsNewObject": returnsNewObject, + "isConstructorRetval": self.isConstructor, + "successCode": successCode, + # 'obj' in this dictionary is the thing whose compartment we are + # trying to do the to-JS conversion in. We're going to put that + # thing in a variable named "conversionScope" if setSlot is true. + # Otherwise, just use "obj" for lack of anything better. + "obj": "conversionScope" if self.setSlot else "obj", + } + + wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues) + + if self.setSlot: + if self.idlNode.isStatic(): + raise TypeError( + "Attribute %s.%s is static, so we don't have a useful slot " + "to cache it in, because we don't have support for that on " + "interface objects. See " + "https://bugzilla.mozilla.org/show_bug.cgi?id=1363870" + % ( + self.descriptor.interface.identifier.name, + self.idlNode.identifier.name, + ) + ) + + # When using a slot on the Xray expando, we need to make sure that + # our initial conversion to a JS::Value is done in the caller + # compartment. When using a slot on our reflector, we want to do + # the conversion in the compartment of that reflector (that is, + # slotStorage). In both cases we want to make sure that we finally + # set up args.rval() to be in the caller compartment. We also need + # to make sure that the conversion steps happen inside a do/while + # that they can break out of on success. + # + # Of course we always have to wrap the value into the slotStorage + # compartment before we store it in slotStorage. + + # postConversionSteps are the steps that run while we're still in + # the compartment we do our conversion in but after we've finished + # the initial conversion into args.rval(). + postConversionSteps = "" + if needsContainsHack(self.idlNode): + # Define a .contains on the object that has the same value as + # .includes; needed for backwards compat in extensions as we + # migrate some DOMStringLists to FrozenArray. + postConversionSteps += dedent( + """ + if (args.rval().isObject() && nsContentUtils::ThreadsafeIsSystemCaller(cx)) { + JS::Rooted<JSObject*> rvalObj(cx, &args.rval().toObject()); + JS::Rooted<JS::Value> includesVal(cx); + if (!JS_GetProperty(cx, rvalObj, "includes", &includesVal) || + !JS_DefineProperty(cx, rvalObj, "contains", includesVal, JSPROP_ENUMERATE)) { + return false; + } + } + + """ + ) + if self.idlNode.getExtendedAttribute("Frozen"): + assert ( + self.idlNode.type.isSequence() or self.idlNode.type.isDictionary() + ) + freezeValue = CGGeneric( + "JS::Rooted<JSObject*> rvalObj(cx, &args.rval().toObject());\n" + "if (!JS_FreezeObject(cx, rvalObj)) {\n" + " return false;\n" + "}\n" + ) + if self.idlNode.type.nullable(): + freezeValue = CGIfWrapper(freezeValue, "args.rval().isObject()") + postConversionSteps += freezeValue.define() + + # slotStorageSteps are steps that run once we have entered the + # slotStorage compartment. + slotStorageSteps = fill( + """ + // Make a copy so that we don't do unnecessary wrapping on args.rval(). + JS::Rooted<JS::Value> storedVal(cx, args.rval()); + if (!${maybeWrap}(cx, &storedVal)) { + return false; + } + JS::SetReservedSlot(slotStorage, slotIndex, storedVal); + """, + maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type), + ) + + checkForXray = mayUseXrayExpandoSlots(self.descriptor, self.idlNode) + + # For the case of Cached attributes, go ahead and preserve our + # wrapper if needed. We need to do this because otherwise the + # wrapper could get garbage-collected and the cached value would + # suddenly disappear, but the whole premise of cached values is that + # they never change without explicit action on someone's part. We + # don't do this for StoreInSlot, since those get dealt with during + # wrapper setup, and failure would involve us trying to clear an + # already-preserved wrapper. + if ( + self.idlNode.getExtendedAttribute("Cached") + and self.descriptor.wrapperCache + ): + preserveWrapper = dedent( + """ + PreserveWrapper(self); + """ + ) + if checkForXray: + preserveWrapper = fill( + """ + if (!isXray) { + // In the Xray case we don't need to do this, because getting the + // expando object already preserved our wrapper. + $*{preserveWrapper} + } + """, + preserveWrapper=preserveWrapper, + ) + slotStorageSteps += preserveWrapper + + if checkForXray: + # In the Xray case we use the current global as conversion + # scope, as explained in the big compartment/conversion comment + # above. + conversionScope = "isXray ? JS::CurrentGlobalOrNull(cx) : slotStorage" + else: + conversionScope = "slotStorage" + + wrapCode = fill( + """ + { + JS::Rooted<JSObject*> conversionScope(cx, ${conversionScope}); + JSAutoRealm ar(cx, conversionScope); + do { // block we break out of when done wrapping + $*{wrapCode} + } while (false); + $*{postConversionSteps} + } + { // And now store things in the realm of our slotStorage. + JSAutoRealm ar(cx, slotStorage); + $*{slotStorageSteps} + } + // And now make sure args.rval() is in the caller realm. + return ${maybeWrap}(cx, args.rval()); + """, + conversionScope=conversionScope, + wrapCode=wrapCode, + postConversionSteps=postConversionSteps, + slotStorageSteps=slotStorageSteps, + maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type), + ) + return wrapCode + + def define(self): + return self.cgRoot.define() + self.wrap_return_value() + + +class CGSwitch(CGList): + """ + A class to generate code for a switch statement. + + Takes three constructor arguments: an expression, a list of cases, + and an optional default. + + Each case is a CGCase. The default is a CGThing for the body of + the default case, if any. + """ + + def __init__(self, expression, cases, default=None): + CGList.__init__(self, [CGIndenter(c) for c in cases]) + self.prepend(CGGeneric("switch (" + expression + ") {\n")) + if default is not None: + self.append( + CGIndenter( + CGWrapper(CGIndenter(default), pre="default: {\n", post="}\n") + ) + ) + + self.append(CGGeneric("}\n")) + + +class CGCase(CGList): + """ + A class to generate code for a case statement. + + Takes three constructor arguments: an expression, a CGThing for + the body (allowed to be None if there is no body), and an optional + argument for whether add a break, add fallthrough annotation or add nothing + (defaulting to add a break). + """ + + ADD_BREAK = 0 + ADD_FALLTHROUGH = 1 + DONT_ADD_BREAK = 2 + + def __init__(self, expression, body, breakOrFallthrough=ADD_BREAK): + CGList.__init__(self, []) + + assert ( + breakOrFallthrough == CGCase.ADD_BREAK + or breakOrFallthrough == CGCase.ADD_FALLTHROUGH + or breakOrFallthrough == CGCase.DONT_ADD_BREAK + ) + + self.append(CGGeneric("case " + expression + ": {\n")) + bodyList = CGList([body]) + if breakOrFallthrough == CGCase.ADD_FALLTHROUGH: + bodyList.append(CGGeneric("[[fallthrough]];\n")) + elif breakOrFallthrough == CGCase.ADD_BREAK: + bodyList.append(CGGeneric("break;\n")) + self.append(CGIndenter(bodyList)) + self.append(CGGeneric("}\n")) + + +class CGMethodCall(CGThing): + """ + A class to generate selection of a method signature from a set of + signatures and generation of a call to that signature. + """ + + def __init__( + self, nativeMethodName, static, descriptor, method, isConstructor=False + ): + CGThing.__init__(self) + + methodName = GetLabelForErrorReporting(descriptor, method, isConstructor) + argDesc = "argument %d" + + if method.getExtendedAttribute("UseCounter"): + useCounterName = methodName.replace(".", "_").replace(" ", "_") + else: + useCounterName = None + + if method.isStatic(): + nativeType = descriptor.nativeType + staticTypeOverride = PropertyDefiner.getStringAttr( + method, "StaticClassOverride" + ) + if staticTypeOverride: + nativeType = staticTypeOverride + nativeMethodName = "%s::%s" % (nativeType, nativeMethodName) + + def requiredArgCount(signature): + arguments = signature[1] + if len(arguments) == 0: + return 0 + requiredArgs = len(arguments) + while requiredArgs and arguments[requiredArgs - 1].optional: + requiredArgs -= 1 + return requiredArgs + + def getPerSignatureCall(signature, argConversionStartsAt=0): + return CGPerSignatureCall( + signature[0], + signature[1], + nativeMethodName, + static, + descriptor, + method, + argConversionStartsAt=argConversionStartsAt, + isConstructor=isConstructor, + useCounterName=useCounterName, + ) + + signatures = method.signatures() + if len(signatures) == 1: + # Special case: we can just do a per-signature method call + # here for our one signature and not worry about switching + # on anything. + signature = signatures[0] + self.cgRoot = CGList([getPerSignatureCall(signature)]) + requiredArgs = requiredArgCount(signature) + + # Skip required arguments check for maplike/setlike interfaces, as + # they can have arguments which are not passed, and are treated as + # if undefined had been explicitly passed. + if requiredArgs > 0 and not method.isMaplikeOrSetlikeOrIterableMethod(): + code = fill( + """ + if (!args.requireAtLeast(cx, "${methodName}", ${requiredArgs})) { + return false; + } + """, + requiredArgs=requiredArgs, + methodName=methodName, + ) + self.cgRoot.prepend(CGGeneric(code)) + return + + # Need to find the right overload + maxArgCount = method.maxArgCount + allowedArgCounts = method.allowedArgCounts + + argCountCases = [] + for argCountIdx, argCount in enumerate(allowedArgCounts): + possibleSignatures = method.signaturesForArgCount(argCount) + + # Try to optimize away cases when the next argCount in the list + # will have the same code as us; if it does, we can fall through to + # that case. + if argCountIdx + 1 < len(allowedArgCounts): + nextPossibleSignatures = method.signaturesForArgCount( + allowedArgCounts[argCountIdx + 1] + ) + else: + nextPossibleSignatures = None + if possibleSignatures == nextPossibleSignatures: + # Same set of signatures means we better have the same + # distinguishing index. So we can in fact just fall through to + # the next case here. + assert len(possibleSignatures) == 1 or ( + method.distinguishingIndexForArgCount(argCount) + == method.distinguishingIndexForArgCount( + allowedArgCounts[argCountIdx + 1] + ) + ) + argCountCases.append( + CGCase(str(argCount), None, CGCase.ADD_FALLTHROUGH) + ) + continue + + if len(possibleSignatures) == 1: + # easy case! + signature = possibleSignatures[0] + argCountCases.append( + CGCase(str(argCount), getPerSignatureCall(signature)) + ) + continue + + distinguishingIndex = method.distinguishingIndexForArgCount(argCount) + + def distinguishingArgument(signature): + args = signature[1] + if distinguishingIndex < len(args): + return args[distinguishingIndex] + assert args[-1].variadic + return args[-1] + + def distinguishingType(signature): + return distinguishingArgument(signature).type + + for sig in possibleSignatures: + # We should not have "any" args at distinguishingIndex, + # since we have multiple possible signatures remaining, + # but "any" is never distinguishable from anything else. + assert not distinguishingType(sig).isAny() + # We can't handle unions at the distinguishing index. + if distinguishingType(sig).isUnion(): + raise TypeError( + "No support for unions as distinguishing " + "arguments yet: %s" % distinguishingArgument(sig).location + ) + # We don't support variadics as the distinguishingArgument yet. + # If you want to add support, consider this case: + # + # undefined(long... foo); + # undefined(long bar, Int32Array baz); + # + # in which we have to convert argument 0 to long before picking + # an overload... but all the variadic stuff needs to go into a + # single array in case we pick that overload, so we have to have + # machinery for converting argument 0 to long and then either + # placing it in the variadic bit or not. Or something. We may + # be able to loosen this restriction if the variadic arg is in + # fact at distinguishingIndex, perhaps. Would need to + # double-check. + if distinguishingArgument(sig).variadic: + raise TypeError( + "No support for variadics as distinguishing " + "arguments yet: %s" % distinguishingArgument(sig).location + ) + + # Convert all our arguments up to the distinguishing index. + # Doesn't matter which of the possible signatures we use, since + # they all have the same types up to that point; just use + # possibleSignatures[0] + caseBody = [ + CGArgumentConverter( + possibleSignatures[0][1][i], + i, + descriptor, + argDesc % (i + 1), + method, + ) + for i in range(0, distinguishingIndex) + ] + + # Select the right overload from our set. + distinguishingArg = "args[%d]" % distinguishingIndex + + def tryCall( + signature, indent, isDefinitelyObject=False, isNullOrUndefined=False + ): + assert not isDefinitelyObject or not isNullOrUndefined + assert isDefinitelyObject or isNullOrUndefined + if isDefinitelyObject: + failureCode = "break;\n" + else: + failureCode = None + type = distinguishingType(signature) + # The argument at index distinguishingIndex can't possibly be + # unset here, because we've already checked that argc is large + # enough that we can examine this argument. But note that we + # still want to claim that optional arguments are optional, in + # case undefined was passed in. + argIsOptional = distinguishingArgument(signature).canHaveMissingValue() + testCode = instantiateJSToNativeConversion( + getJSToNativeConversionInfo( + type, + descriptor, + failureCode=failureCode, + isDefinitelyObject=isDefinitelyObject, + isNullOrUndefined=isNullOrUndefined, + isOptional=argIsOptional, + sourceDescription=(argDesc % (distinguishingIndex + 1)), + ), + { + "declName": "arg%d" % distinguishingIndex, + "holderName": ("arg%d" % distinguishingIndex) + "_holder", + "val": distinguishingArg, + "obj": "obj", + "haveValue": "args.hasDefined(%d)" % distinguishingIndex, + "passedToJSImpl": toStringBool( + isJSImplementedDescriptor(descriptor) + ), + }, + checkForValue=argIsOptional, + ) + caseBody.append(CGIndenter(testCode, indent)) + + # If we got this far, we know we unwrapped to the right + # C++ type, so just do the call. Start conversion with + # distinguishingIndex + 1, since we already converted + # distinguishingIndex. + caseBody.append( + CGIndenter( + getPerSignatureCall(signature, distinguishingIndex + 1), indent + ) + ) + + def hasConditionalConversion(type): + """ + Return whether the argument conversion for this type will be + conditional on the type of incoming JS value. For example, for + interface types the conversion is conditional on the incoming + value being isObject(). + + For the types for which this returns false, we do not have to + output extra isUndefined() or isNullOrUndefined() cases, because + null/undefined values will just fall through into our + unconditional conversion. + """ + if type.isString() or type.isEnum(): + return False + if type.isBoolean(): + distinguishingTypes = ( + distinguishingType(s) for s in possibleSignatures + ) + return any( + t.isString() or t.isEnum() or t.isNumeric() + for t in distinguishingTypes + ) + if type.isNumeric(): + distinguishingTypes = ( + distinguishingType(s) for s in possibleSignatures + ) + return any(t.isString() or t.isEnum() for t in distinguishingTypes) + return True + + def needsNullOrUndefinedCase(type): + """ + Return true if the type needs a special isNullOrUndefined() case + """ + return ( + type.nullable() and hasConditionalConversion(type) + ) or type.isDictionary() + + # First check for undefined and optional distinguishing arguments + # and output a special branch for that case. Note that we don't + # use distinguishingArgument here because we actualy want to + # exclude variadic arguments. Also note that we skip this check if + # we plan to output a isNullOrUndefined() special case for this + # argument anyway, since that will subsume our isUndefined() check. + # This is safe, because there can be at most one nullable + # distinguishing argument, so if we're it we'll definitely get + # picked up by the nullable handling. Also, we can skip this check + # if the argument has an unconditional conversion later on. + undefSigs = [ + s + for s in possibleSignatures + if distinguishingIndex < len(s[1]) + and s[1][distinguishingIndex].optional + and hasConditionalConversion(s[1][distinguishingIndex].type) + and not needsNullOrUndefinedCase(s[1][distinguishingIndex].type) + ] + # Can't have multiple signatures with an optional argument at the + # same index. + assert len(undefSigs) < 2 + if len(undefSigs) > 0: + caseBody.append( + CGGeneric("if (%s.isUndefined()) {\n" % distinguishingArg) + ) + tryCall(undefSigs[0], 2, isNullOrUndefined=True) + caseBody.append(CGGeneric("}\n")) + + # Next, check for null or undefined. That means looking for + # nullable arguments at the distinguishing index and outputting a + # separate branch for them. But if the nullable argument has an + # unconditional conversion, we don't need to do that. The reason + # for that is that at most one argument at the distinguishing index + # is nullable (since two nullable arguments are not + # distinguishable), and null/undefined values will always fall + # through to the unconditional conversion we have, if any, since + # they will fail whatever the conditions on the input value are for + # our other conversions. + nullOrUndefSigs = [ + s + for s in possibleSignatures + if needsNullOrUndefinedCase(distinguishingType(s)) + ] + # Can't have multiple nullable types here + assert len(nullOrUndefSigs) < 2 + if len(nullOrUndefSigs) > 0: + caseBody.append( + CGGeneric("if (%s.isNullOrUndefined()) {\n" % distinguishingArg) + ) + tryCall(nullOrUndefSigs[0], 2, isNullOrUndefined=True) + caseBody.append(CGGeneric("}\n")) + + # Now check for distinguishingArg being various kinds of objects. + # The spec says to check for the following things in order: + # 1) A platform object that's not a platform array object, being + # passed to an interface or "object" arg. + # 2) A callable object being passed to a callback or "object" arg. + # 3) An iterable object being passed to a sequence arg. + # 4) Any object being passed to a array or callback interface or + # dictionary or "object" arg. + + # First grab all the overloads that have a non-callback interface + # (which includes SpiderMonkey interfaces) at the distinguishing + # index. We can also include the ones that have an "object" here, + # since if those are present no other object-typed argument will + # be. + objectSigs = [ + s + for s in possibleSignatures + if ( + distinguishingType(s).isObject() + or distinguishingType(s).isNonCallbackInterface() + ) + ] + + # And all the overloads that take callbacks + objectSigs.extend( + s for s in possibleSignatures if distinguishingType(s).isCallback() + ) + + # And all the overloads that take sequences + objectSigs.extend( + s for s in possibleSignatures if distinguishingType(s).isSequence() + ) + + # Now append all the overloads that take a dictionary or callback + # interface or record. There should be only one of these! + genericObjectSigs = [ + s + for s in possibleSignatures + if ( + distinguishingType(s).isDictionary() + or distinguishingType(s).isRecord() + or distinguishingType(s).isCallbackInterface() + ) + ] + assert len(genericObjectSigs) <= 1 + objectSigs.extend(genericObjectSigs) + + # There might be more than one thing in objectSigs; we need to check + # which ones we unwrap to. + if len(objectSigs) > 0: + # Here it's enough to guard on our argument being an object. + # The code for unwrapping non-callback interfaces, spiderMonkey + # interfaces, and sequences will just bail out and move + # on to the next overload if the object fails to unwrap + # correctly, while "object" accepts any object anyway. We + # could even not do the isObject() check up front here, but in + # cases where we have multiple object overloads it makes sense + # to do it only once instead of for each overload. That will + # also allow the unwrapping test to skip having to do codegen + # for the null-or-undefined case, which we already handled + # above. + caseBody.append(CGGeneric("if (%s.isObject()) {\n" % distinguishingArg)) + for sig in objectSigs: + caseBody.append(CGIndenter(CGGeneric("do {\n"))) + # Indent by 4, since we need to indent further + # than our "do" statement + tryCall(sig, 4, isDefinitelyObject=True) + caseBody.append(CGIndenter(CGGeneric("} while (false);\n"))) + + caseBody.append(CGGeneric("}\n")) + + # Now we only have to consider booleans, numerics, and strings. If + # we only have one of them, then we can just output it. But if not, + # then we need to output some of the cases conditionally: if we have + # a string overload, then boolean and numeric are conditional, and + # if not then boolean is conditional if we have a numeric overload. + def findUniqueSignature(filterLambda): + sigs = [s for s in possibleSignatures if filterLambda(s)] + assert len(sigs) < 2 + if len(sigs) > 0: + return sigs[0] + return None + + stringSignature = findUniqueSignature( + lambda s: ( + distinguishingType(s).isString() or distinguishingType(s).isEnum() + ) + ) + numericSignature = findUniqueSignature( + lambda s: distinguishingType(s).isNumeric() + ) + booleanSignature = findUniqueSignature( + lambda s: distinguishingType(s).isBoolean() + ) + + if stringSignature or numericSignature: + booleanCondition = "%s.isBoolean()" + else: + booleanCondition = None + + if stringSignature: + numericCondition = "%s.isNumber()" + else: + numericCondition = None + + def addCase(sig, condition): + sigCode = getPerSignatureCall(sig, distinguishingIndex) + if condition: + sigCode = CGIfWrapper(sigCode, condition % distinguishingArg) + caseBody.append(sigCode) + + if booleanSignature: + addCase(booleanSignature, booleanCondition) + if numericSignature: + addCase(numericSignature, numericCondition) + if stringSignature: + addCase(stringSignature, None) + + if not booleanSignature and not numericSignature and not stringSignature: + # Just throw; we have no idea what we're supposed to + # do with this. + caseBody.append( + CGGeneric( + 'return cx.ThrowErrorMessage<MSG_OVERLOAD_RESOLUTION_FAILED>("%d", "%d");\n' + % (distinguishingIndex + 1, argCount) + ) + ) + + argCountCases.append(CGCase(str(argCount), CGList(caseBody))) + + overloadCGThings = [] + overloadCGThings.append( + CGGeneric( + "unsigned argcount = std::min(args.length(), %du);\n" % maxArgCount + ) + ) + overloadCGThings.append( + CGSwitch( + "argcount", + argCountCases, + CGGeneric( + dedent( + """ + // Using nsPrintfCString here would require including that + // header. Let's not worry about it. + nsAutoCString argCountStr; + argCountStr.AppendPrintf("%u", args.length()); + return cx.ThrowErrorMessage<MSG_INVALID_OVERLOAD_ARGCOUNT>(argCountStr.get()); + """ + ) + ), + ) + ) + overloadCGThings.append( + CGGeneric( + 'MOZ_CRASH("We have an always-returning default case");\n' + "return false;\n" + ) + ) + self.cgRoot = CGList(overloadCGThings) + + def define(self): + return self.cgRoot.define() + + +class CGGetterCall(CGPerSignatureCall): + """ + A class to generate a native object getter call for a particular IDL + getter. + """ + + def __init__( + self, + returnType, + nativeMethodName, + descriptor, + attr, + dontSetSlot=False, + extendedAttributes=None, + ): + if attr.getExtendedAttribute("UseCounter"): + useCounterName = "%s_%s_getter" % ( + descriptor.interface.identifier.name, + attr.identifier.name, + ) + else: + useCounterName = None + if attr.isStatic(): + nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName) + CGPerSignatureCall.__init__( + self, + returnType, + [], + nativeMethodName, + attr.isStatic(), + descriptor, + attr, + getter=True, + useCounterName=useCounterName, + dontSetSlot=dontSetSlot, + extendedAttributes=extendedAttributes, + ) + + +class FakeIdentifier: + def __init__(self, name): + self.name = name + + +class FakeArgument: + """ + A class that quacks like an IDLArgument. This is used to make + setters look like method calls or for special operations. + """ + + def __init__(self, type, name="arg", allowTreatNonCallableAsNull=False): + self.type = type + self.optional = False + self.variadic = False + self.defaultValue = None + self._allowTreatNonCallableAsNull = allowTreatNonCallableAsNull + + self.identifier = FakeIdentifier(name) + + def allowTreatNonCallableAsNull(self): + return self._allowTreatNonCallableAsNull + + def canHaveMissingValue(self): + return False + + +class CGSetterCall(CGPerSignatureCall): + """ + A class to generate a native object setter call for a particular IDL + setter. + """ + + def __init__(self, argType, nativeMethodName, descriptor, attr): + if attr.getExtendedAttribute("UseCounter"): + useCounterName = "%s_%s_setter" % ( + descriptor.interface.identifier.name, + attr.identifier.name, + ) + else: + useCounterName = None + if attr.isStatic(): + nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName) + CGPerSignatureCall.__init__( + self, + None, + [FakeArgument(argType, allowTreatNonCallableAsNull=True)], + nativeMethodName, + attr.isStatic(), + descriptor, + attr, + setter=True, + useCounterName=useCounterName, + ) + + def wrap_return_value(self): + attr = self.idlNode + clearSlot = "" + if self.descriptor.wrapperCache and attr.slotIndices is not None: + if attr.getExtendedAttribute("StoreInSlot"): + clearSlot = "%s(cx, self);\n" % MakeClearCachedValueNativeName( + self.idlNode + ) + elif attr.getExtendedAttribute("Cached"): + clearSlot = "%s(self);\n" % MakeClearCachedValueNativeName(self.idlNode) + + # We have no return value + return "\n" "%s" "return true;\n" % clearSlot + + +class CGAbstractBindingMethod(CGAbstractStaticMethod): + """ + Common class to generate some of our class hooks. This will generate the + function declaration, get a reference to the JS object for our binding + object (which might be an argument of the class hook or something we get + from a JS::CallArgs), and unwrap into the right C++ type. Subclasses are + expected to override the generate_code function to do the rest of the work. + This function should return a CGThing which is already properly indented. + + getThisObj should be code for getting a JSObject* for the binding + object. "" can be passed in if the binding object is already stored in + 'obj'. + + callArgs should be code for getting a JS::CallArgs into a variable + called 'args'. This can be "" if there is already such a variable + around or if the body does not need a JS::CallArgs. + + """ + + def __init__( + self, + descriptor, + name, + args, + getThisObj, + callArgs="JS::CallArgs args = JS::CallArgsFromVp(argc, vp);\n", + ): + CGAbstractStaticMethod.__init__( + self, descriptor, name, "bool", args, canRunScript=True + ) + + # This can't ever happen, because we only use this for class hooks. + self.unwrapFailureCode = fill( + """ + MOZ_CRASH("Unexpected object in '${name}' hook"); + return false; + """, + name=name, + ) + + if getThisObj == "": + self.getThisObj = None + else: + self.getThisObj = CGGeneric( + "JS::Rooted<JSObject*> obj(cx, %s);\n" % getThisObj + ) + self.callArgs = callArgs + + def definition_body(self): + body = self.callArgs + if self.getThisObj is not None: + body += self.getThisObj.define() + "\n" + body += "%s* self;\n" % self.descriptor.nativeType + body += dedent( + """ + JS::Rooted<JS::Value> rootSelf(cx, JS::ObjectValue(*obj)); + """ + ) + + body += str( + CastableObjectUnwrapper( + self.descriptor, "rootSelf", "&rootSelf", "self", self.unwrapFailureCode + ) + ) + + return body + self.generate_code().define() + + def generate_code(self): + assert False # Override me + + +class CGAbstractStaticBindingMethod(CGAbstractStaticMethod): + """ + Common class to generate the JSNatives for all our static methods, getters + and setters. This will generate the function declaration and unwrap the + global object. Subclasses are expected to override the generate_code + function to do the rest of the work. This function should return a + CGThing which is already properly indented. + """ + + def __init__(self, descriptor, name): + CGAbstractStaticMethod.__init__( + self, descriptor, name, "bool", JSNativeArguments(), canRunScript=True + ) + + def definition_body(self): + # Make sure that "obj" is in the same compartment as "cx", since we'll + # later use it to wrap return values. + unwrap = dedent( + """ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> obj(cx, &args.callee()); + + """ + ) + return unwrap + self.generate_code().define() + + def generate_code(self): + assert False # Override me + + +def MakeNativeName(name): + return name[0].upper() + IDLToCIdentifier(name[1:]) + + +def GetWebExposedName(idlObject, descriptor): + if idlObject == descriptor.operations["Stringifier"]: + return "toString" + name = idlObject.identifier.name + if name == "__namedsetter": + return "named setter" + if name == "__namedgetter": + return "named getter" + if name == "__indexedsetter": + return "indexed setter" + if name == "__indexedgetter": + return "indexed getter" + if name == "__legacycaller": + return "legacy caller" + return name + + +def GetConstructorNameForReporting(descriptor, ctor): + # Figure out the name of our constructor for reporting purposes. + # For unnamed webidl constructors, identifier.name is "constructor" but + # the name JS sees is the interface name; for legacy factory functions + # identifier.name is the actual name. + ctorName = ctor.identifier.name + if ctorName == "constructor": + return descriptor.interface.identifier.name + return ctorName + + +def GetLabelForErrorReporting(descriptor, idlObject, isConstructor): + """ + descriptor is the descriptor for the interface involved + + idlObject is the method (regular or static), attribute (regular or + static), or constructor (named or not) involved. + + isConstructor is true if idlObject is a constructor and false otherwise. + """ + if isConstructor: + return "%s constructor" % GetConstructorNameForReporting(descriptor, idlObject) + + namePrefix = descriptor.interface.identifier.name + name = GetWebExposedName(idlObject, descriptor) + if " " in name: + # It's got a space already, so just space-separate. + return "%s %s" % (namePrefix, name) + + return "%s.%s" % (namePrefix, name) + + +class CGSpecializedMethod(CGAbstractStaticMethod): + """ + A class for generating the C++ code for a specialized method that the JIT + can call with lower overhead. + """ + + def __init__(self, descriptor, method): + self.method = method + name = CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name)) + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "obj"), + Argument("void*", "void_self"), + Argument("const JSJitMethodCallArgs&", "args"), + ] + CGAbstractStaticMethod.__init__( + self, descriptor, name, "bool", args, canRunScript=True + ) + + def definition_body(self): + nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, self.method) + call = CGMethodCall( + nativeName, self.method.isStatic(), self.descriptor, self.method + ).define() + prefix = "" + if self.method.getExtendedAttribute("CrossOriginCallable"): + for signature in self.method.signatures(): + # non-undefined signatures would require us to deal with remote proxies for the + # return value here. + if not signature[0].isUndefined(): + raise TypeError( + "We don't support a method marked as CrossOriginCallable " + "with non-undefined return type" + ) + prototypeID, _ = PrototypeIDAndDepth(self.descriptor) + prefix = fill( + """ + // CrossOriginThisPolicy::UnwrapThisObject stores a ${nativeType}::RemoteProxy in void_self + // if obj is a proxy with a RemoteObjectProxy handler for the right type, or else it stores + // a ${nativeType}. If we get here from the JIT (without going through UnwrapThisObject) we + // know void_self contains a ${nativeType}; we don't have special cases in the JIT to deal + // with remote object proxies. + if (IsRemoteObjectProxy(obj, ${prototypeID})) { + auto* self = static_cast<${nativeType}::RemoteProxy*>(void_self); + $*{call} + } + """, + prototypeID=prototypeID, + nativeType=self.descriptor.nativeType, + call=call, + ) + return prefix + fill( + """ + auto* self = static_cast<${nativeType}*>(void_self); + $*{call} + """, + nativeType=self.descriptor.nativeType, + call=call, + ) + + def auto_profiler_label(self): + interface_name = self.descriptor.interface.identifier.name + method_name = self.method.identifier.name + return fill( + """ + AUTO_PROFILER_LABEL_DYNAMIC_FAST( + "${interface_name}", "${method_name}", DOM, cx, + uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_METHOD) | + uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); + """, + interface_name=interface_name, + method_name=method_name, + ) + + @staticmethod + def should_have_method_description(descriptor, idlMethod): + """ + Returns whether the given IDL method (static, non-static, constructor) + should have a method description declaration, for use in error + reporting. + """ + # If a method has overloads, it needs a method description, because it + # can throw MSG_INVALID_OVERLOAD_ARGCOUNT at the very least. + if len(idlMethod.signatures()) != 1: + return True + + # Methods with only one signature need a method description if one of + # their args needs it. + sig = idlMethod.signatures()[0] + args = sig[1] + return any( + idlTypeNeedsCallContext( + arg.type, + descriptor, + allowTreatNonCallableAsNull=arg.allowTreatNonCallableAsNull(), + ) + for arg in args + ) + + @staticmethod + def error_reporting_label_helper(descriptor, idlMethod, isConstructor): + """ + Returns the method description to use for error reporting for the given + IDL method. Used to implement common error_reporting_label() functions + across different classes. + """ + if not CGSpecializedMethod.should_have_method_description( + descriptor, idlMethod + ): + return None + return GetLabelForErrorReporting(descriptor, idlMethod, isConstructor) + + def error_reporting_label(self): + return CGSpecializedMethod.error_reporting_label_helper( + self.descriptor, self.method, isConstructor=False + ) + + @staticmethod + def makeNativeName(descriptor, method): + if method.underlyingAttr: + return CGSpecializedGetter.makeNativeName(descriptor, method.underlyingAttr) + name = method.identifier.name + return MakeNativeName(descriptor.binaryNameFor(name, method.isStatic())) + + +class CGMethodPromiseWrapper(CGAbstractStaticMethod): + """ + A class for generating a wrapper around another method that will + convert exceptions to promises. + """ + + def __init__(self, descriptor, methodToWrap): + self.method = methodToWrap + name = self.makeName(methodToWrap.name) + args = list(methodToWrap.args) + CGAbstractStaticMethod.__init__( + self, descriptor, name, "bool", args, canRunScript=True + ) + + def definition_body(self): + return fill( + """ + bool ok = ${methodName}(${args}); + if (ok) { + return true; + } + return ConvertExceptionToPromise(cx, args.rval()); + """, + methodName=self.method.name, + args=", ".join(arg.name for arg in self.args), + ) + + @staticmethod + def makeName(methodName): + return methodName + "_promiseWrapper" + + +class CGDefaultToJSONMethod(CGSpecializedMethod): + def __init__(self, descriptor, method): + assert method.isDefaultToJSON() + CGSpecializedMethod.__init__(self, descriptor, method) + + def definition_body(self): + ret = fill( + """ + auto* self = static_cast<${nativeType}*>(void_self); + JS::Rooted<JSObject*> result(cx, JS_NewPlainObject(cx)); + if (!result) { + return false; + } + """, + nativeType=self.descriptor.nativeType, + ) + + jsonDescriptors = [self.descriptor] + interface = self.descriptor.interface.parent + while interface: + descriptor = self.descriptor.getDescriptor(interface.identifier.name) + if descriptor.hasDefaultToJSON: + jsonDescriptors.append(descriptor) + interface = interface.parent + + # Iterate the array in reverse: oldest ancestor first + for descriptor in jsonDescriptors[::-1]: + ret += fill( + """ + if (!${parentclass}::CollectJSONAttributes(cx, obj, MOZ_KnownLive(self), result)) { + return false; + } + """, + parentclass=toBindingNamespace(descriptor.name), + ) + ret += "args.rval().setObject(*result);\n" "return true;\n" + return ret + + +class CGLegacyCallHook(CGAbstractBindingMethod): + """ + Call hook for our object + """ + + def __init__(self, descriptor): + self._legacycaller = descriptor.operations["LegacyCaller"] + # Our "self" is actually the callee in this case, not the thisval. + CGAbstractBindingMethod.__init__( + self, + descriptor, + LEGACYCALLER_HOOK_NAME, + JSNativeArguments(), + getThisObj="&args.callee()", + ) + + def define(self): + if not self._legacycaller: + return "" + return CGAbstractBindingMethod.define(self) + + def generate_code(self): + name = self._legacycaller.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNameFor(name, False)) + return CGMethodCall(nativeName, False, self.descriptor, self._legacycaller) + + def error_reporting_label(self): + # Should act like methods. + return CGSpecializedMethod.error_reporting_label_helper( + self.descriptor, self._legacycaller, isConstructor=False + ) + + +class CGResolveHook(CGAbstractClassHook): + """ + Resolve hook for objects that have the NeedResolve extended attribute. + """ + + def __init__(self, descriptor): + assert descriptor.interface.getExtendedAttribute("NeedResolve") + + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "obj"), + Argument("JS::Handle<jsid>", "id"), + Argument("bool*", "resolvedp"), + ] + CGAbstractClassHook.__init__(self, descriptor, RESOLVE_HOOK_NAME, "bool", args) + + def generate_code(self): + return dedent( + """ + JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(cx); + if (!self->DoResolve(cx, obj, id, &desc)) { + return false; + } + if (desc.isNothing()) { + return true; + } + // If desc.value() is undefined, then the DoResolve call + // has already defined it on the object. Don't try to also + // define it. + MOZ_ASSERT(desc->isDataDescriptor()); + if (!desc->value().isUndefined()) { + JS::Rooted<JS::PropertyDescriptor> defineDesc(cx, *desc); + defineDesc.setResolving(true); + if (!JS_DefinePropertyById(cx, obj, id, defineDesc)) { + return false; + } + } + *resolvedp = true; + return true; + """ + ) + + def definition_body(self): + if self.descriptor.isGlobal(): + # Resolve standard classes + prefix = dedent( + """ + if (!ResolveGlobal(cx, obj, id, resolvedp)) { + return false; + } + if (*resolvedp) { + return true; + } + + """ + ) + else: + prefix = "" + return prefix + CGAbstractClassHook.definition_body(self) + + +class CGMayResolveHook(CGAbstractStaticMethod): + """ + Resolve hook for objects that have the NeedResolve extended attribute. + """ + + def __init__(self, descriptor): + assert descriptor.interface.getExtendedAttribute("NeedResolve") + + args = [ + Argument("const JSAtomState&", "names"), + Argument("jsid", "id"), + Argument("JSObject*", "maybeObj"), + ] + CGAbstractStaticMethod.__init__( + self, descriptor, MAY_RESOLVE_HOOK_NAME, "bool", args + ) + + def definition_body(self): + if self.descriptor.isGlobal(): + # Check whether this would resolve as a standard class. + prefix = dedent( + """ + if (MayResolveGlobal(names, id, maybeObj)) { + return true; + } + + """ + ) + else: + prefix = "" + return prefix + "return %s::MayResolve(id);\n" % self.descriptor.nativeType + + +class CGEnumerateHook(CGAbstractBindingMethod): + """ + Enumerate hook for objects with custom hooks. + """ + + def __init__(self, descriptor): + assert descriptor.interface.getExtendedAttribute("NeedResolve") + + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "obj"), + Argument("JS::MutableHandleVector<jsid>", "properties"), + Argument("bool", "enumerableOnly"), + ] + # Our "self" is actually the "obj" argument in this case, not the thisval. + CGAbstractBindingMethod.__init__( + self, descriptor, NEW_ENUMERATE_HOOK_NAME, args, getThisObj="", callArgs="" + ) + + def generate_code(self): + return CGGeneric( + dedent( + """ + FastErrorResult rv; + self->GetOwnPropertyNames(cx, properties, enumerableOnly, rv); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + return true; + """ + ) + ) + + def definition_body(self): + if self.descriptor.isGlobal(): + # Enumerate standard classes + prefix = dedent( + """ + if (!EnumerateGlobal(cx, obj, properties, enumerableOnly)) { + return false; + } + + """ + ) + else: + prefix = "" + return prefix + CGAbstractBindingMethod.definition_body(self) + + +class CppKeywords: + """ + A class for checking if method names declared in webidl + are not in conflict with C++ keywords. + """ + + keywords = frozenset( + [ + "alignas", + "alignof", + "and", + "and_eq", + "asm", + "assert", + "auto", + "bitand", + "bitor", + "bool", + "break", + "case", + "catch", + "char", + "char16_t", + "char32_t", + "class", + "compl", + "const", + "constexpr", + "const_cast", + "continue", + "decltype", + "default", + "delete", + "do", + "double", + "dynamic_cast", + "else", + "enum", + "explicit", + "export", + "extern", + "false", + "final", + "float", + "for", + "friend", + "goto", + "if", + "inline", + "int", + "long", + "mutable", + "namespace", + "new", + "noexcept", + "not", + "not_eq", + "nullptr", + "operator", + "or", + "or_eq", + "override", + "private", + "protected", + "public", + "register", + "reinterpret_cast", + "return", + "short", + "signed", + "sizeof", + "static", + "static_assert", + "static_cast", + "struct", + "switch", + "template", + "this", + "thread_local", + "throw", + "true", + "try", + "typedef", + "typeid", + "typename", + "union", + "unsigned", + "using", + "virtual", + "void", + "volatile", + "wchar_t", + "while", + "xor", + "xor_eq", + ] + ) + + @staticmethod + def checkMethodName(name): + # Double '_' because 'assert' and '_assert' cannot be used in MS2013 compiler. + # Bug 964892 and bug 963560. + if name in CppKeywords.keywords: + name = "_" + name + "_" + return name + + +class CGStaticMethod(CGAbstractStaticBindingMethod): + """ + A class for generating the C++ code for an IDL static method. + """ + + def __init__(self, descriptor, method): + self.method = method + name = CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name)) + CGAbstractStaticBindingMethod.__init__(self, descriptor, name) + + def generate_code(self): + nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, self.method) + return CGMethodCall(nativeName, True, self.descriptor, self.method) + + def auto_profiler_label(self): + interface_name = self.descriptor.interface.identifier.name + method_name = self.method.identifier.name + return fill( + """ + AUTO_PROFILER_LABEL_DYNAMIC_FAST( + "${interface_name}", "${method_name}", DOM, cx, + uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_METHOD) | + uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); + """, + interface_name=interface_name, + method_name=method_name, + ) + + def error_reporting_label(self): + return CGSpecializedMethod.error_reporting_label_helper( + self.descriptor, self.method, isConstructor=False + ) + + +class CGSpecializedGetter(CGAbstractStaticMethod): + """ + A class for generating the code for a specialized attribute getter + that the JIT can call with lower overhead. + """ + + def __init__(self, descriptor, attr): + self.attr = attr + name = "get_" + IDLToCIdentifier(attr.identifier.name) + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "obj"), + Argument("void*", "void_self"), + Argument("JSJitGetterCallArgs", "args"), + ] + # StoreInSlot attributes have their getters called from Wrap(). We + # really hope they can't run script, and don't want to annotate Wrap() + # methods as doing that anyway, so let's not annotate them as + # MOZ_CAN_RUN_SCRIPT. + CGAbstractStaticMethod.__init__( + self, + descriptor, + name, + "bool", + args, + canRunScript=not attr.getExtendedAttribute("StoreInSlot"), + ) + + def definition_body(self): + prefix = fill( + """ + auto* self = static_cast<${nativeType}*>(void_self); + """, + nativeType=self.descriptor.nativeType, + ) + + if self.attr.isMaplikeOrSetlikeAttr(): + assert not self.attr.getExtendedAttribute("CrossOriginReadable") + # If the interface is maplike/setlike, there will be one getter + # method for the size property of the backing object. Due to having + # to unpack the backing object from the slot, this requires its own + # generator. + return prefix + getMaplikeOrSetlikeSizeGetterBody( + self.descriptor, self.attr + ) + + if self.attr.type.isObservableArray(): + assert not self.attr.getExtendedAttribute("CrossOriginReadable") + # If the attribute is observableArray, due to having to unpack the + # backing object from the slot, this requires its own generator. + return prefix + getObservableArrayGetterBody(self.descriptor, self.attr) + + nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, self.attr) + type = self.attr.type + if self.attr.getExtendedAttribute("CrossOriginReadable"): + remoteType = type + extendedAttributes = self.descriptor.getExtendedAttributes( + self.attr, getter=True + ) + if ( + remoteType.isGeckoInterface() + and not remoteType.unroll().inner.isExternal() + and remoteType.unroll().inner.getExtendedAttribute("ChromeOnly") is None + ): + # We'll use a JSObject. It might make more sense to use remoteType's + # RemoteProxy, but it's not easy to construct a type for that from here. + remoteType = BuiltinTypes[IDLBuiltinType.Types.object] + if "needsErrorResult" not in extendedAttributes: + extendedAttributes.append("needsErrorResult") + prototypeID, _ = PrototypeIDAndDepth(self.descriptor) + prefix = ( + fill( + """ + if (IsRemoteObjectProxy(obj, ${prototypeID})) { + ${nativeType}::RemoteProxy* self = static_cast<${nativeType}::RemoteProxy*>(void_self); + $*{call} + } + """, + prototypeID=prototypeID, + nativeType=self.descriptor.nativeType, + call=CGGetterCall( + remoteType, + nativeName, + self.descriptor, + self.attr, + dontSetSlot=True, + extendedAttributes=extendedAttributes, + ).define(), + ) + + prefix + ) + + if self.attr.slotIndices is not None: + # We're going to store this return value in a slot on some object, + # to cache it. The question is, which object? For dictionary and + # sequence return values, we want to use a slot on the Xray expando + # if we're called via Xrays, and a slot on our reflector otherwise. + # On the other hand, when dealing with some interfacce types + # (e.g. window.document) we want to avoid calling the getter more + # than once. In the case of window.document, it's because the + # getter can start returning null, which would get hidden in the + # non-Xray case by the fact that it's [StoreOnSlot], so the cached + # version is always around. + # + # The upshot is that we use the reflector slot for any getter whose + # type is a gecko interface, whether we're called via Xrays or not. + # Since [Cached] and [StoreInSlot] cannot be used with "NewObject", + # we know that in the interface type case the returned object is + # wrappercached. So creating Xrays to it is reasonable. + if mayUseXrayExpandoSlots(self.descriptor, self.attr): + prefix += fill( + """ + // Have to either root across the getter call or reget after. + bool isXray; + JS::Rooted<JSObject*> slotStorage(cx, GetCachedSlotStorageObject(cx, obj, &isXray)); + if (!slotStorage) { + return false; + } + const size_t slotIndex = isXray ? ${xraySlotIndex} : ${slotIndex}; + """, + xraySlotIndex=memberXrayExpandoReservedSlot( + self.attr, self.descriptor + ), + slotIndex=memberReservedSlot(self.attr, self.descriptor), + ) + else: + prefix += fill( + """ + // Have to either root across the getter call or reget after. + JS::Rooted<JSObject*> slotStorage(cx, js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false)); + MOZ_ASSERT(IsDOMObject(slotStorage)); + const size_t slotIndex = ${slotIndex}; + """, + slotIndex=memberReservedSlot(self.attr, self.descriptor), + ) + + prefix += fill( + """ + MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(JS::GetClass(slotStorage)) > slotIndex); + { + // Scope for cachedVal + JS::Value cachedVal = JS::GetReservedSlot(slotStorage, slotIndex); + if (!cachedVal.isUndefined()) { + args.rval().set(cachedVal); + // The cached value is in the compartment of slotStorage, + // so wrap into the caller compartment as needed. + return ${maybeWrap}(cx, args.rval()); + } + } + + """, + maybeWrap=getMaybeWrapValueFuncForType(self.attr.type), + ) + + return ( + prefix + CGGetterCall(type, nativeName, self.descriptor, self.attr).define() + ) + + def auto_profiler_label(self): + interface_name = self.descriptor.interface.identifier.name + attr_name = self.attr.identifier.name + return fill( + """ + AUTO_PROFILER_LABEL_DYNAMIC_FAST( + "${interface_name}", "${attr_name}", DOM, cx, + uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_GETTER) | + uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); + """, + interface_name=interface_name, + attr_name=attr_name, + ) + + def error_reporting_label(self): + # Getters never need a BindingCallContext. + return None + + @staticmethod + def makeNativeName(descriptor, attr): + name = attr.identifier.name + nativeName = MakeNativeName(descriptor.binaryNameFor(name, attr.isStatic())) + _, resultOutParam, _, _, _ = getRetvalDeclarationForType(attr.type, descriptor) + extendedAttrs = descriptor.getExtendedAttributes(attr, getter=True) + canFail = "needsErrorResult" in extendedAttrs or "canOOM" in extendedAttrs + if resultOutParam or attr.type.nullable() or canFail: + nativeName = "Get" + nativeName + return nativeName + + +class CGGetterPromiseWrapper(CGAbstractStaticMethod): + """ + A class for generating a wrapper around another getter that will + convert exceptions to promises. + """ + + def __init__(self, descriptor, getterToWrap): + self.getter = getterToWrap + name = self.makeName(getterToWrap.name) + args = list(getterToWrap.args) + CGAbstractStaticMethod.__init__( + self, descriptor, name, "bool", args, canRunScript=True + ) + + def definition_body(self): + return fill( + """ + bool ok = ${getterName}(${args}); + if (ok) { + return true; + } + return ConvertExceptionToPromise(cx, args.rval()); + """, + getterName=self.getter.name, + args=", ".join(arg.name for arg in self.args), + ) + + @staticmethod + def makeName(getterName): + return getterName + "_promiseWrapper" + + +class CGStaticGetter(CGAbstractStaticBindingMethod): + """ + A class for generating the C++ code for an IDL static attribute getter. + """ + + def __init__(self, descriptor, attr): + self.attr = attr + name = "get_" + IDLToCIdentifier(attr.identifier.name) + CGAbstractStaticBindingMethod.__init__(self, descriptor, name) + + def generate_code(self): + nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, self.attr) + return CGGetterCall(self.attr.type, nativeName, self.descriptor, self.attr) + + def auto_profiler_label(self): + interface_name = self.descriptor.interface.identifier.name + attr_name = self.attr.identifier.name + return fill( + """ + AUTO_PROFILER_LABEL_DYNAMIC_FAST( + "${interface_name}", "${attr_name}", DOM, cx, + uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_GETTER) | + uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); + """, + interface_name=interface_name, + attr_name=attr_name, + ) + + def error_reporting_label(self): + # Getters never need a BindingCallContext. + return None + + +class CGSpecializedSetter(CGAbstractStaticMethod): + """ + A class for generating the code for a specialized attribute setter + that the JIT can call with lower overhead. + """ + + def __init__(self, descriptor, attr): + self.attr = attr + name = "set_" + IDLToCIdentifier(attr.identifier.name) + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "obj"), + Argument("void*", "void_self"), + Argument("JSJitSetterCallArgs", "args"), + ] + CGAbstractStaticMethod.__init__( + self, descriptor, name, "bool", args, canRunScript=True + ) + + def definition_body(self): + nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, self.attr) + type = self.attr.type + call = CGSetterCall(type, nativeName, self.descriptor, self.attr).define() + prefix = "" + if self.attr.getExtendedAttribute("CrossOriginWritable"): + if type.isGeckoInterface() and not type.unroll().inner.isExternal(): + # a setter taking a Gecko interface would require us to deal with remote + # proxies for the value here. + raise TypeError( + "We don't support the setter of %s marked as " + "CrossOriginWritable because it takes a Gecko interface " + "as the value", + self.attr.identifier.name, + ) + prototypeID, _ = PrototypeIDAndDepth(self.descriptor) + prefix = fill( + """ + if (IsRemoteObjectProxy(obj, ${prototypeID})) { + auto* self = static_cast<${nativeType}::RemoteProxy*>(void_self); + $*{call} + } + """, + prototypeID=prototypeID, + nativeType=self.descriptor.nativeType, + call=call, + ) + + return prefix + fill( + """ + auto* self = static_cast<${nativeType}*>(void_self); + $*{call} + """, + nativeType=self.descriptor.nativeType, + call=call, + ) + + def auto_profiler_label(self): + interface_name = self.descriptor.interface.identifier.name + attr_name = self.attr.identifier.name + return fill( + """ + AUTO_PROFILER_LABEL_DYNAMIC_FAST( + "${interface_name}", "${attr_name}", DOM, cx, + uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_SETTER) | + uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); + """, + interface_name=interface_name, + attr_name=attr_name, + ) + + @staticmethod + def error_reporting_label_helper(descriptor, attr): + # Setters need a BindingCallContext if the type of the attribute needs + # one. + if not idlTypeNeedsCallContext( + attr.type, descriptor, allowTreatNonCallableAsNull=True + ): + return None + return ( + GetLabelForErrorReporting(descriptor, attr, isConstructor=False) + " setter" + ) + + def error_reporting_label(self): + return CGSpecializedSetter.error_reporting_label_helper( + self.descriptor, self.attr + ) + + @staticmethod + def makeNativeName(descriptor, attr): + name = attr.identifier.name + return "Set" + MakeNativeName(descriptor.binaryNameFor(name, attr.isStatic())) + + +class CGStaticSetter(CGAbstractStaticBindingMethod): + """ + A class for generating the C++ code for an IDL static attribute setter. + """ + + def __init__(self, descriptor, attr): + self.attr = attr + name = "set_" + IDLToCIdentifier(attr.identifier.name) + CGAbstractStaticBindingMethod.__init__(self, descriptor, name) + + def generate_code(self): + nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, self.attr) + checkForArg = CGGeneric( + fill( + """ + if (!args.requireAtLeast(cx, "${name} setter", 1)) { + return false; + } + """, + name=self.attr.identifier.name, + ) + ) + call = CGSetterCall(self.attr.type, nativeName, self.descriptor, self.attr) + return CGList([checkForArg, call]) + + def auto_profiler_label(self): + interface_name = self.descriptor.interface.identifier.name + attr_name = self.attr.identifier.name + return fill( + """ + AUTO_PROFILER_LABEL_DYNAMIC_FAST( + "${interface_name}", "${attr_name}", DOM, cx, + uint32_t(js::ProfilingStackFrame::Flags::STRING_TEMPLATE_SETTER) | + uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS)); + """, + interface_name=interface_name, + attr_name=attr_name, + ) + + def error_reporting_label(self): + return CGSpecializedSetter.error_reporting_label_helper( + self.descriptor, self.attr + ) + + +class CGSpecializedForwardingSetter(CGSpecializedSetter): + """ + A class for generating the code for a specialized attribute setter with + PutForwards that the JIT can call with lower overhead. + """ + + def __init__(self, descriptor, attr): + CGSpecializedSetter.__init__(self, descriptor, attr) + + def definition_body(self): + attrName = self.attr.identifier.name + forwardToAttrName = self.attr.getExtendedAttribute("PutForwards")[0] + # JS_GetProperty and JS_SetProperty can only deal with ASCII + assert all(ord(c) < 128 for c in attrName) + assert all(ord(c) < 128 for c in forwardToAttrName) + return fill( + """ + JS::Rooted<JS::Value> v(cx); + if (!JS_GetProperty(cx, obj, "${attr}", &v)) { + return false; + } + + if (!v.isObject()) { + return cx.ThrowErrorMessage<MSG_NOT_OBJECT>("${interface}.${attr}"); + } + + JS::Rooted<JSObject*> targetObj(cx, &v.toObject()); + return JS_SetProperty(cx, targetObj, "${forwardToAttrName}", args[0]); + """, + attr=attrName, + interface=self.descriptor.interface.identifier.name, + forwardToAttrName=forwardToAttrName, + ) + + def error_reporting_label(self): + # We always need to be able to throw. + return ( + GetLabelForErrorReporting(self.descriptor, self.attr, isConstructor=False) + + " setter" + ) + + +class CGSpecializedReplaceableSetter(CGSpecializedSetter): + """ + A class for generating the code for a specialized attribute setter with + Replaceable that the JIT can call with lower overhead. + """ + + def __init__(self, descriptor, attr): + CGSpecializedSetter.__init__(self, descriptor, attr) + + def definition_body(self): + attrName = self.attr.identifier.name + # JS_DefineProperty can only deal with ASCII + assert all(ord(c) < 128 for c in attrName) + return ( + 'return JS_DefineProperty(cx, obj, "%s", args[0], JSPROP_ENUMERATE);\n' + % attrName + ) + + def error_reporting_label(self): + # We never throw directly. + return None + + +class CGSpecializedLenientSetter(CGSpecializedSetter): + """ + A class for generating the code for a specialized attribute setter with + LenientSetter that the JIT can call with lower overhead. + """ + + def __init__(self, descriptor, attr): + CGSpecializedSetter.__init__(self, descriptor, attr) + + def definition_body(self): + attrName = self.attr.identifier.name + # JS_DefineProperty can only deal with ASCII + assert all(ord(c) < 128 for c in attrName) + return dedent( + """ + DeprecationWarning(cx, obj, DeprecatedOperations::eLenientSetter); + return true; + """ + ) + + def error_reporting_label(self): + # We never throw; that's the whole point. + return None + + +def memberReturnsNewObject(member): + return member.getExtendedAttribute("NewObject") is not None + + +class CGMemberJITInfo(CGThing): + """ + A class for generating the JITInfo for a property that points to + our specialized getter and setter. + """ + + def __init__(self, descriptor, member): + self.member = member + self.descriptor = descriptor + + def declare(self): + return "" + + def defineJitInfo( + self, + infoName, + opName, + opType, + infallible, + movable, + eliminatable, + aliasSet, + alwaysInSlot, + lazilyInSlot, + slotIndex, + returnTypes, + args, + ): + """ + aliasSet is a JSJitInfo::AliasSet value, without the "JSJitInfo::" bit. + + args is None if we don't want to output argTypes for some + reason (e.g. we have overloads or we're not a method) and + otherwise an iterable of the arguments for this method. + """ + assert ( + not movable or aliasSet != "AliasEverything" + ) # Can't move write-aliasing things + assert ( + not alwaysInSlot or movable + ) # Things always in slots had better be movable + assert ( + not eliminatable or aliasSet != "AliasEverything" + ) # Can't eliminate write-aliasing things + assert ( + not alwaysInSlot or eliminatable + ) # Things always in slots had better be eliminatable + + def jitInfoInitializer(isTypedMethod): + initializer = fill( + """ + { + { ${opName} }, + { prototypes::id::${name} }, + { PrototypeTraits<prototypes::id::${name}>::Depth }, + JSJitInfo::${opType}, + JSJitInfo::${aliasSet}, /* aliasSet. Not relevant for setters. */ + ${returnType}, /* returnType. Not relevant for setters. */ + ${isInfallible}, /* isInfallible. False in setters. */ + ${isMovable}, /* isMovable. Not relevant for setters. */ + ${isEliminatable}, /* isEliminatable. Not relevant for setters. */ + ${isAlwaysInSlot}, /* isAlwaysInSlot. Only relevant for getters. */ + ${isLazilyCachedInSlot}, /* isLazilyCachedInSlot. Only relevant for getters. */ + ${isTypedMethod}, /* isTypedMethod. Only relevant for methods. */ + ${slotIndex} /* Reserved slot index, if we're stored in a slot, else 0. */ + } + """, + opName=opName, + name=self.descriptor.name, + opType=opType, + aliasSet=aliasSet, + returnType=functools.reduce( + CGMemberJITInfo.getSingleReturnType, returnTypes, "" + ), + isInfallible=toStringBool(infallible), + isMovable=toStringBool(movable), + isEliminatable=toStringBool(eliminatable), + isAlwaysInSlot=toStringBool(alwaysInSlot), + isLazilyCachedInSlot=toStringBool(lazilyInSlot), + isTypedMethod=toStringBool(isTypedMethod), + slotIndex=slotIndex, + ) + return initializer.rstrip() + + slotAssert = fill( + """ + static_assert(${slotIndex} <= JSJitInfo::maxSlotIndex, "We won't fit"); + static_assert(${slotIndex} < ${classReservedSlots}, "There is no slot for us"); + """, + slotIndex=slotIndex, + classReservedSlots=INSTANCE_RESERVED_SLOTS + + self.descriptor.interface.totalMembersInSlots, + ) + if args is not None: + argTypes = "%s_argTypes" % infoName + args = [CGMemberJITInfo.getJSArgType(arg.type) for arg in args] + args.append("JSJitInfo::ArgTypeListEnd") + argTypesDecl = "static const JSJitInfo::ArgType %s[] = { %s };\n" % ( + argTypes, + ", ".join(args), + ) + return fill( + """ + $*{argTypesDecl} + static const JSTypedMethodJitInfo ${infoName} = { + ${jitInfo}, + ${argTypes} + }; + $*{slotAssert} + """, + argTypesDecl=argTypesDecl, + infoName=infoName, + jitInfo=indent(jitInfoInitializer(True)), + argTypes=argTypes, + slotAssert=slotAssert, + ) + + # Unexposed things are meant to be used from C++ directly, so we make + # their jitinfo non-static. That way C++ can get at it. + if self.member.getExtendedAttribute("Unexposed"): + storageClass = "extern" + else: + storageClass = "static" + + return fill( + """ + ${storageClass} const JSJitInfo ${infoName} = ${jitInfo}; + $*{slotAssert} + """, + storageClass=storageClass, + infoName=infoName, + jitInfo=jitInfoInitializer(False), + slotAssert=slotAssert, + ) + + def define(self): + if self.member.isAttr(): + getterinfo = "%s_getterinfo" % IDLToCIdentifier(self.member.identifier.name) + name = IDLToCIdentifier(self.member.identifier.name) + if self.member.type.isPromise(): + name = CGGetterPromiseWrapper.makeName(name) + getter = "get_%s" % name + extendedAttrs = self.descriptor.getExtendedAttributes( + self.member, getter=True + ) + getterinfal = "needsErrorResult" not in extendedAttrs + + # At this point getterinfal is true if our getter either can't throw + # at all, or can only throw OOM. In both cases, it's safe to move, + # or dead-code-eliminate, the getter, because throwing OOM is not + # semantically meaningful, so code can't rely on it happening. Note + # that this makes the behavior consistent for OOM thrown from the + # getter itself and OOM thrown from the to-JS conversion of the + # return value (see the "canOOM" and "infallibleForMember" checks + # below). + movable = self.mayBeMovable() and getterinfal + eliminatable = self.mayBeEliminatable() and getterinfal + aliasSet = self.aliasSet() + + # Now we have to set getterinfal to whether we can _really_ ever + # throw, from the point of view of the JS engine. + getterinfal = ( + getterinfal + and "canOOM" not in extendedAttrs + and infallibleForMember(self.member, self.member.type, self.descriptor) + ) + isAlwaysInSlot = self.member.getExtendedAttribute("StoreInSlot") + + if self.member.slotIndices is not None: + assert ( + isAlwaysInSlot + or self.member.getExtendedAttribute("Cached") + or self.member.type.isObservableArray() + ) + isLazilyCachedInSlot = not isAlwaysInSlot + slotIndex = memberReservedSlot(self.member, self.descriptor) + # We'll statically assert that this is not too big in + # CGUpdateMemberSlotsMethod, in the case when + # isAlwaysInSlot is true. + else: + isLazilyCachedInSlot = False + slotIndex = "0" + + result = self.defineJitInfo( + getterinfo, + getter, + "Getter", + getterinfal, + movable, + eliminatable, + aliasSet, + isAlwaysInSlot, + isLazilyCachedInSlot, + slotIndex, + [self.member.type], + None, + ) + if ( + not self.member.readonly + or self.member.getExtendedAttribute("PutForwards") is not None + or self.member.getExtendedAttribute("Replaceable") is not None + or self.member.getExtendedAttribute("LegacyLenientSetter") is not None + ): + setterinfo = "%s_setterinfo" % IDLToCIdentifier( + self.member.identifier.name + ) + # Actually a JSJitSetterOp, but JSJitGetterOp is first in the + # union. + setter = "(JSJitGetterOp)set_%s" % IDLToCIdentifier( + self.member.identifier.name + ) + # Setters are always fallible, since they have to do a typed unwrap. + result += self.defineJitInfo( + setterinfo, + setter, + "Setter", + False, + False, + False, + "AliasEverything", + False, + False, + "0", + [BuiltinTypes[IDLBuiltinType.Types.undefined]], + None, + ) + return result + if self.member.isMethod(): + methodinfo = "%s_methodinfo" % IDLToCIdentifier(self.member.identifier.name) + name = CppKeywords.checkMethodName( + IDLToCIdentifier(self.member.identifier.name) + ) + if self.member.returnsPromise(): + name = CGMethodPromiseWrapper.makeName(name) + # Actually a JSJitMethodOp, but JSJitGetterOp is first in the union. + method = "(JSJitGetterOp)%s" % name + + # Methods are infallible if they are infallible, have no arguments + # to unwrap, and have a return type that's infallible to wrap up for + # return. + sigs = self.member.signatures() + if len(sigs) != 1: + # Don't handle overloading. If there's more than one signature, + # one of them must take arguments. + methodInfal = False + args = None + movable = False + eliminatable = False + else: + sig = sigs[0] + # For methods that affect nothing, it's OK to set movable to our + # notion of infallible on the C++ side, without considering + # argument conversions, since argument conversions that can + # reliably throw would be effectful anyway and the jit doesn't + # move effectful things. + extendedAttrs = self.descriptor.getExtendedAttributes(self.member) + hasInfallibleImpl = "needsErrorResult" not in extendedAttrs + # At this point hasInfallibleImpl is true if our method either + # can't throw at all, or can only throw OOM. In both cases, it + # may be safe to move, or dead-code-eliminate, the method, + # because throwing OOM is not semantically meaningful, so code + # can't rely on it happening. Note that this makes the behavior + # consistent for OOM thrown from the method itself and OOM + # thrown from the to-JS conversion of the return value (see the + # "canOOM" and "infallibleForMember" checks below). + movable = self.mayBeMovable() and hasInfallibleImpl + eliminatable = self.mayBeEliminatable() and hasInfallibleImpl + # XXXbz can we move the smarts about fallibility due to arg + # conversions into the JIT, using our new args stuff? + if len(sig[1]) != 0 or not infallibleForMember( + self.member, sig[0], self.descriptor + ): + # We have arguments or our return-value boxing can fail + methodInfal = False + else: + methodInfal = hasInfallibleImpl and "canOOM" not in extendedAttrs + # For now, only bother to output args if we're side-effect-free. + if self.member.affects == "Nothing": + args = sig[1] + else: + args = None + + aliasSet = self.aliasSet() + result = self.defineJitInfo( + methodinfo, + method, + "Method", + methodInfal, + movable, + eliminatable, + aliasSet, + False, + False, + "0", + [s[0] for s in sigs], + args, + ) + return result + raise TypeError("Illegal member type to CGPropertyJITInfo") + + def mayBeMovable(self): + """ + Returns whether this attribute or method may be movable, just + based on Affects/DependsOn annotations. + """ + affects = self.member.affects + dependsOn = self.member.dependsOn + assert affects in IDLInterfaceMember.AffectsValues + assert dependsOn in IDLInterfaceMember.DependsOnValues + # Things that are DependsOn=DeviceState are not movable, because we + # don't want them coalesced with each other or loop-hoisted, since + # their return value can change even if nothing is going on from our + # point of view. + return affects == "Nothing" and ( + dependsOn != "Everything" and dependsOn != "DeviceState" + ) + + def mayBeEliminatable(self): + """ + Returns whether this attribute or method may be eliminatable, just + based on Affects/DependsOn annotations. + """ + # dependsOn shouldn't affect this decision at all, except in jitinfo we + # have no way to express "Depends on everything, affects nothing", + # because we only have three alias set values: AliasNone ("depends on + # nothing, affects nothing"), AliasDOMSets ("depends on DOM sets, + # affects nothing"), AliasEverything ("depends on everything, affects + # everything"). So the [Affects=Nothing, DependsOn=Everything] case + # gets encoded as AliasEverything and defineJitInfo asserts that if our + # alias state is AliasEverything then we're not eliminatable (because it + # thinks we might have side-effects at that point). Bug 1155796 is + # tracking possible solutions for this. + affects = self.member.affects + dependsOn = self.member.dependsOn + assert affects in IDLInterfaceMember.AffectsValues + assert dependsOn in IDLInterfaceMember.DependsOnValues + return affects == "Nothing" and dependsOn != "Everything" + + def aliasSet(self): + """ + Returns the alias set to store in the jitinfo. This may not be the + effective alias set the JIT uses, depending on whether we have enough + information about our args to allow the JIT to prove that effectful + argument conversions won't happen. + """ + dependsOn = self.member.dependsOn + assert dependsOn in IDLInterfaceMember.DependsOnValues + + if dependsOn == "Nothing" or dependsOn == "DeviceState": + assert self.member.affects == "Nothing" + return "AliasNone" + + if dependsOn == "DOMState": + assert self.member.affects == "Nothing" + return "AliasDOMSets" + + return "AliasEverything" + + @staticmethod + def getJSReturnTypeTag(t): + if t.nullable(): + # Sometimes it might return null, sometimes not + return "JSVAL_TYPE_UNKNOWN" + if t.isUndefined(): + # No return, every time + return "JSVAL_TYPE_UNDEFINED" + if t.isSequence(): + return "JSVAL_TYPE_OBJECT" + if t.isRecord(): + return "JSVAL_TYPE_OBJECT" + if t.isPromise(): + return "JSVAL_TYPE_OBJECT" + if t.isGeckoInterface(): + return "JSVAL_TYPE_OBJECT" + if t.isString(): + return "JSVAL_TYPE_STRING" + if t.isEnum(): + return "JSVAL_TYPE_STRING" + if t.isCallback(): + return "JSVAL_TYPE_OBJECT" + if t.isAny(): + # The whole point is to return various stuff + return "JSVAL_TYPE_UNKNOWN" + if t.isObject(): + return "JSVAL_TYPE_OBJECT" + if t.isSpiderMonkeyInterface(): + return "JSVAL_TYPE_OBJECT" + if t.isUnion(): + u = t.unroll() + if u.hasNullableType: + # Might be null or not + return "JSVAL_TYPE_UNKNOWN" + return functools.reduce( + CGMemberJITInfo.getSingleReturnType, u.flatMemberTypes, "" + ) + if t.isDictionary(): + return "JSVAL_TYPE_OBJECT" + if t.isObservableArray(): + return "JSVAL_TYPE_OBJECT" + if not t.isPrimitive(): + raise TypeError("No idea what type " + str(t) + " is.") + tag = t.tag() + if tag == IDLType.Tags.bool: + return "JSVAL_TYPE_BOOLEAN" + if tag in [ + IDLType.Tags.int8, + IDLType.Tags.uint8, + IDLType.Tags.int16, + IDLType.Tags.uint16, + IDLType.Tags.int32, + ]: + return "JSVAL_TYPE_INT32" + if tag in [ + IDLType.Tags.int64, + IDLType.Tags.uint64, + IDLType.Tags.unrestricted_float, + IDLType.Tags.float, + IDLType.Tags.unrestricted_double, + IDLType.Tags.double, + ]: + # These all use JS_NumberValue, which can return int or double. + # But TI treats "double" as meaning "int or double", so we're + # good to return JSVAL_TYPE_DOUBLE here. + return "JSVAL_TYPE_DOUBLE" + if tag != IDLType.Tags.uint32: + raise TypeError("No idea what type " + str(t) + " is.") + # uint32 is sometimes int and sometimes double. + return "JSVAL_TYPE_DOUBLE" + + @staticmethod + def getSingleReturnType(existingType, t): + type = CGMemberJITInfo.getJSReturnTypeTag(t) + if existingType == "": + # First element of the list; just return its type + return type + + if type == existingType: + return existingType + if (type == "JSVAL_TYPE_DOUBLE" and existingType == "JSVAL_TYPE_INT32") or ( + existingType == "JSVAL_TYPE_DOUBLE" and type == "JSVAL_TYPE_INT32" + ): + # Promote INT32 to DOUBLE as needed + return "JSVAL_TYPE_DOUBLE" + # Different types + return "JSVAL_TYPE_UNKNOWN" + + @staticmethod + def getJSArgType(t): + assert not t.isUndefined() + if t.nullable(): + # Sometimes it might return null, sometimes not + return ( + "JSJitInfo::ArgType(JSJitInfo::Null | %s)" + % CGMemberJITInfo.getJSArgType(t.inner) + ) + if t.isSequence(): + return "JSJitInfo::Object" + if t.isPromise(): + return "JSJitInfo::Object" + if t.isGeckoInterface(): + return "JSJitInfo::Object" + if t.isString(): + return "JSJitInfo::String" + if t.isEnum(): + return "JSJitInfo::String" + if t.isCallback(): + return "JSJitInfo::Object" + if t.isAny(): + # The whole point is to return various stuff + return "JSJitInfo::Any" + if t.isObject(): + return "JSJitInfo::Object" + if t.isSpiderMonkeyInterface(): + return "JSJitInfo::Object" + if t.isUnion(): + u = t.unroll() + type = "JSJitInfo::Null" if u.hasNullableType else "" + return "JSJitInfo::ArgType(%s)" % functools.reduce( + CGMemberJITInfo.getSingleArgType, u.flatMemberTypes, type + ) + if t.isDictionary(): + return "JSJitInfo::Object" + if not t.isPrimitive(): + raise TypeError("No idea what type " + str(t) + " is.") + tag = t.tag() + if tag == IDLType.Tags.bool: + return "JSJitInfo::Boolean" + if tag in [ + IDLType.Tags.int8, + IDLType.Tags.uint8, + IDLType.Tags.int16, + IDLType.Tags.uint16, + IDLType.Tags.int32, + ]: + return "JSJitInfo::Integer" + if tag in [ + IDLType.Tags.int64, + IDLType.Tags.uint64, + IDLType.Tags.unrestricted_float, + IDLType.Tags.float, + IDLType.Tags.unrestricted_double, + IDLType.Tags.double, + ]: + # These all use JS_NumberValue, which can return int or double. + # But TI treats "double" as meaning "int or double", so we're + # good to return JSVAL_TYPE_DOUBLE here. + return "JSJitInfo::Double" + if tag != IDLType.Tags.uint32: + raise TypeError("No idea what type " + str(t) + " is.") + # uint32 is sometimes int and sometimes double. + return "JSJitInfo::Double" + + @staticmethod + def getSingleArgType(existingType, t): + type = CGMemberJITInfo.getJSArgType(t) + if existingType == "": + # First element of the list; just return its type + return type + + if type == existingType: + return existingType + return "%s | %s" % (existingType, type) + + +class CGStaticMethodJitinfo(CGGeneric): + """ + A class for generating the JITInfo for a promise-returning static method. + """ + + def __init__(self, method): + CGGeneric.__init__( + self, + "\n" + "static const JSJitInfo %s_methodinfo = {\n" + " { (JSJitGetterOp)%s },\n" + " { prototypes::id::_ID_Count }, { 0 }, JSJitInfo::StaticMethod,\n" + " JSJitInfo::AliasEverything, JSVAL_TYPE_OBJECT, false, false,\n" + " false, false, 0\n" + "};\n" + % ( + IDLToCIdentifier(method.identifier.name), + CppKeywords.checkMethodName(IDLToCIdentifier(method.identifier.name)), + ), + ) + + +def getEnumValueName(value): + # Some enum values can be empty strings. Others might have weird + # characters in them. Deal with the former by returning "_empty", + # deal with possible name collisions from that by throwing if the + # enum value is actually "_empty", and throw on any value + # containing non-ASCII chars for now. Replace all chars other than + # [0-9A-Za-z_] with '_'. + if re.match("[^\x20-\x7E]", value): + raise SyntaxError('Enum value "' + value + '" contains non-ASCII characters') + if re.match("^[0-9]", value): + value = "_" + value + value = re.sub(r"[^0-9A-Za-z_]", "_", value) + if re.match("^_[A-Z]|__", value): + raise SyntaxError('Enum value "' + value + '" is reserved by the C++ spec') + if value == "_empty": + raise SyntaxError('"_empty" is not an IDL enum value we support yet') + if value == "": + return "_empty" + nativeName = MakeNativeName(value) + if nativeName == "EndGuard_": + raise SyntaxError( + 'Enum value "' + value + '" cannot be used because it' + " collides with our internal EndGuard_ value. Please" + " rename our internal EndGuard_ to something else" + ) + return nativeName + + +class CGEnumToJSValue(CGAbstractMethod): + def __init__(self, enum): + enumType = enum.identifier.name + self.stringsArray = enumType + "Values::" + ENUM_ENTRY_VARIABLE_NAME + CGAbstractMethod.__init__( + self, + None, + "ToJSValue", + "bool", + [ + Argument("JSContext*", "aCx"), + Argument(enumType, "aArgument"), + Argument("JS::MutableHandle<JS::Value>", "aValue"), + ], + ) + + def definition_body(self): + return fill( + """ + MOZ_ASSERT(uint32_t(aArgument) < ArrayLength(${strings})); + JSString* resultStr = + JS_NewStringCopyN(aCx, ${strings}[uint32_t(aArgument)].value, + ${strings}[uint32_t(aArgument)].length); + if (!resultStr) { + return false; + } + aValue.setString(resultStr); + return true; + """, + strings=self.stringsArray, + ) + + +class CGEnum(CGThing): + def __init__(self, enum): + CGThing.__init__(self) + self.enum = enum + entryDecl = fill( + """ + extern const EnumEntry ${entry_array}[${entry_count}]; + + static constexpr size_t Count = ${real_entry_count}; + + // Our "${entry_array}" contains an extra entry with a null string. + static_assert(mozilla::ArrayLength(${entry_array}) - 1 == Count, + "Mismatch between enum strings and enum count"); + + static_assert(static_cast<size_t>(${name}::EndGuard_) == Count, + "Mismatch between enum value and enum count"); + + inline auto GetString(${name} stringId) { + MOZ_ASSERT(static_cast<${type}>(stringId) < Count); + const EnumEntry& entry = ${entry_array}[static_cast<${type}>(stringId)]; + return Span<const char>{entry.value, entry.length}; + } + """, + entry_array=ENUM_ENTRY_VARIABLE_NAME, + entry_count=self.nEnumStrings(), + # -1 because nEnumStrings() includes a string for EndGuard_ + real_entry_count=self.nEnumStrings() - 1, + name=self.enum.identifier.name, + type=self.underlyingType(), + ) + strings = CGNamespace( + self.stringsNamespace(), + CGGeneric( + declare=entryDecl, + define=fill( + """ + extern const EnumEntry ${name}[${count}] = { + $*{entries} + { nullptr, 0 } + }; + """, + name=ENUM_ENTRY_VARIABLE_NAME, + count=self.nEnumStrings(), + entries="".join( + '{"%s", %d},\n' % (val, len(val)) for val in self.enum.values() + ), + ), + ), + ) + toJSValue = CGEnumToJSValue(enum) + self.cgThings = CGList([strings, toJSValue], "\n") + + def stringsNamespace(self): + return self.enum.identifier.name + "Values" + + def nEnumStrings(self): + return len(self.enum.values()) + 1 + + def underlyingType(self): + count = self.nEnumStrings() + if count <= 256: + return "uint8_t" + if count <= 65536: + return "uint16_t" + raise ValueError( + "Enum " + self.enum.identifier.name + " has more than 65536 values" + ) + + def declare(self): + decl = fill( + """ + enum class ${name} : ${ty} { + $*{enums} + EndGuard_ + }; + """, + name=self.enum.identifier.name, + ty=self.underlyingType(), + enums=",\n".join(map(getEnumValueName, self.enum.values())) + ",\n", + ) + + return decl + "\n" + self.cgThings.declare() + + def define(self): + return self.cgThings.define() + + def deps(self): + return self.enum.getDeps() + + +def getUnionAccessorSignatureType(type, descriptorProvider): + """ + Returns the types that are used in the getter and setter signatures for + union types + """ + # Flat member types have already unwrapped nullables. + assert not type.nullable() + + # Promise types can never appear in unions, because Promise is not + # distinguishable from anything. + assert not type.isPromise() + + if type.isSequence() or type.isRecord(): + if type.isSequence(): + wrapperType = "Sequence" + else: + wrapperType = "Record" + # We don't use the returned template here, so it's OK to just pass no + # sourceDescription. + elementInfo = getJSToNativeConversionInfo( + type.inner, descriptorProvider, isMember=wrapperType + ) + if wrapperType == "Sequence": + innerType = elementInfo.declType + else: + innerType = [recordKeyDeclType(type), elementInfo.declType] + + return CGTemplatedType(wrapperType, innerType, isConst=True, isReference=True) + + # Nested unions are unwrapped automatically into our flatMemberTypes. + assert not type.isUnion() + + if type.isGeckoInterface(): + descriptor = descriptorProvider.getDescriptor( + type.unroll().inner.identifier.name + ) + typeName = CGGeneric(descriptor.nativeType) + if not type.unroll().inner.isExternal(): + typeName = CGWrapper(typeName, post="&") + elif descriptor.interface.identifier.name == "WindowProxy": + typeName = CGGeneric("WindowProxyHolder const&") + else: + # Allow null pointers for old-binding classes. + typeName = CGWrapper(typeName, post="*") + return typeName + + if type.isSpiderMonkeyInterface(): + typeName = CGGeneric(type.name) + return CGWrapper(typeName, post=" const &") + + if type.isJSString(): + raise TypeError("JSString not supported in unions") + + if type.isDOMString() or type.isUSVString(): + return CGGeneric("const nsAString&") + + if type.isUTF8String(): + return CGGeneric("const nsACString&") + + if type.isByteString(): + return CGGeneric("const nsCString&") + + if type.isEnum(): + return CGGeneric(type.inner.identifier.name) + + if type.isCallback(): + return CGGeneric("%s&" % type.unroll().callback.identifier.name) + + if type.isAny(): + return CGGeneric("JS::Value") + + if type.isObject(): + return CGGeneric("JSObject*") + + if type.isDictionary(): + return CGGeneric("const %s&" % type.inner.identifier.name) + + if not type.isPrimitive(): + raise TypeError("Need native type for argument type '%s'" % str(type)) + + return CGGeneric(builtinNames[type.tag()]) + + +def getUnionTypeTemplateVars(unionType, type, descriptorProvider, isMember=False): + assert not type.isUndefined() + assert not isMember or isMember in ("Union", "OwningUnion") + + ownsMembers = isMember == "OwningUnion" + name = getUnionMemberName(type) + holderName = "m" + name + "Holder" + + # By the time tryNextCode is invoked, we're guaranteed the union has been + # constructed as some type, since we've been trying to convert into the + # corresponding member. + tryNextCode = fill( + """ + Destroy${name}(); + tryNext = true; + return true; + """, + name=name, + ) + + sourceDescription = "%s branch of %s" % (type.prettyName(), unionType.prettyName()) + + conversionInfo = getJSToNativeConversionInfo( + type, + descriptorProvider, + failureCode=tryNextCode, + isDefinitelyObject=not type.isDictionary(), + isMember=isMember, + sourceDescription=sourceDescription, + ) + + ctorNeedsCx = conversionInfo.declArgs == "cx" + ctorArgs = "cx" if ctorNeedsCx else "" + + structType = conversionInfo.declType.define() + externalType = getUnionAccessorSignatureType(type, descriptorProvider).define() + + if type.isObject(): + if ownsMembers: + setValue = "mValue.mObject.SetValue(obj);" + else: + setValue = "mValue.mObject.SetValue(cx, obj);" + + body = fill( + """ + MOZ_ASSERT(mType == eUninitialized); + ${setValue} + mType = eObject; + """, + setValue=setValue, + ) + + # It's a bit sketchy to do the security check after setting the value, + # but it keeps the code cleaner and lets us avoid rooting |obj| over the + # call to CallerSubsumes(). + body = body + fill( + """ + if (passedToJSImpl && !CallerSubsumes(obj)) { + cx.ThrowErrorMessage<MSG_PERMISSION_DENIED_TO_PASS_ARG>("${sourceDescription}"); + return false; + } + return true; + """, + sourceDescription=sourceDescription, + ) + + setters = [ + ClassMethod( + "SetToObject", + "bool", + [ + Argument("BindingCallContext&", "cx"), + Argument("JSObject*", "obj"), + Argument("bool", "passedToJSImpl", default="false"), + ], + inline=True, + bodyInHeader=True, + body=body, + ) + ] + elif type.isDictionary() and not type.inner.needsConversionFromJS: + # In this case we are never initialized from JS to start with + setters = None + else: + # Important: we need to not have our declName involve + # maybe-GCing operations. + jsConversion = fill( + conversionInfo.template, + val="value", + maybeMutableVal="value", + declName="memberSlot", + holderName=(holderName if ownsMembers else "%s.ref()" % holderName), + passedToJSImpl="passedToJSImpl", + ) + + jsConversion = fill( + """ + tryNext = false; + { // scope for memberSlot + ${structType}& memberSlot = RawSetAs${name}(${ctorArgs}); + $*{jsConversion} + } + return true; + """, + structType=structType, + name=name, + ctorArgs=ctorArgs, + jsConversion=jsConversion, + ) + + needCallContext = idlTypeNeedsCallContext(type) + if needCallContext: + cxType = "BindingCallContext&" + else: + cxType = "JSContext*" + setters = [ + ClassMethod( + "TrySetTo" + name, + "bool", + [ + Argument(cxType, "cx"), + Argument("JS::Handle<JS::Value>", "value"), + Argument("bool&", "tryNext"), + Argument("bool", "passedToJSImpl", default="false"), + ], + visibility="private", + body=jsConversion, + ) + ] + if needCallContext: + # Add a method for non-binding uses of unions to allow them to set + # things in the union without providing a call context (though if + # they want good error reporting they'll provide one anyway). + shimBody = fill( + """ + BindingCallContext cx(cx_, nullptr); + return TrySetTo${name}(cx, value, tryNext, passedToJSImpl); + """, + name=name, + ) + setters.append( + ClassMethod( + "TrySetTo" + name, + "bool", + [ + Argument("JSContext*", "cx_"), + Argument("JS::Handle<JS::Value>", "value"), + Argument("bool&", "tryNext"), + Argument("bool", "passedToJSImpl", default="false"), + ], + visibility="private", + body=shimBody, + ) + ) + + return { + "name": name, + "structType": structType, + "externalType": externalType, + "setters": setters, + "ctorArgs": ctorArgs, + "ctorArgList": [Argument("JSContext*", "cx")] if ctorNeedsCx else [], + } + + +def getUnionConversionTemplate(type): + assert type.isUnion() + assert not type.nullable() + + memberTypes = type.flatMemberTypes + prettyNames = [] + + interfaceMemberTypes = [t for t in memberTypes if t.isNonCallbackInterface()] + if len(interfaceMemberTypes) > 0: + interfaceObject = [] + for memberType in interfaceMemberTypes: + name = getUnionMemberName(memberType) + interfaceObject.append( + CGGeneric( + "(failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext" + % name + ) + ) + prettyNames.append(memberType.prettyName()) + interfaceObject = CGWrapper( + CGList(interfaceObject, " ||\n"), + pre="done = ", + post=";\n", + reindent=True, + ) + else: + interfaceObject = None + + sequenceObjectMemberTypes = [t for t in memberTypes if t.isSequence()] + if len(sequenceObjectMemberTypes) > 0: + assert len(sequenceObjectMemberTypes) == 1 + memberType = sequenceObjectMemberTypes[0] + name = getUnionMemberName(memberType) + sequenceObject = CGGeneric( + "done = (failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" + % name + ) + prettyNames.append(memberType.prettyName()) + else: + sequenceObject = None + + callbackMemberTypes = [ + t for t in memberTypes if t.isCallback() or t.isCallbackInterface() + ] + if len(callbackMemberTypes) > 0: + assert len(callbackMemberTypes) == 1 + memberType = callbackMemberTypes[0] + name = getUnionMemberName(memberType) + callbackObject = CGGeneric( + "done = (failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" + % name + ) + prettyNames.append(memberType.prettyName()) + else: + callbackObject = None + + dictionaryMemberTypes = [t for t in memberTypes if t.isDictionary()] + if len(dictionaryMemberTypes) > 0: + assert len(dictionaryMemberTypes) == 1 + memberType = dictionaryMemberTypes[0] + name = getUnionMemberName(memberType) + setDictionary = CGGeneric( + "done = (failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" + % name + ) + prettyNames.append(memberType.prettyName()) + else: + setDictionary = None + + recordMemberTypes = [t for t in memberTypes if t.isRecord()] + if len(recordMemberTypes) > 0: + assert len(recordMemberTypes) == 1 + memberType = recordMemberTypes[0] + name = getUnionMemberName(memberType) + recordObject = CGGeneric( + "done = (failed = !TrySetTo%s(cx, ${val}, tryNext, ${passedToJSImpl})) || !tryNext;\n" + % name + ) + prettyNames.append(memberType.prettyName()) + else: + recordObject = None + + objectMemberTypes = [t for t in memberTypes if t.isObject()] + if len(objectMemberTypes) > 0: + assert len(objectMemberTypes) == 1 + # Very important to NOT construct a temporary Rooted here, since the + # SetToObject call can call a Rooted constructor and we need to keep + # stack discipline for Rooted. + object = CGGeneric( + "if (!SetToObject(cx, &${val}.toObject(), ${passedToJSImpl})) {\n" + " return false;\n" + "}\n" + "done = true;\n" + ) + prettyNames.append(objectMemberTypes[0].prettyName()) + else: + object = None + + hasObjectTypes = ( + interfaceObject or sequenceObject or callbackObject or object or recordObject + ) + if hasObjectTypes: + # "object" is not distinguishable from other types + assert not object or not ( + interfaceObject or sequenceObject or callbackObject or recordObject + ) + if sequenceObject or callbackObject: + # An object can be both an sequence object and a callback or + # dictionary, but we shouldn't have both in the union's members + # because they are not distinguishable. + assert not (sequenceObject and callbackObject) + templateBody = CGElseChain([sequenceObject, callbackObject]) + else: + templateBody = None + if interfaceObject: + assert not object + if templateBody: + templateBody = CGIfWrapper(templateBody, "!done") + templateBody = CGList([interfaceObject, templateBody]) + else: + templateBody = CGList([templateBody, object]) + + if recordObject: + templateBody = CGList([templateBody, CGIfWrapper(recordObject, "!done")]) + + templateBody = CGIfWrapper(templateBody, "${val}.isObject()") + else: + templateBody = CGGeneric() + + if setDictionary: + assert not object + templateBody = CGList([templateBody, CGIfWrapper(setDictionary, "!done")]) + + stringTypes = [t for t in memberTypes if t.isString() or t.isEnum()] + numericTypes = [t for t in memberTypes if t.isNumeric()] + booleanTypes = [t for t in memberTypes if t.isBoolean()] + if stringTypes or numericTypes or booleanTypes: + assert len(stringTypes) <= 1 + assert len(numericTypes) <= 1 + assert len(booleanTypes) <= 1 + + # We will wrap all this stuff in a do { } while (0); so we + # can use "break" for flow control. + def getStringOrPrimitiveConversion(memberType): + name = getUnionMemberName(memberType) + return CGGeneric( + "done = (failed = !TrySetTo%s(cx, ${val}, tryNext)) || !tryNext;\n" + "break;\n" % name + ) + + other = CGList([]) + stringConversion = [getStringOrPrimitiveConversion(t) for t in stringTypes] + numericConversion = [getStringOrPrimitiveConversion(t) for t in numericTypes] + booleanConversion = [getStringOrPrimitiveConversion(t) for t in booleanTypes] + if stringConversion: + if booleanConversion: + other.append(CGIfWrapper(booleanConversion[0], "${val}.isBoolean()")) + if numericConversion: + other.append(CGIfWrapper(numericConversion[0], "${val}.isNumber()")) + other.append(stringConversion[0]) + elif numericConversion: + if booleanConversion: + other.append(CGIfWrapper(booleanConversion[0], "${val}.isBoolean()")) + other.append(numericConversion[0]) + else: + assert booleanConversion + other.append(booleanConversion[0]) + + other = CGWrapper(CGIndenter(other), pre="do {\n", post="} while (false);\n") + if hasObjectTypes or setDictionary: + other = CGWrapper(CGIndenter(other), "{\n", post="}\n") + if object: + templateBody = CGElseChain([templateBody, other]) + else: + other = CGWrapper(other, pre="if (!done) ") + templateBody = CGList([templateBody, other]) + else: + assert templateBody.define() == "" + templateBody = other + else: + other = None + + templateBody = CGWrapper( + templateBody, pre="bool done = false, failed = false, tryNext;\n" + ) + throw = CGGeneric( + fill( + """ + if (failed) { + return false; + } + if (!done) { + cx.ThrowErrorMessage<MSG_NOT_IN_UNION>(sourceDescription, "${names}"); + return false; + } + """, + names=", ".join(prettyNames), + ) + ) + + templateBody = CGList([templateBody, throw]) + + hasUndefinedType = any(t.isUndefined() for t in memberTypes) + elseChain = [] + + # The spec does this before anything else, but we do it after checking + # for null in the case of a nullable union. In practice this shouldn't + # make a difference, but it makes things easier because we first need to + # call Construct on our Maybe<...>, before we can set the union type to + # undefined, and we do that below after checking for null (see the + # 'if nullable:' block below). + if hasUndefinedType: + elseChain.append( + CGIfWrapper( + CGGeneric("SetUndefined();\n"), + "${val}.isUndefined()", + ) + ) + + if type.hasNullableType: + nullTest = ( + "${val}.isNull()" if hasUndefinedType else "${val}.isNullOrUndefined()" + ) + elseChain.append( + CGIfWrapper( + CGGeneric("SetNull();\n"), + nullTest, + ) + ) + + if len(elseChain) > 0: + elseChain.append(CGWrapper(CGIndenter(templateBody), pre="{\n", post="}\n")) + templateBody = CGElseChain(elseChain) + + return templateBody + + +def getUnionInitMethods(type, isOwningUnion=False): + assert type.isUnion() + + template = getUnionConversionTemplate(type).define() + + replacements = { + "val": "value", + "passedToJSImpl": "passedToJSImpl", + } + + initBody = fill( + """ + MOZ_ASSERT(mType == eUninitialized); + + $*{conversion} + return true; + """, + conversion=string.Template(template).substitute(replacements), + ) + + return [ + ClassMethod( + "Init", + "bool", + [ + Argument("BindingCallContext&", "cx"), + Argument("JS::Handle<JS::Value>", "value"), + Argument("const char*", "sourceDescription", default='"Value"'), + Argument("bool", "passedToJSImpl", default="false"), + ], + visibility="public", + body=initBody, + ), + ClassMethod( + "Init", + "bool", + [ + Argument("JSContext*", "cx_"), + Argument("JS::Handle<JS::Value>", "value"), + Argument("const char*", "sourceDescription", default='"Value"'), + Argument("bool", "passedToJSImpl", default="false"), + ], + visibility="public", + body=dedent( + """ + BindingCallContext cx(cx_, nullptr); + return Init(cx, value, sourceDescription, passedToJSImpl); + """ + ), + ), + ] + + +class CGUnionStruct(CGThing): + def __init__(self, type, descriptorProvider, ownsMembers=False): + CGThing.__init__(self) + self.type = type.unroll() + self.descriptorProvider = descriptorProvider + self.ownsMembers = ownsMembers + self.struct = self.getStruct() + + def declare(self): + return self.struct.declare() + + def define(self): + return self.struct.define() + + def deps(self): + return self.type.getDeps() + + def getStruct(self): + + members = [ + ClassMember("mType", "TypeOrUninit", body="eUninitialized"), + ClassMember("mValue", "Value"), + ] + ctor = ClassConstructor( + [], bodyInHeader=True, visibility="public", explicit=True + ) + + methods = [] + enumValues = ["eUninitialized"] + toJSValCases = [ + CGCase( + "eUninitialized", CGGeneric("return false;\n"), CGCase.DONT_ADD_BREAK + ) + ] + destructorCases = [CGCase("eUninitialized", None)] + assignmentCase = CGCase( + "eUninitialized", + CGGeneric( + "MOZ_ASSERT(mType == eUninitialized,\n" + ' "We need to destroy ourselves?");\n' + ), + ) + assignmentCases = [assignmentCase] + moveCases = [assignmentCase] + traceCases = [] + unionValues = [] + + def addSpecialType(typename): + enumValue = "e" + typename + enumValues.append(enumValue) + methods.append( + ClassMethod( + "Is" + typename, + "bool", + [], + const=True, + inline=True, + body="return mType == %s;\n" % enumValue, + bodyInHeader=True, + ) + ) + methods.append( + ClassMethod( + "Set" + typename, + "void", + [], + inline=True, + body=fill( + """ + Uninit(); + mType = ${enumValue}; + """, + enumValue=enumValue, + ), + bodyInHeader=True, + ) + ) + destructorCases.append(CGCase(enumValue, None)) + assignmentCase = CGCase( + enumValue, + CGGeneric( + fill( + """ + MOZ_ASSERT(mType == eUninitialized); + mType = ${enumValue}; + """, + enumValue=enumValue, + ) + ), + ) + assignmentCases.append(assignmentCase) + moveCases.append(assignmentCase) + toJSValCases.append( + CGCase( + enumValue, + CGGeneric( + fill( + """ + rval.set${typename}(); + return true; + """, + typename=typename, + ) + ), + CGCase.DONT_ADD_BREAK, + ) + ) + + if self.type.hasNullableType: + addSpecialType("Null") + + hasObjectType = any(t.isObject() for t in self.type.flatMemberTypes) + skipToJSVal = False + for t in self.type.flatMemberTypes: + if t.isUndefined(): + addSpecialType("Undefined") + continue + + vars = getUnionTypeTemplateVars( + self.type, + t, + self.descriptorProvider, + isMember="OwningUnion" if self.ownsMembers else "Union", + ) + if vars["setters"]: + methods.extend(vars["setters"]) + uninit = "Uninit();" + if hasObjectType and not self.ownsMembers: + uninit = ( + 'MOZ_ASSERT(mType != eObject, "This will not play well with Rooted");\n' + + uninit + ) + if not t.isObject() or self.ownsMembers: + body = fill( + """ + if (mType == e${name}) { + return mValue.m${name}.Value(); + } + %s + mType = e${name}; + return mValue.m${name}.SetValue(${ctorArgs}); + """, + **vars, + ) + + # bodyInHeader must be false for return values because they own + # their union members and we don't want include headers in + # UnionTypes.h just to call Addref/Release + methods.append( + ClassMethod( + "RawSetAs" + vars["name"], + vars["structType"] + "&", + vars["ctorArgList"], + bodyInHeader=not self.ownsMembers, + body=body % "MOZ_ASSERT(mType == eUninitialized);", + ) + ) + methods.append( + ClassMethod( + "SetAs" + vars["name"], + vars["structType"] + "&", + vars["ctorArgList"], + bodyInHeader=not self.ownsMembers, + body=body % uninit, + ) + ) + + # Provide a SetStringLiteral() method to support string defaults. + if t.isByteString() or t.isUTF8String(): + charType = "const nsCString::char_type" + elif t.isString(): + charType = "const nsString::char_type" + else: + charType = None + + if charType: + methods.append( + ClassMethod( + "SetStringLiteral", + "void", + # Hack, but it works... + [Argument(charType, "(&aData)[N]")], + inline=True, + bodyInHeader=True, + templateArgs=["int N"], + body="RawSetAs%s().AssignLiteral(aData);\n" % t.name, + ) + ) + + body = fill("return mType == e${name};\n", **vars) + methods.append( + ClassMethod( + "Is" + vars["name"], + "bool", + [], + const=True, + bodyInHeader=True, + body=body, + ) + ) + + body = fill( + """ + MOZ_RELEASE_ASSERT(Is${name}(), "Wrong type!"); + mValue.m${name}.Destroy(); + mType = eUninitialized; + """, + **vars, + ) + methods.append( + ClassMethod( + "Destroy" + vars["name"], + "void", + [], + visibility="private", + bodyInHeader=not self.ownsMembers, + body=body, + ) + ) + + body = fill( + """ + MOZ_RELEASE_ASSERT(Is${name}(), "Wrong type!"); + return mValue.m${name}.Value(); + """, + **vars, + ) + # The non-const version of GetAs* returns our internal type + getterReturnType = "%s&" % vars["structType"] + methods.append( + ClassMethod( + "GetAs" + vars["name"], + getterReturnType, + [], + bodyInHeader=True, + body=body, + ) + ) + # The const version of GetAs* returns our internal type + # for owning unions, but our external type for non-owning + # ones. + if self.ownsMembers: + getterReturnType = "%s const &" % vars["structType"] + else: + getterReturnType = vars["externalType"] + methods.append( + ClassMethod( + "GetAs" + vars["name"], + getterReturnType, + [], + const=True, + bodyInHeader=True, + body=body, + ) + ) + + unionValues.append(fill("UnionMember<${structType} > m${name}", **vars)) + destructorCases.append( + CGCase("e" + vars["name"], CGGeneric("Destroy%s();\n" % vars["name"])) + ) + + enumValues.append("e" + vars["name"]) + + conversionToJS = self.getConversionToJS(vars, t) + if conversionToJS: + toJSValCases.append( + CGCase("e" + vars["name"], conversionToJS, CGCase.DONT_ADD_BREAK) + ) + else: + skipToJSVal = True + + assignmentCases.append( + CGCase( + "e" + vars["name"], + CGGeneric( + "SetAs%s() = aOther.GetAs%s();\n" % (vars["name"], vars["name"]) + ), + ) + ) + moveCases.append( + CGCase( + "e" + vars["name"], + CGGeneric( + "mType = e%s;\n" % vars["name"] + + "mValue.m%s.SetValue(std::move(aOther.mValue.m%s.Value()));\n" + % (vars["name"], vars["name"]) + ), + ) + ) + if self.ownsMembers and typeNeedsRooting(t): + if t.isObject(): + traceCases.append( + CGCase( + "e" + vars["name"], + CGGeneric( + 'JS::TraceRoot(trc, %s, "%s");\n' + % ( + "&mValue.m" + vars["name"] + ".Value()", + "mValue.m" + vars["name"], + ) + ), + ) + ) + elif t.isDictionary(): + traceCases.append( + CGCase( + "e" + vars["name"], + CGGeneric( + "mValue.m%s.Value().TraceDictionary(trc);\n" + % vars["name"] + ), + ) + ) + elif t.isSequence(): + traceCases.append( + CGCase( + "e" + vars["name"], + CGGeneric( + "DoTraceSequence(trc, mValue.m%s.Value());\n" + % vars["name"] + ), + ) + ) + elif t.isRecord(): + traceCases.append( + CGCase( + "e" + vars["name"], + CGGeneric( + "TraceRecord(trc, mValue.m%s.Value());\n" % vars["name"] + ), + ) + ) + else: + assert t.isSpiderMonkeyInterface() + traceCases.append( + CGCase( + "e" + vars["name"], + CGGeneric( + "mValue.m%s.Value().TraceSelf(trc);\n" % vars["name"] + ), + ) + ) + + dtor = CGSwitch("mType", destructorCases).define() + + methods.extend(getUnionInitMethods(self.type, isOwningUnion=self.ownsMembers)) + methods.append( + ClassMethod( + "Uninit", + "void", + [], + visibility="public", + body=dtor, + bodyInHeader=not self.ownsMembers, + inline=not self.ownsMembers, + ) + ) + + if not skipToJSVal: + methods.append( + ClassMethod( + "ToJSVal", + "bool", + [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "scopeObj"), + Argument("JS::MutableHandle<JS::Value>", "rval"), + ], + body=CGSwitch( + "mType", toJSValCases, default=CGGeneric("return false;\n") + ).define(), + const=True, + ) + ) + + constructors = [ctor] + selfName = CGUnionStruct.unionTypeName(self.type, self.ownsMembers) + if self.ownsMembers: + if traceCases: + traceBody = CGSwitch( + "mType", traceCases, default=CGGeneric("") + ).define() + methods.append( + ClassMethod( + "TraceUnion", + "void", + [Argument("JSTracer*", "trc")], + body=traceBody, + ) + ) + + op_body = CGList([]) + op_body.append(CGSwitch("aOther.mType", moveCases)) + constructors.append( + ClassConstructor( + [Argument("%s&&" % selfName, "aOther")], + visibility="public", + body=op_body.define(), + ) + ) + + methods.append( + ClassMethod( + "operator=", + "%s&" % selfName, + [Argument("%s&&" % selfName, "aOther")], + body="this->~%s();\nnew (this) %s (std::move(aOther));\nreturn *this;\n" + % (selfName, selfName), + ) + ) + + body = dedent( + """ + MOZ_RELEASE_ASSERT(mType != eUninitialized); + return static_cast<Type>(mType); + """ + ) + methods.append( + ClassMethod( + "GetType", + "Type", + [], + bodyInHeader=True, + body=body, + const=True, + ) + ) + + if CGUnionStruct.isUnionCopyConstructible(self.type): + constructors.append( + ClassConstructor( + [Argument("const %s&" % selfName, "aOther")], + bodyInHeader=True, + visibility="public", + explicit=True, + body="*this = aOther;\n", + ) + ) + op_body = CGList([]) + op_body.append(CGSwitch("aOther.mType", assignmentCases)) + op_body.append(CGGeneric("return *this;\n")) + methods.append( + ClassMethod( + "operator=", + "%s&" % selfName, + [Argument("const %s&" % selfName, "aOther")], + body=op_body.define(), + ) + ) + disallowCopyConstruction = False + else: + disallowCopyConstruction = True + else: + disallowCopyConstruction = True + + if self.ownsMembers and idlTypeNeedsCycleCollection(self.type): + friend = ( + " friend void ImplCycleCollectionUnlink(%s& aUnion);\n" + % CGUnionStruct.unionTypeName(self.type, True) + ) + else: + friend = "" + + enumValuesNoUninit = [x for x in enumValues if x != "eUninitialized"] + + bases = [ClassBase("AllOwningUnionBase")] if self.ownsMembers else [] + enums = [ + ClassEnum("TypeOrUninit", enumValues, visibility="private"), + ClassEnum( + "Type", + enumValuesNoUninit, + visibility="public", + enumClass=True, + values=["TypeOrUninit::" + x for x in enumValuesNoUninit], + ), + ] + return CGClass( + selfName, + bases=bases, + members=members, + constructors=constructors, + methods=methods, + disallowCopyConstruction=disallowCopyConstruction, + extradeclarations=friend, + destructor=ClassDestructor( + visibility="public", body="Uninit();\n", bodyInHeader=True + ), + enums=enums, + unions=[ClassUnion("Value", unionValues, visibility="private")], + ) + + def getConversionToJS(self, templateVars, type): + if type.isDictionary() and not type.inner.needsConversionToJS: + # We won't be able to convert this dictionary to a JS value, nor + # will we need to, since we don't need a ToJSVal method at all. + return None + + assert not type.nullable() # flatMemberTypes never has nullable types + val = "mValue.m%(name)s.Value()" % templateVars + wrapCode = wrapForType( + type, + self.descriptorProvider, + { + "jsvalRef": "rval", + "jsvalHandle": "rval", + "obj": "scopeObj", + "result": val, + "spiderMonkeyInterfacesAreStructs": True, + }, + ) + return CGGeneric(wrapCode) + + @staticmethod + def isUnionCopyConstructible(type): + return all(isTypeCopyConstructible(t) for t in type.flatMemberTypes) + + @staticmethod + def unionTypeName(type, ownsMembers): + """ + Returns a string name for this known union type. + """ + assert type.isUnion() and not type.nullable() + return ("Owning" if ownsMembers else "") + type.name + + @staticmethod + def unionTypeDecl(type, ownsMembers): + """ + Returns a string for declaring this possibly-nullable union type. + """ + assert type.isUnion() + nullable = type.nullable() + if nullable: + type = type.inner + decl = CGGeneric(CGUnionStruct.unionTypeName(type, ownsMembers)) + if nullable: + decl = CGTemplatedType("Nullable", decl) + return decl.define() + + +class ClassItem: + """Use with CGClass""" + + def __init__(self, name, visibility): + self.name = name + self.visibility = visibility + + def declare(self, cgClass): + assert False + + def define(self, cgClass): + assert False + + +class ClassBase(ClassItem): + def __init__(self, name, visibility="public"): + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return "%s %s" % (self.visibility, self.name) + + def define(self, cgClass): + # Only in the header + return "" + + +class ClassMethod(ClassItem): + def __init__( + self, + name, + returnType, + args, + inline=False, + static=False, + virtual=False, + const=False, + bodyInHeader=False, + templateArgs=None, + visibility="public", + body=None, + breakAfterReturnDecl="\n", + breakAfterSelf="\n", + override=False, + canRunScript=False, + ): + """ + override indicates whether to flag the method as override + """ + assert not override or virtual + assert not (override and static) + self.returnType = returnType + self.args = args + self.inline = inline or bodyInHeader + self.static = static + self.virtual = virtual + self.const = const + self.bodyInHeader = bodyInHeader + self.templateArgs = templateArgs + self.body = body + self.breakAfterReturnDecl = breakAfterReturnDecl + self.breakAfterSelf = breakAfterSelf + self.override = override + self.canRunScript = canRunScript + ClassItem.__init__(self, name, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.canRunScript: + decorators.append("MOZ_CAN_RUN_SCRIPT") + if self.inline: + decorators.append("inline") + if declaring: + if self.static: + decorators.append("static") + if self.virtual and not self.override: + decorators.append("virtual") + if decorators: + return " ".join(decorators) + " " + return "" + + def getBody(self): + # Override me or pass a string to constructor + assert self.body is not None + return self.body + + def declare(self, cgClass): + templateClause = ( + "template <%s>\n" % ", ".join(self.templateArgs) + if self.bodyInHeader and self.templateArgs + else "" + ) + args = ", ".join([a.declare() for a in self.args]) + if self.bodyInHeader: + body = indent(self.getBody()) + body = "\n{\n" + body + "}\n" + else: + body = ";\n" + + return fill( + "${templateClause}${decorators}${returnType}${breakAfterReturnDecl}" + "${name}(${args})${const}${override}${body}" + "${breakAfterSelf}", + templateClause=templateClause, + decorators=self.getDecorators(True), + returnType=self.returnType, + breakAfterReturnDecl=self.breakAfterReturnDecl, + name=self.name, + args=args, + const=" const" if self.const else "", + override=" override" if self.override else "", + body=body, + breakAfterSelf=self.breakAfterSelf, + ) + + def define(self, cgClass): + if self.bodyInHeader: + return "" + + templateArgs = cgClass.templateArgs + if templateArgs: + if cgClass.templateSpecialization: + templateArgs = templateArgs[len(cgClass.templateSpecialization) :] + + if templateArgs: + templateClause = "template <%s>\n" % ", ".join( + [str(a) for a in templateArgs] + ) + else: + templateClause = "" + + return fill( + """ + ${templateClause}${decorators}${returnType} + ${className}::${name}(${args})${const} + { + $*{body} + } + """, + templateClause=templateClause, + decorators=self.getDecorators(False), + returnType=self.returnType, + className=cgClass.getNameString(), + name=self.name, + args=", ".join([a.define() for a in self.args]), + const=" const" if self.const else "", + body=self.getBody(), + ) + + +class ClassUsingDeclaration(ClassItem): + """ + Used for importing a name from a base class into a CGClass + + baseClass is the name of the base class to import the name from + + name is the name to import + + visibility determines the visibility of the name (public, + protected, private), defaults to public. + """ + + def __init__(self, baseClass, name, visibility="public"): + self.baseClass = baseClass + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return "using %s::%s;\n\n" % (self.baseClass, self.name) + + def define(self, cgClass): + return "" + + +class ClassConstructor(ClassItem): + """ + Used for adding a constructor to a CGClass. + + args is a list of Argument objects that are the arguments taken by the + constructor. + + inline should be True if the constructor should be marked inline. + + bodyInHeader should be True if the body should be placed in the class + declaration in the header. + + default should be True if the definition of the constructor should be + `= default;`. + + visibility determines the visibility of the constructor (public, + protected, private), defaults to private. + + explicit should be True if the constructor should be marked explicit. + + baseConstructors is a list of strings containing calls to base constructors, + defaults to None. + + body contains a string with the code for the constructor, defaults to empty. + """ + + def __init__( + self, + args, + inline=False, + bodyInHeader=False, + default=False, + visibility="private", + explicit=False, + constexpr=False, + baseConstructors=None, + body="", + ): + assert not (inline and constexpr) + assert not (bodyInHeader and constexpr) + assert not (default and body) + self.args = args + self.inline = inline or bodyInHeader + self.bodyInHeader = bodyInHeader or constexpr or default + self.default = default + self.explicit = explicit + self.constexpr = constexpr + self.baseConstructors = baseConstructors or [] + self.body = body + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if declaring: + if self.explicit: + decorators.append("explicit") + if self.inline: + decorators.append("inline") + if self.constexpr: + decorators.append("constexpr") + if decorators: + return " ".join(decorators) + " " + return "" + + def getInitializationList(self, cgClass): + items = [str(c) for c in self.baseConstructors] + for m in cgClass.members: + if not m.static: + initialize = m.body + if initialize: + items.append(m.name + "(" + initialize + ")") + + if len(items) > 0: + return "\n : " + ",\n ".join(items) + return "" + + def getBody(self): + return self.body + + def declare(self, cgClass): + args = ", ".join([a.declare() for a in self.args]) + if self.bodyInHeader: + if self.default: + body = " = default;\n" + else: + body = ( + self.getInitializationList(cgClass) + + "\n{\n" + + indent(self.getBody()) + + "}\n" + ) + else: + body = ";\n" + + return fill( + "${decorators}${className}(${args})${body}\n", + decorators=self.getDecorators(True), + className=cgClass.getNameString(), + args=args, + body=body, + ) + + def define(self, cgClass): + if self.bodyInHeader: + return "" + + return fill( + """ + ${decorators} + ${className}::${className}(${args})${initializationList} + { + $*{body} + } + """, + decorators=self.getDecorators(False), + className=cgClass.getNameString(), + args=", ".join([a.define() for a in self.args]), + initializationList=self.getInitializationList(cgClass), + body=self.getBody(), + ) + + +class ClassDestructor(ClassItem): + """ + Used for adding a destructor to a CGClass. + + inline should be True if the destructor should be marked inline. + + bodyInHeader should be True if the body should be placed in the class + declaration in the header. + + visibility determines the visibility of the destructor (public, + protected, private), defaults to private. + + body contains a string with the code for the destructor, defaults to empty. + + virtual determines whether the destructor is virtual, defaults to False. + """ + + def __init__( + self, + inline=False, + bodyInHeader=False, + visibility="private", + body="", + virtual=False, + ): + self.inline = inline or bodyInHeader + self.bodyInHeader = bodyInHeader + self.body = body + self.virtual = virtual + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.virtual and declaring: + decorators.append("virtual") + if self.inline and declaring: + decorators.append("inline") + if decorators: + return " ".join(decorators) + " " + return "" + + def getBody(self): + return self.body + + def declare(self, cgClass): + if self.bodyInHeader: + body = "\n{\n" + indent(self.getBody()) + "}\n" + else: + body = ";\n" + + return fill( + "${decorators}~${className}()${body}\n", + decorators=self.getDecorators(True), + className=cgClass.getNameString(), + body=body, + ) + + def define(self, cgClass): + if self.bodyInHeader: + return "" + return fill( + """ + ${decorators} + ${className}::~${className}() + { + $*{body} + } + """, + decorators=self.getDecorators(False), + className=cgClass.getNameString(), + body=self.getBody(), + ) + + +class ClassMember(ClassItem): + def __init__( + self, + name, + type, + visibility="private", + static=False, + body=None, + hasIgnoreInitCheckFlag=False, + ): + self.type = type + self.static = static + self.body = body + self.hasIgnoreInitCheckFlag = hasIgnoreInitCheckFlag + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return "%s%s%s %s;\n" % ( + "static " if self.static else "", + "MOZ_INIT_OUTSIDE_CTOR " if self.hasIgnoreInitCheckFlag else "", + self.type, + self.name, + ) + + def define(self, cgClass): + if not self.static: + return "" + if self.body: + body = " = " + self.body + else: + body = "" + return "%s %s::%s%s;\n" % (self.type, cgClass.getNameString(), self.name, body) + + +class ClassEnum(ClassItem): + def __init__( + self, name, entries, values=None, visibility="public", enumClass=False + ): + self.entries = entries + self.values = values + self.enumClass = enumClass + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + entries = [] + for i in range(0, len(self.entries)): + if not self.values or i >= len(self.values): + entry = "%s" % self.entries[i] + else: + entry = "%s = %s" % (self.entries[i], self.values[i]) + entries.append(entry) + + decl = ["enum"] + self.enumClass and decl.append("class") + self.name and decl.append(self.name) + decl = " ".join(decl) + + return "%s\n{\n%s\n};\n" % (decl, indent(",\n".join(entries))) + + def define(self, cgClass): + # Only goes in the header + return "" + + +class ClassUnion(ClassItem): + def __init__(self, name, entries, visibility="public"): + self.entries = [entry + ";\n" for entry in entries] + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return "union %s\n{\n%s\n};\n" % (self.name, indent("".join(self.entries))) + + def define(self, cgClass): + # Only goes in the header + return "" + + +class CGClass(CGThing): + def __init__( + self, + name, + bases=[], + members=[], + constructors=[], + destructor=None, + methods=[], + enums=[], + unions=[], + templateArgs=[], + templateSpecialization=[], + isStruct=False, + disallowCopyConstruction=False, + indent="", + decorators="", + extradeclarations="", + extradefinitions="", + ): + CGThing.__init__(self) + self.name = name + self.bases = bases + self.members = members + self.constructors = constructors + # We store our single destructor in a list, since all of our + # code wants lists of members. + self.destructors = [destructor] if destructor else [] + self.methods = methods + self.enums = enums + self.unions = unions + self.templateArgs = templateArgs + self.templateSpecialization = templateSpecialization + self.isStruct = isStruct + self.disallowCopyConstruction = disallowCopyConstruction + self.indent = indent + self.defaultVisibility = "public" if isStruct else "private" + self.decorators = decorators + self.extradeclarations = extradeclarations + self.extradefinitions = extradefinitions + + def getNameString(self): + className = self.name + if self.templateSpecialization: + className += "<%s>" % ", ".join( + [str(a) for a in self.templateSpecialization] + ) + return className + + def declare(self): + result = "" + if self.templateArgs: + templateArgs = [a.declare() for a in self.templateArgs] + templateArgs = templateArgs[len(self.templateSpecialization) :] + result += "template <%s>\n" % ",".join([str(a) for a in templateArgs]) + + type = "struct" if self.isStruct else "class" + + if self.templateSpecialization: + specialization = "<%s>" % ", ".join( + [str(a) for a in self.templateSpecialization] + ) + else: + specialization = "" + + myself = "%s %s%s" % (type, self.name, specialization) + if self.decorators != "": + myself += " " + self.decorators + result += myself + + if self.bases: + inherit = " : " + result += inherit + # Grab our first base + baseItems = [CGGeneric(b.declare(self)) for b in self.bases] + bases = baseItems[:1] + # Indent the rest + bases.extend( + CGIndenter(b, len(myself) + len(inherit)) for b in baseItems[1:] + ) + result += ",\n".join(b.define() for b in bases) + + result += "\n{\n" + + result += self.extradeclarations + + def declareMembers(cgClass, memberList, defaultVisibility): + members = {"private": [], "protected": [], "public": []} + + for member in memberList: + members[member.visibility].append(member) + + if defaultVisibility == "public": + order = ["public", "protected", "private"] + else: + order = ["private", "protected", "public"] + + result = "" + + lastVisibility = defaultVisibility + for visibility in order: + list = members[visibility] + if list: + if visibility != lastVisibility: + result += visibility + ":\n" + for member in list: + result += indent(member.declare(cgClass)) + lastVisibility = visibility + return (result, lastVisibility) + + if self.disallowCopyConstruction: + + class DisallowedCopyConstructor(object): + def __init__(self): + self.visibility = "private" + + def declare(self, cgClass): + name = cgClass.getNameString() + return ( + "%s(const %s&) = delete;\n" + "%s& operator=(const %s&) = delete;\n" + % (name, name, name, name) + ) + + disallowedCopyConstructors = [DisallowedCopyConstructor()] + else: + disallowedCopyConstructors = [] + + order = [ + self.enums, + self.unions, + self.members, + self.constructors + disallowedCopyConstructors, + self.destructors, + self.methods, + ] + + lastVisibility = self.defaultVisibility + pieces = [] + for memberList in order: + code, lastVisibility = declareMembers(self, memberList, lastVisibility) + + if code: + code = code.rstrip() + "\n" # remove extra blank lines at the end + pieces.append(code) + + result += "\n".join(pieces) + result += "};\n" + result = indent(result, len(self.indent)) + return result + + def define(self): + def defineMembers(cgClass, memberList, itemCount, separator=""): + result = "" + for member in memberList: + if itemCount != 0: + result = result + separator + definition = member.define(cgClass) + if definition: + # Member variables would only produce empty lines here. + result += definition + itemCount += 1 + return (result, itemCount) + + order = [ + (self.members, ""), + (self.constructors, "\n"), + (self.destructors, "\n"), + (self.methods, "\n"), + ] + + result = self.extradefinitions + itemCount = 0 + for memberList, separator in order: + memberString, itemCount = defineMembers( + self, memberList, itemCount, separator + ) + result = result + memberString + return result + + +class CGResolveOwnPropertyViaResolve(CGAbstractBindingMethod): + """ + An implementation of Xray ResolveOwnProperty stuff for things that have a + resolve hook. + """ + + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "wrapper"), + Argument("JS::Handle<JSObject*>", "obj"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::MutableHandle<Maybe<JS::PropertyDescriptor>>", "desc"), + ] + CGAbstractBindingMethod.__init__( + self, + descriptor, + "ResolveOwnPropertyViaResolve", + args, + getThisObj="", + callArgs="", + ) + + def generate_code(self): + return CGGeneric( + dedent( + """ + { + // Since we're dealing with an Xray, do the resolve on the + // underlying object first. That gives it a chance to + // define properties on the actual object as needed, and + // then use the fact that it created the objects as a flag + // to avoid re-resolving the properties if someone deletes + // them. + JSAutoRealm ar(cx, obj); + JS_MarkCrossZoneId(cx, id); + JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> objDesc(cx); + if (!self->DoResolve(cx, obj, id, &objDesc)) { + return false; + } + // If desc->value() is undefined, then the DoResolve call + // has already defined the property on the object. Don't + // try to also define it. + if (objDesc.isSome() && + !objDesc->value().isUndefined()) { + JS::Rooted<JS::PropertyDescriptor> defineDesc(cx, *objDesc); + if (!JS_DefinePropertyById(cx, obj, id, defineDesc)) { + return false; + } + } + } + return self->DoResolve(cx, wrapper, id, desc); + """ + ) + ) + + +class CGEnumerateOwnPropertiesViaGetOwnPropertyNames(CGAbstractBindingMethod): + """ + An implementation of Xray EnumerateOwnProperties stuff for things + that have a resolve hook. + """ + + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "wrapper"), + Argument("JS::Handle<JSObject*>", "obj"), + Argument("JS::MutableHandleVector<jsid>", "props"), + ] + CGAbstractBindingMethod.__init__( + self, + descriptor, + "EnumerateOwnPropertiesViaGetOwnPropertyNames", + args, + getThisObj="", + callArgs="", + ) + + def generate_code(self): + return CGGeneric( + dedent( + """ + FastErrorResult rv; + // This wants all own props, not just enumerable ones. + self->GetOwnPropertyNames(cx, props, false, rv); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + return true; + """ + ) + ) + + +class CGPrototypeTraitsClass(CGClass): + def __init__(self, descriptor, indent=""): + templateArgs = [Argument("prototypes::ID", "PrototypeID")] + templateSpecialization = ["prototypes::id::" + descriptor.name] + enums = [ClassEnum("", ["Depth"], [descriptor.interface.inheritanceDepth()])] + CGClass.__init__( + self, + "PrototypeTraits", + indent=indent, + templateArgs=templateArgs, + templateSpecialization=templateSpecialization, + enums=enums, + isStruct=True, + ) + + def deps(self): + return set() + + +class CGClassForwardDeclare(CGThing): + def __init__(self, name, isStruct=False): + CGThing.__init__(self) + self.name = name + self.isStruct = isStruct + + def declare(self): + type = "struct" if self.isStruct else "class" + return "%s %s;\n" % (type, self.name) + + def define(self): + # Header only + return "" + + def deps(self): + return set() + + +class CGProxySpecialOperation(CGPerSignatureCall): + """ + Base class for classes for calling an indexed or named special operation + (don't use this directly, use the derived classes below). + + If checkFound is False, will just assert that the prop is found instead of + checking that it is before wrapping the value. + + resultVar: See the docstring for CGCallGenerator. + + foundVar: For getters and deleters, the generated code can also set a bool + variable, declared by the caller, if the given indexed or named property + already existed. If the caller wants this, it should pass the name of the + bool variable as the foundVar keyword argument to the constructor. The + caller is responsible for declaring the variable and initializing it to + false. + """ + + def __init__( + self, + descriptor, + operation, + checkFound=True, + argumentHandleValue=None, + resultVar=None, + foundVar=None, + ): + self.checkFound = checkFound + self.foundVar = foundVar or "found" + + nativeName = MakeNativeName(descriptor.binaryNameFor(operation, False)) + operation = descriptor.operations[operation] + assert len(operation.signatures()) == 1 + signature = operation.signatures()[0] + + returnType, arguments = signature + + # We pass len(arguments) as the final argument so that the + # CGPerSignatureCall won't do any argument conversion of its own. + CGPerSignatureCall.__init__( + self, + returnType, + arguments, + nativeName, + False, + descriptor, + operation, + len(arguments), + resultVar=resultVar, + objectName="proxy", + ) + + if operation.isSetter(): + # arguments[0] is the index or name of the item that we're setting. + argument = arguments[1] + info = getJSToNativeConversionInfo( + argument.type, + descriptor, + sourceDescription=( + "value being assigned to %s setter" + % descriptor.interface.identifier.name + ), + ) + if argumentHandleValue is None: + argumentHandleValue = "desc.value()" + rootedValue = fill( + """ + JS::Rooted<JS::Value> rootedValue(cx, ${argumentHandleValue}); + """, + argumentHandleValue=argumentHandleValue, + ) + templateValues = { + "declName": argument.identifier.name, + "holderName": argument.identifier.name + "_holder", + "val": argumentHandleValue, + "maybeMutableVal": "&rootedValue", + "obj": "obj", + "passedToJSImpl": "false", + } + self.cgRoot.prepend(instantiateJSToNativeConversion(info, templateValues)) + # rootedValue needs to come before the conversion, so we + # need to prepend it last. + self.cgRoot.prepend(CGGeneric(rootedValue)) + elif operation.isGetter() or operation.isDeleter(): + if foundVar is None: + self.cgRoot.prepend(CGGeneric("bool found = false;\n")) + + def getArguments(self): + args = [(a, a.identifier.name) for a in self.arguments] + if self.idlNode.isGetter() or self.idlNode.isDeleter(): + args.append( + ( + FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean]), + self.foundVar, + ) + ) + return args + + def wrap_return_value(self): + if not self.idlNode.isGetter() or self.templateValues is None: + return "" + + wrap = CGGeneric( + wrapForType(self.returnType, self.descriptor, self.templateValues) + ) + if self.checkFound: + wrap = CGIfWrapper(wrap, self.foundVar) + else: + wrap = CGList([CGGeneric("MOZ_ASSERT(" + self.foundVar + ");\n"), wrap]) + return "\n" + wrap.define() + + +class CGProxyIndexedOperation(CGProxySpecialOperation): + """ + Class to generate a call to an indexed operation. + + If doUnwrap is False, the caller is responsible for making sure a variable + named 'self' holds the C++ object somewhere where the code we generate + will see it. + + If checkFound is False, will just assert that the prop is found instead of + checking that it is before wrapping the value. + + resultVar: See the docstring for CGCallGenerator. + + foundVar: See the docstring for CGProxySpecialOperation. + """ + + def __init__( + self, + descriptor, + name, + doUnwrap=True, + checkFound=True, + argumentHandleValue=None, + resultVar=None, + foundVar=None, + ): + self.doUnwrap = doUnwrap + CGProxySpecialOperation.__init__( + self, + descriptor, + name, + checkFound, + argumentHandleValue=argumentHandleValue, + resultVar=resultVar, + foundVar=foundVar, + ) + + def define(self): + # Our first argument is the id we're getting. + argName = self.arguments[0].identifier.name + if argName == "index": + # We already have our index in a variable with that name + setIndex = "" + else: + setIndex = "uint32_t %s = index;\n" % argName + if self.doUnwrap: + unwrap = "%s* self = UnwrapProxy(proxy);\n" % self.descriptor.nativeType + else: + unwrap = "" + return setIndex + unwrap + CGProxySpecialOperation.define(self) + + +class CGProxyIndexedGetter(CGProxyIndexedOperation): + """ + Class to generate a call to an indexed getter. If templateValues is not None + the returned value will be wrapped with wrapForType using templateValues. + + If doUnwrap is False, the caller is responsible for making sure a variable + named 'self' holds the C++ object somewhere where the code we generate + will see it. + + If checkFound is False, will just assert that the prop is found instead of + checking that it is before wrapping the value. + + foundVar: See the docstring for CGProxySpecialOperation. + """ + + def __init__( + self, + descriptor, + templateValues=None, + doUnwrap=True, + checkFound=True, + foundVar=None, + ): + self.templateValues = templateValues + CGProxyIndexedOperation.__init__( + self, descriptor, "IndexedGetter", doUnwrap, checkFound, foundVar=foundVar + ) + + +class CGProxyIndexedPresenceChecker(CGProxyIndexedGetter): + """ + Class to generate a call that checks whether an indexed property exists. + + For now, we just delegate to CGProxyIndexedGetter + + foundVar: See the docstring for CGProxySpecialOperation. + """ + + def __init__(self, descriptor, foundVar): + CGProxyIndexedGetter.__init__(self, descriptor, foundVar=foundVar) + self.cgRoot.append(CGGeneric("(void)result;\n")) + + +class CGProxyIndexedSetter(CGProxyIndexedOperation): + """ + Class to generate a call to an indexed setter. + """ + + def __init__(self, descriptor, argumentHandleValue=None): + CGProxyIndexedOperation.__init__( + self, descriptor, "IndexedSetter", argumentHandleValue=argumentHandleValue + ) + + +class CGProxyNamedOperation(CGProxySpecialOperation): + """ + Class to generate a call to a named operation. + + 'value' is the jsval to use for the name; None indicates that it should be + gotten from the property id. + + resultVar: See the docstring for CGCallGenerator. + + foundVar: See the docstring for CGProxySpecialOperation. + + tailCode: if we end up with a non-symbol string id, run this code after + we do all our other work. + """ + + def __init__( + self, + descriptor, + name, + value=None, + argumentHandleValue=None, + resultVar=None, + foundVar=None, + tailCode="", + ): + CGProxySpecialOperation.__init__( + self, + descriptor, + name, + argumentHandleValue=argumentHandleValue, + resultVar=resultVar, + foundVar=foundVar, + ) + self.value = value + self.tailCode = tailCode + + def define(self): + # Our first argument is the id we're getting. + argName = self.arguments[0].identifier.name + if argName == "id": + # deal with the name collision + decls = "JS::Rooted<jsid> id_(cx, id);\n" + idName = "id_" + else: + decls = "" + idName = "id" + + decls += "FakeString<char16_t> %s;\n" % argName + + main = fill( + """ + ${nativeType}* self = UnwrapProxy(proxy); + $*{op} + $*{tailCode} + """, + nativeType=self.descriptor.nativeType, + op=CGProxySpecialOperation.define(self), + tailCode=self.tailCode, + ) + + if self.value is None: + return fill( + """ + $*{decls} + bool isSymbol; + if (!ConvertIdToString(cx, ${idName}, ${argName}, isSymbol)) { + return false; + } + if (!isSymbol) { + $*{main} + } + """, + decls=decls, + idName=idName, + argName=argName, + main=main, + ) + + # Sadly, we have to set up nameVal even if we have an atom id, + # because we don't know for sure, and we can end up needing it + # so it needs to be higher up the stack. Using a Maybe here + # seems like probable overkill. + return fill( + """ + $*{decls} + JS::Rooted<JS::Value> nameVal(cx, ${value}); + if (!nameVal.isSymbol()) { + if (!ConvertJSValueToString(cx, nameVal, eStringify, eStringify, + ${argName})) { + return false; + } + $*{main} + } + """, + decls=decls, + value=self.value, + argName=argName, + main=main, + ) + + +class CGProxyNamedGetter(CGProxyNamedOperation): + """ + Class to generate a call to an named getter. If templateValues is not None + the returned value will be wrapped with wrapForType using templateValues. + 'value' is the jsval to use for the name; None indicates that it should be + gotten from the property id. + + foundVar: See the docstring for CGProxySpecialOperation. + """ + + def __init__(self, descriptor, templateValues=None, value=None, foundVar=None): + self.templateValues = templateValues + CGProxyNamedOperation.__init__( + self, descriptor, "NamedGetter", value, foundVar=foundVar + ) + + +class CGProxyNamedPresenceChecker(CGProxyNamedGetter): + """ + Class to generate a call that checks whether a named property exists. + + For now, we just delegate to CGProxyNamedGetter + + foundVar: See the docstring for CGProxySpecialOperation. + """ + + def __init__(self, descriptor, foundVar=None): + CGProxyNamedGetter.__init__(self, descriptor, foundVar=foundVar) + self.cgRoot.append(CGGeneric("(void)result;\n")) + + +class CGProxyNamedSetter(CGProxyNamedOperation): + """ + Class to generate a call to a named setter. + """ + + def __init__(self, descriptor, tailCode, argumentHandleValue=None): + CGProxyNamedOperation.__init__( + self, + descriptor, + "NamedSetter", + argumentHandleValue=argumentHandleValue, + tailCode=tailCode, + ) + + +class CGProxyNamedDeleter(CGProxyNamedOperation): + """ + Class to generate a call to a named deleter. + + resultVar: See the docstring for CGCallGenerator. + + foundVar: See the docstring for CGProxySpecialOperation. + """ + + def __init__(self, descriptor, resultVar=None, foundVar=None): + CGProxyNamedOperation.__init__( + self, descriptor, "NamedDeleter", resultVar=resultVar, foundVar=foundVar + ) + + +class CGProxyIsProxy(CGAbstractMethod): + def __init__(self, descriptor): + args = [Argument("JSObject*", "obj")] + CGAbstractMethod.__init__( + self, descriptor, "IsProxy", "bool", args, alwaysInline=True + ) + + def declare(self): + return "" + + def definition_body(self): + return "return js::IsProxy(obj) && js::GetProxyHandler(obj) == DOMProxyHandler::getInstance();\n" + + +class CGProxyUnwrap(CGAbstractMethod): + def __init__(self, descriptor): + args = [Argument("JSObject*", "obj")] + CGAbstractMethod.__init__( + self, + descriptor, + "UnwrapProxy", + descriptor.nativeType + "*", + args, + alwaysInline=True, + ) + + def declare(self): + return "" + + def definition_body(self): + return fill( + """ + MOZ_ASSERT(js::IsProxy(obj)); + if (js::GetProxyHandler(obj) != DOMProxyHandler::getInstance()) { + MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(obj)); + obj = js::UncheckedUnwrap(obj); + } + MOZ_ASSERT(IsProxy(obj)); + return static_cast<${type}*>(js::GetProxyReservedSlot(obj, DOM_OBJECT_SLOT).toPrivate()); + """, + type=self.descriptor.nativeType, + ) + + +MISSING_PROP_PREF = "dom.missing_prop_counters.enabled" + + +def missingPropUseCountersForDescriptor(desc): + if not desc.needsMissingPropUseCounters: + return "" + + return fill( + """ + if (StaticPrefs::${pref}() && id.isAtom()) { + CountMaybeMissingProperty(proxy, id); + } + + """, + pref=prefIdentifier(MISSING_PROP_PREF), + ) + + +def findAncestorWithInstrumentedProps(desc): + """ + Find an ancestor of desc.interface (not including desc.interface + itself) that has instrumented properties on it. May return None + if there is no such ancestor. + """ + ancestor = desc.interface.parent + while ancestor: + if ancestor.getExtendedAttribute("InstrumentedProps"): + return ancestor + ancestor = ancestor.parent + return None + + +class CGCountMaybeMissingProperty(CGAbstractMethod): + def __init__(self, descriptor): + """ + Returns whether we counted the property involved. + """ + CGAbstractMethod.__init__( + self, + descriptor, + "CountMaybeMissingProperty", + "bool", + [ + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + ], + ) + + def gen_switch(self, switchDecriptor): + """ + Generate a switch from the switch descriptor. The descriptor + dictionary must have the following properties: + + 1) A "precondition" property that contains code to run before the + switch statement. Its value ie a string. + 2) A "condition" property for the condition. Its value is a string. + 3) A "cases" property. Its value is an object that has property names + corresponding to the case labels. The values of those properties + are either new switch descriptor dictionaries (which will then + generate nested switches) or strings to use for case bodies. + """ + cases = [] + for label, body in sorted(six.iteritems(switchDecriptor["cases"])): + if isinstance(body, dict): + body = self.gen_switch(body) + cases.append( + fill( + """ + case ${label}: { + $*{body} + break; + } + """, + label=label, + body=body, + ) + ) + return fill( + """ + $*{precondition} + switch (${condition}) { + $*{cases} + } + """, + precondition=switchDecriptor["precondition"], + condition=switchDecriptor["condition"], + cases="".join(cases), + ) + + def charSwitch(self, props, charIndex): + """ + Create a switch for the given props, based on the first char where + they start to differ at index charIndex or more. Each prop is a tuple + containing interface name and prop name. + + Incoming props should be a sorted list. + """ + if len(props) == 1: + # We're down to one string: just check whether we match it. + return fill( + """ + if (JS_LinearStringEqualsLiteral(str, "${name}")) { + counter.emplace(eUseCounter_${iface}_${name}); + } + """, + iface=self.descriptor.name, + name=props[0], + ) + + switch = dict() + if charIndex == 0: + switch["precondition"] = "StringIdChars chars(nogc, str);\n" + else: + switch["precondition"] = "" + + # Find the first place where we might actually have a difference. + while all(prop[charIndex] == props[0][charIndex] for prop in props): + charIndex += 1 + + switch["condition"] = "chars[%d]" % charIndex + switch["cases"] = dict() + current_props = None + curChar = None + idx = 0 + while idx < len(props): + nextChar = "'%s'" % props[idx][charIndex] + if nextChar != curChar: + if curChar: + switch["cases"][curChar] = self.charSwitch( + current_props, charIndex + 1 + ) + current_props = [] + curChar = nextChar + current_props.append(props[idx]) + idx += 1 + switch["cases"][curChar] = self.charSwitch(current_props, charIndex + 1) + return switch + + def definition_body(self): + ancestor = findAncestorWithInstrumentedProps(self.descriptor) + + if ancestor: + body = fill( + """ + if (${ancestor}_Binding::CountMaybeMissingProperty(proxy, id)) { + return true; + } + + """, + ancestor=ancestor.identifier.name, + ) + else: + body = "" + + instrumentedProps = self.descriptor.instrumentedProps + if not instrumentedProps: + return body + dedent( + """ + return false; + """ + ) + + lengths = set(len(prop) for prop in instrumentedProps) + switchDesc = {"condition": "JS::GetLinearStringLength(str)", "precondition": ""} + switchDesc["cases"] = dict() + for length in sorted(lengths): + switchDesc["cases"][str(length)] = self.charSwitch( + list(sorted(prop for prop in instrumentedProps if len(prop) == length)), + 0, + ) + + return body + fill( + """ + MOZ_ASSERT(StaticPrefs::${pref}() && id.isAtom()); + Maybe<UseCounter> counter; + { + // Scope for our no-GC section, so we don't need to rely on SetUseCounter not GCing. + JS::AutoCheckCannotGC nogc; + JSLinearString* str = JS::AtomToLinearString(id.toAtom()); + // Don't waste time fetching the chars until we've done the length switch. + $*{switch} + } + if (counter) { + SetUseCounter(proxy, *counter); + return true; + } + + return false; + """, + pref=prefIdentifier(MISSING_PROP_PREF), + switch=self.gen_switch(switchDesc), + ) + + +class CGDOMJSProxyHandler_getOwnPropDescriptor(ClassMethod): + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + Argument("bool", "ignoreNamedProps"), + Argument("JS::MutableHandle<Maybe<JS::PropertyDescriptor>>", "desc"), + ] + ClassMethod.__init__( + self, + "getOwnPropDescriptor", + "bool", + args, + virtual=True, + override=True, + const=True, + ) + self.descriptor = descriptor + + def getBody(self): + indexedSetter = self.descriptor.operations["IndexedSetter"] + + if self.descriptor.isMaybeCrossOriginObject(): + xrayDecl = dedent( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy)); + MOZ_ASSERT(IsPlatformObjectSameOrigin(cx, proxy), + "getOwnPropertyDescriptor() and set() should have dealt"); + MOZ_ASSERT(js::IsObjectInContextCompartment(proxy, cx), + "getOwnPropertyDescriptor() and set() should have dealt"); + + """ + ) + xrayCheck = "" + else: + xrayDecl = "bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);\n" + xrayCheck = "!isXray &&" + + if self.descriptor.supportsIndexedProperties(): + attributes = [ + "JS::PropertyAttribute::Configurable", + "JS::PropertyAttribute::Enumerable", + ] + if indexedSetter is not None: + attributes.append("JS::PropertyAttribute::Writable") + setDescriptor = ( + "desc.set(mozilla::Some(JS::PropertyDescriptor::Data(value, { %s })));\nreturn true;\n" + % ", ".join(attributes) + ) + templateValues = { + "jsvalRef": "value", + "jsvalHandle": "&value", + "obj": "proxy", + "successCode": setDescriptor, + } + getIndexed = fill( + """ + uint32_t index = GetArrayIndexFromId(id); + if (IsArrayIndex(index)) { + JS::Rooted<JS::Value> value(cx); + $*{callGetter} + } + + """, + callGetter=CGProxyIndexedGetter( + self.descriptor, templateValues + ).define(), + ) + else: + getIndexed = "" + + missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor) + + if self.descriptor.supportsNamedProperties(): + operations = self.descriptor.operations + attributes = ["JS::PropertyAttribute::Configurable"] + if self.descriptor.namedPropertiesEnumerable: + attributes.append("JS::PropertyAttribute::Enumerable") + if operations["NamedSetter"] is not None: + attributes.append("JS::PropertyAttribute::Writable") + setDescriptor = ( + "desc.set(mozilla::Some(JS::PropertyDescriptor::Data(value, { %s })));\nreturn true;\n" + % ", ".join(attributes) + ) + templateValues = { + "jsvalRef": "value", + "jsvalHandle": "&value", + "obj": "proxy", + "successCode": setDescriptor, + } + + computeCondition = dedent( + """ + bool hasOnProto; + if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) { + return false; + } + callNamedGetter = !hasOnProto; + """ + ) + if self.descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"): + computeCondition = fill( + """ + if (!isXray) { + callNamedGetter = true; + } else { + $*{hasOnProto} + } + """, + hasOnProto=computeCondition, + ) + + outerCondition = "!ignoreNamedProps" + if self.descriptor.supportsIndexedProperties(): + outerCondition = "!IsArrayIndex(index) && " + outerCondition + + namedGetCode = CGProxyNamedGetter(self.descriptor, templateValues).define() + namedGet = fill( + """ + bool callNamedGetter = false; + if (${outerCondition}) { + $*{computeCondition} + } + if (callNamedGetter) { + JS::Rooted<JS::Value> value(cx); + $*{namedGetCode} + } + """, + outerCondition=outerCondition, + computeCondition=computeCondition, + namedGetCode=namedGetCode, + ) + namedGet += "\n" + else: + namedGet = "" + + return fill( + """ + $*{xrayDecl} + $*{getIndexed} + $*{missingPropUseCounters} + JS::Rooted<JSObject*> expando(cx); + if (${xrayCheck}(expando = GetExpandoObject(proxy))) { + if (!JS_GetOwnPropertyDescriptorById(cx, expando, id, desc)) { + return false; + } + if (desc.isSome()) { + return true; + } + } + + $*{namedGet} + desc.reset(); + return true; + """, + xrayDecl=xrayDecl, + xrayCheck=xrayCheck, + getIndexed=getIndexed, + missingPropUseCounters=missingPropUseCounters, + namedGet=namedGet, + ) + + +class CGDOMJSProxyHandler_defineProperty(ClassMethod): + def __init__(self, descriptor): + # The usual convention is to name the ObjectOpResult out-parameter + # `result`, but that name is a bit overloaded around here. + args = [ + Argument("JSContext*", "cx_"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::Handle<JS::PropertyDescriptor>", "desc"), + Argument("JS::ObjectOpResult&", "opresult"), + Argument("bool*", "done"), + ] + ClassMethod.__init__( + self, + "defineProperty", + "bool", + args, + virtual=True, + override=True, + const=True, + ) + self.descriptor = descriptor + + def getBody(self): + set = "" + + indexedSetter = self.descriptor.operations["IndexedSetter"] + if indexedSetter: + error_label = CGSpecializedMethod.error_reporting_label_helper( + self.descriptor, indexedSetter, isConstructor=False + ) + if error_label: + cxDecl = fill( + """ + BindingCallContext cx(cx_, "${error_label}"); + """, + error_label=error_label, + ) + else: + cxDecl = dedent( + """ + JSContext* cx = cx_; + """ + ) + set += fill( + """ + uint32_t index = GetArrayIndexFromId(id); + if (IsArrayIndex(index)) { + $*{cxDecl} + *done = true; + // https://heycam.github.io/webidl/#legacy-platform-object-defineownproperty + // Step 1.1. The no-indexed-setter case is handled by step 1.2. + if (!desc.isDataDescriptor()) { + return opresult.failNotDataDescriptor(); + } + + $*{callSetter} + return opresult.succeed(); + } + """, + cxDecl=cxDecl, + callSetter=CGProxyIndexedSetter(self.descriptor).define(), + ) + elif self.descriptor.supportsIndexedProperties(): + # We allow untrusted content to prevent Xrays from setting a + # property if that property is an indexed property and we have no + # indexed setter. That's how the object would normally behave if + # you tried to set the property on it. That means we don't need to + # do anything special for Xrays here. + set += dedent( + """ + if (IsArrayIndex(GetArrayIndexFromId(id))) { + *done = true; + return opresult.failNoIndexedSetter(); + } + """ + ) + + namedSetter = self.descriptor.operations["NamedSetter"] + if namedSetter: + error_label = CGSpecializedMethod.error_reporting_label_helper( + self.descriptor, namedSetter, isConstructor=False + ) + if error_label: + set += fill( + """ + BindingCallContext cx(cx_, "${error_label}"); + """, + error_label=error_label, + ) + else: + set += dedent( + """ + JSContext* cx = cx_; + """ + ) + if self.descriptor.hasLegacyUnforgeableMembers: + raise TypeError( + "Can't handle a named setter on an interface " + "that has unforgeables. Figure out how that " + "should work!" + ) + tailCode = dedent( + """ + *done = true; + return opresult.succeed(); + """ + ) + set += CGProxyNamedSetter(self.descriptor, tailCode).define() + else: + # We allow untrusted content to prevent Xrays from setting a + # property if that property is already a named property on the + # object and we have no named setter. That's how the object would + # normally behave if you tried to set the property on it. That + # means we don't need to do anything special for Xrays here. + if self.descriptor.supportsNamedProperties(): + set += fill( + """ + JSContext* cx = cx_; + bool found = false; + $*{presenceChecker} + + if (found) { + *done = true; + return opresult.failNoNamedSetter(); + } + """, + presenceChecker=CGProxyNamedPresenceChecker( + self.descriptor, foundVar="found" + ).define(), + ) + if self.descriptor.isMaybeCrossOriginObject(): + set += dedent( + """ + MOZ_ASSERT(IsPlatformObjectSameOrigin(cx_, proxy), + "Why did the MaybeCrossOriginObject defineProperty override fail?"); + MOZ_ASSERT(js::IsObjectInContextCompartment(proxy, cx_), + "Why did the MaybeCrossOriginObject defineProperty override fail?"); + """ + ) + + # In all cases we want to tail-call to our base class; we can + # always land here for symbols. + set += ( + "return mozilla::dom::DOMProxyHandler::defineProperty(%s);\n" + % ", ".join(a.name for a in self.args) + ) + return set + + +def getDeleterBody(descriptor, type, foundVar=None): + """ + type should be "Named" or "Indexed" + + The possible outcomes: + - an error happened (the emitted code returns false) + - own property not found (foundVar=false, deleteSucceeded=true) + - own property found and deleted (foundVar=true, deleteSucceeded=true) + - own property found but can't be deleted (foundVar=true, deleteSucceeded=false) + """ + assert type in ("Named", "Indexed") + deleter = descriptor.operations[type + "Deleter"] + if deleter: + assert type == "Named" + assert foundVar is not None + if descriptor.hasLegacyUnforgeableMembers: + raise TypeError( + "Can't handle a deleter on an interface " + "that has unforgeables. Figure out how " + "that should work!" + ) + # See if the deleter method is fallible. + t = deleter.signatures()[0][0] + if t.isPrimitive() and not t.nullable() and t.tag() == IDLType.Tags.bool: + # The deleter method has a boolean return value. When a + # property is found, the return value indicates whether it + # was successfully deleted. + setDS = fill( + """ + if (!${foundVar}) { + deleteSucceeded = true; + } + """, + foundVar=foundVar, + ) + else: + # No boolean return value: if a property is found, + # deleting it always succeeds. + setDS = "deleteSucceeded = true;\n" + + body = ( + CGProxyNamedDeleter( + descriptor, resultVar="deleteSucceeded", foundVar=foundVar + ).define() + + setDS + ) + elif getattr(descriptor, "supports%sProperties" % type)(): + presenceCheckerClass = globals()["CGProxy%sPresenceChecker" % type] + foundDecl = "" + if foundVar is None: + foundVar = "found" + foundDecl = "bool found = false;\n" + body = fill( + """ + $*{foundDecl} + $*{presenceChecker} + deleteSucceeded = !${foundVar}; + """, + foundDecl=foundDecl, + presenceChecker=presenceCheckerClass( + descriptor, foundVar=foundVar + ).define(), + foundVar=foundVar, + ) + else: + body = None + return body + + +class CGDeleteNamedProperty(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "xray"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::ObjectOpResult&", "opresult"), + ] + CGAbstractStaticMethod.__init__( + self, descriptor, "DeleteNamedProperty", "bool", args + ) + + def definition_body(self): + return fill( + """ + MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(xray)); + MOZ_ASSERT(js::IsProxy(proxy)); + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy)); + JSAutoRealm ar(cx, proxy); + bool deleteSucceeded = false; + bool found = false; + $*{namedBody} + if (!found || deleteSucceeded) { + return opresult.succeed(); + } + return opresult.failCantDelete(); + """, + namedBody=getDeleterBody(self.descriptor, "Named", foundVar="found"), + ) + + +class CGDOMJSProxyHandler_delete(ClassMethod): + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::ObjectOpResult&", "opresult"), + ] + ClassMethod.__init__( + self, "delete_", "bool", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + delete = dedent( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + """ + ) + + if self.descriptor.isMaybeCrossOriginObject(): + delete += dedent( + """ + if (!IsPlatformObjectSameOrigin(cx, proxy)) { + return ReportCrossOriginDenial(cx, id, "delete"_ns); + } + + // Safe to enter the Realm of proxy now. + JSAutoRealm ar(cx, proxy); + JS_MarkCrossZoneId(cx, id); + """ + ) + + indexedBody = getDeleterBody(self.descriptor, "Indexed") + if indexedBody is not None: + # Can't handle cross-origin objects here. + assert not self.descriptor.isMaybeCrossOriginObject() + delete += fill( + """ + uint32_t index = GetArrayIndexFromId(id); + if (IsArrayIndex(index)) { + bool deleteSucceeded; + $*{indexedBody} + return deleteSucceeded ? opresult.succeed() : opresult.failCantDelete(); + } + """, + indexedBody=indexedBody, + ) + + namedBody = getDeleterBody(self.descriptor, "Named", foundVar="found") + if namedBody is not None: + delete += dedent( + """ + // Try named delete only if the named property visibility + // algorithm says the property is visible. + bool tryNamedDelete = true; + { // Scope for expando + JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy)); + if (expando) { + bool hasProp; + if (!JS_HasPropertyById(cx, expando, id, &hasProp)) { + return false; + } + tryNamedDelete = !hasProp; + } + } + """ + ) + + if not self.descriptor.interface.getExtendedAttribute( + "LegacyOverrideBuiltIns" + ): + delete += dedent( + """ + if (tryNamedDelete) { + bool hasOnProto; + if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) { + return false; + } + tryNamedDelete = !hasOnProto; + } + """ + ) + + # We always return above for an index id in the case when we support + # indexed properties, so we can just treat the id as a name + # unconditionally here. + delete += fill( + """ + if (tryNamedDelete) { + bool found = false; + bool deleteSucceeded; + $*{namedBody} + if (found) { + return deleteSucceeded ? opresult.succeed() : opresult.failCantDelete(); + } + } + """, + namedBody=namedBody, + ) + + delete += dedent( + """ + + return dom::DOMProxyHandler::delete_(cx, proxy, id, opresult); + """ + ) + + return delete + + +class CGDOMJSProxyHandler_ownPropNames(ClassMethod): + def __init__( + self, + descriptor, + ): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("unsigned", "flags"), + Argument("JS::MutableHandleVector<jsid>", "props"), + ] + ClassMethod.__init__( + self, "ownPropNames", "bool", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + if self.descriptor.isMaybeCrossOriginObject(): + xrayDecl = dedent( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy)); + if (!IsPlatformObjectSameOrigin(cx, proxy)) { + if (!(flags & JSITER_HIDDEN)) { + // There are no enumerable cross-origin props, so we're done. + return true; + } + + JS::Rooted<JSObject*> holder(cx); + if (!EnsureHolder(cx, proxy, &holder)) { + return false; + } + + if (!js::GetPropertyKeys(cx, holder, flags, props)) { + return false; + } + + return xpc::AppendCrossOriginWhitelistedPropNames(cx, props); + } + + """ + ) + xrayCheck = "" + else: + xrayDecl = "bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);\n" + xrayCheck = "!isXray &&" + + # Per spec, we do indices, then named props, then everything else. + if self.descriptor.supportsIndexedProperties(): + if self.descriptor.lengthNeedsCallerType(): + callerType = callerTypeGetterForDescriptor(self.descriptor) + else: + callerType = "" + addIndices = fill( + """ + + uint32_t length = UnwrapProxy(proxy)->Length(${callerType}); + MOZ_ASSERT(int32_t(length) >= 0); + for (int32_t i = 0; i < int32_t(length); ++i) { + if (!props.append(JS::PropertyKey::Int(i))) { + return false; + } + } + """, + callerType=callerType, + ) + else: + addIndices = "" + + if self.descriptor.supportsNamedProperties(): + if self.descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"): + shadow = "!isXray" + else: + shadow = "false" + + if self.descriptor.supportedNamesNeedCallerType(): + callerType = ", " + callerTypeGetterForDescriptor(self.descriptor) + else: + callerType = "" + + addNames = fill( + """ + nsTArray<nsString> names; + UnwrapProxy(proxy)->GetSupportedNames(names${callerType}); + if (!AppendNamedPropertyIds(cx, proxy, names, ${shadow}, props)) { + return false; + } + """, + callerType=callerType, + shadow=shadow, + ) + if not self.descriptor.namedPropertiesEnumerable: + addNames = CGIfWrapper( + CGGeneric(addNames), "flags & JSITER_HIDDEN" + ).define() + addNames = "\n" + addNames + else: + addNames = "" + + addExpandoProps = fill( + """ + JS::Rooted<JSObject*> expando(cx); + if (${xrayCheck}(expando = DOMProxyHandler::GetExpandoObject(proxy)) && + !js::GetPropertyKeys(cx, expando, flags, props)) { + return false; + } + """, + xrayCheck=xrayCheck, + ) + + if self.descriptor.isMaybeCrossOriginObject(): + # We need to enter our compartment (which we might not be + # in right now) to get the expando props. + addExpandoProps = fill( + """ + { // Scope for accessing the expando. + // Safe to enter our compartment, because IsPlatformObjectSameOrigin tested true. + JSAutoRealm ar(cx, proxy); + $*{addExpandoProps} + } + for (auto& id : props) { + JS_MarkCrossZoneId(cx, id); + } + """, + addExpandoProps=addExpandoProps, + ) + + return fill( + """ + $*{xrayDecl} + $*{addIndices} + $*{addNames} + + $*{addExpandoProps} + + return true; + """, + xrayDecl=xrayDecl, + addIndices=addIndices, + addNames=addNames, + addExpandoProps=addExpandoProps, + ) + + +class CGDOMJSProxyHandler_hasOwn(ClassMethod): + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + Argument("bool*", "bp"), + ] + ClassMethod.__init__( + self, "hasOwn", "bool", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + if self.descriptor.isMaybeCrossOriginObject(): + maybeCrossOrigin = dedent( + """ + if (!IsPlatformObjectSameOrigin(cx, proxy)) { + // Just hand this off to BaseProxyHandler to do the slow-path thing. + // The BaseProxyHandler code is OK with this happening without entering the + // compartment of "proxy", which is important to get the right answers. + return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp); + } + + // Now safe to enter the Realm of proxy and do the rest of the work there. + JSAutoRealm ar(cx, proxy); + JS_MarkCrossZoneId(cx, id); + """ + ) + else: + maybeCrossOrigin = "" + + if self.descriptor.supportsIndexedProperties(): + indexed = fill( + """ + uint32_t index = GetArrayIndexFromId(id); + if (IsArrayIndex(index)) { + bool found = false; + $*{presenceChecker} + + *bp = found; + return true; + } + + """, + presenceChecker=CGProxyIndexedPresenceChecker( + self.descriptor, foundVar="found" + ).define(), + ) + else: + indexed = "" + + if self.descriptor.supportsNamedProperties(): + # If we support indexed properties we always return above for index + # property names, so no need to check for those here. + named = fill( + """ + bool found = false; + $*{presenceChecker} + + *bp = found; + """, + presenceChecker=CGProxyNamedPresenceChecker( + self.descriptor, foundVar="found" + ).define(), + ) + if not self.descriptor.interface.getExtendedAttribute( + "LegacyOverrideBuiltIns" + ): + named = fill( + """ + bool hasOnProto; + if (!HasPropertyOnPrototype(cx, proxy, id, &hasOnProto)) { + return false; + } + if (!hasOnProto) { + $*{protoLacksProperty} + return true; + } + """, + protoLacksProperty=named, + ) + named += "*bp = false;\n" + else: + named += "\n" + else: + named = "*bp = false;\n" + + missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor) + + return fill( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + $*{maybeCrossOrigin} + $*{indexed} + + $*{missingPropUseCounters} + JS::Rooted<JSObject*> expando(cx, GetExpandoObject(proxy)); + if (expando) { + bool b = true; + bool ok = JS_HasPropertyById(cx, expando, id, &b); + *bp = !!b; + if (!ok || *bp) { + return ok; + } + } + + $*{named} + return true; + """, + maybeCrossOrigin=maybeCrossOrigin, + indexed=indexed, + missingPropUseCounters=missingPropUseCounters, + named=named, + ) + + +class CGDOMJSProxyHandler_get(ClassMethod): + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<JS::Value>", "receiver"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::MutableHandle<JS::Value>", "vp"), + ] + ClassMethod.__init__( + self, "get", "bool", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + missingPropUseCounters = missingPropUseCountersForDescriptor(self.descriptor) + + getUnforgeableOrExpando = dedent( + """ + bool expandoHasProp = false; + { // Scope for expando + JS::Rooted<JSObject*> expando(cx, DOMProxyHandler::GetExpandoObject(proxy)); + if (expando) { + if (!JS_HasPropertyById(cx, expando, id, &expandoHasProp)) { + return false; + } + + if (expandoHasProp) { + // Forward the get to the expando object, but our receiver is whatever our + // receiver is. + if (!JS_ForwardGetPropertyTo(cx, expando, id, ${receiver}, vp)) { + return false; + } + } + } + } + """ + ) + + getOnPrototype = dedent( + """ + bool foundOnPrototype; + if (!GetPropertyOnPrototype(cx, proxy, ${receiver}, id, &foundOnPrototype, vp)) { + return false; + } + """ + ) + + if self.descriptor.isMaybeCrossOriginObject(): + # We can't handle these for cross-origin objects + assert not self.descriptor.supportsIndexedProperties() + assert not self.descriptor.supportsNamedProperties() + + return fill( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + if (!IsPlatformObjectSameOrigin(cx, proxy)) { + return CrossOriginGet(cx, proxy, receiver, id, vp); + } + + $*{missingPropUseCounters} + { // Scope for the JSAutoRealm accessing expando and prototype. + JSAutoRealm ar(cx, proxy); + JS::Rooted<JS::Value> wrappedReceiver(cx, receiver); + if (!MaybeWrapValue(cx, &wrappedReceiver)) { + return false; + } + JS_MarkCrossZoneId(cx, id); + + $*{getUnforgeableOrExpando} + if (!expandoHasProp) { + $*{getOnPrototype} + if (!foundOnPrototype) { + MOZ_ASSERT(vp.isUndefined()); + return true; + } + } + } + + return MaybeWrapValue(cx, vp); + """, + missingPropUseCounters=missingPropUseCountersForDescriptor( + self.descriptor + ), + getUnforgeableOrExpando=fill( + getUnforgeableOrExpando, receiver="wrappedReceiver" + ), + getOnPrototype=fill(getOnPrototype, receiver="wrappedReceiver"), + ) + + templateValues = {"jsvalRef": "vp", "jsvalHandle": "vp", "obj": "proxy"} + + getUnforgeableOrExpando = fill( + getUnforgeableOrExpando, receiver="receiver" + ) + dedent( + """ + + if (expandoHasProp) { + return true; + } + """ + ) + if self.descriptor.supportsIndexedProperties(): + getIndexedOrExpando = fill( + """ + uint32_t index = GetArrayIndexFromId(id); + if (IsArrayIndex(index)) { + $*{callGetter} + // Even if we don't have this index, we don't forward the + // get on to our expando object. + } else { + $*{getUnforgeableOrExpando} + } + """, + callGetter=CGProxyIndexedGetter( + self.descriptor, templateValues + ).define(), + getUnforgeableOrExpando=getUnforgeableOrExpando, + ) + else: + getIndexedOrExpando = getUnforgeableOrExpando + + if self.descriptor.supportsNamedProperties(): + getNamed = CGProxyNamedGetter(self.descriptor, templateValues) + if self.descriptor.supportsIndexedProperties(): + getNamed = CGIfWrapper(getNamed, "!IsArrayIndex(index)") + getNamed = getNamed.define() + "\n" + else: + getNamed = "" + + getOnPrototype = fill(getOnPrototype, receiver="receiver") + dedent( + """ + + if (foundOnPrototype) { + return true; + } + + MOZ_ASSERT(vp.isUndefined()); + """ + ) + + if self.descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"): + getNamedOrOnPrototype = getNamed + getOnPrototype + else: + getNamedOrOnPrototype = getOnPrototype + getNamed + + return fill( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + $*{missingPropUseCounters} + $*{indexedOrExpando} + + $*{namedOrOnPropotype} + return true; + """, + missingPropUseCounters=missingPropUseCounters, + indexedOrExpando=getIndexedOrExpando, + namedOrOnPropotype=getNamedOrOnPrototype, + ) + + +class CGDOMJSProxyHandler_setCustom(ClassMethod): + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx_"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::Handle<JS::Value>", "v"), + Argument("bool*", "done"), + ] + ClassMethod.__init__( + self, "setCustom", "bool", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + assertion = ( + "MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),\n" + ' "Should not have a XrayWrapper here");\n' + ) + + # Correctness first. If we have a NamedSetter and [LegacyOverrideBuiltIns], + # always call the NamedSetter and never do anything else. + namedSetter = self.descriptor.operations["NamedSetter"] + if namedSetter is not None and self.descriptor.interface.getExtendedAttribute( + "LegacyOverrideBuiltIns" + ): + # Check assumptions. + if self.descriptor.supportsIndexedProperties(): + raise ValueError( + "In interface " + + self.descriptor.name + + ": " + + "Can't cope with [LegacyOverrideBuiltIns] and an indexed getter" + ) + if self.descriptor.hasLegacyUnforgeableMembers: + raise ValueError( + "In interface " + + self.descriptor.name + + ": " + + "Can't cope with [LegacyOverrideBuiltIns] and unforgeable members" + ) + + tailCode = dedent( + """ + *done = true; + return true; + """ + ) + callSetter = CGProxyNamedSetter( + self.descriptor, tailCode, argumentHandleValue="v" + ) + error_label = CGSpecializedMethod.error_reporting_label_helper( + self.descriptor, namedSetter, isConstructor=False + ) + if error_label: + cxDecl = fill( + """ + BindingCallContext cx(cx_, "${error_label}"); + """, + error_label=error_label, + ) + else: + cxDecl = dedent( + """ + JSContext* cx = cx_; + """ + ) + return fill( + """ + $*{assertion} + $*{cxDecl} + $*{callSetter} + *done = false; + return true; + """, + assertion=assertion, + cxDecl=cxDecl, + callSetter=callSetter.define(), + ) + + # As an optimization, if we are going to call an IndexedSetter, go + # ahead and call it and have done. + indexedSetter = self.descriptor.operations["IndexedSetter"] + if indexedSetter is not None: + error_label = CGSpecializedMethod.error_reporting_label_helper( + self.descriptor, indexedSetter, isConstructor=False + ) + if error_label: + cxDecl = fill( + """ + BindingCallContext cx(cx_, "${error_label}"); + """, + error_label=error_label, + ) + else: + cxDecl = dedent( + """ + JSContext* cx = cx_; + """ + ) + setIndexed = fill( + """ + uint32_t index = GetArrayIndexFromId(id); + if (IsArrayIndex(index)) { + $*{cxDecl} + $*{callSetter} + *done = true; + return true; + } + + """, + cxDecl=cxDecl, + callSetter=CGProxyIndexedSetter( + self.descriptor, argumentHandleValue="v" + ).define(), + ) + else: + setIndexed = "" + + return assertion + setIndexed + "*done = false;\n" "return true;\n" + + +class CGDOMJSProxyHandler_className(ClassMethod): + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + ] + ClassMethod.__init__( + self, + "className", + "const char*", + args, + virtual=True, + override=True, + const=True, + ) + self.descriptor = descriptor + + def getBody(self): + if self.descriptor.isMaybeCrossOriginObject(): + crossOrigin = dedent( + """ + if (!IsPlatformObjectSameOrigin(cx, proxy)) { + return "Object"; + } + + """ + ) + else: + crossOrigin = "" + return fill( + """ + $*{crossOrigin} + return "${name}"; + """, + crossOrigin=crossOrigin, + name=self.descriptor.name, + ) + + +class CGDOMJSProxyHandler_finalizeInBackground(ClassMethod): + def __init__(self, descriptor): + args = [Argument("const JS::Value&", "priv")] + ClassMethod.__init__( + self, + "finalizeInBackground", + "bool", + args, + virtual=True, + override=True, + const=True, + ) + self.descriptor = descriptor + + def getBody(self): + return "return false;\n" + + +class CGDOMJSProxyHandler_finalize(ClassMethod): + def __init__(self, descriptor): + args = [Argument("JS::GCContext*", "gcx"), Argument("JSObject*", "proxy")] + ClassMethod.__init__( + self, "finalize", "void", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + return ( + "%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(proxy);\n" + % (self.descriptor.nativeType, self.descriptor.nativeType) + ) + finalizeHook( + self.descriptor, + FINALIZE_HOOK_NAME, + self.args[0].name, + self.args[1].name, + ).define() + + +class CGDOMJSProxyHandler_objectMoved(ClassMethod): + def __init__(self, descriptor): + args = [Argument("JSObject*", "obj"), Argument("JSObject*", "old")] + ClassMethod.__init__( + self, "objectMoved", "size_t", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + return ( + "%s* self = UnwrapPossiblyNotInitializedDOMObject<%s>(obj);\n" + % (self.descriptor.nativeType, self.descriptor.nativeType) + ) + objectMovedHook( + self.descriptor, + OBJECT_MOVED_HOOK_NAME, + self.args[0].name, + self.args[1].name, + ) + + +class CGDOMJSProxyHandler_getElements(ClassMethod): + def __init__(self, descriptor): + assert descriptor.supportsIndexedProperties() + + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("uint32_t", "begin"), + Argument("uint32_t", "end"), + Argument("js::ElementAdder*", "adder"), + ] + ClassMethod.__init__( + self, "getElements", "bool", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + # Just like ownPropertyKeys we'll assume that we have no holes, so + # we have all properties from 0 to length. If that ever changes + # (unlikely), we'll need to do something a bit more clever with how we + # forward on to our ancestor. + + templateValues = { + "jsvalRef": "temp", + "jsvalHandle": "&temp", + "obj": "proxy", + "successCode": ( + "if (!adder->append(cx, temp)) return false;\n" "continue;\n" + ), + } + get = CGProxyIndexedGetter( + self.descriptor, templateValues, False, False + ).define() + + if self.descriptor.lengthNeedsCallerType(): + callerType = callerTypeGetterForDescriptor(self.descriptor) + else: + callerType = "" + + return fill( + """ + JS::Rooted<JS::Value> temp(cx); + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + ${nativeType}* self = UnwrapProxy(proxy); + uint32_t length = self->Length(${callerType}); + // Compute the end of the indices we'll get ourselves + uint32_t ourEnd = std::max(begin, std::min(end, length)); + + for (uint32_t index = begin; index < ourEnd; ++index) { + $*{get} + } + + if (end > ourEnd) { + JS::Rooted<JSObject*> proto(cx); + if (!js::GetObjectProto(cx, proxy, &proto)) { + return false; + } + return js::GetElementsWithAdder(cx, proto, proxy, ourEnd, end, adder); + } + + return true; + """, + nativeType=self.descriptor.nativeType, + callerType=callerType, + get=get, + ) + + +class CGJSProxyHandler_getInstance(ClassMethod): + def __init__(self, type): + self.type = type + ClassMethod.__init__( + self, "getInstance", "const %s*" % self.type, [], static=True + ) + + def getBody(self): + return fill( + """ + static const ${type} instance; + return &instance; + """, + type=self.type, + ) + + +class CGDOMJSProxyHandler_call(ClassMethod): + def __init__(self): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("const JS::CallArgs&", "args"), + ] + + ClassMethod.__init__( + self, "call", "bool", args, virtual=True, override=True, const=True + ) + + def getBody(self): + return fill( + """ + return js::ForwardToNative(cx, ${legacyCaller}, args); + """, + legacyCaller=LEGACYCALLER_HOOK_NAME, + ) + + +class CGDOMJSProxyHandler_isCallable(ClassMethod): + def __init__(self): + ClassMethod.__init__( + self, + "isCallable", + "bool", + [Argument("JSObject*", "obj")], + virtual=True, + override=True, + const=True, + ) + + def getBody(self): + return dedent( + """ + return true; + """ + ) + + +class CGDOMJSProxyHandler_canNurseryAllocate(ClassMethod): + """ + Override the default canNurseryAllocate in BaseProxyHandler, for cases when + we should be nursery-allocated. + """ + + def __init__(self): + ClassMethod.__init__( + self, + "canNurseryAllocate", + "bool", + [], + virtual=True, + override=True, + const=True, + ) + + def getBody(self): + return dedent( + """ + return true; + """ + ) + + +class CGDOMJSProxyHandler_getOwnPropertyDescriptor(ClassMethod): + """ + Implementation of getOwnPropertyDescriptor. We only use this for + cross-origin objects. + """ + + def __init__(self, descriptor): + assert descriptor.isMaybeCrossOriginObject() + + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::MutableHandle<Maybe<JS::PropertyDescriptor>>", "desc"), + ] + ClassMethod.__init__( + self, + "getOwnPropertyDescriptor", + "bool", + args, + virtual=True, + override=True, + const=True, + ) + self.descriptor = descriptor + + def getBody(self): + return dedent( + """ + // Implementation of <https://html.spec.whatwg.org/multipage/history.html#location-getownproperty>. + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy)); + + // Step 1. + if (IsPlatformObjectSameOrigin(cx, proxy)) { + { // Scope so we can wrap our PropertyDescriptor back into + // the caller compartment. + // Enter the Realm of "proxy" so we can work with it. + JSAutoRealm ar(cx, proxy); + + JS_MarkCrossZoneId(cx, id); + + // The spec messes around with configurability of the returned + // descriptor here, but it's not clear what should actually happen + // here. See <https://github.com/whatwg/html/issues/4157>. For + // now, keep our old behavior and don't do any magic. + if (!dom::DOMProxyHandler::getOwnPropertyDescriptor(cx, proxy, id, desc)) { + return false; + } + } + return JS_WrapPropertyDescriptor(cx, desc); + } + + // Step 2. + if (!CrossOriginGetOwnPropertyHelper(cx, proxy, id, desc)) { + return false; + } + + // Step 3. + if (desc.isSome()) { + return true; + } + + // And step 4. + return CrossOriginPropertyFallback(cx, proxy, id, desc); + """ + ) + + +class CGDOMJSProxyHandler_getSameOriginPrototype(ClassMethod): + """ + Implementation of getSameOriginPrototype. We only use this for + cross-origin objects. + """ + + def __init__(self, descriptor): + assert descriptor.isMaybeCrossOriginObject() + + args = [Argument("JSContext*", "cx")] + ClassMethod.__init__( + self, + "getSameOriginPrototype", + "JSObject*", + args, + virtual=True, + override=True, + const=True, + ) + self.descriptor = descriptor + + def getBody(self): + return dedent( + """ + return GetProtoObjectHandle(cx); + """ + ) + + +class CGDOMJSProxyHandler_definePropertySameOrigin(ClassMethod): + """ + Implementation of definePropertySameOrigin. We only use this for + cross-origin objects. + """ + + def __init__(self, descriptor): + assert descriptor.isMaybeCrossOriginObject() + + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::Handle<JS::PropertyDescriptor>", "desc"), + Argument("JS::ObjectOpResult&", "result"), + ] + ClassMethod.__init__( + self, + "definePropertySameOrigin", + "bool", + args, + virtual=True, + override=True, + const=True, + ) + self.descriptor = descriptor + + def getBody(self): + return dedent( + """ + return dom::DOMProxyHandler::defineProperty(cx, proxy, id, desc, result); + """ + ) + + +class CGDOMJSProxyHandler_set(ClassMethod): + """ + Implementation of set(). We only use this for cross-origin objects. + """ + + def __init__(self, descriptor): + assert descriptor.isMaybeCrossOriginObject() + + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::Handle<jsid>", "id"), + Argument("JS::Handle<JS::Value>", "v"), + Argument("JS::Handle<JS::Value>", "receiver"), + Argument("JS::ObjectOpResult&", "result"), + ] + ClassMethod.__init__( + self, "set", "bool", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + return dedent( + """ + if (!IsPlatformObjectSameOrigin(cx, proxy)) { + return CrossOriginSet(cx, proxy, id, v, receiver, result); + } + + // Safe to enter the Realm of proxy now, since it's same-origin with us. + JSAutoRealm ar(cx, proxy); + JS::Rooted<JS::Value> wrappedReceiver(cx, receiver); + if (!MaybeWrapValue(cx, &wrappedReceiver)) { + return false; + } + + JS::Rooted<JS::Value> wrappedValue(cx, v); + if (!MaybeWrapValue(cx, &wrappedValue)) { + return false; + } + + JS_MarkCrossZoneId(cx, id); + + return dom::DOMProxyHandler::set(cx, proxy, id, wrappedValue, wrappedReceiver, result); + """ + ) + + +class CGDOMJSProxyHandler_EnsureHolder(ClassMethod): + """ + Implementation of EnsureHolder(). We only use this for cross-origin objects. + """ + + def __init__(self, descriptor): + args = [ + Argument("JSContext*", "cx"), + Argument("JS::Handle<JSObject*>", "proxy"), + Argument("JS::MutableHandle<JSObject*>", "holder"), + ] + ClassMethod.__init__( + self, "EnsureHolder", "bool", args, virtual=True, override=True, const=True + ) + self.descriptor = descriptor + + def getBody(self): + return dedent( + """ + return EnsureHolder(cx, proxy, + JSCLASS_RESERVED_SLOTS(JS::GetClass(proxy)) - 1, + sCrossOriginProperties, holder); + """ + ) + + +class CGDOMJSProxyHandler(CGClass): + def __init__(self, descriptor): + assert ( + descriptor.supportsIndexedProperties() + or descriptor.supportsNamedProperties() + or descriptor.isMaybeCrossOriginObject() + ) + methods = [ + CGDOMJSProxyHandler_getOwnPropDescriptor(descriptor), + CGDOMJSProxyHandler_defineProperty(descriptor), + ClassUsingDeclaration("mozilla::dom::DOMProxyHandler", "defineProperty"), + CGDOMJSProxyHandler_ownPropNames(descriptor), + CGDOMJSProxyHandler_hasOwn(descriptor), + CGDOMJSProxyHandler_get(descriptor), + CGDOMJSProxyHandler_className(descriptor), + CGDOMJSProxyHandler_finalizeInBackground(descriptor), + CGDOMJSProxyHandler_finalize(descriptor), + CGJSProxyHandler_getInstance("DOMProxyHandler"), + CGDOMJSProxyHandler_delete(descriptor), + ] + constructors = [ + ClassConstructor([], constexpr=True, visibility="public", explicit=True) + ] + + if descriptor.supportsIndexedProperties(): + methods.append(CGDOMJSProxyHandler_getElements(descriptor)) + if descriptor.operations["IndexedSetter"] is not None or ( + descriptor.operations["NamedSetter"] is not None + and descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns") + ): + methods.append(CGDOMJSProxyHandler_setCustom(descriptor)) + if descriptor.operations["LegacyCaller"]: + methods.append(CGDOMJSProxyHandler_call()) + methods.append(CGDOMJSProxyHandler_isCallable()) + if descriptor.interface.hasProbablyShortLivingWrapper(): + if not descriptor.wrapperCache: + raise TypeError( + "Need a wrapper cache to support nursery " + "allocation of DOM objects" + ) + methods.append(CGDOMJSProxyHandler_canNurseryAllocate()) + if descriptor.wrapperCache: + methods.append(CGDOMJSProxyHandler_objectMoved(descriptor)) + + if descriptor.isMaybeCrossOriginObject(): + methods.extend( + [ + CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor), + CGDOMJSProxyHandler_getSameOriginPrototype(descriptor), + CGDOMJSProxyHandler_definePropertySameOrigin(descriptor), + CGDOMJSProxyHandler_set(descriptor), + CGDOMJSProxyHandler_EnsureHolder(descriptor), + ClassUsingDeclaration( + "MaybeCrossOriginObjectMixins", "EnsureHolder" + ), + ] + ) + + if descriptor.interface.getExtendedAttribute("LegacyOverrideBuiltIns"): + assert not descriptor.isMaybeCrossOriginObject() + parentClass = "ShadowingDOMProxyHandler" + elif descriptor.isMaybeCrossOriginObject(): + parentClass = "MaybeCrossOriginObject<mozilla::dom::DOMProxyHandler>" + else: + parentClass = "mozilla::dom::DOMProxyHandler" + + CGClass.__init__( + self, + "DOMProxyHandler", + bases=[ClassBase(parentClass)], + constructors=constructors, + methods=methods, + ) + + +class CGDOMJSProxyHandlerDeclarer(CGThing): + """ + A class for declaring a DOMProxyHandler. + """ + + def __init__(self, handlerThing): + self.handlerThing = handlerThing + + def declare(self): + # Our class declaration should happen when we're defining + return "" + + def define(self): + return self.handlerThing.declare() + + +class CGDOMJSProxyHandlerDefiner(CGThing): + """ + A class for defining a DOMProxyHandler. + """ + + def __init__(self, handlerThing): + self.handlerThing = handlerThing + + def declare(self): + return "" + + def define(self): + return self.handlerThing.define() + + +def stripTrailingWhitespace(text): + tail = "\n" if text.endswith("\n") else "" + lines = text.splitlines() + return "\n".join(line.rstrip() for line in lines) + tail + + +class MemberProperties: + def __init__(self): + self.isCrossOriginMethod = False + self.isCrossOriginGetter = False + self.isCrossOriginSetter = False + + +def memberProperties(m, descriptor): + props = MemberProperties() + if m.isMethod(): + if not m.isIdentifierLess() or m == descriptor.operations["Stringifier"]: + if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject(): + if m.getExtendedAttribute("CrossOriginCallable"): + props.isCrossOriginMethod = True + elif m.isAttr(): + if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject(): + if m.getExtendedAttribute("CrossOriginReadable"): + props.isCrossOriginGetter = True + if not m.readonly: + if not m.isStatic() and descriptor.interface.hasInterfacePrototypeObject(): + if m.getExtendedAttribute("CrossOriginWritable"): + props.isCrossOriginSetter = True + elif m.getExtendedAttribute("PutForwards"): + if m.getExtendedAttribute("CrossOriginWritable"): + props.isCrossOriginSetter = True + elif m.getExtendedAttribute("Replaceable") or m.getExtendedAttribute( + "LegacyLenientSetter" + ): + if m.getExtendedAttribute("CrossOriginWritable"): + props.isCrossOriginSetter = True + + return props + + +class CGDescriptor(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + + assert ( + not descriptor.concrete + or descriptor.interface.hasInterfacePrototypeObject() + ) + + self._deps = descriptor.interface.getDeps() + + iteratorCGThings = None + if ( + descriptor.interface.isIterable() + and descriptor.interface.maplikeOrSetlikeOrIterable.isPairIterator() + ) or descriptor.interface.isAsyncIterable(): + # We need the Wrap function when using the [Async]IterableIterator type, so we want to declare it before we need it. We don't really want to expose it in the header file, so we make it static too. + iteratorCGThings = [] + itr_iface = ( + descriptor.interface.maplikeOrSetlikeOrIterable.iteratorType.inner + ) + iteratorDescriptor = descriptor.getDescriptor(itr_iface.identifier.name) + iteratorCGThings.append( + CGWrapNonWrapperCacheMethod( + iteratorDescriptor, static=True, signatureOnly=True + ) + ) + iteratorCGThings = CGList( + (CGIndenter(t, declareOnly=True) for t in iteratorCGThings), "\n" + ) + iteratorCGThings = CGWrapper(iteratorCGThings, pre="\n", post="\n") + iteratorCGThings = CGWrapper( + CGNamespace( + toBindingNamespace(iteratorDescriptor.name), iteratorCGThings + ), + post="\n", + ) + + cgThings = [] + + isIteratorInterface = ( + descriptor.interface.isIteratorInterface() + or descriptor.interface.isAsyncIteratorInterface() + ) + if not isIteratorInterface: + cgThings.append( + CGGeneric(declare="typedef %s NativeType;\n" % descriptor.nativeType) + ) + parent = descriptor.interface.parent + if parent: + cgThings.append( + CGGeneric( + "static_assert(IsRefcounted<NativeType>::value == IsRefcounted<%s::NativeType>::value,\n" + ' "Can\'t inherit from an interface with a different ownership model.");\n' + % toBindingNamespace(descriptor.parentPrototypeName) + ) + ) + + defaultToJSONMethod = None + needCrossOriginPropertyArrays = False + unscopableNames = list() + for n in descriptor.interface.legacyFactoryFunctions: + cgThings.append( + CGClassConstructor(descriptor, n, LegacyFactoryFunctionName(n)) + ) + for m in descriptor.interface.members: + if m.isMethod() and m.identifier.name == "QueryInterface": + continue + + props = memberProperties(m, descriptor) + + if m.isMethod(): + if m.getExtendedAttribute("Unscopable"): + assert not m.isStatic() + unscopableNames.append(m.identifier.name) + if m.isDefaultToJSON(): + defaultToJSONMethod = m + elif ( + not m.isIdentifierLess() + or m == descriptor.operations["Stringifier"] + ): + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject() + cgThings.append(CGStaticMethod(descriptor, m)) + if m.returnsPromise(): + cgThings.append(CGStaticMethodJitinfo(m)) + elif descriptor.interface.hasInterfacePrototypeObject(): + specializedMethod = CGSpecializedMethod(descriptor, m) + cgThings.append(specializedMethod) + if m.returnsPromise(): + cgThings.append( + CGMethodPromiseWrapper(descriptor, specializedMethod) + ) + cgThings.append(CGMemberJITInfo(descriptor, m)) + if props.isCrossOriginMethod: + needCrossOriginPropertyArrays = True + # If we've hit the maplike/setlike member itself, go ahead and + # generate its convenience functions. + elif m.isMaplikeOrSetlike(): + cgThings.append(CGMaplikeOrSetlikeHelperGenerator(descriptor, m)) + elif m.isAttr(): + if m.type.isObservableArray(): + cgThings.append( + CGObservableArrayProxyHandlerGenerator(descriptor, m) + ) + cgThings.append(CGObservableArrayHelperGenerator(descriptor, m)) + if m.getExtendedAttribute("Unscopable"): + assert not m.isStatic() + unscopableNames.append(m.identifier.name) + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject() + cgThings.append(CGStaticGetter(descriptor, m)) + elif descriptor.interface.hasInterfacePrototypeObject(): + specializedGetter = CGSpecializedGetter(descriptor, m) + cgThings.append(specializedGetter) + if m.type.isPromise(): + cgThings.append( + CGGetterPromiseWrapper(descriptor, specializedGetter) + ) + if props.isCrossOriginGetter: + needCrossOriginPropertyArrays = True + if not m.readonly: + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject() + cgThings.append(CGStaticSetter(descriptor, m)) + elif descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGSpecializedSetter(descriptor, m)) + if props.isCrossOriginSetter: + needCrossOriginPropertyArrays = True + elif m.getExtendedAttribute("PutForwards"): + cgThings.append(CGSpecializedForwardingSetter(descriptor, m)) + if props.isCrossOriginSetter: + needCrossOriginPropertyArrays = True + elif m.getExtendedAttribute("Replaceable"): + cgThings.append(CGSpecializedReplaceableSetter(descriptor, m)) + elif m.getExtendedAttribute("LegacyLenientSetter"): + # XXX In this case, we need to add an include for mozilla/dom/Document.h to the generated cpp file. + cgThings.append(CGSpecializedLenientSetter(descriptor, m)) + if ( + not m.isStatic() + and descriptor.interface.hasInterfacePrototypeObject() + ): + cgThings.append(CGMemberJITInfo(descriptor, m)) + if m.isConst() and m.type.isPrimitive(): + cgThings.append(CGConstDefinition(m)) + + if defaultToJSONMethod: + cgThings.append(CGDefaultToJSONMethod(descriptor, defaultToJSONMethod)) + cgThings.append(CGMemberJITInfo(descriptor, defaultToJSONMethod)) + + if descriptor.concrete and not descriptor.proxy: + if wantsAddProperty(descriptor): + cgThings.append(CGAddPropertyHook(descriptor)) + + # Always have a finalize hook, regardless of whether the class + # wants a custom hook. + cgThings.append(CGClassFinalizeHook(descriptor)) + + if wantsGetWrapperCache(descriptor): + cgThings.append(CGGetWrapperCacheHook(descriptor)) + + if descriptor.concrete and descriptor.wrapperCache and not descriptor.proxy: + cgThings.append(CGClassObjectMovedHook(descriptor)) + + properties = PropertyArrays(descriptor) + cgThings.append(CGGeneric(define=str(properties))) + cgThings.append(CGNativeProperties(descriptor, properties)) + + if defaultToJSONMethod: + # Now that we know about our property arrays, we can + # output our "collect attribute values" method, which uses those. + cgThings.append( + CGCollectJSONAttributesMethod(descriptor, defaultToJSONMethod) + ) + + # Declare our DOMProxyHandler. + if descriptor.concrete and descriptor.proxy: + cgThings.append( + CGGeneric( + fill( + """ + static_assert(std::is_base_of_v<nsISupports, ${nativeType}>, + "We don't support non-nsISupports native classes for " + "proxy-based bindings yet"); + + """, + nativeType=descriptor.nativeType, + ) + ) + ) + if not descriptor.wrapperCache: + raise TypeError( + "We need a wrappercache to support expandos for proxy-based " + "bindings (" + descriptor.name + ")" + ) + handlerThing = CGDOMJSProxyHandler(descriptor) + cgThings.append(CGDOMJSProxyHandlerDeclarer(handlerThing)) + cgThings.append(CGProxyIsProxy(descriptor)) + cgThings.append(CGProxyUnwrap(descriptor)) + + # Set up our Xray callbacks as needed. This needs to come + # after we have our DOMProxyHandler defined. + if descriptor.wantsXrays: + if descriptor.concrete and descriptor.proxy: + if descriptor.needsXrayNamedDeleterHook(): + cgThings.append(CGDeleteNamedProperty(descriptor)) + elif descriptor.needsXrayResolveHooks(): + cgThings.append(CGResolveOwnPropertyViaResolve(descriptor)) + cgThings.append( + CGEnumerateOwnPropertiesViaGetOwnPropertyNames(descriptor) + ) + if descriptor.wantsXrayExpandoClass: + cgThings.append(CGXrayExpandoJSClass(descriptor)) + + # Now that we have our ResolveOwnProperty/EnumerateOwnProperties stuff + # done, set up our NativePropertyHooks. + cgThings.append(CGNativePropertyHooks(descriptor, properties)) + + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGClassConstructor(descriptor, descriptor.interface.ctor())) + cgThings.append(CGInterfaceObjectJSClass(descriptor, properties)) + cgThings.append(CGLegacyFactoryFunctions(descriptor)) + + cgThings.append(CGLegacyCallHook(descriptor)) + if descriptor.interface.getExtendedAttribute("NeedResolve"): + cgThings.append(CGResolveHook(descriptor)) + cgThings.append(CGMayResolveHook(descriptor)) + cgThings.append(CGEnumerateHook(descriptor)) + + if descriptor.hasNamedPropertiesObject: + cgThings.append(CGGetNamedPropertiesObjectMethod(descriptor)) + + if descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGPrototypeJSClass(descriptor, properties)) + + if ( + descriptor.interface.hasInterfaceObject() + and not descriptor.interface.isExternal() + and descriptor.isExposedConditionally() + ): + cgThings.append(CGConstructorEnabled(descriptor)) + + if ( + descriptor.interface.hasMembersInSlots() + and descriptor.interface.hasChildInterfaces() + ): + raise TypeError( + "We don't support members in slots on " + "non-leaf interfaces like %s" % descriptor.interface.identifier.name + ) + + if descriptor.needsMissingPropUseCounters: + cgThings.append(CGCountMaybeMissingProperty(descriptor)) + + if descriptor.concrete: + if descriptor.interface.isSerializable(): + cgThings.append(CGSerializer(descriptor)) + cgThings.append(CGDeserializer(descriptor)) + + # CGDOMProxyJSClass/CGDOMJSClass need GetProtoObjectHandle, but we don't want to export it for the iterator interfaces, so declare it here. + if isIteratorInterface: + cgThings.append( + CGGetProtoObjectHandleMethod( + descriptor, static=True, signatureOnly=True + ) + ) + + if descriptor.proxy: + cgThings.append(CGDOMJSProxyHandlerDefiner(handlerThing)) + cgThings.append(CGDOMProxyJSClass(descriptor)) + else: + cgThings.append(CGDOMJSClass(descriptor)) + + if descriptor.interface.hasMembersInSlots(): + cgThings.append(CGUpdateMemberSlotsMethod(descriptor)) + + if descriptor.isGlobal(): + assert descriptor.wrapperCache + cgThings.append(CGWrapGlobalMethod(descriptor, properties)) + elif descriptor.wrapperCache: + cgThings.append(CGWrapWithCacheMethod(descriptor)) + cgThings.append(CGWrapMethod(descriptor)) + else: + cgThings.append( + CGWrapNonWrapperCacheMethod(descriptor, static=isIteratorInterface) + ) + + # If we're not wrappercached, we don't know how to clear our + # cached values, since we can't get at the JSObject. + if descriptor.wrapperCache: + cgThings.extend( + CGClearCachedValueMethod(descriptor, m) + for m in clearableCachedAttrs(descriptor) + ) + + haveUnscopables = ( + len(unscopableNames) != 0 + and descriptor.interface.hasInterfacePrototypeObject() + ) + if haveUnscopables: + cgThings.append( + CGList( + [ + CGGeneric("static const char* const unscopableNames[] = {"), + CGIndenter( + CGList( + [CGGeneric('"%s"' % name) for name in unscopableNames] + + [CGGeneric("nullptr")], + ",\n", + ) + ), + CGGeneric("};\n"), + ], + "\n", + ) + ) + + legacyWindowAliases = descriptor.interface.legacyWindowAliases + haveLegacyWindowAliases = len(legacyWindowAliases) != 0 + if haveLegacyWindowAliases: + cgThings.append( + CGList( + [ + CGGeneric("static const char* const legacyWindowAliases[] = {"), + CGIndenter( + CGList( + [ + CGGeneric('"%s"' % name) + for name in legacyWindowAliases + ] + + [CGGeneric("nullptr")], + ",\n", + ) + ), + CGGeneric("};\n"), + ], + "\n", + ) + ) + + # CGCreateInterfaceObjectsMethod needs to come after our + # CGDOMJSClass and unscopables, if any. + cgThings.append( + CGCreateInterfaceObjectsMethod( + descriptor, + properties, + haveUnscopables, + haveLegacyWindowAliases, + static=isIteratorInterface, + ) + ) + + # CGGetProtoObjectMethod and CGGetConstructorObjectMethod need + # to come after CGCreateInterfaceObjectsMethod. + if ( + descriptor.interface.hasInterfacePrototypeObject() + and not descriptor.hasOrdinaryObjectPrototype + ): + cgThings.append( + CGGetProtoObjectHandleMethod(descriptor, static=isIteratorInterface) + ) + if descriptor.interface.hasChildInterfaces(): + assert not isIteratorInterface + cgThings.append(CGGetProtoObjectMethod(descriptor)) + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGGetConstructorObjectHandleMethod(descriptor)) + cgThings.append(CGGetConstructorObjectMethod(descriptor)) + + # See whether we need to generate cross-origin property arrays. + if needCrossOriginPropertyArrays: + cgThings.append(CGCrossOriginProperties(descriptor)) + + cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n") + cgThings = CGWrapper(cgThings, pre="\n", post="\n") + cgThings = CGWrapper( + CGNamespace(toBindingNamespace(descriptor.name), cgThings), post="\n" + ) + self.cgRoot = CGList([iteratorCGThings, cgThings], "\n") + + def declare(self): + return self.cgRoot.declare() + + def define(self): + return self.cgRoot.define() + + def deps(self): + return self._deps + + +class CGNamespacedEnum(CGThing): + def __init__(self, namespace, enumName, names, values, comment=""): + + if not values: + values = [] + + # Account for explicit enum values. + entries = [] + for i in range(0, len(names)): + if len(values) > i and values[i] is not None: + entry = "%s = %s" % (names[i], values[i]) + else: + entry = names[i] + entries.append(entry) + + # Append a Count. + entries.append("_" + enumName + "_Count") + + # Indent. + entries = [" " + e for e in entries] + + # Build the enum body. + enumstr = comment + "enum %s : uint16_t\n{\n%s\n};\n" % ( + enumName, + ",\n".join(entries), + ) + curr = CGGeneric(declare=enumstr) + + # Add some whitespace padding. + curr = CGWrapper(curr, pre="\n", post="\n") + + # Add the namespace. + curr = CGNamespace(namespace, curr) + + # Add the typedef + typedef = "\ntypedef %s::%s %s;\n\n" % (namespace, enumName, enumName) + curr = CGList([curr, CGGeneric(declare=typedef)]) + + # Save the result. + self.node = curr + + def declare(self): + return self.node.declare() + + def define(self): + return "" + + +def initIdsClassMethod(identifiers, atomCacheName): + idinit = [ + '!atomsCache->%s.init(cx, "%s")' % (CGDictionary.makeIdName(id), id) + for id in identifiers + ] + idinit.reverse() + body = fill( + """ + MOZ_ASSERT(reinterpret_cast<jsid*>(atomsCache)->isVoid()); + + // Initialize these in reverse order so that any failure leaves the first one + // uninitialized. + if (${idinit}) { + return false; + } + return true; + """, + idinit=" ||\n ".join(idinit), + ) + return ClassMethod( + "InitIds", + "bool", + [Argument("JSContext*", "cx"), Argument("%s*" % atomCacheName, "atomsCache")], + static=True, + body=body, + visibility="private", + ) + + +class CGDictionary(CGThing): + def __init__(self, dictionary, descriptorProvider): + self.dictionary = dictionary + self.descriptorProvider = descriptorProvider + self.needToInitIds = len(dictionary.members) > 0 + self.memberInfo = [ + ( + member, + getJSToNativeConversionInfo( + member.type, + descriptorProvider, + isMember="Dictionary", + isOptional=member.canHaveMissingValue(), + isKnownMissing=not dictionary.needsConversionFromJS, + defaultValue=member.defaultValue, + sourceDescription=self.getMemberSourceDescription(member), + ), + ) + for member in dictionary.members + ] + + # If we have a union member which is going to be declared in a different + # header but contains something that will be declared in the same header + # as us, bail: the C++ includes won't work out. + for member in dictionary.members: + type = member.type.unroll() + if type.isUnion() and CGHeaders.getUnionDeclarationFilename( + descriptorProvider.getConfig(), type + ) != CGHeaders.getDeclarationFilename(dictionary): + for t in type.flatMemberTypes: + if t.isDictionary() and CGHeaders.getDeclarationFilename( + t.inner + ) == CGHeaders.getDeclarationFilename(dictionary): + raise TypeError( + "Dictionary contains a union that will live in a different " + "header that contains a dictionary from the same header as " + "the original dictionary. This won't compile. Move the " + "inner dictionary to a different Web IDL file to move it " + "to a different header.\n%s\n%s" + % (t.location, t.inner.location) + ) + self.structs = self.getStructs() + + def declare(self): + return self.structs.declare() + + def define(self): + return self.structs.define() + + def base(self): + if self.dictionary.parent: + return self.makeClassName(self.dictionary.parent) + return "DictionaryBase" + + def initMethod(self): + """ + This function outputs the body of the Init() method for the dictionary. + + For the most part, this is some bookkeeping for our atoms so + we can avoid atomizing strings all the time, then we just spit + out the getMemberConversion() output for each member, + separated by newlines. + + """ + body = dedent( + """ + // Passing a null JSContext is OK only if we're initing from null, + // Since in that case we will not have to do any property gets + // Also evaluate isNullOrUndefined in order to avoid false-positive + // checkers by static analysis tools + MOZ_ASSERT_IF(!cx, val.isNull() && val.isNullOrUndefined()); + """ + ) + + if self.needToInitIds: + body += fill( + """ + ${dictName}Atoms* atomsCache = nullptr; + if (cx) { + atomsCache = GetAtomCache<${dictName}Atoms>(cx); + if (reinterpret_cast<jsid*>(atomsCache)->isVoid() && + !InitIds(cx, atomsCache)) { + return false; + } + } + + """, + dictName=self.makeClassName(self.dictionary), + ) + + if self.dictionary.parent: + body += fill( + """ + // Per spec, we init the parent's members first + if (!${dictName}::Init(cx, val)) { + return false; + } + + """, + dictName=self.makeClassName(self.dictionary.parent), + ) + else: + body += dedent( + """ + if (!IsConvertibleToDictionary(val)) { + return cx.ThrowErrorMessage<MSG_CONVERSION_ERROR>(sourceDescription, "dictionary"); + } + + """ + ) + + memberInits = [self.getMemberConversion(m).define() for m in self.memberInfo] + if memberInits: + body += fill( + """ + bool isNull = val.isNullOrUndefined(); + // We only need these if !isNull, in which case we have |cx|. + Maybe<JS::Rooted<JSObject *> > object; + Maybe<JS::Rooted<JS::Value> > temp; + if (!isNull) { + MOZ_ASSERT(cx); + object.emplace(cx, &val.toObject()); + temp.emplace(cx); + } + $*{memberInits} + """, + memberInits="\n".join(memberInits), + ) + + body += "return true;\n" + + return ClassMethod( + "Init", + "bool", + [ + Argument("BindingCallContext&", "cx"), + Argument("JS::Handle<JS::Value>", "val"), + Argument("const char*", "sourceDescription", default='"Value"'), + Argument("bool", "passedToJSImpl", default="false"), + ], + body=body, + ) + + def initWithoutCallContextMethod(self): + """ + This function outputs the body of an Init() method for the dictionary + that takes just a JSContext*. This is needed for non-binding consumers. + """ + body = dedent( + """ + // We don't want to use sourceDescription for our context here; + // that's not really what it's formatted for. + BindingCallContext cx(cx_, nullptr); + return Init(cx, val, sourceDescription, passedToJSImpl); + """ + ) + return ClassMethod( + "Init", + "bool", + [ + Argument("JSContext*", "cx_"), + Argument("JS::Handle<JS::Value>", "val"), + Argument("const char*", "sourceDescription", default='"Value"'), + Argument("bool", "passedToJSImpl", default="false"), + ], + body=body, + ) + + def simpleInitMethod(self): + """ + This function outputs the body of the Init() method for the dictionary, + for cases when we are just default-initializing it. + + """ + relevantMembers = [ + m + for m in self.memberInfo + # We only need to init the things that can have + # default values. + if m[0].optional and m[0].defaultValue + ] + + # We mostly avoid outputting code that uses cx in our native-to-JS + # conversions, but there is one exception: we may have a + # dictionary-typed member that _does_ generally support conversion from + # JS. If we have such a thing, we can pass it a null JSContext and + # JS::NullHandleValue to default-initialize it, but since the + # native-to-JS templates hardcode `cx` as the JSContext value, we're + # going to need to provide that. + haveMemberThatNeedsCx = any( + m[0].type.isDictionary() and m[0].type.unroll().inner.needsConversionFromJS + for m in relevantMembers + ) + if haveMemberThatNeedsCx: + body = dedent( + """ + JSContext* cx = nullptr; + """ + ) + else: + body = "" + + if self.dictionary.parent: + if self.dictionary.parent.needsConversionFromJS: + args = "nullptr, JS::NullHandleValue" + else: + args = "" + body += fill( + """ + // We init the parent's members first + if (!${dictName}::Init(${args})) { + return false; + } + + """, + dictName=self.makeClassName(self.dictionary.parent), + args=args, + ) + + memberInits = [ + self.getMemberConversion(m, isKnownMissing=True).define() + for m in relevantMembers + ] + if memberInits: + body += fill( + """ + $*{memberInits} + """, + memberInits="\n".join(memberInits), + ) + + body += "return true;\n" + + return ClassMethod( + "Init", + "bool", + [ + Argument("const char*", "sourceDescription", default='"Value"'), + Argument("bool", "passedToJSImpl", default="false"), + ], + body=body, + ) + + def initFromJSONMethod(self): + return ClassMethod( + "Init", + "bool", + [Argument("const nsAString&", "aJSON")], + body=dedent( + """ + AutoJSAPI jsapi; + JSObject* cleanGlobal = SimpleGlobalObject::Create(SimpleGlobalObject::GlobalType::BindingDetail); + if (!cleanGlobal) { + return false; + } + if (!jsapi.Init(cleanGlobal)) { + return false; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> json(cx); + bool ok = ParseJSON(cx, aJSON, &json); + NS_ENSURE_TRUE(ok, false); + return Init(cx, json); + """ + ), + ) + + def toJSONMethod(self): + return ClassMethod( + "ToJSON", + "bool", + [Argument("nsAString&", "aJSON")], + body=dedent( + """ + AutoJSAPI jsapi; + jsapi.Init(); + JSContext *cx = jsapi.cx(); + // It's safe to use UnprivilegedJunkScopeOrWorkerGlobal here + // because we'll only be creating objects, in ways that have no + // side-effects, followed by a call to JS::ToJSONMaybeSafely, + // which likewise guarantees no side-effects for the sorts of + // things we will pass it. + JSObject* scope = UnprivilegedJunkScopeOrWorkerGlobal(fallible); + if (!scope) { + JS_ReportOutOfMemory(cx); + return false; + } + JSAutoRealm ar(cx, scope); + JS::Rooted<JS::Value> val(cx); + if (!ToObjectInternal(cx, &val)) { + return false; + } + JS::Rooted<JSObject*> obj(cx, &val.toObject()); + return StringifyToJSON(cx, obj, aJSON); + """ + ), + const=True, + ) + + def toObjectInternalMethod(self): + body = "" + if self.needToInitIds: + body += fill( + """ + ${dictName}Atoms* atomsCache = GetAtomCache<${dictName}Atoms>(cx); + if (reinterpret_cast<jsid*>(atomsCache)->isVoid() && + !InitIds(cx, atomsCache)) { + return false; + } + + """, + dictName=self.makeClassName(self.dictionary), + ) + + if self.dictionary.parent: + body += fill( + """ + // Per spec, we define the parent's members first + if (!${dictName}::ToObjectInternal(cx, rval)) { + return false; + } + JS::Rooted<JSObject*> obj(cx, &rval.toObject()); + + """, + dictName=self.makeClassName(self.dictionary.parent), + ) + else: + body += dedent( + """ + JS::Rooted<JSObject*> obj(cx, JS_NewPlainObject(cx)); + if (!obj) { + return false; + } + rval.set(JS::ObjectValue(*obj)); + + """ + ) + + if self.memberInfo: + body += "\n".join( + self.getMemberDefinition(m).define() for m in self.memberInfo + ) + body += "\nreturn true;\n" + + return ClassMethod( + "ToObjectInternal", + "bool", + [ + Argument("JSContext*", "cx"), + Argument("JS::MutableHandle<JS::Value>", "rval"), + ], + const=True, + body=body, + ) + + def initIdsMethod(self): + assert self.needToInitIds + return initIdsClassMethod( + [m.identifier.name for m in self.dictionary.members], + "%sAtoms" % self.makeClassName(self.dictionary), + ) + + def traceDictionaryMethod(self): + body = "" + if self.dictionary.parent: + cls = self.makeClassName(self.dictionary.parent) + body += "%s::TraceDictionary(trc);\n" % cls + + memberTraces = [ + self.getMemberTrace(m) + for m in self.dictionary.members + if typeNeedsRooting(m.type) + ] + + if memberTraces: + body += "\n".join(memberTraces) + + return ClassMethod( + "TraceDictionary", + "void", + [ + Argument("JSTracer*", "trc"), + ], + body=body, + ) + + @staticmethod + def dictionaryNeedsCycleCollection(dictionary): + return any(idlTypeNeedsCycleCollection(m.type) for m in dictionary.members) or ( + dictionary.parent + and CGDictionary.dictionaryNeedsCycleCollection(dictionary.parent) + ) + + def traverseForCCMethod(self): + body = "" + if self.dictionary.parent and self.dictionaryNeedsCycleCollection( + self.dictionary.parent + ): + cls = self.makeClassName(self.dictionary.parent) + body += "%s::TraverseForCC(aCallback, aFlags);\n" % cls + + for m, _ in self.memberInfo: + if idlTypeNeedsCycleCollection(m.type): + memberName = self.makeMemberName(m.identifier.name) + body += ( + 'ImplCycleCollectionTraverse(aCallback, %s, "%s", aFlags);\n' + % (memberName, memberName) + ) + + return ClassMethod( + "TraverseForCC", + "void", + [ + Argument("nsCycleCollectionTraversalCallback&", "aCallback"), + Argument("uint32_t", "aFlags"), + ], + body=body, + # Inline so we don't pay a codesize hit unless someone actually uses + # this traverse method. + inline=True, + bodyInHeader=True, + ) + + def unlinkForCCMethod(self): + body = "" + if self.dictionary.parent and self.dictionaryNeedsCycleCollection( + self.dictionary.parent + ): + cls = self.makeClassName(self.dictionary.parent) + body += "%s::UnlinkForCC();\n" % cls + + for m, _ in self.memberInfo: + if idlTypeNeedsCycleCollection(m.type): + memberName = self.makeMemberName(m.identifier.name) + body += "ImplCycleCollectionUnlink(%s);\n" % memberName + + return ClassMethod( + "UnlinkForCC", + "void", + [], + body=body, + # Inline so we don't pay a codesize hit unless someone actually uses + # this unlink method. + inline=True, + bodyInHeader=True, + ) + + def assignmentOperator(self): + body = CGList([]) + body.append(CGGeneric("%s::operator=(aOther);\n" % self.base())) + + for m, _ in self.memberInfo: + memberName = self.makeMemberName(m.identifier.name) + if m.canHaveMissingValue(): + memberAssign = CGGeneric( + fill( + """ + ${name}.Reset(); + if (aOther.${name}.WasPassed()) { + ${name}.Construct(aOther.${name}.Value()); + } + """, + name=memberName, + ) + ) + else: + memberAssign = CGGeneric("%s = aOther.%s;\n" % (memberName, memberName)) + body.append(memberAssign) + body.append(CGGeneric("return *this;\n")) + return ClassMethod( + "operator=", + "%s&" % self.makeClassName(self.dictionary), + [Argument("const %s&" % self.makeClassName(self.dictionary), "aOther")], + body=body.define(), + ) + + def canHaveEqualsOperator(self): + return all( + m.type.isString() or m.type.isPrimitive() for (m, _) in self.memberInfo + ) + + def equalsOperator(self): + body = CGList([]) + + for m, _ in self.memberInfo: + memberName = self.makeMemberName(m.identifier.name) + memberTest = CGGeneric( + fill( + """ + if (${memberName} != aOther.${memberName}) { + return false; + } + """, + memberName=memberName, + ) + ) + body.append(memberTest) + body.append(CGGeneric("return true;\n")) + return ClassMethod( + "operator==", + "bool", + [Argument("const %s&" % self.makeClassName(self.dictionary), "aOther")], + const=True, + body=body.define(), + ) + + def getStructs(self): + d = self.dictionary + selfName = self.makeClassName(d) + members = [ + ClassMember( + self.makeMemberName(m[0].identifier.name), + self.getMemberType(m), + visibility="public", + body=self.getMemberInitializer(m), + hasIgnoreInitCheckFlag=True, + ) + for m in self.memberInfo + ] + if d.parent: + # We always want to init our parent with our non-initializing + # constructor arg, because either we're about to init ourselves (and + # hence our parent) or we don't want any init happening. + baseConstructors = [ + "%s(%s)" + % (self.makeClassName(d.parent), self.getNonInitializingCtorArg()) + ] + else: + baseConstructors = None + + if d.needsConversionFromJS: + initArgs = "nullptr, JS::NullHandleValue" + else: + initArgs = "" + ctors = [ + ClassConstructor( + [], + visibility="public", + baseConstructors=baseConstructors, + body=( + "// Safe to pass a null context if we pass a null value\n" + "Init(%s);\n" % initArgs + ), + ), + ClassConstructor( + [Argument("const FastDictionaryInitializer&", "")], + visibility="public", + baseConstructors=baseConstructors, + explicit=True, + bodyInHeader=True, + body='// Do nothing here; this is used by our "Fast" subclass\n', + ), + ] + methods = [] + + if self.needToInitIds: + methods.append(self.initIdsMethod()) + + if d.needsConversionFromJS: + methods.append(self.initMethod()) + methods.append(self.initWithoutCallContextMethod()) + else: + methods.append(self.simpleInitMethod()) + + canBeRepresentedAsJSON = self.dictionarySafeToJSONify(d) + if canBeRepresentedAsJSON and d.getExtendedAttribute("GenerateInitFromJSON"): + methods.append(self.initFromJSONMethod()) + + if d.needsConversionToJS: + methods.append(self.toObjectInternalMethod()) + + if canBeRepresentedAsJSON and d.getExtendedAttribute("GenerateToJSON"): + methods.append(self.toJSONMethod()) + + methods.append(self.traceDictionaryMethod()) + + try: + if self.dictionaryNeedsCycleCollection(d): + methods.append(self.traverseForCCMethod()) + methods.append(self.unlinkForCCMethod()) + except CycleCollectionUnsupported: + # We have some member that we don't know how to CC. Don't output + # our cycle collection overloads, so attempts to CC us will fail to + # compile instead of misbehaving. + pass + + ctors.append( + ClassConstructor( + [Argument("%s&&" % selfName, "aOther")], + default=True, + visibility="public", + baseConstructors=baseConstructors, + ) + ) + + if CGDictionary.isDictionaryCopyConstructible(d): + disallowCopyConstruction = False + # Note: gcc's -Wextra has a warning against not initializng our + # base explicitly. If we have one. Use our non-initializing base + # constructor to get around that. + ctors.append( + ClassConstructor( + [Argument("const %s&" % selfName, "aOther")], + bodyInHeader=True, + visibility="public", + baseConstructors=baseConstructors, + explicit=True, + body="*this = aOther;\n", + ) + ) + methods.append(self.assignmentOperator()) + else: + disallowCopyConstruction = True + + if self.canHaveEqualsOperator(): + methods.append(self.equalsOperator()) + + struct = CGClass( + selfName, + bases=[ClassBase(self.base())], + members=members, + constructors=ctors, + methods=methods, + isStruct=True, + disallowCopyConstruction=disallowCopyConstruction, + ) + + fastDictionaryCtor = ClassConstructor( + [], + visibility="public", + bodyInHeader=True, + baseConstructors=["%s(%s)" % (selfName, self.getNonInitializingCtorArg())], + body="// Doesn't matter what int we pass to the parent constructor\n", + ) + + fastStruct = CGClass( + "Fast" + selfName, + bases=[ClassBase(selfName)], + constructors=[fastDictionaryCtor], + isStruct=True, + ) + + return CGList([struct, CGNamespace("binding_detail", fastStruct)], "\n") + + def deps(self): + return self.dictionary.getDeps() + + @staticmethod + def makeDictionaryName(dictionary): + return dictionary.identifier.name + + def makeClassName(self, dictionary): + return self.makeDictionaryName(dictionary) + + @staticmethod + def makeMemberName(name): + return "m" + name[0].upper() + IDLToCIdentifier(name[1:]) + + def getMemberType(self, memberInfo): + _, conversionInfo = memberInfo + # We can't handle having a holderType here + assert conversionInfo.holderType is None + declType = conversionInfo.declType + if conversionInfo.dealWithOptional: + declType = CGTemplatedType("Optional", declType) + return declType.define() + + def getMemberConversion(self, memberInfo, isKnownMissing=False): + """ + A function that outputs the initialization of a single dictionary + member from the given dictionary value. + + We start with our conversionInfo, which tells us how to + convert a JS::Value to whatever type this member is. We + substiture the template from the conversionInfo with values + that point to our "temp" JS::Value and our member (which is + the C++ value we want to produce). The output is a string of + code to do the conversion. We store this string in + conversionReplacements["convert"]. + + Now we have three different ways we might use (or skip) this + string of code, depending on whether the value is required, + optional with default value, or optional without default + value. We set up a template in the 'conversion' variable for + exactly how to do this, then substitute into it from the + conversionReplacements dictionary. + """ + member, conversionInfo = memberInfo + + # We should only be initializing things with default values if + # we're always-missing. + assert not isKnownMissing or (member.optional and member.defaultValue) + + replacements = { + "declName": self.makeMemberName(member.identifier.name), + # We need a holder name for external interfaces, but + # it's scoped down to the conversion so we can just use + # anything we want. + "holderName": "holder", + "passedToJSImpl": "passedToJSImpl", + } + + if isKnownMissing: + replacements["val"] = "(JS::NullHandleValue)" + else: + replacements["val"] = "temp.ref()" + replacements["maybeMutableVal"] = "temp.ptr()" + + # We can't handle having a holderType here + assert conversionInfo.holderType is None + if conversionInfo.dealWithOptional: + replacements["declName"] = "(" + replacements["declName"] + ".Value())" + if member.defaultValue: + if isKnownMissing: + replacements["haveValue"] = "false" + else: + replacements["haveValue"] = "!isNull && !temp->isUndefined()" + + propId = self.makeIdName(member.identifier.name) + propGet = "JS_GetPropertyById(cx, *object, atomsCache->%s, temp.ptr())" % propId + + conversionReplacements = { + "prop": self.makeMemberName(member.identifier.name), + "convert": string.Template(conversionInfo.template).substitute( + replacements + ), + "propGet": propGet, + } + # The conversion code will only run where a default value or a value passed + # by the author needs to get converted, so we can remember if we have any + # members present here. + conversionReplacements["convert"] += "mIsAnyMemberPresent = true;\n" + if isKnownMissing: + conversion = "" + else: + setTempValue = CGGeneric( + dedent( + """ + if (!${propGet}) { + return false; + } + """ + ) + ) + conditions = getConditionList(member, "cx", "*object") + if len(conditions) != 0: + setTempValue = CGIfElseWrapper( + conditions.define(), + setTempValue, + CGGeneric("temp->setUndefined();\n"), + ) + setTempValue = CGIfWrapper(setTempValue, "!isNull") + conversion = setTempValue.define() + + if member.defaultValue: + if member.type.isUnion() and ( + not member.type.nullable() + or not isinstance(member.defaultValue, IDLNullValue) + ): + # Since this has a default value, it might have been initialized + # already. Go ahead and uninit it before we try to init it + # again. + memberName = self.makeMemberName(member.identifier.name) + if member.type.nullable(): + conversion += fill( + """ + if (!${memberName}.IsNull()) { + ${memberName}.Value().Uninit(); + } + """, + memberName=memberName, + ) + else: + conversion += "%s.Uninit();\n" % memberName + conversion += "${convert}" + elif not conversionInfo.dealWithOptional: + # We're required, but have no default value. Make sure + # that we throw if we have no value provided. + conversion += dedent( + """ + if (!isNull && !temp->isUndefined()) { + ${convert} + } else if (cx) { + // Don't error out if we have no cx. In that + // situation the caller is default-constructing us and we'll + // just assume they know what they're doing. + return cx.ThrowErrorMessage<MSG_MISSING_REQUIRED_DICTIONARY_MEMBER>("%s"); + } + """ + % self.getMemberSourceDescription(member) + ) + conversionReplacements["convert"] = indent( + conversionReplacements["convert"] + ).rstrip() + else: + conversion += ( + "if (!isNull && !temp->isUndefined()) {\n" + " ${prop}.Construct();\n" + "${convert}" + "}\n" + ) + conversionReplacements["convert"] = indent( + conversionReplacements["convert"] + ) + + return CGGeneric(string.Template(conversion).substitute(conversionReplacements)) + + def getMemberDefinition(self, memberInfo): + member = memberInfo[0] + declType = memberInfo[1].declType + memberLoc = self.makeMemberName(member.identifier.name) + if not member.canHaveMissingValue(): + memberData = memberLoc + else: + # The data is inside the Optional<> + memberData = "%s.InternalValue()" % memberLoc + + # If you have to change this list (which you shouldn't!), make sure it + # continues to match the list in test_Object.prototype_props.html + if member.identifier.name in [ + "constructor", + "toString", + "toLocaleString", + "valueOf", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "__defineGetter__", + "__defineSetter__", + "__lookupGetter__", + "__lookupSetter__", + "__proto__", + ]: + raise TypeError( + "'%s' member of %s dictionary shadows " + "a property of Object.prototype, and Xrays to " + "Object can't handle that.\n" + "%s" + % ( + member.identifier.name, + self.dictionary.identifier.name, + member.location, + ) + ) + + propDef = ( + "JS_DefinePropertyById(cx, obj, atomsCache->%s, temp, JSPROP_ENUMERATE)" + % self.makeIdName(member.identifier.name) + ) + + innerTemplate = wrapForType( + member.type, + self.descriptorProvider, + { + "result": "currentValue", + "successCode": ( + "if (!%s) {\n" " return false;\n" "}\n" "break;\n" % propDef + ), + "jsvalRef": "temp", + "jsvalHandle": "&temp", + "returnsNewObject": False, + # 'obj' can just be allowed to be the string "obj", since that + # will be our dictionary object, which is presumably itself in + # the right scope. + "spiderMonkeyInterfacesAreStructs": True, + }, + ) + conversion = CGGeneric(innerTemplate) + conversion = CGWrapper( + conversion, + pre=( + "JS::Rooted<JS::Value> temp(cx);\n" + "%s const & currentValue = %s;\n" % (declType.define(), memberData) + ), + ) + + # Now make sure that our successCode can actually break out of the + # conversion. This incidentally gives us a scope for 'temp' and + # 'currentValue'. + conversion = CGWrapper( + CGIndenter(conversion), + pre=( + "do {\n" + " // block for our 'break' successCode and scope for 'temp' and 'currentValue'\n" + ), + post="} while(false);\n", + ) + if member.canHaveMissingValue(): + # Only do the conversion if we have a value + conversion = CGIfWrapper(conversion, "%s.WasPassed()" % memberLoc) + conditions = getConditionList(member, "cx", "obj") + if len(conditions) != 0: + conversion = CGIfWrapper(conversion, conditions.define()) + return conversion + + def getMemberTrace(self, member): + type = member.type + assert typeNeedsRooting(type) + memberLoc = self.makeMemberName(member.identifier.name) + if not member.canHaveMissingValue(): + memberData = memberLoc + else: + # The data is inside the Optional<> + memberData = "%s.Value()" % memberLoc + + memberName = "%s.%s" % (self.makeClassName(self.dictionary), memberLoc) + + if type.isObject(): + trace = CGGeneric( + 'JS::TraceRoot(trc, %s, "%s");\n' % ("&" + memberData, memberName) + ) + if type.nullable(): + trace = CGIfWrapper(trace, memberData) + elif type.isAny(): + trace = CGGeneric( + 'JS::TraceRoot(trc, %s, "%s");\n' % ("&" + memberData, memberName) + ) + elif ( + type.isSequence() + or type.isDictionary() + or type.isSpiderMonkeyInterface() + or type.isUnion() + or type.isRecord() + ): + if type.nullable(): + memberNullable = memberData + memberData = "%s.Value()" % memberData + if type.isSequence(): + trace = CGGeneric("DoTraceSequence(trc, %s);\n" % memberData) + elif type.isDictionary(): + trace = CGGeneric("%s.TraceDictionary(trc);\n" % memberData) + elif type.isUnion(): + trace = CGGeneric("%s.TraceUnion(trc);\n" % memberData) + elif type.isRecord(): + trace = CGGeneric("TraceRecord(trc, %s);\n" % memberData) + else: + assert type.isSpiderMonkeyInterface() + trace = CGGeneric("%s.TraceSelf(trc);\n" % memberData) + if type.nullable(): + trace = CGIfWrapper(trace, "!%s.IsNull()" % memberNullable) + else: + assert False # unknown type + + if member.canHaveMissingValue(): + trace = CGIfWrapper(trace, "%s.WasPassed()" % memberLoc) + + return trace.define() + + def getMemberInitializer(self, memberInfo): + """ + Get the right initializer for the member. Most members don't need one, + but we need to pre-initialize 'object' that have a default value or are + required (and hence are not inside Optional), so they're safe to trace + at all times. And we can optimize a bit for dictionary-typed members. + """ + member, _ = memberInfo + if member.canHaveMissingValue(): + # Allowed missing value means no need to set it up front, since it's + # inside an Optional and won't get traced until it's actually set + # up. + return None + type = member.type + if type.isDictionary(): + # When we construct ourselves, we don't want to init our member + # dictionaries. Either we're being constructed-but-not-initialized + # ourselves (and then we don't want to init them) or we're about to + # init ourselves and then we'll init them anyway. + return CGDictionary.getNonInitializingCtorArg() + return initializerForType(type) + + def getMemberSourceDescription(self, member): + return "'%s' member of %s" % ( + member.identifier.name, + self.dictionary.identifier.name, + ) + + @staticmethod + def makeIdName(name): + return IDLToCIdentifier(name) + "_id" + + @staticmethod + def getNonInitializingCtorArg(): + return "FastDictionaryInitializer()" + + @staticmethod + def isDictionaryCopyConstructible(dictionary): + if dictionary.parent and not CGDictionary.isDictionaryCopyConstructible( + dictionary.parent + ): + return False + return all(isTypeCopyConstructible(m.type) for m in dictionary.members) + + @staticmethod + def typeSafeToJSONify(type): + """ + Determine whether the given type is safe to convert to JSON. The + restriction is that this needs to be safe while in a global controlled + by an adversary, and "safe" means no side-effects when the JS + representation of this type is converted to JSON. That means that we + have to be pretty restrictive about what things we can allow. For + example, "object" is out, because it may have accessor properties on it. + """ + if type.nullable(): + # Converting null to JSON is always OK. + return CGDictionary.typeSafeToJSONify(type.inner) + + if type.isSequence(): + # Sequences are arrays we create ourselves, with no holes. They + # should be safe if their contents are safe, as long as we suppress + # invocation of .toJSON on objects. + return CGDictionary.typeSafeToJSONify(type.inner) + + if type.isUnion(): + # OK if everything in it is ok. + return all(CGDictionary.typeSafeToJSONify(t) for t in type.flatMemberTypes) + + if type.isDictionary(): + # OK if the dictionary is OK + return CGDictionary.dictionarySafeToJSONify(type.inner) + + if type.isUndefined() or type.isString() or type.isEnum(): + # Strings are always OK. + return True + + if type.isPrimitive(): + # Primitives (numbers and booleans) are ok, as long as + # they're not unrestricted float/double. + return not type.isFloat() or not type.isUnrestricted() + + if type.isRecord(): + # Records are okay, as long as the value type is. + # Per spec, only strings are allowed as keys. + return CGDictionary.typeSafeToJSONify(type.inner) + + return False + + @staticmethod + def dictionarySafeToJSONify(dictionary): + # The dictionary itself is OK, so we're good if all our types are. + return all(CGDictionary.typeSafeToJSONify(m.type) for m in dictionary.members) + + +class CGRegisterWorkerBindings(CGAbstractMethod): + def __init__(self, config): + CGAbstractMethod.__init__( + self, + None, + "RegisterWorkerBindings", + "bool", + [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")], + ) + self.config = config + + def definition_body(self): + descriptors = self.config.getDescriptors( + hasInterfaceObject=True, isExposedInAnyWorker=True, register=True + ) + conditions = [] + for desc in descriptors: + bindingNS = toBindingNamespace(desc.name) + condition = "!%s::GetConstructorObject(aCx)" % bindingNS + if desc.isExposedConditionally(): + condition = ( + "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition + ) + conditions.append(condition) + lines = [ + CGIfWrapper(CGGeneric("return false;\n"), condition) + for condition in conditions + ] + lines.append(CGGeneric("return true;\n")) + return CGList(lines, "\n").define() + + +class CGRegisterWorkerDebuggerBindings(CGAbstractMethod): + def __init__(self, config): + CGAbstractMethod.__init__( + self, + None, + "RegisterWorkerDebuggerBindings", + "bool", + [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")], + ) + self.config = config + + def definition_body(self): + descriptors = self.config.getDescriptors( + hasInterfaceObject=True, isExposedInWorkerDebugger=True, register=True + ) + conditions = [] + for desc in descriptors: + bindingNS = toBindingNamespace(desc.name) + condition = "!%s::GetConstructorObject(aCx)" % bindingNS + if desc.isExposedConditionally(): + condition = ( + "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition + ) + conditions.append(condition) + lines = [ + CGIfWrapper(CGGeneric("return false;\n"), condition) + for condition in conditions + ] + lines.append(CGGeneric("return true;\n")) + return CGList(lines, "\n").define() + + +class CGRegisterWorkletBindings(CGAbstractMethod): + def __init__(self, config): + CGAbstractMethod.__init__( + self, + None, + "RegisterWorkletBindings", + "bool", + [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")], + ) + self.config = config + + def definition_body(self): + descriptors = self.config.getDescriptors( + hasInterfaceObject=True, isExposedInAnyWorklet=True, register=True + ) + conditions = [] + for desc in descriptors: + bindingNS = toBindingNamespace(desc.name) + condition = "!%s::GetConstructorObject(aCx)" % bindingNS + if desc.isExposedConditionally(): + condition = ( + "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition + ) + conditions.append(condition) + lines = [ + CGIfWrapper(CGGeneric("return false;\n"), condition) + for condition in conditions + ] + lines.append(CGGeneric("return true;\n")) + return CGList(lines, "\n").define() + + +class CGRegisterShadowRealmBindings(CGAbstractMethod): + def __init__(self, config): + CGAbstractMethod.__init__( + self, + None, + "RegisterShadowRealmBindings", + "bool", + [Argument("JSContext*", "aCx"), Argument("JS::Handle<JSObject*>", "aObj")], + ) + self.config = config + + def definition_body(self): + descriptors = self.config.getDescriptors( + hasInterfaceObject=True, isExposedInShadowRealms=True, register=True + ) + conditions = [] + for desc in descriptors: + bindingNS = toBindingNamespace(desc.name) + condition = "!%s::GetConstructorObject(aCx)" % bindingNS + if desc.isExposedConditionally(): + condition = ( + "%s::ConstructorEnabled(aCx, aObj) && " % bindingNS + condition + ) + conditions.append(condition) + lines = [ + CGIfWrapper(CGGeneric("return false;\n"), condition) + for condition in conditions + ] + lines.append(CGGeneric("return true;\n")) + return CGList(lines, "\n").define() + + +def BindingNamesOffsetEnum(name): + return CppKeywords.checkMethodName(name.replace(" ", "_")) + + +class CGGlobalNames(CGGeneric): + def __init__(self, names): + """ + names is expected to be a list of tuples of the name and the descriptor it refers to. + """ + + strings = [] + entries = [] + for name, desc in names: + # Generate the entry declaration + # XXX(nika): mCreate & mEnabled require relocations. If we want to + # reduce those, we could move them into separate tables. + nativeEntry = fill( + """ + { + /* mNameOffset */ BindingNamesOffset::${nameOffset}, + /* mNameLength */ ${nameLength}, + /* mConstructorId */ constructors::id::${realname}, + /* mCreate */ ${realname}_Binding::CreateInterfaceObjects, + /* mEnabled */ ${enabled} + } + """, + nameOffset=BindingNamesOffsetEnum(name), + nameLength=len(name), + name=name, + realname=desc.name, + enabled=( + "%s_Binding::ConstructorEnabled" % desc.name + if desc.isExposedConditionally() + else "nullptr" + ), + ) + + entries.append((name, nativeEntry)) + + # Unfortunately, when running tests, we may have no entries. + # PerfectHash will assert if we give it an empty set of entries, so we + # just generate a dummy value. + if len(entries) == 0: + CGGeneric.__init__( + self, + define=dedent( + """ + static_assert(false, "No WebIDL global name entries!"); + """ + ), + ) + return + + # Build the perfect hash function. + phf = PerfectHash(entries, GLOBAL_NAMES_PHF_SIZE) + + # Generate code for the PHF + phfCodegen = phf.codegen( + "WebIDLGlobalNameHash::sEntries", "WebIDLNameTableEntry" + ) + entries = phfCodegen.gen_entries(lambda e: e[1]) + getter = phfCodegen.gen_jslinearstr_getter( + name="WebIDLGlobalNameHash::GetEntry", + return_type="const WebIDLNameTableEntry*", + return_entry=dedent( + """ + if (JS_LinearStringEqualsAscii(aKey, BindingName(entry.mNameOffset), entry.mNameLength)) { + return &entry; + } + return nullptr; + """ + ), + ) + + define = fill( + """ + const uint32_t WebIDLGlobalNameHash::sCount = ${count}; + + $*{entries} + + $*{getter} + """, + count=len(phf.entries), + strings="\n".join(strings) + ";\n", + entries=entries, + getter=getter, + ) + CGGeneric.__init__(self, define=define) + + +def dependencySortObjects(objects, dependencyGetter, nameGetter): + """ + Sort IDL objects with dependencies on each other such that if A + depends on B then B will come before A. This is needed for + declaring C++ classes in the right order, for example. Objects + that have no dependencies are just sorted by name. + + objects should be something that can produce a set of objects + (e.g. a set, iterator, list, etc). + + dependencyGetter is something that, given an object, should return + the set of objects it depends on. + """ + # XXXbz this will fail if we have two webidl files F1 and F2 such that F1 + # declares an object which depends on an object in F2, and F2 declares an + # object (possibly a different one!) that depends on an object in F1. The + # good news is that I expect this to never happen. + sortedObjects = [] + objects = set(objects) + while len(objects) != 0: + # Find the dictionaries that don't depend on anything else + # anymore and move them over. + toMove = [o for o in objects if len(dependencyGetter(o) & objects) == 0] + if len(toMove) == 0: + raise TypeError( + "Loop in dependency graph\n" + "\n".join(o.location for o in objects) + ) + objects = objects - set(toMove) + sortedObjects.extend(sorted(toMove, key=nameGetter)) + return sortedObjects + + +class ForwardDeclarationBuilder: + """ + Create a canonical representation of a set of namespaced forward + declarations. + """ + + def __init__(self): + """ + The set of declarations is represented as a tree of nested namespaces. + Each tree node has a set of declarations |decls| and a dict |children|. + Each declaration is a pair consisting of the class name and a boolean + that is true iff the class is really a struct. |children| maps the + names of inner namespaces to the declarations in that namespace. + """ + self.decls = set() + self.children = {} + + def _ensureNonTemplateType(self, type): + if "<" in type: + # This is a templated type. We don't really know how to + # forward-declare those, and trying to do it naively is not going to + # go well (e.g. we may have :: characters inside the type we're + # templated on!). Just bail out. + raise TypeError( + "Attempt to use ForwardDeclarationBuilder on " + "templated type %s. We don't know how to do that " + "yet." % type + ) + + def _listAdd(self, namespaces, name, isStruct=False): + """ + Add a forward declaration, where |namespaces| is a list of namespaces. + |name| should not contain any other namespaces. + """ + if namespaces: + child = self.children.setdefault(namespaces[0], ForwardDeclarationBuilder()) + child._listAdd(namespaces[1:], name, isStruct) + else: + assert "::" not in name + self.decls.add((name, isStruct)) + + def addInMozillaDom(self, name, isStruct=False): + """ + Add a forward declaration to the mozilla::dom:: namespace. |name| should not + contain any other namespaces. + """ + self._ensureNonTemplateType(name) + self._listAdd(["mozilla", "dom"], name, isStruct) + + def add(self, nativeType, isStruct=False): + """ + Add a forward declaration, where |nativeType| is a string containing + the type and its namespaces, in the usual C++ way. + """ + self._ensureNonTemplateType(nativeType) + components = nativeType.split("::") + self._listAdd(components[:-1], components[-1], isStruct) + + def _build(self, atTopLevel): + """ + Return a codegenerator for the forward declarations. + """ + decls = [] + if self.decls: + decls.append( + CGList( + [ + CGClassForwardDeclare(cname, isStruct) + for cname, isStruct in sorted(self.decls) + ] + ) + ) + for namespace, child in sorted(six.iteritems(self.children)): + decls.append(CGNamespace(namespace, child._build(atTopLevel=False))) + + cg = CGList(decls, "\n") + if not atTopLevel and len(decls) + len(self.decls) > 1: + cg = CGWrapper(cg, pre="\n", post="\n") + return cg + + def build(self): + return self._build(atTopLevel=True) + + def forwardDeclareForType(self, t, config): + t = t.unroll() + if t.isGeckoInterface(): + name = t.inner.identifier.name + try: + desc = config.getDescriptor(name) + self.add(desc.nativeType) + except NoSuchDescriptorError: + pass + + # Note: SpiderMonkey interfaces are typedefs, so can't be + # forward-declared + elif t.isPromise(): + self.addInMozillaDom("Promise") + elif t.isCallback(): + self.addInMozillaDom(t.callback.identifier.name) + elif t.isDictionary(): + self.addInMozillaDom(t.inner.identifier.name, isStruct=True) + elif t.isCallbackInterface(): + self.addInMozillaDom(t.inner.identifier.name) + elif t.isUnion(): + # Forward declare both the owning and non-owning version, + # since we don't know which one we might want + self.addInMozillaDom(CGUnionStruct.unionTypeName(t, False)) + self.addInMozillaDom(CGUnionStruct.unionTypeName(t, True)) + elif t.isRecord(): + self.forwardDeclareForType(t.inner, config) + # Don't need to do anything for void, primitive, string, any or object. + # There may be some other cases we are missing. + + +class CGForwardDeclarations(CGWrapper): + """ + Code generate the forward declarations for a header file. + additionalDeclarations is a list of tuples containing a classname and a + boolean. If the boolean is true we will declare a struct, otherwise we'll + declare a class. + """ + + def __init__( + self, + config, + descriptors, + callbacks, + dictionaries, + callbackInterfaces, + additionalDeclarations=[], + ): + builder = ForwardDeclarationBuilder() + + # Needed for at least Wrap. + for d in descriptors: + # If this is a generated iterator interface, we only create these + # in the generated bindings, and don't need to forward declare. + if ( + d.interface.isIteratorInterface() + or d.interface.isAsyncIteratorInterface() + ): + continue + builder.add(d.nativeType) + if d.interface.isSerializable(): + builder.add("nsIGlobalObject") + # If we're an interface and we have a maplike/setlike declaration, + # we'll have helper functions exposed to the native side of our + # bindings, which will need to show up in the header. If either of + # our key/value types are interfaces, they'll be passed as + # arguments to helper functions, and they'll need to be forward + # declared in the header. + if d.interface.maplikeOrSetlikeOrIterable: + if d.interface.maplikeOrSetlikeOrIterable.hasKeyType(): + builder.forwardDeclareForType( + d.interface.maplikeOrSetlikeOrIterable.keyType, config + ) + if d.interface.maplikeOrSetlikeOrIterable.hasValueType(): + builder.forwardDeclareForType( + d.interface.maplikeOrSetlikeOrIterable.valueType, config + ) + + for m in d.interface.members: + if m.isAttr() and m.type.isObservableArray(): + builder.forwardDeclareForType(m.type, config) + + # We just about always need NativePropertyHooks + builder.addInMozillaDom("NativePropertyHooks", isStruct=True) + builder.addInMozillaDom("ProtoAndIfaceCache") + + for callback in callbacks: + builder.addInMozillaDom(callback.identifier.name) + for t in getTypesFromCallback(callback): + builder.forwardDeclareForType(t, config) + + for d in callbackInterfaces: + builder.add(d.nativeType) + builder.add(d.nativeType + "Atoms", isStruct=True) + for t in getTypesFromDescriptor(d): + builder.forwardDeclareForType(t, config) + if d.hasCEReactions(): + builder.addInMozillaDom("DocGroup") + + for d in dictionaries: + if len(d.members) > 0: + builder.addInMozillaDom(d.identifier.name + "Atoms", isStruct=True) + for t in getTypesFromDictionary(d): + builder.forwardDeclareForType(t, config) + + for className, isStruct in additionalDeclarations: + builder.add(className, isStruct=isStruct) + + CGWrapper.__init__(self, builder.build()) + + +def dependencySortDictionariesAndUnionsAndCallbacks(types): + def getDependenciesFromType(type): + if type.isDictionary(): + return set([type.unroll().inner]) + if type.isSequence(): + return getDependenciesFromType(type.unroll()) + if type.isUnion(): + return set([type.unroll()]) + if type.isRecord(): + return set([type.unroll().inner]) + if type.isCallback(): + return set([type.unroll()]) + return set() + + def getDependencies(unionTypeOrDictionaryOrCallback): + if isinstance(unionTypeOrDictionaryOrCallback, IDLDictionary): + deps = set() + if unionTypeOrDictionaryOrCallback.parent: + deps.add(unionTypeOrDictionaryOrCallback.parent) + for member in unionTypeOrDictionaryOrCallback.members: + deps |= getDependenciesFromType(member.type) + return deps + + if ( + unionTypeOrDictionaryOrCallback.isType() + and unionTypeOrDictionaryOrCallback.isUnion() + ): + deps = set() + for member in unionTypeOrDictionaryOrCallback.flatMemberTypes: + deps |= getDependenciesFromType(member) + return deps + + assert unionTypeOrDictionaryOrCallback.isCallback() + return set() + + def getName(unionTypeOrDictionaryOrCallback): + if isinstance(unionTypeOrDictionaryOrCallback, IDLDictionary): + return unionTypeOrDictionaryOrCallback.identifier.name + + if ( + unionTypeOrDictionaryOrCallback.isType() + and unionTypeOrDictionaryOrCallback.isUnion() + ): + return unionTypeOrDictionaryOrCallback.name + + assert unionTypeOrDictionaryOrCallback.isCallback() + return unionTypeOrDictionaryOrCallback.identifier.name + + return dependencySortObjects(types, getDependencies, getName) + + +class CGBindingRoot(CGThing): + """ + Root codegen class for binding generation. Instantiate the class, and call + declare or define to generate header or cpp code (respectively). + """ + + def __init__(self, config, prefix, webIDLFile): + bindingHeaders = dict.fromkeys( + ("mozilla/dom/NonRefcountedDOMObject.h", "MainThreadUtils.h"), True + ) + bindingDeclareHeaders = dict.fromkeys( + ( + "mozilla/dom/BindingDeclarations.h", + "mozilla/dom/Nullable.h", + ), + True, + ) + + descriptors = config.getDescriptors( + webIDLFile=webIDLFile, hasInterfaceOrInterfacePrototypeObject=True + ) + + unionTypes = UnionsForFile(config, webIDLFile) + + ( + unionHeaders, + unionImplheaders, + unionDeclarations, + traverseMethods, + unlinkMethods, + unionStructs, + ) = UnionTypes(unionTypes, config) + + bindingDeclareHeaders.update(dict.fromkeys(unionHeaders, True)) + bindingHeaders.update(dict.fromkeys(unionImplheaders, True)) + bindingDeclareHeaders["mozilla/dom/UnionMember.h"] = len(unionStructs) > 0 + bindingDeclareHeaders["mozilla/dom/FakeString.h"] = len(unionStructs) > 0 + # BindingUtils.h is only needed for SetToObject. + # If it stops being inlined or stops calling CallerSubsumes + # both this bit and the bit in UnionTypes can be removed. + bindingDeclareHeaders["mozilla/dom/BindingUtils.h"] = any( + d.isObject() for t in unionTypes for d in t.flatMemberTypes + ) + bindingHeaders["mozilla/dom/IterableIterator.h"] = any( + ( + d.interface.isIteratorInterface() + and d.interface.maplikeOrSetlikeOrIterable.isPairIterator() + ) + or d.interface.isAsyncIteratorInterface() + or d.interface.isIterable() + or d.interface.isAsyncIterable() + for d in descriptors + ) + + def memberNeedsSubjectPrincipal(d, m): + if m.isAttr(): + return ( + "needsSubjectPrincipal" in d.getExtendedAttributes(m, getter=True) + ) or ( + not m.readonly + and "needsSubjectPrincipal" + in d.getExtendedAttributes(m, setter=True) + ) + return m.isMethod() and "needsSubjectPrincipal" in d.getExtendedAttributes( + m + ) + + if any( + memberNeedsSubjectPrincipal(d, m) + for d in descriptors + for m in d.interface.members + ): + bindingHeaders["mozilla/BasePrincipal.h"] = True + bindingHeaders["nsJSPrincipals.h"] = True + + # The conditions for which we generate profiler labels are fairly + # complicated. The check below is a little imprecise to make it simple. + # It includes the profiler header in all cases where it is necessary and + # generates only a few false positives. + bindingHeaders["mozilla/ProfilerLabels.h"] = any( + # constructor profiler label + d.interface.legacyFactoryFunctions + or (d.interface.hasInterfaceObject() and d.interface.ctor()) + or any( + # getter/setter profiler labels + m.isAttr() + # method profiler label + or m.isMethod() + for m in d.interface.members + ) + for d in descriptors + ) + + def descriptorHasCrossOriginProperties(desc): + def hasCrossOriginProperty(m): + props = memberProperties(m, desc) + return ( + props.isCrossOriginMethod + or props.isCrossOriginGetter + or props.isCrossOriginSetter + ) + + return any(hasCrossOriginProperty(m) for m in desc.interface.members) + + def descriptorHasObservableArrayTypes(desc): + def hasObservableArrayTypes(m): + return m.isAttr() and m.type.isObservableArray() + + return any(hasObservableArrayTypes(m) for m in desc.interface.members) + + bindingDeclareHeaders["mozilla/dom/RemoteObjectProxy.h"] = any( + descriptorHasCrossOriginProperties(d) for d in descriptors + ) + bindingDeclareHeaders["jsapi.h"] = any( + descriptorHasCrossOriginProperties(d) + or descriptorHasObservableArrayTypes(d) + for d in descriptors + ) + bindingDeclareHeaders["js/TypeDecls.h"] = not bindingDeclareHeaders["jsapi.h"] + bindingDeclareHeaders["js/RootingAPI.h"] = not bindingDeclareHeaders["jsapi.h"] + + # JS::IsCallable + bindingDeclareHeaders["js/CallAndConstruct.h"] = True + + def descriptorHasIteratorAlias(desc): + def hasIteratorAlias(m): + return m.isMethod() and ( + ("@@iterator" in m.aliases) or ("@@asyncIterator" in m.aliases) + ) + + return any(hasIteratorAlias(m) for m in desc.interface.members) + + bindingHeaders["js/Symbol.h"] = any( + descriptorHasIteratorAlias(d) for d in descriptors + ) + + bindingHeaders["js/shadow/Object.h"] = any( + d.interface.hasMembersInSlots() for d in descriptors + ) + + # The symbols supplied by this header are used so ubiquitously it's not + # worth the effort delineating the exact dependency, if it can't be done + # *at* the places where their definitions are required. + bindingHeaders["js/experimental/JitInfo.h"] = True + + # JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, and + # JS::SetReservedSlot are also used too many places to restate + # dependency logic. + bindingHeaders["js/Object.h"] = True + + # JS::IsCallable, JS::Call, JS::Construct + bindingHeaders["js/CallAndConstruct.h"] = True + + # JS_IsExceptionPending + bindingHeaders["js/Exception.h"] = True + + # JS::Map{Clear, Delete, Has, Get, Set} + bindingHeaders["js/MapAndSet.h"] = True + + # JS_DefineElement, JS_DefineProperty, JS_DefinePropertyById, + # JS_DefineUCProperty, JS_ForwardGetPropertyTo, JS_GetProperty, + # JS_GetPropertyById, JS_HasPropertyById, JS_SetProperty, + # JS_SetPropertyById + bindingHeaders["js/PropertyAndElement.h"] = True + + # JS_GetOwnPropertyDescriptorById + bindingHeaders["js/PropertyDescriptor.h"] = True + + def descriptorDeprecated(desc): + iface = desc.interface + return any( + m.getExtendedAttribute("Deprecated") for m in iface.members + [iface] + ) + + bindingHeaders["mozilla/dom/Document.h"] = any( + descriptorDeprecated(d) for d in descriptors + ) + + bindingHeaders["mozilla/dom/DOMJSProxyHandler.h"] = any( + d.concrete and d.proxy for d in descriptors + ) + + bindingHeaders["mozilla/dom/ProxyHandlerUtils.h"] = any( + d.concrete and d.proxy for d in descriptors + ) + + bindingHeaders["js/String.h"] = any( + d.needsMissingPropUseCounters for d in descriptors + ) + + hasCrossOriginObjects = any( + d.concrete and d.isMaybeCrossOriginObject() for d in descriptors + ) + bindingHeaders["mozilla/dom/MaybeCrossOriginObject.h"] = hasCrossOriginObjects + bindingHeaders["AccessCheck.h"] = hasCrossOriginObjects + hasCEReactions = any(d.hasCEReactions() for d in descriptors) + bindingHeaders["mozilla/dom/CustomElementRegistry.h"] = hasCEReactions + bindingHeaders["mozilla/dom/DocGroup.h"] = hasCEReactions + + def descriptorHasChromeOnly(desc): + ctor = desc.interface.ctor() + + return ( + any( + isChromeOnly(a) or needsContainsHack(a) or needsCallerType(a) + for a in desc.interface.members + ) + or desc.interface.getExtendedAttribute("ChromeOnly") is not None + or + # JS-implemented interfaces with an interface object get a + # chromeonly _create method. And interfaces with an + # interface object might have a ChromeOnly constructor. + ( + desc.interface.hasInterfaceObject() + and ( + desc.interface.isJSImplemented() + or (ctor and isChromeOnly(ctor)) + ) + ) + ) + + # XXXkhuey ugly hack but this is going away soon. + bindingHeaders["xpcprivate.h"] = webIDLFile.endswith("EventTarget.webidl") + + hasThreadChecks = any(d.hasThreadChecks() for d in descriptors) + bindingHeaders["nsThreadUtils.h"] = hasThreadChecks + + dictionaries = config.getDictionaries(webIDLFile) + + def dictionaryHasChromeOnly(dictionary): + while dictionary: + if any(isChromeOnly(m) for m in dictionary.members): + return True + dictionary = dictionary.parent + return False + + def needsNonSystemPrincipal(member): + return ( + member.getExtendedAttribute("NeedsSubjectPrincipal") == ["NonSystem"] + or member.getExtendedAttribute("SetterNeedsSubjectPrincipal") + == ["NonSystem"] + or member.getExtendedAttribute("GetterNeedsSubjectPrincipal") + == ["NonSystem"] + ) + + def descriptorNeedsNonSystemPrincipal(d): + return any(needsNonSystemPrincipal(m) for m in d.interface.members) + + def descriptorHasPrefDisabler(desc): + iface = desc.interface + return any( + PropertyDefiner.getControllingCondition(m, desc).hasDisablers() + for m in iface.members + if (m.isMethod() or m.isAttr() or m.isConst()) + ) + + def addPrefHeaderForObject(bindingHeaders, obj): + """ + obj might be a dictionary member or an interface. + """ + if obj is not None: + pref = PropertyDefiner.getStringAttr(obj, "Pref") + if pref: + bindingHeaders[prefHeader(pref)] = True + + def addPrefHeadersForDictionary(bindingHeaders, dictionary): + while dictionary: + for m in dictionary.members: + addPrefHeaderForObject(bindingHeaders, m) + dictionary = dictionary.parent + + for d in dictionaries: + addPrefHeadersForDictionary(bindingHeaders, d) + for d in descriptors: + interface = d.interface + addPrefHeaderForObject(bindingHeaders, interface) + addPrefHeaderForObject(bindingHeaders, interface.ctor()) + + bindingHeaders["mozilla/dom/WebIDLPrefs.h"] = any( + descriptorHasPrefDisabler(d) for d in descriptors + ) + bindingHeaders["nsContentUtils.h"] = ( + any(descriptorHasChromeOnly(d) for d in descriptors) + or any(descriptorNeedsNonSystemPrincipal(d) for d in descriptors) + or any(dictionaryHasChromeOnly(d) for d in dictionaries) + ) + hasNonEmptyDictionaries = any(len(dict.members) > 0 for dict in dictionaries) + callbacks = config.getCallbacks(webIDLFile) + callbackDescriptors = config.getDescriptors( + webIDLFile=webIDLFile, isCallback=True + ) + jsImplemented = config.getDescriptors( + webIDLFile=webIDLFile, isJSImplemented=True + ) + bindingDeclareHeaders["nsWeakReference.h"] = jsImplemented + bindingDeclareHeaders["mozilla/dom/PrototypeList.h"] = descriptors + bindingHeaders["nsIGlobalObject.h"] = jsImplemented + bindingHeaders["AtomList.h"] = ( + hasNonEmptyDictionaries or jsImplemented or callbackDescriptors + ) + + if callbackDescriptors: + bindingDeclareHeaders["mozilla/ErrorResult.h"] = True + + def descriptorClearsPropsInSlots(descriptor): + if not descriptor.wrapperCache: + return False + return any( + m.isAttr() and m.getExtendedAttribute("StoreInSlot") + for m in descriptor.interface.members + ) + + bindingHeaders["nsJSUtils.h"] = any( + descriptorClearsPropsInSlots(d) for d in descriptors + ) + + # Make sure we can sanely use binding_detail in generated code. + cgthings = [ + CGGeneric( + dedent( + """ + namespace binding_detail {}; // Just to make sure it's known as a namespace + using namespace mozilla::dom::binding_detail; + """ + ) + ) + ] + + # Do codegen for all the enums + enums = config.getEnums(webIDLFile) + cgthings.extend(CGEnum(e) for e in enums) + + bindingDeclareHeaders["mozilla/Span.h"] = enums + bindingDeclareHeaders["mozilla/ArrayUtils.h"] = enums + + hasCode = descriptors or callbackDescriptors or dictionaries or callbacks + bindingHeaders["mozilla/dom/BindingUtils.h"] = hasCode + bindingHeaders["mozilla/OwningNonNull.h"] = hasCode + bindingHeaders["<type_traits>"] = hasCode + bindingHeaders["mozilla/dom/BindingDeclarations.h"] = not hasCode and enums + + bindingHeaders["WrapperFactory.h"] = descriptors + bindingHeaders["mozilla/dom/DOMJSClass.h"] = descriptors + bindingHeaders["mozilla/dom/ScriptSettings.h"] = dictionaries # AutoJSAPI + # Ensure we see our enums in the generated .cpp file, for the ToJSValue + # method body. Also ensure that we see jsapi.h. + if enums: + bindingHeaders[CGHeaders.getDeclarationFilename(enums[0])] = True + bindingHeaders["jsapi.h"] = True + + # For things that have [UseCounter] or [InstrumentedProps] or [Trial] + for d in descriptors: + if d.concrete: + if d.instrumentedProps: + bindingHeaders["mozilla/UseCounter.h"] = True + if d.needsMissingPropUseCounters: + bindingHeaders[prefHeader(MISSING_PROP_PREF)] = True + if d.interface.isSerializable(): + bindingHeaders["mozilla/dom/StructuredCloneTags.h"] = True + if d.wantsXrays: + bindingHeaders["mozilla/Atomics.h"] = True + bindingHeaders["mozilla/dom/XrayExpandoClass.h"] = True + if d.wantsXrayExpandoClass: + bindingHeaders["XrayWrapper.h"] = True + for m in d.interface.members: + if m.getExtendedAttribute("UseCounter"): + bindingHeaders["mozilla/UseCounter.h"] = True + if m.getExtendedAttribute("Trial"): + bindingHeaders["mozilla/OriginTrials.h"] = True + + bindingHeaders["mozilla/dom/SimpleGlobalObject.h"] = any( + CGDictionary.dictionarySafeToJSONify(d) for d in dictionaries + ) + + for ancestor in (findAncestorWithInstrumentedProps(d) for d in descriptors): + if not ancestor: + continue + bindingHeaders[CGHeaders.getDeclarationFilename(ancestor)] = True + + cgthings.extend(traverseMethods) + cgthings.extend(unlinkMethods) + + # Do codegen for all the dictionaries. We have to be a bit careful + # here, because we have to generate these in order from least derived + # to most derived so that class inheritance works out. We also have to + # generate members before the dictionary that contains them. + + for t in dependencySortDictionariesAndUnionsAndCallbacks( + dictionaries + unionStructs + callbacks + ): + if t.isDictionary(): + cgthings.append(CGDictionary(t, config)) + elif t.isUnion(): + cgthings.append(CGUnionStruct(t, config)) + cgthings.append(CGUnionStruct(t, config, True)) + else: + assert t.isCallback() + cgthings.append(CGCallbackFunction(t, config)) + cgthings.append(CGNamespace("binding_detail", CGFastCallback(t))) + + # Do codegen for all the descriptors + cgthings.extend([CGDescriptor(x) for x in descriptors]) + + # Do codegen for all the callback interfaces. + cgthings.extend([CGCallbackInterface(x) for x in callbackDescriptors]) + + cgthings.extend( + [ + CGNamespace("binding_detail", CGFastCallback(x.interface)) + for x in callbackDescriptors + ] + ) + + # Do codegen for JS implemented classes + def getParentDescriptor(desc): + if not desc.interface.parent: + return set() + return {desc.getDescriptor(desc.interface.parent.identifier.name)} + + for x in dependencySortObjects( + jsImplemented, getParentDescriptor, lambda d: d.interface.identifier.name + ): + cgthings.append( + CGCallbackInterface(x, spiderMonkeyInterfacesAreStructs=True) + ) + cgthings.append(CGJSImplClass(x)) + + # And make sure we have the right number of newlines at the end + curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n") + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, pre="\n")) + + curr = CGList( + [ + CGForwardDeclarations( + config, + descriptors, + callbacks, + dictionaries, + callbackDescriptors + jsImplemented, + additionalDeclarations=unionDeclarations, + ), + curr, + ], + "\n", + ) + + # Add header includes. + bindingHeaders = [ + header for header, include in six.iteritems(bindingHeaders) if include + ] + bindingDeclareHeaders = [ + header + for header, include in six.iteritems(bindingDeclareHeaders) + if include + ] + + curr = CGHeaders( + descriptors, + dictionaries, + callbacks, + callbackDescriptors, + bindingDeclareHeaders, + bindingHeaders, + prefix, + curr, + config, + jsImplemented, + ) + + # Add include guards. + curr = CGIncludeGuard(prefix, curr) + + # Add the auto-generated comment. + curr = CGWrapper( + curr, + pre=( + AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT % os.path.basename(webIDLFile) + ), + ) + + # Store the final result. + self.root = curr + + def declare(self): + return stripTrailingWhitespace(self.root.declare()) + + def define(self): + return stripTrailingWhitespace(self.root.define()) + + def deps(self): + return self.root.deps() + + +class CGNativeMember(ClassMethod): + def __init__( + self, + descriptorProvider, + member, + name, + signature, + extendedAttrs, + breakAfter=True, + passJSBitsAsNeeded=True, + visibility="public", + spiderMonkeyInterfacesAreStructs=True, + variadicIsSequence=False, + resultNotAddRefed=False, + virtual=False, + override=False, + canRunScript=False, + ): + """ + If spiderMonkeyInterfacesAreStructs is false, SpiderMonkey interfaces + will be passed as JS::Handle<JSObject*>. If it's true they will be + passed as one of the dom::SpiderMonkeyInterfaceObjectStorage subclasses. + + If passJSBitsAsNeeded is false, we don't automatically pass in a + JSContext* or a JSObject* based on the return and argument types. We + can still pass it based on 'implicitJSContext' annotations. + """ + self.descriptorProvider = descriptorProvider + self.member = member + self.extendedAttrs = extendedAttrs + self.resultAlreadyAddRefed = not resultNotAddRefed + self.passJSBitsAsNeeded = passJSBitsAsNeeded + self.spiderMonkeyInterfacesAreStructs = spiderMonkeyInterfacesAreStructs + self.variadicIsSequence = variadicIsSequence + breakAfterSelf = "\n" if breakAfter else "" + ClassMethod.__init__( + self, + name, + self.getReturnType(signature[0], False), + self.getArgs(signature[0], signature[1]), + static=member.isStatic(), + # Mark our getters, which are attrs that + # have a non-void return type, as const. + const=( + not member.isStatic() + and member.isAttr() + and not signature[0].isUndefined() + ), + breakAfterReturnDecl=" ", + breakAfterSelf=breakAfterSelf, + visibility=visibility, + virtual=virtual, + override=override, + canRunScript=canRunScript, + ) + + def getReturnType(self, type, isMember): + return self.getRetvalInfo(type, isMember)[0] + + def getRetvalInfo(self, type, isMember): + """ + Returns a tuple: + + The first element is the type declaration for the retval + + The second element is a default value that can be used on error returns. + For cases whose behavior depends on isMember, the second element will be + None if isMember is true. + + The third element is a template for actually returning a value stored in + "${declName}" and "${holderName}". This means actually returning it if + we're not outparam, else assigning to the "retval" outparam. If + isMember is true, this can be None, since in that case the caller will + never examine this value. + """ + if type.isUndefined(): + return "void", "", "" + if type.isPrimitive() and type.tag() in builtinNames: + result = CGGeneric(builtinNames[type.tag()]) + defaultReturnArg = "0" + if type.nullable(): + result = CGTemplatedType("Nullable", result) + defaultReturnArg = "" + return ( + result.define(), + "%s(%s)" % (result.define(), defaultReturnArg), + "return ${declName};\n", + ) + if type.isJSString(): + if isMember: + raise TypeError("JSString not supported as return type member") + # Outparam + return "void", "", "aRetVal.set(${declName});\n" + if type.isDOMString() or type.isUSVString(): + if isMember: + # No need for a third element in the isMember case + return "nsString", None, None + # Outparam + return "void", "", "aRetVal = ${declName};\n" + if type.isByteString() or type.isUTF8String(): + if isMember: + # No need for a third element in the isMember case + return "nsCString", None, None + # Outparam + return "void", "", "aRetVal = ${declName};\n" + if type.isEnum(): + enumName = type.unroll().inner.identifier.name + if type.nullable(): + enumName = CGTemplatedType("Nullable", CGGeneric(enumName)).define() + defaultValue = "%s()" % enumName + else: + defaultValue = "%s(0)" % enumName + return enumName, defaultValue, "return ${declName};\n" + if type.isGeckoInterface() or type.isPromise(): + if type.isGeckoInterface(): + iface = type.unroll().inner + result = CGGeneric( + self.descriptorProvider.getDescriptor( + iface.identifier.name + ).prettyNativeType + ) + else: + result = CGGeneric("Promise") + if self.resultAlreadyAddRefed: + if isMember: + holder = "RefPtr" + else: + holder = "already_AddRefed" + if memberReturnsNewObject(self.member) or isMember: + warning = "" + else: + warning = "// Return a raw pointer here to avoid refcounting, but make sure it's safe (the object should be kept alive by the callee).\n" + result = CGWrapper(result, pre=("%s%s<" % (warning, holder)), post=">") + else: + result = CGWrapper(result, post="*") + # Since we always force an owning type for callback return values, + # our ${declName} is an OwningNonNull or RefPtr. So we can just + # .forget() to get our already_AddRefed. + return result.define(), "nullptr", "return ${declName}.forget();\n" + if type.isCallback(): + return ( + "already_AddRefed<%s>" % type.unroll().callback.identifier.name, + "nullptr", + "return ${declName}.forget();\n", + ) + if type.isAny(): + if isMember: + # No need for a third element in the isMember case + return "JS::Value", None, None + # Outparam + return "void", "", "aRetVal.set(${declName});\n" + + if type.isObject(): + if isMember: + # No need for a third element in the isMember case + return "JSObject*", None, None + return "void", "", "aRetVal.set(${declName});\n" + if type.isSpiderMonkeyInterface(): + if isMember: + # No need for a third element in the isMember case + return "JSObject*", None, None + if type.nullable(): + returnCode = ( + "${declName}.IsNull() ? nullptr : ${declName}.Value().Obj()" + ) + else: + returnCode = "${declName}.Obj()" + return "void", "", "aRetVal.set(%s);\n" % returnCode + if type.isSequence(): + # If we want to handle sequence-of-sequences return values, we're + # going to need to fix example codegen to not produce nsTArray<void> + # for the relevant argument... + assert not isMember + # Outparam. + if type.nullable(): + returnCode = dedent( + """ + if (${declName}.IsNull()) { + aRetVal.SetNull(); + } else { + aRetVal.SetValue() = std::move(${declName}.Value()); + } + """ + ) + else: + returnCode = "aRetVal = std::move(${declName});\n" + return "void", "", returnCode + if type.isRecord(): + # If we want to handle record-of-record return values, we're + # going to need to fix example codegen to not produce record<void> + # for the relevant argument... + assert not isMember + # In this case we convert directly into our outparam to start with + return "void", "", "" + if type.isDictionary(): + if isMember: + # Only the first member of the tuple matters here, but return + # bogus values for the others in case someone decides to use + # them. + return CGDictionary.makeDictionaryName(type.inner), None, None + # In this case we convert directly into our outparam to start with + return "void", "", "" + if type.isUnion(): + if isMember: + # Only the first member of the tuple matters here, but return + # bogus values for the others in case someone decides to use + # them. + return CGUnionStruct.unionTypeDecl(type, True), None, None + # In this case we convert directly into our outparam to start with + return "void", "", "" + + raise TypeError("Don't know how to declare return value for %s" % type) + + def getArgs(self, returnType, argList): + args = [self.getArg(arg) for arg in argList] + # Now the outparams + if returnType.isJSString(): + args.append(Argument("JS::MutableHandle<JSString*>", "aRetVal")) + elif returnType.isDOMString() or returnType.isUSVString(): + args.append(Argument("nsString&", "aRetVal")) + elif returnType.isByteString() or returnType.isUTF8String(): + args.append(Argument("nsCString&", "aRetVal")) + elif returnType.isSequence(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # And now the actual underlying type + elementDecl = self.getReturnType(returnType.inner, True) + type = CGTemplatedType("nsTArray", CGGeneric(elementDecl)) + if nullable: + type = CGTemplatedType("Nullable", type) + args.append(Argument("%s&" % type.define(), "aRetVal")) + elif returnType.isRecord(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # And now the actual underlying type + elementDecl = self.getReturnType(returnType.inner, True) + type = CGTemplatedType( + "Record", [recordKeyDeclType(returnType), CGGeneric(elementDecl)] + ) + if nullable: + type = CGTemplatedType("Nullable", type) + args.append(Argument("%s&" % type.define(), "aRetVal")) + elif returnType.isDictionary(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + dictType = CGGeneric(CGDictionary.makeDictionaryName(returnType.inner)) + if nullable: + dictType = CGTemplatedType("Nullable", dictType) + args.append(Argument("%s&" % dictType.define(), "aRetVal")) + elif returnType.isUnion(): + args.append( + Argument( + "%s&" % CGUnionStruct.unionTypeDecl(returnType, True), "aRetVal" + ) + ) + elif returnType.isAny(): + args.append(Argument("JS::MutableHandle<JS::Value>", "aRetVal")) + elif returnType.isObject() or returnType.isSpiderMonkeyInterface(): + args.append(Argument("JS::MutableHandle<JSObject*>", "aRetVal")) + + # And the nsIPrincipal + if "needsSubjectPrincipal" in self.extendedAttrs: + # Cheat and assume self.descriptorProvider is a descriptor + if self.descriptorProvider.interface.isExposedInAnyWorker(): + args.append(Argument("Maybe<nsIPrincipal*>", "aSubjectPrincipal")) + elif "needsNonSystemSubjectPrincipal" in self.extendedAttrs: + args.append(Argument("nsIPrincipal*", "aPrincipal")) + else: + args.append(Argument("nsIPrincipal&", "aPrincipal")) + # And the caller type, if desired. + if needsCallerType(self.member): + args.append(Argument("CallerType", "aCallerType")) + # And the ErrorResult or OOMReporter + if "needsErrorResult" in self.extendedAttrs: + # Use aRv so it won't conflict with local vars named "rv" + args.append(Argument("ErrorResult&", "aRv")) + elif "canOOM" in self.extendedAttrs: + args.append(Argument("OOMReporter&", "aRv")) + + # The legacycaller thisval + if self.member.isMethod() and self.member.isLegacycaller(): + # If it has an identifier, we can't deal with it yet + assert self.member.isIdentifierLess() + args.insert(0, Argument("const JS::Value&", "aThisVal")) + # And jscontext bits. + if needCx( + returnType, + argList, + self.extendedAttrs, + self.passJSBitsAsNeeded, + self.member.isStatic(), + ): + args.insert(0, Argument("JSContext*", "cx")) + if needScopeObject( + returnType, + argList, + self.extendedAttrs, + self.descriptorProvider.wrapperCache, + self.passJSBitsAsNeeded, + self.member.getExtendedAttribute("StoreInSlot"), + ): + args.insert(1, Argument("JS::Handle<JSObject*>", "obj")) + # And if we're static, a global + if self.member.isStatic(): + args.insert(0, Argument("const GlobalObject&", "global")) + return args + + def doGetArgType(self, type, optional, isMember): + """ + The main work of getArgType. Returns a string type decl, whether this + is a const ref, as well as whether the type should be wrapped in + Nullable as needed. + + isMember can be false or one of the strings "Sequence", "Variadic", + "Record" + """ + if type.isSequence(): + nullable = type.nullable() + if nullable: + type = type.inner + elementType = type.inner + argType = self.getArgType(elementType, False, "Sequence")[0] + decl = CGTemplatedType("Sequence", argType) + return decl.define(), True, True + + if type.isRecord(): + nullable = type.nullable() + if nullable: + type = type.inner + elementType = type.inner + argType = self.getArgType(elementType, False, "Record")[0] + decl = CGTemplatedType("Record", [recordKeyDeclType(type), argType]) + return decl.define(), True, True + + if type.isUnion(): + # unionTypeDecl will handle nullable types, so return False for + # auto-wrapping in Nullable + return CGUnionStruct.unionTypeDecl(type, isMember), True, False + + if type.isPromise(): + assert not type.nullable() + if optional or isMember: + typeDecl = "OwningNonNull<Promise>" + else: + typeDecl = "Promise&" + return (typeDecl, False, False) + + if type.isGeckoInterface() and not type.isCallbackInterface(): + iface = type.unroll().inner + if iface.identifier.name == "WindowProxy": + return "WindowProxyHolder", True, False + + argIsPointer = type.nullable() or iface.isExternal() + forceOwningType = iface.isCallback() or isMember + if argIsPointer: + if (optional or isMember) and forceOwningType: + typeDecl = "RefPtr<%s>" + else: + typeDecl = "%s*" + else: + if optional or isMember: + if forceOwningType: + typeDecl = "OwningNonNull<%s>" + else: + typeDecl = "NonNull<%s>" + else: + typeDecl = "%s&" + return ( + ( + typeDecl + % self.descriptorProvider.getDescriptor( + iface.identifier.name + ).prettyNativeType + ), + False, + False, + ) + + if type.isSpiderMonkeyInterface(): + if not self.spiderMonkeyInterfacesAreStructs: + return "JS::Handle<JSObject*>", False, False + + # Unroll for the name, in case we're nullable. + return type.unroll().name, True, True + + if type.isJSString(): + if isMember: + raise TypeError("JSString not supported as member") + return "JS::Handle<JSString*>", False, False + + if type.isDOMString() or type.isUSVString(): + if isMember: + declType = "nsString" + else: + declType = "nsAString" + return declType, True, False + + if type.isByteString() or type.isUTF8String(): + # TODO(emilio): Maybe bytestrings could benefit from nsAutoCString + # or such too. + if type.isUTF8String() and not isMember: + declType = "nsACString" + else: + declType = "nsCString" + return declType, True, False + + if type.isEnum(): + return type.unroll().inner.identifier.name, False, True + + if type.isCallback() or type.isCallbackInterface(): + forceOwningType = optional or isMember + if type.nullable(): + if forceOwningType: + declType = "RefPtr<%s>" + else: + declType = "%s*" + else: + if forceOwningType: + declType = "OwningNonNull<%s>" + else: + declType = "%s&" + if type.isCallback(): + name = type.unroll().callback.identifier.name + else: + name = type.unroll().inner.identifier.name + return declType % name, False, False + + if type.isAny(): + # Don't do the rooting stuff for variadics for now + if isMember: + declType = "JS::Value" + else: + declType = "JS::Handle<JS::Value>" + return declType, False, False + + if type.isObject(): + if isMember: + declType = "JSObject*" + else: + declType = "JS::Handle<JSObject*>" + return declType, False, False + + if type.isDictionary(): + typeName = CGDictionary.makeDictionaryName(type.inner) + return typeName, True, True + + assert type.isPrimitive() + + return builtinNames[type.tag()], False, True + + def getArgType(self, type, optional, isMember): + """ + Get the type of an argument declaration. Returns the type CGThing, and + whether this should be a const ref. + + isMember can be False, "Sequence", or "Variadic" + """ + decl, ref, handleNullable = self.doGetArgType(type, optional, isMember) + decl = CGGeneric(decl) + if handleNullable and type.nullable(): + decl = CGTemplatedType("Nullable", decl) + ref = True + if isMember == "Variadic": + arrayType = "Sequence" if self.variadicIsSequence else "nsTArray" + decl = CGTemplatedType(arrayType, decl) + ref = True + elif optional: + # Note: All variadic args claim to be optional, but we can just use + # empty arrays to represent them not being present. + decl = CGTemplatedType("Optional", decl) + ref = True + return (decl, ref) + + def getArg(self, arg): + """ + Get the full argument declaration for an argument + """ + decl, ref = self.getArgType( + arg.type, arg.canHaveMissingValue(), "Variadic" if arg.variadic else False + ) + if ref: + decl = CGWrapper(decl, pre="const ", post="&") + + return Argument(decl.define(), arg.identifier.name) + + def arguments(self): + return self.member.signatures()[0][1] + + +class CGExampleMethod(CGNativeMember): + def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True): + CGNativeMember.__init__( + self, + descriptor, + method, + CGSpecializedMethod.makeNativeName(descriptor, method), + signature, + descriptor.getExtendedAttributes(method), + breakAfter=breakAfter, + variadicIsSequence=True, + ) + + def declare(self, cgClass): + assert self.member.isMethod() + # We skip declaring ourselves if this is a maplike/setlike/iterable + # method, because those get implemented automatically by the binding + # machinery, so the implementor of the interface doesn't have to worry + # about it. + if self.member.isMaplikeOrSetlikeOrIterableMethod(): + return "" + return CGNativeMember.declare(self, cgClass) + + def define(self, cgClass): + return "" + + +class CGExampleGetter(CGNativeMember): + def __init__(self, descriptor, attr): + CGNativeMember.__init__( + self, + descriptor, + attr, + CGSpecializedGetter.makeNativeName(descriptor, attr), + (attr.type, []), + descriptor.getExtendedAttributes(attr, getter=True), + ) + + def declare(self, cgClass): + assert self.member.isAttr() + # We skip declaring ourselves if this is a maplike/setlike attr (in + # practice, "size"), because those get implemented automatically by the + # binding machinery, so the implementor of the interface doesn't have to + # worry about it. + if self.member.isMaplikeOrSetlikeAttr(): + return "" + return CGNativeMember.declare(self, cgClass) + + def define(self, cgClass): + return "" + + +class CGExampleSetter(CGNativeMember): + def __init__(self, descriptor, attr): + CGNativeMember.__init__( + self, + descriptor, + attr, + CGSpecializedSetter.makeNativeName(descriptor, attr), + ( + BuiltinTypes[IDLBuiltinType.Types.undefined], + [FakeArgument(attr.type)], + ), + descriptor.getExtendedAttributes(attr, setter=True), + ) + + def define(self, cgClass): + return "" + + +class CGBindingImplClass(CGClass): + """ + Common codegen for generating a C++ implementation of a WebIDL interface + """ + + def __init__( + self, + descriptor, + cgMethod, + cgGetter, + cgSetter, + wantGetParent=True, + wrapMethodName="WrapObject", + skipStaticMethods=False, + ): + """ + cgMethod, cgGetter and cgSetter are classes used to codegen methods, + getters and setters. + """ + self.descriptor = descriptor + self._deps = descriptor.interface.getDeps() + + iface = descriptor.interface + + self.methodDecls = [] + + def appendMethod(m, isConstructor=False): + sigs = m.signatures() + for s in sigs[:-1]: + # Don't put a blank line after overloads, until we + # get to the last one. + self.methodDecls.append( + cgMethod(descriptor, m, s, isConstructor, breakAfter=False) + ) + self.methodDecls.append(cgMethod(descriptor, m, sigs[-1], isConstructor)) + + if iface.ctor(): + appendMethod(iface.ctor(), isConstructor=True) + for n in iface.legacyFactoryFunctions: + appendMethod(n, isConstructor=True) + for m in iface.members: + if m.isMethod(): + if m.isIdentifierLess(): + continue + if m.isMaplikeOrSetlikeOrIterableMethod(): + # Handled by generated code already + continue + if not m.isStatic() or not skipStaticMethods: + appendMethod(m) + elif m.isAttr(): + if m.isMaplikeOrSetlikeAttr() or m.type.isObservableArray(): + # Handled by generated code already + continue + self.methodDecls.append(cgGetter(descriptor, m)) + if not m.readonly: + self.methodDecls.append(cgSetter(descriptor, m)) + + # Now do the special operations + def appendSpecialOperation(name, op): + if op is None: + return + assert len(op.signatures()) == 1 + returnType, args = op.signatures()[0] + # Make a copy of the args, since we plan to modify them. + args = list(args) + if op.isGetter() or op.isDeleter(): + # This is a total hack. The '&' belongs with the + # type, not the name! But it works, and is simpler + # than trying to somehow make this pretty. + args.append( + FakeArgument( + BuiltinTypes[IDLBuiltinType.Types.boolean], name="&found" + ) + ) + if name == "Stringifier": + if op.isIdentifierLess(): + # XXXbz I wish we were consistent about our renaming here. + name = "Stringify" + else: + # We already added this method + return + if name == "LegacyCaller": + if op.isIdentifierLess(): + # XXXbz I wish we were consistent about our renaming here. + name = "LegacyCall" + else: + # We already added this method + return + self.methodDecls.append( + CGNativeMember( + descriptor, + op, + name, + (returnType, args), + descriptor.getExtendedAttributes(op), + ) + ) + + # Sort things by name so we get stable ordering in the output. + ops = sorted(descriptor.operations.items(), key=lambda x: x[0]) + for name, op in ops: + appendSpecialOperation(name, op) + # If we support indexed properties, then we need a Length() + # method so we know which indices are supported. + if descriptor.supportsIndexedProperties(): + # But we don't need it if we already have an infallible + # "length" attribute, which we often do. + haveLengthAttr = any( + m + for m in iface.members + if m.isAttr() + and CGSpecializedGetter.makeNativeName(descriptor, m) == "Length" + ) + if not haveLengthAttr: + self.methodDecls.append( + CGNativeMember( + descriptor, + FakeMember(), + "Length", + (BuiltinTypes[IDLBuiltinType.Types.unsigned_long], []), + [], + ), + ) + # And if we support named properties we need to be able to + # enumerate the supported names. + if descriptor.supportsNamedProperties(): + self.methodDecls.append( + CGNativeMember( + descriptor, + FakeMember(), + "GetSupportedNames", + ( + IDLSequenceType( + None, BuiltinTypes[IDLBuiltinType.Types.domstring] + ), + [], + ), + [], + ) + ) + + if descriptor.concrete: + wrapArgs = [ + Argument("JSContext*", "aCx"), + Argument("JS::Handle<JSObject*>", "aGivenProto"), + ] + if not descriptor.wrapperCache: + wrapReturnType = "bool" + wrapArgs.append(Argument("JS::MutableHandle<JSObject*>", "aReflector")) + else: + wrapReturnType = "JSObject*" + self.methodDecls.insert( + 0, + ClassMethod( + wrapMethodName, + wrapReturnType, + wrapArgs, + virtual=descriptor.wrapperCache, + breakAfterReturnDecl=" ", + override=descriptor.wrapperCache, + body=self.getWrapObjectBody(), + ), + ) + if descriptor.hasCEReactions(): + self.methodDecls.insert( + 0, + ClassMethod( + "GetDocGroup", + "DocGroup*", + [], + const=True, + breakAfterReturnDecl=" ", + body=self.getGetDocGroupBody(), + ), + ) + if wantGetParent: + self.methodDecls.insert( + 0, + ClassMethod( + "GetParentObject", + self.getGetParentObjectReturnType(), + [], + const=True, + breakAfterReturnDecl=" ", + body=self.getGetParentObjectBody(), + ), + ) + + # Invoke CGClass.__init__ in any subclasses afterwards to do the actual codegen. + + def getWrapObjectBody(self): + return None + + def getGetParentObjectReturnType(self): + # The lack of newline before the end of the string is on purpose. + return dedent( + """ + // This should return something that eventually allows finding a + // path to the global this object is associated with. Most simply, + // returning an actual global works. + nsIGlobalObject*""" + ) + + def getGetParentObjectBody(self): + return None + + def getGetDocGroupBody(self): + return None + + def deps(self): + return self._deps + + +class CGExampleObservableArrayCallback(CGNativeMember): + def __init__(self, descriptor, attr, callbackName): + assert attr.isAttr() + assert attr.type.isObservableArray() + CGNativeMember.__init__( + self, + descriptor, + attr, + self.makeNativeName(attr, callbackName), + ( + BuiltinTypes[IDLBuiltinType.Types.undefined], + [ + FakeArgument(attr.type.inner, "aValue"), + FakeArgument( + BuiltinTypes[IDLBuiltinType.Types.unsigned_long], "aIndex" + ), + ], + ), + ["needsErrorResult"], + ) + + def declare(self, cgClass): + assert self.member.isAttr() + assert self.member.type.isObservableArray() + return CGNativeMember.declare(self, cgClass) + + def define(self, cgClass): + return "" + + @staticmethod + def makeNativeName(attr, callbackName): + assert attr.isAttr() + nativeName = MakeNativeName(attr.identifier.name) + return "On" + callbackName + nativeName + + +class CGExampleClass(CGBindingImplClass): + """ + Codegen for the actual example class implementation for this descriptor + """ + + def __init__(self, descriptor): + CGBindingImplClass.__init__( + self, + descriptor, + CGExampleMethod, + CGExampleGetter, + CGExampleSetter, + wantGetParent=descriptor.wrapperCache, + ) + + self.parentIface = descriptor.interface.parent + if self.parentIface: + self.parentDesc = descriptor.getDescriptor(self.parentIface.identifier.name) + bases = [ClassBase(self.nativeLeafName(self.parentDesc))] + else: + bases = [ + ClassBase( + "nsISupports /* or NonRefcountedDOMObject if this is a non-refcounted object */" + ) + ] + if descriptor.wrapperCache: + bases.append( + ClassBase( + "nsWrapperCache /* Change wrapperCache in the binding configuration if you don't want this */" + ) + ) + + destructorVisibility = "protected" + if self.parentIface: + extradeclarations = ( + "public:\n" + " NS_DECL_ISUPPORTS_INHERITED\n" + " NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(%s, %s)\n" + "\n" + % ( + self.nativeLeafName(descriptor), + self.nativeLeafName(self.parentDesc), + ) + ) + else: + extradeclarations = ( + "public:\n" + " NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n" + " NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(%s)\n" + "\n" % self.nativeLeafName(descriptor) + ) + + if descriptor.interface.hasChildInterfaces(): + decorators = "" + else: + decorators = "final" + + for m in descriptor.interface.members: + if m.isAttr() and m.type.isObservableArray(): + self.methodDecls.append( + CGExampleObservableArrayCallback(descriptor, m, "Set") + ) + self.methodDecls.append( + CGExampleObservableArrayCallback(descriptor, m, "Delete") + ) + + CGClass.__init__( + self, + self.nativeLeafName(descriptor), + bases=bases, + constructors=[ClassConstructor([], visibility="public")], + destructor=ClassDestructor(visibility=destructorVisibility), + methods=self.methodDecls, + decorators=decorators, + extradeclarations=extradeclarations, + ) + + def define(self): + # Just override CGClass and do our own thing + nativeType = self.nativeLeafName(self.descriptor) + + ctordtor = fill( + """ + ${nativeType}::${nativeType}() + { + // Add |MOZ_COUNT_CTOR(${nativeType});| for a non-refcounted object. + } + + ${nativeType}::~${nativeType}() + { + // Add |MOZ_COUNT_DTOR(${nativeType});| for a non-refcounted object. + } + """, + nativeType=nativeType, + ) + + if self.parentIface: + ccImpl = fill( + """ + + // Only needed for refcounted objects. + # error "If you don't have members that need cycle collection, + # then remove all the cycle collection bits from this + # implementation and the corresponding header. If you do, you + # want NS_IMPL_CYCLE_COLLECTION_INHERITED(${nativeType}, + # ${parentType}, your, members, here)" + NS_IMPL_ADDREF_INHERITED(${nativeType}, ${parentType}) + NS_IMPL_RELEASE_INHERITED(${nativeType}, ${parentType}) + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType}) + NS_INTERFACE_MAP_END_INHERITING(${parentType}) + + """, + nativeType=nativeType, + parentType=self.nativeLeafName(self.parentDesc), + ) + else: + ccImpl = fill( + """ + + // Only needed for refcounted objects. + NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(${nativeType}) + NS_IMPL_CYCLE_COLLECTING_ADDREF(${nativeType}) + NS_IMPL_CYCLE_COLLECTING_RELEASE(${nativeType}) + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType}) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_END + + """, + nativeType=nativeType, + ) + + classImpl = ccImpl + ctordtor + "\n" + if self.descriptor.concrete: + if self.descriptor.wrapperCache: + reflectorArg = "" + reflectorPassArg = "" + returnType = "JSObject*" + else: + reflectorArg = ", JS::MutableHandle<JSObject*> aReflector" + reflectorPassArg = ", aReflector" + returnType = "bool" + classImpl += fill( + """ + ${returnType} + ${nativeType}::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto${reflectorArg}) + { + return ${ifaceName}_Binding::Wrap(aCx, this, aGivenProto${reflectorPassArg}); + } + + """, + returnType=returnType, + nativeType=nativeType, + reflectorArg=reflectorArg, + ifaceName=self.descriptor.name, + reflectorPassArg=reflectorPassArg, + ) + + return classImpl + + @staticmethod + def nativeLeafName(descriptor): + return descriptor.nativeType.split("::")[-1] + + +class CGExampleRoot(CGThing): + """ + Root codegen class for example implementation generation. Instantiate the + class and call declare or define to generate header or cpp code, + respectively. + """ + + def __init__(self, config, interfaceName): + descriptor = config.getDescriptor(interfaceName) + + self.root = CGWrapper(CGExampleClass(descriptor), pre="\n", post="\n") + + self.root = CGNamespace.build(["mozilla", "dom"], self.root) + + builder = ForwardDeclarationBuilder() + if descriptor.hasCEReactions(): + builder.addInMozillaDom("DocGroup") + for member in descriptor.interface.members: + if not member.isAttr() and not member.isMethod(): + continue + if member.isStatic(): + builder.addInMozillaDom("GlobalObject") + if member.isAttr(): + if not member.isMaplikeOrSetlikeAttr(): + builder.forwardDeclareForType(member.type, config) + else: + assert member.isMethod() + if not member.isMaplikeOrSetlikeOrIterableMethod(): + for sig in member.signatures(): + builder.forwardDeclareForType(sig[0], config) + for arg in sig[1]: + builder.forwardDeclareForType(arg.type, config) + + self.root = CGList([builder.build(), self.root], "\n") + + # Throw in our #includes + self.root = CGHeaders( + [], + [], + [], + [], + [ + "nsWrapperCache.h", + "nsCycleCollectionParticipant.h", + "mozilla/Attributes.h", + "mozilla/ErrorResult.h", + "mozilla/dom/BindingDeclarations.h", + "js/TypeDecls.h", + ], + [ + "mozilla/dom/%s.h" % interfaceName, + ( + "mozilla/dom/%s" + % CGHeaders.getDeclarationFilename(descriptor.interface) + ), + ], + "", + self.root, + ) + + # And now some include guards + self.root = CGIncludeGuard(interfaceName, self.root) + + # And our license block comes before everything else + self.root = CGWrapper( + self.root, + pre=dedent( + """ + /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + /* vim:set ts=2 sw=2 sts=2 et cindent: */ + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + """ + ), + ) + + def declare(self): + return self.root.declare() + + def define(self): + return self.root.define() + + +def jsImplName(name): + return name + "JSImpl" + + +class CGJSImplMember(CGNativeMember): + """ + Base class for generating code for the members of the implementation class + for a JS-implemented WebIDL interface. + """ + + def __init__( + self, + descriptorProvider, + member, + name, + signature, + extendedAttrs, + breakAfter=True, + passJSBitsAsNeeded=True, + visibility="public", + variadicIsSequence=False, + virtual=False, + override=False, + ): + CGNativeMember.__init__( + self, + descriptorProvider, + member, + name, + signature, + extendedAttrs, + breakAfter=breakAfter, + passJSBitsAsNeeded=passJSBitsAsNeeded, + visibility=visibility, + variadicIsSequence=variadicIsSequence, + virtual=virtual, + override=override, + ) + self.body = self.getImpl() + + def getArgs(self, returnType, argList): + args = CGNativeMember.getArgs(self, returnType, argList) + args.append(Argument("JS::Realm*", "aRealm", "nullptr")) + return args + + +class CGJSImplMethod(CGJSImplMember): + """ + Class for generating code for the methods for a JS-implemented WebIDL + interface. + """ + + def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True): + self.signature = signature + self.descriptor = descriptor + self.isConstructor = isConstructor + CGJSImplMember.__init__( + self, + descriptor, + method, + CGSpecializedMethod.makeNativeName(descriptor, method), + signature, + descriptor.getExtendedAttributes(method), + breakAfter=breakAfter, + variadicIsSequence=True, + passJSBitsAsNeeded=False, + ) + + def getArgs(self, returnType, argList): + if self.isConstructor: + # Skip the JS::Compartment bits for constructors; it's handled + # manually in getImpl. But we do need our aGivenProto argument. We + # allow it to be omitted if the default proto is desired. + return CGNativeMember.getArgs(self, returnType, argList) + [ + Argument("JS::Handle<JSObject*>", "aGivenProto", "nullptr") + ] + return CGJSImplMember.getArgs(self, returnType, argList) + + def getImpl(self): + args = self.getArgs(self.signature[0], self.signature[1]) + if not self.isConstructor: + return "return mImpl->%s(%s);\n" % ( + self.name, + ", ".join(arg.name for arg in args), + ) + + assert self.descriptor.interface.isJSImplemented() + if self.name != "Constructor": + raise TypeError( + "Named constructors are not supported for JS implemented WebIDL. See bug 851287." + ) + if len(self.signature[1]) != 0: + # The first two arguments to the constructor implementation are not + # arguments to the WebIDL constructor, so don't pass them to + # __Init(). The last argument is the prototype we're supposed to + # use, and shouldn't get passed to __Init() either. + assert args[0].argType == "const GlobalObject&" + assert args[1].argType == "JSContext*" + assert args[-1].argType == "JS::Handle<JSObject*>" + assert args[-1].name == "aGivenProto" + constructorArgs = [arg.name for arg in args[2:-1]] + constructorArgs.append("js::GetNonCCWObjectRealm(scopeObj)") + initCall = fill( + """ + // Wrap the object before calling __Init so that __DOM_IMPL__ is available. + JS::Rooted<JSObject*> scopeObj(cx, global.Get()); + MOZ_ASSERT(js::IsObjectInContextCompartment(scopeObj, cx)); + JS::Rooted<JS::Value> wrappedVal(cx); + if (!GetOrCreateDOMReflector(cx, impl, &wrappedVal, aGivenProto)) { + MOZ_ASSERT(JS_IsExceptionPending(cx)); + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + // Initialize the object with the constructor arguments. + impl->mImpl->__Init(${args}); + if (aRv.Failed()) { + return nullptr; + } + """, + args=", ".join(constructorArgs), + ) + else: + initCall = "" + return fill( + """ + RefPtr<${implClass}> impl = + ConstructJSImplementation<${implClass}>("${contractId}", global, aRv); + if (aRv.Failed()) { + return nullptr; + } + $*{initCall} + return impl.forget(); + """, + contractId=self.descriptor.interface.getJSImplementation(), + implClass=self.descriptor.name, + initCall=initCall, + ) + + +# We're always fallible +def callbackGetterName(attr, descriptor): + return "Get" + MakeNativeName( + descriptor.binaryNameFor(attr.identifier.name, attr.isStatic()) + ) + + +def callbackSetterName(attr, descriptor): + return "Set" + MakeNativeName( + descriptor.binaryNameFor(attr.identifier.name, attr.isStatic()) + ) + + +class CGJSImplGetter(CGJSImplMember): + """ + Class for generating code for the getters of attributes for a JS-implemented + WebIDL interface. + """ + + def __init__(self, descriptor, attr): + CGJSImplMember.__init__( + self, + descriptor, + attr, + CGSpecializedGetter.makeNativeName(descriptor, attr), + (attr.type, []), + descriptor.getExtendedAttributes(attr, getter=True), + passJSBitsAsNeeded=False, + ) + + def getImpl(self): + callbackArgs = [arg.name for arg in self.getArgs(self.member.type, [])] + return "return mImpl->%s(%s);\n" % ( + callbackGetterName(self.member, self.descriptorProvider), + ", ".join(callbackArgs), + ) + + +class CGJSImplSetter(CGJSImplMember): + """ + Class for generating code for the setters of attributes for a JS-implemented + WebIDL interface. + """ + + def __init__(self, descriptor, attr): + CGJSImplMember.__init__( + self, + descriptor, + attr, + CGSpecializedSetter.makeNativeName(descriptor, attr), + ( + BuiltinTypes[IDLBuiltinType.Types.undefined], + [FakeArgument(attr.type)], + ), + descriptor.getExtendedAttributes(attr, setter=True), + passJSBitsAsNeeded=False, + ) + + def getImpl(self): + callbackArgs = [ + arg.name + for arg in self.getArgs( + BuiltinTypes[IDLBuiltinType.Types.undefined], + [FakeArgument(self.member.type)], + ) + ] + return "mImpl->%s(%s);\n" % ( + callbackSetterName(self.member, self.descriptorProvider), + ", ".join(callbackArgs), + ) + + +class CGJSImplClass(CGBindingImplClass): + def __init__(self, descriptor): + CGBindingImplClass.__init__( + self, + descriptor, + CGJSImplMethod, + CGJSImplGetter, + CGJSImplSetter, + skipStaticMethods=True, + ) + + if descriptor.interface.parent: + parentClass = descriptor.getDescriptor( + descriptor.interface.parent.identifier.name + ).jsImplParent + baseClasses = [ClassBase(parentClass)] + isupportsDecl = "NS_DECL_ISUPPORTS_INHERITED\n" + ccDecl = "NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(%s, %s)\n" % ( + descriptor.name, + parentClass, + ) + extradefinitions = fill( + """ + NS_IMPL_CYCLE_COLLECTION_INHERITED(${ifaceName}, ${parentClass}, mImpl, mParent) + NS_IMPL_ADDREF_INHERITED(${ifaceName}, ${parentClass}) + NS_IMPL_RELEASE_INHERITED(${ifaceName}, ${parentClass}) + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${ifaceName}) + NS_INTERFACE_MAP_END_INHERITING(${parentClass}) + """, + ifaceName=self.descriptor.name, + parentClass=parentClass, + ) + else: + baseClasses = [ + ClassBase("nsSupportsWeakReference"), + ClassBase("nsWrapperCache"), + ] + isupportsDecl = "NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n" + ccDecl = ( + "NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(%s)\n" % descriptor.name + ) + extradefinitions = fill( + """ + NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(${ifaceName}) + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(${ifaceName}) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + tmp->ClearWeakReferences(); + NS_IMPL_CYCLE_COLLECTION_UNLINK_END + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(${ifaceName}) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + NS_IMPL_CYCLE_COLLECTING_ADDREF(${ifaceName}) + NS_IMPL_CYCLE_COLLECTING_RELEASE(${ifaceName}) + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${ifaceName}) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_END + """, + ifaceName=self.descriptor.name, + ) + + extradeclarations = fill( + """ + public: + $*{isupportsDecl} + $*{ccDecl} + + private: + RefPtr<${jsImplName}> mImpl; + nsCOMPtr<nsIGlobalObject> mParent; + + """, + isupportsDecl=isupportsDecl, + ccDecl=ccDecl, + jsImplName=jsImplName(descriptor.name), + ) + + if descriptor.interface.getExtendedAttribute("WantsEventListenerHooks"): + # No need to do too much sanity checking here; the + # generated code will fail to compile if the methods we + # try to overrid aren't on a superclass. + self.methodDecls.extend( + self.getEventHookMethod(parentClass, "EventListenerAdded") + ) + self.methodDecls.extend( + self.getEventHookMethod(parentClass, "EventListenerRemoved") + ) + + if descriptor.interface.hasChildInterfaces(): + decorators = "" + # We need a protected virtual destructor our subclasses can use + destructor = ClassDestructor(virtual=True, visibility="protected") + else: + decorators = "final" + destructor = ClassDestructor(virtual=False, visibility="private") + + baseConstructors = [ + ( + "mImpl(new %s(nullptr, aJSImplObject, aJSImplGlobal, /* aIncumbentGlobal = */ nullptr))" + % jsImplName(descriptor.name) + ), + "mParent(aParent)", + ] + parentInterface = descriptor.interface.parent + while parentInterface: + if parentInterface.isJSImplemented(): + baseConstructors.insert( + 0, "%s(aJSImplObject, aJSImplGlobal, aParent)" % parentClass + ) + break + parentInterface = parentInterface.parent + if not parentInterface and descriptor.interface.parent: + # We only have C++ ancestors, so only pass along the window + baseConstructors.insert(0, "%s(aParent)" % parentClass) + + constructor = ClassConstructor( + [ + Argument("JS::Handle<JSObject*>", "aJSImplObject"), + Argument("JS::Handle<JSObject*>", "aJSImplGlobal"), + Argument("nsIGlobalObject*", "aParent"), + ], + visibility="public", + baseConstructors=baseConstructors, + ) + + self.methodDecls.append( + ClassMethod( + "_Create", + "bool", + JSNativeArguments(), + static=True, + body=self.getCreateFromExistingBody(), + ) + ) + + CGClass.__init__( + self, + descriptor.name, + bases=baseClasses, + constructors=[constructor], + destructor=destructor, + methods=self.methodDecls, + decorators=decorators, + extradeclarations=extradeclarations, + extradefinitions=extradefinitions, + ) + + def getWrapObjectBody(self): + return fill( + """ + JS::Rooted<JSObject*> obj(aCx, ${name}_Binding::Wrap(aCx, this, aGivenProto)); + if (!obj) { + return nullptr; + } + + // Now define it on our chrome object + JSAutoRealm ar(aCx, mImpl->CallbackGlobalOrNull()); + if (!JS_WrapObject(aCx, &obj)) { + return nullptr; + } + JS::Rooted<JSObject*> callback(aCx, mImpl->CallbackOrNull()); + if (!JS_DefineProperty(aCx, callback, "__DOM_IMPL__", obj, 0)) { + return nullptr; + } + return obj; + """, + name=self.descriptor.name, + ) + + def getGetParentObjectReturnType(self): + return "nsISupports*" + + def getGetParentObjectBody(self): + return "return mParent;\n" + + def getGetDocGroupBody(self): + return dedent( + """ + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mParent); + if (!window) { + return nullptr; + } + return window->GetDocGroup(); + """ + ) + + def getCreateFromExistingBody(self): + # XXXbz we could try to get parts of this (e.g. the argument + # conversions) auto-generated by somehow creating an IDLMethod and + # adding it to our interface, but we'd still need to special-case the + # implementation slightly to have it not try to forward to the JS + # object... + return fill( + """ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (!args.requireAtLeast(cx, "${ifaceName}._create", 2)) { + return false; + } + BindingCallContext callCx(cx, "${ifaceName}._create"); + if (!args[0].isObject()) { + return callCx.ThrowErrorMessage<MSG_NOT_OBJECT>("Argument 1"); + } + if (!args[1].isObject()) { + return callCx.ThrowErrorMessage<MSG_NOT_OBJECT>("Argument 2"); + } + + // GlobalObject will go through wrappers as needed for us, and + // is simpler than the right UnwrapArg incantation. + GlobalObject global(cx, &args[0].toObject()); + if (global.Failed()) { + return false; + } + nsCOMPtr<nsIGlobalObject> globalHolder = do_QueryInterface(global.GetAsSupports()); + MOZ_ASSERT(globalHolder); + JS::Rooted<JSObject*> arg(cx, &args[1].toObject()); + JS::Rooted<JSObject*> argGlobal(cx, JS::CurrentGlobalOrNull(cx)); + RefPtr<${implName}> impl = new ${implName}(arg, argGlobal, globalHolder); + MOZ_ASSERT(js::IsObjectInContextCompartment(arg, cx)); + return GetOrCreateDOMReflector(cx, impl, args.rval()); + """, + ifaceName=self.descriptor.interface.identifier.name, + implName=self.descriptor.name, + ) + + def getEventHookMethod(self, parentClass, methodName): + body = fill( + """ + ${parentClass}::${methodName}(aType); + mImpl->${methodName}(Substring(nsDependentAtomString(aType), 2), IgnoreErrors()); + """, + parentClass=parentClass, + methodName=methodName, + ) + return [ + ClassMethod( + methodName, + "void", + [Argument("nsAtom*", "aType")], + virtual=True, + override=True, + body=body, + ), + ClassUsingDeclaration(parentClass, methodName), + ] + + +def isJSImplementedDescriptor(descriptorProvider): + return ( + isinstance(descriptorProvider, Descriptor) + and descriptorProvider.interface.isJSImplemented() + ) + + +class CGCallback(CGClass): + def __init__( + self, idlObject, descriptorProvider, baseName, methods, getters=[], setters=[] + ): + self.baseName = baseName + self._deps = idlObject.getDeps() + self.idlObject = idlObject + self.name = idlObject.identifier.name + if isJSImplementedDescriptor(descriptorProvider): + self.name = jsImplName(self.name) + # For our public methods that needThisHandling we want most of the + # same args and the same return type as what CallbackMember + # generates. So we want to take advantage of all its + # CGNativeMember infrastructure, but that infrastructure can't deal + # with templates and most especially template arguments. So just + # cheat and have CallbackMember compute all those things for us. + realMethods = [] + for method in methods: + if not isinstance(method, CallbackMember) or not method.needThisHandling: + realMethods.append(method) + else: + realMethods.extend(self.getMethodImpls(method)) + realMethods.append( + ClassMethod( + "operator==", + "bool", + [Argument("const %s&" % self.name, "aOther")], + inline=True, + bodyInHeader=True, + const=True, + body=("return %s::operator==(aOther);\n" % baseName), + ) + ) + CGClass.__init__( + self, + self.name, + bases=[ClassBase(baseName)], + constructors=self.getConstructors(), + methods=realMethods + getters + setters, + ) + + def getConstructors(self): + if ( + not self.idlObject.isInterface() + and not self.idlObject._treatNonObjectAsNull + ): + body = "MOZ_ASSERT(JS::IsCallable(mCallback));\n" + else: + # Not much we can assert about it, other than not being null, and + # CallbackObject does that already. + body = "" + return [ + ClassConstructor( + [ + Argument("JSContext*", "aCx"), + Argument("JS::Handle<JSObject*>", "aCallback"), + Argument("JS::Handle<JSObject*>", "aCallbackGlobal"), + Argument("nsIGlobalObject*", "aIncumbentGlobal"), + ], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=[ + "%s(aCx, aCallback, aCallbackGlobal, aIncumbentGlobal)" + % self.baseName, + ], + body=body, + ), + ClassConstructor( + [ + Argument("JSObject*", "aCallback"), + Argument("JSObject*", "aCallbackGlobal"), + Argument("const FastCallbackConstructor&", ""), + ], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=[ + "%s(aCallback, aCallbackGlobal, FastCallbackConstructor())" + % self.baseName, + ], + body=body, + ), + ClassConstructor( + [ + Argument("JSObject*", "aCallback"), + Argument("JSObject*", "aCallbackGlobal"), + Argument("JSObject*", "aAsyncStack"), + Argument("nsIGlobalObject*", "aIncumbentGlobal"), + ], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=[ + "%s(aCallback, aCallbackGlobal, aAsyncStack, aIncumbentGlobal)" + % self.baseName, + ], + body=body, + ), + ] + + def getMethodImpls(self, method): + assert method.needThisHandling + args = list(method.args) + # Strip out the BindingCallContext&/JSObject* args + # that got added. + assert args[0].name == "cx" and args[0].argType == "BindingCallContext&" + assert args[1].name == "aThisVal" and args[1].argType == "JS::Handle<JS::Value>" + args = args[2:] + + # Now remember which index the ErrorResult argument is at; + # we'll need this below. + assert args[-1].name == "aRv" and args[-1].argType == "ErrorResult&" + rvIndex = len(args) - 1 + assert rvIndex >= 0 + + # Record the names of all the arguments, so we can use them when we call + # the private method. + argnames = [arg.name for arg in args] + argnamesWithThis = ["s.GetCallContext()", "thisValJS"] + argnames + argnamesWithoutThis = [ + "s.GetCallContext()", + "JS::UndefinedHandleValue", + ] + argnames + # Now that we've recorded the argnames for our call to our private + # method, insert our optional argument for the execution reason. + args.append(Argument("const char*", "aExecutionReason", "nullptr")) + + # Make copies of the arg list for the two "without rv" overloads. Note + # that those don't need aExceptionHandling or aRealm arguments because + # those would make not sense anyway: the only sane thing to do with + # exceptions in the "without rv" cases is to report them. + argsWithoutRv = list(args) + argsWithoutRv.pop(rvIndex) + argsWithoutThisAndRv = list(argsWithoutRv) + + # Add the potional argument for deciding whether the CallSetup should + # re-throw exceptions on aRv. + args.append( + Argument("ExceptionHandling", "aExceptionHandling", "eReportExceptions") + ) + # And the argument for communicating when exceptions should really be + # rethrown. In particular, even when aExceptionHandling is + # eRethrowExceptions they won't get rethrown if aRealm is provided + # and its principal doesn't subsume either the callback or the + # exception. + args.append(Argument("JS::Realm*", "aRealm", "nullptr")) + # And now insert our template argument. + argsWithoutThis = list(args) + args.insert(0, Argument("const T&", "thisVal")) + argsWithoutRv.insert(0, Argument("const T&", "thisVal")) + + argnamesWithoutThisAndRv = [arg.name for arg in argsWithoutThisAndRv] + argnamesWithoutThisAndRv.insert(rvIndex, "IgnoreErrors()") + # If we just leave things like that, and have no actual arguments in the + # IDL, we will end up trying to call the templated "without rv" overload + # with "rv" as the thisVal. That's no good. So explicitly append the + # aExceptionHandling and aRealm values we need to end up matching the + # signature of our non-templated "with rv" overload. + argnamesWithoutThisAndRv.extend(["eReportExceptions", "nullptr"]) + + argnamesWithoutRv = [arg.name for arg in argsWithoutRv] + # Note that we need to insert at rvIndex + 1, since we inserted a + # thisVal arg at the start. + argnamesWithoutRv.insert(rvIndex + 1, "IgnoreErrors()") + + errorReturn = method.getDefaultRetval() + + setupCall = fill( + """ + MOZ_ASSERT(!aRv.Failed(), "Don't pass an already-failed ErrorResult to a callback!"); + if (!aExecutionReason) { + aExecutionReason = "${executionReason}"; + } + CallSetup s(this, aRv, aExecutionReason, aExceptionHandling, aRealm); + if (!s.GetContext()) { + MOZ_ASSERT(aRv.Failed()); + return${errorReturn}; + } + """, + errorReturn=errorReturn, + executionReason=method.getPrettyName(), + ) + + bodyWithThis = fill( + """ + $*{setupCall} + JS::Rooted<JS::Value> thisValJS(s.GetContext()); + if (!ToJSValue(s.GetContext(), thisVal, &thisValJS)) { + aRv.Throw(NS_ERROR_FAILURE); + return${errorReturn}; + } + return ${methodName}(${callArgs}); + """, + setupCall=setupCall, + errorReturn=errorReturn, + methodName=method.name, + callArgs=", ".join(argnamesWithThis), + ) + bodyWithoutThis = fill( + """ + $*{setupCall} + return ${methodName}(${callArgs}); + """, + setupCall=setupCall, + errorReturn=errorReturn, + methodName=method.name, + callArgs=", ".join(argnamesWithoutThis), + ) + bodyWithThisWithoutRv = fill( + """ + return ${methodName}(${callArgs}); + """, + methodName=method.name, + callArgs=", ".join(argnamesWithoutRv), + ) + bodyWithoutThisAndRv = fill( + """ + return ${methodName}(${callArgs}); + """, + methodName=method.name, + callArgs=", ".join(argnamesWithoutThisAndRv), + ) + + return [ + ClassMethod( + method.name, + method.returnType, + args, + bodyInHeader=True, + templateArgs=["typename T"], + body=bodyWithThis, + canRunScript=method.canRunScript, + ), + ClassMethod( + method.name, + method.returnType, + argsWithoutThis, + bodyInHeader=True, + body=bodyWithoutThis, + canRunScript=method.canRunScript, + ), + ClassMethod( + method.name, + method.returnType, + argsWithoutRv, + bodyInHeader=True, + templateArgs=["typename T"], + body=bodyWithThisWithoutRv, + canRunScript=method.canRunScript, + ), + ClassMethod( + method.name, + method.returnType, + argsWithoutThisAndRv, + bodyInHeader=True, + body=bodyWithoutThisAndRv, + canRunScript=method.canRunScript, + ), + method, + ] + + def deps(self): + return self._deps + + +class CGCallbackFunction(CGCallback): + def __init__(self, callback, descriptorProvider): + self.callback = callback + if callback.isConstructor(): + methods = [ConstructCallback(callback, descriptorProvider)] + else: + methods = [CallCallback(callback, descriptorProvider)] + CGCallback.__init__( + self, callback, descriptorProvider, "CallbackFunction", methods + ) + + def getConstructors(self): + return CGCallback.getConstructors(self) + [ + ClassConstructor( + [Argument("CallbackFunction*", "aOther")], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=["CallbackFunction(aOther)"], + ) + ] + + +class CGFastCallback(CGClass): + def __init__(self, idlObject): + self._deps = idlObject.getDeps() + baseName = idlObject.identifier.name + constructor = ClassConstructor( + [ + Argument("JSObject*", "aCallback"), + Argument("JSObject*", "aCallbackGlobal"), + ], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=[ + "%s(aCallback, aCallbackGlobal, FastCallbackConstructor())" % baseName, + ], + body="", + ) + + traceMethod = ClassMethod( + "Trace", + "void", + [Argument("JSTracer*", "aTracer")], + inline=True, + bodyInHeader=True, + visibility="public", + body="%s::Trace(aTracer);\n" % baseName, + ) + holdMethod = ClassMethod( + "FinishSlowJSInitIfMoreThanOneOwner", + "void", + [Argument("JSContext*", "aCx")], + inline=True, + bodyInHeader=True, + visibility="public", + body=("%s::FinishSlowJSInitIfMoreThanOneOwner(aCx);\n" % baseName), + ) + + CGClass.__init__( + self, + "Fast%s" % baseName, + bases=[ClassBase(baseName)], + constructors=[constructor], + methods=[traceMethod, holdMethod], + ) + + def deps(self): + return self._deps + + +class CGCallbackInterface(CGCallback): + def __init__(self, descriptor, spiderMonkeyInterfacesAreStructs=False): + iface = descriptor.interface + attrs = [ + m + for m in iface.members + if ( + m.isAttr() + and not m.isStatic() + and (not m.isMaplikeOrSetlikeAttr() or not iface.isJSImplemented()) + ) + ] + getters = [ + CallbackGetter(a, descriptor, spiderMonkeyInterfacesAreStructs) + for a in attrs + ] + setters = [ + CallbackSetter(a, descriptor, spiderMonkeyInterfacesAreStructs) + for a in attrs + if not a.readonly + ] + methods = [ + m + for m in iface.members + if ( + m.isMethod() + and not m.isStatic() + and not m.isIdentifierLess() + and ( + not m.isMaplikeOrSetlikeOrIterableMethod() + or not iface.isJSImplemented() + ) + ) + ] + methods = [ + CallbackOperation(m, sig, descriptor, spiderMonkeyInterfacesAreStructs) + for m in methods + for sig in m.signatures() + ] + + needInitId = False + if iface.isJSImplemented() and iface.ctor(): + sigs = descriptor.interface.ctor().signatures() + if len(sigs) != 1: + raise TypeError("We only handle one constructor. See bug 869268.") + methods.append(CGJSImplInitOperation(sigs[0], descriptor)) + needInitId = True + + idlist = [ + descriptor.binaryNameFor(m.identifier.name, m.isStatic()) + for m in iface.members + if m.isAttr() or m.isMethod() + ] + if needInitId: + idlist.append("__init") + + if iface.isJSImplemented() and iface.getExtendedAttribute( + "WantsEventListenerHooks" + ): + methods.append(CGJSImplEventHookOperation(descriptor, "eventListenerAdded")) + methods.append( + CGJSImplEventHookOperation(descriptor, "eventListenerRemoved") + ) + idlist.append("eventListenerAdded") + idlist.append("eventListenerRemoved") + + if len(idlist) != 0: + methods.append(initIdsClassMethod(idlist, iface.identifier.name + "Atoms")) + CGCallback.__init__( + self, + iface, + descriptor, + "CallbackInterface", + methods, + getters=getters, + setters=setters, + ) + + +class FakeMember: + def __init__(self, name=None): + if name is not None: + self.identifier = FakeIdentifier(name) + + def isStatic(self): + return False + + def isAttr(self): + return False + + def isMethod(self): + return False + + def getExtendedAttribute(self, name): + # Claim to be a [NewObject] so we can avoid the "return a raw pointer" + # comments CGNativeMember codegen would otherwise stick in. + if name == "NewObject": + return True + return None + + +class CallbackMember(CGNativeMember): + # XXXbz It's OK to use CallbackKnownNotGray for wrapScope because + # CallSetup already handled the unmark-gray bits for us. we don't have + # anything better to use for 'obj', really... + def __init__( + self, + sig, + name, + descriptorProvider, + needThisHandling, + rethrowContentException=False, + spiderMonkeyInterfacesAreStructs=False, + wrapScope=None, + canRunScript=False, + passJSBitsAsNeeded=False, + ): + """ + needThisHandling is True if we need to be able to accept a specified + thisObj, False otherwise. + """ + assert not rethrowContentException or not needThisHandling + + self.retvalType = sig[0] + self.originalSig = sig + args = sig[1] + self.argCount = len(args) + if self.argCount > 0: + # Check for variadic arguments + lastArg = args[self.argCount - 1] + if lastArg.variadic: + self.argCountStr = "(%d - 1) + %s.Length()" % ( + self.argCount, + lastArg.identifier.name, + ) + else: + self.argCountStr = "%d" % self.argCount + self.needThisHandling = needThisHandling + # If needThisHandling, we generate ourselves as private and the caller + # will handle generating public versions that handle the "this" stuff. + visibility = "private" if needThisHandling else "public" + self.rethrowContentException = rethrowContentException + + self.wrapScope = wrapScope + # We don't care, for callback codegen, whether our original member was + # a method or attribute or whatnot. Just always pass FakeMember() + # here. + CGNativeMember.__init__( + self, + descriptorProvider, + FakeMember(), + name, + (self.retvalType, args), + extendedAttrs=["needsErrorResult"], + passJSBitsAsNeeded=passJSBitsAsNeeded, + visibility=visibility, + spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs, + canRunScript=canRunScript, + ) + # We have to do all the generation of our body now, because + # the caller relies on us throwing if we can't manage it. + self.body = self.getImpl() + + def getImpl(self): + setupCall = self.getCallSetup() + declRval = self.getRvalDecl() + if self.argCount > 0: + argvDecl = fill( + """ + JS::RootedVector<JS::Value> argv(cx); + if (!argv.resize(${argCount})) { + $*{failureCode} + return${errorReturn}; + } + """, + argCount=self.argCountStr, + failureCode=self.getArgvDeclFailureCode(), + errorReturn=self.getDefaultRetval(), + ) + else: + # Avoid weird 0-sized arrays + argvDecl = "" + convertArgs = self.getArgConversions() + doCall = self.getCall() + returnResult = self.getResultConversion() + + body = declRval + argvDecl + convertArgs + doCall + if self.needsScopeBody(): + body = "{\n" + indent(body) + "}\n" + return setupCall + body + returnResult + + def needsScopeBody(self): + return False + + def getArgvDeclFailureCode(self): + return dedent( + """ + // That threw an exception on the JSContext, and our CallSetup will do + // the right thing with that. + """ + ) + + def getExceptionCode(self, forResult): + return fill( + """ + aRv.Throw(NS_ERROR_UNEXPECTED); + return${defaultRetval}; + """, + defaultRetval=self.getDefaultRetval(), + ) + + def getResultConversion( + self, val="rval", failureCode=None, isDefinitelyObject=False, exceptionCode=None + ): + replacements = { + "val": val, + "holderName": "rvalHolder", + "declName": "rvalDecl", + # We actually want to pass in a null scope object here, because + # wrapping things into our current compartment (that of mCallback) + # is what we want. + "obj": "nullptr", + "passedToJSImpl": "false", + } + + if isJSImplementedDescriptor(self.descriptorProvider): + isCallbackReturnValue = "JSImpl" + else: + isCallbackReturnValue = "Callback" + sourceDescription = "return value of %s" % self.getPrettyName() + convertType = instantiateJSToNativeConversion( + getJSToNativeConversionInfo( + self.retvalType, + self.descriptorProvider, + failureCode=failureCode, + isDefinitelyObject=isDefinitelyObject, + exceptionCode=exceptionCode or self.getExceptionCode(forResult=True), + isCallbackReturnValue=isCallbackReturnValue, + # Allow returning a callback type that + # allows non-callable objects. + allowTreatNonCallableAsNull=True, + sourceDescription=sourceDescription, + ), + replacements, + ) + assignRetval = string.Template( + self.getRetvalInfo(self.retvalType, False)[2] + ).substitute(replacements) + type = convertType.define() + return type + assignRetval + + def getArgConversions(self): + # Just reget the arglist from self.originalSig, because our superclasses + # just have way to many members they like to clobber, so I can't find a + # safe member name to store it in. + argConversions = [ + self.getArgConversion(i, arg) for i, arg in enumerate(self.originalSig[1]) + ] + if not argConversions: + return "\n" + + # Do them back to front, so our argc modifications will work + # correctly, because we examine trailing arguments first. + argConversions.reverse() + # Wrap each one in a scope so that any locals it has don't leak out, and + # also so that we can just "break;" for our successCode. + argConversions = [ + CGWrapper(CGIndenter(CGGeneric(c)), pre="do {\n", post="} while (false);\n") + for c in argConversions + ] + if self.argCount > 0: + argConversions.insert(0, self.getArgcDecl()) + # And slap them together. + return CGList(argConversions, "\n").define() + "\n" + + def getArgConversion(self, i, arg): + argval = arg.identifier.name + + if arg.variadic: + argval = argval + "[idx]" + jsvalIndex = "%d + idx" % i + else: + jsvalIndex = "%d" % i + if arg.canHaveMissingValue(): + argval += ".Value()" + + if arg.type.isDOMString(): + # XPConnect string-to-JS conversion wants to mutate the string. So + # let's give it a string it can mutate + # XXXbz if we try to do a sequence of strings, this will kinda fail. + result = "mutableStr" + prepend = "nsString mutableStr(%s);\n" % argval + else: + result = argval + prepend = "" + + wrapScope = self.wrapScope + if arg.type.isUnion() and wrapScope is None: + prepend += ( + "JS::Rooted<JSObject*> callbackObj(cx, CallbackKnownNotGray());\n" + ) + wrapScope = "callbackObj" + + conversion = prepend + wrapForType( + arg.type, + self.descriptorProvider, + { + "result": result, + "successCode": "continue;\n" if arg.variadic else "break;\n", + "jsvalRef": "argv[%s]" % jsvalIndex, + "jsvalHandle": "argv[%s]" % jsvalIndex, + "obj": wrapScope, + "returnsNewObject": False, + "exceptionCode": self.getExceptionCode(forResult=False), + "spiderMonkeyInterfacesAreStructs": self.spiderMonkeyInterfacesAreStructs, + }, + ) + + if arg.variadic: + conversion = fill( + """ + for (uint32_t idx = 0; idx < ${arg}.Length(); ++idx) { + $*{conversion} + } + break; + """, + arg=arg.identifier.name, + conversion=conversion, + ) + elif arg.canHaveMissingValue(): + conversion = fill( + """ + if (${argName}.WasPassed()) { + $*{conversion} + } else if (argc == ${iPlus1}) { + // This is our current trailing argument; reduce argc + --argc; + } else { + argv[${i}].setUndefined(); + } + """, + argName=arg.identifier.name, + conversion=conversion, + iPlus1=i + 1, + i=i, + ) + return conversion + + def getDefaultRetval(self): + default = self.getRetvalInfo(self.retvalType, False)[1] + if len(default) != 0: + default = " " + default + return default + + def getArgs(self, returnType, argList): + args = CGNativeMember.getArgs(self, returnType, argList) + if not self.needThisHandling: + # Since we don't need this handling, we're the actual method that + # will be called, so we need an aRethrowExceptions argument. + if not self.rethrowContentException: + args.append(Argument("const char*", "aExecutionReason", "nullptr")) + args.append( + Argument( + "ExceptionHandling", "aExceptionHandling", "eReportExceptions" + ) + ) + args.append(Argument("JS::Realm*", "aRealm", "nullptr")) + return args + # We want to allow the caller to pass in a "this" value, as + # well as a BindingCallContext. + return [ + Argument("BindingCallContext&", "cx"), + Argument("JS::Handle<JS::Value>", "aThisVal"), + ] + args + + def getCallSetup(self): + if self.needThisHandling: + # It's been done for us already + return "" + callSetup = "CallSetup s(this, aRv" + if self.rethrowContentException: + # getArgs doesn't add the aExceptionHandling argument but does add + # aRealm for us. + callSetup += ( + ', "%s", eRethrowContentExceptions, aRealm, /* aIsJSImplementedWebIDL = */ ' + % self.getPrettyName() + ) + callSetup += toStringBool( + isJSImplementedDescriptor(self.descriptorProvider) + ) + else: + callSetup += ', "%s", aExceptionHandling, aRealm' % self.getPrettyName() + callSetup += ");\n" + return fill( + """ + $*{callSetup} + if (aRv.Failed()) { + return${errorReturn}; + } + MOZ_ASSERT(s.GetContext()); + BindingCallContext& cx = s.GetCallContext(); + + """, + callSetup=callSetup, + errorReturn=self.getDefaultRetval(), + ) + + def getArgcDecl(self): + return CGGeneric("unsigned argc = %s;\n" % self.argCountStr) + + @staticmethod + def ensureASCIIName(idlObject): + type = "attribute" if idlObject.isAttr() else "operation" + if re.match("[^\x20-\x7E]", idlObject.identifier.name): + raise SyntaxError( + 'Callback %s name "%s" contains non-ASCII ' + "characters. We can't handle that. %s" + % (type, idlObject.identifier.name, idlObject.location) + ) + if re.match('"', idlObject.identifier.name): + raise SyntaxError( + "Callback %s name '%s' contains " + "double-quote character. We can't handle " + "that. %s" % (type, idlObject.identifier.name, idlObject.location) + ) + + +class ConstructCallback(CallbackMember): + def __init__(self, callback, descriptorProvider): + self.callback = callback + CallbackMember.__init__( + self, + callback.signatures()[0], + "Construct", + descriptorProvider, + needThisHandling=False, + canRunScript=True, + ) + + def getRvalDecl(self): + # Box constructedObj for getJSToNativeConversionInfo(). + return "JS::Rooted<JS::Value> rval(cx);\n" + + def getCall(self): + if self.argCount > 0: + args = "JS::HandleValueArray::subarray(argv, 0, argc)" + else: + args = "JS::HandleValueArray::empty()" + + return fill( + """ + JS::Rooted<JS::Value> constructor(cx, JS::ObjectValue(*mCallback)); + JS::Rooted<JSObject*> constructedObj(cx); + if (!JS::Construct(cx, constructor, + ${args}, &constructedObj)) { + aRv.NoteJSContextException(cx); + return${errorReturn}; + } + rval.setObject(*constructedObj); + """, + args=args, + errorReturn=self.getDefaultRetval(), + ) + + def getResultConversion(self): + return CallbackMember.getResultConversion(self, isDefinitelyObject=True) + + def getPrettyName(self): + return self.callback.identifier.name + + +class CallbackMethod(CallbackMember): + def __init__( + self, + sig, + name, + descriptorProvider, + needThisHandling, + rethrowContentException=False, + spiderMonkeyInterfacesAreStructs=False, + canRunScript=False, + ): + CallbackMember.__init__( + self, + sig, + name, + descriptorProvider, + needThisHandling, + rethrowContentException, + spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs, + canRunScript=canRunScript, + ) + + def getRvalDecl(self): + return "JS::Rooted<JS::Value> rval(cx);\n" + + def getNoteCallFailed(self): + return fill( + """ + aRv.NoteJSContextException(cx); + return${errorReturn}; + """, + errorReturn=self.getDefaultRetval(), + ) + + def getCall(self): + if self.argCount > 0: + args = "JS::HandleValueArray::subarray(argv, 0, argc)" + else: + args = "JS::HandleValueArray::empty()" + + return fill( + """ + $*{declCallable} + $*{declThis} + if (${callGuard}!JS::Call(cx, ${thisVal}, callable, + ${args}, &rval)) { + $*{noteError} + } + """, + declCallable=self.getCallableDecl(), + declThis=self.getThisDecl(), + callGuard=self.getCallGuard(), + thisVal=self.getThisVal(), + args=args, + noteError=self.getNoteCallFailed(), + ) + + +class CallCallback(CallbackMethod): + def __init__(self, callback, descriptorProvider): + self.callback = callback + CallbackMethod.__init__( + self, + callback.signatures()[0], + "Call", + descriptorProvider, + needThisHandling=True, + canRunScript=not callback.isRunScriptBoundary(), + ) + + def getNoteCallFailed(self): + if self.retvalType.isPromise(): + return dedent( + """ + // Convert exception to a rejected promise. + // See https://heycam.github.io/webidl/#call-a-user-objects-operation + // step 12 and step 15.5. + return CreateRejectedPromiseFromThrownException(cx, aRv); + """ + ) + return CallbackMethod.getNoteCallFailed(self) + + def getExceptionCode(self, forResult): + # If the result value is a promise, and conversion + # to the promise throws an exception we shouldn't + # try to convert that exception to a promise again. + if self.retvalType.isPromise() and not forResult: + return dedent( + """ + // Convert exception to a rejected promise. + // See https://heycam.github.io/webidl/#call-a-user-objects-operation + // step 10 and step 15.5. + return CreateRejectedPromiseFromThrownException(cx, aRv); + """ + ) + return CallbackMethod.getExceptionCode(self, forResult) + + def getThisDecl(self): + return "" + + def getThisVal(self): + return "aThisVal" + + def getCallableDecl(self): + return "JS::Rooted<JS::Value> callable(cx, JS::ObjectValue(*mCallback));\n" + + def getPrettyName(self): + return self.callback.identifier.name + + def getCallGuard(self): + if self.callback._treatNonObjectAsNull: + return "JS::IsCallable(mCallback) && " + return "" + + +class CallbackOperationBase(CallbackMethod): + """ + Common class for implementing various callback operations. + """ + + def __init__( + self, + signature, + jsName, + nativeName, + descriptor, + singleOperation, + rethrowContentException=False, + spiderMonkeyInterfacesAreStructs=False, + ): + self.singleOperation = singleOperation + self.methodName = descriptor.binaryNameFor(jsName, False) + CallbackMethod.__init__( + self, + signature, + nativeName, + descriptor, + singleOperation, + rethrowContentException, + spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs, + ) + + def getThisDecl(self): + if not self.singleOperation: + return "JS::Rooted<JS::Value> thisValue(cx, JS::ObjectValue(*mCallback));\n" + # This relies on getCallableDecl declaring a boolean + # isCallable in the case when we're a single-operation + # interface. + return dedent( + """ + JS::Rooted<JS::Value> thisValue(cx, isCallable ? aThisVal.get() + : JS::ObjectValue(*mCallback)); + """ + ) + + def getThisVal(self): + return "thisValue" + + def getCallableDecl(self): + getCallableFromProp = fill( + """ + ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx); + if ((reinterpret_cast<jsid*>(atomsCache)->isVoid() && + !InitIds(cx, atomsCache)) || + !GetCallableProperty(cx, atomsCache->${methodAtomName}, &callable)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return${errorReturn}; + } + """, + methodAtomName=CGDictionary.makeIdName(self.methodName), + atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms", + errorReturn=self.getDefaultRetval(), + ) + if not self.singleOperation: + return "JS::Rooted<JS::Value> callable(cx);\n" + getCallableFromProp + return fill( + """ + bool isCallable = JS::IsCallable(mCallback); + JS::Rooted<JS::Value> callable(cx); + if (isCallable) { + callable = JS::ObjectValue(*mCallback); + } else { + $*{getCallableFromProp} + } + """, + getCallableFromProp=getCallableFromProp, + ) + + def getCallGuard(self): + return "" + + +class CallbackOperation(CallbackOperationBase): + """ + Codegen actual WebIDL operations on callback interfaces. + """ + + def __init__(self, method, signature, descriptor, spiderMonkeyInterfacesAreStructs): + self.ensureASCIIName(method) + self.method = method + jsName = method.identifier.name + CallbackOperationBase.__init__( + self, + signature, + jsName, + MakeNativeName(descriptor.binaryNameFor(jsName, False)), + descriptor, + descriptor.interface.isSingleOperationInterface(), + rethrowContentException=descriptor.interface.isJSImplemented(), + spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs, + ) + + def getPrettyName(self): + return "%s.%s" % ( + self.descriptorProvider.interface.identifier.name, + self.method.identifier.name, + ) + + +class CallbackAccessor(CallbackMember): + """ + Shared superclass for CallbackGetter and CallbackSetter. + """ + + def __init__(self, attr, sig, name, descriptor, spiderMonkeyInterfacesAreStructs): + self.ensureASCIIName(attr) + self.attrName = attr.identifier.name + CallbackMember.__init__( + self, + sig, + name, + descriptor, + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented(), + spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs, + ) + + def getPrettyName(self): + return "%s.%s" % ( + self.descriptorProvider.interface.identifier.name, + self.attrName, + ) + + +class CallbackGetter(CallbackAccessor): + def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs): + CallbackAccessor.__init__( + self, + attr, + (attr.type, []), + callbackGetterName(attr, descriptor), + descriptor, + spiderMonkeyInterfacesAreStructs, + ) + + def getRvalDecl(self): + return "JS::Rooted<JS::Value> rval(cx);\n" + + def getCall(self): + return fill( + """ + JS::Rooted<JSObject *> callback(cx, mCallback); + ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx); + if ((reinterpret_cast<jsid*>(atomsCache)->isVoid() + && !InitIds(cx, atomsCache)) || + !JS_GetPropertyById(cx, callback, atomsCache->${attrAtomName}, &rval)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return${errorReturn}; + } + """, + atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms", + attrAtomName=CGDictionary.makeIdName( + self.descriptorProvider.binaryNameFor(self.attrName, False) + ), + errorReturn=self.getDefaultRetval(), + ) + + +class CallbackSetter(CallbackAccessor): + def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs): + CallbackAccessor.__init__( + self, + attr, + ( + BuiltinTypes[IDLBuiltinType.Types.undefined], + [FakeArgument(attr.type)], + ), + callbackSetterName(attr, descriptor), + descriptor, + spiderMonkeyInterfacesAreStructs, + ) + + def getRvalDecl(self): + # We don't need an rval + return "" + + def getCall(self): + return fill( + """ + MOZ_ASSERT(argv.length() == 1); + JS::Rooted<JSObject*> callback(cx, CallbackKnownNotGray()); + ${atomCacheName}* atomsCache = GetAtomCache<${atomCacheName}>(cx); + if ((reinterpret_cast<jsid*>(atomsCache)->isVoid() && + !InitIds(cx, atomsCache)) || + !JS_SetPropertyById(cx, callback, atomsCache->${attrAtomName}, argv[0])) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return${errorReturn}; + } + """, + atomCacheName=self.descriptorProvider.interface.identifier.name + "Atoms", + attrAtomName=CGDictionary.makeIdName( + self.descriptorProvider.binaryNameFor(self.attrName, False) + ), + errorReturn=self.getDefaultRetval(), + ) + + def getArgcDecl(self): + return None + + +class CGJSImplInitOperation(CallbackOperationBase): + """ + Codegen the __Init() method used to pass along constructor arguments for JS-implemented WebIDL. + """ + + def __init__(self, sig, descriptor): + assert sig in descriptor.interface.ctor().signatures() + CallbackOperationBase.__init__( + self, + (BuiltinTypes[IDLBuiltinType.Types.undefined], sig[1]), + "__init", + "__Init", + descriptor, + singleOperation=False, + rethrowContentException=True, + spiderMonkeyInterfacesAreStructs=True, + ) + + def getPrettyName(self): + return "__init" + + +class CGJSImplEventHookOperation(CallbackOperationBase): + """ + Codegen the hooks on a JS impl for adding/removing event listeners. + """ + + def __init__(self, descriptor, name): + self.name = name + + CallbackOperationBase.__init__( + self, + ( + BuiltinTypes[IDLBuiltinType.Types.undefined], + [FakeArgument(BuiltinTypes[IDLBuiltinType.Types.domstring], "aType")], + ), + name, + MakeNativeName(name), + descriptor, + singleOperation=False, + rethrowContentException=False, + spiderMonkeyInterfacesAreStructs=True, + ) + + def getPrettyName(self): + return self.name + + +def getMaplikeOrSetlikeErrorReturn(helperImpl): + """ + Generate return values based on whether a maplike or setlike generated + method is an interface method (which returns bool) or a helper function + (which uses ErrorResult). + """ + if helperImpl: + return dedent( + """ + aRv.Throw(NS_ERROR_UNEXPECTED); + return%s; + """ + % helperImpl.getDefaultRetval() + ) + return "return false;\n" + + +def getMaplikeOrSetlikeBackingObject(descriptor, maplikeOrSetlike, helperImpl=None): + """ + Generate code to get/create a JS backing object for a maplike/setlike + declaration from the declaration slot. + """ + func_prefix = maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title() + ret = fill( + """ + JS::Rooted<JSObject*> backingObj(cx); + bool created = false; + if (!Get${func_prefix}BackingObject(cx, obj, ${slot}, &backingObj, &created)) { + $*{errorReturn} + } + if (created) { + PreserveWrapper<${selfType}>(self); + } + """, + slot=memberReservedSlot(maplikeOrSetlike, descriptor), + func_prefix=func_prefix, + errorReturn=getMaplikeOrSetlikeErrorReturn(helperImpl), + selfType=descriptor.nativeType, + ) + return ret + + +def getMaplikeOrSetlikeSizeGetterBody(descriptor, attr): + """ + Creates the body for the size getter method of maplike/setlike interfaces. + """ + # We should only have one declaration attribute currently + assert attr.identifier.name == "size" + assert attr.isMaplikeOrSetlikeAttr() + return fill( + """ + $*{getBackingObj} + uint32_t result = JS::${funcPrefix}Size(cx, backingObj); + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + args.rval().setNumber(result); + return true; + """, + getBackingObj=getMaplikeOrSetlikeBackingObject( + descriptor, attr.maplikeOrSetlike + ), + funcPrefix=attr.maplikeOrSetlike.prefix, + ) + + +class CGMaplikeOrSetlikeMethodGenerator(CGThing): + """ + Creates methods for maplike/setlike interfaces. It is expected that all + methods will be have a maplike/setlike object attached. Unwrapping/wrapping + will be taken care of by the usual method generation machinery in + CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of + using CGCallGenerator. + """ + + def __init__( + self, + descriptor, + maplikeOrSetlike, + methodName, + needsValueTypeReturn=False, + helperImpl=None, + ): + CGThing.__init__(self) + # True if this will be the body of a C++ helper function. + self.helperImpl = helperImpl + self.descriptor = descriptor + self.maplikeOrSetlike = maplikeOrSetlike + self.cgRoot = CGList([]) + impl_method_name = methodName + if impl_method_name[0] == "_": + # double underscore means this is a js-implemented chrome only rw + # function. Truncate the double underscore so calling the right + # underlying JSAPI function still works. + impl_method_name = impl_method_name[2:] + self.cgRoot.append( + CGGeneric( + getMaplikeOrSetlikeBackingObject( + self.descriptor, self.maplikeOrSetlike, self.helperImpl + ) + ) + ) + self.returnStmt = getMaplikeOrSetlikeErrorReturn(self.helperImpl) + + # Generates required code for the method. Method descriptions included + # in definitions below. Throw if we don't have a method to fill in what + # we're looking for. + try: + methodGenerator = getattr(self, impl_method_name) + except AttributeError: + raise TypeError( + "Missing %s method definition '%s'" + % (self.maplikeOrSetlike.maplikeOrSetlikeType, methodName) + ) + # Method generator returns tuple, containing: + # + # - a list of CGThings representing setup code for preparing to call + # the JS API function + # - a list of arguments needed for the JS API function we're calling + # - list of code CGThings needed for return value conversion. + (setupCode, arguments, setResult) = methodGenerator() + + # Create the actual method call, and then wrap it with the code to + # return the value if needed. + funcName = self.maplikeOrSetlike.prefix + MakeNativeName(impl_method_name) + # Append the list of setup code CGThings + self.cgRoot.append(CGList(setupCode)) + # Create the JS API call + code = dedent( + """ + if (!JS::${funcName}(${args})) { + $*{errorReturn} + } + """ + ) + + if needsValueTypeReturn: + assert self.helperImpl and impl_method_name == "get" + code += fill( + """ + if (result.isUndefined()) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return${retval}; + } + """, + retval=self.helperImpl.getDefaultRetval(), + ) + + self.cgRoot.append( + CGWrapper( + CGGeneric( + fill( + code, + funcName=funcName, + args=", ".join(["cx", "backingObj"] + arguments), + errorReturn=self.returnStmt, + ) + ) + ) + ) + # Append result conversion + self.cgRoot.append(CGList(setResult)) + + def mergeTuples(self, a, b): + """ + Expecting to take 2 tuples were all elements are lists, append the lists in + the second tuple to the lists in the first. + """ + return tuple([x + y for x, y in zip(a, b)]) + + def appendArgConversion(self, name): + """ + Generate code to convert arguments to JS::Values, so they can be + passed into JSAPI functions. + """ + return CGGeneric( + fill( + """ + JS::Rooted<JS::Value> ${name}Val(cx); + if (!ToJSValue(cx, ${name}, &${name}Val)) { + $*{errorReturn} + } + """, + name=name, + errorReturn=self.returnStmt, + ) + ) + + def appendKeyArgConversion(self): + """ + Generates the key argument for methods. Helper functions will use + a RootedVector<JS::Value>, while interface methods have separate JS::Values. + """ + if self.helperImpl: + return ([], ["argv[0]"], []) + return ([self.appendArgConversion("arg0")], ["arg0Val"], []) + + def appendKeyAndValueArgConversion(self): + """ + Generates arguments for methods that require a key and value. Helper + functions will use a RootedVector<JS::Value>, while interface methods have + separate JS::Values. + """ + r = self.appendKeyArgConversion() + if self.helperImpl: + return self.mergeTuples(r, ([], ["argv[1]"], [])) + return self.mergeTuples( + r, ([self.appendArgConversion("arg1")], ["arg1Val"], []) + ) + + def appendIteratorResult(self): + """ + Generate code to output JSObject* return values, needed for functions that + return iterators. Iterators cannot currently be wrapped via Xrays. If + something that would return an iterator is called via Xray, fail early. + """ + # TODO: Bug 1173651 - Remove check once bug 1023984 is fixed. + code = CGGeneric( + dedent( + """ + // TODO (Bug 1173651): Xrays currently cannot wrap iterators. Change + // after bug 1023984 is fixed. + if (xpc::WrapperFactory::IsXrayWrapper(obj)) { + JS_ReportErrorASCII(cx, "Xray wrapping of iterators not supported."); + return false; + } + JS::Rooted<JSObject*> result(cx); + JS::Rooted<JS::Value> v(cx); + """ + ) + ) + arguments = "&v" + setResult = CGGeneric( + dedent( + """ + result = &v.toObject(); + """ + ) + ) + return ([code], [arguments], [setResult]) + + def appendSelfResult(self): + """ + Generate code to return the interface object itself. + """ + code = CGGeneric( + dedent( + """ + JS::Rooted<JSObject*> result(cx); + """ + ) + ) + setResult = CGGeneric( + dedent( + """ + result = obj; + """ + ) + ) + return ([code], [], [setResult]) + + def appendBoolResult(self): + if self.helperImpl: + return ([CGGeneric("bool retVal;\n")], ["&retVal"], []) + return ([CGGeneric("bool result;\n")], ["&result"], []) + + def forEach(self): + """ + void forEach(callback c, any thisval); + + ForEach takes a callback, and a possible value to use as 'this'. The + callback needs to take value, key, and the interface object + implementing maplike/setlike. In order to make sure that the third arg + is our interface object instead of the map/set backing object, we + create a js function with the callback and original object in its + storage slots, then use a helper function in BindingUtils to make sure + the callback is called correctly. + """ + assert not self.helperImpl + code = [ + CGGeneric( + dedent( + """ + // Create a wrapper function. + JSFunction* func = js::NewFunctionWithReserved(cx, ForEachHandler, 3, 0, nullptr); + if (!func) { + return false; + } + JS::Rooted<JSObject*> funcObj(cx, JS_GetFunctionObject(func)); + JS::Rooted<JS::Value> funcVal(cx, JS::ObjectValue(*funcObj)); + js::SetFunctionNativeReserved(funcObj, FOREACH_CALLBACK_SLOT, + JS::ObjectValue(*arg0)); + js::SetFunctionNativeReserved(funcObj, FOREACH_MAPLIKEORSETLIKEOBJ_SLOT, + JS::ObjectValue(*obj)); + """ + ) + ) + ] + arguments = ["funcVal", "arg1"] + return (code, arguments, []) + + def set(self): + """ + object set(key, value); + + Maplike only function, takes key and sets value to it, returns + interface object unless being called from a C++ helper. + """ + assert self.maplikeOrSetlike.isMaplike() + r = self.appendKeyAndValueArgConversion() + if self.helperImpl: + return r + return self.mergeTuples(r, self.appendSelfResult()) + + def add(self): + """ + object add(value); + + Setlike only function, adds value to set, returns interface object + unless being called from a C++ helper + """ + assert self.maplikeOrSetlike.isSetlike() + r = self.appendKeyArgConversion() + if self.helperImpl: + return r + return self.mergeTuples(r, self.appendSelfResult()) + + def get(self): + """ + type? get(key); + + Retrieves a value from a backing object based on the key. Returns value + if key is in backing object, undefined otherwise. + """ + assert self.maplikeOrSetlike.isMaplike() + r = self.appendKeyArgConversion() + + code = [] + # We don't need to create the result variable because it'll be created elsewhere + # for JSObject Get method + if not self.helperImpl or not self.helperImpl.needsScopeBody(): + code = [ + CGGeneric( + dedent( + """ + JS::Rooted<JS::Value> result(cx); + """ + ) + ) + ] + + arguments = ["&result"] + return self.mergeTuples(r, (code, arguments, [])) + + def has(self): + """ + bool has(key); + + Check if an entry exists in the backing object. Returns true if value + exists in backing object, false otherwise. + """ + return self.mergeTuples(self.appendKeyArgConversion(), self.appendBoolResult()) + + def keys(self): + """ + object keys(); + + Returns new object iterator with all keys from backing object. + """ + return self.appendIteratorResult() + + def values(self): + """ + object values(); + + Returns new object iterator with all values from backing object. + """ + return self.appendIteratorResult() + + def entries(self): + """ + object entries(); + + Returns new object iterator with all keys and values from backing + object. Keys will be null for set. + """ + return self.appendIteratorResult() + + def clear(self): + """ + void clear(); + + Removes all entries from map/set. + """ + return ([], [], []) + + def delete(self): + """ + bool delete(key); + + Deletes an entry from the backing object. Returns true if value existed + in backing object, false otherwise. + """ + return self.mergeTuples(self.appendKeyArgConversion(), self.appendBoolResult()) + + def define(self): + return self.cgRoot.define() + + +class CGHelperFunctionGenerator(CallbackMember): + """ + Generates code to allow C++ to perform operations. Gets a context from the + binding wrapper, turns arguments into JS::Values (via + CallbackMember/CGNativeMember argument conversion), then uses + getCall to generate the body for getting result, and maybe convert the + result into return type (via CallbackMember/CGNativeMember result + conversion) + """ + + class HelperFunction(CGAbstractMethod): + """ + Generates context retrieval code and rooted JSObject for interface for + method generator to use + """ + + def __init__(self, descriptor, name, args, code, returnType): + self.code = code + CGAbstractMethod.__init__(self, descriptor, name, returnType, args) + + def definition_body(self): + return self.code + + def __init__( + self, + descriptor, + name, + args, + returnType=BuiltinTypes[IDLBuiltinType.Types.undefined], + needsResultConversion=True, + ): + assert returnType.isType() + self.needsResultConversion = needsResultConversion + + # Run CallbackMember init function to generate argument conversion code. + # wrapScope is set to 'obj' when generating maplike or setlike helper + # functions, as we don't have access to the CallbackPreserveColor + # method. + CallbackMember.__init__( + self, + [returnType, args], + name, + descriptor, + False, + wrapScope="obj", + passJSBitsAsNeeded=typeNeedsCx(returnType), + ) + + # Wrap CallbackMember body code into a CGAbstractMethod to make + # generation easier. + self.implMethod = CGHelperFunctionGenerator.HelperFunction( + descriptor, name, self.args, self.body, self.returnType + ) + + def getCallSetup(self): + # If passJSBitsAsNeeded is true, it means the caller will provide a + # JSContext, so we don't need to create JSContext and enter + # UnprivilegedJunkScopeOrWorkerGlobal here. + code = "MOZ_ASSERT(self);\n" + if not self.passJSBitsAsNeeded: + code += dedent( + """ + AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + // It's safe to use UnprivilegedJunkScopeOrWorkerGlobal here because + // all we want is to wrap into _some_ scope and then unwrap to find + // the reflector, and wrapping has no side-effects. + JSObject* scope = UnprivilegedJunkScopeOrWorkerGlobal(fallible); + if (!scope) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return%s; + } + JSAutoRealm tempRealm(cx, scope); + """ + % self.getDefaultRetval() + ) + + code += dedent( + """ + JS::Rooted<JS::Value> v(cx); + if(!ToJSValue(cx, self, &v)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return%s; + } + // This is a reflector, but due to trying to name things + // similarly across method generators, it's called obj here. + JS::Rooted<JSObject*> obj(cx); + obj = js::UncheckedUnwrap(&v.toObject(), /* stopAtWindowProxy = */ false); + """ + % self.getDefaultRetval() + ) + + # We'd like wrap the inner code in a scope such that the code can use the + # same realm. So here we are creating the result variable outside of the + # scope. + if self.needsScopeBody(): + code += "JS::Rooted<JS::Value> result(cx);\n" + + return code + + def getArgs(self, returnType, argList): + # We don't need the context or the value. We'll generate those instead. + args = CGNativeMember.getArgs(self, returnType, argList) + # Prepend a pointer to the binding object onto the arguments + return [Argument(self.descriptorProvider.nativeType + "*", "self")] + args + + def needsScopeBody(self): + return self.passJSBitsAsNeeded + + def getArgvDeclFailureCode(self): + return "aRv.Throw(NS_ERROR_UNEXPECTED);\n" + + def getResultConversion(self): + if self.needsResultConversion: + code = "" + if self.needsScopeBody(): + code = dedent( + """ + if (!JS_WrapValue(cx, &result)) { + aRv.NoteJSContextException(cx); + return; + } + """ + ) + + failureCode = dedent("aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n") + + exceptionCode = None + if self.retvalType.isPrimitive(): + exceptionCode = dedent( + "aRv.NoteJSContextException(cx);\nreturn%s;\n" + % self.getDefaultRetval() + ) + + return code + CallbackMember.getResultConversion( + self, + "result", + failureCode=failureCode, + isDefinitelyObject=True, + exceptionCode=exceptionCode, + ) + + assignRetval = string.Template( + self.getRetvalInfo(self.retvalType, False)[2] + ).substitute( + { + "declName": "retVal", + } + ) + return assignRetval + + def getRvalDecl(self): + # hack to make sure we put JSAutoRealm inside the body scope + return "JSAutoRealm reflectorRealm(cx, obj);\n" + + def getArgcDecl(self): + # Don't need argc for anything. + return None + + def getCall(self): + assert False # Override me! + + def getPrettyName(self): + return self.name + + def declare(self): + return self.implMethod.declare() + + def define(self): + return self.implMethod.define() + + +class CGMaplikeOrSetlikeHelperFunctionGenerator(CGHelperFunctionGenerator): + """ + Generates code to allow C++ to perform operations on backing objects. Gets + a context from the binding wrapper, turns arguments into JS::Values (via + CallbackMember/CGNativeMember argument conversion), then uses + CGMaplikeOrSetlikeMethodGenerator to generate the body. + """ + + def __init__( + self, + descriptor, + maplikeOrSetlike, + name, + needsKeyArg=False, + needsValueArg=False, + needsValueTypeReturn=False, + needsBoolReturn=False, + needsResultConversion=True, + ): + self.maplikeOrSetlike = maplikeOrSetlike + self.needsValueTypeReturn = needsValueTypeReturn + + args = [] + if needsKeyArg: + args.append(FakeArgument(maplikeOrSetlike.keyType, "aKey")) + if needsValueArg: + assert needsKeyArg + assert not needsValueTypeReturn + args.append(FakeArgument(maplikeOrSetlike.valueType, "aValue")) + + returnType = BuiltinTypes[IDLBuiltinType.Types.undefined] + if needsBoolReturn: + returnType = BuiltinTypes[IDLBuiltinType.Types.boolean] + elif needsValueTypeReturn: + returnType = maplikeOrSetlike.valueType + + CGHelperFunctionGenerator.__init__( + self, + descriptor, + name, + args, + returnType, + needsResultConversion, + ) + + def getCall(self): + return CGMaplikeOrSetlikeMethodGenerator( + self.descriptorProvider, + self.maplikeOrSetlike, + self.name.lower(), + self.needsValueTypeReturn, + helperImpl=self, + ).define() + + +class CGMaplikeOrSetlikeHelperGenerator(CGNamespace): + """ + Declares and defines convenience methods for accessing backing objects on + setlike/maplike interface. Generates function signatures, un/packs + backing objects from slot, etc. + """ + + def __init__(self, descriptor, maplikeOrSetlike): + self.descriptor = descriptor + # Since iterables are folded in with maplike/setlike, make sure we've + # got the right type here. + assert maplikeOrSetlike.isMaplike() or maplikeOrSetlike.isSetlike() + self.maplikeOrSetlike = maplikeOrSetlike + self.namespace = "%sHelpers" % ( + self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title() + ) + self.helpers = [ + CGMaplikeOrSetlikeHelperFunctionGenerator( + descriptor, maplikeOrSetlike, "Clear" + ), + CGMaplikeOrSetlikeHelperFunctionGenerator( + descriptor, + maplikeOrSetlike, + "Delete", + needsKeyArg=True, + needsBoolReturn=True, + needsResultConversion=False, + ), + CGMaplikeOrSetlikeHelperFunctionGenerator( + descriptor, + maplikeOrSetlike, + "Has", + needsKeyArg=True, + needsBoolReturn=True, + needsResultConversion=False, + ), + ] + if self.maplikeOrSetlike.isMaplike(): + self.helpers.append( + CGMaplikeOrSetlikeHelperFunctionGenerator( + descriptor, + maplikeOrSetlike, + "Set", + needsKeyArg=True, + needsValueArg=True, + ) + ) + self.helpers.append( + CGMaplikeOrSetlikeHelperFunctionGenerator( + descriptor, + maplikeOrSetlike, + "Get", + needsKeyArg=True, + needsValueTypeReturn=True, + ) + ) + else: + assert self.maplikeOrSetlike.isSetlike() + self.helpers.append( + CGMaplikeOrSetlikeHelperFunctionGenerator( + descriptor, maplikeOrSetlike, "Add", needsKeyArg=True + ) + ) + CGNamespace.__init__(self, self.namespace, CGList(self.helpers)) + + +class CGIterableMethodGenerator(CGGeneric): + """ + Creates methods for iterable interfaces. Unwrapping/wrapping + will be taken care of by the usual method generation machinery in + CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of + using CGCallGenerator. + """ + + def __init__(self, descriptor, methodName, args): + if methodName == "forEach": + assert len(args) == 2 + + CGGeneric.__init__( + self, + fill( + """ + if (!JS::IsCallable(arg0)) { + cx.ThrowErrorMessage<MSG_NOT_CALLABLE>("Argument 1"); + return false; + } + JS::RootedValueArray<3> callArgs(cx); + callArgs[2].setObject(*obj); + JS::Rooted<JS::Value> ignoredReturnVal(cx); + auto GetKeyAtIndex = &${selfType}::GetKeyAtIndex; + auto GetValueAtIndex = &${selfType}::GetValueAtIndex; + for (size_t i = 0; i < self->GetIterableLength(); ++i) { + if (!CallIterableGetter(cx, GetValueAtIndex, self, i, + callArgs[0])) { + return false; + } + if (!CallIterableGetter(cx, GetKeyAtIndex, self, i, + callArgs[1])) { + return false; + } + if (!JS::Call(cx, arg1, arg0, JS::HandleValueArray(callArgs), + &ignoredReturnVal)) { + return false; + } + } + """, + ifaceName=descriptor.interface.identifier.name, + selfType=descriptor.nativeType, + ), + ) + return + + if descriptor.interface.isIterable(): + assert descriptor.interface.maplikeOrSetlikeOrIterable.isPairIterator() + assert len(args) == 0 + + wrap = f"{descriptor.interface.identifier.name}Iterator_Binding::Wrap" + iterClass = f"mozilla::dom::binding_detail::WrappableIterableIterator<{descriptor.nativeType}, &{wrap}>" + else: + needReturnMethod = toStringBool( + descriptor.interface.maplikeOrSetlikeOrIterable.getExtendedAttribute( + "GenerateReturnMethod" + ) + is not None + ) + wrap = f"{descriptor.interface.identifier.name}AsyncIterator_Binding::Wrap" + iterClass = f"mozilla::dom::binding_detail::WrappableAsyncIterableIterator<{descriptor.nativeType}, {needReturnMethod}, &{wrap}>" + + createIterator = fill( + """ + typedef ${iterClass} itrType; + RefPtr<itrType> result(new itrType(self, + itrType::IteratorType::${itrMethod})); + """, + iterClass=iterClass, + itrMethod=methodName.title(), + ) + + if descriptor.interface.isAsyncIterable(): + args.append("initError") + createIterator = fill( + """ + $*{createIterator} + { + ErrorResult initError; + self->InitAsyncIteratorData(result->Data(), itrType::IteratorType::${itrMethod}, ${args}); + if (initError.MaybeSetPendingException(cx, "Asynchronous iterator initialization steps for ${ifaceName} failed")) { + return false; + } + } + """, + createIterator=createIterator, + itrMethod=methodName.title(), + args=", ".join(args), + ifaceName=descriptor.interface.identifier.name, + ) + + CGGeneric.__init__(self, createIterator) + + +def getObservableArrayBackingObject(descriptor, attr, errorReturn="return false;\n"): + """ + Generate code to get/create a JS backing list for an observableArray attribute + from the declaration slot. + """ + assert attr.isAttr() + assert attr.type.isObservableArray() + + # GetObservableArrayBackingObject may return a wrapped object for Xrays, so + # when we create it we need to unwrap it to store the interface in the + # reserved slot. + return fill( + """ + JS::Rooted<JSObject*> backingObj(cx); + bool created = false; + if (!GetObservableArrayBackingObject(cx, obj, ${slot}, + &backingObj, &created, ${namespace}::ObservableArrayProxyHandler::getInstance(), + self)) { + $*{errorReturn} + } + if (created) { + PreserveWrapper(self); + } + """, + namespace=toBindingNamespace(MakeNativeName(attr.identifier.name)), + slot=memberReservedSlot(attr, descriptor), + errorReturn=errorReturn, + selfType=descriptor.nativeType, + ) + + +def getObservableArrayGetterBody(descriptor, attr): + """ + Creates the body for the getter method of an observableArray attribute. + """ + assert attr.type.isObservableArray() + return fill( + """ + $*{getBackingObj} + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + args.rval().setObject(*backingObj); + return true; + """, + getBackingObj=getObservableArrayBackingObject(descriptor, attr), + ) + + +class CGObservableArrayProxyHandler_callback(ClassMethod): + """ + Base class for declaring and defining the hook methods for ObservableArrayProxyHandler + subclasses to get the interface native object from backing object and calls + its On{Set|Delete}* callback. + + * 'callbackType': "Set" or "Delete". + * 'invalidTypeFatal' (optional): If True, we don't expect the type + conversion would fail, so generate the + assertion code if type conversion fails. + """ + + def __init__( + self, descriptor, attr, name, args, callbackType, invalidTypeFatal=False + ): + assert attr.isAttr() + assert attr.type.isObservableArray() + assert callbackType in ["Set", "Delete"] + self.descriptor = descriptor + self.attr = attr + self.innertype = attr.type.inner + self.callbackType = callbackType + self.invalidTypeFatal = invalidTypeFatal + ClassMethod.__init__( + self, + name, + "bool", + args, + visibility="protected", + virtual=True, + override=True, + const=True, + ) + + def preConversion(self): + """ + The code to run before the conversion steps. + """ + return "" + + def preCallback(self): + """ + The code to run before calling the callback. + """ + return "" + + def postCallback(self): + """ + The code to run after calling the callback, all subclasses should override + this to generate the return values. + """ + assert False # Override me! + + def getBody(self): + exceptionCode = ( + fill( + """ + MOZ_ASSERT_UNREACHABLE("The item in ObservableArray backing list is not ${innertype}?"); + return false; + """, + innertype=self.innertype, + ) + if self.invalidTypeFatal + else None + ) + convertType = instantiateJSToNativeConversion( + getJSToNativeConversionInfo( + self.innertype, + self.descriptor, + sourceDescription="Element in ObservableArray backing list", + exceptionCode=exceptionCode, + ), + { + "declName": "decl", + "holderName": "holder", + "val": "aValue", + }, + ) + callbackArgs = ["decl", "aIndex", "rv"] + if typeNeedsCx(self.innertype): + callbackArgs.insert(0, "cx") + return fill( + """ + MOZ_ASSERT(IsObservableArrayProxy(aProxy)); + $*{preConversion} + + BindingCallContext cx(aCx, "ObservableArray ${name}"); + $*{convertType} + + $*{preCallback} + JS::Value val = js::GetProxyReservedSlot(aProxy, OBSERVABLE_ARRAY_DOM_INTERFACE_SLOT); + auto* interface = static_cast<${ifaceType}*>(val.toPrivate()); + MOZ_ASSERT(interface); + + ErrorResult rv; + MOZ_KnownLive(interface)->${methodName}(${callbackArgs}); + $*{postCallback} + """, + preConversion=self.preConversion(), + name=self.name, + convertType=convertType.define(), + preCallback=self.preCallback(), + ifaceType=self.descriptor.nativeType, + methodName="On%s%s" + % (self.callbackType, MakeNativeName(self.attr.identifier.name)), + callbackArgs=", ".join(callbackArgs), + postCallback=self.postCallback(), + ) + + +class CGObservableArrayProxyHandler_OnDeleteItem( + CGObservableArrayProxyHandler_callback +): + """ + Declares and defines the hook methods for ObservableArrayProxyHandler + subclasses to get the interface native object from backing object and calls + its OnDelete* callback. + """ + + def __init__(self, descriptor, attr): + args = [ + Argument("JSContext*", "aCx"), + Argument("JS::Handle<JSObject*>", "aProxy"), + Argument("JS::Handle<JS::Value>", "aValue"), + Argument("uint32_t", "aIndex"), + ] + CGObservableArrayProxyHandler_callback.__init__( + self, + descriptor, + attr, + "OnDeleteItem", + args, + "Delete", + True, + ) + + def postCallback(self): + return dedent( + """ + return !rv.MaybeSetPendingException(cx); + """ + ) + + +class CGObservableArrayProxyHandler_SetIndexedValue( + CGObservableArrayProxyHandler_callback +): + """ + Declares and defines the hook methods for ObservableArrayProxyHandler + subclasses to run the setting the indexed value steps. + """ + + def __init__(self, descriptor, attr): + args = [ + Argument("JSContext*", "aCx"), + Argument("JS::Handle<JSObject*>", "aProxy"), + Argument("JS::Handle<JSObject*>", "aBackingList"), + Argument("uint32_t", "aIndex"), + Argument("JS::Handle<JS::Value>", "aValue"), + Argument("JS::ObjectOpResult&", "aResult"), + ] + CGObservableArrayProxyHandler_callback.__init__( + self, + descriptor, + attr, + "SetIndexedValue", + args, + "Set", + ) + + def preConversion(self): + return dedent( + """ + uint32_t oldLen; + if (!JS::GetArrayLength(aCx, aBackingList, &oldLen)) { + return false; + } + + if (aIndex > oldLen) { + return aResult.failBadIndex(); + } + """ + ) + + def preCallback(self): + return dedent( + """ + if (aIndex < oldLen) { + JS::Rooted<JS::Value> value(aCx); + if (!JS_GetElement(aCx, aBackingList, aIndex, &value)) { + return false; + } + + if (!OnDeleteItem(aCx, aProxy, value, aIndex)) { + return false; + } + } + + """ + ) + + def postCallback(self): + return dedent( + """ + if (rv.MaybeSetPendingException(cx)) { + return false; + } + + if (!JS_SetElement(aCx, aBackingList, aIndex, aValue)) { + return false; + } + + return aResult.succeed(); + """ + ) + + +class CGObservableArrayProxyHandler(CGThing): + """ + A class for declaring a ObservableArrayProxyHandler. + """ + + def __init__(self, descriptor, attr): + assert attr.isAttr() + assert attr.type.isObservableArray() + methods = [ + # The item stored in backing object should always be converted successfully. + CGObservableArrayProxyHandler_OnDeleteItem(descriptor, attr), + CGObservableArrayProxyHandler_SetIndexedValue(descriptor, attr), + CGJSProxyHandler_getInstance("ObservableArrayProxyHandler"), + ] + parentClass = "mozilla::dom::ObservableArrayProxyHandler" + self.proxyHandler = CGClass( + "ObservableArrayProxyHandler", + bases=[ClassBase(parentClass)], + constructors=[], + methods=methods, + ) + + def declare(self): + # Our class declaration should happen when we're defining + return "" + + def define(self): + return self.proxyHandler.declare() + "\n" + self.proxyHandler.define() + + +class CGObservableArrayProxyHandlerGenerator(CGNamespace): + """ + Declares and defines convenience methods for accessing backing list objects + for observable array attribute. Generates function signatures, un/packs + backing list objects from slot, etc. + """ + + def __init__(self, descriptor, attr): + assert attr.isAttr() + assert attr.type.isObservableArray() + namespace = toBindingNamespace(MakeNativeName(attr.identifier.name)) + proxyHandler = CGObservableArrayProxyHandler(descriptor, attr) + CGNamespace.__init__(self, namespace, proxyHandler) + + +class CGObservableArraySetterGenerator(CGGeneric): + """ + Creates setter for an observableArray attributes. + """ + + def __init__(self, descriptor, attr): + assert attr.isAttr() + assert attr.type.isObservableArray() + getBackingObject = getObservableArrayBackingObject(descriptor, attr) + setElement = dedent( + """ + if (!JS_SetElement(cx, backingObj, i, val)) { + return false; + } + """ + ) + conversion = wrapForType( + attr.type.inner, + descriptor, + { + "result": "arg0.ElementAt(i)", + "successCode": setElement, + "jsvalRef": "val", + "jsvalHandle": "&val", + }, + ) + CGGeneric.__init__( + self, + fill( + """ + if (xpc::WrapperFactory::IsXrayWrapper(obj)) { + JS_ReportErrorASCII(cx, "Accessing from Xray wrapper is not supported."); + return false; + } + + ${getBackingObject} + const ObservableArrayProxyHandler* handler = GetObservableArrayProxyHandler(backingObj); + if (!handler->SetLength(cx, backingObj, 0)) { + return false; + } + + JS::Rooted<JS::Value> val(cx); + for (size_t i = 0; i < arg0.Length(); i++) { + $*{conversion} + } + """, + conversion=conversion, + getBackingObject=getBackingObject, + ), + ) + + +class CGObservableArrayHelperFunctionGenerator(CGHelperFunctionGenerator): + """ + Generates code to allow C++ to perform operations on backing objects. Gets + a context from the binding wrapper, turns arguments into JS::Values (via + CallbackMember/CGNativeMember argument conversion), then uses + MethodBodyGenerator to generate the body. + """ + + class MethodBodyGenerator(CGThing): + """ + Creates methods body for observable array attribute. It is expected that all + methods will be have a maplike/setlike object attached. Unwrapping/wrapping + will be taken care of by the usual method generation machinery in + CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of + using CGCallGenerator. + """ + + def __init__( + self, + descriptor, + attr, + methodName, + helperGenerator, + needsIndexArg, + ): + assert attr.isAttr() + assert attr.type.isObservableArray() + + CGThing.__init__(self) + self.helperGenerator = helperGenerator + self.cgRoot = CGList([]) + + self.cgRoot.append( + CGGeneric( + getObservableArrayBackingObject( + descriptor, + attr, + dedent( + """ + aRv.Throw(NS_ERROR_UNEXPECTED); + return%s; + """ + % helperGenerator.getDefaultRetval() + ), + ) + ) + ) + + # Generates required code for the method. Method descriptions included + # in definitions below. Throw if we don't have a method to fill in what + # we're looking for. + try: + methodGenerator = getattr(self, methodName) + except AttributeError: + raise TypeError( + "Missing observable array method definition '%s'" % methodName + ) + # Method generator returns tuple, containing: + # + # - a list of CGThings representing setup code for preparing to call + # the JS API function + # - JS API function name + # - a list of arguments needed for the JS API function we're calling + # - a list of CGThings representing code needed before return. + (setupCode, funcName, arguments, returnCode) = methodGenerator() + + # Append the list of setup code CGThings + self.cgRoot.append(CGList(setupCode)) + # Create the JS API call + if needsIndexArg: + arguments.insert(0, "aIndex") + self.cgRoot.append( + CGWrapper( + CGGeneric( + fill( + """ + aRv.MightThrowJSException(); + if (!${funcName}(${args})) { + aRv.StealExceptionFromJSContext(cx); + return${retval}; + } + """, + funcName=funcName, + args=", ".join(["cx", "backingObj"] + arguments), + retval=helperGenerator.getDefaultRetval(), + ) + ) + ) + ) + # Append code before return + self.cgRoot.append(CGList(returnCode)) + + def elementat(self): + setupCode = [] + if not self.helperGenerator.needsScopeBody(): + setupCode.append(CGGeneric("JS::Rooted<JS::Value> result(cx);\n")) + returnCode = [ + CGGeneric( + fill( + """ + if (result.isUndefined()) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return${retval}; + } + """, + retval=self.helperGenerator.getDefaultRetval(), + ) + ) + ] + return (setupCode, "JS_GetElement", ["&result"], returnCode) + + def replaceelementat(self): + setupCode = [ + CGGeneric( + fill( + """ + uint32_t length; + aRv.MightThrowJSException(); + if (!JS::GetArrayLength(cx, backingObj, &length)) { + aRv.StealExceptionFromJSContext(cx); + return${retval}; + } + if (aIndex > length) { + aRv.ThrowRangeError("Invalid index"); + return${retval}; + } + """, + retval=self.helperGenerator.getDefaultRetval(), + ) + ) + ] + return (setupCode, "JS_SetElement", ["argv[0]"], []) + + def appendelement(self): + setupCode = [ + CGGeneric( + fill( + """ + uint32_t length; + aRv.MightThrowJSException(); + if (!JS::GetArrayLength(cx, backingObj, &length)) { + aRv.StealExceptionFromJSContext(cx); + return${retval}; + } + """, + retval=self.helperGenerator.getDefaultRetval(), + ) + ) + ] + return (setupCode, "JS_SetElement", ["length", "argv[0]"], []) + + def removelastelement(self): + setupCode = [ + CGGeneric( + fill( + """ + uint32_t length; + aRv.MightThrowJSException(); + if (!JS::GetArrayLength(cx, backingObj, &length)) { + aRv.StealExceptionFromJSContext(cx); + return${retval}; + } + if (length == 0) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return${retval}; + } + """, + retval=self.helperGenerator.getDefaultRetval(), + ) + ) + ] + return (setupCode, "JS::SetArrayLength", ["length - 1"], []) + + def length(self): + return ( + [CGGeneric("uint32_t retVal;\n")], + "JS::GetArrayLength", + ["&retVal"], + [], + ) + + def define(self): + return self.cgRoot.define() + + def __init__( + self, + descriptor, + attr, + name, + returnType=BuiltinTypes[IDLBuiltinType.Types.undefined], + needsResultConversion=True, + needsIndexArg=False, + needsValueArg=False, + ): + assert attr.isAttr() + assert attr.type.isObservableArray() + self.attr = attr + self.needsIndexArg = needsIndexArg + + args = [] + if needsValueArg: + args.append(FakeArgument(attr.type.inner, "aValue")) + + CGHelperFunctionGenerator.__init__( + self, + descriptor, + name, + args, + returnType, + needsResultConversion, + ) + + def getArgs(self, returnType, argList): + if self.needsIndexArg: + argList = [ + FakeArgument(BuiltinTypes[IDLBuiltinType.Types.unsigned_long], "aIndex") + ] + argList + return CGHelperFunctionGenerator.getArgs(self, returnType, argList) + + def getCall(self): + return CGObservableArrayHelperFunctionGenerator.MethodBodyGenerator( + self.descriptorProvider, + self.attr, + self.name.lower(), + self, + self.needsIndexArg, + ).define() + + +class CGObservableArrayHelperGenerator(CGNamespace): + """ + Declares and defines convenience methods for accessing backing object for + observable array type. Generates function signatures, un/packs + backing objects from slot, etc. + """ + + def __init__(self, descriptor, attr): + assert attr.isAttr() + assert attr.type.isObservableArray() + + namespace = "%sHelpers" % MakeNativeName(attr.identifier.name) + helpers = [ + CGObservableArrayHelperFunctionGenerator( + descriptor, + attr, + "ElementAt", + returnType=attr.type.inner, + needsIndexArg=True, + ), + CGObservableArrayHelperFunctionGenerator( + descriptor, + attr, + "ReplaceElementAt", + needsIndexArg=True, + needsValueArg=True, + ), + CGObservableArrayHelperFunctionGenerator( + descriptor, + attr, + "AppendElement", + needsValueArg=True, + ), + CGObservableArrayHelperFunctionGenerator( + descriptor, + attr, + "RemoveLastElement", + ), + CGObservableArrayHelperFunctionGenerator( + descriptor, + attr, + "Length", + returnType=BuiltinTypes[IDLBuiltinType.Types.unsigned_long], + needsResultConversion=False, + ), + ] + CGNamespace.__init__(self, namespace, CGList(helpers, "\n")) + + +class GlobalGenRoots: + """ + Roots for global codegen. + + To generate code, call the method associated with the target, and then + call the appropriate define/declare method. + """ + + @staticmethod + def GeneratedAtomList(config): + # Atom enum + dictionaries = config.dictionaries + + structs = [] + + def memberToAtomCacheMember(binaryNameFor, m): + binaryMemberName = binaryNameFor(m) + return ClassMember( + CGDictionary.makeIdName(binaryMemberName), + "PinnedStringId", + visibility="public", + ) + + def buildAtomCacheStructure(idlobj, binaryNameFor, members): + classMembers = [memberToAtomCacheMember(binaryNameFor, m) for m in members] + structName = idlobj.identifier.name + "Atoms" + return ( + structName, + CGWrapper( + CGClass( + structName, bases=None, isStruct=True, members=classMembers + ), + post="\n", + ), + ) + + for dict in dictionaries: + if len(dict.members) == 0: + continue + + structs.append( + buildAtomCacheStructure(dict, lambda m: m.identifier.name, dict.members) + ) + + for d in config.getDescriptors(isJSImplemented=True) + config.getDescriptors( + isCallback=True + ): + members = [m for m in d.interface.members if m.isAttr() or m.isMethod()] + if d.interface.isJSImplemented() and d.interface.ctor(): + # We'll have an __init() method. + members.append(FakeMember("__init")) + if d.interface.isJSImplemented() and d.interface.getExtendedAttribute( + "WantsEventListenerHooks" + ): + members.append(FakeMember("eventListenerAdded")) + members.append(FakeMember("eventListenerRemoved")) + if len(members) == 0: + continue + + structs.append( + buildAtomCacheStructure( + d.interface, + lambda m: d.binaryNameFor(m.identifier.name, m.isStatic()), + members, + ) + ) + + structs.sort() + generatedStructs = [struct for structName, struct in structs] + structNames = [structName for structName, struct in structs] + + mainStruct = CGWrapper( + CGClass( + "PerThreadAtomCache", + bases=[ClassBase(structName) for structName in structNames], + isStruct=True, + ), + post="\n", + ) + + structs = CGList(generatedStructs + [mainStruct]) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(structs, pre="\n")) + curr = CGWrapper(curr, post="\n") + + # Add include statement for PinnedStringId. + declareIncludes = ["mozilla/dom/PinnedStringId.h"] + curr = CGHeaders([], [], [], [], declareIncludes, [], "GeneratedAtomList", curr) + + # Add include guards. + curr = CGIncludeGuard("GeneratedAtomList", curr) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Done. + return curr + + @staticmethod + def GeneratedEventList(config): + eventList = CGList([]) + for generatedEvent in config.generatedEvents: + eventList.append( + CGGeneric(declare=("GENERATED_EVENT(%s)\n" % generatedEvent)) + ) + return eventList + + @staticmethod + def PrototypeList(config): + + # Prototype ID enum. + descriptorsWithPrototype = config.getDescriptors( + hasInterfacePrototypeObject=True + ) + protos = [d.name for d in descriptorsWithPrototype] + idEnum = CGNamespacedEnum("id", "ID", ["_ID_Start"] + protos, [0, "_ID_Start"]) + idEnum = CGList([idEnum]) + + def fieldSizeAssert(amount, jitInfoField, message): + maxFieldValue = ( + "(uint64_t(1) << (sizeof(std::declval<JSJitInfo>().%s) * 8))" + % jitInfoField + ) + return CGGeneric( + define='static_assert(%s < %s, "%s");\n\n' + % (amount, maxFieldValue, message) + ) + + idEnum.append( + fieldSizeAssert("id::_ID_Count", "protoID", "Too many prototypes!") + ) + + # Wrap all of that in our namespaces. + idEnum = CGNamespace.build( + ["mozilla", "dom", "prototypes"], CGWrapper(idEnum, pre="\n") + ) + idEnum = CGWrapper(idEnum, post="\n") + + curr = CGList( + [ + CGGeneric(define="#include <stdint.h>\n"), + CGGeneric(define="#include <type_traits>\n\n"), + CGGeneric(define='#include "js/experimental/JitInfo.h"\n\n'), + CGGeneric(define='#include "mozilla/dom/BindingNames.h"\n\n'), + CGGeneric(define='#include "mozilla/dom/PrototypeList.h"\n\n'), + idEnum, + ] + ) + + # Let things know the maximum length of the prototype chain. + maxMacroName = "MAX_PROTOTYPE_CHAIN_LENGTH" + maxMacro = CGGeneric( + declare="#define " + maxMacroName + " " + str(config.maxProtoChainLength) + ) + curr.append(CGWrapper(maxMacro, post="\n\n")) + curr.append( + fieldSizeAssert( + maxMacroName, "depth", "Some inheritance chain is too long!" + ) + ) + + # Constructor ID enum. + constructors = [d.name for d in config.getDescriptors(hasInterfaceObject=True)] + idEnum = CGNamespacedEnum( + "id", + "ID", + ["_ID_Start"] + constructors, + ["prototypes::id::_ID_Count", "_ID_Start"], + ) + + # Wrap all of that in our namespaces. + idEnum = CGNamespace.build( + ["mozilla", "dom", "constructors"], CGWrapper(idEnum, pre="\n") + ) + idEnum = CGWrapper(idEnum, post="\n") + + curr.append(idEnum) + + # Named properties object enum. + namedPropertiesObjects = [ + d.name for d in config.getDescriptors(hasNamedPropertiesObject=True) + ] + idEnum = CGNamespacedEnum( + "id", + "ID", + ["_ID_Start"] + namedPropertiesObjects, + ["constructors::id::_ID_Count", "_ID_Start"], + ) + + # Wrap all of that in our namespaces. + idEnum = CGNamespace.build( + ["mozilla", "dom", "namedpropertiesobjects"], CGWrapper(idEnum, pre="\n") + ) + idEnum = CGWrapper(idEnum, post="\n") + + curr.append(idEnum) + + traitsDecls = [ + CGGeneric( + declare=dedent( + """ + template <prototypes::ID PrototypeID> + struct PrototypeTraits; + """ + ) + ) + ] + traitsDecls.extend(CGPrototypeTraitsClass(d) for d in descriptorsWithPrototype) + + ifaceNamesWithProto = [ + d.interface.getClassName() for d in descriptorsWithPrototype + ] + traitsDecls.append( + CGStringTable("NamesOfInterfacesWithProtos", ifaceNamesWithProto) + ) + + traitsDecl = CGNamespace.build(["mozilla", "dom"], CGList(traitsDecls)) + + curr.append(traitsDecl) + + # Add include guards. + curr = CGIncludeGuard("PrototypeList", curr) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Done. + return curr + + @staticmethod + def BindingNames(config): + declare = fill( + """ + enum class BindingNamesOffset : uint16_t { + $*{enumValues} + }; + + namespace binding_detail { + extern const char sBindingNames[]; + } // namespace binding_detail + + MOZ_ALWAYS_INLINE const char* BindingName(BindingNamesOffset aOffset) { + return binding_detail::sBindingNames + static_cast<size_t>(aOffset); + } + """, + enumValues="".join( + "%s = %i,\n" % (BindingNamesOffsetEnum(n), o) + for (n, o) in config.namesStringOffsets + ), + ) + define = fill( + """ + namespace binding_detail { + + const char sBindingNames[] = { + $*{namesString} + }; + + } // namespace binding_detail + + // Making this enum bigger than a uint16_t has consequences on the size + // of some structs (eg. WebIDLNameTableEntry) and tables. We should try + // to avoid that. + static_assert(EnumTypeFitsWithin<BindingNamesOffset, uint16_t>::value, + "Size increase"); + """, + namesString=' "\\0"\n'.join( + '/* %5i */ "%s"' % (o, n) for (n, o) in config.namesStringOffsets + ) + + "\n", + ) + + curr = CGGeneric(declare=declare, define=define) + curr = CGWrapper(curr, pre="\n", post="\n") + + curr = CGNamespace.build(["mozilla", "dom"], curr) + curr = CGWrapper(curr, post="\n") + + curr = CGHeaders( + [], + [], + [], + [], + ["<stddef.h>", "<stdint.h>", "mozilla/Attributes.h"], + ["mozilla/dom/BindingNames.h", "mozilla/EnumTypeTraits.h"], + "BindingNames", + curr, + ) + + # Add include guards. + curr = CGIncludeGuard("BindingNames", curr) + + # Done. + return curr + + @staticmethod + def RegisterBindings(config): + + curr = CGNamespace.build( + ["mozilla", "dom"], CGGlobalNames(config.windowGlobalNames) + ) + curr = CGWrapper(curr, post="\n") + + # Add the includes + defineIncludes = [ + CGHeaders.getDeclarationFilename(desc.interface) + for desc in config.getDescriptors( + hasInterfaceObject=True, isExposedInWindow=True, register=True + ) + ] + defineIncludes.append("mozilla/dom/BindingNames.h") + defineIncludes.append("mozilla/dom/WebIDLGlobalNameHash.h") + defineIncludes.append("mozilla/dom/PrototypeList.h") + defineIncludes.append("mozilla/PerfectHash.h") + defineIncludes.append("js/String.h") + curr = CGHeaders([], [], [], [], [], defineIncludes, "RegisterBindings", curr) + + # Add include guards. + curr = CGIncludeGuard("RegisterBindings", curr) + + # Done. + return curr + + @staticmethod + def RegisterWorkerBindings(config): + + curr = CGRegisterWorkerBindings(config) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n")) + curr = CGWrapper(curr, post="\n") + + # Add the includes + defineIncludes = [ + CGHeaders.getDeclarationFilename(desc.interface) + for desc in config.getDescriptors( + hasInterfaceObject=True, register=True, isExposedInAnyWorker=True + ) + ] + + curr = CGHeaders( + [], [], [], [], [], defineIncludes, "RegisterWorkerBindings", curr + ) + + # Add include guards. + curr = CGIncludeGuard("RegisterWorkerBindings", curr) + + # Done. + return curr + + @staticmethod + def RegisterWorkerDebuggerBindings(config): + + curr = CGRegisterWorkerDebuggerBindings(config) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n")) + curr = CGWrapper(curr, post="\n") + + # Add the includes + defineIncludes = [ + CGHeaders.getDeclarationFilename(desc.interface) + for desc in config.getDescriptors( + hasInterfaceObject=True, register=True, isExposedInWorkerDebugger=True + ) + ] + + curr = CGHeaders( + [], [], [], [], [], defineIncludes, "RegisterWorkerDebuggerBindings", curr + ) + + # Add include guards. + curr = CGIncludeGuard("RegisterWorkerDebuggerBindings", curr) + + # Done. + return curr + + @staticmethod + def RegisterWorkletBindings(config): + + curr = CGRegisterWorkletBindings(config) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n")) + curr = CGWrapper(curr, post="\n") + + # Add the includes + defineIncludes = [ + CGHeaders.getDeclarationFilename(desc.interface) + for desc in config.getDescriptors( + hasInterfaceObject=True, register=True, isExposedInAnyWorklet=True + ) + ] + + curr = CGHeaders( + [], [], [], [], [], defineIncludes, "RegisterWorkletBindings", curr + ) + + # Add include guards. + curr = CGIncludeGuard("RegisterWorkletBindings", curr) + + # Done. + return curr + + @staticmethod + def RegisterShadowRealmBindings(config): + + curr = CGRegisterShadowRealmBindings(config) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(["mozilla", "dom"], CGWrapper(curr, post="\n")) + curr = CGWrapper(curr, post="\n") + + # Add the includes + defineIncludes = [ + CGHeaders.getDeclarationFilename(desc.interface) + for desc in config.getDescriptors( + hasInterfaceObject=True, register=True, isExposedInShadowRealms=True + ) + ] + + curr = CGHeaders( + [], [], [], [], [], defineIncludes, "RegisterShadowRealmBindings", curr + ) + + # Add include guards. + curr = CGIncludeGuard("RegisterShadowRealmBindings", curr) + + # Done. + return curr + + @staticmethod + def UnionTypes(config): + unionTypes = UnionsForFile(config, None) + ( + includes, + implincludes, + declarations, + traverseMethods, + unlinkMethods, + unionStructs, + ) = UnionTypes(unionTypes, config) + + unionStructs = dependencySortDictionariesAndUnionsAndCallbacks(unionStructs) + + unions = CGList( + traverseMethods + + unlinkMethods + + [CGUnionStruct(t, config) for t in unionStructs] + + [CGUnionStruct(t, config, True) for t in unionStructs], + "\n", + ) + + includes.add("mozilla/OwningNonNull.h") + includes.add("mozilla/dom/UnionMember.h") + includes.add("mozilla/dom/BindingDeclarations.h") + # BindingUtils.h is only needed for SetToObject. + # If it stops being inlined or stops calling CallerSubsumes + # both this bit and the bit in CGBindingRoot can be removed. + includes.add("mozilla/dom/BindingUtils.h") + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(["mozilla", "dom"], unions) + + curr = CGWrapper(curr, post="\n") + + builder = ForwardDeclarationBuilder() + for className, isStruct in declarations: + builder.add(className, isStruct=isStruct) + + curr = CGList([builder.build(), curr], "\n") + + curr = CGHeaders([], [], [], [], includes, implincludes, "UnionTypes", curr) + + # Add include guards. + curr = CGIncludeGuard("UnionTypes", curr) + + # Done. + return curr + + @staticmethod + def WebIDLPrefs(config): + prefs = set() + headers = set(["mozilla/dom/WebIDLPrefs.h"]) + for d in config.getDescriptors(hasInterfaceOrInterfacePrototypeObject=True): + for m in d.interface.members: + pref = PropertyDefiner.getStringAttr(m, "Pref") + if pref: + headers.add(prefHeader(pref)) + prefs.add((pref, prefIdentifier(pref))) + prefs = sorted(prefs) + declare = fill( + """ + enum class WebIDLPrefIndex : uint8_t { + NoPref, + $*{prefs} + }; + typedef bool (*WebIDLPrefFunc)(); + extern const WebIDLPrefFunc sWebIDLPrefs[${len}]; + """, + prefs=",\n".join(map(lambda p: "// " + p[0] + "\n" + p[1], prefs)) + "\n", + len=len(prefs) + 1, + ) + define = fill( + """ + const WebIDLPrefFunc sWebIDLPrefs[] = { + nullptr, + $*{prefs} + }; + """, + prefs=",\n".join( + map(lambda p: "// " + p[0] + "\nStaticPrefs::" + p[1], prefs) + ) + + "\n", + ) + prefFunctions = CGGeneric(declare=declare, define=define) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(["mozilla", "dom"], prefFunctions) + + curr = CGWrapper(curr, post="\n") + + curr = CGHeaders([], [], [], [], [], headers, "WebIDLPrefs", curr) + + # Add include guards. + curr = CGIncludeGuard("WebIDLPrefs", curr) + + # Done. + return curr + + @staticmethod + def WebIDLSerializable(config): + # We need a declaration of StructuredCloneTags in the header. + declareIncludes = set( + [ + "mozilla/dom/DOMJSClass.h", + "mozilla/dom/StructuredCloneTags.h", + "js/TypeDecls.h", + ] + ) + defineIncludes = set( + ["mozilla/dom/WebIDLSerializable.h", "mozilla/PerfectHash.h"] + ) + names = list() + for d in config.getDescriptors(isSerializable=True): + names.append(d.name) + defineIncludes.add(CGHeaders.getDeclarationFilename(d.interface)) + + if len(names) == 0: + # We can't really create a PerfectHash out of this, but also there's + # not much point to this file if we have no [Serializable] objects. + # Just spit out an empty file. + return CGIncludeGuard("WebIDLSerializable", CGGeneric("")) + + # If we had a lot of serializable things, it might be worth it to use a + # PerfectHash here, or an array ordered by sctag value and binary + # search. But setting those up would require knowing in this python + # code the values of the various SCTAG_DOM_*. We could hardcode them + # here and add static asserts that the values are right, or switch to + # code-generating StructuredCloneTags.h or something. But in practice, + # there's a pretty small number of serializable interfaces, and just + # doing a linear walk is fine. It's not obviously worse than the + # if-cascade we used to have. Let's just make sure we notice if we do + # end up with a lot of serializable things here. + # + # Also, in practice it looks like compilers compile this linear walk to + # an out-of-bounds check followed by a direct index into an array, by + # basically making a second copy of this array ordered by tag, with the + # holes filled in. Again, worth checking whether this still happens if + # we have too many serializable things. + if len(names) > 20: + raise TypeError( + "We now have %s serializable interfaces. " + "Double-check that the compiler is still " + "generating a jump table." % len(names) + ) + + entries = list() + # Make sure we have stable ordering. + for name in sorted(names): + # Strip off trailing newline to make our formatting look right. + entries.append( + fill( + """ + { + /* mTag */ ${tag}, + /* mDeserialize */ ${name}_Binding::Deserialize + } + """, + tag=StructuredCloneTag(name), + name=name, + )[:-1] + ) + + declare = dedent( + """ + WebIDLDeserializer LookupDeserializer(StructuredCloneTags aTag); + """ + ) + define = fill( + """ + struct WebIDLSerializableEntry { + StructuredCloneTags mTag; + WebIDLDeserializer mDeserialize; + }; + + static const WebIDLSerializableEntry sEntries[] = { + $*{entries} + }; + + WebIDLDeserializer LookupDeserializer(StructuredCloneTags aTag) { + for (auto& entry : sEntries) { + if (entry.mTag == aTag) { + return entry.mDeserialize; + } + } + return nullptr; + } + """, + entries=",\n".join(entries) + "\n", + ) + + code = CGGeneric(declare=declare, define=define) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(["mozilla", "dom"], code) + + curr = CGWrapper(curr, post="\n") + + curr = CGHeaders( + [], [], [], [], declareIncludes, defineIncludes, "WebIDLSerializable", curr + ) + + # Add include guards. + curr = CGIncludeGuard("WebIDLSerializable", curr) + + # Done. + return curr + + +# Code generator for simple events +class CGEventGetter(CGNativeMember): + def __init__(self, descriptor, attr): + ea = descriptor.getExtendedAttributes(attr, getter=True) + CGNativeMember.__init__( + self, + descriptor, + attr, + CGSpecializedGetter.makeNativeName(descriptor, attr), + (attr.type, []), + ea, + resultNotAddRefed=not attr.type.isSequence(), + ) + self.body = self.getMethodBody() + + def getArgs(self, returnType, argList): + if "needsErrorResult" in self.extendedAttrs: + raise TypeError("Event code generator does not support [Throws]!") + if "canOOM" in self.extendedAttrs: + raise TypeError("Event code generator does not support [CanOOM]!") + if not self.member.isAttr(): + raise TypeError("Event code generator does not support methods") + if self.member.isStatic(): + raise TypeError("Event code generators does not support static attributes") + return CGNativeMember.getArgs(self, returnType, argList) + + def getMethodBody(self): + type = self.member.type + memberName = CGDictionary.makeMemberName(self.member.identifier.name) + if ( + (type.isPrimitive() and type.tag() in builtinNames) + or type.isEnum() + or type.isPromise() + or type.isGeckoInterface() + ): + return "return " + memberName + ";\n" + if type.isJSString(): + # https://bugzilla.mozilla.org/show_bug.cgi?id=1580167 + raise TypeError("JSString not supported as member of a generated event") + if ( + type.isDOMString() + or type.isByteString() + or type.isUSVString() + or type.isUTF8String() + ): + return "aRetVal = " + memberName + ";\n" + if type.isSpiderMonkeyInterface() or type.isObject(): + return fill( + """ + if (${memberName}) { + JS::ExposeObjectToActiveJS(${memberName}); + } + aRetVal.set(${memberName}); + return; + """, + memberName=memberName, + ) + if type.isAny(): + return fill( + """ + ${selfName}(aRetVal); + """, + selfName=self.name, + ) + if type.isUnion(): + return "aRetVal = " + memberName + ";\n" + if type.isSequence(): + if type.nullable(): + return ( + "if (" + + memberName + + ".IsNull()) { aRetVal.SetNull(); } else { aRetVal.SetValue(" + + memberName + + ".Value().Clone()); }\n" + ) + else: + return "aRetVal = " + memberName + ".Clone();\n" + raise TypeError("Event code generator does not support this type!") + + def declare(self, cgClass): + if ( + getattr(self.member, "originatingInterface", cgClass.descriptor.interface) + != cgClass.descriptor.interface + ): + return "" + return CGNativeMember.declare(self, cgClass) + + def define(self, cgClass): + if ( + getattr(self.member, "originatingInterface", cgClass.descriptor.interface) + != cgClass.descriptor.interface + ): + return "" + return CGNativeMember.define(self, cgClass) + + +class CGEventSetter(CGNativeMember): + def __init__(self): + raise TypeError("Event code generator does not support setters!") + + +class CGEventMethod(CGNativeMember): + def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True): + self.isInit = False + + CGNativeMember.__init__( + self, + descriptor, + method, + CGSpecializedMethod.makeNativeName(descriptor, method), + signature, + descriptor.getExtendedAttributes(method), + breakAfter=breakAfter, + variadicIsSequence=True, + ) + self.originalArgs = list(self.args) + + iface = descriptor.interface + allowed = isConstructor + if not allowed and iface.getExtendedAttribute("LegacyEventInit"): + # Allow it, only if it fits the initFooEvent profile exactly + # We could check the arg types but it's not worth the effort. + if ( + method.identifier.name == "init" + iface.identifier.name + and signature[1][0].type.isDOMString() + and signature[1][1].type.isBoolean() + and signature[1][2].type.isBoolean() + and + # -3 on the left to ignore the type, bubbles, and cancelable parameters + # -1 on the right to ignore the .trusted property which bleeds through + # here because it is [Unforgeable]. + len(signature[1]) - 3 + == len([x for x in iface.members if x.isAttr()]) - 1 + ): + allowed = True + self.isInit = True + + if not allowed: + raise TypeError("Event code generator does not support methods!") + + def getArgs(self, returnType, argList): + args = [self.getArg(arg) for arg in argList] + return args + + def getArg(self, arg): + decl, ref = self.getArgType( + arg.type, arg.canHaveMissingValue(), "Variadic" if arg.variadic else False + ) + if ref: + decl = CGWrapper(decl, pre="const ", post="&") + + name = arg.identifier.name + name = "a" + name[0].upper() + name[1:] + return Argument(decl.define(), name) + + def declare(self, cgClass): + if self.isInit: + constructorForNativeCaller = "" + else: + self.args = list(self.originalArgs) + self.args.insert(0, Argument("mozilla::dom::EventTarget*", "aOwner")) + constructorForNativeCaller = CGNativeMember.declare(self, cgClass) + + self.args = list(self.originalArgs) + if needCx(None, self.arguments(), [], considerTypes=True, static=True): + self.args.insert(0, Argument("JSContext*", "aCx")) + if not self.isInit: + self.args.insert(0, Argument("const GlobalObject&", "aGlobal")) + + return constructorForNativeCaller + CGNativeMember.declare(self, cgClass) + + def defineInit(self, cgClass): + iface = self.descriptorProvider.interface + members = "" + while iface.identifier.name != "Event": + i = 3 # Skip the boilerplate args: type, bubble,s cancelable. + for m in iface.members: + if m.isAttr(): + # We need to initialize all the member variables that do + # not come from Event. + if ( + getattr(m, "originatingInterface", iface).identifier.name + == "Event" + ): + continue + name = CGDictionary.makeMemberName(m.identifier.name) + members += "%s = %s;\n" % (name, self.args[i].name) + i += 1 + iface = iface.parent + + self.body = fill( + """ + InitEvent(${typeArg}, ${bubblesArg}, ${cancelableArg}); + ${members} + """, + typeArg=self.args[0].name, + bubblesArg=self.args[1].name, + cancelableArg=self.args[2].name, + members=members, + ) + + return CGNativeMember.define(self, cgClass) + + def define(self, cgClass): + self.args = list(self.originalArgs) + if self.isInit: + return self.defineInit(cgClass) + members = "" + holdJS = "" + iface = self.descriptorProvider.interface + while iface.identifier.name != "Event": + for m in self.descriptorProvider.getDescriptor( + iface.identifier.name + ).interface.members: + if m.isAttr(): + # We initialize all the other member variables in the + # Constructor except those ones coming from the Event. + if ( + getattr( + m, "originatingInterface", cgClass.descriptor.interface + ).identifier.name + == "Event" + ): + continue + name = CGDictionary.makeMemberName(m.identifier.name) + if m.type.isSequence(): + # For sequences we may not be able to do a simple + # assignment because the underlying types may not match. + # For example, the argument can be a + # Sequence<OwningNonNull<SomeInterface>> while our + # member is an nsTArray<RefPtr<SomeInterface>>. So + # use AppendElements, which is actually a template on + # the incoming type on nsTArray and does the right thing + # for this case. + target = name + source = "%s.%s" % (self.args[1].name, name) + sequenceCopy = "e->%s.AppendElements(%s);\n" + if m.type.nullable(): + sequenceCopy = CGIfWrapper( + CGGeneric(sequenceCopy), "!%s.IsNull()" % source + ).define() + target += ".SetValue()" + source += ".Value()" + members += sequenceCopy % (target, source) + elif m.type.isSpiderMonkeyInterface(): + srcname = "%s.%s" % (self.args[1].name, name) + if m.type.nullable(): + members += fill( + """ + if (${srcname}.IsNull()) { + e->${varname} = nullptr; + } else { + e->${varname} = ${srcname}.Value().Obj(); + } + """, + varname=name, + srcname=srcname, + ) + else: + members += fill( + """ + e->${varname}.set(${srcname}.Obj()); + """, + varname=name, + srcname=srcname, + ) + else: + members += "e->%s = %s.%s;\n" % (name, self.args[1].name, name) + if ( + m.type.isAny() + or m.type.isObject() + or m.type.isSpiderMonkeyInterface() + ): + holdJS = "mozilla::HoldJSObjects(e.get());\n" + iface = iface.parent + + self.body = fill( + """ + RefPtr<${nativeType}> e = new ${nativeType}(aOwner); + bool trusted = e->Init(aOwner); + e->InitEvent(${eventType}, ${eventInit}.mBubbles, ${eventInit}.mCancelable); + $*{members} + e->SetTrusted(trusted); + e->SetComposed(${eventInit}.mComposed); + $*{holdJS} + return e.forget(); + """, + nativeType=self.descriptorProvider.nativeType.split("::")[-1], + eventType=self.args[0].name, + eventInit=self.args[1].name, + members=members, + holdJS=holdJS, + ) + + self.args.insert(0, Argument("mozilla::dom::EventTarget*", "aOwner")) + constructorForNativeCaller = CGNativeMember.define(self, cgClass) + "\n" + self.args = list(self.originalArgs) + self.body = fill( + """ + nsCOMPtr<mozilla::dom::EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(owner, ${arg0}, ${arg1}); + """, + arg0=self.args[0].name, + arg1=self.args[1].name, + ) + if needCx(None, self.arguments(), [], considerTypes=True, static=True): + self.args.insert(0, Argument("JSContext*", "aCx")) + self.args.insert(0, Argument("const GlobalObject&", "aGlobal")) + return constructorForNativeCaller + CGNativeMember.define(self, cgClass) + + +class CGEventClass(CGBindingImplClass): + """ + Codegen for the actual Event class implementation for this descriptor + """ + + def __init__(self, descriptor): + CGBindingImplClass.__init__( + self, + descriptor, + CGEventMethod, + CGEventGetter, + CGEventSetter, + False, + "WrapObjectInternal", + ) + members = [] + extraMethods = [] + self.membersNeedingCC = [] + self.membersNeedingTrace = [] + + for m in descriptor.interface.members: + if ( + getattr(m, "originatingInterface", descriptor.interface) + != descriptor.interface + ): + continue + + if m.isAttr(): + if m.type.isAny(): + self.membersNeedingTrace.append(m) + # Add a getter that doesn't need a JSContext. Note that we + # don't need to do this if our originating interface is not + # the descriptor's interface, because in that case we + # wouldn't generate the getter that _does_ need a JSContext + # either. + extraMethods.append( + ClassMethod( + CGSpecializedGetter.makeNativeName(descriptor, m), + "void", + [Argument("JS::MutableHandle<JS::Value>", "aRetVal")], + const=True, + body=fill( + """ + JS::ExposeValueToActiveJS(${memberName}); + aRetVal.set(${memberName}); + """, + memberName=CGDictionary.makeMemberName( + m.identifier.name + ), + ), + ) + ) + elif m.type.isObject() or m.type.isSpiderMonkeyInterface(): + self.membersNeedingTrace.append(m) + elif typeNeedsRooting(m.type): + raise TypeError( + "Need to implement tracing for event member of type %s" % m.type + ) + elif idlTypeNeedsCycleCollection(m.type): + self.membersNeedingCC.append(m) + + nativeType = self.getNativeTypeForIDLType(m.type).define() + members.append( + ClassMember( + CGDictionary.makeMemberName(m.identifier.name), + nativeType, + visibility="private", + body="body", + ) + ) + + parent = self.descriptor.interface.parent + self.parentType = self.descriptor.getDescriptor( + parent.identifier.name + ).nativeType.split("::")[-1] + self.nativeType = self.descriptor.nativeType.split("::")[-1] + + if self.needCC(): + isupportsDecl = fill( + """ + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(${nativeType}, ${parentType}) + """, + nativeType=self.nativeType, + parentType=self.parentType, + ) + else: + isupportsDecl = fill( + """ + NS_INLINE_DECL_REFCOUNTING_INHERITED(${nativeType}, ${parentType}) + """, + nativeType=self.nativeType, + parentType=self.parentType, + ) + + baseDeclarations = fill( + """ + public: + $*{isupportsDecl} + + protected: + virtual ~${nativeType}(); + explicit ${nativeType}(mozilla::dom::EventTarget* aOwner); + + """, + isupportsDecl=isupportsDecl, + nativeType=self.nativeType, + parentType=self.parentType, + ) + + className = self.nativeType + asConcreteTypeMethod = ClassMethod( + "As%s" % className, + "%s*" % className, + [], + virtual=True, + body="return this;\n", + breakAfterReturnDecl=" ", + override=True, + ) + extraMethods.append(asConcreteTypeMethod) + + CGClass.__init__( + self, + className, + bases=[ClassBase(self.parentType)], + methods=extraMethods + self.methodDecls, + members=members, + extradeclarations=baseDeclarations, + ) + + def getWrapObjectBody(self): + return ( + "return %s_Binding::Wrap(aCx, this, aGivenProto);\n" % self.descriptor.name + ) + + def needCC(self): + return len(self.membersNeedingCC) != 0 or len(self.membersNeedingTrace) != 0 + + def implTraverse(self): + retVal = "" + for m in self.membersNeedingCC: + retVal += ( + " NS_IMPL_CYCLE_COLLECTION_TRAVERSE(%s)\n" + % CGDictionary.makeMemberName(m.identifier.name) + ) + return retVal + + def implUnlink(self): + retVal = "" + for m in self.membersNeedingCC: + retVal += ( + " NS_IMPL_CYCLE_COLLECTION_UNLINK(%s)\n" + % CGDictionary.makeMemberName(m.identifier.name) + ) + for m in self.membersNeedingTrace: + name = CGDictionary.makeMemberName(m.identifier.name) + if m.type.isAny(): + retVal += " tmp->" + name + ".setUndefined();\n" + elif m.type.isObject() or m.type.isSpiderMonkeyInterface(): + retVal += " tmp->" + name + " = nullptr;\n" + else: + raise TypeError("Unknown traceable member type %s" % m.type) + return retVal + + def implTrace(self): + retVal = "" + for m in self.membersNeedingTrace: + retVal += ( + " NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(%s)\n" + % CGDictionary.makeMemberName(m.identifier.name) + ) + return retVal + + def define(self): + for m in self.membersNeedingTrace: + if not ( + m.type.isAny() or m.type.isObject() or m.type.isSpiderMonkeyInterface() + ): + raise TypeError("Unknown traceable member type %s" % m.type) + + if len(self.membersNeedingTrace) > 0: + dropJS = "mozilla::DropJSObjects(this);\n" + else: + dropJS = "" + # Just override CGClass and do our own thing + ctorParams = ( + "aOwner, nullptr, nullptr" if self.parentType == "Event" else "aOwner" + ) + + if self.needCC(): + classImpl = fill( + """ + + NS_IMPL_CYCLE_COLLECTION_CLASS(${nativeType}) + + NS_IMPL_ADDREF_INHERITED(${nativeType}, ${parentType}) + NS_IMPL_RELEASE_INHERITED(${nativeType}, ${parentType}) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(${nativeType}, ${parentType}) + $*{traverse} + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(${nativeType}, ${parentType}) + $*{trace} + NS_IMPL_CYCLE_COLLECTION_TRACE_END + + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(${nativeType}, ${parentType}) + $*{unlink} + NS_IMPL_CYCLE_COLLECTION_UNLINK_END + + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType}) + NS_INTERFACE_MAP_END_INHERITING(${parentType}) + """, + nativeType=self.nativeType, + parentType=self.parentType, + traverse=self.implTraverse(), + unlink=self.implUnlink(), + trace=self.implTrace(), + ) + else: + classImpl = "" + + classImpl += fill( + """ + + ${nativeType}::${nativeType}(mozilla::dom::EventTarget* aOwner) + : ${parentType}(${ctorParams}) + { + } + + ${nativeType}::~${nativeType}() + { + $*{dropJS} + } + + """, + nativeType=self.nativeType, + ctorParams=ctorParams, + parentType=self.parentType, + dropJS=dropJS, + ) + + return classImpl + CGBindingImplClass.define(self) + + def getNativeTypeForIDLType(self, type): + if type.isPrimitive() and type.tag() in builtinNames: + nativeType = CGGeneric(builtinNames[type.tag()]) + if type.nullable(): + nativeType = CGTemplatedType("Nullable", nativeType) + elif type.isEnum(): + nativeType = CGGeneric(type.unroll().inner.identifier.name) + if type.nullable(): + nativeType = CGTemplatedType("Nullable", nativeType) + elif type.isJSString(): + nativeType = CGGeneric("JS::Heap<JSString*>") + elif type.isDOMString() or type.isUSVString(): + nativeType = CGGeneric("nsString") + elif type.isByteString() or type.isUTF8String(): + nativeType = CGGeneric("nsCString") + elif type.isPromise(): + nativeType = CGGeneric("RefPtr<Promise>") + elif type.isGeckoInterface(): + iface = type.unroll().inner + nativeType = self.descriptor.getDescriptor(iface.identifier.name).nativeType + # Now trim off unnecessary namespaces + nativeType = nativeType.split("::") + if nativeType[0] == "mozilla": + nativeType.pop(0) + if nativeType[0] == "dom": + nativeType.pop(0) + nativeType = CGWrapper( + CGGeneric("::".join(nativeType)), pre="RefPtr<", post=">" + ) + elif type.isAny(): + nativeType = CGGeneric("JS::Heap<JS::Value>") + elif type.isObject() or type.isSpiderMonkeyInterface(): + nativeType = CGGeneric("JS::Heap<JSObject*>") + elif type.isUnion(): + nativeType = CGGeneric(CGUnionStruct.unionTypeDecl(type, True)) + elif type.isSequence(): + if type.nullable(): + innerType = type.inner.inner + else: + innerType = type.inner + if ( + not innerType.isPrimitive() + and not innerType.isEnum() + and not innerType.isDOMString() + and not innerType.isByteString() + and not innerType.isUTF8String() + and not innerType.isPromise() + and not innerType.isGeckoInterface() + ): + raise TypeError( + "Don't know how to properly manage GC/CC for " + "event member of type %s" % type + ) + nativeType = CGTemplatedType( + "nsTArray", self.getNativeTypeForIDLType(innerType) + ) + if type.nullable(): + nativeType = CGTemplatedType("Nullable", nativeType) + else: + raise TypeError("Don't know how to declare event member of type %s" % type) + return nativeType + + +class CGEventRoot(CGThing): + def __init__(self, config, interfaceName): + descriptor = config.getDescriptor(interfaceName) + + self.root = CGWrapper(CGEventClass(descriptor), pre="\n", post="\n") + + self.root = CGNamespace.build(["mozilla", "dom"], self.root) + + self.root = CGList( + [CGClassForwardDeclare("JSContext", isStruct=True), self.root] + ) + + parent = descriptor.interface.parent.identifier.name + + # Throw in our #includes + self.root = CGHeaders( + [descriptor], + [], + [], + [], + [ + config.getDescriptor(parent).headerFile, + "mozilla/Attributes.h", + "mozilla/dom/%sBinding.h" % interfaceName, + "mozilla/dom/BindingUtils.h", + ], + [ + "%s.h" % interfaceName, + "js/GCAPI.h", + "mozilla/HoldDropJSObjects.h", + "mozilla/dom/Nullable.h", + ], + "", + self.root, + config, + ) + + # And now some include guards + self.root = CGIncludeGuard(interfaceName, self.root) + + self.root = CGWrapper( + self.root, + pre=( + AUTOGENERATED_WITH_SOURCE_WARNING_COMMENT + % os.path.basename(descriptor.interface.filename()) + ), + ) + + self.root = CGWrapper( + self.root, + pre=dedent( + """ + /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + /* vim:set ts=2 sw=2 sts=2 et cindent: */ + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + """ + ), + ) + + def declare(self): + return self.root.declare() + + def define(self): + return self.root.define() |