diff -r 000000000000 -r 6474c204b198 dom/bindings/Codegen.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/bindings/Codegen.py Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,13779 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# Common codegen classes. + +import os +import re +import string +import math +import textwrap + +from WebIDL import BuiltinTypes, IDLBuiltinType, IDLNullValue, IDLSequenceType, IDLType, IDLAttribute, IDLUndefinedValue +from Configuration import NoSuchDescriptorError, getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback, Descriptor + +AUTOGENERATED_WARNING_COMMENT = \ + "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n" +ADDPROPERTY_HOOK_NAME = '_addProperty' +FINALIZE_HOOK_NAME = '_finalize' +CONSTRUCT_HOOK_NAME = '_constructor' +LEGACYCALLER_HOOK_NAME = '_legacycaller' +HASINSTANCE_HOOK_NAME = '_hasInstance' +NEWRESOLVE_HOOK_NAME = '_newResolve' +ENUMERATE_HOOK_NAME = '_enumerate' +ENUM_ENTRY_VARIABLE_NAME = 'strings' +INSTANCE_RESERVED_SLOTS = 3 + + +def memberReservedSlot(member): + return "(DOM_INSTANCE_RESERVED_SLOTS + %d)" % member.slotIndex + + +def toStringBool(arg): + return str(not not arg).lower() + + +def toBindingNamespace(arg): + return re.sub("((_workers)?$)", "Binding\\1", arg) + + +def isTypeCopyConstructible(type): + # Nullable and sequence stuff doesn't affect copy-constructibility + type = type.unroll() + return (type.isPrimitive() or type.isString() or type.isEnum() or + (type.isUnion() and + CGUnionStruct.isUnionCopyConstructible(type)) or + (type.isDictionary() and + CGDictionary.isDictionaryCopyConstructible(type.inner)) or + # Interface types are only copy-constructible if they're Gecko + # interfaces. SpiderMonkey interfaces are not copy-constructible + # because of rooting issues. + (type.isInterface() and type.isGeckoInterface())) + + +def wantsAddProperty(desc): + return (desc.concrete and + desc.wrapperCache and + not (desc.workers and + desc.interface.getExtendedAttribute("Global"))) + + +# 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). + """ + if s == "": + return s + return re.sub(lineStartDetector, indentLevel * " ", s) + + +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) + +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. + """ + + # This works by transforming the fill()-template to an equivalent + # string.Template. + multiline_substitution_re = re.compile(r"( *)\$\*{(\w+)}(\n)?") + + def replace(match): + """ + Replaces a line like ' $*{xyz}\n' with '${xyz_n}', + where n is the indent depth, and add a corresponding entry to args. + """ + 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) + + # Multiline text without a newline at the end is probably a mistake. + if not (args[name] == "" or args[name].endswith("\n")): + raise ValueError("Argument %s with value %r is missing a newline" % (name, args[name])) + + # Now replace this whole line of template with the indented equivalent. + modified_name = name + "_" + str(depth) + indented_value = indent(args[name], depth) + if modified_name in args: + assert args[modified_name] == indented_value + else: + args[modified_name] = indented_value + return "${" + modified_name + "}" + + t = dedent(template) + assert t.endswith("\n") or "\n" not in t + t = re.sub(multiline_substitution_re, replace, t) + t = string.Template(t) + return t.substitute(args) + + +class CGThing(): + """ + Abstract base class for things that spit out code. + """ + def __init__(self): + pass # Nothing for now + + def declare(self): + """Produce code for a header file.""" + assert False # Override me! + + def define(self): + """Produce code for a cpp file.""" + assert False # Override me! + + def deps(self): + """Produce the deps for a pp file""" + assert False # Override me! + + +class CGStringTable(CGThing): + """ + Generate a string table for the given strings with a function accessor: + + const char *accessorName(unsigned int index) { + static const char table[] = "..."; + static const uint16_t indices = { ... }; + return &table[indices[index]]; + } + + This is more efficient than the more natural: + + const char *table[] = { + ... + }; + + The uint16_t indices are smaller than the pointer equivalents, and the + string table requires no runtime relocations. + """ + def __init__(self, accessorName, strings): + CGThing.__init__(self) + self.accessorName = accessorName + self.strings = strings + + def declare(self): + return "extern const char *%s(unsigned int aIndex);\n" % self.accessorName + + def define(self): + table = ' "\\0" '.join('"%s"' % s for s in self.strings) + indices = [] + currentIndex = 0 + for s in self.strings: + indices.append(currentIndex) + currentIndex += len(s) + 1 # for the null terminator + return fill( + """ + const char *${name}(unsigned int aIndex) + { + static const char table[] = ${table}; + static const uint16_t indices[] = { ${indices} }; + static_assert(${currentIndex} <= UINT16_MAX, "string table overflow!"); + return &table[indices[aIndex]]; + } + """, + name=self.accessorName, + table=table, + indices=", ".join("%d" % index for index in indices), + currentIndex=currentIndex) + + +class CGNativePropertyHooks(CGThing): + """ + Generate a NativePropertyHooks for a given descriptor + """ + def __init__(self, descriptor, properties): + CGThing.__init__(self) + self.descriptor = descriptor + self.properties = properties + + def declare(self): + if self.descriptor.workers: + return "" + return dedent(""" + // We declare this as an array so that retrieving a pointer to this + // binding's property hooks only requires compile/link-time resolvable + // address arithmetic. Declaring it as a pointer instead would require + // doing a run-time load to fetch a pointer to this binding's property + // hooks. And then structures which embedded a pointer to this structure + // would require a run-time load for proper initialization, which would + // then induce static constructors. Lots of static constructors. + extern const NativePropertyHooks sNativePropertyHooks[]; + """).rstrip() # BOGUS strip newline from the last line here (!) + + def define(self): + if self.descriptor.workers: + return "" + if self.descriptor.concrete and self.descriptor.proxy: + resolveOwnProperty = "ResolveOwnProperty" + enumerateOwnProperties = "EnumerateOwnProperties" + elif self.descriptor.needsXrayResolveHooks(): + resolveOwnProperty = "ResolveOwnPropertyViaNewresolve" + enumerateOwnProperties = "EnumerateOwnPropertiesViaGetOwnPropertyNames" + else: + resolveOwnProperty = "nullptr" + enumerateOwnProperties = "nullptr" + if self.properties.hasNonChromeOnly(): + regular = "&sNativeProperties" + else: + regular = "nullptr" + if self.properties.hasChromeOnly(): + chrome = "&sChromeOnlyNativeProperties" + 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" + parent = self.descriptor.interface.parent + parentHooks = (toBindingNamespace(parent.identifier.name) + "::sNativePropertyHooks" + if parent else 'nullptr') + + return fill( + """ + const NativePropertyHooks sNativePropertyHooks[] = { { + ${resolveOwnProperty}, + ${enumerateOwnProperties}, + { ${regular}, ${chrome} }, + ${prototypeID}, + ${constructorID}, + ${parentHooks} + } }; + """, + resolveOwnProperty=resolveOwnProperty, + enumerateOwnProperties=enumerateOwnProperties, + regular=regular, + chrome=chrome, + prototypeID=prototypeID, + constructorID=constructorID, + parentHooks=parentHooks) + + +def NativePropertyHooks(descriptor): + return "&sWorkerNativePropertyHooks" if descriptor.workers else "sNativePropertyHooks" + + +def DOMClass(descriptor): + def make_name(d): + return "%s%s" % (d.interface.identifier.name, '_workers' if d.workers else '') + + protoList = ['prototypes::id::' + make_name(descriptor.getDescriptor(proto)) for proto in descriptor.prototypeChain] + # Pad out the list to the right length with _ID_Count so we + # guarantee that all the lists are the same length. _ID_Count + # is never the ID of any prototype, so it's safe to use as + # padding. + protoList.extend(['prototypes::id::_ID_Count'] * (descriptor.config.maxProtoChainLength - len(protoList))) + + return fill( + """ + { + { ${protoChain} }, + IsBaseOf::value, + ${hooks}, + GetParentObject<${nativeType}>::Get, + GetProtoObject, + GetCCParticipant<${nativeType}>::Get() + } + """, + protoChain=', '.join(protoList), + nativeType=descriptor.nativeType, + hooks=NativePropertyHooks(descriptor)) + + +class CGDOMJSClass(CGThing): + """ + Generate a DOMJSClass for a given descriptor + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def declare(self): + return "" + + def define(self): + traceHook = 'nullptr' + callHook = LEGACYCALLER_HOOK_NAME if self.descriptor.operations["LegacyCaller"] else 'nullptr' + slotCount = INSTANCE_RESERVED_SLOTS + self.descriptor.interface.totalMembersInSlots + classFlags = "JSCLASS_IS_DOMJSCLASS | " + classExtensionAndObjectOps = """\ +JS_NULL_CLASS_EXT, +JS_NULL_OBJECT_OPS +""" + if self.descriptor.interface.getExtendedAttribute("Global"): + classFlags += "JSCLASS_DOM_GLOBAL | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS) | JSCLASS_IMPLEMENTS_BARRIERS" + traceHook = "JS_GlobalObjectTraceHook" + if not self.descriptor.workers: + classExtensionAndObjectOps = """\ +{ + nsGlobalWindow::OuterObject, /* outerObject */ + nullptr, /* innerObject */ + nullptr, /* iteratorObject */ + false, /* isWrappedNative */ + nullptr /* weakmapKeyDelegateOp */ +}, +{ + nullptr, /* lookupGeneric */ + nullptr, /* lookupProperty */ + nullptr, /* lookupElement */ + nullptr, /* defineGeneric */ + nullptr, /* defineProperty */ + nullptr, /* defineElement */ + nullptr, /* getGeneric */ + nullptr, /* getProperty */ + nullptr, /* getElement */ + nullptr, /* setGeneric */ + nullptr, /* setProperty */ + nullptr, /* setElement */ + nullptr, /* getGenericAttributes */ + nullptr, /* setGenericAttributes */ + nullptr, /* deleteProperty */ + nullptr, /* deleteElement */ + nullptr, /* watch */ + nullptr, /* unwatch */ + nullptr, /* slice */ + nullptr, /* enumerate */ + JS_ObjectToOuterObject /* thisObject */ +} +""" + else: + classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount + if self.descriptor.interface.getExtendedAttribute("NeedNewResolve"): + newResolveHook = "(JSResolveOp)" + NEWRESOLVE_HOOK_NAME + classFlags += " | JSCLASS_NEW_RESOLVE" + enumerateHook = ENUMERATE_HOOK_NAME + elif self.descriptor.interface.getExtendedAttribute("Global"): + newResolveHook = "(JSResolveOp) mozilla::dom::ResolveGlobal" + classFlags += " | JSCLASS_NEW_RESOLVE" + enumerateHook = "mozilla::dom::EnumerateGlobal" + else: + newResolveHook = "JS_ResolveStub" + enumerateHook = "JS_EnumerateStub" + + return fill( # BOGUS extra blank line at the top + """ + + static const DOMJSClass Class = { + { "${name}", + ${flags}, + ${addProperty}, /* addProperty */ + JS_DeletePropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + ${enumerate}, /* enumerate */ + ${resolve}, /* resolve */ + JS_ConvertStub, + ${finalize}, /* finalize */ + ${call}, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + ${trace}, /* trace */ + JS_NULL_CLASS_SPEC, + $*{classExtensionAndObjectOps} + }, + $*{descriptor} + }; + """, + name=self.descriptor.interface.identifier.name, + flags=classFlags, + addProperty=ADDPROPERTY_HOOK_NAME if wantsAddProperty(self.descriptor) else 'JS_PropertyStub', + enumerate=enumerateHook, + resolve=newResolveHook, + finalize=FINALIZE_HOOK_NAME, + call=callHook, + trace=traceHook, + classExtensionAndObjectOps=classExtensionAndObjectOps, + descriptor=DOMClass(self.descriptor)) + + +class CGDOMProxyJSClass(CGThing): + """ + Generate a DOMJSClass for a given proxy descriptor + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def declare(self): + return "" + + def define(self): + flags = ["JSCLASS_IS_DOMJSCLASS"] + # We don't use an IDL annotation for JSCLASS_EMULATES_UNDEFINED because + # we don't want people ever adding that to any interface other than + # HTMLAllCollection. So just hardcode it here. + if self.descriptor.interface.identifier.name == "HTMLAllCollection": + flags.append("JSCLASS_EMULATES_UNDEFINED") + callHook = LEGACYCALLER_HOOK_NAME if self.descriptor.operations["LegacyCaller"] else 'nullptr' + return fill( # BOGUS extra blank line at the top + """ + + static const DOMJSClass Class = { + PROXY_CLASS_DEF("${name}", + 0, /* extra slots */ + ${flags}, + ${call}, /* call */ + nullptr /* construct */), + $*{descriptor} + }; + """, + name=self.descriptor.interface.identifier.name, + flags=" | ".join(flags), + call=callHook, + descriptor=DOMClass(self.descriptor)) + + +def PrototypeIDAndDepth(descriptor): + prototypeID = "prototypes::id::" + if descriptor.interface.hasInterfacePrototypeObject(): + prototypeID += descriptor.interface.identifier.name + if descriptor.workers: + prototypeID += "_workers" + depth = "PrototypeTraits<%s>::Depth" % prototypeID + else: + prototypeID += "_ID_Count" + depth = "0" + return (prototypeID, depth) + + +def UseHolderForUnforgeable(descriptor): + return (descriptor.concrete and + descriptor.proxy and + any(m for m in descriptor.interface.members if m.isAttr() and m.isUnforgeable())) + + +def CallOnUnforgeableHolder(descriptor, code, isXrayCheck=None, + useSharedRoot=False): + """ + Generate the code to execute the code in "code" on an unforgeable holder if + needed. code should be a string containing the code to execute. If it + contains a ${holder} string parameter it will be replaced with the + unforgeable holder object. + + If isXrayCheck is not None it should be a string that contains a statement + returning whether proxy is an Xray. If isXrayCheck is None the generated + code won't try to unwrap Xrays. + + If useSharedRoot is true, we will use an existing + JS::Rooted sharedRoot for storing our unforgeable holder instead + of declaring a new Rooted. + """ + if isXrayCheck is not None: + pre = fill( + """ + // Scope for 'global', 'ac' and 'unforgeableHolder' + { + JS::Rooted global(cx); + Maybe ac; + if (${isXrayCheck}) { + global = js::GetGlobalForObjectCrossCompartment(js::UncheckedUnwrap(proxy)); + ac.construct(cx, global); + } else { + global = js::GetGlobalForObjectCrossCompartment(proxy); + } + """, + isXrayCheck=isXrayCheck) + else: + pre = dedent(""" + // Scope for 'global' and 'unforgeableHolder' + { + JSObject* global = js::GetGlobalForObjectCrossCompartment(proxy); + """) + + if useSharedRoot: + holderDecl = "JS::Rooted& unforgeableHolder(sharedRoot);\n" + else: + holderDecl = "JS::Rooted unforgeableHolder(cx);\n" + + code = string.Template(code).substitute({"holder": "unforgeableHolder"}) + return fill( + """ + $*{pre} + $*{holderDecl} + unforgeableHolder = GetUnforgeableHolder(global, prototypes::id::${name}); + $*{code} + } + """, + pre=pre, + holderDecl=holderDecl, + name=descriptor.name, + code=code) + + +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" + if UseHolderForUnforgeable(self.descriptor): + slotCount += " + 1 /* slot for the JSObject holding the unforgeable properties */" + return fill( + """ + static const DOMIfaceAndProtoJSClass PrototypeClass = { + { + "${name}Prototype", + JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}), + JS_PropertyStub, /* addProperty */ + JS_DeletePropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + nullptr, /* trace */ + JSCLASS_NO_INTERNAL_MEMBERS + }, + eInterfacePrototype, + ${hooks}, + "[object ${name}Prototype]", + ${prototypeID}, + ${depth} + }; + """, + name=self.descriptor.interface.identifier.name, + slotCount=slotCount, + hooks=NativePropertyHooks(self.descriptor), + prototypeID=prototypeID, + depth=depth) + + +def NeedsGeneratedHasInstance(descriptor): + return descriptor.hasXPConnectImpls or descriptor.interface.isConsequential() + + +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(): + ctorname = CONSTRUCT_HOOK_NAME + else: + ctorname = "ThrowingConstructor" + if NeedsGeneratedHasInstance(self.descriptor): + hasinstance = HASINSTANCE_HOOK_NAME + elif self.descriptor.interface.hasInterfacePrototypeObject(): + hasinstance = "InterfaceHasInstance" + else: + hasinstance = "nullptr" + prototypeID, depth = PrototypeIDAndDepth(self.descriptor) + slotCount = "DOM_INTERFACE_SLOTS_BASE" + if len(self.descriptor.interface.namedConstructors) > 0: + slotCount += (" + %i /* slots for the named constructors */" % + len(self.descriptor.interface.namedConstructors)) + return fill( # BOGUS extra newline at the top + """ + + static const DOMIfaceAndProtoJSClass InterfaceObjectClass = { + { + "Function", + JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}), + JS_PropertyStub, /* addProperty */ + JS_DeletePropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + nullptr, /* finalize */ + ${ctorname}, /* call */ + ${hasInstance}, /* hasInstance */ + ${ctorname}, /* construct */ + nullptr, /* trace */ + JSCLASS_NO_INTERNAL_MEMBERS + }, + eInterface, + ${hooks}, + "function ${name}() {\\n [native code]\\n}", + ${prototypeID}, + ${depth} + }; + """, + slotCount=slotCount, + ctorname=ctorname, + hasInstance=hasinstance, + hooks=NativePropertyHooks(self.descriptor), + name=self.descriptor.interface.identifier.name, + prototypeID=prototypeID, + depth=depth) + + +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 + + +class CGGeneric(CGThing): + """ + A class that spits out a fixed string into the codegen. Can spit out a + separate string for the declaration too. + """ + def __init__(self, define="", declare=""): + self.declareText = declare + self.defineText = define + + def declare(self): + return self.declareText + + def define(self): + return self.defineText + + def deps(self): + return set() + + +class CGIndenter(CGThing): + """ + A class that takes another CGThing and generates code that indents that + CGThing by some number of spaces. The default indent is two spaces. + """ + def __init__(self, child, indentLevel=2, declareOnly=False): + assert isinstance(child, CGThing) + CGThing.__init__(self) + self.child = child + self.indentLevel = indentLevel + self.declareOnly = declareOnly + + def declare(self): + return indent(self.child.declare(), self.indentLevel) + + def define(self): + defn = self.child.define() + if self.declareOnly: + return defn + else: + return indent(defn, self.indentLevel) + + +class CGWrapper(CGThing): + """ + Generic CGThing that wraps other CGThings with pre and post text. + """ + def __init__(self, child, pre="", post="", declarePre=None, + declarePost=None, definePre=None, definePost=None, + declareOnly=False, defineOnly=False, reindent=False): + CGThing.__init__(self) + self.child = child + self.declarePre = declarePre or pre + self.declarePost = declarePost or post + self.definePre = definePre or pre + self.definePost = definePost or post + self.declareOnly = declareOnly + self.defineOnly = defineOnly + self.reindent = reindent + + def declare(self): + if self.defineOnly: + return '' + decl = self.child.declare() + if self.reindent: + decl = self.reindentString(decl, self.declarePre) + return self.declarePre + decl + self.declarePost + + def define(self): + if self.declareOnly: + return '' + defn = self.child.define() + if self.reindent: + defn = self.reindentString(defn, self.definePre) + return self.definePre + defn + self.definePost + + @staticmethod + def reindentString(stringToIndent, widthString): + # We don't use lineStartDetector because we don't want to + # insert whitespace at the beginning of our _first_ line. + # Use the length of the last line of width string, in case + # it is a multiline string. + lastLineWidth = len(widthString.splitlines()[-1]) + return stripTrailingWhitespace( + stringToIndent.replace("\n", "\n" + (" " * lastLineWidth))) + + def deps(self): + return self.child.deps() + + +class CGIfWrapper(CGList): + def __init__(self, child, condition): + CGList.__init__(self, [ + CGWrapper(CGGeneric(condition), pre="if (", post=") {\n", reindent=True), + CGIndenter(child), + CGGeneric("}\n") + ]) + + +class CGIfElseWrapper(CGList): + def __init__(self, condition, ifTrue, ifFalse): + CGList.__init__(self, [ + CGWrapper(CGGeneric(condition), pre="if (", post=") {\n", reindent=True), + CGIndenter(ifTrue), + CGGeneric("} else {\n"), + CGIndenter(ifFalse), + CGGeneric("}\n") + ]) + + +class CGElseChain(CGThing): + """ + Concatenate if statements in an if-else-if-else chain. + """ + def __init__(self, children): + self.children = [c for c in children if c is not None] + + def declare(self): + assert False + + def define(self): + if not self.children: + return "" + s = self.children[0].define() + assert s.endswith("\n") + for child in self.children[1:]: + code = child.define() + assert code.startswith("if") or code.startswith("{") + assert code.endswith("\n") + s = s.rstrip() + " else " + code + return s + + +class CGTemplatedType(CGWrapper): + def __init__(self, templateName, child, isConst=False, isReference=False): + const = "const " if isConst else "" + pre = "%s%s<" % (const, templateName) + ref = "&" if isReference else "" + post = ">%s" % ref + CGWrapper.__init__(self, child, pre=pre, post=post) + + +class CGNamespace(CGWrapper): + def __init__(self, namespace, child, declareOnly=False): + pre = "namespace %s {\n" % namespace + post = "} // namespace %s\n" % namespace + CGWrapper.__init__(self, child, pre=pre, post=post, + declareOnly=declareOnly) + + @staticmethod + def build(namespaces, child, declareOnly=False): + """ + Static helper method to build multiple wrapped namespaces. + """ + if not namespaces: + return CGWrapper(child, declareOnly=declareOnly) + inner = CGNamespace.build(namespaces[1:], child, declareOnly=declareOnly) + return CGNamespace(namespaces[0], inner, declareOnly=declareOnly) + + +class CGIncludeGuard(CGWrapper): + """ + Generates include guards for a header. + """ + def __init__(self, prefix, child): + """|prefix| is the filename without the extension.""" + define = 'mozilla_dom_%s_h' % prefix + CGWrapper.__init__(self, child, + declarePre='#ifndef %s\n#define %s\n\n' % (define, define), + declarePost='\n#endif // %s\n' % define) + + +def getRelevantProviders(descriptor, config): + if descriptor is not None: + return [descriptor] + # Do both the non-worker and worker versions + return [ + config.getDescriptorProvider(False), + config.getDescriptorProvider(True) + ] + + +def getAllTypes(descriptors, dictionaries, callbacks): + """ + Generate all the types we're dealing with. For each type, a tuple + containing type, descriptor, dictionary is yielded. The + descriptor and dictionary can be None if the type does not come + from a descriptor or dictionary; they will never both be non-None. + """ + for d in descriptors: + if d.interface.isExternal(): + continue + for t in getTypesFromDescriptor(d): + yield (t, d, None) + for dictionary in dictionaries: + for t in getTypesFromDictionary(dictionary): + yield (t, None, dictionary) + for callback in callbacks: + for t in getTypesFromCallback(callback): + yield (t, None, None) + + +class CGHeaders(CGWrapper): + """ + Generates the appropriate include statements. + """ + def __init__(self, descriptors, dictionaries, callbacks, + callbackDescriptors, + declareIncludes, defineIncludes, prefix, child, + config=None, jsImplementedDescriptors=[]): + """ + Builds a set of includes to cover |descriptors|. + + Also includes the files in |declareIncludes| in the header + file and the files in |defineIncludes| in the .cpp. + + |prefix| contains the basename of the file that we generate include + statements for. + """ + + # Determine the filenames for which we need headers. + interfaceDeps = [d.interface for d in descriptors] + ancestors = [] + for iface in interfaceDeps: + if iface.parent: + # We're going to need our parent's prototype, to use as the + # prototype of our prototype object. + ancestors.append(iface.parent) + # And if we have an interface object, we'll need the nearest + # ancestor with an interface object too, so we can use its + # interface object as the proto of our interface object. + if iface.hasInterfaceObject(): + parent = iface.parent + while parent and not parent.hasInterfaceObject(): + parent = parent.parent + if parent: + ancestors.append(parent) + interfaceDeps.extend(ancestors) + bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps) + + # Grab all the implementation declaration files we need. + implementationIncludes = set(d.headerFile for d in descriptors if d.needsHeaderInclude()) + + # Grab the includes for checking hasInstance + interfacesImplementingSelf = set() + for d in descriptors: + interfacesImplementingSelf |= d.interface.interfacesImplementingSelf + implementationIncludes |= set(self.getDeclarationFilename(i) for i in + interfacesImplementingSelf) + + # Grab the includes for the things that involve XPCOM interfaces + hasInstanceIncludes = set("nsIDOM" + d.interface.identifier.name + ".h" for d + in descriptors if + NeedsGeneratedHasInstance(d) and + d.interface.hasInterfacePrototypeObject()) + + # Now find all the things we'll need as arguments because we + # need to wrap or unwrap them. + bindingHeaders = set() + declareIncludes = set(declareIncludes) + + def addHeadersForType((t, descriptor, dictionary)): + """ + Add the relevant headers for this type. We use descriptor and + dictionary, if passed, to decide what to do with interface types. + """ + assert not descriptor or not dictionary + # Dictionaries have members that need to be actually + # declared, not just forward-declared. + if dictionary: + headerSet = declareIncludes + else: + headerSet = bindingHeaders + if t.nullable(): + # Need to make sure that Nullable as a dictionary + # member works. + headerSet.add("mozilla/dom/Nullable.h") + unrolled = t.unroll() + if unrolled.isUnion(): + # UnionConversions.h includes UnionTypes.h + bindingHeaders.add("mozilla/dom/UnionConversions.h") + if dictionary: + # Our dictionary definition is in the header and + # needs the union type. + declareIncludes.add("mozilla/dom/UnionTypes.h") + elif unrolled.isDate(): + if dictionary or jsImplementedDescriptors: + declareIncludes.add("mozilla/dom/Date.h") + else: + bindingHeaders.add("mozilla/dom/Date.h") + elif unrolled.isInterface(): + if unrolled.isSpiderMonkeyInterface(): + bindingHeaders.add("jsfriendapi.h") + headerSet.add("mozilla/dom/TypedArray.h") + else: + providers = getRelevantProviders(descriptor, config) + for p in providers: + try: + typeDesc = p.getDescriptor(unrolled.inner.identifier.name) + except NoSuchDescriptorError: + continue + # 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 nsRefPtr and have inline + # constructors/destructors. + # + # XXXbz maybe dictionaries with interface members + # should just have out-of-line constructors and + # destructors? + headerSet.add(typeDesc.headerFile) + elif unrolled.isDictionary(): + headerSet.add(self.getDeclarationFilename(unrolled.inner)) + elif unrolled.isCallback(): + # Callbacks are both a type and an object + headerSet.add(self.getDeclarationFilename(unrolled)) + elif unrolled.isFloat() and not unrolled.isUnrestricted(): + # Restricted floats are tested for finiteness + bindingHeaders.add("mozilla/FloatingPoint.h") + bindingHeaders.add("mozilla/dom/PrimitiveConversions.h") + elif unrolled.isEnum(): + filename = self.getDeclarationFilename(unrolled.inner) + declareIncludes.add(filename) + elif unrolled.isPrimitive(): + bindingHeaders.add("mozilla/dom/PrimitiveConversions.h") + elif unrolled.isMozMap(): + if dictionary or jsImplementedDescriptors: + declareIncludes.add("mozilla/dom/MozMap.h") + else: + bindingHeaders.add("mozilla/dom/MozMap.h") + # Also add headers for the type the MozMap is + # parametrized over, if needed. + addHeadersForType((t.inner, descriptor, dictionary)) + + map(addHeadersForType, + getAllTypes(descriptors + callbackDescriptors, dictionaries, + callbacks)) + + # Now make sure we're not trying to include the header from inside itself + declareIncludes.discard(prefix + ".h") + + # Now for non-callback descriptors make sure we include any + # headers needed by Func declarations. + for desc in descriptors: + if desc.interface.isExternal(): + continue + + def addHeaderForFunc(func): + if func is None: + return + # Include the right class header, which we can only do + # if this is a class member function. + if 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") + + for m in desc.interface.members: + addHeaderForFunc(PropertyDefiner.getStringAttr(m, "Func")) + # getExtendedAttribute() returns a list, extract the entry. + funcList = desc.interface.getExtendedAttribute("Func") + if funcList is not None: + addHeaderForFunc(funcList[0]) + + for d in dictionaries: + if d.parent: + declareIncludes.add(self.getDeclarationFilename(d.parent)) + bindingHeaders.add(self.getDeclarationFilename(d)) + + 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 BindingUtils.h so we can wrap "this" objects + declareIncludes.add("mozilla/dom/BindingUtils.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 BindingUtils.h so we can wrap "this" objects + declareIncludes.add("mozilla/dom/BindingUtils.h") + + # Also need to include the headers for ancestors of + # JS-implemented interfaces. + for jsImplemented in jsImplementedDescriptors: + jsParent = jsImplemented.interface.parent + if jsParent: + parentDesc = jsImplemented.getDescriptor(jsParent.identifier.name) + declareIncludes.add(parentDesc.jsImplParentHeader) + + # Let the machinery do its thing. + def _includeString(includes): + return ''.join(['#include "%s"\n' % i for i in includes]) + '\n' + CGWrapper.__init__(self, child, + declarePre=_includeString(sorted(declareIncludes)), + definePre=_includeString(sorted(set(defineIncludes) | + bindingIncludes | + bindingHeaders | + hasInstanceIncludes | + implementationIncludes))) + + @staticmethod + def getDeclarationFilename(decl): + # Use our local version of the header, not the exported one, so that + # test bindings, which don't export, will work correctly. + basename = os.path.basename(decl.filename()) + return basename.replace('.webidl', 'Binding.h') + + +def SortedDictValues(d): + """ + Returns a list of values from the dict sorted by key. + """ + return [v for k, v in sorted(d.items())] + + +def UnionTypes(descriptors, dictionaries, callbacks, config): + """ + Returns a tuple containing a set of header filenames to include in + UnionTypes.h, a set of header filenames to include in UnionTypes.cpp, a set + of tuples containing a type declaration and a boolean if the type is a + struct for member types of the unions and a CGList containing CGUnionStructs + for every union. + """ + + # Now find all the things we'll need as arguments and return values because + # we need to wrap or unwrap them. + headers = set() + implheaders = set(["UnionTypes.h"]) + declarations = set() + unionStructs = dict() + owningUnionStructs = dict() + + for t, descriptor, dictionary in getAllTypes(descriptors, dictionaries, callbacks): + # Add info for the given type. descriptor and dictionary, if present, are + # used to figure out what to do with interface types. + assert not descriptor or not dictionary + + t = t.unroll() + while t.isMozMap(): + t = t.inner.unroll() + if not t.isUnion(): + continue + name = str(t) + if name not in unionStructs: + providers = getRelevantProviders(descriptor, config) + # FIXME: Unions are broken in workers. See bug 809899. + unionStructs[name] = CGUnionStruct(t, providers[0]) + owningUnionStructs[name] = CGUnionStruct(t, providers[0], + ownsMembers=True) + + def addHeadersForType(f): + if f.nullable(): + headers.add("mozilla/dom/Nullable.h") + f = f.unroll() + if f.isInterface(): + if f.isSpiderMonkeyInterface(): + headers.add("jsfriendapi.h") + headers.add("mozilla/dom/TypedArray.h") + else: + for p in providers: + try: + typeDesc = p.getDescriptor(f.inner.identifier.name) + except NoSuchDescriptorError: + continue + if typeDesc.interface.isCallback(): + # Callback interfaces always use strong refs, so + # we need to include the right header to be able + # to Release() in our inlined code. + headers.add(typeDesc.headerFile) + else: + declarations.add((typeDesc.nativeType, False)) + implheaders.add(typeDesc.headerFile) + elif f.isDictionary(): + # For a dictionary, we need to see its declaration in + # UnionTypes.h so we have its sizeof and know how big to + # make our union. + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + # And if it needs rooting, we need RootedDictionary too + if typeNeedsRooting(f): + headers.add("mozilla/dom/RootedDictionary.h") + elif f.isEnum(): + # Need to see the actual definition of the enum, + # unfortunately. + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + elif f.isCallback(): + # Callbacks always use strong refs, so we need to include + # the right header to be able to Release() in our inlined + # code. + headers.add(CGHeaders.getDeclarationFilename(f)) + elif f.isMozMap(): + headers.add("mozilla/dom/MozMap.h") + # And add headers for the type we're parametrized over + addHeadersForType(f.inner) + + for f in t.flatMemberTypes: + assert not f.nullable() + addHeadersForType(f) + + return (headers, implheaders, declarations, + CGList(SortedDictValues(unionStructs) + + SortedDictValues(owningUnionStructs), + "\n")) + + +def UnionConversions(descriptors, dictionaries, callbacks, config): + """ + Returns a CGThing to declare all union argument conversion helper structs. + """ + # Now find all the things we'll need as arguments because we + # need to unwrap them. + headers = set() + unionConversions = dict() + + for t, descriptor, dictionary in getAllTypes(descriptors, dictionaries, callbacks): + # Add info for the given type. descriptor and dictionary, if passed, are + # used to figure out what to do with interface types. + assert not descriptor or not dictionary + + t = t.unroll() + if not t.isUnion(): + continue + name = str(t) + if name not in unionConversions: + providers = getRelevantProviders(descriptor, config) + unionConversions[name] = CGUnionConversionStruct(t, providers[0]) + def addHeadersForType(f, providers): + f = f.unroll() + if f.isInterface(): + if f.isSpiderMonkeyInterface(): + headers.add("jsfriendapi.h") + headers.add("mozilla/dom/TypedArray.h") + elif f.inner.isExternal(): + providers = getRelevantProviders(descriptor, config) + for p in providers: + try: + typeDesc = p.getDescriptor(f.inner.identifier.name) + except NoSuchDescriptorError: + continue + headers.add(typeDesc.headerFile) + else: + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + # Check for whether we have a possibly-XPConnect-implemented + # interface. If we do, the right descriptor will come from + # providers[0], because that would be the non-worker + # descriptor provider, if we have one at all. + if (f.isGeckoInterface() and + providers[0].getDescriptor(f.inner.identifier.name).hasXPConnectImpls): + headers.add("nsDOMQS.h") + elif f.isDictionary(): + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + elif f.isPrimitive(): + headers.add("mozilla/dom/PrimitiveConversions.h") + elif f.isMozMap(): + headers.add("mozilla/dom/MozMap.h") + # And the internal type of the MozMap + addHeadersForType(f.inner, providers) + + for f in t.flatMemberTypes: + addHeadersForType(f, providers) + + return (headers, + CGWrapper(CGList(SortedDictValues(unionConversions), "\n"), + post="\n\n")) + + +class Argument(): + """ + A class for outputting the type and name of an argument + """ + def __init__(self, argType, name, default=None): + self.argType = argType + self.name = name + self.default = default + + def declare(self): + string = self.argType + ' ' + self.name + if self.default is not None: + string += " = " + self.default + return string + + def define(self): + return self.argType + ' ' + self.name + + +class CGAbstractMethod(CGThing): + """ + An abstract class for generating code for a method. Subclasses + should override definition_body to create the actual code. + + descriptor is the descriptor for the interface the method is associated with + + name is the name of the method as a string + + returnType is the IDLType of the return value + + args is a list of Argument objects + + inline should be True to generate an inline method, whose body is + part of the declaration. + + alwaysInline should be True to generate an inline method annotated with + MOZ_ALWAYS_INLINE. + + static should be True to generate a static method, which only has + a definition. + + If templateArgs is not None it should be a list of strings containing + template arguments, and the function will be templatized using those + arguments. + """ + def __init__(self, descriptor, name, returnType, args, inline=False, alwaysInline=False, static=False, templateArgs=None): + CGThing.__init__(self) + self.descriptor = descriptor + self.name = name + self.returnType = returnType + self.args = args + self.inline = inline + self.alwaysInline = alwaysInline + self.static = static + self.templateArgs = templateArgs + + def _argstring(self, declare): + return ', '.join([a.declare() if declare else a.define() for a in self.args]) + + def _template(self): + if self.templateArgs is None: + return '' + return 'template <%s>\n' % ', '.join(self.templateArgs) + + def _decorators(self): + decorators = [] + if self.alwaysInline: + decorators.append('MOZ_ALWAYS_INLINE') + elif self.inline: + decorators.append('inline') + if self.static: + decorators.append('static') + decorators.append(self.returnType) + maybeNewline = " " if self.inline else "\n" + return ' '.join(decorators) + maybeNewline + + def declare(self): + if self.inline: + return self._define(True) + return "%s%s%s(%s);\n" % (self._template(), self._decorators(), self.name, self._argstring(True)) + + def _define(self, fromDeclare=False): + return self.definition_prologue(fromDeclare) + self.definition_body() + self.definition_epilogue() + + def define(self): + return "" if self.inline else self._define() + + def definition_prologue(self, fromDeclare): + return "%s%s%s(%s)\n{\n" % (self._template(), self._decorators(), + self.name, self._argstring(fromDeclare)) + + def definition_epilogue(self): + return "}\n" + + def definition_body(self): + assert False # Override me! + + +class CGAbstractStaticMethod(CGAbstractMethod): + """ + Abstract base class for codegen of implementation-only (no + declaration) static methods. + """ + def __init__(self, descriptor, name, returnType, args): + CGAbstractMethod.__init__(self, descriptor, name, returnType, args, + inline=False, static=True) + + def declare(self): + # We only have implementation + return "" + + +class CGAbstractClassHook(CGAbstractStaticMethod): + """ + Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw + 'this' unwrapping as it assumes that the unwrapped type is always known. + """ + def __init__(self, descriptor, name, returnType, args): + CGAbstractStaticMethod.__init__(self, descriptor, name, returnType, + args) + + def definition_body_prologue(self): + return ("\n" # BOGUS extra blank line at start of function + " %s* self = UnwrapDOMObject<%s>(obj);\n" % + (self.descriptor.nativeType, self.descriptor.nativeType)) + + def definition_body(self): + return self.definition_body_prologue() + self.generate_code() + + def generate_code(self): + assert False # Override me! + + +class CGGetJSClassMethod(CGAbstractMethod): + def __init__(self, descriptor): + CGAbstractMethod.__init__(self, descriptor, 'GetJSClass', 'const JSClass*', + []) + + def definition_body(self): + return " return Class.ToJSClass();\n" + + +class CGAddPropertyHook(CGAbstractClassHook): + """ + A hook for addProperty, used to preserve our wrapper from GC. + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'obj'), + Argument('JS::Handle', 'id'), + Argument('JS::MutableHandle', 'vp')] + CGAbstractClassHook.__init__(self, descriptor, ADDPROPERTY_HOOK_NAME, + 'bool', args) + + def generate_code(self): + assert self.descriptor.wrapperCache + return indent(dedent(""" + // We don't want to preserve if we don't have a wrapper. + if (self->GetWrapperPreserveColor()) { + PreserveWrapper(self); + } + return true; + """)) + + +def DeferredFinalizeSmartPtr(descriptor): + if descriptor.nativeOwnership == 'owned': + smartPtr = 'nsAutoPtr' + else: + smartPtr = 'nsRefPtr' + return smartPtr + + +def finalizeHook(descriptor, hookName, freeOp): + finalize = "JSBindingFinalized<%s>::Finalized(self);\n" % descriptor.nativeType + if descriptor.wrapperCache: + finalize += "ClearWrapper(self, self);\n" + if descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + finalize += "self->mExpandoAndGeneration.expando = JS::UndefinedValue();\n" + if descriptor.interface.getExtendedAttribute("Global"): + finalize += "mozilla::dom::FinalizeGlobal(CastToJSFreeOp(%s), obj);\n" % freeOp + finalize += ("AddForDeferredFinalization<%s, %s >(self);\n" % + (descriptor.nativeType, DeferredFinalizeSmartPtr(descriptor))) + return CGIfWrapper(CGGeneric(finalize), "self") + + +class CGClassFinalizeHook(CGAbstractClassHook): + """ + A hook for finalize, used to release our native object. + """ + def __init__(self, descriptor): + args = [Argument('js::FreeOp*', 'fop'), Argument('JSObject*', 'obj')] + CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME, + 'void', args) + + def generate_code(self): + return indent(finalizeHook(self.descriptor, self.name, self.args[0].name).define()) + + +class CGClassConstructor(CGAbstractStaticMethod): + """ + JS-visible constructor for our objects + """ + def __init__(self, descriptor, ctor, name=CONSTRUCT_HOOK_NAME): + args = [Argument('JSContext*', 'cx'), + Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args) + self._ctor = ctor + + def define(self): + if not self._ctor: + return "" + return CGAbstractStaticMethod.define(self) + + def definition_body(self): + return self.generate_code() + + def generate_code(self): + # [ChromeOnly] interfaces may only be constructed by chrome. + chromeOnlyCheck = "" + if isChromeOnly(self._ctor): + chromeOnlyCheck = dedent(""" + if (!nsContentUtils::ThreadsafeIsCallerChrome()) { + return ThrowingConstructor(cx, argc, vp); + } + + """) + + # Additionally, we want to throw if a caller does a bareword invocation + # of a constructor without |new|. We don't enforce this for chrome in + # realease builds to avoid the addon compat fallout of making that + # change. See bug 916644. + # + # Figure out the name of our constructor for error reporting purposes. + # For unnamed webidl constructors, identifier.name is "constructor" but + # the name JS sees is the interface name; for named constructors + # identifier.name is the actual name. + name = self._ctor.identifier.name + if name != "constructor": + ctorName = name + else: + ctorName = self.descriptor.interface.identifier.name + + preamble = fill( # BOGUS extra blank line at beginning of function body + """ + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted obj(cx, &args.callee()); + $*{chromeOnlyCheck} + bool mayInvoke = args.isConstructing(); + #ifdef RELEASE_BUILD + mayInvoke = mayInvoke || nsContentUtils::ThreadsafeIsCallerChrome(); + #endif // RELEASE_BUILD + if (!mayInvoke) { + // XXXbz wish I could get the name from the callee instead of + // Adding more relocations + return ThrowConstructorWithoutNew(cx, "${ctorName}"); + } + """, + chromeOnlyCheck=chromeOnlyCheck, + ctorName=ctorName) + + name = self._ctor.identifier.name + nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name)) + callGenerator = CGMethodCall(nativeName, True, self.descriptor, + self._ctor, isConstructor=True, + constructorName=ctorName) + return indent(preamble) + callGenerator.define() + + +# Encapsulate the constructor in a helper method to share genConstructorBody with CGJSImplMethod. +class CGConstructNavigatorObjectHelper(CGAbstractStaticMethod): + """ + Construct a new JS-implemented WebIDL DOM object, for use on navigator. + """ + def __init__(self, descriptor): + name = "ConstructNavigatorObjectHelper" + args = [Argument('JSContext*', 'cx'), + Argument('GlobalObject&', 'global'), + Argument('ErrorResult&', 'aRv')] + rtype = 'already_AddRefed<%s>' % descriptor.name + CGAbstractStaticMethod.__init__(self, descriptor, name, rtype, args) + + def definition_body(self): + return indent(genConstructorBody(self.descriptor)) + + +class CGConstructNavigatorObject(CGAbstractMethod): + """ + Wrap a JS-implemented WebIDL object into a JS value, for use on navigator. + """ + def __init__(self, descriptor): + name = 'ConstructNavigatorObject' + args = [Argument('JSContext*', 'aCx'), Argument('JS::Handle', 'aObj')] + CGAbstractMethod.__init__(self, descriptor, name, 'JSObject*', args) + + def definition_body(self): + if not self.descriptor.interface.isJSImplemented(): + raise TypeError("Only JS-implemented classes are currently supported " + "on navigator. See bug 856820.") + return indent(fill( + """ + GlobalObject global(aCx, aObj); + if (global.Failed()) { + return nullptr; + } + ErrorResult rv; + JS::Rooted v(aCx); + { // Scope to make sure |result| goes out of scope while |v| is rooted + nsRefPtr result = ConstructNavigatorObjectHelper(aCx, global, rv); + rv.WouldReportJSException(); + if (rv.Failed()) { + ThrowMethodFailedWithDetails(aCx, rv, "${descriptorName}", "navigatorConstructor"); + return nullptr; + } + if (!WrapNewBindingObject(aCx, result, &v)) { + //XXX Assertion disabled for now, see bug 991271. + MOZ_ASSERT(true || JS_IsExceptionPending(aCx)); + return nullptr; + } + } + return &v.toObject(); + """, + descriptorName=self.descriptor.name)) + + +class CGClassConstructHookHolder(CGGeneric): + def __init__(self, descriptor): + if descriptor.interface.ctor(): + constructHook = CONSTRUCT_HOOK_NAME + else: + constructHook = "ThrowingConstructor" + CGGeneric.__init__(self, fill( + """ + static const JSNativeHolder ${CONSTRUCT_HOOK_NAME}_holder = { + ${constructHook}, + ${hooks} + }; + """, + CONSTRUCT_HOOK_NAME=CONSTRUCT_HOOK_NAME, + constructHook=constructHook, + hooks=NativePropertyHooks(descriptor))) + + +def NamedConstructorName(m): + return '_' + m.identifier.name + + +class CGNamedConstructors(CGThing): + def __init__(self, descriptor): + self.descriptor = descriptor + CGThing.__init__(self) + + def declare(self): + return "" + + def define(self): + if len(self.descriptor.interface.namedConstructors) == 0: + return "" + + constructorID = "constructors::id::" + if self.descriptor.interface.hasInterfaceObject(): + constructorID += self.descriptor.name + else: + constructorID += "_ID_Count" + + namedConstructors = "" + for n in self.descriptor.interface.namedConstructors: + namedConstructors += ( + "{ \"%s\", { %s, &sNamedConstructorNativePropertyHooks }, %i },\n" % + (n.identifier.name, NamedConstructorName(n), methodLength(n))) + + return fill( + """ + const NativePropertyHooks sNamedConstructorNativePropertyHooks = { + nullptr, + nullptr, + { nullptr, nullptr }, + prototypes::id::${name}, + ${constructorID}, + nullptr + }; + + static const NamedConstructor namedConstructors[] = { + $*{namedConstructors} + { nullptr, { nullptr, nullptr }, 0 } + }; + """, + name=self.descriptor.name, + constructorID=constructorID, + namedConstructors=namedConstructors) + + +class CGClassHasInstanceHook(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'obj'), + Argument('JS::MutableHandle', 'vp'), + Argument('bool*', 'bp')] + CGAbstractStaticMethod.__init__(self, descriptor, HASINSTANCE_HOOK_NAME, + 'bool', args) + + def define(self): + if not NeedsGeneratedHasInstance(self.descriptor): + return "" + return CGAbstractStaticMethod.define(self) + + def definition_body(self): + return self.generate_code() + + def generate_code(self): + # BOGUS extra blank line at start of function + header = dedent(""" + + if (!vp.isObject()) { + *bp = false; + return true; + } + + JS::Rooted instance(cx, &vp.toObject()); + """) + if self.descriptor.interface.hasInterfacePrototypeObject(): + return indent( + header + + fill( + """ + + static_assert(IsBaseOf::value, + "HasInstance only works for nsISupports-based classes."); + + bool ok = InterfaceHasInstance(cx, obj, instance, bp); + if (!ok || *bp) { + return ok; + } + + // FIXME Limit this to chrome by checking xpc::AccessCheck::isChrome(obj). + nsISupports* native = + nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, + js::UncheckedUnwrap(instance)); + nsCOMPtr qiResult = do_QueryInterface(native); + *bp = !!qiResult; + return true; + + """, # BOGUS extra blank line at end of function + nativeType=self.descriptor.nativeType, + name=self.descriptor.interface.identifier.name)) + + hasInstanceCode = dedent(""" + + const DOMClass* domClass = GetDOMClass(js::UncheckedUnwrap(instance)); + *bp = false; + if (!domClass) { + // Not a DOM object, so certainly not an instance of this interface + return true; + } + """) + if self.descriptor.interface.identifier.name == "ChromeWindow": + setBp = "*bp = UnwrapDOMObject(js::UncheckedUnwrap(instance))->IsChromeWindow()" + else: + setBp = "*bp = true" + # Sort interaces implementing self by name so we get stable output. + for iface in sorted(self.descriptor.interface.interfacesImplementingSelf, + key=lambda iface: iface.identifier.name): + hasInstanceCode += fill( + """ + + if (domClass->mInterfaceChain[PrototypeTraits::Depth] == prototypes::id::${name}) { + ${setBp}; + return true; + } + """, + name=iface.identifier.name, + setBp=setBp) + hasInstanceCode += "return true;\n" + return indent(header + hasInstanceCode) + + +def isChromeOnly(m): + return m.getExtendedAttribute("ChromeOnly") + + +def getAvailableInTestFunc(obj): + availableIn = obj.getExtendedAttribute("AvailableIn") + if availableIn is None: + return None + assert isinstance(availableIn, list) and len(availableIn) == 1 + if availableIn[0] == "PrivilegedApps": + return "IsInPrivilegedApp" + if availableIn[0] == "CertifiedApps": + return "IsInCertifiedApp" + raise TypeError("Unknown AvailableIn value '%s'" % availableIn[0]) + + +class MemberCondition: + """ + An object representing the condition for a member to actually be + exposed. Any of pref, func, and available can be None. If not + None, they should be strings that have the pref name (for "pref") + or function name (for "func" and "available"). + """ + def __init__(self, pref, func, available=None): + assert pref is None or isinstance(pref, str) + assert func is None or isinstance(func, str) + assert available is None or isinstance(available, str) + self.pref = pref + + def toFuncPtr(val): + if val is None: + return "nullptr" + return "&" + val + self.func = toFuncPtr(func) + self.available = toFuncPtr(available) + + def __eq__(self, other): + return (self.pref == other.pref and self.func == other.func and + self.available == other.available) + + def __ne__(self, other): + return not self.__eq__(other) + + +class PropertyDefiner: + """ + A common superclass for defining things on prototype objects. + + Subclasses should implement generateArray to generate the actual arrays of + things we're defining. They should also set self.chrome to the list of + things only exposed to chrome and self.regular to the list of things exposed + to both chrome and web pages. + """ + def __init__(self, descriptor, name): + self.descriptor = descriptor + self.name = name + # self.prefCacheData will store an array of (prefname, bool*) + # pairs for our bool var caches. generateArray will fill it + # in as needed. + self.prefCacheData = [] + + def hasChromeOnly(self): + return len(self.chrome) > 0 + + def hasNonChromeOnly(self): + return len(self.regular) > 0 + + def variableName(self, chrome): + if chrome: + if self.hasChromeOnly(): + return "sChrome" + self.name + else: + if self.hasNonChromeOnly(): + return "s" + self.name + return "nullptr" + + def usedForXrays(self): + # No Xrays in workers. + return not self.descriptor.workers + + def __str__(self): + # We only need to generate id arrays for things that will end + # up used via ResolveProperty or EnumerateProperties. + str = self.generateArray(self.regular, self.variableName(False), + self.usedForXrays()) + if self.hasChromeOnly(): + str += self.generateArray(self.chrome, self.variableName(True), + self.usedForXrays()) + return str + + @staticmethod + def getStringAttr(member, name): + attr = member.getExtendedAttribute(name) + if attr is None: + return None + # It's a list of strings + assert len(attr) == 1 + assert attr[0] is not None + return attr[0] + + @staticmethod + def getControllingCondition(interfaceMember): + return MemberCondition(PropertyDefiner.getStringAttr(interfaceMember, + "Pref"), + PropertyDefiner.getStringAttr(interfaceMember, + "Func"), + getAvailableInTestFunc(interfaceMember)) + + def generatePrefableArray(self, array, name, specTemplate, specTerminator, + specType, getCondition, getDataTuple, doIdArrays): + """ + This method generates our various arrays. + + array is an array of interface members as passed to generateArray + + name is the name as passed to generateArray + + specTemplate is a template for each entry of the spec array + + 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 for substitution into specTemplate. + """ + + # 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 + lastCondition = getCondition(array[0]) # So we won't put a specTerminator + # at the very front of the list. + specs = [] + prefableSpecs = [] + + prefableTemplate = ' { true, %s, %s, &%s[%d] }' + prefCacheTemplate = '&%s[%d].enabled' + + def switchToCondition(props, condition): + # Remember the info about where our pref-controlled + # booleans live. + if condition.pref is not None: + props.prefCacheData.append( + (condition.pref, + prefCacheTemplate % (name, len(prefableSpecs)))) + # Set up pointers to the new sets of specs inside prefableSpecs + prefableSpecs.append(prefableTemplate % + (condition.func, + condition.available, + name + "_specs", len(specs))) + + switchToCondition(self, lastCondition) + + for member in array: + curCondition = getCondition(member) + if lastCondition != curCondition: + # Terminate previous list + specs.append(specTerminator) + # And switch to our new pref + switchToCondition(self, curCondition) + lastCondition = curCondition + # And the actual spec + specs.append(specTemplate % getDataTuple(member)) + specs.append(specTerminator) + prefableSpecs.append(" { false, nullptr }") + + specType = "const " + specType + arrays = fill( + """ + static ${specType} ${name}_specs[] = { + ${specs} + }; + + // Can't be const because the pref-enabled boolean needs to be writable + static Prefable<${specType}> ${name}[] = { + ${prefableSpecs} + }; + + """, + specType=specType, + name=name, + specs=',\n'.join(specs), + prefableSpecs=',\n'.join(prefableSpecs)) + if doIdArrays: + arrays += "static jsid %s_ids[%i];\n\n" % (name, len(specs)) + return arrays + + +# The length of a method is the minimum of the lengths of the +# argument lists of all its overloads. +def overloadLength(arguments): + i = len(arguments) + while i > 0 and arguments[i - 1].optional: + i -= 1 + return i + + +def methodLength(method): + signatures = method.signatures() + return min(overloadLength(arguments) for retType, arguments in signatures) + + +class MethodDefiner(PropertyDefiner): + """ + A class for defining methods on a prototype object. + """ + def __init__(self, descriptor, name, static): + 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 + not m.isIdentifierLess()] + else: + methods = [] + self.chrome = [] + self.regular = [] + for m in methods: + if m.identifier.name == 'queryInterface': + if self.descriptor.workers: + continue + if m.isStatic(): + raise TypeError("Legacy queryInterface member shouldn't be static") + signatures = m.signatures() + + def argTypeIsIID(arg): + return arg.type.inner.isExternal() and arg.type.inner.identifier.name == 'IID' + if len(signatures) > 1 or len(signatures[0][1]) > 1 or not argTypeIsIID(signatures[0][1][0]): + raise TypeError("There should be only one queryInterface method with 1 argument of type IID") + + # Make sure to not stick QueryInterface on abstract interfaces that + # have hasXPConnectImpls (like EventTarget). So only put it on + # interfaces that are concrete and all of whose ancestors are abstract. + def allAncestorsAbstract(iface): + if not iface.parent: + return True + desc = self.descriptor.getDescriptor(iface.parent.identifier.name) + if desc.concrete: + return False + return allAncestorsAbstract(iface.parent) + if (not self.descriptor.interface.hasInterfacePrototypeObject() or + not self.descriptor.concrete or + not allAncestorsAbstract(self.descriptor.interface)): + raise TypeError("QueryInterface is only supported on " + "interfaces that are concrete and all " + "of whose ancestors are abstract: " + + self.descriptor.name) + condition = "WantsQueryInterface<%s>::Enabled" % descriptor.nativeType + self.regular.append({ + "name": 'QueryInterface', + "methodInfo": False, + "length": 1, + "flags": "0", + "condition": MemberCondition(None, condition) + }) + continue + + method = { + "name": m.identifier.name, + "methodInfo": not m.isStatic(), + "length": methodLength(m), + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition(m), + "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"), + "returnsPromise": m.returnsPromise() + } + if isChromeOnly(m): + self.chrome.append(method) + else: + self.regular.append(method) + + # FIXME Check for an existing iterator on the interface first. + if any(m.isGetter() and m.isIndexed() for m in methods): + self.regular.append({ + "name": "@@iterator", + "methodInfo": False, + "selfHostedName": "ArrayValues", + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": MemberCondition(None, None) + }) + + if not static: + stringifier = descriptor.operations['Stringifier'] + if stringifier: + toStringDesc = { + "name": "toString", + "nativeName": stringifier.identifier.name, + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition(stringifier) + } + if isChromeOnly(stringifier): + self.chrome.append(toStringDesc) + else: + self.regular.append(toStringDesc) + jsonifier = descriptor.operations['Jsonifier'] + if jsonifier: + toJSONDesc = { + "name": "toJSON", + "nativeName": jsonifier.identifier.name, + "length": 0, + "flags": "JSPROP_ENUMERATE", + "condition": PropertyDefiner.getControllingCondition(jsonifier) + } + if isChromeOnly(jsonifier): + self.chrome.append(toJSONDesc) + else: + self.regular.append(toJSONDesc) + elif (descriptor.interface.isJSImplemented() and + descriptor.interface.hasInterfaceObject()): + self.chrome.append({ + "name": '_create', + "nativeName": ("%s::_Create" % descriptor.name), + "methodInfo": False, + "length": 2, + "flags": "0", + "condition": MemberCondition(None, None) + }) + + if static: + if not descriptor.interface.hasInterfaceObject(): + # static methods go on the interface object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + else: + if not descriptor.interface.hasInterfacePrototypeObject(): + # non-static methods go on the interface prototype object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + + def generateArray(self, array, name, doIdArrays): + if len(array) == 0: + return "" + + def condition(m): + return m["condition"] + + def specData(m): + if "selfHostedName" in m: + selfHostedName = '"%s"' % m["selfHostedName"] + assert not m.get("methodInfo", True) + accessor = "nullptr" + jitinfo = "nullptr" + else: + selfHostedName = "nullptr" + accessor = m.get("nativeName", m["name"]) + if m.get("methodInfo", True): + # Cast this in case the methodInfo is a + # JSTypedMethodJitInfo. + jitinfo = ("reinterpret_cast(&%s_methodinfo)" % accessor) + if m.get("allowCrossOriginThis", False): + if m.get("returnsPromise", False): + raise TypeError("%s returns a Promise but should " + "be allowed cross-origin?" % + accessor) + accessor = "genericCrossOriginMethod" + elif self.descriptor.needsSpecialGenericOps(): + if m.get("returnsPromise", False): + raise TypeError("%s returns a Promise but needs " + "special generic ops?" % + accessor) + accessor = "genericMethod" + elif m.get("returnsPromise", False): + accessor = "GenericPromiseReturningBindingMethod" + else: + accessor = "GenericBindingMethod" + else: + if m.get("returnsPromise", False): + jitinfo = "&%s_methodinfo" % accessor + accessor = "StaticMethodPromiseWrapper" + else: + jitinfo = "nullptr" + + return (m["name"], accessor, jitinfo, m["length"], m["flags"], selfHostedName) + + return self.generatePrefableArray( + array, name, + ' JS_FNSPEC("%s", %s, %s, %s, %s, %s)', + ' JS_FS_END', + 'JSFunctionSpec', + condition, specData, doIdArrays) + + +class AttrDefiner(PropertyDefiner): + def __init__(self, descriptor, name, static, unforgeable=False): + assert not (static and unforgeable) + PropertyDefiner.__init__(self, descriptor, name) + self.name = name + # Ignore non-static attributes for interfaces without a proto object + if descriptor.interface.hasInterfacePrototypeObject() or static: + attributes = [m for m in descriptor.interface.members if + m.isAttr() and m.isStatic() == static and + m.isUnforgeable() == unforgeable] + else: + attributes = [] + self.chrome = [m for m in attributes if isChromeOnly(m)] + self.regular = [m for m in attributes if not isChromeOnly(m)] + self.static = static + self.unforgeable = unforgeable + + if static: + if not descriptor.interface.hasInterfaceObject(): + # static attributes go on the interface object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + else: + if not descriptor.interface.hasInterfacePrototypeObject(): + # non-static attributes go on the interface prototype object + assert not self.hasChromeOnly() and not self.hasNonChromeOnly() + + def generateArray(self, array, name, doIdArrays): + if len(array) == 0: + return "" + + def flags(attr): + unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else "" + return ("JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS" + + unforgeable) + + def getter(attr): + if self.static: + accessor = 'get_' + attr.identifier.name + jitinfo = "nullptr" + else: + if attr.hasLenientThis(): + accessor = "genericLenientGetter" + elif attr.getExtendedAttribute("CrossOriginReadable"): + accessor = "genericCrossOriginGetter" + elif self.descriptor.needsSpecialGenericOps(): + accessor = "genericGetter" + else: + accessor = "GenericBindingGetter" + jitinfo = "&%s_getterinfo" % attr.identifier.name + return "{ { JS_CAST_NATIVE_TO(%s, JSPropertyOp), %s } }" % \ + (accessor, jitinfo) + + def setter(attr): + if (attr.readonly and + attr.getExtendedAttribute("PutForwards") is None and + attr.getExtendedAttribute("Replaceable") is None): + return "JSOP_NULLWRAPPER" + if self.static: + accessor = 'set_' + attr.identifier.name + jitinfo = "nullptr" + else: + if attr.hasLenientThis(): + accessor = "genericLenientSetter" + elif attr.getExtendedAttribute("CrossOriginWritable"): + accessor = "genericCrossOriginSetter" + elif self.descriptor.needsSpecialGenericOps(): + accessor = "genericSetter" + else: + accessor = "GenericBindingSetter" + jitinfo = "&%s_setterinfo" % attr.identifier.name + return "{ { JS_CAST_NATIVE_TO(%s, JSStrictPropertyOp), %s } }" % \ + (accessor, jitinfo) + + def specData(attr): + return (attr.identifier.name, flags(attr), getter(attr), + setter(attr)) + + return self.generatePrefableArray( + array, name, + ' { "%s", %s, %s, %s}', + ' JS_PS_END', + 'JSPropertySpec', + PropertyDefiner.getControllingCondition, specData, doIdArrays) + + +class ConstDefiner(PropertyDefiner): + """ + A class for definining constants on the interface object + """ + def __init__(self, descriptor, name): + PropertyDefiner.__init__(self, descriptor, name) + self.name = name + constants = [m for m in descriptor.interface.members if m.isConst()] + self.chrome = [m for m in constants if isChromeOnly(m)] + self.regular = [m for m in constants if not isChromeOnly(m)] + + def generateArray(self, array, name, doIdArrays): + if len(array) == 0: + return "" + + def specData(const): + return (const.identifier.name, + convertConstIDLValueToJSVal(const.value)) + + return self.generatePrefableArray( + array, name, + ' { "%s", %s }', + ' { 0, JS::UndefinedValue() }', + 'ConstantSpec', + PropertyDefiner.getControllingCondition, specData, doIdArrays) + + +class PropertyArrays(): + def __init__(self, descriptor): + self.staticMethods = MethodDefiner(descriptor, "StaticMethods", + static=True) + self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes", + static=True) + self.methods = MethodDefiner(descriptor, "Methods", static=False) + self.attrs = AttrDefiner(descriptor, "Attributes", static=False) + self.unforgeableAttrs = AttrDefiner(descriptor, "UnforgeableAttributes", + static=False, unforgeable=True) + self.consts = ConstDefiner(descriptor, "Constants") + + @staticmethod + def arrayNames(): + return ["staticMethods", "staticAttrs", "methods", "attrs", + "unforgeableAttrs", "consts"] + + def hasChromeOnly(self): + return any(getattr(self, a).hasChromeOnly() for a in self.arrayNames()) + + def hasNonChromeOnly(self): + return any(getattr(self, a).hasNonChromeOnly() for a in self.arrayNames()) + + def __str__(self): + define = "" + for array in self.arrayNames(): + define += str(getattr(self, array)) + return define + + +class CGNativeProperties(CGList): + def __init__(self, descriptor, properties): + def generateNativeProperties(name, chrome): + def check(p): + return p.hasChromeOnly() if chrome else p.hasNonChromeOnly() + + nativeProps = [] + for array in properties.arrayNames(): + propertyArray = getattr(properties, array) + if check(propertyArray): + if propertyArray.usedForXrays(): + ids = "%(name)s_ids" + else: + ids = "nullptr" + props = "%(name)s, " + ids + ", %(name)s_specs" + props = (props % {'name': propertyArray.variableName(chrome)}) + else: + props = "nullptr, nullptr, nullptr" + nativeProps.append(CGGeneric(props)) + return CGWrapper(CGIndenter(CGList(nativeProps, ",\n")), + pre="static const NativeProperties %s = {\n" % name, + post="\n};\n") + + nativeProperties = [] + if properties.hasNonChromeOnly(): + nativeProperties.append( + generateNativeProperties("sNativeProperties", False)) + if properties.hasChromeOnly(): + nativeProperties.append( + generateNativeProperties("sChromeOnlyNativeProperties", True)) + + CGList.__init__(self, nativeProperties, "\n") + + def declare(self): + return "" + + def define(self): + # BOGUSly strip off a newline + return CGList.define(self).rstrip() + + +class CGCreateInterfaceObjectsMethod(CGAbstractMethod): + """ + Generate the CreateInterfaceObjects method for an interface descriptor. + + properties should be a PropertyArrays instance. + """ + def __init__(self, descriptor, properties): + args = [Argument('JSContext*', 'aCx'), + Argument('JS::Handle', 'aGlobal'), + Argument('ProtoAndIfaceCache&', 'aProtoAndIfaceCache'), + Argument('bool', 'aDefineOnGlobal')] + CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args) + self.properties = properties + + def definition_body(self): + if len(self.descriptor.prototypeChain) == 1: + parentProtoType = "Rooted" + if self.descriptor.interface.getExtendedAttribute("ArrayClass"): + getParentProto = "aCx, JS_GetArrayPrototype(aCx, aGlobal)" + else: + getParentProto = "aCx, JS_GetObjectPrototype(aCx, aGlobal)" + else: + parentProtoName = self.descriptor.prototypeChain[-2] + parentDesc = self.descriptor.getDescriptor(parentProtoName) + if parentDesc.workers: + parentProtoName += '_workers' + getParentProto = ("%s::GetProtoObject(aCx, aGlobal)" % + toBindingNamespace(parentProtoName)) + parentProtoType = "Handle" + + parentWithInterfaceObject = self.descriptor.interface.parent + while (parentWithInterfaceObject and + not parentWithInterfaceObject.hasInterfaceObject()): + parentWithInterfaceObject = parentWithInterfaceObject.parent + if parentWithInterfaceObject: + parentIfaceName = parentWithInterfaceObject.identifier.name + parentDesc = self.descriptor.getDescriptor(parentIfaceName) + if parentDesc.workers: + parentIfaceName += "_workers" + getConstructorProto = ("%s::GetConstructorObject(aCx, aGlobal)" % + toBindingNamespace(parentIfaceName)) + constructorProtoType = "Handle" + else: + getConstructorProto = "aCx, JS_GetFunctionPrototype(aCx, aGlobal)" + constructorProtoType = "Rooted" + + 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 + + idsToInit = [] + # There is no need to init any IDs in workers, because worker bindings + # don't have Xrays. + if not self.descriptor.workers: + for var in self.properties.arrayNames(): + props = getattr(self.properties, var) + # We only have non-chrome ids to init if we have no chrome ids. + if props.hasChromeOnly(): + idsToInit.append(props.variableName(True)) + if props.hasNonChromeOnly(): + idsToInit.append(props.variableName(False)) + if len(idsToInit) > 0: + initIdCalls = ["!InitIds(aCx, %s, %s_ids)" % (varname, varname) + for varname in idsToInit] + idsInitedFlag = CGGeneric("static bool sIdsInited = false;\n") + setFlag = CGGeneric("sIdsInited = true;\n") + initIdConditionals = [CGIfWrapper(CGGeneric("return;\n"), call) + for call in initIdCalls] + initIds = CGList([idsInitedFlag, + CGIfWrapper(CGList(initIdConditionals + [setFlag]), + "!sIdsInited && NS_IsMainThread()")]) + else: + initIds = None + + prefCacheData = [] + for var in self.properties.arrayNames(): + props = getattr(self.properties, var) + prefCacheData.extend(props.prefCacheData) + if len(prefCacheData) != 0: + prefCacheData = [ + CGGeneric('Preferences::AddBoolVarCache(%s, "%s");\n' % (ptr, pref)) + for pref, ptr in prefCacheData] + prefCache = CGWrapper(CGIndenter(CGList(prefCacheData)), + pre=("static bool sPrefCachesInited = false;\n" + "if (!sPrefCachesInited) {\n" + " sPrefCachesInited = true;\n"), + post="}\n") + else: + prefCache = None + + if UseHolderForUnforgeable(self.descriptor): + createUnforgeableHolder = CGGeneric(dedent(""" + JS::Rooted unforgeableHolder(aCx, + JS_NewObjectWithGivenProto(aCx, nullptr, JS::NullPtr(), JS::NullPtr())); + if (!unforgeableHolder) { + return; + } + """)) + defineUnforgeables = InitUnforgeablePropertiesOnObject(self.descriptor, + "unforgeableHolder", + self.properties) + createUnforgeableHolder = CGList( + [createUnforgeableHolder, defineUnforgeables]) + else: + createUnforgeableHolder = None + + getParentProto = fill( + """ + JS::${type} parentProto(${getParentProto}); + if (!parentProto) { + return; + } + """, + type=parentProtoType, + getParentProto=getParentProto) + + getConstructorProto = fill( + """ + JS::${type} constructorProto(${getConstructorProto}); + if (!constructorProto) { + return; + } + """, + type=constructorProtoType, + getConstructorProto=getConstructorProto) + + if (needInterfaceObject and + self.descriptor.needsConstructHookHolder()): + constructHookHolder = "&" + CONSTRUCT_HOOK_NAME + "_holder" + else: + constructHookHolder = "nullptr" + if self.descriptor.interface.ctor(): + constructArgs = methodLength(self.descriptor.interface.ctor()) + else: + constructArgs = 0 + if len(self.descriptor.interface.namedConstructors) > 0: + namedConstructors = "namedConstructors" + else: + namedConstructors = "nullptr" + + if needInterfacePrototypeObject: + protoClass = "&PrototypeClass.mBase" + protoCache = "&aProtoAndIfaceCache.EntrySlotOrCreate(prototypes::id::%s)" % self.descriptor.name + else: + protoClass = "nullptr" + protoCache = "nullptr" + if needInterfaceObject: + interfaceClass = "&InterfaceObjectClass.mBase" + interfaceCache = "&aProtoAndIfaceCache.EntrySlotOrCreate(constructors::id::%s)" % self.descriptor.name + else: + # We don't have slots to store the named constructors. + assert len(self.descriptor.interface.namedConstructors) == 0 + interfaceClass = "nullptr" + interfaceCache = "nullptr" + + if self.descriptor.concrete: + domClass = "&Class.mClass" + else: + domClass = "nullptr" + + if self.properties.hasNonChromeOnly(): + properties = "&sNativeProperties" + else: + properties = "nullptr" + if self.properties.hasChromeOnly(): + accessCheck = "nsContentUtils::ThreadsafeIsCallerChrome()" + chromeProperties = accessCheck + " ? &sChromeOnlyNativeProperties : nullptr" + else: + chromeProperties = "nullptr" + + call = fill( + """ + dom::CreateInterfaceObjects(aCx, aGlobal, parentProto, + ${protoClass}, ${protoCache}, + constructorProto, ${interfaceClass}, ${constructHookHolder}, ${constructArgs}, ${namedConstructors}, + ${interfaceCache}, + ${domClass}, + ${properties}, + ${chromeProperties}, + ${name}, aDefineOnGlobal); + """, + protoClass=protoClass, + protoCache=protoCache, + interfaceClass=interfaceClass, + constructHookHolder=constructHookHolder, + constructArgs=constructArgs, + namedConstructors=namedConstructors, + interfaceCache=interfaceCache, + domClass=domClass, + properties=properties, + chromeProperties=chromeProperties, + name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr") + + if UseHolderForUnforgeable(self.descriptor): + assert needInterfacePrototypeObject + setUnforgeableHolder = CGGeneric(fill( + """ + JSObject* proto = aProtoAndIfaceCache.EntrySlotOrCreate(prototypes::id::${name}); + if (proto) { + js::SetReservedSlot(proto, DOM_INTERFACE_PROTO_SLOTS_BASE, + JS::ObjectValue(*unforgeableHolder)); + } + """, + name=self.descriptor.name)) + else: + setUnforgeableHolder = None + functionBody = CGList( + [CGGeneric(getParentProto), CGGeneric(getConstructorProto), initIds, + prefCache, createUnforgeableHolder, CGGeneric(call), setUnforgeableHolder], + "\n") + return CGIndenter(functionBody).define() + + +class CGGetPerInterfaceObject(CGAbstractMethod): + """ + A method for getting a per-interface object (a prototype object or interface + constructor object). + """ + def __init__(self, descriptor, name, idPrefix="", extraArgs=[]): + args = [Argument('JSContext*', 'aCx'), + Argument('JS::Handle', 'aGlobal')] + extraArgs + CGAbstractMethod.__init__(self, descriptor, name, + 'JS::Handle', args) + self.id = idPrefix + "id::" + self.descriptor.name + + def definition_body(self): + # BOGUS extra blank line at the beginning of the code below + # BOGUS - should be a blank line between an if-block and following comment below + return indent(fill( + """ + + /* Make sure our global is sane. Hopefully we can remove this sometime */ + if (!(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL)) { + return JS::NullPtr(); + } + /* Check to see whether the interface objects are already installed */ + ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(aGlobal); + if (!protoAndIfaceCache.EntrySlotIfExists(${id})) { + CreateInterfaceObjects(aCx, aGlobal, protoAndIfaceCache, aDefineOnGlobal); + } + + /* + * The object might _still_ be null, but that's OK. + * + * Calling fromMarkedLocation() is safe because protoAndIfaceCache is + * traced by TraceProtoAndIfaceCache() and its contents are never + * changed after they have been set. + */ + return JS::Handle::fromMarkedLocation(protoAndIfaceCache.EntrySlotMustExist(${id}).address()); + """, + id=self.id)) + + +class CGGetProtoObjectMethod(CGGetPerInterfaceObject): + """ + A method for getting the interface prototype object. + """ + def __init__(self, descriptor): + CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject", + "prototypes::") + + def definition_body(self): + # BOGUS extra blank line at start of method + return indent(dedent(""" + + /* Get the interface prototype object for this class. This will create the + object as needed. */ + bool aDefineOnGlobal = true; + """)) + CGGetPerInterfaceObject.definition_body(self) + + +class CGGetConstructorObjectMethod(CGGetPerInterfaceObject): + """ + A method for getting the interface constructor object. + """ + def __init__(self, descriptor): + CGGetPerInterfaceObject.__init__( + self, descriptor, "GetConstructorObject", + "constructors::", + extraArgs=[Argument("bool", "aDefineOnGlobal", "true")]) + + def definition_body(self): + # BOGUS extra blank line at start of method + return indent(dedent(""" + + /* Get the interface object for this class. This will create the object as + needed. */ + """)) + CGGetPerInterfaceObject.definition_body(self) + + +class CGDefineDOMInterfaceMethod(CGAbstractMethod): + """ + A method for resolve hooks to try to lazily define the interface object for + a given interface. + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'aCx'), + Argument('JS::Handle', 'aGlobal'), + Argument('JS::Handle', 'id'), + Argument('bool', 'aDefineOnGlobal')] + CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', 'JSObject*', args) + + def declare(self): + if self.descriptor.workers: + return '' + return CGAbstractMethod.declare(self) + + def define(self): + if self.descriptor.workers: + return '' + return CGAbstractMethod.define(self) + + def definition_body(self): + if len(self.descriptor.interface.namedConstructors) > 0: + getConstructor = indent(dedent(""" + JSObject* interfaceObject = GetConstructorObject(aCx, aGlobal, aDefineOnGlobal); + if (!interfaceObject) { + return nullptr; + } + for (unsigned slot = DOM_INTERFACE_SLOTS_BASE; slot < JSCLASS_RESERVED_SLOTS(&InterfaceObjectClass.mBase); ++slot) { + JSObject* constructor = &js::GetReservedSlot(interfaceObject, slot).toObject(); + if (JS_GetFunctionId(JS_GetObjectFunction(constructor)) == JSID_TO_STRING(id)) { + return constructor; + } + } + return interfaceObject; + """)) + else: + getConstructor = " return GetConstructorObject(aCx, aGlobal, aDefineOnGlobal);\n" + return getConstructor + + +class CGConstructorEnabled(CGAbstractMethod): + """ + A method for testing whether we should be exposing this interface + object or navigator property. This can perform various tests + depending on what conditions are specified on the interface. + """ + def __init__(self, descriptor): + CGAbstractMethod.__init__(self, descriptor, + 'ConstructorEnabled', 'bool', + [Argument("JSContext*", "aCx"), + Argument("JS::Handle", "aObj")]) + + def definition_body(self): + conditions = [] + iface = self.descriptor.interface + pref = iface.getExtendedAttribute("Pref") + if pref: + assert isinstance(pref, list) and len(pref) == 1 + conditions.append('Preferences::GetBool("%s")' % pref[0]) + if iface.getExtendedAttribute("ChromeOnly"): + conditions.append("nsContentUtils::ThreadsafeIsCallerChrome()") + func = iface.getExtendedAttribute("Func") + if func: + assert isinstance(func, list) and len(func) == 1 + conditions.append("%s(aCx, aObj)" % func[0]) + availableIn = getAvailableInTestFunc(iface) + if availableIn: + conditions.append("%s(aCx, aObj)" % availableIn) + # We should really have some conditions + assert len(conditions) + body = CGWrapper(CGList((CGGeneric(cond) for cond in conditions), + " &&\n"), + pre="return ", post=";\n", reindent=True) + return CGIndenter(body).define() + + +def CreateBindingJSObject(descriptor, properties, parent): + # 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. + objDecl = "JS::Rooted obj(aCx);\n" + if descriptor.proxy: + create = fill( + """ + JS::Rooted proxyPrivateVal(aCx, JS::PrivateValue(aObject)); + js::ProxyOptions options; + options.setClass(&Class.mBase); + obj = NewProxyObject(aCx, DOMProxyHandler::getInstance(), + proxyPrivateVal, proto, ${parent}, options); + if (!obj) { + return nullptr; + } + + """, + parent=parent) + if descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + create += dedent(""" + js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, + JS::PrivateValue(&aObject->mExpandoAndGeneration)); + + """) + else: + create = fill( + """ + obj = JS_NewObject(aCx, Class.ToJSClass(), proto, ${parent}); + if (!obj) { + return nullptr; + } + + js::SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(aObject)); + """, + parent=parent) + if "Window" in descriptor.interface.identifier.name: + create = dedent(""" + MOZ_ASSERT(false, + "Our current reserved slot situation is unsafe for globals. Fix " + "bug 760095!"); + """) + create + create = objDecl + create + + if descriptor.nativeOwnership == 'refcounted': + create += "NS_ADDREF(aObject);\n" + else: + create += dedent(""" + // Make sure the native objects inherit from NonRefcountedDOMObject so that we + // log their ctor and dtor. + MustInheritFromNonRefcountedDOMObject(aObject); + *aTookOwnership = true; + """) + return create + + +def InitUnforgeablePropertiesOnObject(descriptor, obj, properties, failureReturnValue=""): + """ + properties is a PropertyArrays instance + """ + defineUnforgeables = fill( + """ + if (!DefineUnforgeableAttributes(aCx, ${obj}, %s)) { + return${rv}; + } + """, + obj=obj, + rv=" " + failureReturnValue if failureReturnValue else "") + + unforgeableAttrs = properties.unforgeableAttrs + unforgeables = [] + if unforgeableAttrs.hasNonChromeOnly(): + unforgeables.append(CGGeneric(defineUnforgeables % + unforgeableAttrs.variableName(False))) + if unforgeableAttrs.hasChromeOnly(): + unforgeables.append( + CGIfWrapper(CGGeneric(defineUnforgeables % + unforgeableAttrs.variableName(True)), + "nsContentUtils::ThreadsafeIsCallerChrome()")) + return CGList(unforgeables) + + +def InitUnforgeableProperties(descriptor, properties): + """ + properties is a PropertyArrays instance + """ + unforgeableAttrs = properties.unforgeableAttrs + if not unforgeableAttrs.hasNonChromeOnly() and not unforgeableAttrs.hasChromeOnly(): + return "" + + if descriptor.proxy: + unforgeableProperties = CGGeneric( + "// Unforgeable properties on proxy-based bindings are stored in an object held\n" + "// by the interface prototype object.\n" + "\n") # BOGUS extra blank line + else: + unforgeableProperties = CGWrapper( + InitUnforgeablePropertiesOnObject(descriptor, "obj", properties, "nullptr"), + pre=( + "// Important: do unforgeable property setup after we have handed\n" + "// over ownership of the C++ object to obj as needed, so that if\n" + "// we fail and it ends up GCed it won't have problems in the\n" + "// finalizer trying to drop its ownership of the C++ object.\n")) + return CGWrapper(unforgeableProperties, pre="\n").define() + + +def AssertInheritanceChain(descriptor): + asserts = "" + iface = descriptor.interface + while iface: + desc = descriptor.getDescriptor(iface.identifier.name) + asserts += ( + " MOZ_ASSERT(static_cast<%s*>(aObject) == \n" + " reinterpret_cast<%s*>(aObject));\n" % + (desc.nativeType, desc.nativeType)) + iface = iface.parent + asserts += " MOZ_ASSERT(ToSupportsIsCorrect(aObject));\n" + return asserts + + +def InitMemberSlots(descriptor, wrapperCache): + """ + 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 "\n" # BOGUS blank line only if this returns empty + if wrapperCache: + clearWrapper = " aCache->ClearWrapper();\n" + else: + clearWrapper = "" + return ("if (!UpdateMemberSlots(aCx, obj, aObject)) {\n" + "%s" + " return nullptr;\n" + "}\n" % clearWrapper) + + +class CGWrapWithCacheMethod(CGAbstractMethod): + """ + Create a wrapper JSObject for a given native that implements nsWrapperCache. + + properties should be a PropertyArrays instance. + """ + def __init__(self, descriptor, properties): + assert descriptor.interface.hasInterfacePrototypeObject() + args = [Argument('JSContext*', 'aCx'), + Argument(descriptor.nativeType + '*', 'aObject'), + Argument('nsWrapperCache*', 'aCache')] + CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args) + self.properties = properties + + def definition_body(self): + return fill( + """ + ${assertion} + MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache), + "nsISupports must be on our primary inheritance chain"); + + JS::Rooted parent(aCx, + GetRealParentObject(aObject, + WrapNativeParent(aCx, aObject->GetParentObject()))); + if (!parent) { + return nullptr; + } + + // That might have ended up wrapping us already, due to the wonders + // of XBL. Check for that, and bail out as needed. Scope so we don't + // collide with the "obj" we declare in CreateBindingJSObject. + { + JSObject* obj = aCache->GetWrapper(); + if (obj) { + return obj; + } + } + + JSAutoCompartment ac(aCx, parent); + JS::Rooted global(aCx, JS_GetGlobalForObject(aCx, parent)); + JS::Handle proto = GetProtoObject(aCx, global); + if (!proto) { + return nullptr; + } + + $*{parent} + + $*{unforgeable} + + aCache->SetWrapper(obj); + $*{slots} + return obj; + """, + assertion=AssertInheritanceChain(self.descriptor), + parent=CreateBindingJSObject(self.descriptor, self.properties, + "parent"), + unforgeable=InitUnforgeableProperties(self.descriptor, self.properties), + slots=InitMemberSlots(self.descriptor, True)) + + +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')] + CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args, + inline=True, templateArgs=["class T"]) + + def definition_body(self): + return " return Wrap(aCx, aObject, aObject);\n" + + +class CGWrapNonWrapperCacheMethod(CGAbstractMethod): + """ + Create a wrapper JSObject for a given native that does not implement + nsWrapperCache. + + properties should be a PropertyArrays instance. + """ + def __init__(self, descriptor, properties): + # XXX can we wrap if we don't have an interface prototype object? + assert descriptor.interface.hasInterfacePrototypeObject() + args = [Argument('JSContext*', 'aCx'), + Argument(descriptor.nativeType + '*', 'aObject')] + if descriptor.nativeOwnership == 'owned': + args.append(Argument('bool*', 'aTookOwnership')) + CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args) + self.properties = properties + + def definition_body(self): + return fill( + """ + ${assertions} + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + JS::Handle proto = GetProtoObject(aCx, global); + if (!proto) { + return nullptr; + } + + $*{global_} + + $*{unforgeable} + + $*{slots} + return obj; + """, + assertions=AssertInheritanceChain(self.descriptor), + global_=CreateBindingJSObject(self.descriptor, self.properties, + "global"), + unforgeable=InitUnforgeableProperties(self.descriptor, self.properties), + slots=InitMemberSlots(self.descriptor, False)) + + +class CGWrapGlobalMethod(CGAbstractMethod): + """ + Create a wrapper JSObject for a global. The global must implement + nsWrapperCache. + + properties should be a PropertyArrays instance. + """ + def __init__(self, descriptor, properties): + assert descriptor.interface.hasInterfacePrototypeObject() + args = [Argument('JSContext*', 'aCx'), + Argument(descriptor.nativeType + '*', 'aObject'), + Argument('nsWrapperCache*', 'aCache'), + Argument('JS::CompartmentOptions&', 'aOptions'), + Argument('JSPrincipals*', 'aPrincipal')] + CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args) + self.descriptor = descriptor + self.properties = properties + + def definition_body(self): + return fill( + """ + ${assertions} + MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache), + "nsISupports must be on our primary inheritance chain"); + + JS::Rooted obj(aCx); + obj = CreateGlobal<${nativeType}, GetProtoObject>(aCx, + aObject, + aCache, + Class.ToJSClass(), + aOptions, + aPrincipal); + + $*{unforgeable} + + $*{slots} + + // XXXkhuey can't do this yet until workers can lazy resolve. + // JS_FireOnNewGlobalObject(aCx, obj); + + return obj; + """, + assertions=AssertInheritanceChain(self.descriptor), + nativeType=self.descriptor.nativeType, + unforgeable=InitUnforgeableProperties(self.descriptor, self.properties), + slots=InitMemberSlots(self.descriptor, True)) + + +class CGUpdateMemberSlotsMethod(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'aCx'), + Argument('JS::Handle', 'aWrapper'), + Argument(descriptor.nativeType + '*', 'aObject')] + CGAbstractStaticMethod.__init__(self, descriptor, 'UpdateMemberSlots', 'bool', args) + + def definition_body(self): + body = ("JS::Rooted temp(aCx);\n" + "JSJitGetterCallArgs args(&temp);\n") + for m in self.descriptor.interface.members: + if m.isAttr() and m.getExtendedAttribute("StoreInSlot"): + body += fill( + """ + + static_assert(${slot} < js::shadow::Object::MAX_FIXED_SLOTS, + "Not enough fixed slots to fit '${interface}.${member}'"); + if (!get_${member}(aCx, aWrapper, aObject, args)) { + return false; + } + // Getter handled setting our reserved slots + """, + slot=memberReservedSlot(m), + interface=self.descriptor.interface.identifier.name, + member=m.identifier.name) + + body += "\nreturn true;\n" + return indent(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 = ("ClearCached%sValue" % MakeNativeName(member.identifier.name)) + CGAbstractMethod.__init__(self, descriptor, name, returnType, args) + + def definition_body(self): + slotIndex = memberReservedSlot(self.member) + 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 obj(aCx);\n" + noopRetval = " true" + saveMember = ( + "JS::Rooted oldValue(aCx, js::GetReservedSlot(obj, %s));\n" % + slotIndex) + regetMember = fill( + """ + JS::Rooted temp(aCx); + JSJitGetterCallArgs args(&temp); + JSAutoCompartment ac(aCx, obj); + if (!get_${name}(aCx, obj, aObject, args)) { + js::SetReservedSlot(obj, ${slotIndex}, oldValue); + nsJSUtils::ReportPendingException(aCx); + return false; + } + return true; + """, + name=self.member.identifier.name, + slotIndex=slotIndex) + else: + declObj = "JSObject* obj;\n" + noopRetval = "" + saveMember = "" + regetMember = "" + + return indent(fill( + """ + $*{declObj} + obj = aObject->GetWrapper(); + if (!obj) { + return${noopRetval}; + } + $*{saveMember} + js::SetReservedSlot(obj, ${slotIndex}, JS::UndefinedValue()); + $*{regetMember} + """, + declObj=declObj, + noopRetval=noopRetval, + saveMember=saveMember, + slotIndex=slotIndex, + regetMember=regetMember)) + + +class CGIsPermittedMethod(CGAbstractMethod): + """ + crossOriginGetters/Setters/Methods are sets of names of the relevant members. + """ + def __init__(self, descriptor, crossOriginGetters, crossOriginSetters, + crossOriginMethods): + self.crossOriginGetters = crossOriginGetters + self.crossOriginSetters = crossOriginSetters + self.crossOriginMethods = crossOriginMethods + args = [Argument("JSFlatString*", "prop"), + Argument("jschar", "propFirstChar"), + Argument("bool", "set")] + CGAbstractMethod.__init__(self, descriptor, "IsPermitted", "bool", args, + inline=True) + + def definition_body(self): + allNames = self.crossOriginGetters | self.crossOriginSetters | self.crossOriginMethods + readwrite = self.crossOriginGetters & self.crossOriginSetters + readonly = (self.crossOriginGetters - self.crossOriginSetters) | self.crossOriginMethods + writeonly = self.crossOriginSetters - self.crossOriginGetters + cases = {} + for name in sorted(allNames): + cond = 'JS_FlatStringEqualsAscii(prop, "%s")' % name + if name in readonly: + cond = "!set && %s" % cond + elif name in writeonly: + cond = "set && %s" % cond + else: + assert name in readwrite + firstLetter = name[0] + case = cases.get(firstLetter, CGList([])) + case.append(CGGeneric("if (%s) {\n" + " return true;\n" + "}\n" % cond)) + cases[firstLetter] = case + caseList = [] + for firstLetter in sorted(cases.keys()): + caseList.append(CGCase("'%s'" % firstLetter, cases[firstLetter])) + switch = CGSwitch("propFirstChar", caseList) + return indent(switch.define() + "\nreturn false;\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 named by the "source" argument + based on the passed-in descriptor and storing it in a variable + called by the name in the "target" argument. + + codeOnFailure is the code to run if unwrapping fails. + + If isCallbackReturnValue is "JSImpl" and our descriptor is also + JS-implemented, fall back to just creating the right object if what we + have isn't one already. + + If allowCrossOriginObj is True, then we'll first do an + UncheckedUnwrap and then operate on the result. + """ + def __init__(self, descriptor, source, target, codeOnFailure, + exceptionCode=None, isCallbackReturnValue=False, + allowCrossOriginObj=False): + self.substitution = { + "type": descriptor.nativeType, + "protoID": "prototypes::id::" + descriptor.name, + "target": target, + "codeOnFailure": codeOnFailure, + } + if allowCrossOriginObj: + self.substitution["uncheckedObjDecl"] = ( + "JS::Rooted uncheckedObj(cx, js::UncheckedUnwrap(%s));\n" % source) + self.substitution["source"] = "uncheckedObj" + xpconnectUnwrap = dedent(""" + nsresult rv; + { // Scope for the JSAutoCompartment, because we only + // want to be in that compartment for the UnwrapArg call. + JSAutoCompartment ac(cx, ${source}); + rv = UnwrapArg<${type}>(cx, val, &objPtr, &objRef.ptr, &val); + } + """) + else: + self.substitution["uncheckedObjDecl"] = "" + self.substitution["source"] = source + xpconnectUnwrap = "nsresult rv = UnwrapArg<${type}>(cx, val, &objPtr, &objRef.ptr, &val);\n" + + if descriptor.hasXPConnectImpls: + # We don't use xpc_qsUnwrapThis because it will always throw on + # unwrap failure, whereas we want to control whether we throw or + # not. + self.substitution["codeOnFailure"] = string.Template( + "${type} *objPtr;\n" + "SelfRef objRef;\n" + "JS::Rooted val(cx, JS::ObjectValue(*${source}));\n" + + xpconnectUnwrap + + "if (NS_FAILED(rv)) {\n" + "${indentedCodeOnFailure}" + "}\n" + "// We should be castable!\n" + "MOZ_ASSERT(!objRef.ptr);\n" + "// We should have an object, too!\n" + "MOZ_ASSERT(objPtr);\n" + "${target} = objPtr;\n" + ).substitute(self.substitution, + indentedCodeOnFailure=indent(codeOnFailure)) + elif (isCallbackReturnValue == "JSImpl" and + descriptor.interface.isJSImplemented()): + exceptionCode = exceptionCode or codeOnFailure + self.substitution["codeOnFailure"] = fill( + """ + // Be careful to not wrap random DOM objects here, even if + // they're wrapped in opaque security wrappers for some reason. + // XXXbz Wish we could check for a JS-implemented object + // that already has a content reflection... + if (!IsDOMObject(js::UncheckedUnwrap(${source}))) { + nsCOMPtr ourWindow; + if (!GetWindowForJSImplementedObject(cx, Callback(), getter_AddRefs(ourWindow))) { + $*{exceptionCode} + } + JS::Rooted jsImplSourceObj(cx, ${source}); + ${target} = new ${type}(jsImplSourceObj, ourWindow); + } else { + $*{codeOnFailure} + } + """, + exceptionCode=exceptionCode, + **self.substitution) + else: + self.substitution["codeOnFailure"] = codeOnFailure + + def __str__(self): + substitution = self.substitution.copy() + substitution["codeOnFailure"] %= { + 'securityError': 'rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO' + } + return fill( + """ + { + $*{uncheckedObjDecl} + nsresult rv = UnwrapObject<${protoID}, ${type}>(${source}, ${target}); + if (NS_FAILED(rv)) { + $*{codeOnFailure} + } + } + """, + **substitution) + + +class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper): + """ + As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails + """ + def __init__(self, descriptor, source, target, exceptionCode, + isCallbackReturnValue, sourceDescription): + CastableObjectUnwrapper.__init__( + self, descriptor, source, target, + 'ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s", "%s");\n' + '%s' % (sourceDescription, descriptor.interface.identifier.name, + exceptionCode), + exceptionCode, + isCallbackReturnValue) + + +class CGCallbackTempRoot(CGGeneric): + def __init__(self, name): + define = dedent(""" + { // Scope for tempRoot + JS::Rooted tempRoot(cx, &${val}.toObject()); + ${declName} = new %s(tempRoot, mozilla::dom::GetIncumbentGlobal()); + } + """) % name + CGGeneric.__init__(self, define=define) + + +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 + ${mutableVal} is a mutable handle to the JS::Value in question + ${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. + + 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 (char16_t*, length) + pair that sets this string default value. This string is suitable for + passing as the second argument of handleDefault; in particular it does not + end with a ';' + """ + assert defaultValue.type.isDOMString() + return ("static const char16_t data[] = { %s };\n" + "%s(data, ArrayLength(data) - 1)" % + (", ".join(["'" + char + "'" for char in + defaultValue.value] + ["0"]), + method)) + + +# If this function is modified, modify CGNativeMember.getArg and +# CGNativeMember.getRetvalInfo accordingly. The latter cares about the decltype +# and holdertype we end up using, because it needs to be able to return the code +# that will convert those to the actual return value of the callback function. +def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, + isDefinitelyObject=False, + isMember=False, + isOptional=False, + invalidEnumValueFatal=True, + defaultValue=None, + treatNullAs="Default", + isEnforceRange=False, + isClamp=False, + isNullOrUndefined=False, + exceptionCode=None, + lenientFloatCode=None, + allowTreatNonCallableAsNull=False, + isCallbackReturnValue=False, + sourceDescription="value"): + """ + Get a template for converting a JS value to a native object based on the + given type and descriptor. If failureCode is given, then we're actually + testing whether we can convert the argument to the desired type. That + means that failures to convert due to the JS value being the wrong type of + value need to use failureCode instead of throwing exceptions. Failures to + convert that are due to JS exceptions (from toString or valueOf methods) or + out of memory conditions need to throw exceptions no matter what + failureCode is. However what actually happens when throwing an exception + can be controlled by exceptionCode. The only requirement on that is that + exceptionCode must end up doing a return, and every return from this + function must happen via exceptionCode if exceptionCode is not None. + + If isDefinitelyObject is True, that means we know the value + isObject() and we have no need to recheck that. + + if isMember is not False, we're being converted from a property of some JS + object, not from an actual method argument, so we can't rely on our jsval + being rooted or outliving us in any way. Callers can pass "Dictionary", + "Variadic", "Sequence", or "OwningUnion" to indicate that the conversion is + for something that is a dictionary member, a variadic argument, a sequence, + or an owning union respectively. + + If isOptional is true, then we are doing conversion of an optional + argument with no default value. + + invalidEnumValueFatal controls whether an invalid enum value conversion + attempt will throw (if true) or simply return without doing anything (if + false). + + If defaultValue is not None, it's the IDL default value for this conversion + + If isEnforceRange is true, we're converting an integer and throwing if the + value is out of range. + + If isClamp is true, we're converting an integer and clamping if the + value is out of range. + + If lenientFloatCode is not None, it should be used in cases when + we're a non-finite float that's not unrestricted. + + If allowTreatNonCallableAsNull is true, then [TreatNonCallableAsNull] and + [TreatNonObjectAsNull] extended attributes on nullable callback functions + will be honored. + + If isCallbackReturnValue is "JSImpl" or "Callback", then the declType may be + adjusted to make it easier to return from a callback. Since that type is + never directly observable by any consumers of the callback code, this is OK. + Furthermore, if isCallbackReturnValue is "JSImpl", that affects the behavior + of the FailureFatalCastableObjectUnwrapper conversion; this is used for + implementing auto-wrapping of JS-implemented return values from a + JS-implemented interface. + + sourceDescription is a description of what this JS value represents, to be + used in error reporting. Callers should assume that it might get placed in + the middle of a sentence. If it ends up at the beginning of a sentence, its + first character will be automatically uppercased. + + The return value from this function is a JSToNativeConversionInfo. + """ + # If we have a defaultValue then we're not actually optional for + # purposes of what we need to be declared as. + assert defaultValue is None or not isOptional + + # Also, we should not have a defaultValue if we know we're an object + assert not isDefinitelyObject or defaultValue is None + + # And we can't both be an object and be null or undefined + assert not isDefinitelyObject or not isNullOrUndefined + + # If exceptionCode is not set, we'll just rethrow the exception we got. + # Note that we can't just set failureCode to exceptionCode, because setting + # failureCode will prevent pending exceptions from being set in cases when + # they really should be! + if exceptionCode is None: + exceptionCode = "return false;\n" + # We often want exceptionCode to be indented, since it often appears in an + # if body. + exceptionCodeIndented = CGIndenter(CGGeneric(exceptionCode)) + + # Unfortunately, .capitalize() on a string will lowercase things inside the + # string, which we do not want. + def firstCap(string): + return string[0].upper() + string[1:] + + # Helper functions for dealing with failures due to the JS value being the + # wrong type of value + def onFailureNotAnObject(failureCode): + return CGGeneric( + failureCode or + ('ThrowErrorMessage(cx, MSG_NOT_OBJECT, "%s");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))) + + def onFailureBadType(failureCode, typeName): + return CGGeneric( + failureCode or + ('ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s", "%s");\n' + '%s' % (firstCap(sourceDescription), typeName, exceptionCode))) + + def onFailureNotCallable(failureCode): + return CGGeneric( + failureCode or + ('ThrowErrorMessage(cx, MSG_NOT_CALLABLE, "%s");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))) + + # A helper function for handling default values. Takes a template + # body and the C++ code to set the default value and wraps the + # given template body in handling for the default value. + def handleDefault(template, setDefault): + if defaultValue is None: + return template + return ( + "if (${haveValue}) {\n" + + indent(template) + + "} else {\n" + + indent(setDefault) + + "}\n") + + # A helper function for handling null default values. Much like + # handleDefault, but checks that the default value, if it exists, is null. + def handleDefaultNull(template, codeToSetNull): + if (defaultValue is not None and + not isinstance(defaultValue, IDLNullValue)): + raise TypeError("Can't handle non-null default value here") + return handleDefault(template, codeToSetNull) + + # 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 type.nullable(): + templateBody = handleDefaultNull(templateBody, codeToSetNull) + else: + assert defaultValue is None + + return templateBody + + # A helper function for converting things that look like a JSObject*. + def handleJSObjectType(type, isMember, failureCode): + if not isMember: + if isOptional: + # We have a specialization of Optional that will use a + # Rooted for the storage here. + declType = CGGeneric("JS::Handle") + else: + declType = CGGeneric("JS::Rooted") + declArgs = "cx" + else: + assert (isMember in + ("Sequence", "Variadic", "Dictionary", "OwningUnion", "MozMap")) + # We'll get traced by the sequence or dictionary or union tracer + declType = CGGeneric("JSObject*") + declArgs = None + templateBody = "${declName} = &${val}.toObject();\n" + setToNullCode = "${declName} = nullptr;\n" + template = wrapObjectTemplate(templateBody, type, setToNullCode, + failureCode) + return JSToNativeConversionInfo(template, declType=declType, + dealWithOptional=isOptional, + declArgs=declArgs) + + assert not (isEnforceRange and isClamp) # These are mutually exclusive + + if type.isArray(): + raise TypeError("Can't handle array arguments yet") + + if type.isSequence(): + assert not isEnforceRange and not isClamp + + if failureCode is None: + notSequence = ('ThrowErrorMessage(cx, MSG_NOT_SEQUENCE, "%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode)) + else: + notSequence = failureCode + + nullable = type.nullable() + # Be very careful not to change "type": we need it later + if nullable: + elementType = type.inner.inner + else: + elementType = type.inner + + # We want to use auto arrays if we can, but we have to be careful with + # reallocation behavior for arrays. In particular, if we use auto + # arrays for sequences and have a sequence of elements which are + # themselves sequences or have sequences as members, we have a problem. + # In that case, resizing the outermost nsAutoTarray to the right size + # will memmove its elements, but nsAutoTArrays 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 or Optional. + if isMember or isOptional or nullable or isCallbackReturnValue: + sequenceClass = "Sequence" + else: + sequenceClass = "binding_detail::AutoSequence" + + # XXXbz we can't include the index in the sourceDescription, because + # we don't really have a way to pass one in dynamically at runtime... + elementInfo = getJSToNativeConversionInfo( + elementType, descriptorProvider, isMember="Sequence", + exceptionCode=exceptionCode, lenientFloatCode=lenientFloatCode, + isCallbackReturnValue=isCallbackReturnValue, + sourceDescription="element of %s" % sourceDescription) + if elementInfo.dealWithOptional: + raise TypeError("Shouldn't have optional things in sequences") + if elementInfo.holderType is not None: + raise TypeError("Shouldn't need holders for sequences") + + typeName = CGTemplatedType(sequenceClass, elementInfo.declType) + sequenceType = typeName.define() + if nullable: + typeName = CGTemplatedType("Nullable", typeName) + arrayRef = "${declName}.SetValue()" + else: + arrayRef = "${declName}" + + elementConversion = string.Template(elementInfo.template).substitute({ + "val": "temp", + "mutableVal": "&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" + }) + + # NOTE: Keep this in sync with variadic conversions as needed + templateBody = fill( + """ + JS::ForOfIterator iter(cx); + if (!iter.init($${val}, JS::ForOfIterator::AllowNonIterable)) { + $*{exceptionCode} + } + if (!iter.valueIsIterable()) { + $*{notSequence} + } + ${sequenceType} &arr = ${arrayRef}; + JS::Rooted temp(cx); + while (true) { + bool done; + if (!iter.next(&temp, &done)) { + $*{exceptionCode} + } + if (done) { + break; + } + ${elementType}* slotPtr = arr.AppendElement(); + if (!slotPtr) { + JS_ReportOutOfMemory(cx); + $*{exceptionCode} + } + ${elementType}& slot = *slotPtr; + $*{elementConversion} + } + """, + exceptionCode=exceptionCode, + notSequence=notSequence, + sequenceType=sequenceType, + arrayRef=arrayRef, + elementType=elementInfo.declType.define(), + elementConversion=elementConversion) + + templateBody = wrapObjectTemplate(templateBody, type, + "${declName}.SetNull();\n", notSequence) + # Sequence arguments that might contain traceable things need + # to get traced + if not isMember and typeNeedsRooting(elementType): + holderType = CGTemplatedType("SequenceRooter", elementInfo.declType) + # If our sequence is nullable, this will set the Nullable to be + # not-null, but that's ok because we make an explicit SetNull() call + # on it as needed if our JS value is actually null. + holderArgs = "cx, &%s" % arrayRef + else: + holderType = None + holderArgs = None + + return JSToNativeConversionInfo(templateBody, declType=typeName, + holderType=holderType, + dealWithOptional=isOptional, + holderArgs=holderArgs) + + if type.isMozMap(): + assert not isEnforceRange and not isClamp + if failureCode is None: + notMozMap = ('ThrowErrorMessage(cx, MSG_NOT_OBJECT, "%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode)) + else: + notMozMap = failureCode + + nullable = type.nullable() + # Be very careful not to change "type": we need it later + if nullable: + valueType = type.inner.inner + else: + valueType = type.inner + + valueInfo = getJSToNativeConversionInfo( + valueType, descriptorProvider, isMember="MozMap", + exceptionCode=exceptionCode, lenientFloatCode=lenientFloatCode, + isCallbackReturnValue=isCallbackReturnValue, + sourceDescription="value in %s" % sourceDescription) + if valueInfo.dealWithOptional: + raise TypeError("Shouldn't have optional things in MozMap") + if valueInfo.holderType is not None: + raise TypeError("Shouldn't need holders for MozMap") + + typeName = CGTemplatedType("MozMap", valueInfo.declType) + mozMapType = typeName.define() + if nullable: + typeName = CGTemplatedType("Nullable", typeName) + mozMapRef = "${declName}.SetValue()" + else: + mozMapRef = "${declName}" + + valueConversion = string.Template(valueInfo.template).substitute({ + "val": "temp", + "mutableVal": "&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" + }) + + templateBody = fill( + """ + ${mozMapType} &mozMap = ${mozMapRef}; + + JS::Rooted mozMapObj(cx, &$${val}.toObject()); + JS::AutoIdArray ids(cx, JS_Enumerate(cx, mozMapObj)); + if (!ids) { + $*{exceptionCode} + } + JS::Rooted propNameValue(cx); + JS::Rooted temp(cx); + JS::Rooted curId(cx); + for (size_t i = 0; i < ids.length(); ++i) { + // Make sure we get the value before converting the name, since + // getting the value can trigger GC but our name is a dependent + // string. + curId = ids[i]; + binding_detail::FakeDependentString propName; + if (!JS_GetPropertyById(cx, mozMapObj, curId, &temp) || + !JS_IdToValue(cx, curId, &propNameValue) || + !ConvertJSValueToString(cx, propNameValue, &propNameValue, + eStringify, eStringify, propName)) { + $*{exceptionCode} + } + + ${valueType}* slotPtr = mozMap.AddEntry(propName); + if (!slotPtr) { + JS_ReportOutOfMemory(cx); + $*{exceptionCode} + } + ${valueType}& slot = *slotPtr; + $*{valueConversion} + } + """, + exceptionCode=exceptionCode, + mozMapType=mozMapType, + mozMapRef=mozMapRef, + valueType=valueInfo.declType.define(), + valueConversion=valueConversion) + + templateBody = wrapObjectTemplate(templateBody, type, + "${declName}.SetNull();\n", + notMozMap) + + declType = typeName + declArgs = None + holderType = None + holderArgs = None + # MozMap arguments that might contain traceable things need + # to get traced + if not isMember and isCallbackReturnValue: + # Go ahead and just convert directly into our actual return value + declType = CGWrapper(declType, post="&") + declArgs = "aRetVal" + elif not isMember and typeNeedsRooting(valueType): + holderType = CGTemplatedType("MozMapRooter", valueInfo.declType) + # If our MozMap is nullable, this will set the Nullable to be + # not-null, but that's ok because we make an explicit SetNull() call + # on it as needed if our JS value is actually null. + holderArgs = "cx, &%s" % mozMapRef + + return JSToNativeConversionInfo(templateBody, declType=declType, + declArgs=declArgs, + holderType=holderType, + dealWithOptional=isOptional, + holderArgs=holderArgs) + + if type.isUnion(): + nullable = type.nullable() + if nullable: + type = type.inner + + unionArgumentObj = "${declName}" if isMember else "${holderName}" + if nullable: + # If we're a member, we're a Nullable, which hasn't been told it has + # a value. Otherwise we're an already-constructed Maybe. + unionArgumentObj += ".SetValue()" if isMember else ".ref()" + + memberTypes = type.flatMemberTypes + names = [] + + interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes) + if len(interfaceMemberTypes) > 0: + interfaceObject = [] + for memberType in interfaceMemberTypes: + if type.isGeckoInterface(): + name = memberType.inner.identifier.name + else: + name = memberType.name + interfaceObject.append( + CGGeneric("(failed = !%s.TrySetTo%s(cx, ${val}, ${mutableVal}, tryNext)) || !tryNext" % + (unionArgumentObj, name))) + names.append(name) + interfaceObject = CGWrapper(CGList(interfaceObject, " ||\n"), + pre="done = ", post=";\n\n", reindent=True) + else: + interfaceObject = None + + arrayObjectMemberTypes = filter(lambda t: t.isArray() or t.isSequence(), memberTypes) + if len(arrayObjectMemberTypes) > 0: + raise TypeError("Bug 767924: We don't support sequences in unions yet") + else: + arrayObject = None + + dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes) + if len(dateObjectMemberTypes) > 0: + assert len(dateObjectMemberTypes) == 1 + memberType = dateObjectMemberTypes[0] + name = memberType.name + dateObject = CGGeneric("%s.SetTo%s(cx, ${val}, ${mutableVal});\n" + "done = true;\n" % (unionArgumentObj, name)) + dateObject = CGIfWrapper(dateObject, "JS_ObjectIsDate(cx, argObj)") + names.append(name) + else: + dateObject = None + + callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes) + if len(callbackMemberTypes) > 0: + assert len(callbackMemberTypes) == 1 + memberType = callbackMemberTypes[0] + name = memberType.name + callbackObject = CGGeneric( + "done = (failed = !%s.TrySetTo%s(cx, ${val}, ${mutableVal}, tryNext)) || !tryNext;\n" % + (unionArgumentObj, name)) + names.append(name) + else: + callbackObject = None + + dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes) + if len(dictionaryMemberTypes) > 0: + assert len(dictionaryMemberTypes) == 1 + name = dictionaryMemberTypes[0].inner.identifier.name + setDictionary = CGGeneric( + "done = (failed = !%s.TrySetTo%s(cx, ${val}, ${mutableVal}, tryNext)) || !tryNext;\n" % + (unionArgumentObj, name)) + names.append(name) + else: + setDictionary = None + + mozMapMemberTypes = filter(lambda t: t.isMozMap(), memberTypes) + if len(mozMapMemberTypes) > 0: + raise TypeError("We don't support MozMap in unions yet") + + objectMemberTypes = filter(lambda t: t.isObject(), memberTypes) + if len(objectMemberTypes) > 0: + assert len(objectMemberTypes) == 1 + # Very important to NOT construct a temporary Rooted here, since the + # SetToObject call can call a Rooted constructor and we need to keep + # stack discipline for Rooted. + object = CGGeneric("%s.SetToObject(cx, &${val}.toObject());\n" + "done = true;\n" % unionArgumentObj) + names.append(objectMemberTypes[0].name) + else: + object = None + + hasObjectTypes = interfaceObject or arrayObject or dateObject or callbackObject or object + if hasObjectTypes: + # "object" is not distinguishable from other types + assert not object or not (interfaceObject or arrayObject or dateObject or callbackObject) + if arrayObject or dateObject or callbackObject: + # An object can be both an array object and a callback or + # dictionary, but we shouldn't have both in the union's members + # because they are not distinguishable. + assert not (arrayObject and callbackObject) + templateBody = CGElseChain([arrayObject, dateObject, callbackObject]) + else: + templateBody = None + if interfaceObject: + assert not object + if templateBody: + templateBody = CGIfWrapper(templateBody, "!done") + templateBody = CGList([interfaceObject, templateBody]) + else: + templateBody = CGList([templateBody, object]) + + if dateObject: + templateBody.prepend(CGGeneric("JS::Rooted argObj(cx, &${val}.toObject());\n")) + 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): + if memberType.isEnum(): + name = memberType.inner.identifier.name + else: + name = memberType.name + return CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${mutableVal}, tryNext)) || !tryNext;\n" + "break;\n" % (unionArgumentObj, name)) + other = CGList([]) + stringConversion = map(getStringOrPrimitiveConversion, stringTypes) + numericConversion = map(getStringOrPrimitiveConversion, numericTypes) + booleanConversion = map(getStringOrPrimitiveConversion, booleanTypes) + if stringConversion: + if booleanConversion: + other.append(CGIfWrapper(booleanConversion[0], + "${val}.isBoolean()")) + if numericConversion: + other.append(CGIfWrapper(numericConversion[0], + "${val}.isNumber()")) + other.append(stringConversion[0]) + elif numericConversion: + if booleanConversion: + other.append(CGIfWrapper(booleanConversion[0], + "${val}.isBoolean()")) + other.append(numericConversion[0]) + else: + assert booleanConversion + other.append(booleanConversion[0]) + + other = CGWrapper(CGIndenter(other), pre="do {\n", post="} while (0);\n") + if hasObjectTypes or setDictionary: + other = CGWrapper(CGIndenter(other), "{\n", post="}\n") + if object: + templateBody = CGElseChain([templateBody, other]) + else: + other = CGWrapper(other, pre="if (!done) ") + templateBody = CGList([templateBody, other]) + else: + assert templateBody.define() == "" + templateBody = other + else: + other = None + + templateBody = CGWrapper(templateBody, pre="bool done = false, failed = false, tryNext;\n") + throw = CGGeneric(fill( + """ + if (failed) { + $*{exceptionCode} + } + if (!done) { + ThrowErrorMessage(cx, MSG_NOT_IN_UNION, "${desc}", "${names}"); + $*{exceptionCode} + } + """, + exceptionCode=exceptionCode, + desc=firstCap(sourceDescription), + names=", ".join(names))) + + templateBody = CGWrapper(CGIndenter(CGList([templateBody, throw])), pre="{\n", post="}\n") + + typeName = CGUnionStruct.unionTypeDecl(type, isMember) + argumentTypeName = typeName + "Argument" + if nullable: + typeName = "Nullable<" + typeName + " >" + + def handleNull(templateBody, setToNullVar, extraConditionForNull=""): + nullTest = "%s${val}.isNullOrUndefined()" % extraConditionForNull + return CGIfElseWrapper(nullTest, + CGGeneric("%s.SetNull();\n" % setToNullVar), + templateBody) + + if type.hasNullableType: + assert not nullable + # Make sure to handle a null default value here + if defaultValue and isinstance(defaultValue, IDLNullValue): + assert defaultValue.type == type + extraConditionForNull = "!(${haveValue}) || " + else: + extraConditionForNull = "" + templateBody = handleNull(templateBody, unionArgumentObj, + extraConditionForNull=extraConditionForNull) + + declType = CGGeneric(typeName) + if isMember: + 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 + # isMember we do it all by hand because we do not want our holder + # constructed if we're null. But if we're isMember 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 isMember: + holderArgs = "${declName}.Value().SetValue()" + declType = CGTemplatedType("Optional", declType) + constructDecl = CGGeneric("${declName}.Construct();\n") + declLoc = "${declName}.Value()" + else: + holderArgs = "${declName}.SetValue()" + if holderType is not None: + constructHolder = CGGeneric("${holderName}.construct(%s);\n" % holderArgs) + else: + constructHolder = None + # Don't need to pass those args when the holder is being constructed + holderArgs = None + else: + holderArgs = "${declName}" + constructHolder = None + + if defaultValue and not isinstance(defaultValue, IDLNullValue): + tag = defaultValue.type.tag() + + if tag in numericSuffixes or tag is IDLType.Tags.bool: + defaultStr = getHandleDefault(defaultValue) + value = declLoc + (".Value()" if nullable else "") + default = CGGeneric("%s.RawSetAs%s() = %s;\n" % + (value, defaultValue.type, defaultStr)) + else: + default = CGGeneric( + handleDefaultStringValue( + defaultValue, "%s.SetStringData" % unionArgumentObj) + + ";\n") + + templateBody = CGIfElseWrapper("!(${haveValue})", default, templateBody) + + templateBody = CGList([constructHolder, templateBody]) + + if nullable: + if defaultValue: + if isinstance(defaultValue, IDLNullValue): + extraConditionForNull = "!(${haveValue}) || " + else: + extraConditionForNull = "${haveValue} && " + else: + extraConditionForNull = "" + templateBody = handleNull(templateBody, declLoc, + extraConditionForNull=extraConditionForNull) + elif (not type.hasNullableType and defaultValue and + isinstance(defaultValue, IDLNullValue)): + assert type.hasDictionaryType + assert defaultValue.type.isDictionary() + if not isMember and typeNeedsRooting(defaultValue.type): + ctorArgs = "cx" + else: + ctorArgs = "" + initDictionaryWithNull = CGIfWrapper( + CGGeneric("return false;\n"), + ('!%s.RawSetAs%s(%s).Init(cx, JS::NullHandleValue, "Member of %s")' + % (declLoc, getUnionMemberName(defaultValue.type), + ctorArgs, type))) + templateBody = CGIfElseWrapper("!(${haveValue})", + initDictionaryWithNull, + templateBody) + + templateBody = CGList([constructDecl, templateBody]) + + return JSToNativeConversionInfo(templateBody.define(), + declType=declType, + holderType=holderType, + holderArgs=holderArgs, + dealWithOptional=isOptional and (not nullable or isMember)) + + if type.isGeckoInterface(): + assert not isEnforceRange and not isClamp + + descriptor = descriptorProvider.getDescriptor( + type.unroll().inner.identifier.name) + + if descriptor.nativeType == 'JSObject': + # XXXbz Workers code does this sometimes + assert descriptor.workers + return handleJSObjectType(type, isMember, failureCode) + + if descriptor.interface.isCallback(): + name = descriptor.interface.identifier.name + if type.nullable() or isCallbackReturnValue: + declType = CGGeneric("nsRefPtr<%s>" % name) + else: + declType = CGGeneric("OwningNonNull<%s>" % name) + # BOGUS extra blank line here turns out to be at the end of a block: + conversion = indent(CGCallbackTempRoot(name).define()) + "\n" + + template = wrapObjectTemplate(conversion, type, + "${declName} = nullptr;\n", + failureCode) + return JSToNativeConversionInfo(template, 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 nsRefPtr or raw pointer for callback return values to make + # them easier to return. + argIsPointer = (type.nullable() or type.unroll().inner.isExternal() or + isCallbackReturnValue) + + # Sequences and non-worker callbacks have to hold a strong ref to the + # thing being passed down. Union return values must hold a strong ref + # because they may be returning an addrefed pointer. + # 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. + forceOwningType = ((descriptor.interface.isCallback() and + not descriptor.workers) or + isMember or + isCallbackReturnValue) + + if forceOwningType and descriptor.nativeOwnership == 'owned': + raise TypeError("Interface %s has 'owned' nativeOwnership, so we " + "don't know how to keep it alive in %s" % + (descriptor.interface.identifier.name, + sourceDescription)) + + 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 = "nsRefPtr<" + typeName + ">" + else: + declType = typePtr + else: + if forceOwningType: + declType = "OwningNonNull<" + typeName + ">" + else: + declType = "NonNull<" + typeName + ">" + + templateBody = "" + if not descriptor.skipGen and not descriptor.interface.isConsequential() and not descriptor.interface.isExternal(): + if failureCode is not None: + templateBody += str(CastableObjectUnwrapper( + descriptor, + "&${val}.toObject()", + "${declName}", + failureCode)) + else: + templateBody += str(FailureFatalCastableObjectUnwrapper( + descriptor, + "&${val}.toObject()", + "${declName}", + exceptionCode, + isCallbackReturnValue, + firstCap(sourceDescription))) + elif descriptor.workers: + return handleJSObjectType(type, isMember, failureCode) + else: + # Either external, or new-binding non-castable. We always have a + # holder for these, because we don't actually know whether we have + # to addref when unwrapping or not. So we just pass an + # getter_AddRefs(nsRefPtr) 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 += "nsRefPtr<" + typeName + "> ${holderName};\n" + else: + holderType = "nsRefPtr<" + typeName + ">" + templateBody += ( + "JS::Rooted tmpVal(cx, ${val});\n" + + typePtr + " tmp;\n" + "if (NS_FAILED(UnwrapArg<" + typeName + ">(cx, ${val}, &tmp, static_cast<" + typeName + "**>(getter_AddRefs(${holderName})), &tmpVal))) {\n") + templateBody += CGIndenter(onFailureBadType(failureCode, + descriptor.interface.identifier.name)).define() + templateBody += ("}\n" + "MOZ_ASSERT(tmp);\n") + + if not isDefinitelyObject and not forceOwningType: + # Our tmpVal will go out of scope, so we can't rely on it + # for rooting + templateBody += dedent(""" + if (tmpVal != ${val} && !${holderName}) { + // We have to have a strong ref, because we got this off + // some random object that might get GCed + ${holderName} = tmp; + } + """) + + # And store our tmp, before it goes out of scope. + templateBody += "${declName} = tmp;\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.name + arrayType = CGGeneric(name) + declType = arrayType + if type.nullable(): + declType = CGTemplatedType("Nullable", declType) + objRef = "${declName}.SetValue()" + else: + objRef = "${declName}" + + # Again, this is a bit strange since we are actually building a + # template string here. ${objRef} and $*{badType} below are filled in + # right now; $${val} expands to ${val}, to be filled in later. + template = fill( + """ + if (!${objRef}.Init(&$${val}.toObject())) { + $*{badType} + } + + """, # BOGUS extra blank line + objRef=objRef, + badType=onFailureBadType(failureCode, type.name).define()) + template = wrapObjectTemplate(template, type, "${declName}.SetNull();\n", + failureCode) + if not isMember: + # This is a bit annoying. In a union we don't want to have a + # holder, since unions don't support that. But if we're optional we + # want to have a holder, so that the callee doesn't see + # Optional >. So do a holder if we're + # optional and use a RootedTypedArray otherwise. + if isOptional: + holderType = CGTemplatedType("TypedArrayRooter", arrayType) + # If our typed array is nullable, this will set the Nullable to + # be not-null, but that's ok because we make an explicit + # SetNull() call on it as needed if our JS value is actually + # null. XXXbz Because "Maybe" takes const refs for constructor + # arguments, we can't pass a reference here; have to pass a + # pointer. + holderArgs = "cx, &%s" % objRef + declArgs = None + else: + holderType = None + holderArgs = None + declType = CGTemplatedType("RootedTypedArray", declType) + declArgs = "cx" + else: + holderType = None + holderArgs = None + declArgs = None + return JSToNativeConversionInfo(template, + declType=declType, + holderType=holderType, + dealWithOptional=isOptional, + declArgs=declArgs, + holderArgs=holderArgs) + + if type.isDOMString(): + assert not isEnforceRange and not isClamp + + treatAs = { + "Default": "eStringify", + "EmptyString": "eEmpty", + "Null": "eNull", + } + if type.nullable(): + # For nullable strings null becomes a null string. + treatNullAs = "Null" + # For nullable strings undefined also becomes a null string. + undefinedBehavior = "eNull" + else: + undefinedBehavior = "eStringify" + nullBehavior = treatAs[treatNullAs] + + def getConversionCode(varName): + conversionCode = ( + "if (!ConvertJSValueToString(cx, ${val}, ${mutableVal}, %s, %s, %s)) {\n" + "%s" + "}\n" % (nullBehavior, undefinedBehavior, varName, + exceptionCodeIndented.define())) + if defaultValue is None: + return conversionCode + + if isinstance(defaultValue, IDLNullValue): + assert(type.nullable()) + defaultCode = "%s.SetNull()" % varName + else: + defaultCode = handleDefaultStringValue(defaultValue, + "%s.SetData" % varName) + return handleDefault(conversionCode, defaultCode + ";\n") + + if isMember: + # We have to make a copy, except in the variadic case, because our + # jsval may well not live as long as our string needs to. + declType = CGGeneric("nsString") + if isMember == "Variadic": + # The string is kept alive by the argument, so we can just + # depend on it. + assignString = "${declName}.Rebind(str.Data(), str.Length());\n" + else: + assignString = "${declName} = str;\n" + return JSToNativeConversionInfo( + fill( + """ + { + binding_detail::FakeDependentString str; + $*{convert} + $*{assign} + } + + """, # BOGUS extra newline + convert=getConversionCode("str"), + assign=assignString), + declType=declType, + dealWithOptional=isOptional) + + if isOptional: + declType = "Optional" + holderType = CGGeneric("binding_detail::FakeDependentString") + conversionCode = ("%s" + "${declName} = &${holderName};\n" % + getConversionCode("${holderName}")) + else: + declType = "binding_detail::FakeDependentString" + holderType = None + conversionCode = getConversionCode("${declName}") + + # No need to deal with optional here; we handled it already + return JSToNativeConversionInfo( + conversionCode, + declType=CGGeneric(declType), + holderType=holderType) + + if type.isByteString(): + assert not isEnforceRange and not isClamp + + nullable = toStringBool(type.nullable()) + + conversionCode = ( + "if (!ConvertJSValueToByteString(cx, ${val}, ${mutableVal}, %s, ${declName})) {\n" + "%s" + "}\n" % (nullable, exceptionCodeIndented.define())) + # ByteString arguments cannot have a default value. + assert defaultValue is None + + return JSToNativeConversionInfo( + conversionCode, + declType=CGGeneric("nsCString"), + dealWithOptional=isOptional) + + if type.isEnum(): + assert not isEnforceRange and not isClamp + + enumName = type.unroll().inner.identifier.name + declType = CGGeneric(enumName) + if type.nullable(): + declType = CGTemplatedType("Nullable", declType) + declType = declType.define() + enumLoc = "${declName}.SetValue()" + else: + enumLoc = "${declName}" + declType = declType.define() + + if invalidEnumValueFatal: + handleInvalidEnumValueCode = "MOZ_ASSERT(index >= 0);\n" + else: + # invalidEnumValueFatal is false only for attributes. So we won't + # have a non-default exceptionCode here unless attribute "arg + # conversion" code starts passing in an exceptionCode. At which + # point we'll need to figure out what that even means. + assert exceptionCode == "return false;\n" + handleInvalidEnumValueCode = dedent(""" + if (index < 0) { + return true; + } + """) + + template = fill( + """ + { + bool ok; + int index = FindEnumStringIndex<${invalidEnumValueFatal}>(cx, $${val}, ${values}, "${enumtype}", "${sourceDescription}", &ok); + if (!ok) { + $*{exceptionCode} + } + $*{handleInvalidEnumValueCode} + ${enumLoc} = static_cast<${enumtype}>(index); + } + """, + enumtype=enumName, + values=enumName + "Values::" + ENUM_ENTRY_VARIABLE_NAME, + invalidEnumValueFatal=toStringBool(invalidEnumValueFatal), + handleInvalidEnumValueCode=handleInvalidEnumValueCode, + exceptionCode=exceptionCode, + enumLoc=enumLoc, + sourceDescription=firstCap(sourceDescription)) + + setNull = "${declName}.SetNull();\n" + + if type.nullable(): + template = CGIfElseWrapper("${val}.isNullOrUndefined()", + CGGeneric(setNull), + CGGeneric(template)).define() + + if defaultValue is not None: + if isinstance(defaultValue, IDLNullValue): + assert type.nullable() + template = handleDefault(template, setNull) + else: + assert(defaultValue.type.tag() == IDLType.Tags.domstring) + template = handleDefault(template, + ("%s = %s::%s;\n" % + (enumLoc, enumName, + getEnumValueName(defaultValue.value)))) + return JSToNativeConversionInfo(template, declType=CGGeneric(declType), + dealWithOptional=isOptional) + + if type.isCallback(): + assert not isEnforceRange and not isClamp + assert not type.treatNonCallableAsNull() or type.nullable() + assert not type.treatNonObjectAsNull() or type.nullable() + assert not type.treatNonObjectAsNull() or not type.treatNonCallableAsNull() + + name = type.unroll().identifier.name + if type.nullable(): + declType = CGGeneric("nsRefPtr<%s>" % name) + else: + declType = CGGeneric("OwningNonNull<%s>" % name) + conversion = indent(CGCallbackTempRoot(name).define()) + + if allowTreatNonCallableAsNull and type.treatNonCallableAsNull(): + haveCallable = "JS_ObjectIsCallable(cx, &${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 + "\n"), # BOGUS extra blank line + CGGeneric("${declName} = nullptr;\n")).define() + else: + template = conversion + else: + template = wrapObjectTemplate( + "if (JS_ObjectIsCallable(cx, &${val}.toObject())) {\n" + + conversion + + "} else {\n" + + indent(onFailureNotCallable(failureCode).define()) + + "}\n", + type, + "${declName} = nullptr;\n", + failureCode) + return JSToNativeConversionInfo(template, declType=declType, + dealWithOptional=isOptional) + + if type.isAny(): + assert not isEnforceRange and not isClamp + + declArgs = None + if isMember in ("Variadic", "Sequence", "Dictionary", "MozMap"): + # Rooting is handled by the sequence and dictionary tracers. + declType = "JS::Value" + else: + assert not isMember + declType = "JS::Rooted" + declArgs = "cx" + + assert not isOptional + templateBody = "${declName} = ${val};\n" + # We may not have a default value if we're being converted for + # a setter, say. + if defaultValue: + if isinstance(defaultValue, IDLNullValue): + defaultHandling = "${declName} = JS::NullValue();\n" + else: + assert isinstance(defaultValue, IDLUndefinedValue) + defaultHandling = "${declName} = JS::UndefinedValue();\n" + templateBody = handleDefault(templateBody, defaultHandling) + return JSToNativeConversionInfo(templateBody, + declType=CGGeneric(declType), + declArgs=declArgs) + + if type.isObject(): + assert not isEnforceRange and not isClamp + return handleJSObjectType(type, isMember, failureCode) + + if type.isDictionary(): + # There are no nullable dictionaries + assert not type.nullable() or isCallbackReturnValue + # All optional dictionaries always have default values, so we + # should be able to assume not isOptional here. + assert not isOptional + # In the callback return value case we never have to worry + # about a default value; we always have a value. + assert not isCallbackReturnValue or defaultValue is None + + typeName = CGDictionary.makeDictionaryName(type.unroll().inner) + if not isMember and not isCallbackReturnValue: + # Since we're not a member and not nullable or optional, no one will + # see our real type, so we can do the fast version of the dictionary + # that doesn't pre-initialize members. + typeName = "binding_detail::Fast" + typeName + + declType = CGGeneric(typeName) + + # We do manual default value handling here, because we + # actually do want a jsval, and we only handle null anyway + # NOTE: if isNullOrUndefined or isDefinitelyObject are true, + # we know we have a value, so we don't have to worry about the + # default value. + if (not isNullOrUndefined and not isDefinitelyObject and + defaultValue is not None): + assert(isinstance(defaultValue, IDLNullValue)) + val = "(${haveValue}) ? ${val} : JS::NullHandleValue" + else: + val = "${val}" + + if failureCode is not None: + if isDefinitelyObject: + dictionaryTest = "IsObjectValueConvertibleToDictionary" + else: + dictionaryTest = "IsConvertibleToDictionary" + # Check that the value we have can in fact be converted to + # a dictionary, and return failureCode if not. + template = CGIfWrapper( + CGGeneric(failureCode), + "!%s(cx, ${val})" % dictionaryTest).define() + "\n" + else: + template = "" + + dictLoc = "${declName}" + if type.nullable(): + dictLoc += ".SetValue()" + + template += ('if (!%s.Init(cx, %s, "%s")) {\n' + "%s" + "}\n" % (dictLoc, val, firstCap(sourceDescription), + exceptionCodeIndented.define())) + + if type.nullable(): + declType = CGTemplatedType("Nullable", declType) + template = CGIfElseWrapper("${val}.isNullOrUndefined()", + CGGeneric("${declName}.SetNull();\n"), + CGGeneric(template)).define() + + # Dictionary arguments that might contain traceable things need to get + # traced + if not isMember and isCallbackReturnValue: + # Go ahead and just convert directly into our actual return value + declType = CGWrapper(declType, post="&") + declArgs = "aRetVal" + elif not isMember and typeNeedsRooting(type): + declType = CGTemplatedType("RootedDictionary", declType) + declArgs = "cx" + else: + declArgs = None + + return JSToNativeConversionInfo(template, declType=declType, + declArgs=declArgs) + + if type.isVoid(): + assert not isOptional + # This one only happens for return values, and its easy: Just + # ignore the jsval. + return JSToNativeConversionInfo("") + + if type.isDate(): + assert not isEnforceRange and not isClamp + + declType = CGGeneric("Date") + if type.nullable(): + declType = CGTemplatedType("Nullable", declType) + dateVal = "${declName}.SetValue()" + else: + dateVal = "${declName}" + + if failureCode is None: + notDate = ('ThrowErrorMessage(cx, MSG_NOT_DATE, "%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode)) + else: + notDate = failureCode + + conversion = fill( + """ + JS::Rooted possibleDateObject(cx, &$${val}.toObject()); + if (!JS_ObjectIsDate(cx, possibleDateObject) || + !${dateVal}.SetTimeStamp(cx, possibleDateObject)) { + $*{notDate} + } + """, + dateVal=dateVal, + notDate=notDate) + + conversion = wrapObjectTemplate(conversion, type, + "${declName}.SetNull();\n", notDate) + return JSToNativeConversionInfo(conversion, + declType=declType, + dealWithOptional=isOptional) + + if not type.isPrimitive(): + raise TypeError("Need conversion for argument type '%s'" % str(type)) + + typeName = builtinNames[type.tag()] + + conversionBehavior = "eDefault" + if isEnforceRange: + assert type.isInteger() + conversionBehavior = "eEnforceRange" + elif isClamp: + assert type.isInteger() + conversionBehavior = "eClamp" + + if type.nullable(): + declType = CGGeneric("Nullable<" + typeName + ">") + writeLoc = "${declName}.SetValue()" + readLoc = "${declName}.Value()" + nullCondition = "${val}.isNullOrUndefined()" + if defaultValue is not None and isinstance(defaultValue, IDLNullValue): + nullCondition = "!(${haveValue}) || " + nullCondition + template = ( + "if (%s) {\n" + " ${declName}.SetNull();\n" + "} else if (!ValueToPrimitive<%s, %s>(cx, ${val}, &%s)) {\n" + "%s" + "}\n" % (nullCondition, typeName, conversionBehavior, + writeLoc, exceptionCodeIndented.define())) + else: + assert(defaultValue is None or + not isinstance(defaultValue, IDLNullValue)) + writeLoc = "${declName}" + readLoc = writeLoc + template = ( + "if (!ValueToPrimitive<%s, %s>(cx, ${val}, &%s)) {\n" + "%s" + "}\n" % (typeName, conversionBehavior, writeLoc, + exceptionCodeIndented.define())) + declType = CGGeneric(typeName) + + if type.isFloat() and not type.isUnrestricted(): + if lenientFloatCode is not None: + nonFiniteCode = lenientFloatCode + else: + nonFiniteCode = ('ThrowErrorMessage(cx, MSG_NOT_FINITE, "%s");\n' + "%s" % (firstCap(sourceDescription), exceptionCode)) + template = template.rstrip() + template += fill( + """ + else if (!mozilla::IsFinite(${readLoc})) { + // Note: mozilla::IsFinite will do the right thing + // when passed a non-finite float too. + $*{nonFiniteCode} + } + """, + readLoc=readLoc, + nonFiniteCode=nonFiniteCode) + + if (defaultValue is not None and + # We already handled IDLNullValue, so just deal with the other ones + not isinstance(defaultValue, IDLNullValue)): + tag = defaultValue.type.tag() + defaultStr = getHandleDefault(defaultValue) + template = CGIfElseWrapper( + "${haveValue}", + CGGeneric(template), + CGGeneric("%s = %s;\n" % (writeLoc, defaultStr))).define() + + return JSToNativeConversionInfo(template, declType=declType, + dealWithOptional=isOptional) + + +def instantiateJSToNativeConversion(info, replacements, checkForValue=False): + """ + Take a JSToNativeConversionInfo as returned by getJSToNativeConversionInfo + and a set of replacements as required by the strings in such an object, and + generate code to convert into stack C++ types. + + If checkForValue is True, then the conversion will get wrapped in + a check for ${haveValue}. + """ + templateBody, declType, holderType, dealWithOptional = ( + info.template, info.declType, info.holderType, info.dealWithOptional) + + if dealWithOptional and not checkForValue: + raise TypeError("Have to deal with optional things, but don't know how") + if checkForValue and declType is None: + raise TypeError("Need to predeclare optional things, so they will be " + "outside the check for big enough arg count!") + + # We can't precompute our holder constructor arguments, since + # those might depend on ${declName}, which we change below. Just + # compute arguments at the point when we need them as we go. + def getArgsCGThing(args): + return CGGeneric(string.Template(args).substitute(replacements)) + + result = CGList([]) + # Make a copy of "replacements" since we may be about to start modifying it + replacements = dict(replacements) + originalDeclName = replacements["declName"] + if declType is not None: + if dealWithOptional: + replacements["declName"] = "%s.Value()" % originalDeclName + declType = CGTemplatedType("Optional", declType) + declCtorArgs = None + elif info.declArgs is not None: + declCtorArgs = CGWrapper(getArgsCGThing(info.declArgs), + pre="(", post=")") + else: + declCtorArgs = None + result.append( + CGList([declType, CGGeneric(" "), + CGGeneric(originalDeclName), + declCtorArgs, CGGeneric(";\n")])) + + originalHolderName = replacements["holderName"] + if holderType is not None: + if dealWithOptional: + replacements["holderName"] = "%s.ref()" % originalHolderName + holderType = CGTemplatedType("Maybe", holderType) + holderCtorArgs = None + elif info.holderArgs is not None: + holderCtorArgs = CGWrapper(getArgsCGThing(info.holderArgs), + pre="(", post=")") + else: + holderCtorArgs = None + result.append( + CGList([holderType, CGGeneric(" "), + CGGeneric(originalHolderName), + holderCtorArgs, CGGeneric(";\n")])) + + 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.construct(%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 "INT_TO_JSVAL(%s)" % (value.value) + if tag == IDLType.Tags.uint32: + return "UINT_TO_JSVAL(%sU)" % (value.value) + if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]: + return "DOUBLE_TO_JSVAL(%s)" % numericValue(tag, value.value) + if tag == IDLType.Tags.bool: + return "JSVAL_TRUE" if value.value else "JSVAL_FALSE" + if tag in [IDLType.Tags.float, IDLType.Tags.double]: + return "DOUBLE_TO_JSVAL(%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, + 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" + } + self.replacementVariables["val"] = string.Template( + "args[${index}]").substitute(replacer) + self.replacementVariables["mutableVal"] = self.replacementVariables["val"] + haveValueCheck = string.Template( + "args.hasDefined(${index})").substitute(replacer) + self.replacementVariables["haveValue"] = haveValueCheck + self.descriptorProvider = descriptorProvider + if self.argument.optional and not self.argument.defaultValue: + self.argcAndIndex = replacer + else: + self.argcAndIndex = None + self.invalidEnumValueFatal = invalidEnumValueFatal + self.lenientFloatCode = lenientFloatCode + + def define(self): + typeConversion = getJSToNativeConversionInfo( + self.argument.type, + self.descriptorProvider, + isOptional=(self.argcAndIndex is not None and + not self.argument.variadic), + invalidEnumValueFatal=self.invalidEnumValueFatal, + defaultValue=self.argument.defaultValue, + treatNullAs=self.argument.treatNullAs, + isEnforceRange=self.argument.enforceRange, + isClamp=self.argument.clamp, + lenientFloatCode=self.lenientFloatCode, + isMember="Variadic" if self.argument.variadic else False, + allowTreatNonCallableAsNull=self.argument.allowTreatNonCallableAsNull(), + sourceDescription=self.argDescription) + + if not self.argument.variadic: + return instantiateJSToNativeConversion( + typeConversion, + self.replacementVariables, + self.argcAndIndex is not None).define() + + # Variadic arguments get turned into a sequence. + if typeConversion.dealWithOptional: + raise TypeError("Shouldn't have optional things in variadics") + if typeConversion.holderType is not None: + raise TypeError("Shouldn't need holders for variadics") + + replacer = dict(self.argcAndIndex, **self.replacementVariables) + replacer["seqType"] = CGTemplatedType("binding_detail::AutoSequence", + typeConversion.declType).define() + if typeNeedsRooting(self.argument.type): + rooterDecl = ("SequenceRooter<%s> ${holderName}(cx, &${declName});\n" % + typeConversion.declType.define()) + else: + rooterDecl = "" + replacer["elemType"] = typeConversion.declType.define() + + # NOTE: Keep this in sync with sequence conversions as needed + variadicConversion = string.Template( + "${seqType} ${declName};\n" + + rooterDecl + + dedent(""" + if (${argc} > ${index}) { + if (!${declName}.SetCapacity(${argc} - ${index})) { + JS_ReportOutOfMemory(cx); + return false; + } + for (uint32_t variadicArg = ${index}; variadicArg < ${argc}; ++variadicArg) { + ${elemType}& slot = *${declName}.AppendElement(); + """) + ).substitute(replacer) + + val = string.Template("args[variadicArg]").substitute(replacer) + variadicConversion += indent( + string.Template(typeConversion.template).substitute({ + "val": val, + "mutableVal": 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"] + }), 4) + + variadicConversion += (" }\n" + "}\n") + return variadicConversion + + +def getMaybeWrapValueFuncForType(type): + # Callbacks might actually be DOM objects; nothing prevents a page from + # doing that. + if type.isCallback() or type.isCallbackInterface() or type.isObject(): + if type.nullable(): + return "MaybeWrapObjectOrNullValue" + return "MaybeWrapObjectValue" + # Spidermonkey interfaces are never DOM objects. Neither are sequences or + # dictionaries, since those are always plain JS objects. + if type.isSpiderMonkeyInterface() or type.isDictionary() or type.isSequence(): + if type.nullable(): + return "MaybeWrapNonDOMObjectOrNullValue" + return "MaybeWrapNonDOMObjectValue" + if type.isAny(): + return "MaybeWrapValue" + + # For other types, just go ahead an fall back on MaybeWrapValue for now: + # it's always safe to do, and shouldn't be particularly slow for any of + # them + return "MaybeWrapValue" + + +sequenceWrapLevel = 0 +mozMapWrapLevel = 0 + + +def getWrapTemplateForType(type, descriptorProvider, result, successCode, + returnsNewObject, exceptionCode, typedArraysAreStructs): + """ + Reflect a C++ value stored in "result", of IDL type "type" into JS. The + "successCode" is the code to run once we have successfully done the + conversion and must guarantee that execution of the conversion template + stops once the successCode has executed (e.g. by doing a 'return', or by + doing a 'break' if the entire conversion template is inside a block that + the 'break' will exit). + + If typedArraysAreStructs is true, then if the type is a typed array, + "result" is one of the dom::TypedArray subclasses, not a JSObject*. + + The resulting string should be used with string.Template. It + needs the following keys when substituting: + + jsvalHandle: something that can be passed to methods taking a + JS::MutableHandle. This can be a + JS::MutableHandle or a JS::Rooted*. + 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 or a + JS::Rooted. + obj: a JS::Handle. + + Returns (templateString, infallibility of conversion template) + """ + if successCode is None: + successCode = "return true;\n" + + def setUndefined(): + return _setValue("", setter="setUndefined") + + def setNull(): + return _setValue("", setter="setNull") + + def setInt32(value): + return _setValue(value, setter="setInt32") + + def setString(value): + return _setValue(value, setter="setString") + + def setObject(value, wrapAsType=None): + return _setValue(value, wrapAsType=wrapAsType, setter="setObject") + + def setObjectOrNull(value, wrapAsType=None): + return _setValue(value, wrapAsType=wrapAsType, setter="setObjectOrNull") + + def setUint32(value): + return _setValue(value, setter="setNumber") + + def setDouble(value): + return _setValue("JS_NumberValue(%s)" % value) + + def setBoolean(value): + return _setValue(value, setter="setBoolean") + + def _setValue(value, wrapAsType=None, setter="set"): + """ + Returns the code to set the jsval to value. + + If wrapAsType is not None, then will wrap the resulting value using the + function that getMaybeWrapValueFuncForType(wrapAsType) returns. + Otherwise, no wrapping will be done. + """ + if wrapAsType is None: + tail = successCode + else: + tail = fill( + """ + if (!${maybeWrap}(cx, $${jsvalHandle})) { + $*{exceptionCode} + } + $*{successCode} + """, + maybeWrap=getMaybeWrapValueFuncForType(wrapAsType), + exceptionCode=exceptionCode, + successCode=successCode) + return ("${jsvalRef}.%s(%s);\n" % (setter, value)) + tail + + def wrapAndSetPtr(wrapCall, failureCode=None): + """ + Returns the code to set the jsval by calling "wrapCall". "failureCode" + is the code to run if calling "wrapCall" fails + """ + if failureCode is None: + failureCode = exceptionCode + return fill( + """ + if (!${wrapCall}) { + $*{failureCode} + } + $*{successCode} + """, + wrapCall=wrapCall, + failureCode=failureCode, + successCode=successCode) + + if type is None or type.isVoid(): + return (setUndefined(), True) + + if type.isArray(): + raise TypeError("Can't handle array return values yet") + + if (type.isSequence() or type.isMozMap()) and type.nullable(): + # These are both wrapped in Nullable<> + recTemplate, recInfall = getWrapTemplateForType(type.inner, descriptorProvider, + "%s.Value()" % result, successCode, + returnsNewObject, exceptionCode, + typedArraysAreStructs) + code = fill( + """ + + if (${result}.IsNull()) { + $*{setNull} + } + $*{recTemplate} + """, + result=result, + setNull=setNull(), + recTemplate=recTemplate) + return code, recInfall + + if type.isSequence(): + # Now do non-nullable sequences. Our success code is just to break to + # where we set the element in the array. Note that we bump the + # sequenceWrapLevel around this call so that nested sequence conversions + # will use different iteration variables. + global sequenceWrapLevel + index = "sequenceIdx%d" % sequenceWrapLevel + sequenceWrapLevel += 1 + innerTemplate = wrapForType( + type.inner, descriptorProvider, + { + 'result': "%s[%s]" % (result, index), + 'successCode': "break;\n", + 'jsvalRef': "tmp", + 'jsvalHandle': "&tmp", + 'returnsNewObject': returnsNewObject, + 'exceptionCode': exceptionCode, + 'obj': "returnArray" + }) + sequenceWrapLevel -= 1 + code = fill( + """ + + uint32_t length = ${result}.Length(); + JS::Rooted returnArray(cx, JS_NewArrayObject(cx, length)); + if (!returnArray) { + $*{exceptionCode} + } + // Scope for 'tmp' + { + JS::Rooted tmp(cx); + for (uint32_t ${index} = 0; ${index} < length; ++${index}) { + // Control block to let us common up the JS_DefineElement calls when there + // are different ways to succeed at wrapping the object. + do { + $*{innerTemplate} + } while (0); + if (!JS_DefineElement(cx, returnArray, ${index}, tmp, + nullptr, nullptr, JSPROP_ENUMERATE)) { + $*{exceptionCode} + } + } + } + $*{set} + """, + result=result, + exceptionCode=exceptionCode, + index=index, + innerTemplate=innerTemplate, + set=setObject("*returnArray")) + + return (code, False) + + if type.isMozMap(): + # Now do non-nullable MozMap. Our success code is just to break to + # where we define the property on the object. Note that we bump the + # mozMapWrapLevel around this call so that nested MozMap conversions + # will use different temp value names. + global mozMapWrapLevel + valueName = "mozMapValue%d" % mozMapWrapLevel + mozMapWrapLevel += 1 + innerTemplate = wrapForType( + type.inner, descriptorProvider, + { + 'result': valueName, + 'successCode': "break;\n", + 'jsvalRef': "tmp", + 'jsvalHandle': "&tmp", + 'returnsNewObject': returnsNewObject, + 'exceptionCode': exceptionCode, + 'obj': "returnObj" + }) + mozMapWrapLevel -= 1 + code = fill( + """ + + nsTArray keys; + ${result}.GetKeys(keys); + JS::Rooted returnObj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); + if (!returnObj) { + $*{exceptionCode} + } + // Scope for 'tmp' + { + JS::Rooted tmp(cx); + for (size_t idx = 0; idx < keys.Length(); ++idx) { + auto& ${valueName} = ${result}.Get(keys[idx]); + // Control block to let us common up the JS_DefineUCProperty calls when there + // are different ways to succeed at wrapping the value. + do { + $*{innerTemplate} + } while (0); + if (!JS_DefineUCProperty(cx, returnObj, keys[idx].get(), + keys[idx].Length(), tmp, + JS_PropertyStub, JS_StrictPropertyStub, + JSPROP_ENUMERATE)) { + $*{exceptionCode} + } + } + } + $*{set} + """, + result=result, + exceptionCode=exceptionCode, + valueName=valueName, + innerTemplate=innerTemplate, + set=setObject("*returnObj")) + + return (code, False) + + if type.isGeckoInterface() and not type.isCallbackInterface(): + descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name) + if type.nullable(): + wrappingCode = ("if (!%s) {\n" % (result) + + indent(setNull()) + + "}\n") + else: + wrappingCode = "" + + if not descriptor.interface.isExternal() and not descriptor.skipGen: + if descriptor.wrapperCache: + assert descriptor.nativeOwnership != 'owned' + wrapMethod = "WrapNewBindingObject" + else: + if not returnsNewObject: + raise MethodNotNewObjectError(descriptor.interface.identifier.name) + if descriptor.nativeOwnership == 'owned': + wrapMethod = "WrapNewBindingNonWrapperCachedOwnedObject" + else: + wrapMethod = "WrapNewBindingNonWrapperCachedObject" + wrap = "%s(cx, ${obj}, %s, ${jsvalHandle})" % (wrapMethod, result) + if not descriptor.hasXPConnectImpls: + # Can only fail to wrap as a new-binding object + # if they already threw an exception. + #XXX Assertion disabled for now, see bug 991271. + failed = ("MOZ_ASSERT(true || JS_IsExceptionPending(cx));\n" + + exceptionCode) + else: + if descriptor.notflattened: + raise TypeError("%s has XPConnect impls but not flattened; " + "fallback won't work correctly" % + descriptor.interface.identifier.name) + # Try old-style wrapping for bindings which might be XPConnect impls. + failed = wrapAndSetPtr("HandleNewBindingWrappingFailure(cx, ${obj}, %s, ${jsvalHandle})" % result) + else: + if descriptor.notflattened: + getIID = "&NS_GET_IID(%s), " % descriptor.nativeType + else: + getIID = "" + wrap = "WrapObject(cx, %s, %s${jsvalHandle})" % (result, getIID) + failed = None + + wrappingCode += wrapAndSetPtr(wrap, failed) + return (wrappingCode, False) + + if type.isDOMString(): + if type.nullable(): + return (wrapAndSetPtr("xpc::StringToJsval(cx, %s, ${jsvalHandle})" % result), False) + else: + return (wrapAndSetPtr("xpc::NonVoidStringToJsval(cx, %s, ${jsvalHandle})" % result), False) + + if type.isByteString(): + if type.nullable(): + return (wrapAndSetPtr("ByteStringToJsval(cx, %s, ${jsvalHandle})" % result), False) + else: + return (wrapAndSetPtr("NonVoidByteStringToJsval(cx, %s, ${jsvalHandle})" % result), False) + + if type.isEnum(): + if type.nullable(): + resultLoc = "%s.Value()" % result + else: + resultLoc = result + conversion = fill( + """ + { + // Scope for resultStr + MOZ_ASSERT(uint32_t(${result}) < ArrayLength(${strings})); + JSString* resultStr = JS_NewStringCopyN(cx, ${strings}[uint32_t(${result})].value, ${strings}[uint32_t(${result})].length); + if (!resultStr) { + $*{exceptionCode} + } + $*{setResultStr} + } + """, + result=resultLoc, + strings=(type.unroll().inner.identifier.name + "Values::" + + ENUM_ENTRY_VARIABLE_NAME), + exceptionCode=exceptionCode, + setResultStr=setString("resultStr")) + + if type.nullable(): + conversion = CGIfElseWrapper( + "%s.IsNull()" % result, + CGGeneric(setNull()), + CGGeneric(conversion)).define() + return conversion, False + + if type.isCallback() or type.isCallbackInterface(): + wrapCode = setObject( + "*GetCallbackFromCallbackObject(%(result)s)", + wrapAsType=type) + if type.nullable(): + wrapCode = ( + "if (%(result)s) {\n" + + indent(wrapCode) + + "} else {\n" + + indent(setNull()) + + "}\n") + wrapCode = wrapCode % {"result": result} + return wrapCode, False + + if type.isAny(): + # See comments in WrapNewBindingObject explaining why we need + # to wrap here. + # NB: _setValue(..., type-that-is-any) calls JS_WrapValue(), so is fallible + return (_setValue(result, wrapAsType=type), False) + + if (type.isObject() or (type.isSpiderMonkeyInterface() and + not typedArraysAreStructs)): + # See comments in WrapNewBindingObject explaining why we need + # to wrap here. + if type.nullable(): + toValue = "%s" + setter = setObjectOrNull + else: + toValue = "*%s" + setter = setObject + # NB: setObject{,OrNull}(..., some-object-type) calls JS_WrapValue(), so is fallible + return (setter(toValue % result, wrapAsType=type), False) + + if not (type.isUnion() or type.isPrimitive() or type.isDictionary() or + type.isDate() or + (type.isSpiderMonkeyInterface() and typedArraysAreStructs)): + raise TypeError("Need to learn to wrap %s" % type) + + if type.nullable(): + recTemplate, recInfal = getWrapTemplateForType(type.inner, descriptorProvider, + "%s.Value()" % result, successCode, + returnsNewObject, exceptionCode, + typedArraysAreStructs) + return ("if (%s.IsNull()) {\n" % result + + indent(setNull()) + + "}\n" + + recTemplate, recInfal) + + if type.isSpiderMonkeyInterface(): + assert typedArraysAreStructs + # See comments in WrapNewBindingObject 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.ToObject(cx, ${jsvalHandle})" % result), + False) + + if type.isDate(): + return (wrapAndSetPtr("%s.ToDateObject(cx, ${jsvalHandle})" % result), + False) + + tag = type.tag() + + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16, + IDLType.Tags.uint16, IDLType.Tags.int32]: + return (setInt32("int32_t(%s)" % result), True) + + elif tag in [IDLType.Tags.int64, IDLType.Tags.uint64, + IDLType.Tags.unrestricted_float, IDLType.Tags.float, + IDLType.Tags.unrestricted_double, IDLType.Tags.double]: + # XXXbz will cast to double do the "even significand" thing that webidl + # calls for for 64-bit ints? Do we care? + return (setDouble("double(%s)" % result), True) + + elif tag == IDLType.Tags.uint32: + return (setUint32(result), True) + + elif tag == IDLType.Tags.bool: + return (setBoolean(result), True) + + else: + raise TypeError("Need to learn to wrap primitive: %s" % type) + + +def wrapForType(type, descriptorProvider, templateValues): + """ + Reflect a C++ value of IDL type "type" into JS. TemplateValues is a dict + that should contain: + + * 'jsvalRef': something that can have .address() called on it to get a + JS::Value* and .set() called on it to set it to a JS::Value. + This can be a JS::MutableHandle or a + JS::Rooted. + * 'jsvalHandle': something that can be passed to methods taking a + JS::MutableHandle. This can be a + JS::MutableHandle or a JS::Rooted*. + * '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. + """ + wrap = getWrapTemplateForType(type, descriptorProvider, + templateValues.get('result', 'result'), + templateValues.get('successCode', None), + templateValues.get('returnsNewObject', False), + templateValues.get('exceptionCode', + "return false;\n"), + templateValues.get('typedArraysAreStructs', + False))[0] + + defaultValues = {'obj': 'obj'} + return string.Template(wrap).substitute(defaultValues, **templateValues) + + +def infallibleForMember(member, type, descriptorProvider): + """ + Determine the fallibility of changing a C++ value of IDL type "type" into + JS for the given attribute. Apart from returnsNewObject, all the defaults + are used, since the fallbility does not change based on the boolean values, + and the template will be discarded. + + CURRENT ASSUMPTIONS: + We assume that successCode for wrapping up return values cannot contain + failure conditions. + """ + return getWrapTemplateForType(type, descriptorProvider, 'result', None, + memberReturnsNewObject(member), "return false;\n", + False)[1] + + +def leafTypeNeedsCx(type, retVal): + return (type.isAny() or type.isObject() or + (retVal and type.isSpiderMonkeyInterface())) + + +def leafTypeNeedsScopeObject(type, retVal): + return retVal and type.isSpiderMonkeyInterface() + + +def leafTypeNeedsRooting(type): + return leafTypeNeedsCx(type, False) or type.isSpiderMonkeyInterface() + + +def typeNeedsRooting(type): + return typeMatchesLambda(type, + lambda t: leafTypeNeedsRooting(t)) + + +def typeNeedsCx(type, retVal=False): + return typeMatchesLambda(type, + lambda t: leafTypeNeedsCx(t, retVal)) + + +def typeNeedsScopeObject(type, retVal=False): + return typeMatchesLambda(type, + lambda t: leafTypeNeedsScopeObject(t, retVal)) + + +def typeMatchesLambda(type, func): + if type is None: + return False + if type.nullable(): + return typeMatchesLambda(type.inner, func) + if type.isSequence() or type.isMozMap() or type.isArray(): + 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, + resultAlreadyAddRefed, + isMember=False): + """ + Returns a tuple containing four 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. + """ + if returnType is None or returnType.isVoid(): + # Nothing to declare + return 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 + if returnType.isDOMString(): + if isMember: + return CGGeneric("nsString"), "ref", None, None + return CGGeneric("DOMString"), "ref", None, None + if returnType.isByteString(): + return CGGeneric("nsCString"), "ref", None, None + if returnType.isEnum(): + result = CGGeneric(returnType.unroll().inner.identifier.name) + if returnType.nullable(): + result = CGTemplatedType("Nullable", result) + return result, None, None, None + if returnType.isGeckoInterface(): + result = CGGeneric(descriptorProvider.getDescriptor( + returnType.unroll().inner.identifier.name).nativeType) + if descriptorProvider.getDescriptor( + returnType.unroll().inner.identifier.name).nativeOwnership == 'owned': + result = CGTemplatedType("nsAutoPtr", result) + elif resultAlreadyAddRefed: + result = CGTemplatedType("nsRefPtr", result) + else: + result = CGWrapper(result, post="*") + return result, None, None, None + if returnType.isCallback(): + name = returnType.unroll().identifier.name + return CGGeneric("nsRefPtr<%s>" % name), None, None, None + if returnType.isAny(): + if isMember: + return CGGeneric("JS::Value"), None, None, None + return CGGeneric("JS::Rooted"), "ptr", None, "cx" + if returnType.isObject() or returnType.isSpiderMonkeyInterface(): + if isMember: + return CGGeneric("JSObject*"), None, None, None + return CGGeneric("JS::Rooted"), "ptr", None, "cx" + if returnType.isSequence(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # If our result is already addrefed, use the right type in the + # sequence argument here. + result, _, _, _ = getRetvalDeclarationForType(returnType.inner, + descriptorProvider, + resultAlreadyAddRefed, + 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 + if returnType.isMozMap(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # If our result is already addrefed, use the right type in the + # MozMap argument here. + result, _, _, _ = getRetvalDeclarationForType(returnType.inner, + descriptorProvider, + resultAlreadyAddRefed, + isMember="MozMap") + # While we have our inner type, set up our rooter, if needed + if not isMember and typeNeedsRooting(returnType): + rooter = CGGeneric("MozMapRooter<%s> resultRooter(cx, &result);\n" % + result.define()) + else: + rooter = None + result = CGTemplatedType("MozMap", result) + if nullable: + result = CGTemplatedType("Nullable", result) + return result, "ref", rooter, None + 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 + 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 + if returnType.isDate(): + result = CGGeneric("Date") + if returnType.nullable(): + result = CGTemplatedType("Nullable", result) + return result, None, None, None + raise TypeError("Don't know how to declare return value for %s" % + returnType) + + +def isResultAlreadyAddRefed(extendedAttributes): + return 'resultNotAddRefed' not in extendedAttributes + + +def needCx(returnType, arguments, extendedAttributes, considerTypes): + return (considerTypes and + (typeNeedsCx(returnType, True) or + any(typeNeedsCx(a.type) for a in arguments)) or + 'implicitJSContext' in extendedAttributes) + + +def needScopeObject(returnType, arguments, extendedAttributes, + isWrapperCached, considerTypes, isMember): + """ + isMember should be true if we're dealing with an attribute + annotated as [StoreInSlot]. + """ + return (considerTypes and not isWrapperCached and + ((not isMember and typeNeedsScopeObject(returnType, True)) or + any(typeNeedsScopeObject(a.type) for a in arguments))) + + +class CGCallGenerator(CGThing): + """ + A class to generate an actual call to a C++ object. Assumes that the C++ + object is stored in a variable whose name is given by the |object| argument. + + errorReport should be a CGThing for an error report or None if no + error reporting is needed. + """ + def __init__(self, errorReport, arguments, argsPre, returnType, + extendedAttributes, descriptorProvider, nativeMethodName, + static, object="self", argsPost=[]): + CGThing.__init__(self) + + assert errorReport is None or isinstance(errorReport, CGThing) + + isFallible = errorReport is not None + + resultAlreadyAddRefed = isResultAlreadyAddRefed(extendedAttributes) + result, resultOutParam, resultRooter, resultArgs = \ + getRetvalDeclarationForType( + returnType, descriptorProvider, resultAlreadyAddRefed) + + args = CGList([CGGeneric(arg) for arg in argsPre], ", ") + for a, name in arguments: + arg = CGGeneric(name) + + # Now constify the things that need it + def needsConst(a): + if a.type.isDictionary(): + return True + if a.type.isSequence(): + return True + if a.type.isMozMap(): + return True + # isObject() types are always a JS::Rooted, whether + # nullable or not, and it turns out a const JS::Rooted + # is not very helpful at all (in particular, it won't + # even convert to a JS::Handle). + # XXX bz Well, why not??? + if a.type.nullable() and not a.type.isObject(): + return True + if a.type.isString(): + return True + if a.optional and not a.defaultValue: + # If a.defaultValue, then it's not going to use an Optional, + # so doesn't need to be const just due to being optional. + # This also covers variadic arguments. + 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 to T& + if (((a.type.isGeckoInterface() or a.type.isCallback()) and not a.type.nullable()) or + a.type.isDOMString()): + arg = CGWrapper(arg, pre="NonNullHelper(", post=")") + args.append(arg) + + # Return values that go in outparams go here + if resultOutParam is not None: + if resultOutParam is "ref": + args.append(CGGeneric("result")) + else: + assert resultOutParam is "ptr" + args.append(CGGeneric("&result")) + + if isFallible: + args.append(CGGeneric("rv")) + args.extend(CGGeneric(arg) for arg in argsPost) + + # Build up our actual call + self.cgRoot = CGList([]) + + call = CGGeneric(nativeMethodName) + if not static: + call = CGWrapper(call, pre="%s->" % object) + call = CGList([call, CGWrapper(args, pre="(", post=");\n")]) + if result is not None: + if resultRooter is not None: + self.cgRoot.prepend(resultRooter) + if resultArgs is not None: + resultArgs = "(%s)" % resultArgs + else: + resultArgs = "" + result = CGWrapper(result, post=(" result%s;\n" % resultArgs)) + self.cgRoot.prepend(result) + if not resultOutParam: + call = CGWrapper(call, pre="result = ") + + call = CGWrapper(call) + self.cgRoot.append(call) + + if isFallible: + self.cgRoot.prepend(CGGeneric("ErrorResult rv;\n")) + self.cgRoot.append(CGGeneric("rv.WouldReportJSException();\n")) + self.cgRoot.append(CGGeneric("if (rv.Failed()) {\n")) + self.cgRoot.append(CGIndenter(errorReport)) + self.cgRoot.append(CGGeneric("}\n")) + + def define(self): + return self.cgRoot.define() + + +def getUnionMemberName(type): + if type.isGeckoInterface(): + return type.inner.identifier.name + if type.isEnum(): + return type.inner.identifier.name + if type.isArray() or type.isSequence(): + return str(type) + return type.name + + +class MethodNotNewObjectError(Exception): + def __init__(self, typename): + self.typename = typename + +# A counter for making sure that when we're wrapping up things in +# nested sequences we don't use the same variable name to iterate over +# different sequences. +sequenceWrapLevel = 0 + + +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::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::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.isDictionary(): + assert not type.nullable() + myDict = type.inner + memberWraps = [] + while myDict: + for member in myDict.members: + memberWrap = wrapArgIntoCurrentCompartment( + member, + "%s.%s" % (value, CGDictionary.makeMemberName(member.identifier.name))) + if memberWrap: + memberWraps.append(memberWrap) + myDict = myDict.parent + return CGList(memberWraps) if len(memberWraps) != 0 else None + + if type.isUnion(): + memberWraps = [] + if type.nullable(): + type = type.inner + value = "%s.Value()" % value + for member in type.flatMemberTypes: + memberName = getUnionMemberName(member) + memberWrap = wrapTypeIntoCurrentCompartment( + member, "%s.GetAs%s()" % (value, memberName)) + if memberWrap: + memberWrap = CGIfWrapper( + memberWrap, "%s.Is%s()" % (value, memberName)) + memberWraps.append(memberWrap) + return CGList(memberWraps, "else ") if len(memberWraps) != 0 else None + + if (type.isString() or type.isPrimitive() or type.isEnum() or + type.isGeckoInterface() or type.isCallback() or type.isDate()): + # All of these don't need wrapping + return None + + raise TypeError("Unknown type; we don't know how to wrap it in constructor " + "arguments: %s" % type) + + +def wrapArgIntoCurrentCompartment(arg, value, isMember=True): + """ + As wrapTypeIntoCurrentCompartment but handles things being optional + """ + origValue = value + isOptional = arg.optional and not arg.defaultValue + if isOptional: + value = value + ".Value()" + wrap = wrapTypeIntoCurrentCompartment(arg.type, value, isMember) + if wrap and isOptional: + wrap = CGIfWrapper(wrap, "%s.WasPassed()" % origValue) + return wrap + + +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. + + We also need to know whether this is a method or a getter/setter + to do error reporting correctly. + + The idlNode parameter can be either a method or an attr. We can query + |idlNode.identifier| in both cases, so we can be agnostic between the two. + """ + # XXXbz For now each entry in the argument list is either an + # IDLArgument or a FakeArgument, but longer-term we may want to + # have ways of flagging things like JSContext* or optional_argc in + # there. + + def __init__(self, returnType, arguments, nativeMethodName, static, + descriptor, idlNode, argConversionStartsAt=0, getter=False, + setter=False, isConstructor=False): + assert idlNode.isMethod() == (not getter and not setter) + assert idlNode.isAttr() == (getter or setter) + # Constructors are always static + assert not isConstructor or static + + CGThing.__init__(self) + self.returnType = returnType + self.descriptor = descriptor + self.idlNode = idlNode + self.extendedAttributes = descriptor.getExtendedAttributes(idlNode, + getter=getter, + setter=setter) + self.arguments = arguments + self.argCount = len(arguments) + cgThings = [] + lenientFloatCode = None + if idlNode.getExtendedAttribute('LenientFloat') is not None: + if setter: + lenientFloatCode = "return true;\n" + elif idlNode.isMethod(): + lenientFloatCode = ("args.rval().setUndefined();\n" + "return true;\n") + + argsPre = [] + if static: + nativeMethodName = "%s::%s" % (descriptor.nativeType, + nativeMethodName) + # 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. + needsCx = needCx(returnType, arguments, self.extendedAttributes, + not descriptor.interface.isJSImplemented()) + if needsCx and not (static and descriptor.workers): + argsPre.append("cx") + + needsUnwrap = False + argsPost = [] + if isConstructor: + needsUnwrap = True + needsUnwrappedVar = False + unwrappedVar = "obj" + elif descriptor.interface.isJSImplemented(): + needsUnwrap = True + needsUnwrappedVar = True + argsPost.append("js::GetObjectCompartment(unwrappedObj.empty() ? obj : unwrappedObj.ref())") + elif needScopeObject(returnType, arguments, self.extendedAttributes, + descriptor.wrapperCache, True, + idlNode.getExtendedAttribute("StoreInSlot")): + needsUnwrap = True + needsUnwrappedVar = True + argsPre.append("unwrappedObj.empty() ? obj : unwrappedObj.ref()") + + 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 > unwrappedObj;\n")) + unwrappedVar = "unwrappedObj.ref()" + + if idlNode.isMethod() and idlNode.isLegacycaller(): + # If we can have legacycaller with identifier, we can't + # just use the idlNode to determine whether we're + # generating code for the legacycaller or not. + assert idlNode.isIdentifierLess() + # Pass in our thisVal + argsPre.append("args.thisv()") + + ourName = "%s.%s" % (descriptor.interface.identifier.name, + idlNode.identifier.name) + if idlNode.isMethod(): + argDescription = "argument %(index)d of " + ourName + elif setter: + argDescription = "value being assigned to %s" % ourName + else: + assert self.argCount == 0 + + if needsUnwrap: + # It's very important that we construct our unwrappedObj, if we need + # to do it, before we might start setting up Rooted things for our + # arguments, so that we don't violate the stack discipline Rooted + # depends on. + cgThings.append(CGGeneric( + "bool objIsXray = xpc::WrapperFactory::IsXrayWrapper(obj);\n")) + if needsUnwrappedVar: + cgThings.append(CGIfWrapper( + CGGeneric("unwrappedObj.construct(cx, obj);\n"), + "objIsXray")) + + for i in range(argConversionStartsAt, self.argCount): + cgThings.append( + CGArgumentConverter(arguments[i], i, self.descriptor, + argDescription % {"index": i + 1}, + invalidEnumValueFatal=not setter, + lenientFloatCode=lenientFloatCode)) + + 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(string.Template(dedent(""" + ${obj} = js::CheckedUnwrap(${obj}); + if (!${obj}) { + return false; + } + """)).substitute({'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 ac;\n")) + xraySteps.append(CGGeneric("ac.construct(cx, obj);\n")) + xraySteps.extend( + wrapArgIntoCurrentCompartment(arg, argname, isMember=False) + for arg, argname in self.getArguments()) + + cgThings.append( + CGIfWrapper(CGList(xraySteps), + "objIsXray")) + + cgThings.append(CGCallGenerator( + self.getErrorReport() if self.isFallible() else None, + self.getArguments(), argsPre, returnType, + self.extendedAttributes, descriptor, nativeMethodName, + static, argsPost=argsPost)) + self.cgRoot = CGList(cgThings) + + def getArguments(self): + return [(a, "arg" + str(i)) for i, a in enumerate(self.arguments)] + + def isFallible(self): + return 'infallible' not in self.extendedAttributes + + def wrap_return_value(self): + returnsNewObject = memberReturnsNewObject(self.idlNode) + if returnsNewObject: + # We better be returning addrefed things! + assert(isResultAlreadyAddRefed(self.extendedAttributes) or + # NewObject can return raw pointers to owned objects + (self.returnType.isGeckoInterface() and + self.descriptor.getDescriptor(self.returnType.unroll().inner.identifier.name).nativeOwnership == 'owned')) + + setSlot = self.idlNode.isAttr() and self.idlNode.slotIndex is not None + if setSlot: + # For attributes in slots, we want to do some + # post-processing once we've wrapped them. + successCode = "break;\n" + else: + successCode = None + + resultTemplateValues = { + 'jsvalRef': 'args.rval()', + 'jsvalHandle': 'args.rval()', + 'returnsNewObject': returnsNewObject, + 'successCode': successCode, + 'obj': "reflector" if setSlot else "obj" + } + try: + wrapCode = wrapForType(self.returnType, self.descriptor, resultTemplateValues) + except MethodNotNewObjectError, err: + assert not returnsNewObject + raise TypeError("%s being returned from non-NewObject method or property %s.%s" % + (err.typename, + self.descriptor.interface.identifier.name, + self.idlNode.identifier.name)) + if setSlot: + # We need to make sure that our initial wrapping is done in the + # reflector compartment, but that we finally set args.rval() in the + # caller compartment. We also need to make sure that the actual + # wrapping steps happen inside a do/while that they can break out + # of. + + # postSteps are the steps that run while we're still in the + # reflector compartment but after we've finished the initial + # wrapping into args.rval(). + postSteps = "" + if self.idlNode.getExtendedAttribute("Frozen"): + assert self.idlNode.type.isSequence() or self.idlNode.type.isDictionary() + freezeValue = CGGeneric( + "JS::Rooted 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()") + postSteps += freezeValue.define() + postSteps += ("js::SetReservedSlot(reflector, %s, args.rval());\n" % + memberReservedSlot(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): + postSteps += "PreserveWrapper(self);\n" + + wrapCode = fill( + """ + { // Make sure we wrap and store in the slot in reflector's compartment + JSAutoCompartment ac(cx, reflector); + do { // block we break out of when done wrapping + $*{wrapCode} + } while (0); + $*{postSteps} + } + // And now make sure args.rval() is in the caller compartment + return ${maybeWrap}(cx, args.rval()); + """, + wrapCode=wrapCode, + postSteps=postSteps, + maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type)) + return wrapCode + + def getErrorReport(self): + jsImplemented = "" + if self.descriptor.interface.isJSImplemented(): + jsImplemented = ", true" + return CGGeneric('return ThrowMethodFailedWithDetails(cx, rv, "%s", "%s"%s);\n' + % (self.descriptor.interface.identifier.name, + self.idlNode.identifier.name, + jsImplemented)) + + def define(self): + return (self.cgRoot.define() + self.wrap_return_value()) + + +class CGSwitch(CGList): + """ + A class to generate code for a switch statement. + + Takes three constructor arguments: an expression, a list of cases, + and an optional default. + + Each case is a CGCase. The default is a CGThing for the body of + the default case, if any. + """ + def __init__(self, expression, cases, default=None): + CGList.__init__(self, [CGIndenter(c) for c in cases]) + self.prepend(CGGeneric("switch (" + expression + ") {\n")) + if default is not None: + self.append( + CGIndenter( + CGWrapper( + CGIndenter(default), + pre="default: {\n", + post=" break;\n}\n"))) + + self.append(CGGeneric("}\n")) + + +class CGCase(CGList): + """ + A class to generate code for a case statement. + + Takes three constructor arguments: an expression, a CGThing for + the body (allowed to be None if there is no body), and an optional + argument (defaulting to False) for whether to fall through. + """ + def __init__(self, expression, body, fallThrough=False): + CGList.__init__(self, []) + self.append(CGGeneric("case " + expression + ": {\n")) + bodyList = CGList([body]) + if fallThrough: + bodyList.append(CGGeneric("/* Fall through */\n")) + else: + bodyList.append(CGGeneric("break;\n")) + self.append(CGIndenter(bodyList)) + self.append(CGGeneric("}\n")) + + +class CGMethodCall(CGThing): + """ + A class to generate selection of a method signature from a set of + signatures and generation of a call to that signature. + """ + def __init__(self, nativeMethodName, static, descriptor, method, + isConstructor=False, constructorName=None): + CGThing.__init__(self) + + if isConstructor: + assert constructorName is not None + methodName = constructorName + else: + methodName = "%s.%s" % (descriptor.interface.identifier.name, method.identifier.name) + argDesc = "argument %d of " + methodName + + 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) + + 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([CGIndenter(getPerSignatureCall(signature))]) + requiredArgs = requiredArgCount(signature) + + if requiredArgs > 0: + code = indent(fill( # BOGUS extra blank line + """ + + if (args.length() < ${requiredArgs}) { + return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${methodName}"); + } + """, + requiredArgs=requiredArgs, + methodName=methodName)) + self.cgRoot.prepend(CGGeneric(code)) + return + + # Need to find the right overload + maxArgCount = method.maxArgCount + allowedArgCounts = method.allowedArgCounts + + argCountCases = [] + for argCountIdx, argCount in enumerate(allowedArgCounts): + possibleSignatures = method.signaturesForArgCount(argCount) + + # Try to optimize away cases when the next argCount in the list + # will have the same code as us; if it does, we can fall through to + # that case. + if argCountIdx+1 < len(allowedArgCounts): + nextPossibleSignatures = \ + method.signaturesForArgCount(allowedArgCounts[argCountIdx+1]) + else: + nextPossibleSignatures = None + if possibleSignatures == nextPossibleSignatures: + # Same set of signatures means we better have the same + # distinguishing index. So we can in fact just fall through to + # the next case here. + assert (len(possibleSignatures) == 1 or + (method.distinguishingIndexForArgCount(argCount) == + method.distinguishingIndexForArgCount(allowedArgCounts[argCountIdx+1]))) + argCountCases.append(CGCase(str(argCount), None, True)) + continue + + if len(possibleSignatures) == 1: + # easy case! + signature = possibleSignatures[0] + argCountCases.append( + CGCase(str(argCount), getPerSignatureCall(signature))) + continue + + distinguishingIndex = method.distinguishingIndexForArgCount(argCount) + + def distinguishingArgument(signature): + args = signature[1] + if distinguishingIndex < len(args): + return args[distinguishingIndex] + assert args[-1].variadic + return args[-1] + + def distinguishingType(signature): + return distinguishingArgument(signature).type + + for sig in possibleSignatures: + # We should not have "any" args at distinguishingIndex, + # since we have multiple possible signatures remaining, + # but "any" is never distinguishable from anything else. + assert not distinguishingType(sig).isAny() + # We can't handle unions at the distinguishing index. + if distinguishingType(sig).isUnion(): + raise TypeError("No support for unions as distinguishing " + "arguments yet: %s" % + distinguishingArgument(sig).location) + # We don't support variadics as the distinguishingArgument yet. + # If you want to add support, consider this case: + # + # void(long... foo); + # void(long bar, Int32Array baz); + # + # in which we have to convert argument 0 to long before picking + # an overload... but all the variadic stuff needs to go into a + # single array in case we pick that overload, so we have to have + # machinery for converting argument 0 to long and then either + # placing it in the variadic bit or not. Or something. We may + # be able to loosen this restriction if the variadic arg is in + # fact at distinguishingIndex, perhaps. Would need to + # double-check. + if distinguishingArgument(sig).variadic: + raise TypeError("No support for variadics as distinguishing " + "arguments yet: %s" % + distinguishingArgument(sig).location) + + # Convert all our arguments up to the distinguishing index. + # Doesn't matter which of the possible signatures we use, since + # they all have the same types up to that point; just use + # possibleSignatures[0] + caseBody = [CGArgumentConverter(possibleSignatures[0][1][i], + i, descriptor, + argDesc % (i + 1)) + 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).optional and + not distinguishingArgument(signature).defaultValue) + 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, + "mutableVal": distinguishingArg, + "obj": "obj", + "haveValue": "args.hasDefined(%d)" % distinguishingIndex + }, + checkForValue=argIsOptional) + caseBody.append(CGIndenter(testCode, indent)) + + # If we got this far, we know we unwrapped to the right + # C++ type, so just do the call. Start conversion with + # distinguishingIndex + 1, since we already converted + # distinguishingIndex. + caseBody.append(CGIndenter( + getPerSignatureCall(signature, distinguishingIndex + 1), + indent)) + + def hasConditionalConversion(type): + """ + Return whether the argument conversion for this type will be + conditional on the type of incoming JS value. For example, for + interface types the conversion is conditional on the incoming + value being isObject(). + + For the types for which this returns false, we do not have to + output extra isUndefined() or isNullOrUndefined() cases, because + null/undefined values will just fall through into our + unconditional conversion. + """ + if type.isString() or type.isEnum(): + return False + if type.isBoolean(): + distinguishingTypes = (distinguishingType(s) for s in + possibleSignatures) + return any(t.isString() or t.isEnum() or t.isNumeric() + for t in distinguishingTypes) + if type.isNumeric(): + distinguishingTypes = (distinguishingType(s) for s in + possibleSignatures) + return any(t.isString() or t.isEnum() + for t in distinguishingTypes) + return True + + def needsNullOrUndefinedCase(type): + """ + Return true if the type needs a special isNullOrUndefined() case + """ + return ((type.nullable() and + hasConditionalConversion(type)) or + type.isDictionary()) + + # First check for undefined and optional distinguishing arguments + # and output a special branch for that case. Note that we don't + # use distinguishingArgument here because we actualy want to + # exclude variadic arguments. Also note that we skip this check if + # we plan to output a isNullOrUndefined() special case for this + # argument anyway, since that will subsume our isUndefined() check. + # This is safe, because there can be at most one nullable + # distinguishing argument, so if we're it we'll definitely get + # picked up by the nullable handling. Also, we can skip this check + # if the argument has an unconditional conversion later on. + undefSigs = [s for s in possibleSignatures if + distinguishingIndex < len(s[1]) and + s[1][distinguishingIndex].optional and + hasConditionalConversion(s[1][distinguishingIndex].type) and + not needsNullOrUndefinedCase(s[1][distinguishingIndex].type)] + # Can't have multiple signatures with an optional argument at the + # same index. + assert len(undefSigs) < 2 + if len(undefSigs) > 0: + caseBody.append(CGGeneric("if (%s.isUndefined()) {\n" % + distinguishingArg)) + tryCall(undefSigs[0], 2, isNullOrUndefined=True) + caseBody.append(CGGeneric("}\n")) + + # Next, check for null or undefined. That means looking for + # nullable arguments at the distinguishing index and outputting a + # separate branch for them. But if the nullable argument has an + # unconditional conversion, we don't need to do that. The reason + # for that is that at most one argument at the distinguishing index + # is nullable (since two nullable arguments are not + # distinguishable), and null/undefined values will always fall + # through to the unconditional conversion we have, if any, since + # they will fail whatever the conditions on the input value are for + # our other conversions. + nullOrUndefSigs = [s for s in possibleSignatures + if needsNullOrUndefinedCase(distinguishingType(s))] + # Can't have multiple nullable types here + assert len(nullOrUndefSigs) < 2 + if len(nullOrUndefSigs) > 0: + caseBody.append(CGGeneric("if (%s.isNullOrUndefined()) {\n" % + distinguishingArg)) + tryCall(nullOrUndefSigs[0], 2, isNullOrUndefined=True) + caseBody.append(CGGeneric("}\n")) + + # Now check for distinguishingArg being various kinds of objects. + # The spec says to check for the following things in order: + # 1) A platform object that's not a platform array object, being + # passed to an interface or "object" arg. + # 2) A Date object being passed to a Date or "object" arg. + # 3) A RegExp object being passed to a RegExp or "object" arg. + # 4) A callable object being passed to a callback or "object" arg. + # 5) Any non-Date and non-RegExp object being passed to a + # array or sequence or callback interface dictionary or + # "object" arg. + # + # We can can coalesce these five cases together, as long as we make + # sure to check whether our object works as an interface argument + # before checking whether it works as an arraylike or dictionary or + # callback function or callback interface. + + # First grab all the overloads that have a non-callback interface + # (which includes typed arrays and arraybuffers) at the + # distinguishing index. We can also include the ones that have an + # "object" here, since if those are present no other object-typed + # argument will be. + objectSigs = [ + s for s in possibleSignatures + if (distinguishingType(s).isObject() or + distinguishingType(s).isNonCallbackInterface())] + + # And all the overloads that take Date + objectSigs.extend(s for s in possibleSignatures + if distinguishingType(s).isDate()) + + # And all the overloads that take callbacks + objectSigs.extend(s for s in possibleSignatures + if distinguishingType(s).isCallback()) + + # Now append all the overloads that take an array or sequence or + # dictionary or callback interface: + objectSigs.extend(s for s in possibleSignatures + if (distinguishingType(s).isArray() or + distinguishingType(s).isSequence() or + distinguishingType(s).isDictionary() or + distinguishingType(s).isCallbackInterface())) + + # There might be more than one thing in objectSigs; we need to check + # which ones we unwrap to. + if len(objectSigs) > 0: + # Here it's enough to guard on our argument being an object. The + # code for unwrapping non-callback interfaces, typed arrays, + # sequences, arrays, and Dates will just bail out and move on to + # the next overload if the object fails to unwrap correctly, + # while "object" accepts any object anyway. We could even not + # do the isObject() check up front here, but in cases where we + # have multiple object overloads it makes sense to do it only + # once instead of for each overload. That will also allow the + # unwrapping test to skip having to do codegen for the + # null-or-undefined case, which we already handled above. + caseBody.append(CGGeneric("if (%s.isObject()) {\n" % + distinguishingArg)) + for sig in objectSigs: + caseBody.append(CGIndenter(CGGeneric("do {\n"))) + # Indent by 4, since we need to indent further + # than our "do" statement + tryCall(sig, 4, isDefinitelyObject=True) + caseBody.append(CGIndenter(CGGeneric("} while (0);\n"))) + + caseBody.append(CGGeneric("}\n")) + + # Now we only have to consider booleans, numerics, and strings. If + # we only have one of them, then we can just output it. But if not, + # then we need to output some of the cases conditionally: if we have + # a string overload, then boolean and numeric are conditional, and + # if not then boolean is conditional if we have a numeric overload. + def findUniqueSignature(filterLambda): + sigs = filter(filterLambda, possibleSignatures) + assert len(sigs) < 2 + if len(sigs) > 0: + return sigs[0] + return None + + stringSignature = findUniqueSignature( + lambda s: (distinguishingType(s).isString() or + distinguishingType(s).isEnum())) + numericSignature = findUniqueSignature( + lambda s: distinguishingType(s).isNumeric()) + booleanSignature = findUniqueSignature( + lambda s: distinguishingType(s).isBoolean()) + + if stringSignature or numericSignature: + booleanCondition = "%s.isBoolean()" + else: + booleanCondition = None + + if stringSignature: + numericCondition = "%s.isNumber()" + else: + numericCondition = None + + def addCase(sig, condition): + sigCode = getPerSignatureCall(sig, distinguishingIndex) + if condition: + sigCode = CGIfWrapper(sigCode, + condition % distinguishingArg) + caseBody.append(sigCode) + + if booleanSignature: + addCase(booleanSignature, booleanCondition) + if numericSignature: + addCase(numericSignature, numericCondition) + if stringSignature: + addCase(stringSignature, None) + + if (not booleanSignature and not numericSignature and + not stringSignature): + # Just throw; we have no idea what we're supposed to + # do with this. + caseBody.append(CGGeneric( + 'return ThrowErrorMessage(cx, MSG_OVERLOAD_RESOLUTION_FAILED, "%d", "%d", "%s");\n' % + (distinguishingIndex + 1, argCount, methodName))) + + argCountCases.append(CGCase(str(argCount), CGList(caseBody))) + + overloadCGThings = [] + overloadCGThings.append( + CGGeneric("unsigned argcount = std::min(args.length(), %du);\n" % + maxArgCount)) + overloadCGThings.append( + CGSwitch("argcount", + argCountCases, + # BOGUS extra blank line at end of default block + CGGeneric('return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "%s");\n\n' % + methodName))) + overloadCGThings.append( + CGGeneric('MOZ_CRASH("We have an always-returning default case");\n' + 'return false;\n')) + self.cgRoot = CGWrapper(CGIndenter(CGList(overloadCGThings)), + pre="\n") + + 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): + CGPerSignatureCall.__init__(self, returnType, [], nativeMethodName, + attr.isStatic(), descriptor, attr, + getter=True) + + +class FakeArgument(): + """ + A class that quacks like an IDLArgument. This is used to make + setters look like method calls or for special operations. + """ + def __init__(self, type, interfaceMember, name="arg", allowTreatNonCallableAsNull=False): + self.type = type + self.optional = False + self.variadic = False + self.defaultValue = None + self._allowTreatNonCallableAsNull = allowTreatNonCallableAsNull + self.treatNullAs = interfaceMember.treatNullAs + if isinstance(interfaceMember, IDLAttribute): + self.enforceRange = interfaceMember.enforceRange + self.clamp = interfaceMember.clamp + else: + self.enforceRange = False + self.clamp = False + + class FakeIdentifier(): + def __init__(self): + self.name = name + self.identifier = FakeIdentifier() + + def allowTreatNonCallableAsNull(self): + return self._allowTreatNonCallableAsNull + + +class CGSetterCall(CGPerSignatureCall): + """ + A class to generate a native object setter call for a particular IDL + setter. + """ + def __init__(self, argType, nativeMethodName, descriptor, attr): + CGPerSignatureCall.__init__(self, None, [FakeArgument(argType, attr, allowTreatNonCallableAsNull=True)], + nativeMethodName, attr.isStatic(), + descriptor, attr, setter=True) + + def wrap_return_value(self): + attr = self.idlNode + if self.descriptor.wrapperCache and attr.slotIndex is not None: + if attr.getExtendedAttribute("StoreInSlot"): + args = "cx, self" + else: + args = "self" + clearSlot = ("ClearCached%sValue(%s);\n" % + (MakeNativeName(self.idlNode.identifier.name), args)) + else: + clearSlot = "" + + # We have no return value + return ("\n" + "%s" + "return true;\n" % clearSlot) + + +class CGAbstractBindingMethod(CGAbstractStaticMethod): + """ + Common class to generate the JSNatives for all our methods, getters, and + setters. This will generate the function declaration and unwrap the + |this| object. Subclasses are expected to override the generate_code + function to do the rest of the work. This function should return a + CGThing which is already properly indented. + + getThisObj should be code for getting a JSObject* for the binding + object. If this is None, we will auto-generate code based on + descriptor to do the right thing. "" can be passed in if the + binding object is already stored in 'obj'. + + callArgs should be code for getting a JS::CallArgs into a variable + called 'args'. This can be "" if there is already such a variable + around. + + If allowCrossOriginThis is true, then this-unwrapping will first do an + UncheckedUnwrap and after that operate on the result. + """ + def __init__(self, descriptor, name, args, unwrapFailureCode=None, + getThisObj=None, + callArgs="JS::CallArgs args = JS::CallArgsFromVp(argc, vp);\n", + allowCrossOriginThis=False): + CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) + + if unwrapFailureCode is None: + self.unwrapFailureCode = 'return ThrowErrorMessage(cx, MSG_THIS_DOES_NOT_IMPLEMENT_INTERFACE, "Value", "%s");\n' % descriptor.interface.identifier.name + else: + self.unwrapFailureCode = unwrapFailureCode + + if getThisObj == "": + self.getThisObj = None + else: + if getThisObj is None: + if descriptor.interface.isOnGlobalProtoChain(): + ensureCondition = "!args.thisv().isNullOrUndefined() && !args.thisv().isObject()" + getThisObj = "args.thisv().isObject() ? &args.thisv().toObject() : js::GetGlobalForObjectCrossCompartment(&args.callee())" + else: + ensureCondition = "!args.thisv().isObject()" + getThisObj = "&args.thisv().toObject()" + unwrapFailureCode = self.unwrapFailureCode % {'securityError': 'false'} + ensureThisObj = CGIfWrapper(CGGeneric(unwrapFailureCode), + ensureCondition) + else: + ensureThisObj = None + self.getThisObj = CGList( + [ensureThisObj, + CGGeneric("JS::Rooted obj(cx, %s);\n" % + getThisObj)]) + self.callArgs = callArgs + self.allowCrossOriginThis = allowCrossOriginThis + + def definition_body(self): + body = self.callArgs + if self.getThisObj is not None: + body += self.getThisObj.define() + "\n" + body += "%s* self;\n" % self.descriptor.nativeType + + # Our descriptor might claim that we're not castable, simply because + # we're someone's consequential interface. But for this-unwrapping, we + # know that we're the real deal. So fake a descriptor here for + # consumption by CastableObjectUnwrapper. + body += str(CastableObjectUnwrapper( + self.descriptor, + "obj", "self", self.unwrapFailureCode, + allowCrossOriginObj=self.allowCrossOriginThis)) + + return indent(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): + args = [Argument('JSContext*', 'cx'), + Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) + + 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 = indent(dedent(""" + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted 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() + name[1:] + + +class CGGenericMethod(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL method. + + If allowCrossOriginThis is true, then this-unwrapping will first do an + UncheckedUnwrap and after that operate on the result. + """ + def __init__(self, descriptor, allowCrossOriginThis=False): + args = [Argument('JSContext*', 'cx'), + Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + unwrapFailureCode = ( + 'return ThrowInvalidThis(cx, args, GetInvalidThisErrorForMethod(%%(securityError)s), "%s");\n' % + descriptor.interface.identifier.name) + name = "genericCrossOriginMethod" if allowCrossOriginThis else "genericMethod" + CGAbstractBindingMethod.__init__(self, descriptor, name, + args, + unwrapFailureCode=unwrapFailureCode, + allowCrossOriginThis=allowCrossOriginThis) + + def generate_code(self): + return CGGeneric(indent(dedent(""" + const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); + MOZ_ASSERT(info->type() == JSJitInfo::Method); + JSJitMethodOp method = info->method; + return method(cx, obj, self, JSJitMethodCallArgs(args)); + """))) + + +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(method.identifier.name) + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'obj'), + Argument('%s*' % descriptor.nativeType, 'self'), + Argument('const JSJitMethodCallArgs&', 'args')] + CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args) + + def definition_body(self): + nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, + self.method) + return CGMethodCall(nativeName, self.method.isStatic(), self.descriptor, + self.method).define() + + @staticmethod + def makeNativeName(descriptor, method): + name = method.identifier.name + return MakeNativeName(descriptor.binaryNames.get(name, name)) + + +class CGMethodPromiseWrapper(CGAbstractStaticMethod): + """ + A class for generating a wrapper around another method that will + convert exceptions to promises. + """ + def __init__(self, descriptor, methodToWrap): + self.method = methodToWrap + name = self.makeName(methodToWrap.name) + args = list(methodToWrap.args) + CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args) + + def definition_body(self): + return indent(fill( + """ + // Make sure to save the callee before someone maybe messes + // with rval(). + JS::Rooted callee(cx, &args.callee()); + bool ok = ${methodName}(${args}); + if (ok) { + return true; + } + return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee), + args.rval()); + """, + methodName=self.method.name, + args=", ".join(arg.name for arg in self.args))) + + @staticmethod + def makeName(methodName): + return methodName + "_promiseWrapper" + + +class CGJsonifierMethod(CGSpecializedMethod): + def __init__(self, descriptor, method): + assert method.isJsonifier() + CGSpecializedMethod.__init__(self, descriptor, method) + + def definition_body(self): + ret = dedent(""" + JS::Rooted result(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); + if (!result) { + return false; + } + """) + for m in self.descriptor.interface.members: + if m.isAttr() and not m.isStatic() and m.type.isSerializable(): + ret += fill( + """ + { // scope for "temp" + JS::Rooted temp(cx); + if (!get_${name}(cx, obj, self, JSJitGetterCallArgs(&temp))) { + return false; + } + if (!JS_DefineProperty(cx, result, "${name}", temp, JSPROP_ENUMERATE)) { + return false; + } + } + """, + name=m.identifier.name) + + ret += ('args.rval().setObject(*result);\n' + 'return true;\n') + return indent(ret) + + +class CGLegacyCallHook(CGAbstractBindingMethod): + """ + Call hook for our object + """ + def __init__(self, descriptor): + self._legacycaller = descriptor.operations["LegacyCaller"] + args = [Argument('JSContext*', 'cx'), + Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + # Our "self" is actually the callee in this case, not the thisval. + CGAbstractBindingMethod.__init__( + self, descriptor, LEGACYCALLER_HOOK_NAME, + args, 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.binaryNames.get(name, name)) + return CGMethodCall(nativeName, False, self.descriptor, + self._legacycaller) + + +class CGNewResolveHook(CGAbstractBindingMethod): + """ + NewResolve hook for objects with custom hooks. + """ + def __init__(self, descriptor): + assert descriptor.interface.getExtendedAttribute("NeedNewResolve") + + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'obj'), + Argument('JS::Handle', 'id'), + Argument('JS::MutableHandle', 'objp')] + # Our "self" is actually the "obj" argument in this case, not the thisval. + CGAbstractBindingMethod.__init__( + self, descriptor, NEWRESOLVE_HOOK_NAME, + args, getThisObj="", callArgs="") + + def generate_code(self): + return CGGeneric(indent(dedent(""" + JS::Rooted desc(cx); + if (!self->DoNewResolve(cx, obj, id, &desc)) { + return false; + } + if (!desc.object()) { + return true; + } + // If desc.value() is undefined, then the DoNewResolve call + // has already defined it on the object. Don't try to also + // define it. + if (!desc.value().isUndefined() && + !JS_DefinePropertyById(cx, obj, id, desc.value(), + desc.getter(), desc.setter(), + desc.attributes())) { + return false; + } + objp.set(obj); + return true; + """))) + + def definition_body(self): + if self.descriptor.interface.getExtendedAttribute("Global"): + # Resolve standard classes + prefix = indent(dedent(""" + if (!ResolveGlobal(cx, obj, id, objp)) { + return false; + } + if (objp) { + return true; + } + + """)) + else: + prefix = "" + return prefix + CGAbstractBindingMethod.definition_body(self) + + +class CGEnumerateHook(CGAbstractBindingMethod): + """ + Enumerate hook for objects with custom hooks. + """ + def __init__(self, descriptor): + assert descriptor.interface.getExtendedAttribute("NeedNewResolve") + + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'obj')] + # Our "self" is actually the "obj" argument in this case, not the thisval. + CGAbstractBindingMethod.__init__( + self, descriptor, ENUMERATE_HOOK_NAME, + args, getThisObj="", callArgs="") + + def generate_code(self): + return CGGeneric(indent(dedent(""" + nsAutoTArray names; + ErrorResult rv; + self->GetOwnPropertyNames(cx, names, rv); + rv.WouldReportJSException(); + if (rv.Failed()) { + return ThrowMethodFailedWithDetails(cx, rv, "%s", "enumerate"); + } + JS::Rooted dummy(cx); + for (uint32_t i = 0; i < names.Length(); ++i) { + if (!JS_LookupUCProperty(cx, obj, names[i].get(), names[i].Length(), &dummy)) { + return false; + } + } + return true; + """))) + + def definition_body(self): + if self.descriptor.interface.getExtendedAttribute("Global"): + # Enumerate standard classes + prefix = indent(dedent(""" + if (!EnumerateGlobal(cx, obj)) { + return false; + } + + """)) + else: + prefix = "" + return prefix + CGAbstractBindingMethod.definition_body(self) + + +class CppKeywords(): + """ + A class for checking if method names declared in webidl + are not in conflict with C++ keywords. + """ + keywords = frozenset([ + 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'assert', 'auto', 'bitand', 'bitor', 'bool', + 'break', 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class', 'compl', 'const', + 'constexpr', 'const_cast', 'continue', 'decltype', 'default', 'delete', 'do', 'double', + 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'final', 'float', + 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', + 'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'override', 'private', + 'protected', 'public', 'register', 'reinterpret_cast', 'return', 'short', 'signed', + 'sizeof', 'static', 'static_assert', 'static_cast', 'struct', 'switch', 'template', 'this', + 'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename', 'union', + 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'xor', 'xor_eq']) + + @staticmethod + def checkMethodName(name): + # Double '_' because 'assert' and '_assert' cannot be used in MS2013 compiler. + # Bug 964892 and bug 963560. + if name in CppKeywords.keywords: + name = '_' + name + '_' + return name + + +class CGStaticMethod(CGAbstractStaticBindingMethod): + """ + A class for generating the C++ code for an IDL static method. + """ + def __init__(self, descriptor, method): + self.method = method + name = method.identifier.name + CGAbstractStaticBindingMethod.__init__(self, descriptor, name) + + def generate_code(self): + nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, + self.method) + return CGMethodCall(nativeName, True, self.descriptor, self.method) + + +class CGGenericGetter(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL attribute getter. + """ + def __init__(self, descriptor, lenientThis=False, allowCrossOriginThis=False): + args = [Argument('JSContext*', 'cx'), + Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + if lenientThis: + name = "genericLenientGetter" + unwrapFailureCode = dedent(""" + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + if (!ReportLenientThisUnwrappingFailure(cx, &args.callee())) { + return false; + } + args.rval().set(JS::UndefinedValue()); + return true; + """) + else: + if allowCrossOriginThis: + name = "genericCrossOriginGetter" + else: + name = "genericGetter" + unwrapFailureCode = ( + 'return ThrowInvalidThis(cx, args, GetInvalidThisErrorForGetter(%%(securityError)s), "%s");\n' % + descriptor.interface.identifier.name) + CGAbstractBindingMethod.__init__(self, descriptor, name, args, + unwrapFailureCode, + allowCrossOriginThis=allowCrossOriginThis) + + def generate_code(self): + return CGGeneric(indent(dedent(""" + const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); + MOZ_ASSERT(info->type() == JSJitInfo::Getter); + JSJitGetterOp getter = info->getter; + return getter(cx, obj, self, JSJitGetterCallArgs(args)); + """))) + + +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_' + attr.identifier.name + args = [ + Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'obj'), + Argument('%s*' % descriptor.nativeType, 'self'), + Argument('JSJitGetterCallArgs', 'args') + ] + CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) + + def definition_body(self): + nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, + self.attr) + if self.attr.slotIndex is not None: + if (self.descriptor.hasXPConnectImpls and + (self.descriptor.interface.identifier.name != 'Window' or + self.attr.identifier.name != 'document')): + raise TypeError("Interface '%s' has XPConnect impls, so we " + "can't use our slot for property '%s'!" % + (self.descriptor.interface.identifier.name, + self.attr.identifier.name)) + prefix = indent(fill( + """ + // Have to either root across the getter call or reget after. + JS::Rooted reflector(cx); + // Safe to do an unchecked unwrap, since we've gotten this far. + // Also make sure to unwrap outer windows, since we want the + // real DOM object. + reflector = IsDOMObject(obj) ? obj : js::UncheckedUnwrap(obj, /* stopAtOuter = */ false); + { + // Scope for cachedVal + JS::Value cachedVal = js::GetReservedSlot(reflector, ${slot}); + if (!cachedVal.isUndefined()) { + args.rval().set(cachedVal); + // The cached value is in the compartment of reflector, + // so wrap into the caller compartment as needed. + return ${maybeWrap}(cx, args.rval()); + } + } + + """, + slot=memberReservedSlot(self.attr), + maybeWrap=getMaybeWrapValueFuncForType(self.attr.type))) + else: + prefix = "" + + return (prefix + + indent(CGGetterCall(self.attr.type, nativeName, + self.descriptor, self.attr).define())) + + @staticmethod + def makeNativeName(descriptor, attr): + name = attr.identifier.name + nativeName = MakeNativeName(descriptor.binaryNames.get(name, name)) + # resultOutParam does not depend on whether resultAlreadyAddRefed is set + _, resultOutParam, _, _ = getRetvalDeclarationForType(attr.type, + descriptor, + False) + infallible = ('infallible' in + descriptor.getExtendedAttributes(attr, getter=True)) + if resultOutParam or attr.type.nullable() or not infallible: + nativeName = "Get" + nativeName + return nativeName + + +class CGStaticGetter(CGAbstractStaticBindingMethod): + """ + A class for generating the C++ code for an IDL static attribute getter. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'get_' + attr.identifier.name + CGAbstractStaticBindingMethod.__init__(self, descriptor, name) + + def generate_code(self): + nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, + self.attr) + return CGIndenter(CGGetterCall(self.attr.type, nativeName, + self.descriptor, self.attr)) + + +class CGGenericSetter(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL attribute setter. + """ + def __init__(self, descriptor, lenientThis=False, allowCrossOriginThis=False): + args = [Argument('JSContext*', 'cx'), + Argument('unsigned', 'argc'), + Argument('JS::Value*', 'vp')] + if lenientThis: + name = "genericLenientSetter" + unwrapFailureCode = dedent(""" + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + if (!ReportLenientThisUnwrappingFailure(cx, &args.callee())) { + return false; + } + args.rval().set(JS::UndefinedValue()); + return true; + """) + else: + if allowCrossOriginThis: + name = "genericCrossOriginSetter" + else: + name = "genericSetter" + unwrapFailureCode = ( + 'return ThrowInvalidThis(cx, args, GetInvalidThisErrorForSetter(%%(securityError)s), "%s");\n' % + descriptor.interface.identifier.name) + + CGAbstractBindingMethod.__init__(self, descriptor, name, args, + unwrapFailureCode, + allowCrossOriginThis=allowCrossOriginThis) + + def generate_code(self): + return CGGeneric(indent(fill( + """ + if (args.length() == 0) { + return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${name} attribute setter"); + } + const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev()); + MOZ_ASSERT(info->type() == JSJitInfo::Setter); + JSJitSetterOp setter = info->setter; + if (!setter(cx, obj, self, JSJitSetterCallArgs(args))) { + return false; + } + args.rval().set(JSVAL_VOID); + return true; + """, + name=self.descriptor.interface.identifier.name))) + + +class CGSpecializedSetter(CGAbstractStaticMethod): + """ + A class for generating the code for a specialized attribute setter + that the JIT can call with lower overhead. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'set_' + attr.identifier.name + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'obj'), + Argument('%s*' % descriptor.nativeType, 'self'), + Argument('JSJitSetterCallArgs', 'args')] + CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) + + def definition_body(self): + nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, + self.attr) + return CGIndenter(CGSetterCall(self.attr.type, nativeName, + self.descriptor, self.attr)).define() + + @staticmethod + def makeNativeName(descriptor, attr): + name = attr.identifier.name + return "Set" + MakeNativeName(descriptor.binaryNames.get(name, name)) + + +class CGStaticSetter(CGAbstractStaticBindingMethod): + """ + A class for generating the C++ code for an IDL static attribute setter. + """ + def __init__(self, descriptor, attr): + self.attr = attr + name = 'set_' + attr.identifier.name + CGAbstractStaticBindingMethod.__init__(self, descriptor, name) + + def generate_code(self): + nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, + self.attr) + checkForArg = CGGeneric(fill( + """ + if (args.length() == 0) { + return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${name} setter"); + } + """, + name=self.attr.identifier.name)) + call = CGSetterCall(self.attr.type, nativeName, self.descriptor, + self.attr) + return CGIndenter(CGList([checkForArg, call])) + + +class CGSpecializedForwardingSetter(CGSpecializedSetter): + """ + A class for generating the code for a specialized attribute setter with + PutForwards that the JIT can call with lower overhead. + """ + def __init__(self, descriptor, attr): + CGSpecializedSetter.__init__(self, descriptor, attr) + + def definition_body(self): + attrName = self.attr.identifier.name + forwardToAttrName = self.attr.getExtendedAttribute("PutForwards")[0] + # JS_GetProperty and JS_SetProperty can only deal with ASCII + assert all(ord(c) < 128 for c in attrName) + assert all(ord(c) < 128 for c in forwardToAttrName) + return indent(fill( + """ + JS::Rooted v(cx); + if (!JS_GetProperty(cx, obj, "${attr}", &v)) { + return false; + } + + if (!v.isObject()) { + return ThrowErrorMessage(cx, MSG_NOT_OBJECT, "${interface}.${attr}"); + } + + JS::Rooted targetObj(cx, &v.toObject()); + return JS_SetProperty(cx, targetObj, "${forwardToAttrName}", args[0]); + """, + attr=attrName, + interface=self.descriptor.interface.identifier.name, + forwardToAttrName=forwardToAttrName)) + + +class CGSpecializedReplaceableSetter(CGSpecializedSetter): + """ + A class for generating the code for a specialized attribute setter with + Replaceable that the JIT can call with lower overhead. + """ + def __init__(self, descriptor, attr): + CGSpecializedSetter.__init__(self, descriptor, attr) + + def definition_body(self): + attrName = self.attr.identifier.name + # JS_DefineProperty can only deal with ASCII + assert all(ord(c) < 128 for c in attrName) + return indent('return JS_DefineProperty(cx, obj, "%s", args[0], JSPROP_ENUMERATE);\n' % + attrName) + + +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, + aliasSet, hasSlot, 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 hasSlot or movable) # Things with slots had better be movable + + def jitInfoInitializer(isTypedMethod): + initializer = fill( + """ + { + { ${opName} }, + prototypes::id::${name}, + PrototypeTraits::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. */ + ${isInSlot}, /* isInSlot. Only relevant for getters. */ + ${isTypedMethod}, /* isTypedMethod. Only relevant for methods. */ + ${slotIndex} /* Reserved slot index, if we're stored in a slot, else 0. */ + } + """, + opName=opName, + name=self.descriptor.name, + opType=opType, + aliasSet=aliasSet, + returnType=reduce(CGMemberJITInfo.getSingleReturnType, returnTypes, + ""), + isInfallible=toStringBool(infallible), + isMovable=toStringBool(movable), + isInSlot=toStringBool(hasSlot), + isTypedMethod=toStringBool(isTypedMethod), + slotIndex=slotIndex) + return initializer.rstrip() + + 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} + }; + """, + argTypesDecl=argTypesDecl, + infoName=infoName, + jitInfo=jitInfoInitializer(True), + argTypes=argTypes) + + return ("\n" + "static const JSJitInfo %s = %s;\n" + % (infoName, jitInfoInitializer(False))) + + def define(self): + if self.member.isAttr(): + getterinfo = ("%s_getterinfo" % self.member.identifier.name) + # We need the cast here because JSJitGetterOp has a "void* self" + # while we have the right type. + getter = ("(JSJitGetterOp)get_%s" % self.member.identifier.name) + getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True) + getterconst = (self.member.getExtendedAttribute("SameObject") or + self.member.getExtendedAttribute("Constant")) + getterpure = getterconst or self.member.getExtendedAttribute("Pure") + if getterconst: + aliasSet = "AliasNone" + elif getterpure: + aliasSet = "AliasDOMSets" + else: + aliasSet = "AliasEverything" + movable = getterpure and getterinfal + + getterinfal = getterinfal and infallibleForMember(self.member, self.member.type, self.descriptor) + isInSlot = self.member.getExtendedAttribute("StoreInSlot") + if isInSlot: + slotIndex = memberReservedSlot(self.member) + # We'll statically assert that this is not too big in + # CGUpdateMemberSlotsMethod + else: + slotIndex = "0" + + result = self.defineJitInfo(getterinfo, getter, "Getter", + getterinfal, movable, aliasSet, + isInSlot, 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): + setterinfo = ("%s_setterinfo" % self.member.identifier.name) + # Actually a JSJitSetterOp, but JSJitGetterOp is first in the + # union. + setter = ("(JSJitGetterOp)set_%s" % self.member.identifier.name) + # Setters are always fallible, since they have to do a typed unwrap. + result += self.defineJitInfo(setterinfo, setter, "Setter", + False, False, "AliasEverything", + False, "0", + [BuiltinTypes[IDLBuiltinType.Types.void]], + None) + return result + if self.member.isMethod(): + methodinfo = ("%s_methodinfo" % self.member.identifier.name) + name = CppKeywords.checkMethodName(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) + methodPure = self.member.getExtendedAttribute("Pure") + + # 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 + else: + sig = sigs[0] + # For pure methods, it's OK to set movable to our notion of + # infallible on the C++ side, without considering argument + # conversions, since argument conversions that can reliably + # throw would be effectful anyway and the jit doesn't move + # effectful things. + hasInfallibleImpl = "infallible" in self.descriptor.getExtendedAttributes(self.member) + movable = methodPure and hasInfallibleImpl + # XXXbz can we move the smarts about fallibility due to arg + # conversions into the JIT, using our new args stuff? + if (len(sig[1]) != 0 or + not infallibleForMember(self.member, sig[0], self.descriptor)): + # We have arguments or our return-value boxing can fail + methodInfal = False + else: + methodInfal = hasInfallibleImpl + # For now, only bother to output args if we're pure + if methodPure: + args = sig[1] + else: + args = None + + if args is not None: + aliasSet = "AliasDOMSets" + else: + aliasSet = "AliasEverything" + result = self.defineJitInfo(methodinfo, method, "Method", + methodInfal, movable, aliasSet, False, "0", + [s[0] for s in sigs], args) + return result + raise TypeError("Illegal member type to CGPropertyJITInfo") + + @staticmethod + def getJSReturnTypeTag(t): + if t.nullable(): + # Sometimes it might return null, sometimes not + return "JSVAL_TYPE_UNKNOWN" + if t.isVoid(): + # No return, every time + return "JSVAL_TYPE_UNDEFINED" + if t.isArray(): + # No idea yet + assert False + if t.isSequence(): + return "JSVAL_TYPE_OBJECT" + if t.isMozMap(): + return "JSVAL_TYPE_OBJECT" + if t.isGeckoInterface(): + return "JSVAL_TYPE_OBJECT" + if t.isString(): + return "JSVAL_TYPE_STRING" + if t.isEnum(): + return "JSVAL_TYPE_STRING" + if t.isCallback(): + return "JSVAL_TYPE_OBJECT" + if t.isAny(): + # The whole point is to return various stuff + return "JSVAL_TYPE_UNKNOWN" + if t.isObject(): + return "JSVAL_TYPE_OBJECT" + if t.isSpiderMonkeyInterface(): + return "JSVAL_TYPE_OBJECT" + if t.isUnion(): + u = t.unroll() + if u.hasNullableType: + # Might be null or not + return "JSVAL_TYPE_UNKNOWN" + return reduce(CGMemberJITInfo.getSingleReturnType, + u.flatMemberTypes, "") + if t.isDictionary(): + return "JSVAL_TYPE_OBJECT" + if t.isDate(): + return "JSVAL_TYPE_OBJECT" + if not t.isPrimitive(): + raise TypeError("No idea what type " + str(t) + " is.") + tag = t.tag() + if tag == IDLType.Tags.bool: + return "JSVAL_TYPE_BOOLEAN" + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, + IDLType.Tags.int16, IDLType.Tags.uint16, + IDLType.Tags.int32]: + return "JSVAL_TYPE_INT32" + if tag in [IDLType.Tags.int64, IDLType.Tags.uint64, + IDLType.Tags.unrestricted_float, IDLType.Tags.float, + IDLType.Tags.unrestricted_double, IDLType.Tags.double]: + # These all use JS_NumberValue, which can return int or double. + # But TI treats "double" as meaning "int or double", so we're + # good to return JSVAL_TYPE_DOUBLE here. + return "JSVAL_TYPE_DOUBLE" + if tag != IDLType.Tags.uint32: + raise TypeError("No idea what type " + str(t) + " is.") + # uint32 is sometimes int and sometimes double. + return "JSVAL_TYPE_DOUBLE" + + @staticmethod + def getSingleReturnType(existingType, t): + type = CGMemberJITInfo.getJSReturnTypeTag(t) + if existingType == "": + # First element of the list; just return its type + return type + + if type == existingType: + return existingType + if ((type == "JSVAL_TYPE_DOUBLE" and + existingType == "JSVAL_TYPE_INT32") or + (existingType == "JSVAL_TYPE_DOUBLE" and + type == "JSVAL_TYPE_INT32")): + # Promote INT32 to DOUBLE as needed + return "JSVAL_TYPE_DOUBLE" + # Different types + return "JSVAL_TYPE_UNKNOWN" + + @staticmethod + def getJSArgType(t): + assert not t.isVoid() + if t.nullable(): + # Sometimes it might return null, sometimes not + return "JSJitInfo::ArgType(JSJitInfo::Null | %s)" % CGMemberJITInfo.getJSArgType(t.inner) + if t.isArray(): + # No idea yet + assert False + if t.isSequence(): + return "JSJitInfo::Object" + if t.isGeckoInterface(): + return "JSJitInfo::Object" + if t.isString(): + return "JSJitInfo::String" + if t.isEnum(): + return "JSJitInfo::String" + if t.isCallback(): + return "JSJitInfo::Object" + if t.isAny(): + # The whole point is to return various stuff + return "JSJitInfo::Any" + if t.isObject(): + return "JSJitInfo::Object" + if t.isSpiderMonkeyInterface(): + return "JSJitInfo::Object" + if t.isUnion(): + u = t.unroll() + type = "JSJitInfo::Null" if u.hasNullableType else "" + return ("JSJitInfo::ArgType(%s)" % + reduce(CGMemberJITInfo.getSingleArgType, + u.flatMemberTypes, type)) + if t.isDictionary(): + return "JSJitInfo::Object" + if t.isDate(): + return "JSJitInfo::Object" + if not t.isPrimitive(): + raise TypeError("No idea what type " + str(t) + " is.") + tag = t.tag() + if tag == IDLType.Tags.bool: + return "JSJitInfo::Boolean" + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, + IDLType.Tags.int16, IDLType.Tags.uint16, + IDLType.Tags.int32]: + return "JSJitInfo::Integer" + if tag in [IDLType.Tags.int64, IDLType.Tags.uint64, + IDLType.Tags.unrestricted_float, IDLType.Tags.float, + IDLType.Tags.unrestricted_double, IDLType.Tags.double]: + # These all use JS_NumberValue, which can return int or double. + # But TI treats "double" as meaning "int or double", so we're + # good to return JSVAL_TYPE_DOUBLE here. + return "JSJitInfo::Double" + if tag != IDLType.Tags.uint32: + raise TypeError("No idea what type " + str(t) + " is.") + # uint32 is sometimes int and sometimes double. + return "JSJitInfo::Double" + + @staticmethod + def getSingleArgType(existingType, t): + type = CGMemberJITInfo.getJSArgType(t) + if existingType == "": + # First element of the list; just return its type + return type + + if type == existingType: + return existingType + return "%s | %s" % (existingType, type) + + +class CGStaticMethodJitinfo(CGGeneric): + """ + A class for generating the JITInfo for a promise-returning static method. + """ + def __init__(self, method): + CGGeneric.__init__( + self, + "\n" + "static const JSJitInfo %s_methodinfo = {\n" + " { (JSJitGetterOp)%s },\n" + " prototypes::id::_ID_Count, 0, JSJitInfo::StaticMethod,\n" + " JSJitInfo::AliasEverything, JSVAL_TYPE_MISSING, false, false,\n" + " false, false, 0\n" + "};\n" % + (method.identifier.name, method.identifier.name)) + + +def getEnumValueName(value): + # Some enum values can be empty strings. Others might have weird + # characters in them. Deal with the former by returning "_empty", + # deal with possible name collisions from that by throwing if the + # enum value is actually "_empty", and throw on any value + # containing non-ASCII chars for now. Replace all chars other than + # [0-9A-Za-z_] with '_'. + if re.match("[^\x20-\x7E]", value): + raise SyntaxError('Enum value "' + value + '" contains non-ASCII characters') + if re.match("^[0-9]", value): + return '_' + value + value = re.sub(r'[^0-9A-Za-z_]', '_', value) + if re.match("^_[A-Z]|__", value): + raise SyntaxError('Enum value "' + value + '" is reserved by the C++ spec') + if value == "_empty": + raise SyntaxError('"_empty" is not an IDL enum value we support yet') + if value == "": + return "_empty" + return MakeNativeName(value) + + +class CGEnum(CGThing): + def __init__(self, enum): + CGThing.__init__(self) + self.enum = enum + + def stringsNamespace(self): + return self.enum.identifier.name + "Values" + + def nEnumStrings(self): + return len(self.enum.values()) + 1 + + def declare(self): + decl = fill( # BOGUS extra newline at top + """ + + MOZ_BEGIN_ENUM_CLASS(${name}, uint32_t) + $*{enums} + MOZ_END_ENUM_CLASS(${name}) + """, + name=self.enum.identifier.name, + enums=",\n".join(map(getEnumValueName, self.enum.values())) + "\n") + strings = CGNamespace(self.stringsNamespace(), + CGGeneric(declare="extern const EnumEntry %s[%d];\n" + % (ENUM_ENTRY_VARIABLE_NAME, self.nEnumStrings()))) + return decl + "\n" + strings.declare() + + def define(self): + strings = fill( # BOGUS extra newline at top + """ + + 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())) + # BOGUS - CGNamespace automatically indents; the extra indent() below causes + # the output to be indented 4 spaces. + return CGNamespace(self.stringsNamespace(), + CGGeneric(define=indent(strings))).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 + """ + if type.isArray(): + raise TypeError("Can't handle array arguments yet") + + if type.isSequence(): + nullable = type.nullable() + if nullable: + type = type.inner.inner + else: + type = type.inner + # We don't use the returned template here, so it's OK to just pass no + # sourceDescription. + elementInfo = getJSToNativeConversionInfo(type, descriptorProvider, + isMember="Sequence") + typeName = CGTemplatedType("Sequence", elementInfo.declType, + isReference=True) + if nullable: + typeName = CGTemplatedType("Nullable", typeName, isReference=True) + + return typeName + + if type.isUnion(): + typeName = CGGeneric(type.name) + if type.nullable(): + typeName = CGTemplatedType("Nullable", typeName, isReference=True) + + return typeName + + if type.isGeckoInterface(): + descriptor = descriptorProvider.getDescriptor( + type.unroll().inner.identifier.name) + typeName = CGGeneric(descriptor.nativeType) + # Allow null pointers for nullable types and old-binding classes + if type.nullable() or type.unroll().inner.isExternal(): + typeName = CGWrapper(typeName, post="*") + else: + typeName = CGWrapper(typeName, post="&") + return typeName + + if type.isSpiderMonkeyInterface(): + typeName = CGGeneric(type.name) + if type.nullable(): + typeName = CGTemplatedType("Nullable", typeName) + return CGWrapper(typeName, post="&") + + if type.isDOMString(): + return CGGeneric("const nsAString&") + + if type.isByteString(): + return CGGeneric("const nsCString&") + + if type.isEnum(): + if type.nullable(): + raise TypeError("We don't support nullable enumerated arguments or " + "union members yet") + return CGGeneric(type.inner.identifier.name) + + if type.isCallback(): + if type.nullable(): + typeName = "%s*" + else: + typeName = "%s&" + return CGGeneric(typeName % type.unroll().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)) + + typeName = CGGeneric(builtinNames[type.tag()]) + if type.nullable(): + typeName = CGTemplatedType("Nullable", typeName, isReference=True) + return typeName + + +def getUnionTypeTemplateVars(unionType, type, descriptorProvider, + ownsMembers=False): + # For dictionaries and sequences we need to pass None as the failureCode + # for getJSToNativeConversionInfo. + # Also, for dictionaries we would need to handle conversion of + # null/undefined to the dictionary correctly. + if type.isSequence(): + raise TypeError("Can't handle sequences in unions") + + name = getUnionMemberName(type) + + # By the time tryNextCode is invoked, we're guaranteed the union has been + # constructed as some type, since we've been trying to convert into the + # corresponding member. + prefix = "" if ownsMembers else "mUnion." + tryNextCode = ("%sDestroy%s();\n" + "tryNext = true;\n" + "return true;\n" % (prefix, name)) + conversionInfo = getJSToNativeConversionInfo( + type, descriptorProvider, failureCode=tryNextCode, + isDefinitelyObject=not type.isDictionary(), + isMember=("OwningUnion" if ownsMembers else None), + sourceDescription="member of %s" % unionType) + + ctorNeedsCx = conversionInfo.declArgs == "cx" + ctorArgs = "cx" if ctorNeedsCx else "" + + # This is ugly, but UnionMember needs to call a constructor with no + # arguments so the type can't be const. + structType = conversionInfo.declType.define() + if structType.startswith("const "): + structType = structType[6:] + externalType = getUnionAccessorSignatureType(type, descriptorProvider).define() + + if type.isObject(): + if ownsMembers: + body = dedent(""" + MOZ_ASSERT(mType == eUninitialized); + mValue.mObject.SetValue(obj); + mType = eObject; + """) + else: + body = dedent(""" + MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized); + mUnion.mValue.mObject.SetValue(cx, obj); + mUnion.mType = mUnion.eObject; + """) + setter = ClassMethod("SetToObject", "void", + [Argument("JSContext*", "cx"), + Argument("JSObject*", "obj")], + inline=True, bodyInHeader=True, + body=body) + + else: + # Important: we need to not have our declName involve + # maybe-GCing operations. + jsConversion = string.Template(conversionInfo.template).substitute({ + "val": "value", + "mutableVal": "pvalue", + "declName": "memberSlot", + "holderName": "m" + name + "Holder", + }) + jsConversion = fill( + """ + tryNext = false; + { // scope for memberSlot + ${structType}& memberSlot = RawSetAs${name}(${ctorArgs}); + $*{jsConversion} + } + return true; + """, + structType=structType, + name=name, + ctorArgs=ctorArgs, + jsConversion=jsConversion) + + setter = ClassMethod("TrySetTo" + name, "bool", + [Argument("JSContext*", "cx"), + Argument("JS::Handle", "value"), + Argument("JS::MutableHandle", "pvalue"), + Argument("bool&", "tryNext")], + inline=not ownsMembers, + bodyInHeader=not ownsMembers, + body=jsConversion) + + return { + "name": name, + "structType": structType, + "externalType": externalType, + "setter": setter, + "holderType": conversionInfo.holderType.define() if conversionInfo.holderType else None, + "ctorArgs": ctorArgs, + "ctorArgList": [Argument("JSContext*", "cx")] if ctorNeedsCx else [] + } + + +class CGUnionStruct(CGThing): + def __init__(self, type, descriptorProvider, ownsMembers=False): + CGThing.__init__(self) + self.type = type.unroll() + self.descriptorProvider = descriptorProvider + self.ownsMembers = ownsMembers + self.struct = self.getStruct() + + def declare(self): + return self.struct.declare() + + def define(self): + return self.struct.define() + + def getStruct(self): + + members = [ClassMember("mType", "Type", body="eUninitialized"), + ClassMember("mValue", "Value")] + ctor = ClassConstructor([], bodyInHeader=True, visibility="public", + explicit=True) + + methods = [] + enumValues = ["eUninitialized"] + toJSValCases = [CGCase("eUninitialized", CGGeneric("return false;\n"))] + destructorCases = [CGCase("eUninitialized", None)] + assignmentCases = [ + CGCase("eUninitialized", + CGGeneric('MOZ_ASSERT(mType == eUninitialized,\n' + ' "We need to destroy ourselves?");\n'))] + traceCases = [] + unionValues = [] + if self.type.hasNullableType: + enumValues.append("eNull") + methods.append(ClassMethod("IsNull", "bool", [], const=True, inline=True, + body="return mType == eNull;\n", + bodyInHeader=True)) + methods.append(ClassMethod("SetNull", "void", [], inline=True, + body=("Uninit();\n" + "mType = eNull;\n"), + bodyInHeader=True)) + destructorCases.append(CGCase("eNull", None)) + assignmentCases.append(CGCase("eNull", + CGGeneric("MOZ_ASSERT(mType == eUninitialized);\n" + "mType = eNull;\n"))) + toJSValCases.append(CGCase("eNull", CGGeneric("rval.setNull();\n" + "return true;\n"))) + + hasObjectType = any(t.isObject() for t in self.type.flatMemberTypes) + for t in self.type.flatMemberTypes: + vars = getUnionTypeTemplateVars(self.type, + t, self.descriptorProvider, + ownsMembers=self.ownsMembers) + if vars["name"] != "Object" or self.ownsMembers: + body = fill( + """ + if (mType == e${name}) { + return mValue.m${name}.Value(); + } + %s + mType = e${name}; + return mValue.m${name}.SetValue(${ctorArgs}); + """, + **vars) + + # bodyInHeader must be false for return values because they own + # their union members and we don't want include headers in + # UnionTypes.h just to call Addref/Release + methods.append(ClassMethod( + "RawSetAs" + vars["name"], + vars["structType"] + "&", + vars["ctorArgList"], + bodyInHeader=not self.ownsMembers, + body=body % "MOZ_ASSERT(mType == eUninitialized);")) + uninit = "Uninit();" + if hasObjectType and not self.ownsMembers: + uninit = 'MOZ_ASSERT(mType != eObject, "This will not play well with Rooted");\n' + uninit + methods.append(ClassMethod( + "SetAs" + vars["name"], + vars["structType"] + "&", + vars["ctorArgList"], + bodyInHeader=not self.ownsMembers, + body=body % uninit)) + if self.ownsMembers: + methods.append(vars["setter"]) + if t.isString(): + methods.append( + ClassMethod("SetStringData", "void", + [Argument("const nsString::char_type*", "aData"), + Argument("nsString::size_type", "aLength")], + inline=True, bodyInHeader=True, + body="RawSetAsString().Assign(aData, aLength);\n")) + + body = fill( + """ + MOZ_ASSERT(Is${name}(), "Wrong type!"); + mValue.m${name}.Destroy(); + mType = eUninitialized; + """, + **vars) + methods.append(ClassMethod("Destroy" + vars["name"], + "void", + [], + visibility="private", + bodyInHeader=not self.ownsMembers, + body=body)) + + body = fill("return mType == e${name};\n", **vars) + methods.append(ClassMethod("Is" + vars["name"], + "bool", + [], + const=True, + bodyInHeader=True, + body=body)) + + body = fill( + """ + MOZ_ASSERT(Is${name}(), "Wrong type!"); + return const_cast<${structType}&>(mValue.m${name}.Value()); + """, + **vars) + if self.ownsMembers: + getterReturnType = "%s&" % vars["structType"] + else: + getterReturnType = vars["externalType"] + methods.append(ClassMethod("GetAs" + vars["name"], + getterReturnType, + [], + const=True, + bodyInHeader=True, + body=body)) + + unionValues.append( + fill("UnionMember<${structType} > m${name}", **vars)) + enumValues.append("e" + vars["name"]) + + toJSValCases.append( + CGCase("e" + vars["name"], + self.getConversionToJS(vars, t))) + destructorCases.append( + CGCase("e" + vars["name"], + CGGeneric("Destroy%s();\n" % vars["name"]))) + assignmentCases.append( + CGCase("e" + vars["name"], + CGGeneric("SetAs%s() = aOther.GetAs%s();\n" % + (vars["name"], vars["name"])))) + if self.ownsMembers and typeNeedsRooting(t): + if t.isObject(): + traceCases.append( + CGCase("e" + vars["name"], + CGGeneric('JS_CallObjectTracer(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"]))) + else: + assert t.isSpiderMonkeyInterface() + traceCases.append( + CGCase("e" + vars["name"], + CGGeneric("mValue.m%s.Value().TraceSelf(trc);\n" % + vars["name"]))) + + dtor = CGSwitch("mType", destructorCases).define() + + methods.append(ClassMethod("Uninit", "void", [], + visibility="private", body=dtor, + bodyInHeader=not self.ownsMembers, + inline=not self.ownsMembers)) + + methods.append( + ClassMethod( + "ToJSVal", + "bool", + [ + Argument("JSContext*", "cx"), + Argument("JS::Handle", "scopeObj"), + Argument("JS::MutableHandle", "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: + # BOGUS blank line in default case + traceBody = CGSwitch("mType", traceCases, + default=CGGeneric("\n")).define() + else: + # BOGUS blank line in method + traceBody = "\n" + methods.append(ClassMethod("TraceUnion", "void", + [Argument("JSTracer*", "trc")], + body=traceBody)) + if CGUnionStruct.isUnionCopyConstructible(self.type): + constructors.append( + ClassConstructor( + [Argument("const %s&" % selfName, "aOther")], + bodyInHeader=True, + visibility="public", + explicit=True, + body="*this = aOther;\n")) + methods.append(ClassMethod( + "operator=", "void", + [Argument("const %s&" % selfName, "aOther")], + body=CGSwitch("aOther.mType", assignmentCases).define())) + disallowCopyConstruction = False + else: + disallowCopyConstruction = True + else: + disallowCopyConstruction = True + + friend = " friend class %sArgument;\n" % str(self.type) if not self.ownsMembers else "" + bases = [ClassBase("AllOwningUnionBase")] if self.ownsMembers else [] + return CGClass(selfName, + bases=bases, + members=members, + constructors=constructors, + methods=methods, + disallowCopyConstruction=disallowCopyConstruction, + extradeclarations=friend, + destructor=ClassDestructor(visibility="public", + body="Uninit();", + bodyInHeader=True), + enums=[ClassEnum("Type", enumValues, visibility="private")], + unions=[ClassUnion("Value", unionValues, visibility="private")]) + + def getConversionToJS(self, templateVars, type): + assert not type.nullable() # flatMemberTypes never has nullable types + val = "mValue.m%(name)s.Value()" % templateVars + wrapCode = wrapForType( + type, self.descriptorProvider, + { + "jsvalRef": "rval", + "jsvalHandle": "rval", + "obj": "scopeObj", + "result": val, + "typedArraysAreStructs": True + }) + return CGGeneric(wrapCode) + + @staticmethod + def isUnionCopyConstructible(type): + return all(isTypeCopyConstructible(t) for t in type.flatMemberTypes) + + @staticmethod + def unionTypeName(type, ownsMembers): + """ + Returns a string name for this known union type. + """ + assert type.isUnion() and not type.nullable() + return ("Owning" if ownsMembers else "") + type.name + + @staticmethod + def unionTypeDecl(type, ownsMembers): + """ + Returns a string for declaring this possibly-nullable union type. + """ + assert type.isUnion() + nullable = type.nullable() + if nullable: + type = type.inner + decl = CGGeneric(CGUnionStruct.unionTypeName(type, ownsMembers)) + if nullable: + decl = CGTemplatedType("Nullable", decl) + return decl.define() + + +class CGUnionConversionStruct(CGThing): + def __init__(self, type, descriptorProvider): + CGThing.__init__(self) + self.type = type.unroll() + self.descriptorProvider = descriptorProvider + + def declare(self): + + structName = str(self.type) + members = [ClassMember("mUnion", structName + "&", + body="const_cast<%s&>(aUnion)" % structName)] + # Argument needs to be a const ref because that's all Maybe<> allows + ctor = ClassConstructor([Argument("const %s&" % structName, "aUnion")], + bodyInHeader=True, + visibility="public", + explicit=True) + methods = [] + + if self.type.hasNullableType: + methods.append(ClassMethod("SetNull", "bool", [], + body=("MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized);\n" + "mUnion.mType = mUnion.eNull;\n" + "return true;\n"), + inline=True, bodyInHeader=True)) + + for t in self.type.flatMemberTypes: + vars = getUnionTypeTemplateVars(self.type, + t, self.descriptorProvider) + methods.append(vars["setter"]) + if vars["name"] != "Object": + body = fill( + """ + MOZ_ASSERT(mUnion.mType == mUnion.eUninitialized); + mUnion.mType = mUnion.e${name}; + return mUnion.mValue.m${name}.SetValue(${ctorArgs}); + """, + **vars) + methods.append(ClassMethod("RawSetAs" + vars["name"], + vars["structType"] + "&", + vars["ctorArgList"], + bodyInHeader=True, + body=body, + visibility="private")) + if t.isString(): + methods.append( + ClassMethod("SetStringData", "void", + [Argument("const nsDependentString::char_type*", "aData"), + Argument("nsDependentString::size_type", "aLength")], + inline=True, bodyInHeader=True, + body="RawSetAsString().SetData(aData, aLength);\n")) + + if vars["holderType"] is not None: + members.append(ClassMember("m%sHolder" % vars["name"], + vars["holderType"])) + + return CGClass(structName + "Argument", + members=members, + constructors=[ctor], + methods=methods, + disallowCopyConstruction=True).declare() + + def define(self): + return "\n" + + +class ClassItem: + """ Use with CGClass """ + def __init__(self, name, visibility): + self.name = name + self.visibility = visibility + + def declare(self, cgClass): + assert False + + def define(self, cgClass): + assert False + + +class ClassBase(ClassItem): + def __init__(self, name, visibility='public'): + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return '%s %s' % (self.visibility, self.name) + + def define(self, cgClass): + # Only in the header + return '' + + +class ClassMethod(ClassItem): + def __init__(self, name, returnType, args, inline=False, static=False, + virtual=False, const=False, bodyInHeader=False, + templateArgs=None, visibility='public', body=None, + breakAfterReturnDecl="\n", + breakAfterSelf="\n", override=False): + """ + override indicates whether to flag the method as MOZ_OVERRIDE + """ + assert not override or virtual + assert not (override and static) + self.returnType = returnType + self.args = args + self.inline = inline or bodyInHeader + self.static = static + self.virtual = virtual + self.const = const + self.bodyInHeader = bodyInHeader + self.templateArgs = templateArgs + self.body = body + self.breakAfterReturnDecl = breakAfterReturnDecl + self.breakAfterSelf = breakAfterSelf + self.override = override + ClassItem.__init__(self, name, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.inline: + decorators.append('inline') + if declaring: + if self.static: + decorators.append('static') + if self.virtual: + decorators.append('virtual') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getBody(self): + # Override me or pass a string to constructor + assert self.body is not None + return self.body + + def declare(self, cgClass): + templateClause = ('template <%s>\n' % ', '.join(self.templateArgs) + if self.bodyInHeader and self.templateArgs else '') + args = ', '.join([a.declare() for a in self.args]) + if self.bodyInHeader: + body = indent(self.getBody()) + body = '\n{\n' + body + '}\n' + else: + body = ';\n' + + return fill( + "${templateClause}${decorators}${returnType}${breakAfterReturnDecl}" + "${name}(${args})${const}${override}${body}" + "${breakAfterSelf}", + templateClause=templateClause, + decorators=self.getDecorators(True), + returnType=self.returnType, + breakAfterReturnDecl=self.breakAfterReturnDecl, + name=self.name, + args=args, + const=' const' if self.const else '', + override=' MOZ_OVERRIDE' if self.override else '', + body=body, + breakAfterSelf=self.breakAfterSelf) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + templateArgs = cgClass.templateArgs + if templateArgs: + if cgClass.templateSpecialization: + templateArgs = \ + templateArgs[len(cgClass.templateSpecialization):] + + if templateArgs: + templateClause = \ + 'template <%s>\n' % ', '.join([str(a) for a in templateArgs]) + else: + templateClause = '' + + return fill( + """ + ${templateClause}${decorators}${returnType} + ${className}::${name}(${args})${const} + { + $*{body} + } + """, + templateClause=templateClause, + decorators=self.getDecorators(False), + returnType=self.returnType, + className=cgClass.getNameString(), + name=self.name, + args=', '.join([a.define() for a in self.args]), + const=' const' if self.const else '', + body=self.getBody()) + + +class ClassUsingDeclaration(ClassItem): + """ + Used for importing a name from a base class into a CGClass + + baseClass is the name of the base class to import the name from + + name is the name to import + + visibility determines the visibility of the name (public, + protected, private), defaults to public. + """ + def __init__(self, baseClass, name, visibility='public'): + self.baseClass = baseClass + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return "using %s::%s;\n\n" % (self.baseClass, self.name) + + def define(self, cgClass): + return '' + + +class ClassConstructor(ClassItem): + """ + Used for adding a constructor to a CGClass. + + args is a list of Argument objects that are the arguments taken by the + constructor. + + inline should be True if the constructor should be marked inline. + + bodyInHeader should be True if the body should be placed in the class + declaration in the header. + + visibility determines the visibility of the constructor (public, + protected, private), defaults to private. + + explicit should be True if the constructor should be marked explicit. + + baseConstructors is a list of strings containing calls to base constructors, + defaults to None. + + body contains a string with the code for the constructor, defaults to empty. + """ + def __init__(self, args, inline=False, bodyInHeader=False, + visibility="private", explicit=False, baseConstructors=None, + body=""): + self.args = args + self.inline = inline or bodyInHeader + self.bodyInHeader = bodyInHeader + self.explicit = explicit + self.baseConstructors = baseConstructors or [] + self.body = body + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.explicit: + decorators.append('explicit') + if self.inline and declaring: + decorators.append('inline') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getInitializationList(self, cgClass): + items = [str(c) for c in self.baseConstructors] + for m in cgClass.members: + if not m.static: + initialize = m.body + if initialize: + items.append(m.name + "(" + initialize + ")") + + if len(items) > 0: + return '\n : ' + ',\n '.join(items) + return '' + + def getBody(self): + return self.body + + def declare(self, cgClass): + args = ', '.join([a.declare() for a in self.args]) + if self.bodyInHeader: + body = self.getInitializationList(cgClass) + '\n{\n' + indent(self.getBody()) + '}\n' + else: + body = ';\n' + + return fill( + "${decorators}${className}(${args})${body}\n", + decorators=self.getDecorators(True), + className=cgClass.getNameString(), + args=args, + body=body) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + return fill( + """ + ${decorators} + ${className}::${className}(${args})${initializationList} + { + $*{body} + } + """, + decorators=self.getDecorators(False), + className=cgClass.getNameString(), + args=', '.join([a.define() for a in self.args]), + initializationList=self.getInitializationList(cgClass), + body=self.getBody()) + + +class ClassDestructor(ClassItem): + """ + Used for adding a destructor to a CGClass. + + inline should be True if the destructor should be marked inline. + + bodyInHeader should be True if the body should be placed in the class + declaration in the header. + + visibility determines the visibility of the destructor (public, + protected, private), defaults to private. + + body contains a string with the code for the destructor, defaults to empty. + + virtual determines whether the destructor is virtual, defaults to False. + """ + def __init__(self, inline=False, bodyInHeader=False, + visibility="private", body='', virtual=False): + self.inline = inline or bodyInHeader + self.bodyInHeader = bodyInHeader + self.body = body + self.virtual = virtual + ClassItem.__init__(self, None, visibility) + + def getDecorators(self, declaring): + decorators = [] + if self.virtual and declaring: + decorators.append('virtual') + if self.inline and declaring: + decorators.append('inline') + if decorators: + return ' '.join(decorators) + ' ' + return '' + + def getBody(self): + return self.body + + def declare(self, cgClass): + if self.bodyInHeader: + body = '\n{\n' + indent(self.getBody()) + '}\n' + else: + body = ';\n' + + return fill( + "${decorators}~${className}()${body}\n", + decorators=self.getDecorators(True), + className=cgClass.getNameString(), + body=body) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + return fill( + """ + ${decorators} + ${className}::~${className}() + { + $*{body} + } + """, + decorators=self.getDecorators(False), + className=cgClass.getNameString(), + body=self.getBody() or "\n") # BOGUS extra blank line if empty + + +class ClassMember(ClassItem): + def __init__(self, name, type, visibility="private", static=False, + body=None): + self.type = type + self.static = static + self.body = body + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return '%s%s %s;\n' % ('static ' if self.static else '', self.type, + self.name) + + def define(self, cgClass): + if not self.static: + return '' + if self.body: + body = " = " + self.body + else: + body = "" + return '%s %s::%s%s;\n' % (self.type, cgClass.getNameString(), + self.name, body) + + +class ClassTypedef(ClassItem): + def __init__(self, name, type, visibility="public"): + self.type = type + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return 'typedef %s %s;\n' % (self.type, self.name) + + def define(self, cgClass): + # Only goes in the header + return '' + + +class ClassEnum(ClassItem): + def __init__(self, name, entries, values=None, visibility="public"): + self.entries = entries + self.values = values + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + entries = [] + for i in range(0, len(self.entries)): + if not self.values or i >= len(self.values): + entry = '%s' % self.entries[i] + else: + entry = '%s = %s' % (self.entries[i], self.values[i]) + entries.append(entry) + name = '' if not self.name else ' ' + self.name + return 'enum%s\n{\n%s\n};\n' % (name, indent(',\n'.join(entries))) + + def define(self, cgClass): + # Only goes in the header + return '' + + +class ClassUnion(ClassItem): + def __init__(self, name, entries, visibility="public"): + self.entries = [entry + ";\n" for entry in entries] + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return "union %s\n{\n%s\n};\n" % (self.name, indent(''.join(self.entries))) + + def define(self, cgClass): + # Only goes in the header + return '' + + +class CGClass(CGThing): + def __init__(self, name, bases=[], members=[], constructors=[], + destructor=None, methods=[], + typedefs=[], enums=[], unions=[], templateArgs=[], + templateSpecialization=[], isStruct=False, + disallowCopyConstruction=False, indent='', + decorators='', + extradeclarations='', + extradefinitions=''): + CGThing.__init__(self) + self.name = name + self.bases = bases + self.members = members + self.constructors = constructors + # We store our single destructor in a list, since all of our + # code wants lists of members. + self.destructors = [destructor] if destructor else [] + self.methods = methods + self.typedefs = typedefs + self.enums = enums + self.unions = unions + self.templateArgs = templateArgs + self.templateSpecialization = templateSpecialization + self.isStruct = isStruct + self.disallowCopyConstruction = disallowCopyConstruction + self.indent = indent + self.defaultVisibility = 'public' if isStruct else 'private' + self.decorators = decorators + self.extradeclarations = extradeclarations + self.extradefinitions = extradefinitions + + def getNameString(self): + className = self.name + if self.templateSpecialization: + className += '<%s>' % ', '.join([str(a) + for a in self.templateSpecialization]) + return className + + def declare(self): + result = '' + if self.templateArgs: + templateArgs = [a.declare() for a in self.templateArgs] + templateArgs = templateArgs[len(self.templateSpecialization):] + result += ('template <%s>\n' % + ','.join([str(a) for a in templateArgs])) + + type = 'struct' if self.isStruct else 'class' + + if self.templateSpecialization: + specialization = \ + '<%s>' % ', '.join([str(a) for a in self.templateSpecialization]) + else: + specialization = '' + + myself = '%s %s%s' % (type, self.name, specialization) + if self.decorators != '': + myself += " " + self.decorators + result += myself + + if self.bases: + inherit = ' : ' + result += inherit + # Grab our first base + baseItems = [CGGeneric(b.declare(self)) for b in self.bases] + bases = baseItems[:1] + # Indent the rest + bases.extend(CGIndenter(b, len(myself) + len(inherit)) + for b in baseItems[1:]) + result += ",\n".join(b.define() for b in bases) + + result += '\n{\n' + + result += self.extradeclarations + + def declareMembers(cgClass, memberList, defaultVisibility): + members = {'private': [], 'protected': [], 'public': []} + + for member in memberList: + members[member.visibility].append(member) + + if defaultVisibility == 'public': + order = ['public', 'protected', 'private'] + else: + order = ['private', 'protected', 'public'] + + result = '' + + lastVisibility = defaultVisibility + for visibility in order: + list = members[visibility] + if list: + if visibility != lastVisibility: + result += visibility + ':\n' + for member in list: + result += indent(member.declare(cgClass)) + lastVisibility = visibility + return (result, lastVisibility) + + if self.disallowCopyConstruction: + class DisallowedCopyConstructor(object): + def __init__(self): + self.visibility = "private" + + def declare(self, cgClass): + name = cgClass.getNameString() + return ("%s(const %s&) MOZ_DELETE;\n" + "void operator=(const %s) MOZ_DELETE;\n" % (name, name, name)) + + disallowedCopyConstructors = [DisallowedCopyConstructor()] + else: + disallowedCopyConstructors = [] + + order = [self.enums, self.unions, + self.typedefs, self.members, + self.constructors + disallowedCopyConstructors, + self.destructors, self.methods] + + lastVisibility = self.defaultVisibility + pieces = [] + for memberList in order: + code, lastVisibility = declareMembers(self, memberList, lastVisibility) + + if code: + code = code.rstrip() + "\n" # remove extra blank lines at the end + pieces.append(code) + + result += '\n'.join(pieces) + result += '};\n' + result = indent(result, len(self.indent)) + return result + + def define(self): + def defineMembers(cgClass, memberList, itemCount, separator=''): + result = '' + for member in memberList: + if itemCount != 0: + result = result + separator + definition = member.define(cgClass) + if definition: + # Member variables would only produce empty lines here. + result += definition + itemCount += 1 + return (result, itemCount) + + order = [(self.members, ''), (self.constructors, '\n'), + (self.destructors, '\n'), (self.methods, '\n')] + + result = self.extradefinitions + itemCount = 0 + for memberList, separator in order: + memberString, itemCount = defineMembers(self, memberList, + itemCount, separator) + result = result + memberString + return result + + +class CGResolveOwnProperty(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'wrapper'), + Argument('JS::Handle', 'obj'), + Argument('JS::Handle', 'id'), + Argument('JS::MutableHandle', 'desc'), + ] + CGAbstractStaticMethod.__init__(self, descriptor, "ResolveOwnProperty", + "bool", args) + + def definition_body(self): + # BOGUS extra blank line at end of function + return " return js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, wrapper, id, desc);\n\n" + + +class CGResolveOwnPropertyViaNewresolve(CGAbstractBindingMethod): + """ + An implementation of Xray ResolveOwnProperty stuff for things that have a + newresolve hook. + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'wrapper'), + Argument('JS::Handle', 'obj'), + Argument('JS::Handle', 'id'), + Argument('JS::MutableHandle', 'desc')] + CGAbstractBindingMethod.__init__(self, descriptor, + "ResolveOwnPropertyViaNewresolve", + args, getThisObj="", + callArgs="") + + def generate_code(self): + return CGGeneric(indent(dedent(""" + { + // Since we're dealing with an Xray, do the resolve on the + // underlying object first. That gives it a chance to + // define properties on the actual object as needed, and + // then use the fact that it created the objects as a flag + // to avoid re-resolving the properties if someone deletes + // them. + JSAutoCompartment ac(cx, obj); + JS::Rooted objDesc(cx); + if (!self->DoNewResolve(cx, obj, id, &objDesc)) { + return false; + } + // If desc.value() is undefined, then the DoNewResolve call + // has already defined the property on the object. Don't + // try to also define it. + if (objDesc.object() && + !objDesc.value().isUndefined() && + !JS_DefinePropertyById(cx, obj, id, objDesc.value(), + objDesc.getter(), objDesc.setter(), + objDesc.attributes())) { + return false; + } + } + return self->DoNewResolve(cx, wrapper, id, desc); + """))) + + +class CGEnumerateOwnProperties(CGAbstractStaticMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'wrapper'), + Argument('JS::Handle', 'obj'), + Argument('JS::AutoIdVector&', 'props')] + CGAbstractStaticMethod.__init__(self, descriptor, + "EnumerateOwnProperties", "bool", args) + + def definition_body(self): + # BOGUS extra newline + return " return js::GetProxyHandler(obj)->getOwnPropertyNames(cx, wrapper, props);\n\n" + + +class CGEnumerateOwnPropertiesViaGetOwnPropertyNames(CGAbstractBindingMethod): + """ + An implementation of Xray EnumerateOwnProperties stuff for things + that have a newresolve hook. + """ + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'wrapper'), + Argument('JS::Handle', 'obj'), + Argument('JS::AutoIdVector&', 'props')] + CGAbstractBindingMethod.__init__(self, descriptor, + "EnumerateOwnPropertiesViaGetOwnPropertyNames", + args, getThisObj="", + callArgs="") + + def generate_code(self): + return CGIndenter(CGGeneric(dedent(""" + nsAutoTArray names; + ErrorResult rv; + self->GetOwnPropertyNames(cx, names, rv); + rv.WouldReportJSException(); + if (rv.Failed()) { + return ThrowMethodFailedWithDetails(cx, rv, "%s", "enumerate"); + } + // OK to pass null as "proxy" because it's ignored if + // shadowPrototypeProperties is true + return AppendNamedPropertyIds(cx, JS::NullPtr(), names, true, props); + """))) + + +class CGPrototypeTraitsClass(CGClass): + def __init__(self, descriptor, indent=''): + templateArgs = [Argument('prototypes::ID', 'PrototypeID')] + templateSpecialization = ['prototypes::id::' + descriptor.name] + enums = [ClassEnum('', ['Depth'], + [descriptor.interface.inheritanceDepth()])] + CGClass.__init__(self, 'PrototypeTraits', indent=indent, + templateArgs=templateArgs, + templateSpecialization=templateSpecialization, + enums=enums, isStruct=True) + + def deps(self): + return set() + + +class CGClassForwardDeclare(CGThing): + def __init__(self, name, isStruct=False): + CGThing.__init__(self) + self.name = name + self.isStruct = isStruct + + def declare(self): + type = 'struct' if self.isStruct else 'class' + return '%s %s;\n' % (type, self.name) + + def define(self): + # Header only + return '' + + def deps(self): + return set() + + +class CGProxySpecialOperation(CGPerSignatureCall): + """ + Base class for classes for calling an indexed or named special operation + (don't use this directly, use the derived classes below). + + If checkFound is False, will just assert that the prop is found instead of + checking that it is before wrapping the value. + """ + def __init__(self, descriptor, operation, checkFound=True, argumentMutableValue=None): + self.checkFound = checkFound + + nativeName = MakeNativeName(descriptor.binaryNames.get(operation, operation)) + operation = descriptor.operations[operation] + assert len(operation.signatures()) == 1 + signature = operation.signatures()[0] + + returnType, arguments = signature + + # We pass len(arguments) as the final argument so that the + # CGPerSignatureCall won't do any argument conversion of its own. + CGPerSignatureCall.__init__(self, returnType, arguments, nativeName, + False, descriptor, operation, + len(arguments)) + + if operation.isSetter() or operation.isCreator(): + # arguments[0] is the index or name of the item that we're setting. + argument = arguments[1] + info = getJSToNativeConversionInfo( + argument.type, descriptor, + treatNullAs=argument.treatNullAs, + sourceDescription=("value being assigned to %s setter" % + descriptor.interface.identifier.name)) + if argumentMutableValue is None: + argumentMutableValue = "desc.value()" + templateValues = { + "declName": argument.identifier.name, + "holderName": argument.identifier.name + "_holder", + "val": argumentMutableValue, + "mutableVal": argumentMutableValue, + "obj": "obj" + } + self.cgRoot.prepend(instantiateJSToNativeConversion(info, templateValues)) + elif operation.isGetter() or operation.isDeleter(): + self.cgRoot.prepend(CGGeneric("bool found;\n")) + + def getArguments(self): + args = [(a, a.identifier.name) for a in self.arguments] + if self.idlNode.isGetter() or self.idlNode.isDeleter(): + args.append((FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean], + self.idlNode), + "found")) + 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, "found") + else: + wrap = CGList([CGGeneric("MOZ_ASSERT(found);\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. + """ + def __init__(self, descriptor, name, doUnwrap=True, checkFound=True, + argumentMutableValue=None): + self.doUnwrap = doUnwrap + CGProxySpecialOperation.__init__(self, descriptor, name, checkFound, + argumentMutableValue=argumentMutableValue) + + 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. + """ + def __init__(self, descriptor, templateValues=None, doUnwrap=True, + checkFound=True): + self.templateValues = templateValues + CGProxyIndexedOperation.__init__(self, descriptor, 'IndexedGetter', + doUnwrap, checkFound) + + +class CGProxyIndexedPresenceChecker(CGProxyIndexedGetter): + """ + Class to generate a call that checks whether an indexed property exists. + + For now, we just delegate to CGProxyIndexedGetter + """ + def __init__(self, descriptor): + CGProxyIndexedGetter.__init__(self, descriptor) + self.cgRoot.append(CGGeneric("(void)result;\n")) + + +class CGProxyIndexedSetter(CGProxyIndexedOperation): + """ + Class to generate a call to an indexed setter. + """ + def __init__(self, descriptor, argumentMutableValue=None): + CGProxyIndexedOperation.__init__(self, descriptor, 'IndexedSetter', + argumentMutableValue=argumentMutableValue) + + +class CGProxyIndexedDeleter(CGProxyIndexedOperation): + """ + Class to generate a call to an indexed deleter. + """ + def __init__(self, descriptor): + CGProxyIndexedOperation.__init__(self, descriptor, 'IndexedDeleter') + + +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. + """ + def __init__(self, descriptor, name, value=None, argumentMutableValue=None): + CGProxySpecialOperation.__init__(self, descriptor, name, + argumentMutableValue=argumentMutableValue) + self.value = value + + def define(self): + # Our first argument is the id we're getting. + argName = self.arguments[0].identifier.name + if argName == "id": + # deal with the name collision + idDecl = "JS::Rooted id_(cx, id);\n" + idName = "id_" + else: + idDecl = "" + idName = "id" + unwrapString = fill( + """ + if (!ConvertJSValueToString(cx, nameVal, &nameVal, + eStringify, eStringify, ${argName})) { + return false; + } + """, + argName=argName) + if self.value is None: + # We're just using 'id', and if it's an atom we can take a + # fast path here. + unwrapString = fill( + """ + if (MOZ_LIKELY(JSID_IS_ATOM(${idName}))) { + ${argName}.SetData(js::GetAtomChars(JSID_TO_ATOM(${idName})), js::GetAtomLength(JSID_TO_ATOM(${idName}))); + } else { + nameVal = js::IdToValue(${idName}); + $*{unwrapString} + } + """, + idName=idName, + argName=argName, + unwrapString=unwrapString) + else: + unwrapString = ("nameVal = %s;\n" % self.value) + unwrapString + + # 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( + """ + JS::Rooted nameVal(cx); + $*{idDecl} + binding_detail::FakeDependentString ${argName}; + $*{unwrapString} + + ${nativeType}* self = UnwrapProxy(proxy); + $*{op} + """, + idDecl=idDecl, + argName=argName, + unwrapString=unwrapString, + nativeType=self.descriptor.nativeType, + op=CGProxySpecialOperation.define(self)) + + +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. + """ + def __init__(self, descriptor, templateValues=None, value=None): + self.templateValues = templateValues + CGProxyNamedOperation.__init__(self, descriptor, 'NamedGetter', value) + + +class CGProxyNamedPresenceChecker(CGProxyNamedGetter): + """ + Class to generate a call that checks whether a named property exists. + + For now, we just delegate to CGProxyNamedGetter + """ + def __init__(self, descriptor): + CGProxyNamedGetter.__init__(self, descriptor) + self.cgRoot.append(CGGeneric("(void)result;\n")) + + +class CGProxyNamedSetter(CGProxyNamedOperation): + """ + Class to generate a call to a named setter. + """ + def __init__(self, descriptor, argumentMutableValue=None): + CGProxyNamedOperation.__init__(self, descriptor, 'NamedSetter', + argumentMutableValue=argumentMutableValue) + + +class CGProxyNamedDeleter(CGProxyNamedOperation): + """ + Class to generate a call to a named deleter. + """ + def __init__(self, descriptor): + CGProxyNamedOperation.__init__(self, descriptor, 'NamedDeleter') + + +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 indent(fill( + """ + MOZ_ASSERT(js::IsProxy(obj)); + if (js::GetProxyHandler(obj) != DOMProxyHandler::getInstance()) { + MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(obj)); + obj = js::UncheckedUnwrap(obj); + } + MOZ_ASSERT(IsProxy(obj)); + return static_cast<${type}*>(js::GetProxyPrivate(obj).toPrivate()); + """, + type=self.descriptor.nativeType)) + + +class CGDOMJSProxyHandler_getOwnPropDescriptor(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'proxy'), + Argument('JS::Handle', 'id'), + Argument('bool', 'ignoreNamedProps'), + Argument('JS::MutableHandle', 'desc')] + ClassMethod.__init__(self, "getOwnPropDescriptor", "bool", args, + virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + indexedSetter = self.descriptor.operations['IndexedSetter'] + + if self.descriptor.supportsIndexedProperties(): + readonly = toStringBool(indexedSetter is None) + fillDescriptor = "FillPropertyDescriptor(desc, proxy, %s);\nreturn true;\n" % readonly + templateValues = { + 'jsvalRef': 'desc.value()', + 'jsvalHandle': 'desc.value()', + 'obj': 'proxy', + 'successCode': fillDescriptor + } + getIndexed = fill( + """ + int32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + $*{callGetter} + } + + """, + callGetter=CGProxyIndexedGetter(self.descriptor, templateValues).define()) + else: + getIndexed = "" + + if UseHolderForUnforgeable(self.descriptor): + tryHolder = dedent(""" + if (!JS_GetPropertyDescriptorById(cx, ${holder}, id, desc)) { + return false; + } + MOZ_ASSERT_IF(desc.object(), desc.object() == ${holder}); + """) + + # We don't want to look at the unforgeable holder at all + # in the xray case; that part got handled already. + getUnforgeable = fill( + """ + if (!isXray) { + $*{callOnUnforgeable} + if (desc.object()) { + desc.object().set(proxy); + return true; + } + } + + """, + callOnUnforgeable=CallOnUnforgeableHolder(self.descriptor, tryHolder)) + else: + getUnforgeable = "" + + if self.descriptor.supportsNamedProperties(): + operations = self.descriptor.operations + readonly = toStringBool(operations['NamedSetter'] is None) + enumerable = ( + "self->NameIsEnumerable(Constify(%s))" % + # First [0] means first (and only) signature, [1] means + # "arguments" as opposed to return type, [0] means first (and + # only) argument. + operations['NamedGetter'].signatures()[0][1][0].identifier.name) + fillDescriptor = ( + "FillPropertyDescriptor(desc, proxy, %s, %s);\n" + "return true;\n" % (readonly, enumerable)) + templateValues = {'jsvalRef': 'desc.value()', 'jsvalHandle': 'desc.value()', + 'obj': 'proxy', 'successCode': fillDescriptor} + condition = "!HasPropertyOnPrototype(cx, proxy, id)" + if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + condition = "(!isXray || %s)" % condition + condition = "!ignoreNamedProps && " + condition + if self.descriptor.supportsIndexedProperties(): + condition = "!IsArrayIndex(index) && " + condition + namedGet = (CGIfWrapper(CGProxyNamedGetter(self.descriptor, templateValues), + condition).define() + + "\n") + else: + namedGet = "" + + return fill( + """ + bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy); + $*{getIndexed} + $*{getUnforgeable} + JS::Rooted expando(cx); + if (!isXray && (expando = GetExpandoObject(proxy))) { + if (!JS_GetPropertyDescriptorById(cx, expando, id, desc)) { + return false; + } + if (desc.object()) { + // Pretend the property lives on the wrapper. + desc.object().set(proxy); + return true; + } + } + + $*{namedGet} + desc.object().set(nullptr); + return true; + """, + getIndexed=getIndexed, + getUnforgeable=getUnforgeable, + namedGet=namedGet) + + +class CGDOMJSProxyHandler_defineProperty(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'proxy'), + Argument('JS::Handle', 'id'), + Argument('JS::MutableHandle', 'desc'), + Argument('bool*', 'defined')] + ClassMethod.__init__(self, "defineProperty", "bool", args, virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + set = "" + + indexedSetter = self.descriptor.operations['IndexedSetter'] + if indexedSetter: + if self.descriptor.operations['IndexedCreator'] is not indexedSetter: + raise TypeError("Can't handle creator that's different from the setter") + set += fill( + """ + int32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + *defined = true; + $*{callSetter} + return true; + } + """, + callSetter=CGProxyIndexedSetter(self.descriptor).define()) + elif self.descriptor.supportsIndexedProperties(): + set += fill( + """ + if (IsArrayIndex(GetArrayIndexFromId(cx, id))) { + return js::IsInNonStrictPropertySet(cx) || ThrowErrorMessage(cx, MSG_NO_INDEXED_SETTER, "${name}"); + } + """, + name=self.descriptor.name) + + if UseHolderForUnforgeable(self.descriptor): + defineOnUnforgeable = ("bool hasUnforgeable;\n" + "if (!JS_HasPropertyById(cx, ${holder}, id, &hasUnforgeable)) {\n" + " return false;\n" + "}\n" + "if (hasUnforgeable) {\n" + " *defined = true;" # SUPER BOGUS missing newline + " bool unused;\n" + " return js_DefineOwnProperty(cx, ${holder}, id, desc, &unused);\n" + "}\n" + "\n") # BOGUS extra blank line at end of block or method + set += CallOnUnforgeableHolder(self.descriptor, + defineOnUnforgeable, + "xpc::WrapperFactory::IsXrayWrapper(proxy)") + + namedSetter = self.descriptor.operations['NamedSetter'] + if namedSetter: + if self.descriptor.operations['NamedCreator'] is not namedSetter: + raise TypeError("Can't handle creator that's different from the setter") + # If we support indexed properties, we won't get down here for + # indices, so we can just do our setter unconditionally here. + set += fill( + """ + *defined = true; + $*{callSetter} + + return true; + + """, # BOGUS extra blank line at end of method + callSetter=CGProxyNamedSetter(self.descriptor).define()) + else: + if self.descriptor.supportsNamedProperties(): + set += fill( + """ + $*{presenceChecker} + + if (found) { + return js::IsInNonStrictPropertySet(cx) || ThrowErrorMessage(cx, MSG_NO_NAMED_SETTER, "${name}"); + } + """, + presenceChecker=CGProxyNamedPresenceChecker(self.descriptor).define(), + name=self.descriptor.name) + set += ("return mozilla::dom::DOMProxyHandler::defineProperty(%s);\n" % + ", ".join(a.name for a in self.args)) + return set + + +class CGDOMJSProxyHandler_delete(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'proxy'), + Argument('JS::Handle', 'id'), + Argument('bool*', 'bp')] + ClassMethod.__init__(self, "delete_", "bool", args, + virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + def getDeleterBody(type): + """ + type should be "Named" or "Indexed" + """ + assert type in ("Named", "Indexed") + deleter = self.descriptor.operations[type + 'Deleter'] + if deleter: + if (not deleter.signatures()[0][0].isPrimitive() or + deleter.signatures()[0][0].nullable() or + deleter.signatures()[0][0].tag() != IDLType.Tags.bool): + setBp = "*bp = true;\n" + else: + setBp = dedent(""" + if (found) { + *bp = result; + } else { + *bp = true; + } + """) + body = (eval("CGProxy%sDeleter" % type)(self.descriptor).define() + + setBp) + elif eval("self.descriptor.supports%sProperties()" % type): + body = (eval("CGProxy%sPresenceChecker" % type)(self.descriptor).define() + + dedent(""" + if (found) { + *bp = false; + } else { + *bp = true; + } + """)) + else: + body = None + return body + + delete = dedent(""" + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + """) + + indexedBody = getDeleterBody("Indexed") + if indexedBody is not None: + delete += fill( + """ + int32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + $*{indexedBody} + // We always return here, even if the property was not found + return true; + } + """, + indexedBody=indexedBody) + + if UseHolderForUnforgeable(self.descriptor): + unforgeable = dedent(""" + bool hasUnforgeable; + if (!JS_HasPropertyById(cx, ${holder}, id, &hasUnforgeable)) { + return false; + } + if (hasUnforgeable) { + *bp = false; + return true; + } + """) + delete += CallOnUnforgeableHolder(self.descriptor, unforgeable) + delete += "\n" + + namedBody = getDeleterBody("Named") + if namedBody is not None: + # 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 += (namedBody + + "if (found) {\n" + " return true;\n" + "}\n\n") # BOGUS extra blank line + if not self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + delete = CGIfWrapper(CGGeneric(delete), + "!HasPropertyOnPrototype(cx, proxy, id)").define() + else: + delete += "\n" # BOGUS extra blank line + + delete += dedent(""" + + return dom::DOMProxyHandler::delete_(cx, proxy, id, bp); + """) + + return delete + + +class CGDOMJSProxyHandler_ownPropNames(ClassMethod): + def __init__(self, descriptor, ): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'proxy'), + Argument('unsigned', 'flags'), + Argument('JS::AutoIdVector&', 'props')] + ClassMethod.__init__(self, "ownPropNames", "bool", args, + virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + # Per spec, we do indices, then named props, then everything else + if self.descriptor.supportsIndexedProperties(): + addIndices = dedent(""" + + uint32_t length = UnwrapProxy(proxy)->Length(); + MOZ_ASSERT(int32_t(length) >= 0); + for (int32_t i = 0; i < int32_t(length); ++i) { + if (!props.append(INT_TO_JSID(i))) { + return false; + } + } + """) + else: + addIndices = "" + + if UseHolderForUnforgeable(self.descriptor): + addUnforgeable = dedent(""" + if (!js::GetPropertyNames(cx, ${holder}, flags, &props)) { + return false; + } + """) + addUnforgeable = CallOnUnforgeableHolder(self.descriptor, + addUnforgeable, + "isXray") + else: + addUnforgeable = "" + + if self.descriptor.supportsNamedProperties(): + if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + shadow = "!isXray" + else: + shadow = "false" + addNames = fill( + """ + + nsTArray names; + UnwrapProxy(proxy)->GetSupportedNames(flags, names); + if (!AppendNamedPropertyIds(cx, proxy, names, ${shadow}, props)) { + return false; + } + """, + shadow=shadow) + else: + addNames = "" + + return fill( + """ + bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy); + $*{addIndices} + $*{addUnforgeable} + $*{addNames} + + JS::Rooted expando(cx); + if (!isXray && (expando = DOMProxyHandler::GetExpandoObject(proxy)) && + !js::GetPropertyNames(cx, expando, flags, &props)) { + return false; + } + + return true; + """, + addIndices=addIndices, + addUnforgeable=addUnforgeable, + addNames=addNames) + + +class CGDOMJSProxyHandler_hasOwn(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'proxy'), + Argument('JS::Handle', 'id'), + Argument('bool*', 'bp')] + ClassMethod.__init__(self, "hasOwn", "bool", args, + virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + if self.descriptor.supportsIndexedProperties(): + indexed = fill( + """ + int32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + $*{presenceChecker} + + *bp = found; + return true; + } + + """, + presenceChecker=CGProxyIndexedPresenceChecker(self.descriptor).define()) + else: + indexed = "" + + if UseHolderForUnforgeable(self.descriptor): + unforgeable = dedent(""" + bool b = true; + bool ok = JS_AlreadyHasOwnPropertyById(cx, ${holder}, id, &b); + *bp = !!b; + if (!ok || *bp) { + return ok; + } + """) + unforgeable = CallOnUnforgeableHolder(self.descriptor, unforgeable) + else: + unforgeable = "" + + 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 = (CGProxyNamedPresenceChecker(self.descriptor).define() + + "\n" + + "*bp = found;\n") + if not self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + # BOGUS extra blank line at end of block + named = CGIfWrapper(CGGeneric(named + "return true;\n\n"), + "!HasPropertyOnPrototype(cx, proxy, id)").define() + named += "*bp = false;\n" + else: + named += "\n" + else: + named = "*bp = false;\n" + + return fill( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + $*{indexed} + $*{unforgeable} + + JS::Rooted expando(cx, GetExpandoObject(proxy)); + if (expando) { + bool b = true; + bool ok = JS_HasPropertyById(cx, expando, id, &b); + *bp = !!b; + if (!ok || *bp) { + return ok; + } + } + + $*{named} + return true; + """, + indexed=indexed, + unforgeable=unforgeable, + named=named) + + +class CGDOMJSProxyHandler_get(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'proxy'), + Argument('JS::Handle', 'receiver'), + Argument('JS::Handle', 'id'), + Argument('JS::MutableHandle', 'vp')] + ClassMethod.__init__(self, "get", "bool", args, + virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + getUnforgeableOrExpando = "JS::Rooted sharedRoot(cx);\n" + if UseHolderForUnforgeable(self.descriptor): + hasUnforgeable = dedent(""" + bool hasUnforgeable; + if (!JS_AlreadyHasOwnPropertyById(cx, ${holder}, id, &hasUnforgeable)) { + return false; + } + if (hasUnforgeable) { + return JS_ForwardGetPropertyTo(cx, ${holder}, id, proxy, vp); + } + """) + getUnforgeableOrExpando += CallOnUnforgeableHolder(self.descriptor, + hasUnforgeable, + useSharedRoot=True) + getUnforgeableOrExpando += dedent(""" + { // Scope for expando + JS::Rooted& expando(sharedRoot); + expando = DOMProxyHandler::GetExpandoObject(proxy); + if (expando) { + bool hasProp; + if (!JS_HasPropertyById(cx, expando, id, &hasProp)) { + return false; + } + + if (hasProp) { + // Forward the get to the expando object, but our receiver is whatever our + // receiver is. + return JS_ForwardGetPropertyTo(cx, expando, id, receiver, vp); + } + } + } + """) + + templateValues = {'jsvalRef': 'vp', 'jsvalHandle': 'vp', 'obj': 'proxy'} + + if self.descriptor.supportsIndexedProperties(): + getIndexedOrExpando = fill( + """ + int32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + $*{callGetter} + // Even if we don't have this index, we don't forward the + // get on to our expando object. + } else { + $*{getUnforgeableOrExpando} + } + """, + callGetter=CGProxyIndexedGetter(self.descriptor, templateValues).define(), + getUnforgeableOrExpando=getUnforgeableOrExpando) + else: + getIndexedOrExpando = getUnforgeableOrExpando + + if self.descriptor.supportsNamedProperties(): + getNamed = CGProxyNamedGetter(self.descriptor, templateValues) + if self.descriptor.supportsIndexedProperties(): + getNamed = CGIfWrapper(getNamed, "!IsArrayIndex(index)") + getNamed = getNamed.define() + "\n" + else: + getNamed = "" + + getOnPrototype = dedent(""" + bool foundOnPrototype; + if (!GetPropertyOnPrototype(cx, proxy, id, &foundOnPrototype, vp.address())) { + return false; + } + + if (foundOnPrototype) { + return true; + } + + """) + if self.descriptor.interface.getExtendedAttribute('OverrideBuiltins'): + getNamed = getNamed + getOnPrototype + else: + getNamed = getOnPrototype + getNamed + + return fill( + """ + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + $*{indexedOrExpando} + + $*{named} + vp.setUndefined(); + return true; + """, + indexedOrExpando=getIndexedOrExpando, + named=getNamed) + + +class CGDOMJSProxyHandler_setCustom(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'proxy'), + Argument('JS::Handle', 'id'), + Argument('JS::MutableHandle', 'vp'), + Argument('bool*', 'done')] + ClassMethod.__init__(self, "setCustom", "bool", args, virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + assertion = ("MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),\n" + ' "Should not have a XrayWrapper here");\n') + + # Correctness first. If we have a NamedSetter and [OverrideBuiltins], + # always call the NamedSetter and never do anything else. + namedSetter = self.descriptor.operations['NamedSetter'] + if (namedSetter is not None and + self.descriptor.interface.getExtendedAttribute('OverrideBuiltins')): + # Check assumptions. + if self.descriptor.supportsIndexedProperties(): + raise ValueError("In interface " + self.descriptor.name + ": " + + "Can't cope with [OverrideBuiltins] and an indexed getter") + if self.descriptor.operations['NamedCreator'] is not namedSetter: + raise ValueError("In interface " + self.descriptor.name + ": " + + "Can't cope with named setter that is not also a named creator") + if UseHolderForUnforgeable(self.descriptor): + raise ValueError("In interface " + self.descriptor.name + ": " + + "Can't cope with [OverrideBuiltins] and unforgeable members") + + callSetter = CGProxyNamedSetter(self.descriptor, argumentMutableValue="vp") + return (assertion + + callSetter.define() + + "*done = true;\n" + "return true;\n") + + # As an optimization, if we are going to call an IndexedSetter, go + # ahead and call it and have done. + indexedSetter = self.descriptor.operations['IndexedSetter'] + if indexedSetter is not None: + if self.descriptor.operations['IndexedCreator'] is not indexedSetter: + raise ValueError("In interface " + self.descriptor.name + ": " + + "Can't cope with indexed setter that is not " + + "also an indexed creator") + setIndexed = fill( + """ + int32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + $*{callSetter} + *done = true; + return true; + } + + """, + callSetter=CGProxyIndexedSetter(self.descriptor, + argumentMutableValue="vp").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', 'proxy')] + ClassMethod.__init__(self, "className", "const char*", args, + virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + return 'return "%s";\n' % self.descriptor.name + + +class CGDOMJSProxyHandler_finalizeInBackground(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JS::Value', 'priv')] + ClassMethod.__init__(self, "finalizeInBackground", "bool", args, + virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + return "return false;\n" + + +class CGDOMJSProxyHandler_finalize(ClassMethod): + def __init__(self, descriptor): + args = [Argument('JSFreeOp*', 'fop'), Argument('JSObject*', 'proxy')] + ClassMethod.__init__(self, "finalize", "void", args, + virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + return ("%s* self = UnwrapProxy(proxy);\n\n" % self.descriptor.nativeType + + finalizeHook(self.descriptor, FINALIZE_HOOK_NAME, self.args[0].name).define()) + + +class CGDOMJSProxyHandler_slice(ClassMethod): + def __init__(self, descriptor): + assert descriptor.supportsIndexedProperties() + + args = [Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'proxy'), + Argument('uint32_t', 'begin'), + Argument('uint32_t', 'end'), + Argument('JS::Handle', 'array')] + ClassMethod.__init__(self, "slice", "bool", args, virtual=True, override=True) + self.descriptor = descriptor + + def getBody(self): + # Just like getOwnPropertyNames 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': ("js::UnsafeDefineElement(cx, array, index - begin, temp);\n" + "continue;\n") + } + get = CGProxyIndexedGetter(self.descriptor, templateValues, False, False).define() + + return fill( + """ + JS::Rooted temp(cx); + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + "Should not have a XrayWrapper here"); + + ${nativeType}* self = UnwrapProxy(proxy); + uint32_t length = self->Length(); + // Compute the end of the indices we'll get ourselves + uint32_t ourEnd = std::max(begin, std::min(end, length)); + + for (uint32_t index = begin; index < ourEnd; ++index) { + $*{get} + } + + if (end > ourEnd) { + JS::Rooted proto(cx); + if (!js::GetObjectProto(cx, proxy, &proto)) { + return false; + } + return js::SliceSlowly(cx, proto, proxy, ourEnd, end, array); + } + + return true; + """, + nativeType=self.descriptor.nativeType, + get=get) + + +class CGDOMJSProxyHandler_getInstance(ClassMethod): + def __init__(self): + ClassMethod.__init__(self, "getInstance", "DOMProxyHandler*", [], static=True) + + def getBody(self): + return dedent(""" + static DOMProxyHandler instance; + return &instance; + """) + + +class CGDOMJSProxyHandler(CGClass): + def __init__(self, descriptor): + assert (descriptor.supportsIndexedProperties() or + descriptor.supportsNamedProperties()) + methods = [CGDOMJSProxyHandler_getOwnPropDescriptor(descriptor), + CGDOMJSProxyHandler_defineProperty(descriptor), + ClassUsingDeclaration("mozilla::dom::DOMProxyHandler", + "defineProperty"), + CGDOMJSProxyHandler_ownPropNames(descriptor), + CGDOMJSProxyHandler_hasOwn(descriptor), + CGDOMJSProxyHandler_get(descriptor), + CGDOMJSProxyHandler_className(descriptor), + CGDOMJSProxyHandler_finalizeInBackground(descriptor), + CGDOMJSProxyHandler_finalize(descriptor), + CGDOMJSProxyHandler_getInstance(), + CGDOMJSProxyHandler_delete(descriptor)] + if descriptor.supportsIndexedProperties(): + methods.append(CGDOMJSProxyHandler_slice(descriptor)) + if (descriptor.operations['IndexedSetter'] is not None or + (descriptor.operations['NamedSetter'] is not None and + descriptor.interface.getExtendedAttribute('OverrideBuiltins'))): + methods.append(CGDOMJSProxyHandler_setCustom(descriptor)) + + CGClass.__init__(self, 'DOMProxyHandler', + bases=[ClassBase('mozilla::dom::DOMProxyHandler')], + 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 CGDescriptor(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + + assert not descriptor.concrete or descriptor.interface.hasInterfacePrototypeObject() + + if descriptor.nativeOwnership == 'owned' and ( + descriptor.interface.hasChildInterfaces() or + descriptor.interface.parent): + raise TypeError("Owned interface cannot have a parent or children") + + self._deps = descriptor.interface.getDeps() + + cgThings = [] + cgThings.append(CGGeneric(declare="typedef %s NativeType;\n" % + descriptor.nativeType)) + # These are set to true if at least one non-static + # method/getter/setter or jsonifier exist on the interface. + (hasMethod, hasGetter, hasLenientGetter, hasSetter, hasJsonifier, + hasLenientSetter) = False, False, False, False, False, False + crossOriginMethods, crossOriginGetters, crossOriginSetters = set(), set(), set() + for n in descriptor.interface.namedConstructors: + cgThings.append(CGClassConstructor(descriptor, n, + NamedConstructorName(n))) + for m in descriptor.interface.members: + if m.isMethod() and m.identifier.name == 'queryInterface': + continue + if m.isMethod() and m == descriptor.operations['Jsonifier']: + hasJsonifier = True + hasMethod = descriptor.needsSpecialGenericOps() + jsonifierMethod = m + elif (m.isMethod() and + (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 m.getExtendedAttribute("CrossOriginCallable"): + crossOriginMethods.add(m.identifier.name) + elif descriptor.needsSpecialGenericOps(): + hasMethod = True + elif m.isAttr(): + if m.stringifier: + raise TypeError("Stringifier attributes not supported yet. " + "See bug 824857.\n" + "%s" % m.location) + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject + cgThings.append(CGStaticGetter(descriptor, m)) + elif descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGSpecializedGetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientGetter = True + elif m.getExtendedAttribute("CrossOriginReadable"): + crossOriginGetters.add(m.identifier.name) + elif descriptor.needsSpecialGenericOps(): + hasGetter = True + if not m.readonly: + for extAttr in ["PutForwards", "Replaceable"]: + if m.getExtendedAttribute(extAttr): + raise TypeError("Writable attributes should not " + "have %s specified.\n" + "%s" % + (extAttr, m.location)) + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject + cgThings.append(CGStaticSetter(descriptor, m)) + elif descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGSpecializedSetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientSetter = True + elif m.getExtendedAttribute("CrossOriginWritable"): + crossOriginSetters.add(m.identifier.name) + elif descriptor.needsSpecialGenericOps(): + hasSetter = True + elif m.getExtendedAttribute("PutForwards"): + cgThings.append(CGSpecializedForwardingSetter(descriptor, m)) + if m.getExtendedAttribute("CrossOriginWritable"): + crossOriginSetters.add(m.identifier.name) + elif descriptor.needsSpecialGenericOps(): + hasSetter = True + elif m.getExtendedAttribute("Replaceable"): + cgThings.append(CGSpecializedReplaceableSetter(descriptor, m)) + if descriptor.needsSpecialGenericOps(): + hasSetter = True + if (not m.isStatic() and + descriptor.interface.hasInterfacePrototypeObject()): + cgThings.append(CGMemberJITInfo(descriptor, m)) + if hasJsonifier: + cgThings.append(CGJsonifierMethod(descriptor, jsonifierMethod)) + cgThings.append(CGMemberJITInfo(descriptor, jsonifierMethod)) + if hasMethod: + cgThings.append(CGGenericMethod(descriptor)) + if len(crossOriginMethods): + cgThings.append(CGGenericMethod(descriptor, + allowCrossOriginThis=True)) + if hasGetter: + cgThings.append(CGGenericGetter(descriptor)) + if hasLenientGetter: + cgThings.append(CGGenericGetter(descriptor, lenientThis=True)) + if len(crossOriginGetters): + cgThings.append(CGGenericGetter(descriptor, + allowCrossOriginThis=True)) + if hasSetter: + cgThings.append(CGGenericSetter(descriptor)) + if hasLenientSetter: + cgThings.append(CGGenericSetter(descriptor, lenientThis=True)) + if len(crossOriginSetters): + cgThings.append(CGGenericSetter(descriptor, + allowCrossOriginThis=True)) + + if descriptor.interface.getNavigatorProperty(): + cgThings.append(CGConstructNavigatorObjectHelper(descriptor)) + cgThings.append(CGConstructNavigatorObject(descriptor)) + + if descriptor.concrete and not descriptor.proxy: + if wantsAddProperty(descriptor): + cgThings.append(CGAddPropertyHook(descriptor)) + + # Always have a finalize hook, regardless of whether the class + # wants a custom hook. + cgThings.append(CGClassFinalizeHook(descriptor)) + + properties = PropertyArrays(descriptor) + cgThings.append(CGGeneric(define=str(properties))) + cgThings.append(CGNativeProperties(descriptor, properties)) + + # Set up our Xray callbacks as needed. Note that we don't need to do + # it in workers. + if not descriptor.workers and descriptor.concrete and descriptor.proxy: + cgThings.append(CGResolveOwnProperty(descriptor)) + cgThings.append(CGEnumerateOwnProperties(descriptor)) + elif descriptor.needsXrayResolveHooks(): + cgThings.append(CGResolveOwnPropertyViaNewresolve(descriptor)) + cgThings.append(CGEnumerateOwnPropertiesViaGetOwnPropertyNames(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(CGClassHasInstanceHook(descriptor)) + cgThings.append(CGInterfaceObjectJSClass(descriptor, properties)) + if descriptor.needsConstructHookHolder(): + cgThings.append(CGClassConstructHookHolder(descriptor)) + cgThings.append(CGNamedConstructors(descriptor)) + + cgThings.append(CGLegacyCallHook(descriptor)) + if descriptor.interface.getExtendedAttribute("NeedNewResolve"): + cgThings.append(CGNewResolveHook(descriptor)) + cgThings.append(CGEnumerateHook(descriptor)) + + if descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGPrototypeJSClass(descriptor, properties)) + + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGDefineDOMInterfaceMethod(descriptor)) + + if ((descriptor.interface.hasInterfaceObject() or descriptor.interface.getNavigatorProperty()) and + not descriptor.interface.isExternal() and + descriptor.isExposedConditionally() and + # Workers stuff is never conditional + not descriptor.workers): + cgThings.append(CGConstructorEnabled(descriptor)) + + if descriptor.concrete: + if descriptor.proxy: + if descriptor.interface.totalMembersInSlots != 0: + raise TypeError("We can't have extra reserved slots for " + "proxy interface %s" % + descriptor.interface.identifier.name) + cgThings.append(CGGeneric(fill( + """ + static_assert(IsBaseOf::value, + "We don't support non-nsISupports native classes for " + "proxy-based bindings yet"); + + """, + nativeType=descriptor.nativeType))) + if not descriptor.wrapperCache: + raise TypeError("We need a wrappercache to support expandos for proxy-based " + "bindings (" + descriptor.name + ")") + handlerThing = CGDOMJSProxyHandler(descriptor) + cgThings.append(CGDOMJSProxyHandlerDeclarer(handlerThing)) + cgThings.append(CGProxyIsProxy(descriptor)) + cgThings.append(CGProxyUnwrap(descriptor)) + cgThings.append(CGDOMJSProxyHandlerDefiner(handlerThing)) + cgThings.append(CGDOMProxyJSClass(descriptor)) + else: + cgThings.append(CGDOMJSClass(descriptor)) + cgThings.append(CGGetJSClassMethod(descriptor)) + if descriptor.interface.hasMembersInSlots(): + if descriptor.interface.hasChildInterfaces(): + raise TypeError("We don't support members in slots on " + "non-leaf interfaces like %s" % + descriptor.interface.identifier.name) + cgThings.append(CGUpdateMemberSlotsMethod(descriptor)) + + if descriptor.interface.getExtendedAttribute("Global"): + assert descriptor.wrapperCache + cgThings.append(CGWrapGlobalMethod(descriptor, properties)) + elif descriptor.wrapperCache: + cgThings.append(CGWrapWithCacheMethod(descriptor, properties)) + cgThings.append(CGWrapMethod(descriptor)) + else: + cgThings.append(CGWrapNonWrapperCacheMethod(descriptor, + properties)) + + # 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 descriptor.interface.members if + m.isAttr() and + # Constants should never need clearing! + not m.getExtendedAttribute("Constant") and + not m.getExtendedAttribute("SameObject") and + m.slotIndex is not None) + + # CGCreateInterfaceObjectsMethod needs to come after our + # CGDOMJSClass, if any. + cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties)) + + # CGGetProtoObjectMethod and CGGetConstructorObjectMethod need + # to come after CGCreateInterfaceObjectsMethod. + if descriptor.interface.hasInterfacePrototypeObject(): + cgThings.append(CGGetProtoObjectMethod(descriptor)) + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGGetConstructorObjectMethod(descriptor)) + + # See whether we need we need to generate an IsPermitted method + if crossOriginGetters or crossOriginSetters or crossOriginMethods: + cgThings.append(CGIsPermittedMethod(descriptor, + crossOriginGetters, + crossOriginSetters, + crossOriginMethods)) + + cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n") + cgThings = CGWrapper(cgThings, pre='\n', post='\n') + self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), + cgThings), + post='\n') + + def declare(self): + return self.cgRoot.declare() + + def define(self): + return self.cgRoot.define() + + def deps(self): + return self._deps + + +class CGNamespacedEnum(CGThing): + def __init__(self, namespace, enumName, names, values, comment=""): + + if not values: + values = [] + + # Account for explicit enum values. + entries = [] + for i in range(0, len(names)): + if len(values) > i and values[i] is not None: + entry = "%s = %s" % (names[i], values[i]) + else: + entry = names[i] + entries.append(entry) + + # Append a Count. + entries.append('_' + enumName + '_Count') + + # Indent. + entries = [' ' + e for e in entries] + + # Build the enum body. + enumstr = comment + 'enum %s\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 "" + + +class CGDictionary(CGThing): + def __init__(self, dictionary, descriptorProvider): + self.dictionary = dictionary + self.descriptorProvider = descriptorProvider + self.needToInitIds = len(dictionary.members) > 0 + self.memberInfo = [ + (member, + getJSToNativeConversionInfo( + member.type, + descriptorProvider, + isEnforceRange=member.enforceRange, + isClamp=member.clamp, + isMember="Dictionary", + isOptional=(not member.defaultValue), + defaultValue=member.defaultValue, + sourceDescription=("'%s' member of %s" % + (member.identifier.name, dictionary.identifier.name)))) + for member in dictionary.members] + + # If we have a union member containing something in the same + # file as us, bail: the C++ includes won't work out. + for member in dictionary.members: + type = member.type.unroll() + if type.isUnion(): + for t in type.flatMemberTypes: + if (t.isDictionary() and + CGHeaders.getDeclarationFilename(t.inner) == + CGHeaders.getDeclarationFilename(dictionary)): + raise TypeError( + "Dictionary contains a union that contains a " + "dictionary in the same WebIDL file. This won't " + "compile. Move the inner dictionary to a " + "different file.\n%s\n%s" % + (t.location, t.inner.location)) + self.structs = self.getStructs() + + def declare(self): + return self.structs.declare() + + def define(self): + return self.structs.define() + + def base(self): + if self.dictionary.parent: + return self.makeClassName(self.dictionary.parent) + return "DictionaryBase" + + def initMethod(self): + body = dedent(""" + // Passing a null JSContext is OK only if we're initing from null, + // Since in that case we will not have to do any property gets + MOZ_ASSERT_IF(!cx, val.isNull()); + """) + + if self.needToInitIds: + body += fill( + """ + ${dictName}Atoms* atomsCache = nullptr; + if (cx) { + atomsCache = GetAtomCache<${dictName}Atoms>(cx); + if (!*reinterpret_cast(atomsCache) && !InitIds(cx, atomsCache)) { + return false; + } + } + + """, + dictName=self.makeClassName(self.dictionary)) + + if self.dictionary.parent: + body += fill( + """ + // Per spec, we init the parent's members first + if (!${dictName}::Init(cx, val)) { + return false; + } + MOZ_ASSERT(IsConvertibleToDictionary(cx, val)); + + """, + dictName=self.makeClassName(self.dictionary.parent)) + else: + body += fill( + """ + if (!IsConvertibleToDictionary(cx, val)) { + return ThrowErrorMessage(cx, MSG_NOT_DICTIONARY, sourceDescription); + } + + """) + + memberInits = [self.getMemberConversion(m).define() + for m in self.memberInfo] + if memberInits: + body += fill( + """ + bool isNull = val.isNullOrUndefined(); + // We only need these if !isNull, in which case we have |cx|. + Maybe > object; + Maybe > temp; + if (!isNull) { + object.construct(cx, &val.toObject()); + temp.construct(cx); + } + $*{memberInits} + """, + memberInits="\n".join(memberInits)) + + body += "return true;\n" + + return ClassMethod("Init", "bool", [ + Argument('JSContext*', 'cx'), + Argument('JS::Handle', 'val'), + Argument('const char*', 'sourceDescription', default='"Value"') + ], body=body) + + def initFromJSONMethod(self): + return ClassMethod( + "Init", "bool", + [Argument('const nsAString&', 'aJSON')], + body=dedent(""" + MOZ_ASSERT(NS_IsMainThread()); + AutoSafeJSContext cx; + JS::Rooted json(cx); + bool ok = ParseJSON(cx, aJSON, &json); + NS_ENSURE_TRUE(ok, false); + return Init(cx, json); + """)) + + def toObjectMethod(self): + body = "" + if self.needToInitIds: + body += fill( + """ + ${dictName}Atoms* atomsCache = GetAtomCache<${dictName}Atoms>(cx); + if (!*reinterpret_cast(atomsCache) && !InitIds(cx, atomsCache)) { + return false; + } + + """, + dictName=self.makeClassName(self.dictionary)) + + if self.dictionary.parent: + body += fill( + """ + // Per spec, we define the parent's members first + if (!${dictName}::ToObject(cx, rval)) { + return false; + } + JS::Rooted obj(cx, &rval.toObject()); + + """, + dictName=self.makeClassName(self.dictionary.parent)) + else: + body += fill( + """ + JS::Rooted obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); + if (!obj) { + return false; + } + rval.set(JS::ObjectValue(*obj)); + + """) + + if self.memberInfo: + body += "\n".join(self.getMemberDefinition(m).define() + for m in self.memberInfo) + else: + body += "\n" # BOGUS extra blank line + body += "\nreturn true;\n" + + return ClassMethod("ToObject", "bool", [ + Argument('JSContext*', 'cx'), + Argument('JS::MutableHandle', 'rval'), + ], const=True, body=body) + + def initIdsMethod(self): + assert self.needToInitIds + idinit = ['!atomsCache->%s.init(cx, "%s")' % + (CGDictionary.makeIdName(m.identifier.name), + m.identifier.name) + for m in self.dictionary.members] + idinit.reverse() + body = fill( + """ + MOZ_ASSERT(!*reinterpret_cast(atomsCache)); + + // Initialize these in reverse order so that any failure leaves the first one + // uninitialized. + if (${idinit}) { + return false; + } + return true; + """, + idinit=" ||\n ".join(idinit)) + + return ClassMethod("InitIds", "bool", [ + Argument("JSContext*", "cx"), + Argument("%sAtoms*" % self.makeClassName(self.dictionary), + "atomsCache"), + ], static=True, body=body, visibility="private") + + 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) + else: + body += "\n" # BOGUS extra newline + + return ClassMethod("TraceDictionary", "void", [ + Argument("JSTracer*", "trc"), + ], body=body) + + def assignmentOperator(self): + body = CGList([]) + if self.dictionary.parent: + body.append(CGGeneric( + "%s::operator=(aOther);\n" % + self.makeClassName(self.dictionary.parent))) + for m, _ in self.memberInfo: + memberName = self.makeMemberName(m.identifier.name) + if not m.defaultValue: + memberAssign = CGGeneric(fill( + """ + if (aOther.${name}.WasPassed()) { + ${name}.Construct(); + ${name}.Value() = aOther.${name}.Value(); + } else { + ${name}.Reset(); + } + """, + name=memberName)) + else: + memberAssign = CGGeneric( + "%s = aOther.%s;\n" % (memberName, memberName)) + body.append(memberAssign) + return ClassMethod( + "operator=", "void", + [Argument("const %s&" % self.makeClassName(self.dictionary), + "aOther")], + body=body.define() or "\n") # BOGUS blank line when empty + + 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)) + for m in self.memberInfo] + ctors = [ + ClassConstructor( + [], + visibility="public", + body=( + "// Safe to pass a null context if we pass a null value\n" + "Init(nullptr, JS::NullHandleValue);\n")), + ClassConstructor( + [Argument("int", "")], + visibility="protected", + explicit=True, + bodyInHeader=True, + body='// Do nothing here; this is used by our "Fast" subclass\n') + ] + methods = [] + + if self.needToInitIds: + methods.append(self.initIdsMethod()) + + methods.append(self.initMethod()) + methods.append(self.initFromJSONMethod()) + try: + methods.append(self.toObjectMethod()) + except MethodNotNewObjectError: + # If we can't have a ToObject() because one of our members can only + # be returned from [NewObject] methods, then just skip generating + # ToObject(). + pass + methods.append(self.traceDictionaryMethod()) + + if CGDictionary.isDictionaryCopyConstructible(d): + disallowCopyConstruction = False + # Note: no base constructors because our operator= will + # deal with that. + ctors.append(ClassConstructor([Argument("const %s&" % selfName, + "aOther")], + bodyInHeader=True, + visibility="public", + explicit=True, + body="*this = aOther;\n")) + methods.append(self.assignmentOperator()) + else: + disallowCopyConstruction = True + + struct = CGClass(selfName, + bases=[ClassBase(self.base())], + members=members, + constructors=ctors, + methods=methods, + isStruct=True, + disallowCopyConstruction=disallowCopyConstruction) + + fastDictionaryCtor = ClassConstructor( + [], + visibility="public", + bodyInHeader=True, + baseConstructors=["%s(42)" % selfName], + 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() + 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): + member, conversionInfo = memberInfo + replacements = { + "val": "temp.ref()", + "mutableVal": "&temp.ref()", + "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" + } + # We can't handle having a holderType here + assert conversionInfo.holderType is None + if conversionInfo.dealWithOptional: + replacements["declName"] = "(" + replacements["declName"] + ".Value())" + if member.defaultValue: + replacements["haveValue"] = "!isNull && !temp.ref().isUndefined()" + + propId = self.makeIdName(member.identifier.name) + propGet = ("JS_GetPropertyById(cx, object.ref(), atomsCache->%s, &temp.ref())" % + propId) + + conversionReplacements = { + "prop": self.makeMemberName(member.identifier.name), + "convert": string.Template(conversionInfo.template).substitute(replacements), + "propGet": propGet + } + conversion = ("if (!isNull && !${propGet}) {\n" + " return false;\n" + "}\n") + if member.defaultValue: + conversion += "${convert}" + else: + conversion += ( + "if (!isNull && !temp.ref().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 member.defaultValue: + memberData = memberLoc + else: + # The data is inside the Optional<> + memberData = "%s.InternalValue()" % memberLoc + + # If you have to change this list (which you shouldn't!), make sure it + # continues to match the list in test_Object.prototype_props.html + if (member.identifier.name in + ["constructor", "toSource", "toString", "toLocaleString", "valueOf", + "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", + "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", + "__lookupGetter__", "__lookupSetter__", "__proto__"]): + raise TypeError("'%s' member of %s dictionary shadows " + "a property of Object.prototype, and Xrays to " + "Object can't handle that.\n" + "%s" % + (member.identifier.name, + self.dictionary.identifier.name, + member.location)) + + propDef = ( + 'JS_DefinePropertyById(cx, obj, atomsCache->%s, temp, nullptr, nullptr, JSPROP_ENUMERATE)' % + self.makeIdName(member.identifier.name)) + + innerTemplate = wrapForType( + member.type, self.descriptorProvider, + { + 'result': "currentValue", + 'successCode': ("if (!%s) {\n" + " return false;\n" + "}\n" + "break;\n" % propDef), + 'jsvalRef': "temp", + 'jsvalHandle': "&temp", + 'returnsNewObject': False, + # 'obj' can just be allowed to be the string "obj", since that + # will be our dictionary object, which is presumably itself in + # the right scope. + 'typedArraysAreStructs': True + }) + conversion = CGGeneric(innerTemplate) + conversion = CGWrapper(conversion, + pre=("JS::Rooted temp(cx);\n" + "%s const & currentValue = %s;\n" % + (declType.define(), memberData) + )) + + # Now make sure that our successCode can actually break out of the + # conversion. This incidentally gives us a scope for 'temp' and + # 'currentValue'. + conversion = CGWrapper( + CGIndenter(conversion), + pre=("do {\n" + " // block for our 'break' successCode and scope for 'temp' and 'currentValue'\n"), + post="} while(0);\n") + if not member.defaultValue: + # Only do the conversion if we have a value + conversion = CGIfWrapper(conversion, "%s.WasPassed()" % memberLoc) + return conversion + + def getMemberTrace(self, member): + type = member.type + assert typeNeedsRooting(type) + memberLoc = self.makeMemberName(member.identifier.name) + if member.defaultValue: + 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_CallObjectTracer(trc, %s, "%s");\n' % + ("&"+memberData, memberName)) + if type.nullable(): + trace = CGIfWrapper(trace, memberData) + elif type.isAny(): + trace = CGGeneric('JS_CallValueTracer(trc, %s, "%s");\n' % + ("&"+memberData, memberName)) + elif (type.isSequence() or type.isDictionary() or + type.isSpiderMonkeyInterface() or type.isUnion()): + if type.nullable(): + memberNullable = memberData + memberData = "%s.Value()" % memberData + if type.isSequence(): + trace = CGGeneric('DoTraceSequence(trc, %s);\n' % memberData) + elif type.isDictionary(): + trace = CGGeneric('%s.TraceDictionary(trc);\n' % memberData) + elif type.isUnion(): + trace = CGGeneric('%s.TraceUnion(trc);\n' % memberData) + else: + assert type.isSpiderMonkeyInterface() + trace = CGGeneric('%s.TraceSelf(trc);\n' % memberData) + if type.nullable(): + trace = CGIfWrapper(trace, "!%s.IsNull()" % memberNullable) + else: + assert False # unknown type + + if not member.defaultValue: + trace = CGIfWrapper(trace, "%s.WasPassed()" % memberLoc) + + return trace.define() + + def getMemberInitializer(self, memberInfo): + """ + Get the right initializer for the member. Most members don't need one, + but we need to pre-initialize 'any' and 'object' that have a default + value, so they're safe to trace at all times. + """ + member, _ = memberInfo + if not member.defaultValue: + # No default value means no need to set it up front, since it's + # inside an Optional and won't get traced until it's actually set + # up. + return None + type = member.type + if type.isAny(): + return "JS::UndefinedValue()" + if type.isObject(): + return "nullptr" + return None + + @staticmethod + def makeIdName(name): + return name + "_id" + + @staticmethod + def getDictionaryDependenciesFromType(type): + if type.isDictionary(): + return set([type.unroll().inner]) + if type.isSequence() or type.isArray(): + return CGDictionary.getDictionaryDependenciesFromType(type.unroll()) + return set() + + @staticmethod + def getDictionaryDependencies(dictionary): + deps = set() + if dictionary.parent: + deps.add(dictionary.parent) + for member in dictionary.members: + deps |= CGDictionary.getDictionaryDependenciesFromType(member.type) + return deps + + @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) + + +class CGRegisterProtos(CGAbstractMethod): + def __init__(self, config): + CGAbstractMethod.__init__(self, None, 'Register', 'void', + [Argument('nsScriptNameSpaceManager*', 'aNameSpaceManager')]) + self.config = config + + def _defineMacro(self): + return dedent(""" + #define REGISTER_PROTO(_dom_class, _ctor_check) \\ + aNameSpaceManager->RegisterDefineDOMInterface(MOZ_UTF16(#_dom_class), _dom_class##Binding::DefineDOMInterface, _ctor_check); + #define REGISTER_CONSTRUCTOR(_dom_constructor, _dom_class, _ctor_check) \\ + aNameSpaceManager->RegisterDefineDOMInterface(MOZ_UTF16(#_dom_constructor), _dom_class##Binding::DefineDOMInterface, _ctor_check); + #define REGISTER_NAVIGATOR_CONSTRUCTOR(_prop, _dom_class, _ctor_check) \\ + aNameSpaceManager->RegisterNavigatorDOMConstructor(MOZ_UTF16(_prop), _dom_class##Binding::ConstructNavigatorObject, _ctor_check); + """) + + def _undefineMacro(self): + return dedent(""" + #undef REGISTER_CONSTRUCTOR + #undef REGISTER_PROTO + #undef REGISTER_NAVIGATOR_CONSTRUCTOR + """) + + def _registerProtos(self): + def getCheck(desc): + if not desc.isExposedConditionally(): + return "nullptr" + return "%sBinding::ConstructorEnabled" % desc.name + lines = [] + for desc in self.config.getDescriptors(hasInterfaceObject=True, + isExternal=False, + workers=False, + register=True): + lines.append("REGISTER_PROTO(%s, %s);\n" % (desc.name, getCheck(desc))) + lines.extend("REGISTER_CONSTRUCTOR(%s, %s, %s);\n" % (n.identifier.name, desc.name, getCheck(desc)) + for n in desc.interface.namedConstructors) + for desc in self.config.getDescriptors(isNavigatorProperty=True, register=True): + propName = desc.interface.getNavigatorProperty() + assert propName + lines.append('REGISTER_NAVIGATOR_CONSTRUCTOR("%s", %s, %s);\n' % (propName, desc.name, getCheck(desc))) + return ''.join(lines) + + def definition_body(self): + return "\n" + self._defineMacro() + "\n" + self._registerProtos() + "\n" + self._undefineMacro() + + +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 _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._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. + """ + components = nativeType.split('::') + self._listAdd(components[:-1], components[-1], isStruct) + + def _build(self, atTopLevel): + """ + Return a codegenerator for the forward declarations. + """ + decls = [] + if self.decls: + decls.append(CGList([CGClassForwardDeclare(cname, isStruct) + for cname, isStruct in sorted(self.decls)])) + for namespace, child in sorted(self.children.iteritems()): + decls.append(CGNamespace(namespace, child._build(atTopLevel=False), declareOnly=True)) + + cg = CGList(decls, "\n") + if not atTopLevel and len(decls) + len(self.decls) > 1: + cg = CGWrapper(cg, pre='\n', post='\n') + return cg + + def build(self): + return self._build(atTopLevel=True) + + +class CGForwardDeclarations(CGWrapper): + """ + Code generate the forward declarations for a header file. + """ + def __init__(self, config, descriptors, mainCallbacks, workerCallbacks, + dictionaries, callbackInterfaces): + builder = ForwardDeclarationBuilder() + + def forwardDeclareForType(t, workerness='both'): + t = t.unroll() + if t.isGeckoInterface(): + name = t.inner.identifier.name + # Find and add the non-worker implementation, if any. + if workerness != 'workeronly': + try: + desc = config.getDescriptor(name, False) + builder.add(desc.nativeType) + except NoSuchDescriptorError: + pass + # Find and add the worker implementation, if any. + if workerness != 'mainthreadonly': + try: + desc = config.getDescriptor(name, True) + builder.add(desc.nativeType) + except NoSuchDescriptorError: + pass + elif t.isCallback(): + builder.addInMozillaDom(str(t)) + elif t.isDictionary(): + builder.addInMozillaDom(t.inner.identifier.name, isStruct=True) + elif t.isCallbackInterface(): + builder.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 + builder.addInMozillaDom(CGUnionStruct.unionTypeName(t, False)) + builder.addInMozillaDom(CGUnionStruct.unionTypeName(t, True)) + elif t.isMozMap(): + forwardDeclareForType(t.inner, workerness) + # Don't need to do anything for void, primitive, string, any or object. + # There may be some other cases we are missing. + + # Needed for at least Wrap. + for d in descriptors: + builder.add(d.nativeType) + + # We just about always need NativePropertyHooks + builder.addInMozillaDom("NativePropertyHooks") + builder.addInMozillaDom("ProtoAndIfaceCache") + + for callback in mainCallbacks: + forwardDeclareForType(callback) + for t in getTypesFromCallback(callback): + forwardDeclareForType(t, workerness='mainthreadonly') + + for callback in workerCallbacks: + forwardDeclareForType(callback) + for t in getTypesFromCallback(callback): + forwardDeclareForType(t, workerness='workeronly') + + for d in callbackInterfaces: + builder.add(d.nativeType) + for t in getTypesFromDescriptor(d): + forwardDeclareForType(t) + + for d in dictionaries: + if len(d.members) > 0: + builder.addInMozillaDom(d.identifier.name + "Atoms", isStruct=True) + for t in getTypesFromDictionary(d): + forwardDeclareForType(t) + + CGWrapper.__init__(self, builder.build()) + + +class CGBindingRoot(CGThing): + """ + Root codegen class for binding generation. Instantiate the class, and call + declare or define to generate header or cpp code (respectively). + """ + def __init__(self, config, prefix, webIDLFile): + bindingHeaders = {} + descriptors = config.getDescriptors(webIDLFile=webIDLFile, + hasInterfaceOrInterfacePrototypeObject=True, + skipGen=False) + + def descriptorRequiresPreferences(desc): + iface = desc.interface + return any(m.getExtendedAttribute("Pref") for m in iface.members + [iface]) + + bindingHeaders["mozilla/Preferences.h"] = any( + descriptorRequiresPreferences(d) for d in descriptors) + bindingHeaders["mozilla/dom/NonRefcountedDOMObject.h"] = any( + d.nativeOwnership == 'owned' for d in descriptors) + bindingHeaders["mozilla/dom/DOMJSProxyHandler.h"] = any( + d.concrete and d.proxy for d in descriptors) + + def descriptorHasChromeOnly(desc): + return (any(isChromeOnly(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. + (desc.interface.isJSImplemented() and + desc.interface.hasInterfaceObject())) + + bindingHeaders["nsContentUtils.h"] = any( + descriptorHasChromeOnly(d) for d in descriptors) + # XXXkhuey ugly hack but this is going away soon. + bindingHeaders['xpcprivate.h'] = webIDLFile.endswith("EventTarget.webidl") + hasWorkerStuff = len(config.getDescriptors(webIDLFile=webIDLFile, + workers=True)) != 0 + bindingHeaders["WorkerPrivate.h"] = hasWorkerStuff + bindingHeaders["nsThreadUtils.h"] = hasWorkerStuff + + dictionaries = config.getDictionaries(webIDLFile=webIDLFile) + bindingHeaders["nsCxPusher.h"] = dictionaries + bindingHeaders["AtomList.h"] = any( + len(dict.members) > 0 for dict in dictionaries) + mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile, + workers=False) + workerCallbacks = config.getCallbacks(webIDLFile=webIDLFile, + workers=True) + callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile, + isCallback=True) + jsImplemented = config.getDescriptors(webIDLFile=webIDLFile, + isJSImplemented=True) + bindingHeaders["nsPIDOMWindow.h"] = jsImplemented + + def addHeaderBasedOnTypes(header, typeChecker): + bindingHeaders[header] = ( + bindingHeaders.get(header, False) or + any(map(typeChecker, + getAllTypes(descriptors + callbackDescriptors, + dictionaries, + mainCallbacks + workerCallbacks)))) + + bindingHeaders["nsDOMQS.h"] = any(d.hasXPConnectImpls for d in descriptors) + # Only mainthread things can have hasXPConnectImpls + provider = config.getDescriptorProvider(False) + + def checkForXPConnectImpls(typeInfo): + type, _, _ = typeInfo + type = type.unroll() + while type.isMozMap(): + type = type.inner.unroll() + if not type.isInterface() or not type.isGeckoInterface(): + return False + try: + typeDesc = provider.getDescriptor(type.inner.identifier.name) + except NoSuchDescriptorError: + return False + return typeDesc.hasXPConnectImpls + addHeaderBasedOnTypes("nsDOMQS.h", checkForXPConnectImpls) + + def descriptorClearsPropsInSlots(descriptor): + if not descriptor.wrapperCache: + return False + return any(m.isAttr() and m.getExtendedAttribute("StoreInSlot") + for m in descriptor.interface.members) + bindingHeaders["nsJSUtils.h"] = any(descriptorClearsPropsInSlots(d) for d in descriptors) + + # Do codegen for all the enums + enums = config.getEnums(webIDLFile) + cgthings = [CGEnum(e) for e in enums] + + hasCode = (descriptors or callbackDescriptors or dictionaries or + mainCallbacks or workerCallbacks) + bindingHeaders["mozilla/dom/BindingUtils.h"] = hasCode + bindingHeaders["mozilla/dom/OwningNonNull.h"] = hasCode + bindingHeaders["mozilla/dom/BindingDeclarations.h"] = ( + not hasCode and enums) + + bindingHeaders["WrapperFactory.h"] = descriptors + bindingHeaders["mozilla/dom/DOMJSClass.h"] = descriptors + + # 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. + cgthings.extend([CGDictionary(d, config.getDescriptorProvider(False)) + for d in + dependencySortObjects(dictionaries, + CGDictionary.getDictionaryDependencies, + lambda d: d.identifier.name)]) + + # Do codegen for all the callbacks. + cgthings.extend(CGCallbackFunction(c, config.getDescriptorProvider(False)) + for c in mainCallbacks) + + cgthings.extend(CGCallbackFunction(c, config.getDescriptorProvider(True)) + for c in workerCallbacks if c not in mainCallbacks) + + # Do codegen for all the descriptors + cgthings.extend([CGDescriptor(x) for x in descriptors]) + + # Do codegen for all the callback interfaces. Skip worker callbacks. + cgthings.extend([CGCallbackInterface(x) for x in callbackDescriptors if + not x.workers]) + + # 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)) + 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, + mainCallbacks, workerCallbacks, + dictionaries, + callbackDescriptors + jsImplemented), + curr], + "\n") + + # Add header includes. + bindingHeaders = [header + for header, include in bindingHeaders.iteritems() + if include] + declareIncludes = [ + 'mozilla/dom/BindingDeclarations.h', + 'mozilla/dom/Nullable.h', + 'mozilla/ErrorResult.h', + 'jspubtd.h', + 'js/RootingAPI.h', + ] + if jsImplemented: + declareIncludes.append('nsWeakReference.h') + + curr = CGHeaders(descriptors, + dictionaries, + mainCallbacks + workerCallbacks, + callbackDescriptors, + declareIncludes, + bindingHeaders, + prefix, + curr, + config, + jsImplemented) + + # Add include guards. + curr = CGIncludeGuard(prefix, curr) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # 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", + jsObjectsArePtr=False, variadicIsSequence=False): + """ + If jsObjectsArePtr is true, typed arrays and "object" will be + passed as JSObject*. + + 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 = isResultAlreadyAddRefed(self.extendedAttrs) + self.passJSBitsAsNeeded = passJSBitsAsNeeded + self.jsObjectsArePtr = jsObjectsArePtr + self.variadicIsSequence = variadicIsSequence + breakAfterSelf = "\n" if breakAfter else "" + ClassMethod.__init__(self, name, + self.getReturnType(signature[0], False), + self.getArgs(signature[0], signature[1]), + static=member.isStatic(), + # Mark our getters, which are attrs that + # have a non-void return type, as const. + const=(not member.isStatic() and member.isAttr() and + not signature[0].isVoid()), + breakAfterReturnDecl=" ", + breakAfterSelf=breakAfterSelf, + visibility=visibility) + + def getReturnType(self, type, isMember): + return self.getRetvalInfo(type, isMember)[0] + + def getRetvalInfo(self, type, isMember): + """ + Returns a tuple: + + The first element is the type declaration for the retval + + The second element is a default value that can be used on error returns. + For cases whose behavior depends on isMember, the second element will be + None if isMember is true. + + The third element is a template for actually returning a value stored in + "${declName}" and "${holderName}". This means actually returning it if + we're not outparam, else assigning to the "retval" outparam. If + isMember is true, this can be None, since in that case the caller will + never examine this value. + """ + if type.isVoid(): + return "void", "", "" + if type.isPrimitive() and type.tag() in builtinNames: + result = CGGeneric(builtinNames[type.tag()]) + defaultReturnArg = "0" + if type.nullable(): + result = CGTemplatedType("Nullable", result) + defaultReturnArg = "" + return (result.define(), + "%s(%s)" % (result.define(), defaultReturnArg), + "return ${declName};\n") + if type.isDOMString(): + if isMember: + # No need for a third element in the isMember case + return "nsString", None, None + # Outparam + return "void", "", "aRetVal = ${declName};\n" + if type.isByteString(): + if isMember: + # No need for a third element in the isMember case + return "nsCString", None, None + # Outparam + return "void", "", "aRetVal = ${declName};\n" + if type.isEnum(): + enumName = type.unroll().inner.identifier.name + if type.nullable(): + enumName = CGTemplatedType("Nullable", + CGGeneric(enumName)).define() + defaultValue = "%s()" % enumName + else: + defaultValue = "%s(0)" % enumName + return enumName, defaultValue, "return ${declName};\n" + if type.isGeckoInterface(): + iface = type.unroll().inner + result = CGGeneric(self.descriptorProvider.getDescriptor( + iface.identifier.name).prettyNativeType) + if self.resultAlreadyAddRefed: + if isMember: + holder = "nsRefPtr" + else: + holder = "already_AddRefed" + if memberReturnsNewObject(self.member): + warning = "" + else: + warning = "// Mark this as resultNotAddRefed to return raw pointers\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 nsRefPtr. 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().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();\n" + else: + returnCode = "${declName}.Obj();\n" + 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 + # for the relevant argument... + assert not isMember + # Outparam. + if type.nullable(): + returnCode = dedent(""" + if (${declName}.IsNull()) { + aRetVal.SetNull(); + } else { + aRetVal.SetValue().SwapElements(${declName}.Value()); + } + """) + else: + returnCode = "aRetVal.SwapElements(${declName});\n" + return "void", "", returnCode + if type.isMozMap(): + # If we want to handle MozMap-of-MozMap return values, we're + # going to need to fix example codegen to not produce MozMap + # for the relevant argument... + assert not isMember + # In this case we convert directly into our outparam to start with + return "void", "", "" + if type.isDate(): + result = CGGeneric("Date") + if type.nullable(): + result = CGTemplatedType("Nullable", result) + return (result.define(), "%s()" % result.define(), + "return ${declName};\n") + if type.isDictionary(): + if isMember: + # Only the first member of the tuple matters here, but return + # bogus values for the others in case someone decides to use + # them. + return CGDictionary.makeDictionaryName(type.inner), None, None + # In this case we convert directly into our outparam to start with + return "void", "", "" + if type.isUnion(): + if isMember: + # Only the first member of the tuple matters here, but return + # bogus values for the others in case someone decides to use + # them. + return CGUnionStruct.unionTypeDecl(type, True), None, None + # In this case we convert directly into our outparam to start with + return "void", "", "" + + raise TypeError("Don't know how to declare return value for %s" % + type) + + def getArgs(self, returnType, argList): + args = [self.getArg(arg) for arg in argList] + # Now the outparams + if returnType.isDOMString(): + args.append(Argument("nsString&", "aRetVal")) + elif returnType.isByteString(): + args.append(Argument("nsCString&", "aRetVal")) + elif returnType.isSequence(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # And now the actual underlying type + elementDecl = self.getReturnType(returnType.inner, True) + type = CGTemplatedType("nsTArray", CGGeneric(elementDecl)) + if nullable: + type = CGTemplatedType("Nullable", type) + args.append(Argument("%s&" % type.define(), "aRetVal")) + elif returnType.isMozMap(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + # And now the actual underlying type + elementDecl = self.getReturnType(returnType.inner, True) + type = CGTemplatedType("MozMap", CGGeneric(elementDecl)) + if nullable: + type = CGTemplatedType("Nullable", type) + args.append(Argument("%s&" % type.define(), "aRetVal")) + elif returnType.isDictionary(): + nullable = returnType.nullable() + if nullable: + returnType = returnType.inner + dictType = CGGeneric(CGDictionary.makeDictionaryName(returnType.inner)) + if nullable: + dictType = CGTemplatedType("Nullable", dictType) + args.append(Argument("%s&" % dictType.define(), "aRetVal")) + elif returnType.isUnion(): + args.append(Argument("%s&" % + CGUnionStruct.unionTypeDecl(returnType, True), + "aRetVal")) + elif returnType.isAny(): + args.append(Argument("JS::MutableHandle", "aRetVal")) + elif returnType.isObject() or returnType.isSpiderMonkeyInterface(): + args.append(Argument("JS::MutableHandle", "aRetVal")) + + # And the ErrorResult + if 'infallible' not in self.extendedAttrs: + # Use aRv so it won't conflict with local vars named "rv" + args.append(Argument("ErrorResult&", "aRv")) + # The legacycaller thisval + if self.member.isMethod() and self.member.isLegacycaller(): + # If it has an identifier, we can't deal with it yet + assert self.member.isIdentifierLess() + args.insert(0, Argument("JS::Value", "aThisVal")) + # And jscontext bits. + if needCx(returnType, argList, self.extendedAttrs, + self.passJSBitsAsNeeded): + 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", "obj")) + # And if we're static, a global + if self.member.isStatic(): + args.insert(0, Argument("const GlobalObject&", "global")) + return args + + def doGetArgType(self, type, optional, isMember): + """ + The main work of getArgType. Returns a string type decl, whether this + is a const ref, as well as whether the type should be wrapped in + Nullable as needed. + + isMember can be false or one of the strings "Sequence", "Variadic", + "MozMap" + """ + if type.isArray(): + raise TypeError("Can't handle array arguments yet") + + if type.isSequence(): + nullable = type.nullable() + if nullable: + type = type.inner + elementType = type.inner + argType = self.getArgType(elementType, False, "Sequence")[0] + decl = CGTemplatedType("Sequence", argType) + return decl.define(), True, True + + if type.isMozMap(): + nullable = type.nullable() + if nullable: + type = type.inner + elementType = type.inner + argType = self.getArgType(elementType, False, "MozMap")[0] + decl = CGTemplatedType("MozMap", argType) + return decl.define(), True, True + + if type.isUnion(): + # unionTypeDecl will handle nullable types, so return False for + # auto-wrapping in Nullable + return CGUnionStruct.unionTypeDecl(type, isMember), True, False + + if type.isGeckoInterface() and not type.isCallbackInterface(): + iface = type.unroll().inner + argIsPointer = type.nullable() or iface.isExternal() + forceOwningType = iface.isCallback() or isMember + if argIsPointer: + if (optional or isMember) and forceOwningType: + typeDecl = "nsRefPtr<%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 self.jsObjectsArePtr: + return "JSObject*", False, False + + return type.name, True, True + + if type.isDOMString(): + if isMember: + declType = "nsString" + else: + declType = "nsAString" + return declType, True, False + + if type.isByteString(): + declType = "nsCString" + return declType, True, False + + if type.isEnum(): + return type.unroll().inner.identifier.name, False, True + + if type.isCallback() or type.isCallbackInterface(): + forceOwningType = optional or isMember + if type.nullable(): + if forceOwningType: + declType = "nsRefPtr<%s>" + else: + declType = "%s*" + else: + if forceOwningType: + declType = "OwningNonNull<%s>" + else: + declType = "%s&" + if type.isCallback(): + name = type.unroll().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" + return declType, False, False + + if type.isObject(): + if isMember: + declType = "JSObject*" + else: + declType = "JS::Handle" + return declType, False, False + + if type.isDictionary(): + typeName = CGDictionary.makeDictionaryName(type.inner) + return typeName, True, True + + if type.isDate(): + return "Date", False, True + + assert type.isPrimitive() + + return builtinNames[type.tag()], False, True + + def getArgType(self, type, optional, isMember): + """ + Get the type of an argument declaration. Returns the type CGThing, and + whether this should be a const ref. + + isMember can be False, "Sequence", or "Variadic" + """ + decl, ref, handleNullable = self.doGetArgType(type, optional, isMember) + decl = CGGeneric(decl) + if handleNullable and type.nullable(): + decl = CGTemplatedType("Nullable", decl) + ref = True + if isMember == "Variadic": + arrayType = "Sequence" if self.variadicIsSequence else "nsTArray" + decl = CGTemplatedType(arrayType, decl) + ref = True + elif optional: + # Note: All variadic args claim to be optional, but we can just use + # empty arrays to represent them not being present. + decl = CGTemplatedType("Optional", decl) + ref = True + return (decl, ref) + + def getArg(self, arg): + """ + Get the full argument declaration for an argument + """ + decl, ref = self.getArgType(arg.type, + arg.optional and not arg.defaultValue, + "Variadic" if arg.variadic else False) + if ref: + decl = CGWrapper(decl, pre="const ", post="&") + + return Argument(decl.define(), arg.identifier.name) + + +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 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 define(self, cgClass): + return '' + + +class CGExampleSetter(CGNativeMember): + def __init__(self, descriptor, attr): + CGNativeMember.__init__(self, descriptor, attr, + CGSpecializedSetter.makeNativeName(descriptor, + attr), + (BuiltinTypes[IDLBuiltinType.Types.void], + [FakeArgument(attr.type, attr)]), + descriptor.getExtendedAttributes(attr, + setter=True)) + + def define(self, cgClass): + return '' + + +class CGBindingImplClass(CGClass): + """ + Common codegen for generating a C++ implementation of a WebIDL interface + """ + def __init__(self, descriptor, cgMethod, cgGetter, cgSetter, wantGetParent=True): + """ + cgMethod, cgGetter and cgSetter are classes used to codegen methods, + getters and setters. + """ + self.descriptor = descriptor + self._deps = descriptor.interface.getDeps() + + iface = descriptor.interface + + self.methodDecls = [] + + def appendMethod(m, isConstructor=False): + sigs = m.signatures() + for s in sigs[:-1]: + # Don't put a blank line after overloads, until we + # get to the last one. + self.methodDecls.append(cgMethod(descriptor, m, s, + isConstructor, + breakAfter=False)) + self.methodDecls.append(cgMethod(descriptor, m, sigs[-1], + isConstructor)) + + if iface.ctor(): + appendMethod(iface.ctor(), isConstructor=True) + for n in iface.namedConstructors: + appendMethod(n, isConstructor=True) + for m in iface.members: + if m.isMethod(): + if m.isIdentifierLess(): + continue + appendMethod(m) + elif m.isAttr(): + self.methodDecls.append(cgGetter(descriptor, m)) + if not m.readonly: + self.methodDecls.append(cgSetter(descriptor, m)) + + # Now do the special operations + def appendSpecialOperation(name, op): + if op is None: + return + if name == "IndexedCreator" or name == "NamedCreator": + # These are identical to the setters + return + assert len(op.signatures()) == 1 + returnType, args = op.signatures()[0] + # Make a copy of the args, since we plan to modify them. + args = list(args) + if op.isGetter() or op.isDeleter(): + # This is a total hack. The '&' belongs with the + # type, not the name! But it works, and is simpler + # than trying to somehow make this pretty. + args.append(FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean], + op, name="&found")) + if name == "Stringifier": + if op.isIdentifierLess(): + # XXXbz I wish we were consistent about our renaming here. + name = "Stringify" + else: + # We already added this method + return + if name == "LegacyCaller": + if op.isIdentifierLess(): + # XXXbz I wish we were consistent about our renaming here. + name = "LegacyCall" + else: + # We already added this method + return + if name == "Jsonifier": + # We already added this method + return + self.methodDecls.append( + CGNativeMember(descriptor, op, + name, + (returnType, args), + descriptor.getExtendedAttributes(op))) + # Sort things by name so we get stable ordering in the output. + ops = descriptor.operations.items() + ops.sort(key=lambda x: x[0]) + for name, op in ops: + appendSpecialOperation(name, op) + # If we support indexed properties, then we need a Length() + # method so we know which indices are supported. + if descriptor.supportsIndexedProperties(): + # But we don't need it if we already have an infallible + # "length" attribute, which we often do. + haveLengthAttr = any( + m for m in iface.members if m.isAttr() and + CGSpecializedGetter.makeNativeName(descriptor, m) == "Length") + if not haveLengthAttr: + self.methodDecls.append( + CGNativeMember(descriptor, FakeMember(), + "Length", + (BuiltinTypes[IDLBuiltinType.Types.unsigned_long], + []), + {"infallible": True})) + # And if we support named properties we need to be able to + # enumerate the supported names and test whether they're enumerable. + if descriptor.supportsNamedProperties(): + self.methodDecls.append( + CGNativeMember( + descriptor, FakeMember(), + "GetSupportedNames", + (IDLSequenceType(None, + BuiltinTypes[IDLBuiltinType.Types.domstring]), + # Let's use unsigned long for the type here, though really + # it's just a C++ "unsigned"... + [FakeArgument(BuiltinTypes[IDLBuiltinType.Types.unsigned_long], + FakeMember(), + name="aFlags")]), + {"infallible": True})) + self.methodDecls.append( + CGNativeMember( + descriptor, FakeMember(), + "NameIsEnumerable", + (BuiltinTypes[IDLBuiltinType.Types.boolean], + [FakeArgument(BuiltinTypes[IDLBuiltinType.Types.domstring], + FakeMember(), + name="aName")]), + { "infallible": True })) + + wrapArgs = [Argument('JSContext*', 'aCx')] + self.methodDecls.insert(0, + ClassMethod("WrapObject", "JSObject*", + wrapArgs, virtual=descriptor.wrapperCache, + breakAfterReturnDecl=" ", + override=descriptor.wrapperCache, + body=self.getWrapObjectBody())) + if wantGetParent: + self.methodDecls.insert(0, + ClassMethod("GetParentObject", + self.getGetParentObjectReturnType(), + [], const=True, + breakAfterReturnDecl=" ", + body=self.getGetParentObjectBody())) + + # Invoke CGClass.__init__ in any subclasses afterwards to do the actual codegen. + + def getWrapObjectBody(self): + return None + + def getGetParentObjectReturnType(self): + return ("// TODO: return something sensible here, and change the return type\n" + "%s*" % self.descriptor.nativeType.split('::')[-1]) + + def getGetParentObjectBody(self): + return None + + def deps(self): + return self._deps + + +class CGExampleClass(CGBindingImplClass): + """ + Codegen for the actual example class implementation for this descriptor + """ + def __init__(self, descriptor): + CGBindingImplClass.__init__(self, descriptor, + CGExampleMethod, CGExampleGetter, CGExampleSetter, + wantGetParent=descriptor.wrapperCache) + + self.refcounted = descriptor.nativeOwnership == "refcounted" + + self.parentIface = descriptor.interface.parent + if self.parentIface: + self.parentDesc = descriptor.getDescriptor( + self.parentIface.identifier.name) + bases = [ClassBase(self.nativeLeafName(self.parentDesc))] + else: + bases = [] + if self.refcounted: + bases.append(ClassBase("nsISupports /* Change nativeOwnership in the binding configuration if you don't want this */")) + if descriptor.wrapperCache: + bases.append(ClassBase("nsWrapperCache /* Change wrapperCache in the binding configuration if you don't want this */")) + else: + bases.append(ClassBase("NonRefcountedDOMObject")) + + if self.refcounted: + if self.parentIface: + extradeclarations = ( + "public:\n" + " NS_DECL_ISUPPORTS_INHERITED\n" + " NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(%s, %s)\n" + "\n" % (self.nativeLeafName(descriptor), + self.nativeLeafName(self.parentDesc))) + else: + extradeclarations = ( + "public:\n" + " NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n" + " NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(%s)\n" + "\n" % self.nativeLeafName(descriptor)) + else: + extradeclarations = "" + + if descriptor.interface.hasChildInterfaces(): + decorators = "" + else: + decorators = "MOZ_FINAL" + + CGClass.__init__(self, self.nativeLeafName(descriptor), + bases=bases, + constructors=[ClassConstructor([], + visibility="public")], + destructor=ClassDestructor(visibility="public"), + methods=self.methodDecls, + decorators=decorators, + extradeclarations=extradeclarations) + + def define(self): + # Just override CGClass and do our own thing + if self.descriptor.wrapperCache: + setDOMBinding = " SetIsDOMBinding();\n" + else: + setDOMBinding = "" + if self.refcounted: + ctordtor = dedent(""" + ${nativeType}::${nativeType}() + { + %s} + + ${nativeType}::~${nativeType}() + { + } + """) % setDOMBinding + else: + ctordtor = dedent(""" + ${nativeType}::${nativeType}() + { + MOZ_COUNT_CTOR(${nativeType}); + } + + ${nativeType}::~${nativeType}() + { + MOZ_COUNT_DTOR(${nativeType}); + } + """) + + if self.refcounted: + if self.parentIface: + ccImpl = dedent(""" + + NS_IMPL_CYCLE_COLLECTION_INHERITED_0(${nativeType}, ${parentType}) + NS_IMPL_ADDREF_INHERITED(${nativeType}, ${parentType}) + NS_IMPL_RELEASE_INHERITED(${nativeType}, ${parentType}) + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${nativeType}) + NS_INTERFACE_MAP_END_INHERITING(${parentType}) + + """) + else: + ccImpl = dedent(""" + + 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 + + """) + else: + ccImpl = "" + + classImpl = ccImpl + ctordtor + "\n" + dedent(""" + JSObject* + ${nativeType}::WrapObject(JSContext* aCx) + { + return ${ifaceName}Binding::Wrap(aCx, this); + } + + """) + return string.Template(classImpl).substitute( + ifaceName=self.descriptor.name, + nativeType=self.nativeLeafName(self.descriptor), + parentType=self.nativeLeafName(self.parentDesc) if self.parentIface else "") + + @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): + # Let's assume we're not doing workers stuff + descriptor = config.getDescriptor(interfaceName, False) + + self.root = CGWrapper(CGExampleClass(descriptor), + pre="\n", post="\n") + + self.root = CGNamespace.build(["mozilla", "dom"], self.root) + + self.root = CGList([CGClassForwardDeclare("JSContext", isStruct=True), + self.root], "\n") + + # Throw in our #includes + self.root = CGHeaders([], [], [], [], + ["nsWrapperCache.h", + "nsCycleCollectionParticipant.h", + "mozilla/Attributes.h", + "mozilla/ErrorResult.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", jsObjectsArePtr=False, + variadicIsSequence=False): + CGNativeMember.__init__(self, descriptorProvider, member, name, + signature, extendedAttrs, breakAfter=breakAfter, + passJSBitsAsNeeded=passJSBitsAsNeeded, + visibility=visibility, + jsObjectsArePtr=jsObjectsArePtr, + variadicIsSequence=variadicIsSequence) + self.body = self.getImpl() + + def getArgs(self, returnType, argList): + args = CGNativeMember.getArgs(self, returnType, argList) + args.append(Argument("JSCompartment*", "aCompartment", "nullptr")) + return args + + +class CGJSImplMethod(CGJSImplMember): + """ + Class for generating code for the methods for a JS-implemented WebIDL + interface. + """ + def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True): + 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 JSCompartment bits for constructors; it's handled + # manually in getImpl. + return CGNativeMember.getArgs(self, returnType, argList) + return CGJSImplMember.getArgs(self, returnType, argList) + + def getImpl(self): + args = self.getArgs(self.signature[0], self.signature[1]) + if not self.isConstructor: + return 'return mImpl->%s(%s);\n' % (self.name, ", ".join(arg.name for arg in args)) + + assert self.descriptor.interface.isJSImplemented() + if self.name != 'Constructor': + raise TypeError("Named constructors are not supported for JS implemented WebIDL. See bug 851287.") + if len(self.signature[1]) != 0: + # The first two arguments to the constructor implementation are not + # arguments to the WebIDL constructor, so don't pass them to __Init() + assert args[0].argType == 'const GlobalObject&' + assert args[1].argType == 'JSContext*' + constructorArgs = [arg.name for arg in args[2:]] + constructorArgs.append("js::GetObjectCompartment(scopeObj)") + initCall = fill( + """ + // Wrap the object before calling __Init so that __DOM_IMPL__ is available. + nsCOMPtr globalHolder = do_QueryInterface(window); + JS::Rooted scopeObj(cx, globalHolder->GetGlobalJSObject()); + MOZ_ASSERT(js::IsObjectInContextCompartment(scopeObj, cx)); + JS::Rooted wrappedVal(cx); + if (!WrapNewBindingObject(cx, impl, &wrappedVal)) { + //XXX Assertion disabled for now, see bug 991271. + MOZ_ASSERT(true || JS_IsExceptionPending(cx)); + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + // Initialize the object with the constructor arguments. + impl->mImpl->__Init(${args}); + if (aRv.Failed()) { + return nullptr; + } + """, + args=", ".join(constructorArgs)) + else: + initCall = "" + return genConstructorBody(self.descriptor, initCall) + + +def genConstructorBody(descriptor, initCall=""): + return fill( + """ + JS::Rooted jsImplObj(cx); + nsCOMPtr window = + ConstructJSImplementation(cx, "${contractId}", global, &jsImplObj, aRv); + if (aRv.Failed()) { + return nullptr; + } + // Build the C++ implementation. + nsRefPtr<${implClass}> impl = new ${implClass}(jsImplObj, window); + $*{initCall} + return impl.forget(); + """, + contractId=descriptor.interface.getJSImplementation(), + implClass=descriptor.name, + initCall=initCall) + + +# We're always fallible +def callbackGetterName(attr, descriptor): + return "Get" + MakeNativeName( + descriptor.binaryNames.get(attr.identifier.name, attr.identifier.name)) + + +def callbackSetterName(attr, descriptor): + return "Set" + MakeNativeName( + descriptor.binaryNames.get(attr.identifier.name, attr.identifier.name)) + + +class CGJSImplGetter(CGJSImplMember): + """ + Class for generating code for the getters of attributes for a JS-implemented + WebIDL interface. + """ + def __init__(self, descriptor, attr): + CGJSImplMember.__init__(self, descriptor, attr, + CGSpecializedGetter.makeNativeName(descriptor, + attr), + (attr.type, []), + descriptor.getExtendedAttributes(attr, + getter=True), + passJSBitsAsNeeded=False) + + def getImpl(self): + callbackArgs = [arg.name for arg in self.getArgs(self.member.type, [])] + return 'return mImpl->%s(%s);\n' % ( + callbackGetterName(self.member, self.descriptorProvider), + ", ".join(callbackArgs)) + + +class CGJSImplSetter(CGJSImplMember): + """ + Class for generating code for the setters of attributes for a JS-implemented + WebIDL interface. + """ + def __init__(self, descriptor, attr): + CGJSImplMember.__init__(self, descriptor, attr, + CGSpecializedSetter.makeNativeName(descriptor, + attr), + (BuiltinTypes[IDLBuiltinType.Types.void], + [FakeArgument(attr.type, attr)]), + descriptor.getExtendedAttributes(attr, + setter=True), + passJSBitsAsNeeded=False) + + def getImpl(self): + callbackArgs = [arg.name for arg in self.getArgs(BuiltinTypes[IDLBuiltinType.Types.void], + [FakeArgument(self.member.type, self.member)])] + return 'mImpl->%s(%s);\n' % ( + callbackSetterName(self.member, self.descriptorProvider), + ", ".join(callbackArgs)) + + +class CGJSImplClass(CGBindingImplClass): + def __init__(self, descriptor): + CGBindingImplClass.__init__(self, descriptor, CGJSImplMethod, CGJSImplGetter, CGJSImplSetter) + + if descriptor.interface.parent: + parentClass = descriptor.getDescriptor( + descriptor.interface.parent.identifier.name).jsImplParent + baseClasses = [ClassBase(parentClass)] + isupportsDecl = "NS_DECL_ISUPPORTS_INHERITED\n" + ccDecl = ("NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(%s, %s)\n" % + (descriptor.name, parentClass)) + constructorBody = dedent(""" + // Make sure we're an nsWrapperCache already + MOZ_ASSERT(static_cast(this)); + // And that our ancestor has called SetIsDOMBinding() + MOZ_ASSERT(IsDOMBinding()); + """) + extradefinitions = fill( + """ + NS_IMPL_CYCLE_COLLECTION_INHERITED(${ifaceName}, ${parentClass}, mImpl, mParent) + NS_IMPL_ADDREF_INHERITED(${ifaceName}, ${parentClass}) + NS_IMPL_RELEASE_INHERITED(${ifaceName}, ${parentClass}) + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(${ifaceName}) + NS_INTERFACE_MAP_END_INHERITING(${parentClass}) + """, + ifaceName=self.descriptor.name, + parentClass=parentClass) + else: + baseClasses = [ClassBase("nsSupportsWeakReference"), + ClassBase("nsWrapperCache")] + isupportsDecl = "NS_DECL_CYCLE_COLLECTING_ISUPPORTS\n" + ccDecl = ("NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(%s)\n" % + descriptor.name) + constructorBody = "SetIsDOMBinding();\n" + extradefinitions = fill( + """ + NS_IMPL_CYCLE_COLLECTION_CLASS(${ifaceName}) + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(${ifaceName}) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + tmp->ClearWeakReferences(); + NS_IMPL_CYCLE_COLLECTION_UNLINK_END + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(${ifaceName}) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(${ifaceName}) + NS_IMPL_CYCLE_COLLECTING_ADDREF(${ifaceName}) + NS_IMPL_CYCLE_COLLECTING_RELEASE(${ifaceName}) + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(${ifaceName}) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_END + """, + ifaceName=self.descriptor.name) + + extradeclarations = fill( + """ + public: + $*{isupportsDecl} + $*{ccDecl} + + private: + nsRefPtr<${jsImplName}> mImpl; + nsCOMPtr mParent; + + """, + isupportsDecl=isupportsDecl, + ccDecl=ccDecl, + jsImplName=jsImplName(descriptor.name)) + + if descriptor.interface.hasChildInterfaces(): + decorators = "" + # We need a public virtual destructor our subclasses can use + destructor = ClassDestructor(virtual=True, visibility="public") + else: + decorators = "MOZ_FINAL" + destructor = None + + baseConstructors = [ + ("mImpl(new %s(aJSImplObject, /* aIncumbentGlobal = */ nullptr))" % + jsImplName(descriptor.name)), + "mParent(aParent)"] + parentInterface = descriptor.interface.parent + while parentInterface: + if parentInterface.isJSImplemented(): + baseConstructors.insert( + 0, "%s(aJSImplObject, aParent)" % parentClass) + break + parentInterface = parentInterface.parent + if not parentInterface and descriptor.interface.parent: + # We only have C++ ancestors, so only pass along the window + baseConstructors.insert(0, + "%s(aParent)" % parentClass) + + constructor = ClassConstructor( + [Argument("JS::Handle", "aJSImplObject"), + Argument("nsPIDOMWindow*", "aParent")], + visibility="public", + baseConstructors=baseConstructors, + body=constructorBody) + + self.methodDecls.append( + ClassMethod("_Create", + "bool", + [Argument("JSContext*", "cx"), + Argument("unsigned", "argc"), + Argument("JS::Value*", "vp")], + 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 obj(aCx, ${name}Binding::Wrap(aCx, this)); + if (!obj) { + return nullptr; + } + + // Now define it on our chrome object + JSAutoCompartment ac(aCx, mImpl->Callback()); + if (!JS_WrapObject(aCx, &obj)) { + return nullptr; + } + if (!JS_DefineProperty(aCx, mImpl->Callback(), "__DOM_IMPL__", obj, 0)) { + return nullptr; + } + return obj; + """, + name=self.descriptor.name) + + def getGetParentObjectReturnType(self): + return "nsISupports*" + + def getGetParentObjectBody(self): + return "return mParent;\n" + + def getCreateFromExistingBody(self): + # XXXbz we could try to get parts of this (e.g. the argument + # conversions) auto-generated by somehow creating an IDLMethod and + # adding it to our interface, but we'd still need to special-case the + # implementation slightly to have it not try to forward to the JS + # object... + return fill( + """ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() < 2) { + return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${ifaceName}._create"); + } + if (!args[0].isObject()) { + return ThrowErrorMessage(cx, MSG_NOT_OBJECT, "Argument 1 of ${ifaceName}._create"); + } + if (!args[1].isObject()) { + return ThrowErrorMessage(cx, MSG_NOT_OBJECT, "Argument 2 of ${ifaceName}._create"); + } + + // GlobalObject will go through wrappers as needed for us, and + // is simpler than the right UnwrapArg incantation. + GlobalObject global(cx, &args[0].toObject()); + if (global.Failed()) { + return false; + } + nsCOMPtr window = do_QueryInterface(global.GetAsSupports()); + if (!window) { + return ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "Argument 1 of ${ifaceName}._create", "Window"); + } + JS::Rooted arg(cx, &args[1].toObject()); + nsRefPtr<${implName}> impl = new ${implName}(arg, window); + MOZ_ASSERT(js::IsObjectInContextCompartment(arg, cx)); + return WrapNewBindingObject(cx, impl, args.rval()); + """, + ifaceName=self.descriptor.interface.identifier.name, + implName=self.descriptor.name) + + +def isJSImplementedDescriptor(descriptorProvider): + return (isinstance(descriptorProvider, Descriptor) and + descriptorProvider.interface.isJSImplemented()) + + +class CGCallback(CGClass): + def __init__(self, idlObject, descriptorProvider, baseName, methods, + getters=[], setters=[]): + self.baseName = baseName + self._deps = idlObject.getDeps() + self.idlObject = idlObject + name = idlObject.identifier.name + if isJSImplementedDescriptor(descriptorProvider): + name = jsImplName(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 method.needThisHandling: + realMethods.append(method) + else: + realMethods.extend(self.getMethodImpls(method)) + realMethods.append( + ClassMethod("operator==", "bool", + [Argument("const %s&" % name, "aOther")], + inline=True, bodyInHeader=True, + const=True, + body=("return %s::operator==(aOther);\n" % baseName))) + CGClass.__init__(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_ObjectIsCallable(nullptr, mCallback));\n" + else: + # Not much we can assert about it, other than not being null, and + # CallbackObject does that already. + body = "" + return [ClassConstructor( + [Argument("JS::Handle", "aCallback"), + Argument("nsIGlobalObject*", "aIncumbentGlobal")], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=[ + "%s(aCallback, aIncumbentGlobal)" % self.baseName, + ], + body=body)] + + def getMethodImpls(self, method): + assert method.needThisHandling + args = list(method.args) + # Strip out the JSContext*/JSObject* args + # that got added. + assert args[0].name == "cx" and args[0].argType == "JSContext*" + assert args[1].name == "aThisVal" and args[1].argType == "JS::Handle" + args = args[2:] + # Record the names of all the arguments, so we can use them when we call + # the private method. + argnames = [arg.name for arg in args] + argnamesWithThis = ["s.GetContext()", "thisValJS"] + argnames + argnamesWithoutThis = ["s.GetContext()", "JS::UndefinedHandleValue"] + argnames + # Now that we've recorded the argnames for our call to our private + # method, insert our optional argument for deciding whether the + # CallSetup should re-throw exceptions on aRv. + args.append(Argument("ExceptionHandling", "aExceptionHandling", + "eReportExceptions")) + # And now insert our template argument. + argsWithoutThis = list(args) + args.insert(0, Argument("const T&", "thisObjPtr")) + errorReturn = method.getDefaultRetval() + + setupCall = fill( + """ + CallSetup s(this, aRv, aExceptionHandling); + if (!s.GetContext()) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return${errorReturn}; + } + """, + errorReturn=errorReturn) + + bodyWithThis = fill( + """ + $*{setupCall} + JS::Rooted thisObjJS(s.GetContext(), + WrapCallThisObject(s.GetContext(), thisObjPtr)); + if (!thisObjJS) { + aRv.Throw(NS_ERROR_FAILURE); + return${errorReturn}; + } + JS::Rooted thisValJS(s.GetContext(), + JS::ObjectValue(*thisObjJS)); + 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)) + + return [ClassMethod(method.name, method.returnType, args, + bodyInHeader=True, + templateArgs=["typename T"], + body=bodyWithThis), + ClassMethod(method.name, method.returnType, argsWithoutThis, + bodyInHeader=True, + body=bodyWithoutThis), + method] + + def deps(self): + return self._deps + + +class CGCallbackFunction(CGCallback): + def __init__(self, callback, descriptorProvider): + self.callback = callback + CGCallback.__init__(self, callback, descriptorProvider, + "CallbackFunction", + methods=[CallCallback(callback, descriptorProvider)]) + + def getConstructors(self): + return CGCallback.getConstructors(self) + [ + ClassConstructor( + [Argument("CallbackFunction*", "aOther")], + bodyInHeader=True, + visibility="public", + explicit=True, + baseConstructors=["CallbackFunction(aOther)"])] + + +class CGCallbackInterface(CGCallback): + def __init__(self, descriptor): + iface = descriptor.interface + attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()] + getters = [CallbackGetter(a, descriptor) for a in attrs] + setters = [CallbackSetter(a, descriptor) for a in attrs + if not a.readonly] + methods = [m for m in iface.members + if m.isMethod() and not m.isStatic() and not m.isIdentifierLess()] + methods = [CallbackOperation(m, sig, descriptor) for m in methods + for sig in m.signatures()] + if iface.isJSImplemented() and iface.ctor(): + sigs = descriptor.interface.ctor().signatures() + if len(sigs) != 1: + raise TypeError("We only handle one constructor. See bug 869268.") + methods.append(CGJSImplInitOperation(sigs[0], descriptor)) + CGCallback.__init__(self, iface, descriptor, "CallbackInterface", + methods, getters=getters, setters=setters) + + +class FakeMember(): + def __init__(self): + self.treatNullAs = "Default" + + 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 "mark this + # resultNotAddRefed" comments CGNativeMember codegen would + # otherwise stick in. + if name == "NewObject": + return True + return None + + +class CallbackMember(CGNativeMember): + def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=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 + # We don't care, for callback codegen, whether our original member was + # a method or attribute or whatnot. Just always pass FakeMember() + # here. + CGNativeMember.__init__(self, descriptorProvider, FakeMember(), + name, (self.retvalType, args), + extendedAttrs={}, + passJSBitsAsNeeded=False, + visibility=visibility, + jsObjectsArePtr=True) + # We have to do all the generation of our body now, because + # the caller relies on us throwing if we can't manage it. + self.exceptionCode = ("aRv.Throw(NS_ERROR_UNEXPECTED);\n" + "return%s;\n" % self.getDefaultRetval()) + self.body = self.getImpl() + + def getImpl(self): + setupCall = self.getCallSetup() + declRval = self.getRvalDecl() + if self.argCount > 0: + argvDecl = fill( + """ + JS::AutoValueVector argv(cx); + if (!argv.resize(${argCount})) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return${errorReturn}; + } + """, + argCount=self.argCountStr, + errorReturn=self.getDefaultRetval()) + else: + # Avoid weird 0-sized arrays + argvDecl = "" + convertArgs = self.getArgConversions() + doCall = self.getCall() + returnResult = self.getResultConversion() + + return setupCall + declRval + argvDecl + convertArgs + doCall + returnResult + + def getResultConversion(self): + replacements = { + "val": "rval", + "mutableVal": "&rval", + "holderName": "rvalHolder", + "declName": "rvalDecl", + # We actually want to pass in a null scope object here, because + # wrapping things into our current compartment (that of mCallback) + # is what we want. + "obj": "nullptr" + } + + if isJSImplementedDescriptor(self.descriptorProvider): + isCallbackReturnValue = "JSImpl" + else: + isCallbackReturnValue = "Callback" + sourceDescription = "return value of %s" % self.getPrettyName() + convertType = instantiateJSToNativeConversion( + getJSToNativeConversionInfo(self.retvalType, + self.descriptorProvider, + exceptionCode=self.exceptionCode, + isCallbackReturnValue=isCallbackReturnValue, + sourceDescription=sourceDescription), + replacements) + assignRetval = string.Template( + self.getRetvalInfo(self.retvalType, + False)[2]).substitute(replacements) + type = convertType.define() + if type == "": + type = "\n" # BOGUS extra blank line + if assignRetval == "": + assignRetval = "\n" # BOGUS extra blank line + 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\n" # BOGUS extra blank line + + # Do them back to front, so our argc modifications will work + # correctly, because we examine trailing arguments first. + argConversions.reverse() + # Wrap each one in a scope so that any locals it has don't leak out, and + # also so that we can just "break;" for our successCode. + argConversions = [CGWrapper(CGIndenter(CGGeneric(c)), + pre="do {\n", + post="} while (0);\n") + for c in argConversions] + if self.argCount > 0: + argConversions.insert(0, self.getArgcDecl()) + # And slap them together. + return CGList(argConversions, "\n").define() + "\n" + + def getArgConversion(self, i, arg): + argval = arg.identifier.name + + if arg.variadic: + argval = argval + "[idx]" + jsvalIndex = "%d + idx" % i + else: + jsvalIndex = "%d" % i + if arg.optional and not arg.defaultValue: + argval += ".Value()" + if arg.type.isDOMString(): + # XPConnect string-to-JS conversion wants to mutate the string. So + # let's give it a string it can mutate + # XXXbz if we try to do a sequence of strings, this will kinda fail. + result = "mutableStr" + prepend = "nsString mutableStr(%s);\n" % argval + else: + result = argval + prepend = "" + + try: + conversion = prepend + wrapForType( + arg.type, self.descriptorProvider, + { + 'result': result, + 'successCode': "continue;\n" if arg.variadic else "break;\n", + 'jsvalRef': "argv.handleAt(%s)" % jsvalIndex, + 'jsvalHandle': "argv.handleAt(%s)" % jsvalIndex, + # XXXbz we don't have anything better to use for 'obj', + # really... It's OK to use CallbackPreserveColor because + # CallSetup already handled the unmark-gray bits for us. + 'obj': 'CallbackPreserveColor()', + 'returnsNewObject': False, + 'exceptionCode': self.exceptionCode + }) + except MethodNotNewObjectError as err: + raise TypeError("%s being passed as an argument to %s but is not " + "wrapper cached, so can't be reliably converted to " + "a JS object." % + (err.typename, self.getPrettyName())) + if arg.variadic: + conversion = fill( + """ + for (uint32_t idx = 0; idx < ${arg}.Length(); ++idx) { + $*{conversion} + } + break; + """, + arg=arg.identifier.name, + conversion=conversion) + elif arg.optional and not arg.defaultValue: + conversion = fill( + """ + if (${argName}.WasPassed()) { + $*{conversion} + } else if (argc == ${iPlus1}) { + // This is our current trailing argument; reduce argc + --argc; + } else { + argv[${i}] = JS::UndefinedValue(); + } + """, + 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 self.rethrowContentException: + args.append(Argument("JSCompartment*", "aCompartment", "nullptr")) + else: + args.append(Argument("ExceptionHandling", "aExceptionHandling", + "eReportExceptions")) + return args + # We want to allow the caller to pass in a "this" value, as + # well as a JSContext. + return [Argument("JSContext*", "cx"), + Argument("JS::Handle", "aThisVal")] + args + + def getCallSetup(self): + if self.needThisHandling: + # It's been done for us already + return "" + callSetup = "CallSetup s(this, aRv" + if self.rethrowContentException: + # getArgs doesn't add the aExceptionHandling argument but does add + # aCompartment for us. + callSetup += ", eRethrowContentExceptions, aCompartment, /* aIsJSImplementedWebIDL = */ " + callSetup += toStringBool(isJSImplementedDescriptor(self.descriptorProvider)) + else: + callSetup += ", aExceptionHandling" + callSetup += ");\n" + return fill( + """ + $*{callSetup} + JSContext* cx = s.GetContext(); + if (!cx) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return${errorReturn}; + } + """, + callSetup=callSetup, + errorReturn=self.getDefaultRetval()) + + def getArgcDecl(self): + return CGGeneric("unsigned argc = %s;\n" % self.argCountStr) + + @staticmethod + def ensureASCIIName(idlObject): + type = "attribute" if idlObject.isAttr() else "operation" + if re.match("[^\x20-\x7E]", idlObject.identifier.name): + raise SyntaxError('Callback %s name "%s" contains non-ASCII ' + "characters. We can't handle that. %s" % + (type, idlObject.identifier.name, + idlObject.location)) + if re.match('"', idlObject.identifier.name): + raise SyntaxError("Callback %s name '%s' contains " + "double-quote character. We can't handle " + "that. %s" % + (type, idlObject.identifier.name, + idlObject.location)) + + +class CallbackMethod(CallbackMember): + def __init__(self, sig, name, descriptorProvider, needThisHandling, + rethrowContentException=False): + CallbackMember.__init__(self, sig, name, descriptorProvider, + needThisHandling, rethrowContentException) + + def getRvalDecl(self): + return "JS::Rooted rval(cx, JS::UndefinedValue());\n" + + def getCall(self): + if self.argCount > 0: + args = "JS::HandleValueArray::subarray(argv, 0, argc)" + else: + args = "JS::HandleValueArray::empty()" + + return fill( + """ + $*{declCallable} + $*{declThis} + if (${callGuard}!JS::Call(cx, ${thisVal}, callable, + ${args}, &rval)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return${errorReturn}; + } + """, + declCallable=self.getCallableDecl(), + declThis=self.getThisDecl(), + callGuard=self.getCallGuard(), + thisVal=self.getThisVal(), + args=args, + errorReturn=self.getDefaultRetval()) + + +class CallCallback(CallbackMethod): + def __init__(self, callback, descriptorProvider): + self.callback = callback + CallbackMethod.__init__(self, callback.signatures()[0], "Call", + descriptorProvider, needThisHandling=True) + + def getThisDecl(self): + return "" + + def getThisVal(self): + return "aThisVal" + + def getCallableDecl(self): + return "JS::Rooted callable(cx, JS::ObjectValue(*mCallback));\n" + + def getPrettyName(self): + return self.callback.identifier.name + + def getCallGuard(self): + if self.callback._treatNonObjectAsNull: + return "JS_ObjectIsCallable(cx, mCallback) && " + return "" + + +class CallbackOperationBase(CallbackMethod): + """ + Common class for implementing various callback operations. + """ + def __init__(self, signature, jsName, nativeName, descriptor, singleOperation, rethrowContentException=False): + self.singleOperation = singleOperation + self.methodName = descriptor.binaryNames.get(jsName, jsName) + CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation, rethrowContentException) + + def getThisDecl(self): + if not self.singleOperation: + return "JS::Rooted 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 thisValue(cx, isCallable ? aThisVal.get() + : JS::ObjectValue(*mCallback)); + """) + + def getThisVal(self): + return "thisValue" + + def getCallableDecl(self): + getCallableFromProp = fill( + """ + if (!GetCallableProperty(cx, "${methodName}", &callable)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return${errorReturn}; + } + """, + methodName=self.methodName, + errorReturn=self.getDefaultRetval()) + if not self.singleOperation: + return 'JS::Rooted callable(cx);\n' + getCallableFromProp + return fill( + """ + bool isCallable = JS_ObjectIsCallable(cx, mCallback); + JS::Rooted 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): + self.ensureASCIIName(method) + self.method = method + jsName = method.identifier.name + CallbackOperationBase.__init__(self, signature, + jsName, + MakeNativeName(descriptor.binaryNames.get(jsName, jsName)), + descriptor, descriptor.interface.isSingleOperationInterface(), + rethrowContentException=descriptor.interface.isJSImplemented()) + + 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): + self.ensureASCIIName(attr) + self.attrName = attr.identifier.name + CallbackMember.__init__(self, sig, name, descriptor, + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented()) + + def getPrettyName(self): + return "%s.%s" % (self.descriptorProvider.interface.identifier.name, + self.attrName) + + +class CallbackGetter(CallbackAccessor): + def __init__(self, attr, descriptor): + CallbackAccessor.__init__(self, attr, + (attr.type, []), + callbackGetterName(attr, descriptor), + descriptor) + + def getRvalDecl(self): + return "JS::Rooted rval(cx, JS::UndefinedValue());\n" + + def getCall(self): + return fill( + """ + JS::Rooted callback(cx, mCallback); + if (!JS_GetProperty(cx, callback, "${attrName}", &rval)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return${errorReturn}; + } + """, + attrName=self.descriptorProvider.binaryNames.get(self.attrName, + self.attrName), + errorReturn=self.getDefaultRetval()) + + +class CallbackSetter(CallbackAccessor): + def __init__(self, attr, descriptor): + CallbackAccessor.__init__(self, attr, + (BuiltinTypes[IDLBuiltinType.Types.void], + [FakeArgument(attr.type, attr)]), + callbackSetterName(attr, descriptor), + descriptor) + + def getRvalDecl(self): + # We don't need an rval + return "" + + def getCall(self): + return fill( + """ + MOZ_ASSERT(argv.length() == 1); + if (!JS_SetProperty(cx, CallbackPreserveColor(), "${attrName}", argv.handleAt(0))) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return${errorReturn}; + } + """, + attrName=self.descriptorProvider.binaryNames.get(self.attrName, + self.attrName), + errorReturn=self.getDefaultRetval()) + + def getArgcDecl(self): + return None + + +class CGJSImplInitOperation(CallbackOperationBase): + """ + Codegen the __Init() method used to pass along constructor arguments for JS-implemented WebIDL. + """ + def __init__(self, sig, descriptor): + assert sig in descriptor.interface.ctor().signatures() + CallbackOperationBase.__init__(self, (BuiltinTypes[IDLBuiltinType.Types.void], sig[1]), + "__init", "__Init", descriptor, False, True) + + def getPrettyName(self): + return "__init" + + +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 = [] + + for dict in dictionaries: + dictMembers = dict.members + if len(dictMembers) == 0: + continue + + classMembers = [ClassMember(CGDictionary.makeIdName(m.identifier.name), + "InternedStringId", + visibility="public") for m in dictMembers] + + structName = dict.identifier.name + "Atoms" + structs.append((structName, + CGWrapper(CGClass(structName, + bases=None, + isStruct=True, + members=classMembers), post='\n'))) + + 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 InternedStringId. + declareIncludes = ['mozilla/dom/BindingUtils.h'] + curr = CGHeaders([], [], [], [], declareIncludes, [], 'GeneratedAtomList', + curr) + + # Add include guards. + curr = CGIncludeGuard('GeneratedAtomList', curr) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Done. + return curr + + @staticmethod + def 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]) + + # This is only used by DOM worker code, once there are no more consumers + # of INTERFACE_CHAIN_* this code should be removed. + def ifaceChainMacro(ifaceCount): + supplied = [CGGeneric(declare="_iface_" + str(i + 1)) for i in range(ifaceCount)] + remaining = [CGGeneric(declare="prototypes::id::_ID_Count")] * (config.maxProtoChainLength - ifaceCount) + macro = CGWrapper(CGList(supplied, ", "), + pre="#define INTERFACE_CHAIN_" + str(ifaceCount) + "(", + post=") \\\n", + declareOnly=True) + macroContent = CGIndenter(CGList(supplied + remaining, ", \\\n")) + macroContent = CGIndenter(CGWrapper(macroContent, pre="{ \\\n", + post=" \\\n}", + declareOnly=True)) + return CGWrapper(CGList([macro, macroContent]), post="\n\n", + declareOnly=True) + + idEnum.append(ifaceChainMacro(1)) + + def fieldSizeAssert(amount, jitInfoField, message): + maxFieldValue = "(uint64_t(1) << (sizeof(((JSJitInfo*)nullptr)->%s) * 8))" % jitInfoField + return CGGeneric(declare="static_assert(%s < %s, \"%s\");\n\n" + % (amount, maxFieldValue, message)) + + idEnum.append(fieldSizeAssert("id::_ID_Count", "protoID", + "Too many prototypes!")) + + # Wrap all of that in our namespaces. + idEnum = CGNamespace.build(['mozilla', 'dom', 'prototypes'], + CGWrapper(idEnum, pre='\n')) + idEnum = CGWrapper(idEnum, post='\n') + + curr = CGList([CGGeneric(define="#include \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) + + traitsDecls = [CGGeneric(declare=dedent(""" + template + struct PrototypeTraits; + """))] + traitsDecls.extend(CGPrototypeTraitsClass(d) for d in descriptorsWithPrototype) + + ifaceNamesWithProto = [d.interface.identifier.name + for d in descriptorsWithPrototype] + traitsDecls.append(CGStringTable("NamesOfInterfacesWithProtos", + ifaceNamesWithProto)) + + traitsDecl = CGNamespace.build(['mozilla', 'dom'], + CGList(traitsDecls)) + + curr.append(traitsDecl) + + # Add include guards. + curr = CGIncludeGuard('PrototypeList', curr) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Done. + return curr + + @staticmethod + def RegisterBindings(config): + + # TODO - Generate the methods we want + curr = CGRegisterProtos(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, + workers=False, + register=True)] + defineIncludes.append('nsScriptNameSpaceManager.h') + defineIncludes.extend([CGHeaders.getDeclarationFilename(desc.interface) + for desc in config.getDescriptors(isNavigatorProperty=True, + workers=False, + register=True)]) + curr = CGHeaders([], [], [], [], [], defineIncludes, 'RegisterBindings', + curr) + + # Add include guards. + curr = CGIncludeGuard('RegisterBindings', curr) + + # Done. + return curr + + @staticmethod + def UnionTypes(config): + + (includes, implincludes, + declarations, unions) = UnionTypes(config.getDescriptors(), + config.getDictionaries(), + config.getCallbacks(), + config) + includes.add("mozilla/dom/OwningNonNull.h") + includes.add("mozilla/dom/UnionMember.h") + includes.add("mozilla/dom/BindingDeclarations.h") + # Need BindingUtils.h for FakeDependentString + includes.add("mozilla/dom/BindingUtils.h") + implincludes.add("mozilla/dom/PrimitiveConversions.h") + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(['mozilla', 'dom'], unions) + + curr = CGWrapper(curr, post='\n') + + namespaces = [] + stack = [CGList([])] + for clazz, isStruct in sorted(declarations): + elements = clazz.split("::") + clazz = CGClassForwardDeclare(elements.pop(), isStruct=isStruct) + i = 0 + if len(elements) > 0: + common = min(len(namespaces), len(elements)) + while i < common and namespaces[i] == elements[i]: + i += 1 + + # pop all the namespaces that should be closed + namespaces = namespaces[:i] + + # add all the namespaces that should be opened + for j, namespace in enumerate(elements[i:]): + namespaces.append(namespace) + # every CGNamespace that we add holds a CGList + list = CGList([]) + # add the new namespace to the list on top of the stack + stack[i + j].append(CGNamespace(namespace, list)) + # set the top of the namespace stack to the list of the new + # namespace + stack[i + j + 1:] = [list] + + stack[len(elements)].append(clazz) + + curr = CGList([stack[0], curr], "\n") + + curr = CGHeaders([], [], [], [], includes, implincludes, 'UnionTypes', + curr) + + # Add include guards. + curr = CGIncludeGuard('UnionTypes', curr) + + # Done. + return curr + + @staticmethod + def UnionConversions(config): + + headers, unions = UnionConversions(config.getDescriptors(), + config.getDictionaries(), + config.getCallbacks(), + config) + + # Wrap all of that in our namespaces. + curr = CGNamespace.build(['mozilla', 'dom'], unions) + + curr = CGWrapper(curr, post='\n') + + headers.update(["nsDebug.h", "mozilla/dom/UnionTypes.h"]) + curr = CGHeaders([], [], [], [], headers, [], 'UnionConversions', curr) + + # Add include guards. + curr = CGIncludeGuard('UnionConversions', curr) + + # Done. + return curr + + +# Code generator for simple events +class CGEventGetter(CGNativeMember): + def __init__(self, descriptor, attr): + ea = descriptor.getExtendedAttributes(attr, getter=True) + ea.append('resultNotAddRefed') + CGNativeMember.__init__(self, descriptor, attr, + CGSpecializedGetter.makeNativeName(descriptor, + attr), + (attr.type, []), + ea) + self.body = self.getMethodBody() + + def getArgs(self, returnType, argList): + if 'infallible' not in self.extendedAttrs: + raise TypeError("Event code generator does not support [Throws]!") + if not self.member.isAttr(): + raise TypeError("Event code generator does not support methods") + if self.member.isStatic(): + raise TypeError("Event code generators does not support static attributes") + return CGNativeMember.getArgs(self, returnType, argList) + + def getMethodBody(self): + type = self.member.type + memberName = CGDictionary.makeMemberName(self.member.identifier.name) + if (type.isPrimitive() and type.tag() in builtinNames) or type.isEnum() or type.isGeckoInterface(): + return "return " + memberName + ";\n" + if type.isDOMString() or type.isByteString(): + 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( + """ + JS::ExposeValueToActiveJS(${memberName}); + aRetVal.set(${memberName}); + return; + """, + memberName=memberName) + if type.isUnion(): + return "aRetVal = " + memberName + ";\n" + raise TypeError("Event code generator does not support this type!") + + def declare(self, cgClass): + if getattr(self.member, "originatingInterface", + cgClass.descriptor.interface) != cgClass.descriptor.interface: + return "" + return CGNativeMember.declare(self, cgClass) + + def define(self, cgClass): + if getattr(self.member, "originatingInterface", + cgClass.descriptor.interface) != cgClass.descriptor.interface: + return "" + return CGNativeMember.define(self, cgClass) + + +class CGEventSetter(CGNativeMember): + def __init__(self): + raise TypeError("Event code generator does not support setters!") + + +class CGEventMethod(CGNativeMember): + def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True): + if not isConstructor: + raise TypeError("Event code generator does not support methods!") + self.wantsConstructorForNativeCaller = True + CGNativeMember.__init__(self, descriptor, method, + CGSpecializedMethod.makeNativeName(descriptor, + method), + signature, + descriptor.getExtendedAttributes(method), + breakAfter=breakAfter, + variadicIsSequence=True) + self.originalArgs = list(self.args) + + 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.optional and not arg.defaultValue, + "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): + self.args = list(self.originalArgs) + self.args.insert(0, Argument("mozilla::dom::EventTarget*", "aOwner")) + constructorForNativeCaller = CGNativeMember.declare(self, cgClass) + "\n" + self.args = list(self.originalArgs) + if needCx(None, self.descriptorProvider.interface.members, [], True): + self.args.insert(0, Argument("JSContext*", "aCx")) + self.args.insert(0, Argument("const GlobalObject&", "aGlobal")) + self.args.append(Argument('ErrorResult&', 'aRv')) + return constructorForNativeCaller + CGNativeMember.declare(self, cgClass) + + def define(self, cgClass): + self.args = list(self.originalArgs) + 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) + 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( + """ + nsRefPtr<${nativeType}> e = new ${nativeType}(aOwner); + bool trusted = e->Init(aOwner); + e->InitEvent(${eventType}, ${eventInit}.mBubbles, ${eventInit}.mCancelable); + $*{members} + e->SetTrusted(trusted); + $*{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 owner = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(owner, ${arg0}, ${arg1}); + """, + arg0=self.args[0].name, + arg1=self.args[1].name) + if needCx(None, self.descriptorProvider.interface.members, [], True): + self.args.insert(0, Argument("JSContext*", "aCx")) + self.args.insert(0, Argument("const GlobalObject&", "aGlobal")) + self.args.append(Argument('ErrorResult&', 'aRv')) + return constructorForNativeCaller + CGNativeMember.define(self, cgClass) + + +class CGEventClass(CGBindingImplClass): + """ + Codegen for the actual Event class implementation for this descriptor + """ + def __init__(self, descriptor): + CGBindingImplClass.__init__(self, descriptor, CGEventMethod, CGEventGetter, CGEventSetter, False) + members = [] + for m in descriptor.interface.members: + if m.isAttr(): + if getattr(m, "originatingInterface", + descriptor.interface) != descriptor.interface: + continue + if m.type.isPrimitive() and m.type.tag() in builtinNames: + nativeType = CGGeneric(builtinNames[m.type.tag()]) + if m.type.nullable(): + nativeType = CGTemplatedType("Nullable", nativeType) + nativeType = nativeType.define() + elif m.type.isEnum(): + nativeType = m.type.unroll().inner.identifier.name + if m.type.nullable(): + nativeType = CGTemplatedType("Nullable", + CGGeneric(nativeType)).define() + elif m.type.isDOMString(): + nativeType = "nsString" + elif m.type.isByteString(): + nativeType = "nsCString" + elif m.type.isGeckoInterface(): + iface = m.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="nsRefPtr<", post=">").define() + elif m.type.isAny(): + nativeType = "JS::Heap" + elif m.type.isObject() or m.type.isSpiderMonkeyInterface(): + nativeType = "JS::Heap" + elif m.type.isUnion(): + nativeType = CGUnionStruct.unionTypeDecl(m.type, True) + else: + raise TypeError("Don't know how to declare member of type %s" % + m.type) + members.append(ClassMember(CGDictionary.makeMemberName(m.identifier.name), + nativeType, + visibility="private", + body="body")) + + parent = self.descriptor.interface.parent + self.parentType = self.descriptor.getDescriptor(parent.identifier.name).nativeType.split('::')[-1] + baseDeclarations = fill( + """ + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(${nativeType}, ${parentType}) + virtual ~${nativeType}(); + protected: + ${nativeType}(mozilla::dom::EventTarget* aOwner); + + """, + nativeType=self.descriptor.nativeType.split('::')[-1], + parentType=self.parentType) + + className = descriptor.nativeType.split('::')[-1] + asConcreteTypeMethod = ClassMethod("As%s" % className, + "%s*" % className, + [], + virtual=True, + body="return this;\n", + breakAfterReturnDecl=" ") + + CGClass.__init__(self, className, + bases=[ClassBase(self.parentType)], + methods=[asConcreteTypeMethod]+self.methodDecls, + members=members, + extradeclarations=baseDeclarations) + + def getWrapObjectBody(self): + return "return %sBinding::Wrap(aCx, this);\n" % self.descriptor.name + + def implTraverse(self): + retVal = "" + for m in self.descriptor.interface.members: + if m.isAttr() and m.type.isGeckoInterface(): + retVal += (" NS_IMPL_CYCLE_COLLECTION_TRAVERSE(" + + CGDictionary.makeMemberName(m.identifier.name) + + ")\n") + return retVal + + def implUnlink(self): + retVal = "" + for m in self.descriptor.interface.members: + if m.isAttr(): + name = CGDictionary.makeMemberName(m.identifier.name) + if m.type.isGeckoInterface(): + retVal += " NS_IMPL_CYCLE_COLLECTION_UNLINK(" + name + ")\n" + elif m.type.isAny(): + retVal += " tmp->" + name + ".setUndefined();\n" + elif m.type.isObject() or m.type.isSpiderMonkeyInterface(): + retVal += " tmp->" + name + " = nullptr;\n" + return retVal + + def implTrace(self): + retVal = "" + for m in self.descriptor.interface.members: + if m.isAttr(): + name = CGDictionary.makeMemberName(m.identifier.name) + if m.type.isAny(): + retVal += " NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(" + name + ")\n" + elif m.type.isObject() or m.type.isSpiderMonkeyInterface(): + retVal += " NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(" + name + ")\n" + elif typeNeedsRooting(m.type): + raise TypeError("Need to implement tracing for event " + "member of type %s" % m.type) + return retVal + + def define(self): + dropJS = "" + for m in self.descriptor.interface.members: + if m.isAttr(): + member = CGDictionary.makeMemberName(m.identifier.name) + if m.type.isAny(): + dropJS += member + " = JS::UndefinedValue();\n" + elif m.type.isObject() or m.type.isSpiderMonkeyInterface(): + dropJS += member + " = nullptr;\n" + if dropJS != "": + dropJS += "mozilla::DropJSObjects(this);\n" + # Just override CGClass and do our own thing + nativeType = self.descriptor.nativeType.split('::')[-1] + ctorParams = ("aOwner, nullptr, nullptr" if self.parentType == "Event" + else "aOwner") + + classImpl = fill( + """ + + NS_IMPL_CYCLE_COLLECTION_CLASS(${nativeType}) + + NS_IMPL_ADDREF_INHERITED(${nativeType}, ${parentType}) + NS_IMPL_RELEASE_INHERITED(${nativeType}, ${parentType}) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(${nativeType}, ${parentType}) + $*{traverse} + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(${nativeType}, ${parentType}) + $*{trace} + NS_IMPL_CYCLE_COLLECTION_TRACE_END + + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(${nativeType}, ${parentType}) + $*{unlink} + NS_IMPL_CYCLE_COLLECTION_UNLINK_END + + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(${nativeType}) + NS_INTERFACE_MAP_END_INHERITING(${parentType}) + + ${nativeType}::${nativeType}(mozilla::dom::EventTarget* aOwner) + : ${parentType}(${ctorParams}) + { + } + + ${nativeType}::~${nativeType}() + { + $*{dropJS} + } + + """, + ifaceName=self.descriptor.name, + nativeType=nativeType, + ctorParams=ctorParams, + parentType=self.parentType, + traverse=self.implTraverse(), + unlink=self.implUnlink(), + trace=self.implTrace(), + dropJS=dropJS) + return classImpl + CGBindingImplClass.define(self) + + +class CGEventRoot(CGThing): + def __init__(self, config, interfaceName): + # Let's assume we're not doing workers stuff, for now + descriptor = config.getDescriptor(interfaceName, False) + + 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, False).headerFile, + "mozilla/Attributes.h", + "mozilla/ErrorResult.h", + "mozilla/dom/%sBinding.h" % interfaceName, + 'mozilla/dom/BindingUtils.h', + ], + [ + "%s.h" % interfaceName, + "js/GCAPI.h", + 'mozilla/dom/Nullable.h', + 'nsDOMQS.h' + ], + "", self.root) + + # And now some include guards + self.root = CGIncludeGuard(interfaceName, self.root) + + self.root = CGWrapper(self.root, pre=AUTOGENERATED_WARNING_COMMENT) + + 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()