diff options
Diffstat (limited to 'components/script/dom/bindings/codegen/CodegenRust.py')
-rw-r--r-- | components/script/dom/bindings/codegen/CodegenRust.py | 5534 |
1 files changed, 5534 insertions, 0 deletions
diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py new file mode 100644 index 00000000000..1666589940e --- /dev/null +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -0,0 +1,5534 @@ +# 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 operator +import os +import re +import string + +from WebIDL import ( + BuiltinTypes, + IDLBuiltinType, + IDLNullValue, + IDLType, + IDLUndefinedValue, +) + +from Configuration import getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback + +AUTOGENERATED_WARNING_COMMENT = \ + "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n" +ADDPROPERTY_HOOK_NAME = '_addProperty' +FINALIZE_HOOK_NAME = '_finalize' +TRACE_HOOK_NAME = '_trace' +CONSTRUCT_HOOK_NAME = '_constructor' +HASINSTANCE_HOOK_NAME = '_hasInstance' + +def replaceFileIfChanged(filename, newContents): + """ + Read a copy of the old file, so that we don't touch it if it hasn't changed. + Returns True if the file was updated, false otherwise. + """ + #XXXjdm This doesn't play well with make right now. + # Force the file to always be updated, or else changing CodegenRust.py + # will cause many autogenerated bindings to be regenerated perpetually + # until the result is actually different. + + #oldFileContents = "" + #try: + # oldFile = open(filename, 'rb') + # oldFileContents = ''.join(oldFile.readlines()) + # oldFile.close() + #except: + # pass + + #if newContents == oldFileContents: + # return False + + f = open(filename, 'wb') + f.write(newContents) + f.close() + + return True + +def toStringBool(arg): + return str(not not arg).lower() + +def toBindingNamespace(arg): + return re.sub("((_workers)?$)", "Binding\\1", arg); + +def stripTrailingWhitespace(text): + tail = '\n' if text.endswith('\n') else '' + lines = text.splitlines() + for i in range(len(lines)): + lines[i] = lines[i].rstrip() + return '\n'.join(lines) + tail + +def MakeNativeName(name): + return name[0].upper() + name[1:] + +builtinNames = { + IDLType.Tags.bool: 'bool', + IDLType.Tags.int8: 'i8', + IDLType.Tags.int16: 'i16', + IDLType.Tags.int32: 'i32', + IDLType.Tags.int64: 'i64', + IDLType.Tags.uint8: 'u8', + IDLType.Tags.uint16: 'u16', + IDLType.Tags.uint32: 'u32', + IDLType.Tags.uint64: 'u64', + IDLType.Tags.float: 'f32', + IDLType.Tags.double: 'f64' +} + +numericTags = [ + IDLType.Tags.int8, IDLType.Tags.uint8, + IDLType.Tags.int16, IDLType.Tags.uint16, + IDLType.Tags.int32, IDLType.Tags.uint32, + IDLType.Tags.int64, IDLType.Tags.uint64, + IDLType.Tags.float, IDLType.Tags.double + ] + +class CastableObjectUnwrapper(): + """ + A class for unwrapping an object named by the "source" argument + based on the passed-in descriptor. Stringifies to a Rust expression of + the appropriate type. + + codeOnFailure is the code to run if unwrapping fails. + """ + def __init__(self, descriptor, source, codeOnFailure): + self.substitution = { + "type": descriptor.nativeType, + "depth": descriptor.interface.inheritanceDepth(), + "prototype": "PrototypeList::id::" + descriptor.name, + "protoID": "PrototypeList::id::" + descriptor.name + " as uint", + "source": source, + "codeOnFailure": CGIndenter(CGGeneric(codeOnFailure), 4).define(), + } + + def __str__(self): + return string.Template( +"""match unwrap_jsmanaged(${source}, ${prototype}, ${depth}) { + Ok(val) => val, + Err(()) => { +${codeOnFailure} + } +}""").substitute(self.substitution) + + +class CGThing(): + """ + Abstract base class for things that spit out code. + """ + def __init__(self): + pass # Nothing for now + + def define(self): + """Produce code for a Rust file.""" + assert(False) # Override me! + + +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 define(self): + parent = self.descriptor.interface.parent + if parent: + parentHooks = "Some(&::dom::bindings::codegen::Bindings::%sBinding::sNativePropertyHooks)" % parent.identifier.name + else: + parentHooks = "None" + + substitutions = { + "parentHooks": parentHooks + } + + return string.Template( + "pub static sNativePropertyHooks: NativePropertyHooks = NativePropertyHooks {\n" + " native_properties: &sNativeProperties,\n" + " proto_hooks: ${parentHooks},\n" + "};\n").substitute(substitutions) + + +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, argsPre, nativeMethodName, static, descriptor, method): + CGThing.__init__(self) + + methodName = '\\"%s.%s\\"' % (descriptor.interface.identifier.name, method.identifier.name) + + 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, signatureIndex=0): + return CGPerSignatureCall(signature[0], argsPre, signature[1], + nativeMethodName + '_'*signatureIndex, + static, descriptor, + method, argConversionStartsAt) + + + signatures = method.signatures() + if len(signatures) == 1: + # Special case: we can just do a per-signature method call + # here for our one signature and not worry about switching + # on anything. + signature = signatures[0] + self.cgRoot = CGList([getPerSignatureCall(signature)]) + requiredArgs = requiredArgCount(signature) + + + if requiredArgs > 0: + code = ( + "if argc < %d {\n" + " throw_type_error(cx, \"Not enough arguments to %s.\");\n" + " return 0;\n" + "}" % (requiredArgs, methodName)) + self.cgRoot.prepend( + CGWrapper(CGGeneric(code), pre="\n", post="\n")) + + return + + # Need to find the right overload + maxArgCount = method.maxArgCount + allowedArgCounts = method.allowedArgCounts + + argCountCases = [] + for argCount in allowedArgCounts: + possibleSignatures = method.signaturesForArgCount(argCount) + if len(possibleSignatures) == 1: + # easy case! + signature = possibleSignatures[0] + + + sigIndex = signatures.index(signature) + argCountCases.append( + CGCase(str(argCount), getPerSignatureCall(signature, + signatureIndex=sigIndex))) + continue + + distinguishingIndex = method.distinguishingIndexForArgCount(argCount) + + # We can't handle unions at the distinguishing index. + for (returnType, args) in possibleSignatures: + if args[distinguishingIndex].type.isUnion(): + raise TypeError("No support for unions as distinguishing " + "arguments yet: %s", + args[distinguishingIndex].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 = [CGGeneric("let argv_start = JS_ARGV(cx, vp);")] + caseBody.extend([ CGArgumentConverter(possibleSignatures[0][1][i], + i, "argv_start", "argc", + descriptor) for i in + range(0, distinguishingIndex) ]) + + # Select the right overload from our set. + distinguishingArg = "(*argv_start.offset(%d))" % distinguishingIndex + + def pickFirstSignature(condition, filterLambda): + sigs = filter(filterLambda, possibleSignatures) + assert len(sigs) < 2 + if len(sigs) > 0: + if condition is None: + caseBody.append( + getPerSignatureCall(sigs[0], distinguishingIndex, + possibleSignatures.index(sigs[0]))) + else: + caseBody.append(CGGeneric("if " + condition + " {")) + caseBody.append(CGIndenter( + getPerSignatureCall(sigs[0], distinguishingIndex, + possibleSignatures.index(sigs[0])))) + caseBody.append(CGGeneric("}")) + return True + return False + + # First check for null or undefined + pickFirstSignature("%s.isNullOrUndefined()" % distinguishingArg, + lambda s: (s[1][distinguishingIndex].type.nullable() or + s[1][distinguishingIndex].type.isDictionary())) + + # Now check for distinguishingArg being an object that implements a + # non-callback interface. That includes typed arrays and + # arraybuffers. + interfacesSigs = [ + s for s in possibleSignatures + if (s[1][distinguishingIndex].type.isObject() or + s[1][distinguishingIndex].type.isNonCallbackInterface()) ] + # There might be more than one of these; we need to check + # which ones we unwrap to. + + if len(interfacesSigs) > 0: + # The spec says that we should check for "platform objects + # implementing an interface", but it's enough to guard on these + # being an object. The code for unwrapping non-callback + # interfaces and typed arrays will just bail out and move on to + # the next overload if the object fails to unwrap correctly. 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).is_object() {" % + (distinguishingArg))) + for idx, sig in enumerate(interfacesSigs): + caseBody.append(CGIndenter(CGGeneric("loop {"))); + type = sig[1][distinguishingIndex].type + + # 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. + template, _, declType, needsRooting = getJSToNativeConversionTemplate( + type, descriptor, failureCode="break;", isDefinitelyObject=True) + + testCode = instantiateJSToNativeConversionTemplate( + template, + {"val": distinguishingArg}, + declType, + "arg%d" % distinguishingIndex, + needsRooting) + + # Indent by 4, since we need to indent further than our "do" statement + caseBody.append(CGIndenter(testCode, 4)); + # If we got this far, we know we unwrapped to the right + # interface, so just do the call. Start conversion with + # distinguishingIndex + 1, since we already converted + # distinguishingIndex. + caseBody.append(CGIndenter( + getPerSignatureCall(sig, distinguishingIndex + 1, idx), 4)) + caseBody.append(CGIndenter(CGGeneric("}"))) + + caseBody.append(CGGeneric("}")) + + # XXXbz Now we're supposed to check for distinguishingArg being + # an array or a platform object that supports indexed + # properties... skip that last for now. It's a bit of a pain. + pickFirstSignature("%s.isObject() && IsArrayLike(cx, &%s.toObject())" % + (distinguishingArg, distinguishingArg), + lambda s: + (s[1][distinguishingIndex].type.isArray() or + s[1][distinguishingIndex].type.isSequence() or + s[1][distinguishingIndex].type.isObject())) + + # Check for Date objects + # XXXbz Do we need to worry about security wrappers around the Date? + pickFirstSignature("%s.isObject() && JS_ObjectIsDate(cx, &%s.toObject())" % + (distinguishingArg, distinguishingArg), + lambda s: (s[1][distinguishingIndex].type.isDate() or + s[1][distinguishingIndex].type.isObject())) + + # Check for vanilla JS objects + # XXXbz Do we need to worry about security wrappers? + pickFirstSignature("%s.isObject() && !IsPlatformObject(cx, &%s.toObject())" % + (distinguishingArg, distinguishingArg), + lambda s: (s[1][distinguishingIndex].type.isCallback() or + s[1][distinguishingIndex].type.isCallbackInterface() or + s[1][distinguishingIndex].type.isDictionary() or + s[1][distinguishingIndex].type.isObject())) + + # The remaining cases are mutually exclusive. The + # pickFirstSignature calls are what change caseBody + # Check for strings or enums + if pickFirstSignature(None, + lambda s: (s[1][distinguishingIndex].type.isString() or + s[1][distinguishingIndex].type.isEnum())): + pass + # Check for primitives + elif pickFirstSignature(None, + lambda s: s[1][distinguishingIndex].type.isPrimitive()): + pass + # Check for "any" + elif pickFirstSignature(None, + lambda s: s[1][distinguishingIndex].type.isAny()): + pass + else: + # Just throw; we have no idea what we're supposed to + # do with this. + caseBody.append(CGGeneric("return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS);")) + + argCountCases.append(CGCase(str(argCount), + CGList(caseBody, "\n"))) + + overloadCGThings = [] + overloadCGThings.append( + CGGeneric("let argcount = cmp::min(argc, %d);" % + maxArgCount)) + overloadCGThings.append( + CGSwitch("argcount", + argCountCases, + CGGeneric("throw_type_error(cx, \"Not enough arguments to %s.\");\n" + "return 0;\n" % methodName))) + #XXXjdm Avoid unreachable statement warnings + #overloadCGThings.append( + # CGGeneric('fail!("We have an always-returning default case");\n' + # 'return 0;')) + self.cgRoot = CGWrapper(CGList(overloadCGThings, "\n"), + pre="\n") + + def define(self): + return self.cgRoot.define() + +class FakeCastableDescriptor(): + def __init__(self, descriptor): + self.nativeType = "*const %s" % descriptor.concreteType + self.name = descriptor.name + class FakeInterface: + def inheritanceDepth(self): + return descriptor.interface.inheritanceDepth() + self.interface = FakeInterface() + +def dictionaryHasSequenceMember(dictionary): + return (any(typeIsSequenceOrHasSequenceMember(m.type) for m in + dictionary.members) or + (dictionary.parent and + dictionaryHasSequenceMember(dictionary.parent))) + +def typeIsSequenceOrHasSequenceMember(type): + if type.nullable(): + type = type.inner + if type.isSequence(): + return True + if type.isArray(): + elementType = type.inner + return typeIsSequenceOrHasSequenceMember(elementType) + if type.isDictionary(): + return dictionaryHasSequenceMember(type.inner) + if type.isUnion(): + return any(typeIsSequenceOrHasSequenceMember(m.type) for m in + type.flatMemberTypes) + return False + +def typeNeedsRooting(type, descriptorProvider): + return type.isGeckoInterface() and descriptorProvider.getDescriptor(type.name).needsRooting + +def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None, + isDefinitelyObject=False, + isMember=False, + isArgument=False, + invalidEnumValueFatal=True, + defaultValue=None, + treatNullAs="Default", + isEnforceRange=False, + isClamp=False, + exceptionCode=None, + allowTreatNonObjectAsNull=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. + + If isDefinitelyObject is True, that means we know the value + isObject() and we have no need to recheck that. + + if isMember is True, 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. Any caller + passing true needs to ensure that it is handled correctly in + typeIsSequenceOrHasSequenceMember. + + 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 allowTreatNonObjectAsNull is true, then [TreatNonObjectAsNull] + extended attributes on nullable callback functions will be honored. + + The return value from this function is a tuple consisting of four things: + + 1) A string representing the conversion code. This will have template + substitution performed on it as follows: + + ${val} replaced by an expression for the JS::Value in question + + 2) A string or None representing Rust code for the default value (if any). + + 3) A CGThing representing the native C++ type we're converting to + (declType). This is allowed to be None if the conversion code is + supposed to be used as-is. + + 4) A boolean indicating whether the caller has to root the result. + + """ + # We should not have a defaultValue if we know we're an object + assert(not isDefinitelyObject or defaultValue is None) + + # 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 0;" + + needsRooting = typeNeedsRooting(type, descriptorProvider) + + def handleOptional(template, declType, default): + assert (defaultValue is None) == (default is None) + return (template, default, declType, needsRooting) + + # 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 + # Helper functions for dealing with failures due to the JS value being the + # wrong type of value + def onFailureNotAnObject(failureCode): + return CGWrapper( + CGGeneric( + failureCode or + ('throw_type_error(cx, "%s is not an object.");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))), + post="\n") + def onFailureBadType(failureCode, typeName): + return CGWrapper( + CGGeneric( + failureCode or + ('throw_type_error(cx, \"%s does not implement interface %s.\");\n' + '%s' % (firstCap(sourceDescription), typeName, + exceptionCode))), + post="\n") + def onFailureNotCallable(failureCode): + return CGWrapper( + CGGeneric( + failureCode or + ('throw_type_error(cx, \"%s is not callable.\");\n' + '%s' % (firstCap(sourceDescription), exceptionCode))), + post="\n") + + + # A helper function for handling null default values. Checks that the + # default value, if it exists, is null. + def handleDefaultNull(nullValue): + if defaultValue is None: + return None + + if not isinstance(defaultValue, IDLNullValue): + raise TypeError("Can't handle non-null default value here") + + assert type.nullable() or type.isDictionary() + return nullValue + + # A helper function for wrapping up the template body for + # possibly-nullable objecty stuff + def wrapObjectTemplate(templateBody, isDefinitelyObject, type, + failureCode=None): + if not isDefinitelyObject: + # Handle the non-object cases by wrapping up the whole + # thing in an if cascade. + templateBody = ( + "if (${val}).is_object() {\n" + + CGIndenter(CGGeneric(templateBody)).define() + "\n") + if type.nullable(): + templateBody += ( + "} else if (${val}).is_null_or_undefined() {\n" + " None\n") + templateBody += ( + "} else {\n" + + CGIndenter(onFailureNotAnObject(failureCode)).define() + + "}\n") + + return templateBody + + assert not (isEnforceRange and isClamp) # These are mutually exclusive + + if type.isArray(): + raise TypeError("Can't handle array arguments yet") + + if type.isSequence(): + raise TypeError("Can't handle sequence arguments yet") + + if type.isUnion(): + declType = CGGeneric(type.name + "::" + type.name) + if type.nullable(): + declType = CGWrapper(declType, pre="Option<", post=" >") + + templateBody = ("match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + " Ok(value) => value,\n" + " Err(()) => { %s },\n" + "}" % exceptionCode) + + return handleOptional(templateBody, declType, handleDefaultNull("None")) + + if type.isGeckoInterface(): + assert not isEnforceRange and not isClamp + + descriptor = descriptorProvider.getDescriptor( + type.unroll().inner.identifier.name) + + if descriptor.interface.isCallback(): + name = descriptor.nativeType + declType = CGGeneric("Option<%s>" % name); + conversion = ("Some(%s::new((${val}).to_object()))" % name) + + template = wrapObjectTemplate(conversion, isDefinitelyObject, type, + failureCode) + return handleOptional(template, declType, handleDefaultNull("None")) + + if isMember: + descriptorType = descriptor.memberType + elif isArgument: + descriptorType = descriptor.argumentType + else: + descriptorType = descriptor.nativeType + + templateBody = "" + if descriptor.interface.isConsequential(): + raise TypeError("Consequential interface %s being used as an " + "argument" % descriptor.interface.identifier.name) + + if failureCode is None: + substitutions = { + "sourceDescription": sourceDescription, + "interface": descriptor.interface.identifier.name, + "exceptionCode": exceptionCode, + } + unwrapFailureCode = string.Template( + 'throw_type_error(cx, "${sourceDescription} does not ' + 'implement interface ${interface}.");\n' + '${exceptionCode}').substitute(substitutions) + else: + unwrapFailureCode = failureCode + + templateBody = str(CastableObjectUnwrapper( + descriptor, + "(${val}).to_object()", + unwrapFailureCode)) + + declType = CGGeneric(descriptorType) + if type.nullable(): + templateBody = "Some(%s)" % templateBody + declType = CGWrapper(declType, pre="Option<", post=">") + + if isMember: + templateBody += ".root()" + + templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject, + type, failureCode) + + return handleOptional(templateBody, declType, handleDefaultNull("None")) + + if type.isSpiderMonkeyInterface(): + raise TypeError("Can't handle SpiderMonkey interface arguments yet") + + if type.isDOMString(): + assert not isEnforceRange and not isClamp + + treatAs = { + "Default": "Default", + "EmptyString": "Empty", + } + if treatNullAs not in treatAs: + raise TypeError("We don't support [TreatNullAs=%s]" % treatNullAs) + if type.nullable(): + nullBehavior = "()" + else: + nullBehavior = treatAs[treatNullAs] + + conversionCode = ( + "match FromJSValConvertible::from_jsval(cx, ${val}, %s) {\n" + " Ok(strval) => strval,\n" + " Err(_) => { %s },\n" + "}" % (nullBehavior, exceptionCode)) + + if defaultValue is None: + default = None + elif isinstance(defaultValue, IDLNullValue): + assert type.nullable() + default = "None" + else: + assert defaultValue.type.tag() == IDLType.Tags.domstring + value = "str::from_utf8(data).unwrap().to_string()" + if type.nullable(): + value = "Some(%s)" % value + + default = ( + "static data: [u8, ..%s] = [ %s ];\n" + "%s" % + (len(defaultValue.value) + 1, + ", ".join(["'" + char + "' as u8" for char in defaultValue.value] + ["0"]), + value)) + + declType = "DOMString" + if type.nullable(): + declType = "Option<%s>" % declType + + return handleOptional(conversionCode, CGGeneric(declType), default) + + if type.isByteString(): + assert not isEnforceRange and not isClamp + + conversionCode = ( + "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + " Ok(strval) => strval,\n" + " Err(_) => { %s },\n" + "}" % exceptionCode) + + declType = CGGeneric("ByteString") + if type.nullable(): + declType = CGWrapper(declType, pre="Option<", post=">") + + return handleOptional(conversionCode, declType, handleDefaultNull("None")) + + if type.isEnum(): + assert not isEnforceRange and not isClamp + + if type.nullable(): + raise TypeError("We don't support nullable enumerated arguments " + "yet") + enum = type.inner.identifier.name + if invalidEnumValueFatal: + handleInvalidEnumValueCode = exceptionCode + else: + handleInvalidEnumValueCode = "return 1;" + + template = ( + "match FindEnumStringIndex(cx, ${val}, %(values)s) {\n" + " Err(_) => { %(exceptionCode)s },\n" + " Ok(None) => { %(handleInvalidEnumValueCode)s },\n" + " Ok(Some(index)) => {\n" + " //XXXjdm need some range checks up in here.\n" + " unsafe { mem::transmute(index) }\n" + " },\n" + "}" % { "values" : enum + "Values::strings", + "exceptionCode" : exceptionCode, +"handleInvalidEnumValueCode" : handleInvalidEnumValueCode }) + + if defaultValue is not None: + assert(defaultValue.type.tag() == IDLType.Tags.domstring) + default = "%sValues::%s" % (enum, getEnumValueName(defaultValue.value)) + else: + default = None + + return handleOptional(template, CGGeneric(enum), default) + + if type.isCallback(): + assert not isEnforceRange and not isClamp + assert not type.treatNonCallableAsNull() + assert not type.treatNonObjectAsNull() or type.nullable() + assert not type.treatNonObjectAsNull() or not type.treatNonCallableAsNull() + + declType = CGGeneric('%s::%s' % (type.unroll().module(), type.unroll().identifier.name)) + + conversion = CGCallbackTempRoot(declType.define()) + + if type.nullable(): + declType = CGTemplatedType("Option", declType) + conversion = CGWrapper(conversion, pre="Some(", post=")") + + if allowTreatNonObjectAsNull and type.treatNonObjectAsNull(): + if not isDefinitelyObject: + haveObject = "${val}.is_object()" + template = CGIfElseWrapper(haveObject, + conversion, + CGGeneric("None")).define() + else: + template = conversion + else: + template = CGIfElseWrapper("JS_ObjectIsCallable(cx, ${val}.to_object()) != 0", + conversion, + onFailureNotCallable(failureCode)).define() + template = wrapObjectTemplate( + template, + isDefinitelyObject, + type, + failureCode) + + if defaultValue is not None: + assert allowTreatNonObjectAsNull + assert type.treatNonObjectAsNull() + assert type.nullable() + assert isinstance(defaultValue, IDLNullValue) + default = "None" + else: + default = None + + return (template, default, declType, needsRooting) + + if type.isAny(): + assert not isEnforceRange and not isClamp + + declType = CGGeneric("JSVal") + + if defaultValue is None: + default = None + elif isinstance(defaultValue, IDLNullValue): + default = "NullValue()" + elif isinstance(defaultValue, IDLUndefinedValue): + default = "UndefinedValue()" + else: + raise TypeError("Can't handle non-null, non-undefined default value here") + + return handleOptional("${val}", declType, default) + + if type.isObject(): + raise TypeError("Can't handle object arguments yet") + + if type.isDictionary(): + if failureCode is not None: + raise TypeError("Can't handle dictionaries when failureCode is not None") + # There are no nullable dictionaries + assert not type.nullable() + + typeName = CGDictionary.makeDictionaryName(type.inner) + declType = CGGeneric(typeName) + template = ("match %s::new(cx, ${val}) {\n" + " Ok(dictionary) => dictionary,\n" + " Err(_) => return 0,\n" + "}" % typeName) + + return handleOptional(template, declType, handleDefaultNull("%s::empty()" % typeName)) + + if type.isVoid(): + # This one only happens for return values, and its easy: Just + # ignore the jsval. + return ("", None, None, False) + + if not type.isPrimitive(): + raise TypeError("Need conversion for argument type '%s'" % str(type)) + + assert not isEnforceRange and not isClamp + + if failureCode is None: + failureCode = 'return 0' + + declType = CGGeneric(builtinNames[type.tag()]) + if type.nullable(): + declType = CGWrapper(declType, pre="Option<", post=">") + + #XXXjdm support conversionBehavior here + template = ( + "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n" + " Ok(v) => v,\n" + " Err(_) => { %s }\n" + "}" % exceptionCode) + + if defaultValue is not None: + if isinstance(defaultValue, IDLNullValue): + assert type.nullable() + defaultStr = "None" + else: + tag = defaultValue.type.tag() + if tag in numericTags: + defaultStr = str(defaultValue.value) + else: + assert(tag == IDLType.Tags.bool) + defaultStr = toStringBool(defaultValue.value) + + if type.nullable(): + defaultStr = "Some(%s)" % defaultStr + else: + defaultStr = None + + return handleOptional(template, declType, defaultStr) + +def instantiateJSToNativeConversionTemplate(templateBody, replacements, + declType, declName, needsRooting): + """ + Take the templateBody and declType as returned by + getJSToNativeConversionTemplate, a set of replacements as required by the + strings in such a templateBody, and a declName, and generate code to + convert into a stack Rust binding with that name. + """ + result = CGList([], "\n") + + conversion = CGGeneric( + string.Template(templateBody).substitute(replacements) + ) + + if declType is not None: + newDecl = [ + CGGeneric("let "), + CGGeneric(declName), + CGGeneric(": "), + declType, + CGGeneric(" = "), + conversion, + CGGeneric(";"), + ] + result.append(CGList(newDecl)) + else: + result.append(conversion) + + # Add an empty CGGeneric to get an extra newline after the argument + # conversion. + result.append(CGGeneric("")) + + if needsRooting: + rootBody = "let %s = %s.root();" % (declName, declName) + result.append(CGGeneric(rootBody)) + result.append(CGGeneric("")) + + return result; + +def convertConstIDLValueToJSVal(value): + if isinstance(value, IDLNullValue): + return "NullVal" + tag = value.type.tag() + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16, + IDLType.Tags.uint16, IDLType.Tags.int32]: + return "IntVal(%s)" % (value.value) + if tag == IDLType.Tags.uint32: + return "UintVal(%s)" % (value.value) + if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]: + return "DoubleVal(%s)" % (value.value) + if tag == IDLType.Tags.bool: + return "BoolVal(true)" if value.value else "BoolVal(false)" + if tag in [IDLType.Tags.float, IDLType.Tags.double]: + return "DoubleVal(%s)" % (value.value) + raise TypeError("Const value of unhandled type: " + value.type) + +class CGArgumentConverter(CGThing): + """ + A class that takes an IDL argument object, its index in the + argument list, and the argv and argc strings and generates code to + unwrap the argument to the right native type. + """ + def __init__(self, argument, index, argv, argc, descriptorProvider, + invalidEnumValueFatal=True): + CGThing.__init__(self) + assert(not argument.defaultValue or argument.optional) + + replacer = { + "index": index, + "argc": argc, + "argv": argv + } + condition = string.Template("${index} < ${argc}").substitute(replacer) + + replacementVariables = { + "val": string.Template("(*${argv}.offset(${index}))").substitute(replacer), + } + + template, default, declType, needsRooting = getJSToNativeConversionTemplate( + argument.type, + descriptorProvider, + invalidEnumValueFatal=invalidEnumValueFatal, + defaultValue=argument.defaultValue, + treatNullAs=argument.treatNullAs, + isEnforceRange=argument.enforceRange, + isClamp=argument.clamp, + isMember="Variadic" if argument.variadic else False, + allowTreatNonObjectAsNull=argument.allowTreatNonCallableAsNull()) + + if not argument.variadic: + if argument.optional: + if argument.defaultValue: + assert default + template = CGIfElseWrapper(condition, + CGGeneric(template), + CGGeneric(default)).define() + else: + assert not default + declType = CGWrapper(declType, pre="Option<", post=">") + template = CGIfElseWrapper(condition, + CGGeneric("Some(%s)" % template), + CGGeneric("None")).define() + else: + assert not default + + self.converter = instantiateJSToNativeConversionTemplate( + template, replacementVariables, declType, "arg%d" % index, + needsRooting) + else: + assert argument.optional + variadicConversion = { + "val": string.Template("(*${argv}.offset(variadicArg as int))").substitute(replacer), + } + innerConverter = instantiateJSToNativeConversionTemplate( + template, variadicConversion, declType, "slot", + needsRooting) + + seqType = CGTemplatedType("Vec", declType) + variadicConversion = string.Template( + "{\n" + " let mut vector: ${seqType} = Vec::with_capacity((${argc} - ${index}) as uint);\n" + " for variadicArg in range(${index}, ${argc}) {\n" + "${inner}\n" + " vector.push(slot);\n" + " }\n" + " vector\n" + "}" + ).substitute({ + "index": index, + "argc": argc, + "seqType": seqType.define(), + "inner": CGIndenter(innerConverter, 4).define(), + }) + + self.converter = instantiateJSToNativeConversionTemplate( + variadicConversion, replacementVariables, seqType, "arg%d" % index, + False) + + def define(self): + return self.converter.define() + + +def wrapForType(jsvalRef, result='result', successCode='return 1;'): + """ + Reflect a Rust value into JS. + + * 'jsvalRef': a Rust reference to the JSVal in which to store the result + of the conversion; + * 'result': the name of the variable in which the Rust value is stored; + * 'successCode': the code to run once we have done the conversion. + """ + return "%s = (%s).to_jsval(cx);\n%s" % (jsvalRef, result, successCode) + + +def typeNeedsCx(type, retVal=False): + if type is None: + return False + if type.nullable(): + type = type.inner + if type.isSequence() or type.isArray(): + type = type.inner + if type.isUnion(): + return any(typeNeedsCx(t) for t in type.unroll().flatMemberTypes) + if retVal and type.isSpiderMonkeyInterface(): + return True + return type.isAny() or type.isObject() + +def typeRetValNeedsRooting(type): + if type is None: + return False + if type.nullable(): + type = type.inner + return type.isGeckoInterface() and not type.isCallback() and not type.isCallbackInterface() + +def memberIsCreator(member): + return member.getExtendedAttribute("Creator") is not None + +# Returns a CGThing containing the type of the return value. +def getRetvalDeclarationForType(returnType, descriptorProvider): + if returnType is None or returnType.isVoid(): + # Nothing to declare + return CGGeneric("()") + if returnType.isPrimitive() and returnType.tag() in builtinNames: + result = CGGeneric(builtinNames[returnType.tag()]) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isDOMString(): + result = CGGeneric("DOMString") + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isByteString(): + result = CGGeneric("ByteString") + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isEnum(): + result = CGGeneric(returnType.unroll().inner.identifier.name) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isGeckoInterface(): + descriptor = descriptorProvider.getDescriptor( + returnType.unroll().inner.identifier.name) + result = CGGeneric(descriptor.returnType) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isCallback(): + result = CGGeneric('%s::%s' % (returnType.unroll().module(), + returnType.unroll().identifier.name)) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isUnion(): + result = CGGeneric('%s::%s' % (returnType.unroll().name, returnType.unroll().name)) + if returnType.nullable(): + result = CGWrapper(result, pre="Option<", post=">") + return result + if returnType.isAny(): + return CGGeneric("JSVal") + if returnType.isObject() or returnType.isSpiderMonkeyInterface(): + return CGGeneric("*mut JSObject") + if returnType.isSequence(): + raise TypeError("We don't support sequence return values") + + raise TypeError("Don't know how to declare return value for %s" % + returnType) + +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.regular to the list of + things exposed to web pages. + """ + def __init__(self, descriptor, name): + self.descriptor = descriptor + self.name = name + + def variableName(self): + return "s" + self.name + + def length(self): + return len(self.regular) + + def __str__(self): + # We only need to generate id arrays for things that will end + # up used via ResolveProperty or EnumerateProperties. + return self.generateArray(self.regular, self.variableName()) + + def generatePrefableArray(self, array, name, specTemplate, specTerminator, + specType, getDataTuple): + """ + This method generates our various arrays. + + array is an array of interface members as passed to generateArray + + name is the name as passed to generateArray + + specTemplate is a template for each entry of the spec array + + specTerminator is a terminator for the spec array (inserted at the end + of the array), or None + + specType is the actual typename of our spec + + getDataTuple is a callback function that takes an array entry and + returns a tuple suitable for substitution into specTemplate. + """ + + assert(len(array) is not 0) + specs = [] + + for member in array: + specs.append(specTemplate % getDataTuple(member)) + if specTerminator: + specs.append(specTerminator) + + return (("static %s: &'static [%s] = &[\n" + + ",\n".join(specs) + "\n" + + "];\n\n") % (name, specType)) + +# The length of a method is the maximum of the lengths of the +# argument lists of all its overloads. +def methodLength(method): + signatures = method.signatures() + return max([len(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 __ + methods = [m for m in descriptor.interface.members if + m.isMethod() and m.isStatic() == static and + not m.isIdentifierLess()] + self.regular = [{"name": m.identifier.name, + "methodInfo": not m.isStatic(), + "length": methodLength(m), + "flags": "JSPROP_ENUMERATE" } + for m in methods] + + # 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, + "nativeName": "JS_ArrayIterator", + "length": 0, + "flags": "JSPROP_ENUMERATE" }) + + def generateArray(self, array, name): + if len(array) == 0: + return "" + + def specData(m): + if m.get("methodInfo", True): + jitinfo = ("&%s_methodinfo" % m["name"]) + accessor = "genericMethod" + else: + jitinfo = "0 as *const JSJitInfo" + accessor = m.get("nativeName", m["name"]) + return (m["name"], accessor, jitinfo, m["length"], m["flags"]) + + def stringDecl(m): + return "static %s_name: [u8, ..%i] = %s;\n" % (m["name"], len(m["name"]) + 1, + str_to_const_array(m["name"])) + + decls = ''.join([stringDecl(m) for m in array]) + return decls + self.generatePrefableArray( + array, name, + ' JSFunctionSpec {name: &%s_name as *const u8 as *const libc::c_char, call: JSNativeWrapper {op: Some(%s), info: %s}, nargs: %s, flags: %s as u16, selfHostedName: 0 as *const libc::c_char }', + ' JSFunctionSpec {name: 0 as *const libc::c_char, call: JSNativeWrapper {op: None, info: 0 as *const JSJitInfo}, nargs: 0, flags: 0, selfHostedName: 0 as *const libc::c_char }', + 'JSFunctionSpec', + specData) + +class AttrDefiner(PropertyDefiner): + def __init__(self, descriptor, name, static): + PropertyDefiner.__init__(self, descriptor, name) + self.name = name + self.regular = [ + m + for m in descriptor.interface.members + if m.isAttr() and m.isStatic() == static + ] + self.static = static + + def generateArray(self, array, name): + if len(array) == 0: + return "" + + def flags(attr): + return "JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS" + + def getter(attr): + if self.static: + accessor = 'get_' + attr.identifier.name + jitinfo = "0" + else: + if attr.hasLenientThis(): + accessor = "genericLenientGetter" + else: + accessor = "genericGetter" + jitinfo = "&%s_getterinfo" % attr.identifier.name + + return ("JSPropertyOpWrapper {op: Some(%(native)s), info: %(info)s as *const JSJitInfo}" + % {"info" : jitinfo, + "native" : accessor}) + + def setter(attr): + if attr.readonly: + return "JSStrictPropertyOpWrapper {op: None, info: 0 as *const JSJitInfo}" + + if self.static: + accessor = 'set_' + attr.identifier.name + jitinfo = "0" + else: + if attr.hasLenientThis(): + accessor = "genericLenientSetter" + else: + accessor = "genericSetter" + jitinfo = "&%s_setterinfo" % attr.identifier.name + + return ("JSStrictPropertyOpWrapper {op: Some(%(native)s), info: %(info)s as *const JSJitInfo}" + % {"info" : jitinfo, + "native" : accessor}) + + def specData(attr): + return (attr.identifier.name, flags(attr), getter(attr), + setter(attr)) + + def stringDecl(attr): + name = attr.identifier.name + return "static %s_name: [u8, ..%i] = %s;\n" % (name, len(name) + 1, + str_to_const_array(name)) + + decls = ''.join([stringDecl(m) for m in array]) + + return decls + self.generatePrefableArray( + array, name, + ' JSPropertySpec { name: &%s_name as *const u8 as *const libc::c_char, tinyid: 0, flags: ((%s) & 0xFF) as u8, getter: %s, setter: %s }', + ' JSPropertySpec { name: 0 as *const libc::c_char, tinyid: 0, flags: 0, getter: JSPropertyOpWrapper {op: None, info: 0 as *const JSJitInfo}, setter: JSStrictPropertyOpWrapper {op: None, info: 0 as *const JSJitInfo} }', + 'JSPropertySpec', + specData) + +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 + self.regular = [m for m in descriptor.interface.members if m.isConst()] + + def generateArray(self, array, name): + if len(array) == 0: + return "" + + def specData(const): + return (const.identifier.name, + convertConstIDLValueToJSVal(const.value)) + + def stringDecl(const): + name = const.identifier.name + return "static %s_name: &'static [u8] = &%s;\n" % (name, str_to_const_array(name)) + + decls = ''.join([stringDecl(m) for m in array]) + + return decls + self.generatePrefableArray( + array, name, + ' ConstantSpec { name: %s_name, value: %s }', + None, + 'ConstantSpec', + specData) + +# 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) +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): + CGThing.__init__(self) + self.child = child + self.indent = " " * indentLevel + + def define(self): + defn = self.child.define() + if defn is not "": + return re.sub(lineStartDetector, self.indent, defn) + else: + return defn + +class CGWrapper(CGThing): + """ + Generic CGThing that wraps other CGThings with pre and post text. + """ + def __init__(self, child, pre="", post="", reindent=False): + CGThing.__init__(self) + self.child = child + self.pre = pre + self.post = post + self.reindent = reindent + + def define(self): + defn = self.child.define() + if self.reindent: + # We don't use lineStartDetector because we don't want to + # insert whitespace at the beginning of our _first_ line. + defn = stripTrailingWhitespace( + defn.replace("\n", "\n" + (" " * len(self.pre)))) + return self.pre + defn + self.post + +class CGImports(CGWrapper): + """ + Generates the appropriate import/use statements. + """ + def __init__(self, child, descriptors, imports): + """ + Adds a set of imports. + """ + ignored_warnings = [ + # Allow unreachable_code because we use 'break' in a way that + # sometimes produces two 'break's in a row. See for example + # CallbackMember.getArgConversions. + 'unreachable_code', + 'non_camel_case_types', + 'non_uppercase_statics', + 'unnecessary_parens', + 'unused_imports', + 'unused_variable', + 'unused_unsafe', + 'unused_mut', + 'dead_assignment', + 'dead_code', + ] + + statements = ['#![allow(%s)]' % ','.join(ignored_warnings)] + statements.extend('use %s;' % i for i in sorted(imports)) + + CGWrapper.__init__(self, child, + pre='\n'.join(statements) + '\n\n') + + @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.rs') + +class CGIfWrapper(CGWrapper): + def __init__(self, child, condition): + pre = CGWrapper(CGGeneric(condition), pre="if ", post=" {\n", + reindent=True) + CGWrapper.__init__(self, CGIndenter(child), pre=pre.define(), + post="\n}") + +class CGTemplatedType(CGWrapper): + def __init__(self, templateName, child): + CGWrapper.__init__(self, child, pre=templateName + "<", post=">") + +class CGNamespace(CGWrapper): + def __init__(self, namespace, child, public=False): + pre = "%smod %s {\n" % ("pub " if public else "", namespace) + post = "} // mod %s\n" % namespace + CGWrapper.__init__(self, child, pre=pre, post=post) + + @staticmethod + def build(namespaces, child, public=False): + """ + Static helper method to build multiple wrapped namespaces. + """ + if not namespaces: + return child + inner = CGNamespace.build(namespaces[1:], child, public=public) + return CGNamespace(namespaces[0], inner, public=public) + +def DOMClass(descriptor): + protoList = ['PrototypeList::id::' + proto for proto in descriptor.prototypeChain] + # Pad out the list to the right length with IDCount so we + # guarantee that all the lists are the same length. IDCount + # is never the ID of any prototype, so it's safe to use as + # padding. + protoList.extend(['PrototypeList::id::IDCount'] * (descriptor.config.maxProtoChainLength - len(protoList))) + prototypeChainString = ', '.join(protoList) + return """DOMClass { + interface_chain: [ %s ], + native_hooks: &sNativePropertyHooks, +}""" % prototypeChainString + +class CGDOMJSClass(CGThing): + """ + Generate a DOMJSClass for a given descriptor + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + traceHook = "Some(%s)" % TRACE_HOOK_NAME + if self.descriptor.isGlobal(): + flags = "JSCLASS_IS_GLOBAL | JSCLASS_DOM_GLOBAL" + slots = "JSCLASS_GLOBAL_SLOT_COUNT + 1" + else: + flags = "0" + slots = "1" + return """ +static Class_name: [u8, ..%i] = %s; +static Class: DOMJSClass = DOMJSClass { + base: js::Class { + name: &Class_name as *const u8 as *const libc::c_char, + flags: JSCLASS_IS_DOMJSCLASS | %s | (((%s) & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT as uint), //JSCLASS_HAS_RESERVED_SLOTS(%s), + addProperty: Some(JS_PropertyStub), + delProperty: Some(JS_PropertyStub), + getProperty: Some(JS_PropertyStub), + setProperty: Some(JS_StrictPropertyStub), + enumerate: Some(JS_EnumerateStub), + resolve: Some(JS_ResolveStub), + convert: Some(JS_ConvertStub), + finalize: Some(%s), + checkAccess: None, + call: None, + hasInstance: None, + construct: None, + trace: %s, + + ext: js::ClassExtension { + equality: 0 as *const u8, + outerObject: %s, + innerObject: None, + iteratorObject: 0 as *const u8, + unused: 0 as *const u8, + isWrappedNative: 0 as *const u8, + }, + + ops: js::ObjectOps { + lookupGeneric: 0 as *const u8, + lookupProperty: 0 as *const u8, + lookupElement: 0 as *const u8, + lookupSpecial: 0 as *const u8, + defineGeneric: 0 as *const u8, + defineProperty: 0 as *const u8, + defineElement: 0 as *const u8, + defineSpecial: 0 as *const u8, + getGeneric: 0 as *const u8, + getProperty: 0 as *const u8, + getElement: 0 as *const u8, + getElementIfPresent: 0 as *const u8, + getSpecial: 0 as *const u8, + setGeneric: 0 as *const u8, + setProperty: 0 as *const u8, + setElement: 0 as *const u8, + setSpecial: 0 as *const u8, + getGenericAttributes: 0 as *const u8, + getPropertyAttributes: 0 as *const u8, + getElementAttributes: 0 as *const u8, + getSpecialAttributes: 0 as *const u8, + setGenericAttributes: 0 as *const u8, + setPropertyAttributes: 0 as *const u8, + setElementAttributes: 0 as *const u8, + setSpecialAttributes: 0 as *const u8, + deleteProperty: 0 as *const u8, + deleteElement: 0 as *const u8, + deleteSpecial: 0 as *const u8, + + enumerate: 0 as *const u8, + typeOf: 0 as *const u8, + thisObject: %s, + clear: 0 as *const u8, + }, + }, + dom_class: %s +}; +""" % (len(self.descriptor.interface.identifier.name) + 1, + str_to_const_array(self.descriptor.interface.identifier.name), + flags, slots, slots, + FINALIZE_HOOK_NAME, traceHook, + self.descriptor.outerObjectHook, + self.descriptor.outerObjectHook, + CGIndenter(CGGeneric(DOMClass(self.descriptor))).define()) + +def str_to_const_array(s): + return "[" + (", ".join(map(lambda x: "'" + x + "' as u8", list(s)) + ['0 as u8'])) + "]" + +class CGPrototypeJSClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + return """ +static PrototypeClassName__: [u8, ..%s] = %s; +static PrototypeClass: JSClass = JSClass { + name: &PrototypeClassName__ as *const u8 as *const libc::c_char, + flags: (1 & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT as uint, //JSCLASS_HAS_RESERVED_SLOTS(1) + addProperty: Some(JS_PropertyStub), + delProperty: Some(JS_PropertyStub), + getProperty: Some(JS_PropertyStub), + setProperty: Some(JS_StrictPropertyStub), + enumerate: Some(JS_EnumerateStub), + resolve: Some(JS_ResolveStub), + convert: Some(JS_ConvertStub), + finalize: None, + checkAccess: None, + call: None, + hasInstance: None, + construct: None, + trace: None, + reserved: [0 as *mut libc::c_void, ..40] +}; +""" % (len(self.descriptor.interface.identifier.name + "Prototype") + 1, + str_to_const_array(self.descriptor.interface.identifier.name + "Prototype")) + +class CGInterfaceObjectJSClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + if True: + return "" + ctorname = "0 as *const u8" if not self.descriptor.interface.ctor() else CONSTRUCT_HOOK_NAME + hasinstance = HASINSTANCE_HOOK_NAME + return """ +static InterfaceObjectClass: JSClass = { + %s, 0, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_StrictPropertyStub, + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + 0 as *const u8, + 0 as *const u8, + %s, + %s, + %s, + 0 as *const u8, + JSCLASS_NO_INTERNAL_MEMBERS +}; +""" % (str_to_const_array("Function"), ctorname, hasinstance, ctorname) + +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) + self.children = children + self.joiner = joiner + def append(self, child): + self.children.append(child) + def prepend(self, child): + self.children.insert(0, child) + def join(self, generator): + return self.joiner.join(filter(lambda s: len(s) > 0, (child for child in generator))) + + def define(self): + return self.join(child.define() for child in self.children if child is not None) + + +class CGIfElseWrapper(CGList): + def __init__(self, condition, ifTrue, ifFalse): + kids = [ CGIfWrapper(ifTrue, condition), + CGWrapper(CGIndenter(ifFalse), pre=" else {\n", post="\n}") ] + CGList.__init__(self, kids) + + +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, text): + self.text = text + + def define(self): + return self.text + +class CGCallbackTempRoot(CGGeneric): + def __init__(self, name): + val = "%s::new(tempRoot)" % name + define = """{ + let tempRoot = ${val}.to_object(); + %s +}""" % val + CGGeneric.__init__(self, define) + + +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: + 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) + +def SortedTuples(l): + """ + Sort a list of tuples based on the first item in the tuple + """ + return sorted(l, key=operator.itemgetter(0)) + +def SortedDictValues(d): + """ + Returns a list of values from the dict sorted by key. + """ + # Create a list of tuples containing key and value, sorted on key. + d = SortedTuples(d.items()) + # We're only interested in the values. + return (i[1] for i in d) + +def UnionTypes(descriptors, dictionaries, callbacks, config): + """ + Returns a CGList containing CGUnionStructs for every union. + """ + + imports = [ + 'dom::bindings::utils::unwrap_jsmanaged', + 'dom::bindings::codegen::PrototypeList', + 'dom::bindings::conversions::FromJSValConvertible', + 'dom::bindings::conversions::ToJSValConvertible', + 'dom::bindings::conversions::Default', + 'dom::bindings::error::throw_not_in_union', + 'dom::bindings::js::JS', + 'dom::types::*', + 'js::jsapi::JSContext', + 'js::jsval::JSVal', + 'servo_util::str::DOMString', + ] + + # Now find all the things we'll need as arguments and return values because + # we need to wrap or unwrap them. + unionStructs = dict() + for (t, descriptor, dictionary) in getAllTypes(descriptors, dictionaries, callbacks): + assert not descriptor or not dictionary + t = t.unroll() + if not t.isUnion(): + continue + name = str(t) + if not name in unionStructs: + provider = descriptor or config.getDescriptorProvider() + unionStructs[name] = CGNamespace(name, + CGImports(CGList([ + CGUnionStruct(t, provider), + CGUnionConversionStruct(t, provider) + ]), [], imports), + public=True) + + return CGList(SortedDictValues(unionStructs), "\n\n") + + +class Argument(): + """ + A class for outputting the type and name of an argument + """ + def __init__(self, argType, name, default=None, mutable=False): + self.argType = argType + self.name = name + self.default = default + self.mutable = mutable + def declare(self): + string = ('mut ' if self.mutable else '') + self.name + ((': ' + self.argType) if self.argType else '') + #XXXjdm Support default arguments somehow :/ + #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. + + 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, extern=False, pub=False, templateArgs=None, unsafe=True): + CGThing.__init__(self) + self.descriptor = descriptor + self.name = name + self.returnType = returnType + self.args = args + self.alwaysInline = alwaysInline + self.extern = extern + self.templateArgs = templateArgs + self.pub = pub; + self.unsafe = unsafe + def _argstring(self): + return ', '.join([a.declare() for a in self.args]) + def _template(self): + if self.templateArgs is None: + return '' + return '<%s>\n' % ', '.join(self.templateArgs) + + def _decorators(self): + decorators = [] + if self.alwaysInline: + decorators.append('#[inline(always)]') + + if self.extern: + decorators.append('extern') + + if self.pub: + decorators.append('pub') + + if not decorators: + return '' + return ' '.join(decorators) + ' ' + + def _returnType(self): + return (" -> %s" % self.returnType) if self.returnType != "void" else "" + + def define(self): + body = self.definition_body() + if self.unsafe: + body = CGWrapper(body, pre="unsafe {\n", post="\n}") + + return CGWrapper(CGIndenter(body), + pre=self.definition_prologue(), + post=self.definition_epilogue()).define() + + def definition_prologue(self): + return "%sfn %s%s(%s)%s {\n" % (self._decorators(), self.name, self._template(), + self._argstring(), self._returnType()) + def definition_epilogue(self): + return "\n}\n" + def definition_body(self): + assert(False) # Override me! + +def CreateBindingJSObject(descriptor, parent=None): + create = "let mut raw: JS<%s> = JS::from_raw(&*aObject);\n" % descriptor.concreteType + if descriptor.proxy: + assert not descriptor.isGlobal() + create += """ +let handler = RegisterBindings::proxy_handlers[PrototypeList::proxies::%s as uint]; +let mut private = PrivateValue(squirrel_away_unique(aObject) as *const libc::c_void); +let obj = with_compartment(aCx, proto, || { + NewProxyObject(aCx, handler, + &private, + proto, %s, + ptr::mut_null(), ptr::mut_null()) +}); +assert!(obj.is_not_null()); + +""" % (descriptor.name, parent) + else: + if descriptor.isGlobal(): + create += "let obj = CreateDOMGlobal(aCx, &Class.base as *const js::Class as *const JSClass);\n" + else: + create += ("let obj = with_compartment(aCx, proto, || {\n" + " JS_NewObject(aCx, &Class.base as *const js::Class as *const JSClass, &*proto, &*%s)\n" + "});\n" % parent) + create += """assert!(obj.is_not_null()); + +JS_SetReservedSlot(obj, DOM_OBJECT_SLOT as u32, + PrivateValue(squirrel_away_unique(aObject) as *const libc::c_void)); +""" + return create + +class CGWrapMethod(CGAbstractMethod): + """ + Class that generates the FooBinding::Wrap function for non-callback + interfaces. + """ + def __init__(self, descriptor): + assert not descriptor.interface.isCallback() + if not descriptor.isGlobal(): + args = [Argument('*mut JSContext', 'aCx'), Argument('&GlobalRef', 'aScope'), + Argument("Box<%s>" % descriptor.concreteType, 'aObject', mutable=True)] + else: + args = [Argument('*mut JSContext', 'aCx'), + Argument("Box<%s>" % descriptor.concreteType, 'aObject', mutable=True)] + retval = 'Temporary<%s>' % descriptor.concreteType + CGAbstractMethod.__init__(self, descriptor, 'Wrap', retval, args, pub=True) + + def definition_body(self): + if not self.descriptor.isGlobal(): + return CGGeneric("""\ +let scope = aScope.reflector().get_jsobject(); +assert!(scope.is_not_null()); +assert!(((*JS_GetClass(scope)).flags & JSCLASS_IS_GLOBAL) != 0); + +let proto = with_compartment(aCx, scope, || GetProtoObject(aCx, scope, scope)); +assert!(proto.is_not_null()); + +%s + +raw.reflector().set_jsobject(obj); + +Temporary::new(raw)""" % CreateBindingJSObject(self.descriptor, "scope")) + else: + return CGGeneric("""\ +%s +with_compartment(aCx, obj, || { + let proto = GetProtoObject(aCx, obj, obj); + JS_SetPrototype(aCx, obj, proto); + + raw.reflector().set_jsobject(obj); + + RegisterBindings::Register(aCx, obj); +}); + +Temporary::new(raw)""" % CreateBindingJSObject(self.descriptor)) + + +class CGIDLInterface(CGThing): + """ + Class for codegen of an implementation of the IDLInterface trait. + """ + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + replacer = { + 'type': self.descriptor.name, + 'depth': self.descriptor.interface.inheritanceDepth(), + } + return string.Template(""" +impl IDLInterface for ${type} { + fn get_prototype_id(_: Option<${type}>) -> PrototypeList::id::ID { + PrototypeList::id::${type} + } + fn get_prototype_depth(_: Option<${type}>) -> uint { + ${depth} + } +} +""").substitute(replacer) + + +class CGAbstractExternMethod(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, extern=True) + +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.consts = ConstDefiner(descriptor, "Constants") + pass + + @staticmethod + def arrayNames(): + return [ "staticMethods", "staticAttrs", "methods", "attrs", "consts" ] + + def variableNames(self): + names = {} + for array in self.arrayNames(): + names[array] = getattr(self, array).variableName() + return names + def __str__(self): + define = "" + for array in self.arrayNames(): + define += str(getattr(self, array)) + return define + + +class CGNativeProperties(CGThing): + def __init__(self, descriptor, properties): + CGThing.__init__(self) + self.properties = properties + + def define(self): + def getField(array): + propertyArray = getattr(self.properties, array) + if propertyArray.length() > 0: + value = "Some(%s)" % propertyArray.variableName() + else: + value = "None" + + return CGGeneric(string.Template('${name}: ${value},').substitute({ + 'name': array, + 'value': value, + })) + + nativeProps = CGList([getField(array) for array in self.properties.arrayNames()], '\n') + return CGWrapper(CGIndenter(nativeProps), + pre="static sNativeProperties: NativeProperties = NativeProperties {\n", + post="\n};\n").define() + + +class CGCreateInterfaceObjectsMethod(CGAbstractMethod): + """ + Generate the CreateInterfaceObjects method for an interface descriptor. + + properties should be a PropertyArrays instance. + """ + def __init__(self, descriptor, properties): + assert not descriptor.interface.isCallback() + args = [Argument('*mut JSContext', 'aCx'), Argument('*mut JSObject', 'aGlobal'), + Argument('*mut JSObject', 'aReceiver')] + CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', '*mut JSObject', args) + self.properties = properties + def definition_body(self): + protoChain = self.descriptor.prototypeChain + if len(protoChain) == 1: + getParentProto = "JS_GetObjectPrototype(aCx, aGlobal)" + else: + parentProtoName = self.descriptor.prototypeChain[-2] + getParentProto = ("%s::GetProtoObject(aCx, aGlobal, aReceiver)" % + toBindingNamespace(parentProtoName)) + + getParentProto = ("let parentProto: *mut JSObject = %s;\n" + "assert!(parentProto.is_not_null());\n") % getParentProto + + if self.descriptor.concrete: + if self.descriptor.proxy: + domClass = "&Class" + else: + domClass = "&Class.dom_class" + else: + domClass = "ptr::null()" + + if self.descriptor.interface.hasInterfaceObject(): + if self.descriptor.interface.ctor(): + constructHook = CONSTRUCT_HOOK_NAME + constructArgs = methodLength(self.descriptor.interface.ctor()) + else: + constructHook = "ThrowingConstructor" + constructArgs = 0 + + constructor = 'Some((%s, "%s", %d))' % ( + constructHook, self.descriptor.interface.identifier.name, + constructArgs) + else: + constructor = 'None' + + call = """return CreateInterfaceObjects2(aCx, aGlobal, aReceiver, parentProto, + &PrototypeClass, %s, + %s, + &sNativeProperties);""" % (constructor, domClass) + + return CGList([ + CGGeneric(getParentProto), + CGGeneric(call % self.properties.variableNames()) + ], "\n") + +class CGGetPerInterfaceObject(CGAbstractMethod): + """ + A method for getting a per-interface object (a prototype object or interface + constructor object). + """ + def __init__(self, descriptor, name, idPrefix="", pub=False): + args = [Argument('*mut JSContext', 'aCx'), Argument('*mut JSObject', 'aGlobal'), + Argument('*mut JSObject', 'aReceiver')] + CGAbstractMethod.__init__(self, descriptor, name, + '*mut JSObject', args, pub=pub) + self.id = idPrefix + "id::" + self.descriptor.name + def definition_body(self): + return CGGeneric(""" + +/* aGlobal and aReceiver are usually the same, but they can be different + too. For example a sandbox often has an xray wrapper for a window as the + prototype of the sandbox's global. In that case aReceiver is the xray + wrapper and aGlobal is the sandbox's global. + */ + +assert!(((*JS_GetClass(aGlobal)).flags & JSCLASS_DOM_GLOBAL) != 0); + +/* Check to see whether the interface objects are already installed */ +let protoOrIfaceArray = GetProtoOrIfaceArray(aGlobal); +let cachedObject: *mut JSObject = *protoOrIfaceArray.offset(%s as int); +if cachedObject.is_null() { + let tmp: *mut JSObject = CreateInterfaceObjects(aCx, aGlobal, aReceiver); + assert!(tmp.is_not_null()); + *protoOrIfaceArray.offset(%s as int) = tmp; + tmp +} else { + cachedObject +}""" % (self.id, self.id)) + +class CGGetProtoObjectMethod(CGGetPerInterfaceObject): + """ + A method for getting the interface prototype object. + """ + def __init__(self, descriptor): + CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject", + "PrototypeList::", pub=True) + def definition_body(self): + return CGList([ + CGGeneric("""\ +/* Get the interface prototype object for this class. This will create the + object as needed. */"""), + 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::") + def definition_body(self): + return CGList([ + CGGeneric("""\ +/* Get the interface object for this class. This will create the object as + needed. */"""), + CGGetPerInterfaceObject.definition_body(self), + ]) + + +class CGDefineProxyHandler(CGAbstractMethod): + """ + A method to create and cache the proxy trap for a given interface. + """ + def __init__(self, descriptor): + assert descriptor.proxy + CGAbstractMethod.__init__(self, descriptor, 'DefineProxyHandler', '*const libc::c_void', [], pub=True) + + def define(self): + return CGAbstractMethod.define(self) + + def definition_body(self): + body = """\ +let traps = ProxyTraps { + getPropertyDescriptor: Some(getPropertyDescriptor), + getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor), + defineProperty: Some(defineProperty), + getOwnPropertyNames: ptr::null(), + delete_: Some(delete_), + enumerate: ptr::null(), + + has: None, + hasOwn: Some(hasOwn), + get: Some(get), + set: None, + keys: ptr::null(), + iterate: None, + + call: None, + construct: None, + nativeCall: ptr::null(), + hasInstance: None, + typeOf: None, + objectClassIs: None, + obj_toString: Some(obj_toString), + fun_toString: None, + //regexp_toShared: ptr::null(), + defaultValue: None, + iteratorNext: None, + finalize: Some(%s), + getElementIfPresent: None, + getPrototypeOf: None, + trace: Some(%s) +}; + +CreateProxyHandler(&traps, &Class as *const _ as *const _) +""" % (FINALIZE_HOOK_NAME, + TRACE_HOOK_NAME) + return CGGeneric(body) + + + +class CGDefineDOMInterfaceMethod(CGAbstractMethod): + """ + A method for resolve hooks to try to lazily define the interface object for + a given interface. + """ + def __init__(self, descriptor): + assert descriptor.interface.hasInterfaceObject() + args = [ + Argument('*mut JSContext', 'cx'), + Argument('*mut JSObject', 'global'), + ] + CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', 'void', args, pub=True) + + def define(self): + return CGAbstractMethod.define(self) + + def definition_body(self): + return CGGeneric("""\ +assert!(global.is_not_null()); +assert!(GetProtoObject(cx, global, global).is_not_null());""") + +def needCx(returnType, arguments, considerTypes): + return (considerTypes and + (typeNeedsCx(returnType, True) or + any(typeNeedsCx(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. + + errorResult should be a string for the value to return in case of an + exception from the native code, or None if no error reporting is needed. + """ + def __init__(self, errorResult, arguments, argsPre, returnType, + extendedAttributes, descriptorProvider, nativeMethodName, + static, object="this"): + CGThing.__init__(self) + + assert errorResult is None or isinstance(errorResult, str) + + isFallible = errorResult is not None + + result = getRetvalDeclarationForType(returnType, descriptorProvider) + if isFallible: + result = CGWrapper(result, pre="Result<", post=", Error>") + + args = CGList([CGGeneric(arg) for arg in argsPre], ", ") + for (a, name) in arguments: + #XXXjdm Perhaps we should pass all nontrivial types by borrowed pointer + if a.type.isGeckoInterface(): + if not (a.type.nullable() or a.optional): + name = "&" + name + elif a.type.isDictionary(): + name = "&" + name + args.append(CGGeneric(name)) + + needsCx = needCx(returnType, (a for (a, _) in arguments), True) + + if not "cx" in argsPre and needsCx: + args.prepend(CGGeneric("cx")) + + # Build up our actual call + self.cgRoot = CGList([], "\n") + + call = CGGeneric(nativeMethodName) + if static: + call = CGWrapper(call, pre="%s::" % descriptorProvider.interface.identifier.name) + else: + call = CGWrapper(call, pre="(*%s)." % object) + call = CGList([call, CGWrapper(args, pre="(", post=")")]) + + self.cgRoot.append(CGList([ + CGGeneric("let result: "), + result, + CGGeneric(" = "), + call, + CGGeneric(";"), + ])) + + if isFallible: + if static: + glob = "" + else: + glob = " let global = global_object_for_js_object(this.reflector().get_jsobject());\n"\ + " let global = global.root();\n" + + self.cgRoot.append(CGGeneric( + "let result = match result {\n" + " Ok(result) => result,\n" + " Err(e) => {\n" + "%s" + " throw_dom_exception(cx, &global.root_ref(), e);\n" + " return%s;\n" + " },\n" + "};\n" % (glob, errorResult))) + + if typeRetValNeedsRooting(returnType): + self.cgRoot.append(CGGeneric("let result = result.root();")) + + def define(self): + return self.cgRoot.define() + +class MethodNotCreatorError(Exception): + def __init__(self, typename): + self.typename = typename + +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, argsPre, arguments, nativeMethodName, static, + descriptor, idlNode, argConversionStartsAt=0, + getter=False, setter=False): + CGThing.__init__(self) + self.returnType = returnType + self.descriptor = descriptor + self.idlNode = idlNode + self.extendedAttributes = descriptor.getExtendedAttributes(idlNode, + getter=getter, + setter=setter) + self.argsPre = argsPre + self.arguments = arguments + self.argCount = len(arguments) + if self.argCount > argConversionStartsAt: + # Insert our argv in there + cgThings = [CGGeneric(self.getArgvDecl())] + else: + cgThings = [] + cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(), + self.getArgc(), self.descriptor, + invalidEnumValueFatal=not setter) for + i in range(argConversionStartsAt, self.argCount)]) + + cgThings.append(CGCallGenerator( + ' false as JSBool' if self.isFallible() else None, + self.getArguments(), self.argsPre, returnType, + self.extendedAttributes, descriptor, nativeMethodName, + static)) + self.cgRoot = CGList(cgThings, "\n") + + def getArgv(self): + return "argv" if self.argCount > 0 else "" + def getArgvDecl(self): + return "\nlet argv = JS_ARGV(cx, vp);\n" + def getArgc(self): + return "argc" + def getArguments(self): + def process(arg, i): + argVal = "arg" + str(i) + if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback(): + argVal += ".root_ref()" + return argVal + return [(a, process(a, i)) for (i, a) in enumerate(self.arguments)] + + def isFallible(self): + return not 'infallible' in self.extendedAttributes + + def wrap_return_value(self): + return wrapForType('*vp') + + def define(self): + return (self.cgRoot.define() + "\n" + 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], "\n") + self.prepend(CGWrapper(CGGeneric(expression), + pre="match ", post=" {")); + if default is not None: + self.append( + CGIndenter( + CGWrapper( + CGIndenter(default), + pre="_ => {\n", + post="\n}" + ) + ) + ) + + self.append(CGGeneric("}")) + +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, [], "\n") + self.append(CGWrapper(CGGeneric(expression), post=" => {")) + bodyList = CGList([body], "\n") + if fallThrough: + raise TypeError("fall through required but unsupported") + #bodyList.append(CGGeneric('fail!("fall through unsupported"); /* Fall through */')) + self.append(CGIndenter(bodyList)); + self.append(CGGeneric("}")) + +class CGGetterCall(CGPerSignatureCall): + """ + A class to generate a native object getter call for a particular IDL + getter. + """ + def __init__(self, argsPre, returnType, nativeMethodName, descriptor, attr): + CGPerSignatureCall.__init__(self, returnType, argsPre, [], + 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, allowTreatNonObjectAsNull=False): + self.type = type + self.optional = False + self.variadic = False + self.defaultValue = None + self._allowTreatNonObjectAsNull = allowTreatNonObjectAsNull + self.treatNullAs = interfaceMember.treatNullAs + self.enforceRange = False + self.clamp = False + + def allowTreatNonCallableAsNull(self): + return self._allowTreatNonObjectAsNull + +class CGSetterCall(CGPerSignatureCall): + """ + A class to generate a native object setter call for a particular IDL + setter. + """ + def __init__(self, argsPre, argType, nativeMethodName, descriptor, attr): + CGPerSignatureCall.__init__(self, None, argsPre, + [FakeArgument(argType, attr, allowTreatNonObjectAsNull=True)], + nativeMethodName, attr.isStatic(), descriptor, attr, + setter=True) + def wrap_return_value(self): + # We have no return value + return "\nreturn 1;" + def getArgc(self): + return "1" + def getArgvDecl(self): + # We just get our stuff from our last arg no matter what + return "" + +class CGAbstractBindingMethod(CGAbstractExternMethod): + """ + 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. + """ + def __init__(self, descriptor, name, args, unwrapFailureCode=None): + CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args) + + if unwrapFailureCode is None: + self.unwrapFailureCode = ( + 'throw_type_error(cx, "\\"this\\" object does not ' + 'implement interface %s.");\n' + 'return 0;' % descriptor.interface.identifier.name) + else: + self.unwrapFailureCode = unwrapFailureCode + + def definition_body(self): + # 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 FailureFatalCastableObjectUnwrapper. + unwrapThis = str(CastableObjectUnwrapper( + FakeCastableDescriptor(self.descriptor), + "obj", self.unwrapFailureCode)) + unwrapThis = CGGeneric( + "let obj: *mut JSObject = JS_THIS_OBJECT(cx, vp as *mut JSVal);\n" + "if obj.is_null() {\n" + " return false as JSBool;\n" + "}\n" + "\n" + "let this: JS<%s> = %s;\n" % (self.descriptor.concreteType, unwrapThis)) + return CGList([ unwrapThis, self.generate_code() ], "\n") + + def generate_code(self): + assert(False) # Override me + + +class CGAbstractStaticBindingMethod(CGAbstractMethod): + """ + 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('*mut JSContext', 'cx'), + Argument('libc::c_uint', 'argc'), + Argument('*mut JSVal', 'vp'), + ] + CGAbstractMethod.__init__(self, descriptor, name, "JSBool", args, extern=True) + + def definition_body(self): + return self.generate_code() + + def generate_code(self): + assert False # Override me + + +class CGGenericMethod(CGAbstractBindingMethod): + """ + A class for generating the C++ code for an IDL method.. + """ + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('libc::c_uint', 'argc'), + Argument('*mut JSVal', 'vp')] + CGAbstractBindingMethod.__init__(self, descriptor, 'genericMethod', args) + + def generate_code(self): + return CGGeneric( + "let _info: *const JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "return CallJitMethodOp(_info, cx, obj, this.unsafe_get() as *mut libc::c_void, argc, vp);") + +class CGSpecializedMethod(CGAbstractExternMethod): + """ + 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 = method.identifier.name + args = [Argument('*mut JSContext', 'cx'), Argument('JSHandleObject', '_obj'), + Argument('*const %s' % descriptor.concreteType, 'this'), + Argument('libc::c_uint', 'argc'), Argument('*mut JSVal', 'vp')] + CGAbstractExternMethod.__init__(self, descriptor, name, 'JSBool', args) + + def definition_body(self): + nativeName = CGSpecializedMethod.makeNativeName(self.descriptor, + self.method) + return CGWrapper(CGMethodCall([], nativeName, self.method.isStatic(), + self.descriptor, self.method), + pre="let this = JS::from_raw(this);\n" + "let this = this.root();\n") + + @staticmethod + def makeNativeName(descriptor, method): + return MakeNativeName(method.identifier.name) + +class CGStaticMethod(CGAbstractStaticBindingMethod): + """ + A class for generating the Rust 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): + args = [Argument('*mut JSContext', 'cx'), Argument('libc::c_uint', 'argc'), + Argument('*mut JSVal', 'vp')] + if lenientThis: + name = "genericLenientGetter" + unwrapFailureCode = ( + "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n" + "JS_SET_RVAL(cx, vp, JS::UndefinedValue());\n" + "return true;") + else: + name = "genericGetter" + unwrapFailureCode = None + CGAbstractBindingMethod.__init__(self, descriptor, name, args, + unwrapFailureCode) + + def generate_code(self): + return CGGeneric( + "let info: *const JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "return CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *mut libc::c_void, vp);\n") + +class CGSpecializedGetter(CGAbstractExternMethod): + """ + 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('*mut JSContext', 'cx'), + Argument('JSHandleObject', '_obj'), + Argument('*const %s' % descriptor.concreteType, 'this'), + Argument('*mut JSVal', 'vp') ] + CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args) + + def definition_body(self): + nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, + self.attr) + + return CGWrapper(CGGetterCall([], self.attr.type, nativeName, + self.descriptor, self.attr), + pre="let this = JS::from_raw(this);\n" + "let this = this.root();\n") + + @staticmethod + def makeNativeName(descriptor, attr): + nativeName = MakeNativeName(attr.identifier.name) + infallible = ('infallible' in + descriptor.getExtendedAttributes(attr, getter=True)) + if attr.type.nullable() or not infallible: + return "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 CGGetterCall([], self.attr.type, nativeName, self.descriptor, + self.attr) + + +class CGGenericSetter(CGAbstractBindingMethod): + """ + A class for generating the Rust code for an IDL attribute setter. + """ + def __init__(self, descriptor, lenientThis=False): + args = [Argument('*mut JSContext', 'cx'), Argument('libc::c_uint', 'argc'), + Argument('*mut JSVal', 'vp')] + if lenientThis: + name = "genericLenientSetter" + unwrapFailureCode = ( + "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n" + "return true;") + else: + name = "genericSetter" + unwrapFailureCode = None + CGAbstractBindingMethod.__init__(self, descriptor, name, args, + unwrapFailureCode) + + def generate_code(self): + return CGGeneric( + "let mut undef = UndefinedValue();\n" + "let argv: *mut JSVal = if argc != 0 { JS_ARGV(cx, vp) } else { &mut undef as *mut JSVal };\n" + "let info: *const JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" + "if CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *mut libc::c_void, argv) == 0 {\n" + " return 0;\n" + "}\n" + "*vp = UndefinedValue();\n" + "return 1;") + +class CGSpecializedSetter(CGAbstractExternMethod): + """ + 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('*mut JSContext', 'cx'), + Argument('JSHandleObject', '_obj'), + Argument('*const %s' % descriptor.concreteType, 'this'), + Argument('*mut JSVal', 'argv')] + CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args) + + def definition_body(self): + nativeName = CGSpecializedSetter.makeNativeName(self.descriptor, + self.attr) + return CGWrapper(CGSetterCall([], self.attr.type, nativeName, + self.descriptor, self.attr), + pre="let this = JS::from_raw(this);\n" + "let this = this.root();\n") + + @staticmethod + def makeNativeName(descriptor, attr): + return "Set" + MakeNativeName(attr.identifier.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( + "let argv = JS_ARGV(cx, vp);\n" + "if (argc == 0) {\n" + " throw_type_error(cx, \"Not enough arguments to %s setter.\");\n" + " return 0;\n" + "}\n" % self.attr.identifier.name) + call = CGSetterCall([], self.attr.type, nativeName, self.descriptor, + self.attr) + return CGList([checkForArg, call]) + + +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 defineJitInfo(self, infoName, opName, infallible): + protoID = "PrototypeList::id::%s as u32" % self.descriptor.name + depth = self.descriptor.interface.inheritanceDepth() + failstr = "true" if infallible else "false" + return ("\n" + "static %s: JSJitInfo = JSJitInfo {\n" + " op: %s as *const u8,\n" + " protoID: %s,\n" + " depth: %s,\n" + " isInfallible: %s, /* False in setters. */\n" + " isConstant: false /* Only relevant for getters. */\n" + "};\n" % (infoName, opName, protoID, depth, failstr)) + + def define(self): + if self.member.isAttr(): + getterinfo = ("%s_getterinfo" % self.member.identifier.name) + getter = ("get_%s" % self.member.identifier.name) + getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True) + result = self.defineJitInfo(getterinfo, getter, getterinfal) + if not self.member.readonly: + setterinfo = ("%s_setterinfo" % self.member.identifier.name) + setter = ("set_%s" % self.member.identifier.name) + # Setters are always fallible, since they have to do a typed unwrap. + result += self.defineJitInfo(setterinfo, setter, False) + return result + if self.member.isMethod(): + methodinfo = ("%s_methodinfo" % self.member.identifier.name) + # Actually a JSJitMethodOp, but JSJitPropertyOp by struct definition. + method = ("%s" % self.member.identifier.name) + + # Methods are infallible if they are infallible, have no arguments + # to unwrap, and have a return type that's infallible to wrap up for + # return. + methodInfal = False + 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. + sig = sigs[0] + if len(sig[1]) == 0: + # No arguments and infallible return boxing + methodInfal = True + + result = self.defineJitInfo(methodinfo, method, methodInfal) + return result + raise TypeError("Illegal member type to CGPropertyJITInfo") + +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): + raise SyntaxError('Enum value "' + value + '" starts with a digit') + 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) + inner = """ +use dom::bindings::conversions::ToJSValConvertible; +use js::jsapi::JSContext; +use js::jsval::JSVal; + +#[repr(uint)] +#[deriving(Encodable, PartialEq)] +pub enum valuelist { + %s +} + +pub static strings: &'static [&'static str] = &[ + %s, +]; + +impl ToJSValConvertible for valuelist { + fn to_jsval(&self, cx: *mut JSContext) -> JSVal { + strings[*self as uint].to_string().to_jsval(cx) + } +} +""" % (",\n ".join(map(getEnumValueName, enum.values())), + ",\n ".join(['"%s"' % val for val in enum.values()])) + + self.cgRoot = CGList([ + CGNamespace.build([enum.identifier.name + "Values"], + CGIndenter(CGGeneric(inner)), public=True), + CGGeneric("pub type %s = self::%sValues::valuelist;\n" % + (enum.identifier.name, enum.identifier.name)), + ]) + + def define(self): + return self.cgRoot.define() + + +def convertConstIDLValueToRust(value): + tag = value.type.tag() + if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, + IDLType.Tags.int16, IDLType.Tags.uint16, + IDLType.Tags.int32, IDLType.Tags.uint32, + IDLType.Tags.int64, IDLType.Tags.uint64, + IDLType.Tags.float, IDLType.Tags.double]: + return str(value.value) + + if tag == IDLType.Tags.bool: + return toStringBool(value.value) + + raise TypeError("Const value of unhandled type: " + value.type) + +class CGConstant(CGThing): + def __init__(self, constants): + CGThing.__init__(self) + self.constants = constants + + def define(self): + def stringDecl(const): + name = const.identifier.name + value = convertConstIDLValueToRust(const.value) + return CGGeneric("pub static %s: %s = %s;\n" % (name, builtinNames[const.value.type.tag()], value)) + + return CGIndenter(CGList(stringDecl(m) for m in self.constants)).define() + +def getUnionTypeTemplateVars(type, descriptorProvider): + # For dictionaries and sequences we need to pass None as the failureCode + # for getJSToNativeConversionTemplate. + # Also, for dictionaries we would need to handle conversion of + # null/undefined to the dictionary correctly. + if type.isDictionary() or type.isSequence(): + raise TypeError("Can't handle dictionaries or sequences in unions") + + if type.isGeckoInterface(): + name = type.inner.identifier.name + typeName = descriptorProvider.getDescriptor(name).nativeType + elif type.isEnum(): + name = type.inner.identifier.name + typeName = name + elif type.isArray() or type.isSequence(): + name = str(type) + #XXXjdm dunno about typeName here + typeName = "/*" + type.name + "*/" + elif type.isDOMString(): + name = type.name + typeName = "DOMString" + elif type.isPrimitive(): + name = type.name + typeName = builtinNames[type.tag()] + else: + name = type.name + typeName = "/*" + type.name + "*/" + + template, _, _, _ = getJSToNativeConversionTemplate( + type, descriptorProvider, failureCode="return Ok(None);", + exceptionCode='return Err(());', + isDefinitelyObject=True) + + assert not type.isObject() + jsConversion = string.Template(template).substitute({ + "val": "value", + }) + jsConversion = CGWrapper(CGGeneric(jsConversion), pre="Ok(Some(", post="))") + + return { + "name": name, + "typeName": typeName, + "jsConversion": jsConversion, + } + +class CGUnionStruct(CGThing): + def __init__(self, type, descriptorProvider): + assert not type.nullable() + assert not type.hasNullableType + + CGThing.__init__(self) + self.type = type + self.descriptorProvider = descriptorProvider + + def define(self): + templateVars = map(lambda t: getUnionTypeTemplateVars(t, self.descriptorProvider), + self.type.flatMemberTypes) + enumValues = [ + " e%s(%s)," % (v["name"], v["typeName"]) for v in templateVars + ] + enumConversions = [ + " e%s(ref inner) => inner.to_jsval(cx)," % v["name"] for v in templateVars + ] + return ("""pub enum %s { +%s +} + +impl ToJSValConvertible for %s { + fn to_jsval(&self, cx: *mut JSContext) -> JSVal { + match *self { +%s + } + } +} +""") % (self.type, "\n".join(enumValues), + self.type, "\n".join(enumConversions)) + + +class CGUnionConversionStruct(CGThing): + def __init__(self, type, descriptorProvider): + assert not type.nullable() + assert not type.hasNullableType + + CGThing.__init__(self) + self.type = type + self.descriptorProvider = descriptorProvider + + def from_jsval(self): + memberTypes = self.type.flatMemberTypes + names = [] + conversions = [] + + interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes) + if len(interfaceMemberTypes) > 0: + def get_name(memberType): + if self.type.isGeckoInterface(): + return memberType.inner.identifier.name + + return memberType.name + + def get_match(name): + return ( + "match %s::TryConvertTo%s(cx, value) {\n" + " Err(_) => return Err(()),\n" + " Ok(Some(value)) => return Ok(e%s(value)),\n" + " Ok(None) => (),\n" + "}\n") % (self.type, name, name) + + typeNames = [get_name(memberType) for memberType in interfaceMemberTypes] + interfaceObject = CGList(CGGeneric(get_match(typeName)) for typeName in typeNames) + names.extend(typeNames) + else: + interfaceObject = None + + arrayObjectMemberTypes = filter(lambda t: t.isArray() or t.isSequence(), memberTypes) + if len(arrayObjectMemberTypes) > 0: + assert len(arrayObjectMemberTypes) == 1 + raise TypeError("Can't handle arrays or sequences in unions.") + else: + arrayObject = None + + dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes) + if len(dateObjectMemberTypes) > 0: + assert len(dateObjectMemberTypes) == 1 + raise TypeError("Can't handle dates in unions.") + else: + dateObject = None + + callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes) + if len(callbackMemberTypes) > 0: + assert len(callbackMemberTypes) == 1 + raise TypeError("Can't handle callbacks in unions.") + else: + callbackObject = None + + dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes) + if len(dictionaryMemberTypes) > 0: + raise TypeError("No support for unwrapping dictionaries as member " + "of a union") + else: + dictionaryObject = None + + if callbackObject or dictionaryObject: + assert False, "Not currently supported" + else: + nonPlatformObject = None + + objectMemberTypes = filter(lambda t: t.isObject(), memberTypes) + if len(objectMemberTypes) > 0: + raise TypeError("Can't handle objects in unions.") + else: + object = None + + hasObjectTypes = interfaceObject or arrayObject or dateObject or nonPlatformObject or object + if hasObjectTypes: + assert interfaceObject + templateBody = CGList([interfaceObject], "\n") + conversions.append(CGIfWrapper(templateBody, "value.is_object()")) + + otherMemberTypes = [ + t for t in memberTypes if t.isPrimitive() or t.isString() or t.isEnum() + ] + if len(otherMemberTypes) > 0: + assert len(otherMemberTypes) == 1 + memberType = otherMemberTypes[0] + if memberType.isEnum(): + name = memberType.inner.identifier.name + else: + name = memberType.name + match = ( + "match %s::TryConvertTo%s(cx, value) {\n" + " Err(_) => return Err(()),\n" + " Ok(Some(value)) => return Ok(e%s(value)),\n" + " Ok(None) => (),\n" + "}\n") % (self.type, name, name) + conversions.append(CGGeneric(match)) + names.append(name) + + conversions.append(CGGeneric( + "throw_not_in_union(cx, \"%s\");\n" + "Err(())" % ", ".join(names))) + method = CGWrapper( + CGIndenter(CGList(conversions, "\n\n")), + pre="fn from_jsval(cx: *mut JSContext, value: JSVal, _option: ()) -> Result<%s, ()> {\n" % self.type, + post="\n}") + return CGWrapper( + CGIndenter(method), + pre="impl FromJSValConvertible<()> for %s {\n" % self.type, + post="\n}") + + def try_method(self, t): + templateVars = getUnionTypeTemplateVars(t, self.descriptorProvider) + returnType = "Result<Option<%s>, ()>" % templateVars["typeName"] + jsConversion = templateVars["jsConversion"] + + return CGWrapper( + CGIndenter(jsConversion, 4), + pre="fn TryConvertTo%s(cx: *mut JSContext, value: JSVal) -> %s {\n" % (t.name, returnType), + post="\n}") + + def define(self): + from_jsval = self.from_jsval() + methods = CGIndenter(CGList([ + self.try_method(t) for t in self.type.flatMemberTypes + ], "\n\n")) + return """ +%s + +impl %s { +%s +} +""" % (from_jsval.define(), self.type, methods.define()) + + +class ClassItem: + """ Use with CGClass """ + def __init__(self, name, visibility): + self.name = name + self.visibility = visibility + def declare(self, cgClass): + assert False + def define(self, cgClass): + assert False + +class ClassBase(ClassItem): + def __init__(self, name, visibility='pub'): + 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 + self.returnType = returnType + self.args = args + self.inline = False + self.static = static + self.virtual = virtual + self.const = const + self.bodyInHeader = True + 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 = '<%s>' % ', '.join(self.templateArgs) \ + if self.bodyInHeader and self.templateArgs else '' + args = ', '.join([a.declare() for a in self.args]) + if self.bodyInHeader: + body = CGIndenter(CGGeneric(self.getBody())).define() + body = ' {\n' + body + '\n}' + else: + body = ';' + + return string.Template("${decorators}%s" + "${visibility}fn ${name}${templateClause}(${args})${returnType}${const}${override}${body}%s" % + (self.breakAfterReturnDecl, self.breakAfterSelf) + ).substitute({ + 'templateClause': templateClause, + 'decorators': self.getDecorators(True), + 'returnType': (" -> %s" % self.returnType) if self.returnType else "", + 'name': self.name, + 'const': ' const' if self.const else '', + 'override': ' MOZ_OVERRIDE' if self.override else '', + 'args': args, + 'body': body, + 'visibility': self.visibility + ' ' if self.visibility is not 'priv' else '' + }) + + def define(self, cgClass): + pass + +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 string.Template("""using ${baseClass}::${name}; +""").substitute({ 'baseClass': self.baseClass, + 'name': 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="priv", explicit=False, baseConstructors=None, + body=""): + self.args = args + self.inline = False + 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, cgClass): + initializers = [" parent: %s" % str(self.baseConstructors[0])] + return (self.body + ( + "%s {\n" + "%s\n" + "}") % (cgClass.name, '\n'.join(initializers))) + + def declare(self, cgClass): + args = ', '.join([a.declare() for a in self.args]) + body = ' ' + self.getBody(cgClass); + body = stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + body = ' {\n' + body + '}' + + return string.Template("""pub fn ${decorators}new(${args}) -> ${className}${body} +""").substitute({ 'decorators': self.getDecorators(True), + 'className': cgClass.getNameString(), + 'args': args, + 'body': body }) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + args = ', '.join([a.define() for a in self.args]) + + body = ' ' + self.getBody() + body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + + return string.Template("""${decorators} +${className}::${className}(${args})${initializationList} +{${body}} +""").substitute({ 'decorators': self.getDecorators(False), + 'className': cgClass.getNameString(), + 'args': args, + 'initializationList': self.getInitializationList(cgClass), + 'body': body }) + +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 = ' ' + self.getBody(); + body = stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + body = '\n{\n' + body + '}' + else: + body = ';' + + return string.Template("""${decorators}~${className}()${body} +""").substitute({ 'decorators': self.getDecorators(True), + 'className': cgClass.getNameString(), + 'body': body }) + + def define(self, cgClass): + if self.bodyInHeader: + return '' + + body = ' ' + self.getBody() + body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n ')) + if len(body) > 0: + body += '\n' + + return string.Template("""${decorators} +${className}::~${className}() +{${body}} +""").substitute({ 'decorators': self.getDecorators(False), + 'className': cgClass.getNameString(), + 'body': body }) + +class ClassMember(ClassItem): + def __init__(self, name, type, visibility="priv", 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' % (self.visibility, self.name, self.type) + + 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, ',\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 + ";" for entry in entries] + ClassItem.__init__(self, name, visibility) + + def declare(self, cgClass): + return 'union %s\n{\n %s\n};\n' % (self.name, '\n '.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.decorators = decorators + self.extradeclarations = extradeclarations + self.extradefinitions = extradefinitions + + def getNameString(self): + className = self.name + if self.templateSpecialization: + className = className + \ + '<%s>' % ', '.join([str(a) for a + in self.templateSpecialization]) + return className + + def define(self): + result = '' + if self.templateArgs: + templateArgs = [a.declare() for a in self.templateArgs] + templateArgs = templateArgs[len(self.templateSpecialization):] + result = result + self.indent + 'template <%s>\n' \ + % ','.join([str(a) for a in templateArgs]) + + if self.templateSpecialization: + specialization = \ + '<%s>' % ', '.join([str(a) for a in self.templateSpecialization]) + else: + specialization = '' + + myself = '' + if self.decorators != '': + myself += self.decorators + '\n' + myself += '%spub struct %s%s' % (self.indent, self.name, specialization) + result += myself + + assert len(self.bases) == 1 #XXjdm Can we support multiple inheritance? + + result += '{\n%s\n' % self.indent + + if self.bases: + self.members = [ClassMember("parent", self.bases[0].name, "pub")] + self.members + + result += CGIndenter(CGGeneric(self.extradeclarations), + len(self.indent)).define() + + def declareMembers(cgClass, memberList): + result = '' + + for member in memberList: + declaration = member.declare(cgClass) + declaration = CGIndenter(CGGeneric(declaration)).define() + result = result + declaration + return result + + 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, '')] + + for (memberList, separator) in order: + memberString = declareMembers(self, memberList) + if self.indent: + memberString = CGIndenter(CGGeneric(memberString), + len(self.indent)).define() + result = result + memberString + + result += self.indent + '}\n\n' + result += 'impl %s {\n' % self.name + + order = [(self.constructors + disallowedCopyConstructors, '\n'), + (self.destructors, '\n'), (self.methods, '\n)')] + for (memberList, separator) in order: + memberString = declareMembers(self, memberList) + if self.indent: + memberString = CGIndenter(CGGeneric(memberString), + len(self.indent)).define() + result = result + memberString + + result += "}" + return result + +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). + """ + def __init__(self, descriptor, operation): + nativeName = MakeNativeName(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] + template, _, declType, needsRooting = getJSToNativeConversionTemplate( + argument.type, descriptor, treatNullAs=argument.treatNullAs) + templateValues = { + "val": "(*desc).value", + } + self.cgRoot.prepend(instantiateJSToNativeConversionTemplate( + template, templateValues, declType, argument.identifier.name, + needsRooting)) + elif operation.isGetter(): + self.cgRoot.prepend(CGGeneric("let mut found = false;")) + + def getArguments(self): + def process(arg): + argVal = arg.identifier.name + if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback(): + argVal += ".root_ref()" + return argVal + args = [(a, process(a)) for a in self.arguments] + if self.idlNode.isGetter(): + args.append((FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean], + self.idlNode), + "&mut found")) + return args + + def wrap_return_value(self): + if not self.idlNode.isGetter() or self.templateValues is None: + return "" + + wrap = CGGeneric(wrapForType(**self.templateValues)) + wrap = CGIfWrapper(wrap, "found") + return "\n" + wrap.define() + +class CGProxyIndexedGetter(CGProxySpecialOperation): + """ + Class to generate a call to an indexed getter. If templateValues is not None + the returned value will be wrapped with wrapForType using templateValues. + """ + def __init__(self, descriptor, templateValues=None): + self.templateValues = templateValues + CGProxySpecialOperation.__init__(self, descriptor, 'IndexedGetter') + +class CGProxyIndexedSetter(CGProxySpecialOperation): + """ + Class to generate a call to an indexed setter. + """ + def __init__(self, descriptor): + CGProxySpecialOperation.__init__(self, descriptor, 'IndexedSetter') + +class CGProxyNamedGetter(CGProxySpecialOperation): + """ + Class to generate a call to an named getter. If templateValues is not None + the returned value will be wrapped with wrapForType using templateValues. + """ + def __init__(self, descriptor, templateValues=None): + self.templateValues = templateValues + CGProxySpecialOperation.__init__(self, descriptor, 'NamedGetter') + +class CGProxyNamedSetter(CGProxySpecialOperation): + """ + Class to generate a call to a named setter. + """ + def __init__(self, descriptor): + CGProxySpecialOperation.__init__(self, descriptor, 'NamedSetter') + +class CGProxyUnwrap(CGAbstractMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSObject', 'obj')] + CGAbstractMethod.__init__(self, descriptor, "UnwrapProxy", '*const ' + descriptor.concreteType, args, alwaysInline=True) + + def definition_body(self): + return CGGeneric("""/*if (xpc::WrapperFactory::IsXrayWrapper(obj)) { + obj = js::UnwrapObject(obj); +}*/ +//MOZ_ASSERT(IsProxy(obj)); +let box_ = GetProxyPrivate(obj).to_private() as *const %s; +return box_;""" % self.descriptor.concreteType) + +class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'), + Argument('jsid', 'id'), Argument('bool', 'set'), + Argument('*mut JSPropertyDescriptor', 'desc')] + CGAbstractExternMethod.__init__(self, descriptor, "getOwnPropertyDescriptor", + "bool", args) + self.descriptor = descriptor + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + indexedSetter = self.descriptor.operations['IndexedSetter'] + + setOrIndexedGet = "" + if indexedGetter or indexedSetter: + setOrIndexedGet += "let index = GetArrayIndexFromId(cx, id);\n" + + if indexedGetter: + readonly = toStringBool(self.descriptor.operations['IndexedSetter'] is None) + fillDescriptor = "FillPropertyDescriptor(&mut *desc, proxy, %s);\nreturn true;" % readonly + templateValues = {'jsvalRef': '(*desc).value', 'successCode': fillDescriptor} + get = ("if index.is_some() {\n" + + " let index = index.unwrap();\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" + + "}\n") + + if indexedSetter or self.descriptor.operations['NamedSetter']: + setOrIndexedGet += "if set != 0 {\n" + if indexedSetter: + setOrIndexedGet += (" if index.is_some() {\n" + + " let index = index.unwrap();\n") + if not 'IndexedCreator' in self.descriptor.operations: + # FIXME need to check that this is a 'supported property index' + assert False + setOrIndexedGet += (" FillPropertyDescriptor(&mut *desc, proxy, false);\n" + + " return true;\n" + + " }\n") + if self.descriptor.operations['NamedSetter']: + setOrIndexedGet += " if RUST_JSID_IS_STRING(id) {\n" + if not 'NamedCreator' in self.descriptor.operations: + # FIXME need to check that this is a 'supported property name' + assert False + setOrIndexedGet += (" FillPropertyDescriptor(&mut *desc, proxy, false);\n" + + " return true;\n" + + " }\n") + setOrIndexedGet += "}" + if indexedGetter: + setOrIndexedGet += (" else {\n" + + CGIndenter(CGGeneric(get)).define() + + "}") + setOrIndexedGet += "\n\n" + elif indexedGetter: + setOrIndexedGet += ("if !set {\n" + + CGIndenter(CGGeneric(get)).define() + + "}\n\n") + + namedGetter = self.descriptor.operations['NamedGetter'] + if namedGetter: + readonly = toStringBool(self.descriptor.operations['NamedSetter'] is None) + fillDescriptor = "FillPropertyDescriptor(&mut *desc, proxy, %s);\nreturn true;" % readonly + templateValues = {'jsvalRef': '(*desc).value', 'successCode': fillDescriptor} + # Once we start supporting OverrideBuiltins we need to make + # ResolveOwnProperty or EnumerateOwnProperties filter out named + # properties that shadow prototype properties. + namedGet = ("\n" + + "if !set && RUST_JSID_IS_STRING(id) != 0 && !HasPropertyOnPrototype(cx, proxy, id) {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + "\n" + + "}\n") + else: + namedGet = "" + + return setOrIndexedGet + """let expando: *mut JSObject = GetExpandoObject(proxy); +//if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) { +if expando.is_not_null() { + let flags = if set { JSRESOLVE_ASSIGNING } else { 0 } | JSRESOLVE_QUALIFIED; + if JS_GetPropertyDescriptorById(cx, expando, id, flags, desc) == 0 { + return false; + } + if (*desc).obj.is_not_null() { + // Pretend the property lives on the wrapper. + (*desc).obj = proxy; + return true; + } +} +""" + namedGet + """ +(*desc).obj = ptr::mut_null(); +return true;""" + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'), + Argument('jsid', 'id'), + Argument('*const JSPropertyDescriptor', 'desc')] + CGAbstractExternMethod.__init__(self, descriptor, "defineProperty", "bool", args) + self.descriptor = descriptor + def getBody(self): + set = "" + + indexedSetter = self.descriptor.operations['IndexedSetter'] + if indexedSetter: + if not (self.descriptor.operations['IndexedCreator'] is indexedSetter): + raise TypeError("Can't handle creator that's different from the setter") + set += ("let index = GetArrayIndexFromId(cx, id);\n" + + "if index.is_some() {\n" + + " let index = index.unwrap();\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() + + " return true;\n" + + "}\n") + elif self.descriptor.operations['IndexedGetter']: + set += ("if GetArrayIndexFromId(cx, id).is_some() {\n" + + " return false;\n" + + " //return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" + + "}\n") % self.descriptor.name + + namedSetter = self.descriptor.operations['NamedSetter'] + if namedSetter: + if not self.descriptor.operations['NamedCreator'] is namedSetter: + raise TypeError("Can't handle creator that's different from the setter") + set += ("if RUST_JSID_IS_STRING(id) != 0 {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedSetter(self.descriptor)).define() + "\n" + + "}\n") + elif self.descriptor.operations['NamedGetter']: + set += ("if RUST_JSID_IS_STRING(id) {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + + " if (found) {\n" + " return false;\n" + + " //return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" + + " }\n" + + " return true;\n" + "}\n") % (self.descriptor.name) + return set + """return proxyhandler::defineProperty_(%s);""" % ", ".join(a.name for a in self.args) + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'), + Argument('jsid', 'id'), Argument('*mut bool', 'bp')] + CGAbstractExternMethod.__init__(self, descriptor, "hasOwn", "bool", args) + self.descriptor = descriptor + def getBody(self): + indexedGetter = self.descriptor.operations['IndexedGetter'] + if indexedGetter: + indexed = ("let index = GetArrayIndexFromId(cx, id);\n" + + "if index.is_some() {\n" + + " let index = index.unwrap();\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor)).define() + "\n" + + " *bp = found;\n" + + " return true;\n" + + "}\n\n") + else: + indexed = "" + + namedGetter = self.descriptor.operations['NamedGetter'] + if namedGetter: + named = ("if RUST_JSID_IS_STRING(id) != 0 && !HasPropertyOnPrototype(cx, proxy, id) {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + "\n" + + " *bp = found;\n" + " return true;\n" + "}\n" + + "\n") + else: + named = "" + + return indexed + """let expando: *mut JSObject = GetExpandoObject(proxy); +if expando.is_not_null() { + let mut b: JSBool = 1; + let ok = JS_HasPropertyById(cx, expando, id, &mut b) != 0; + *bp = b != 0; + if !ok || *bp { + return ok; + } +} + +""" + named + """*bp = false; +return true;""" + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGDOMJSProxyHandler_get(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'), + Argument('*mut JSObject', 'receiver'), Argument('jsid', 'id'), + Argument('*mut JSVal', 'vp')] + CGAbstractExternMethod.__init__(self, descriptor, "get", "bool", args) + self.descriptor = descriptor + def getBody(self): + getFromExpando = """let expando = GetExpandoObject(proxy); +if expando.is_not_null() { + let mut hasProp = 0; + if JS_HasPropertyById(cx, expando, id, &mut hasProp) == 0 { + return false; + } + + if hasProp != 0 { + return JS_GetPropertyById(cx, expando, id, vp) != 0; + } +}""" + + templateValues = { + 'jsvalRef': '*vp', + 'successCode': 'return true;', + } + + indexedGetter = self.descriptor.operations['IndexedGetter'] + if indexedGetter: + getIndexedOrExpando = ("let index = GetArrayIndexFromId(cx, id);\n" + + "if index.is_some() {\n" + + " let index = index.unwrap();\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()) + getIndexedOrExpando += """ + // Even if we don't have this index, we don't forward the + // get on to our expando object. +} else { + %s +} +""" % (stripTrailingWhitespace(getFromExpando.replace('\n', '\n '))) + else: + getIndexedOrExpando = getFromExpando + "\n" + + namedGetter = self.descriptor.operations['NamedGetter'] + if namedGetter and False: #XXXjdm unfinished + getNamed = ("if (JSID_IS_STRING(id)) {\n" + + " let name = jsid_to_str(cx, id);\n" + + " let this = UnwrapProxy(proxy);\n" + + " let this = JS::from_raw(this);\n" + + " let this = this.root();\n" + + CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + + "}\n") % (self.descriptor.concreteType) + else: + getNamed = "" + + return """//MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy), + //"Should not have a XrayWrapper here"); + +%s +let mut found = false; +if !GetPropertyOnPrototype(cx, proxy, id, &mut found, vp) { + return false; +} + +if found { + return true; +} +%s +*vp = UndefinedValue(); +return true;""" % (getIndexedOrExpando, getNamed) + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGDOMJSProxyHandler_obj_toString(CGAbstractExternMethod): + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy')] + CGAbstractExternMethod.__init__(self, descriptor, "obj_toString", "*mut JSString", args) + self.descriptor = descriptor + def getBody(self): + stringifier = self.descriptor.operations['Stringifier'] + if stringifier: + nativeName = MakeNativeName(stringifier.identifier.name) + signature = stringifier.signatures()[0] + returnType = signature[0] + extendedAttributes = self.descriptor.getExtendedAttributes(stringifier) + infallible = 'infallible' in extendedAttributes + if not infallible: + error = CGGeneric( + ('ThrowMethodFailedWithDetails(cx, rv, "%s", "toString");\n' + + "return NULL;") % self.descriptor.interface.identifier.name) + else: + error = None + call = CGCallGenerator(error, [], "", returnType, extendedAttributes, self.descriptor, nativeName, False, object="UnwrapProxy(proxy)") + return call.define() + """ + +JSString* jsresult; +return xpc_qsStringToJsstring(cx, result, &jsresult) ? jsresult : NULL;""" + + return """let s = "%s".to_c_str(); + _obj_toString(cx, s.as_ptr())""" % self.descriptor.name + + def definition_body(self): + return CGGeneric(self.getBody()) + +class CGAbstractClassHook(CGAbstractExternMethod): + """ + 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): + CGAbstractExternMethod.__init__(self, descriptor, name, returnType, + args) + + def definition_body_prologue(self): + return CGGeneric("""\ +let this: *const %s = unwrap::<%s>(obj); +""" % (self.descriptor.concreteType, self.descriptor.concreteType)) + + def definition_body(self): + return CGList([ + self.definition_body_prologue(), + self.generate_code(), + ]) + + def generate_code(self): + # Override me + assert(False) + +def finalizeHook(descriptor, hookName, context): + release = """let val = JS_GetReservedSlot(obj, dom_object_slot(obj)); +let _: Box<%s> = mem::transmute(val.to_private()); +debug!("%s finalize: {:p}", this); +""" % (descriptor.concreteType, descriptor.concreteType) + return release + +class CGClassTraceHook(CGAbstractClassHook): + """ + A hook to trace through our native object; used for GC and CC + """ + def __init__(self, descriptor): + args = [Argument('*mut JSTracer', 'trc'), Argument('*mut JSObject', 'obj')] + CGAbstractClassHook.__init__(self, descriptor, TRACE_HOOK_NAME, 'void', + args) + + def generate_code(self): + return CGGeneric("(*this).trace(%s);" % self.args[0].name) + +class CGClassConstructHook(CGAbstractExternMethod): + """ + JS-visible constructor for our objects + """ + def __init__(self, descriptor): + args = [Argument('*mut JSContext', 'cx'), Argument('u32', 'argc'), Argument('*mut JSVal', 'vp')] + CGAbstractExternMethod.__init__(self, descriptor, CONSTRUCT_HOOK_NAME, + 'JSBool', args) + self._ctor = self.descriptor.interface.ctor() + + def define(self): + if not self._ctor: + return "" + return CGAbstractExternMethod.define(self) + + def definition_body(self): + preamble = CGGeneric("""\ +let global = global_object_for_js_object(JS_CALLEE(cx, vp).to_object()); +let global = global.root(); +""") + nativeName = MakeNativeName(self._ctor.identifier.name) + callGenerator = CGMethodCall(["&global.root_ref()"], nativeName, True, + self.descriptor, self._ctor) + return CGList([preamble, callGenerator]) + +class CGClassFinalizeHook(CGAbstractClassHook): + """ + A hook for finalize, used to release our native object. + """ + def __init__(self, descriptor): + args = [Argument('*mut JSFreeOp', 'fop'), Argument('*mut JSObject', 'obj')] + CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME, + 'void', args) + + def generate_code(self): + return CGGeneric(finalizeHook(self.descriptor, self.name, self.args[0].name)) + +class CGDOMJSProxyHandlerDOMClass(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + self.descriptor = descriptor + + def define(self): + return """ +static Class: DOMClass = """ + DOMClass(self.descriptor) + """; + +""" + + +class CGInterfaceTrait(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + + def argument_type(ty, optional=False, defaultValue=None, variadic=False): + _, _, declType, _ = getJSToNativeConversionTemplate( + ty, descriptor, isArgument=True) + + if variadic: + declType = CGWrapper(declType, pre="Vec<", post=">") + elif optional and not defaultValue: + declType = CGWrapper(declType, pre="Option<", post=">") + + if ty.isGeckoInterface() and not (ty.nullable() or optional): + declType = CGWrapper(declType, pre="&") + elif ty.isDictionary(): + declType = CGWrapper(declType, pre="&") + + return declType.define() + + def attribute_arguments(needCx, argument=None): + if needCx: + yield "cx", "*mut JSContext" + + if argument: + yield "value", argument_type(argument) + + def method_arguments(returnType, arguments, trailing=None): + if needCx(returnType, arguments, True): + yield "cx", "*mut JSContext" + + for argument in arguments: + ty = argument_type(argument.type, argument.optional, + argument.defaultValue, argument.variadic) + yield CGDictionary.makeMemberName(argument.identifier.name), ty + + if trailing: + yield trailing + + def return_type(rettype, infallible): + result = getRetvalDeclarationForType(rettype, descriptor) + if not infallible: + result = CGWrapper(result, pre="Fallible<", post=">") + return result.define() + + def members(): + for m in descriptor.interface.members: + if m.isMethod() and not m.isStatic(): + name = CGSpecializedMethod.makeNativeName(descriptor, m) + infallible = 'infallible' in descriptor.getExtendedAttributes(m) + for idx, (rettype, arguments) in enumerate(m.signatures()): + arguments = method_arguments(rettype, arguments) + rettype = return_type(rettype, infallible) + yield name + ('_' * idx), arguments, rettype + elif m.isAttr() and not m.isStatic(): + name = CGSpecializedGetter.makeNativeName(descriptor, m) + infallible = 'infallible' in descriptor.getExtendedAttributes(m, getter=True) + needCx = typeNeedsCx(m.type) + yield name, attribute_arguments(needCx), return_type(m.type, infallible) + + if not m.readonly: + name = CGSpecializedSetter.makeNativeName(descriptor, m) + infallible = 'infallible' in descriptor.getExtendedAttributes(m, setter=True) + if infallible: + rettype = "()" + else: + rettype = "ErrorResult" + yield name, attribute_arguments(needCx, m.type), rettype + + if descriptor.proxy: + for name, operation in descriptor.operations.iteritems(): + if not operation: + continue + + assert len(operation.signatures()) == 1 + rettype, arguments = operation.signatures()[0] + + infallible = 'infallible' in descriptor.getExtendedAttributes(operation) + arguments = method_arguments(rettype, arguments, ("found", "&mut bool")) + rettype = return_type(rettype, infallible) + yield name, arguments, rettype + + def fmt(arguments): + return "".join(", %s: %s" % argument for argument in arguments) + + methods = CGList([ + CGGeneric("fn %s(&self%s) -> %s;\n" % (name, fmt(arguments), rettype)) + for name, arguments, rettype in members() + ], "") + self.cgRoot = CGWrapper(CGIndenter(methods), + pre="pub trait %sMethods {\n" % descriptor.interface.identifier.name, + post="}") + + def define(self): + return self.cgRoot.define() + + +class CGDescriptor(CGThing): + def __init__(self, descriptor): + CGThing.__init__(self) + + assert not descriptor.interface.isCallback() + + cgThings = [] + cgThings.append(CGGetProtoObjectMethod(descriptor)) + if descriptor.interface.hasInterfaceObject(): + # https://github.com/mozilla/servo/issues/2665 + # cgThings.append(CGGetConstructorObjectMethod(descriptor)) + pass + + (hasMethod, hasGetter, hasLenientGetter, + hasSetter, hasLenientSetter) = False, False, False, False, False + for m in descriptor.interface.members: + if m.isMethod() and not m.isIdentifierLess(): + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject() + cgThings.append(CGStaticMethod(descriptor, m)) + else: + cgThings.append(CGSpecializedMethod(descriptor, m)) + cgThings.append(CGMemberJITInfo(descriptor, m)) + hasMethod = True + elif m.isAttr(): + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject() + cgThings.append(CGStaticGetter(descriptor, m)) + else: + cgThings.append(CGSpecializedGetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientGetter = True + else: + hasGetter = True + + if not m.readonly: + if m.isStatic(): + assert descriptor.interface.hasInterfaceObject() + cgThings.append(CGStaticSetter(descriptor, m)) + else: + cgThings.append(CGSpecializedSetter(descriptor, m)) + if m.hasLenientThis(): + hasLenientSetter = True + else: + hasSetter = True + + if not m.isStatic(): + cgThings.append(CGMemberJITInfo(descriptor, m)) + if hasMethod: + cgThings.append(CGGenericMethod(descriptor)) + if hasGetter: + cgThings.append(CGGenericGetter(descriptor)) + if hasLenientGetter: + pass + if hasSetter: + cgThings.append(CGGenericSetter(descriptor)) + if hasLenientSetter: + pass + + if descriptor.concrete: + cgThings.append(CGClassFinalizeHook(descriptor)) + cgThings.append(CGClassTraceHook(descriptor)) + + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGClassConstructHook(descriptor)) + cgThings.append(CGInterfaceObjectJSClass(descriptor)) + + cgThings.append(CGPrototypeJSClass(descriptor)) + + properties = PropertyArrays(descriptor) + cgThings.append(CGGeneric(str(properties))) + cgThings.append(CGNativeProperties(descriptor, properties)) + cgThings.append(CGNativePropertyHooks(descriptor, properties)) + cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties)) + + cgThings.append(CGNamespace.build([descriptor.name + "Constants"], + CGConstant(m for m in descriptor.interface.members if m.isConst()), + public=True)) + + if descriptor.interface.hasInterfaceObject(): + cgThings.append(CGDefineDOMInterfaceMethod(descriptor)) + + if descriptor.proxy: + cgThings.append(CGDefineProxyHandler(descriptor)) + + if descriptor.concrete: + if descriptor.proxy: + #cgThings.append(CGProxyIsProxy(descriptor)) + cgThings.append(CGProxyUnwrap(descriptor)) + cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor)) + cgThings.append(CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor)) + cgThings.append(CGDOMJSProxyHandler_obj_toString(descriptor)) + cgThings.append(CGDOMJSProxyHandler_get(descriptor)) + cgThings.append(CGDOMJSProxyHandler_hasOwn(descriptor)) + if descriptor.operations['IndexedSetter'] or descriptor.operations['NamedSetter']: + cgThings.append(CGDOMJSProxyHandler_defineProperty(descriptor)) + + #cgThings.append(CGDOMJSProxyHandler(descriptor)) + #cgThings.append(CGIsMethod(descriptor)) + pass + else: + cgThings.append(CGDOMJSClass(descriptor)) + pass + + cgThings.append(CGWrapMethod(descriptor)) + + cgThings.append(CGIDLInterface(descriptor)) + cgThings.append(CGInterfaceTrait(descriptor)) + + cgThings = CGList(cgThings, "\n") + cgThings = CGWrapper(cgThings, pre='\n', post='\n') + #self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), + # cgThings), + # post='\n') + self.cgRoot = cgThings + + def define(self): + return self.cgRoot.define() + +class CGNamespacedEnum(CGThing): + def __init__(self, namespace, enumName, names, values, comment="", deriving=""): + + 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 = ' + str(len(entries))) + + # Indent. + entries = [' ' + e for e in entries] + + # Build the enum body. + enumstr = comment + 'pub enum %s {\n%s\n}\n' % (enumName, ',\n'.join(entries)) + if deriving: + enumstr = ('#[deriving(%s)]\n' % deriving) + enumstr + curr = CGGeneric(enumstr) + + # Add some whitespace padding. + curr = CGWrapper(curr, pre='\n',post='\n') + + # Add the namespace. + curr = CGNamespace(namespace, curr, public=True) + + # Add the typedef + #typedef = '\ntypedef %s::%s %s;\n\n' % (namespace, enumName, enumName) + #curr = CGList([curr, CGGeneric(typedef)]) + + # Save the result. + self.node = curr + + def define(self): + return self.node.define() + +class CGDictionary(CGThing): + def __init__(self, dictionary, descriptorProvider): + self.dictionary = dictionary; + if all(CGDictionary(d, descriptorProvider).generatable for + d in CGDictionary.getDictionaryDependencies(dictionary)): + self.generatable = True + else: + self.generatable = False + # Nothing else to do here + return + self.memberInfo = [ + (member, + getJSToNativeConversionTemplate(member.type, + descriptorProvider, + isMember="Dictionary", + defaultValue=member.defaultValue, + failureCode="return Err(());", + exceptionCode="return Err(());")) + for member in dictionary.members ] + + def define(self): + if not self.generatable: + return "" + return self.struct() + "\n" + self.impl() + + def struct(self): + d = self.dictionary + if d.parent: + inheritance = " pub parent: %s::%s<'a, 'b>,\n" % (self.makeModuleName(d.parent), + self.makeClassName(d.parent)) + else: + inheritance = "" + memberDecls = [" pub %s: %s," % + (self.makeMemberName(m[0].identifier.name), self.getMemberType(m)) + for m in self.memberInfo] + + return (string.Template( + "pub struct ${selfName}<'a, 'b> {\n" + + "${inheritance}" + + "\n".join(memberDecls) + "\n" + + "}").substitute( { "selfName": self.makeClassName(d), + "inheritance": inheritance })) + + def impl(self): + d = self.dictionary + if d.parent: + initParent = ("parent: match %s::%s::new(cx, val) {\n" + " Ok(parent) => parent,\n" + " Err(_) => return Err(()),\n" + "},\n") % (self.makeModuleName(d.parent), + self.makeClassName(d.parent)) + else: + initParent = "" + + def memberInit(memberInfo): + member, _ = memberInfo + name = self.makeMemberName(member.identifier.name) + conversion = self.getMemberConversion(memberInfo) + return CGGeneric("%s: %s,\n" % (name, conversion.define())) + + memberInits = CGList([memberInit(m) for m in self.memberInfo]) + + return string.Template( + "impl<'a, 'b> ${selfName}<'a, 'b> {\n" + " pub fn empty() -> ${selfName}<'a, 'b> {\n" + " ${selfName}::new(ptr::mut_null(), NullValue()).unwrap()\n" + " }\n" + " pub fn new(cx: *mut JSContext, val: JSVal) -> Result<${selfName}<'a, 'b>, ()> {\n" + " let object = if val.is_null_or_undefined() {\n" + " ptr::mut_null()\n" + " } else if val.is_object() {\n" + " val.to_object()\n" + " } else {\n" + " throw_type_error(cx, \"Value not an object.\");\n" + " return Err(());\n" + " };\n" + " Ok(${selfName} {\n" + "${initParent}" + "${initMembers}" + " })\n" + " }\n" + "}").substitute({ + "selfName": self.makeClassName(d), + "initParent": CGIndenter(CGGeneric(initParent), indentLevel=6).define(), + "initMembers": CGIndenter(memberInits, indentLevel=6).define(), + }) + + @staticmethod + def makeDictionaryName(dictionary): + return dictionary.identifier.name + + def makeClassName(self, dictionary): + return self.makeDictionaryName(dictionary) + + @staticmethod + def makeModuleName(dictionary): + name = dictionary.identifier.name + if name.endswith('Init'): + return toBindingNamespace(name.replace('Init', '')) + #XXXjdm This breaks on the test webidl files, sigh. + #raise TypeError("No idea how to find this dictionary's definition: " + name) + return "/* uh oh */ %s" % name + + def getMemberType(self, memberInfo): + member, (_, _, declType, _) = memberInfo + if not member.defaultValue: + declType = CGWrapper(declType, pre="Option<", post=">") + return declType.define() + + def getMemberConversion(self, memberInfo): + def indent(s): + return CGIndenter(CGGeneric(s), 8).define() + + member, (templateBody, default, declType, _) = memberInfo + replacements = { "val": "value" } + conversion = string.Template(templateBody).substitute(replacements) + + assert (member.defaultValue is None) == (default is None) + if not default: + default = "None" + conversion = "Some(%s)" % conversion + + conversion = ( + "match get_dictionary_property(cx, object, \"%s\") {\n" + " Err(()) => return Err(()),\n" + " Ok(Some(value)) => {\n" + "%s\n" + " },\n" + " Ok(None) => {\n" + "%s\n" + " },\n" + "}") % (member.identifier.name, indent(conversion), indent(default)) + + return CGGeneric(conversion) + + @staticmethod + def makeIdName(name): + return name + "_id" + + @staticmethod + def makeMemberName(name): + # Can't use Rust keywords as member names. + if name == "type": + return name + "_" + return name + + @staticmethod + def getDictionaryDependencies(dictionary): + deps = set(); + if dictionary.parent: + deps.add(dictionary.parent) + for member in dictionary.members: + if member.type.isDictionary(): + deps.add(member.type.unroll().inner) + return deps + +class CGRegisterProtos(CGAbstractMethod): + def __init__(self, config): + arguments = [ + Argument('*mut JSContext', 'cx'), + Argument('*mut JSObject', 'global'), + ] + CGAbstractMethod.__init__(self, None, 'Register', 'void', arguments, + unsafe=False, pub=True) + self.config = config + + def definition_body(self): + return CGList([ + CGGeneric("codegen::Bindings::%sBinding::DefineDOMInterface(cx, global);" % desc.name) + for desc in self.config.getDescriptors(hasInterfaceObject=True, register=True) + ], "\n") + + +class CGRegisterProxyHandlersMethod(CGAbstractMethod): + def __init__(self, descriptors): + CGAbstractMethod.__init__(self, None, 'RegisterProxyHandlers', 'void', [], + unsafe=True, pub=True) + self.descriptors = descriptors + + def definition_body(self): + return CGList([ + CGGeneric("proxy_handlers[proxies::%s as uint] = codegen::Bindings::%sBinding::DefineProxyHandler();" % (desc.name, desc.name)) + for desc in self.descriptors + ], "\n") + + +class CGRegisterProxyHandlers(CGThing): + def __init__(self, config): + descriptors = config.getDescriptors(proxy=True) + length = len(descriptors) + self.root = CGList([ + CGGeneric("pub static mut proxy_handlers: [*const libc::c_void, ..%d] = [0 as *const libc::c_void, ..%d];" % (length, length)), + CGRegisterProxyHandlersMethod(descriptors), + ], "\n") + + def define(self): + return self.root.define() + + +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): + descriptors = config.getDescriptors(webIDLFile=webIDLFile, + isCallback=False) + dictionaries = config.getDictionaries(webIDLFile=webIDLFile) + + cgthings = [] + + mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile) + callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile, + isCallback=True) + + # Do codegen for all the enums + cgthings = [CGEnum(e) for e in config.getEnums(webIDLFile)] + + cgthings.extend([CGDictionary(d, config.getDescriptorProvider()) + for d in dictionaries]) + + # Do codegen for all the callbacks. + cgthings.extend(CGList([CGCallbackFunction(c, config.getDescriptorProvider()), + CGCallbackFunctionImpl(c)], "\n") + for c in mainCallbacks) + + # Do codegen for all the descriptors + cgthings.extend([CGDescriptor(x) for x in descriptors]) + + # Do codegen for all the callback interfaces. + cgthings.extend(CGList([CGCallbackInterface(x), + CGCallbackFunctionImpl(x)], "\n") + for x in callbackDescriptors) + + # 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(['dom'], + # CGWrapper(curr, pre="\n")) + + # Add imports + #XXXjdm This should only import the namespace for the current binding, + # not every binding ever. + curr = CGImports(curr, descriptors, [ + 'js', + 'js::{JS_ARGV, JS_CALLEE, JS_THIS_OBJECT}', + 'js::{JSCLASS_GLOBAL_SLOT_COUNT, JSCLASS_IS_DOMJSCLASS}', + 'js::{JSCLASS_IS_GLOBAL, JSCLASS_RESERVED_SLOTS_SHIFT}', + 'js::{JSCLASS_RESERVED_SLOTS_MASK, JSID_VOID, JSJitInfo}', + 'js::{JSPROP_ENUMERATE, JSPROP_NATIVE_ACCESSORS, JSPROP_SHARED}', + 'js::{JSRESOLVE_ASSIGNING, JSRESOLVE_QUALIFIED}', + 'js::jsapi::{JS_CallFunctionValue, JS_GetClass, JS_GetGlobalForObject}', + 'js::jsapi::{JS_GetObjectPrototype, JS_GetProperty, JS_GetPropertyById}', + 'js::jsapi::{JS_GetPropertyDescriptorById, JS_GetReservedSlot}', + 'js::jsapi::{JS_HasProperty, JS_HasPropertyById, JS_IsExceptionPending}', + 'js::jsapi::{JS_NewObject, JS_ObjectIsCallable, JS_SetPrototype}', + 'js::jsapi::{JS_SetReservedSlot, JS_WrapValue, JSBool, JSContext}', + 'js::jsapi::{JSClass, JSFreeOp, JSFunctionSpec, JSHandleObject, jsid}', + 'js::jsapi::{JSNativeWrapper, JSObject, JSPropertyDescriptor, JS_ArrayIterator}', + 'js::jsapi::{JSPropertyOpWrapper, JSPropertySpec, JS_PropertyStub}', + 'js::jsapi::{JSStrictPropertyOpWrapper, JSString, JSTracer, JS_ConvertStub}', + 'js::jsapi::{JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub}', + 'js::jsval::JSVal', + 'js::jsval::{ObjectValue, ObjectOrNullValue, PrivateValue}', + 'js::jsval::{NullValue, UndefinedValue}', + 'js::glue::{CallJitMethodOp, CallJitPropertyOp, CreateProxyHandler}', + 'js::glue::{GetProxyPrivate, NewProxyObject, ProxyTraps}', + 'js::glue::{RUST_FUNCTION_VALUE_TO_JITINFO}', + 'js::glue::{RUST_JS_NumberValue, RUST_JSID_IS_STRING}', + 'js::rust::with_compartment', + 'dom::types::*', + 'dom::bindings', + 'dom::bindings::global::GlobalRef', + 'dom::bindings::js::{JS, JSRef, Root, RootedReference, Temporary}', + 'dom::bindings::js::{OptionalRootable, OptionalRootedRootable, ResultRootable}', + 'dom::bindings::js::{OptionalRootedReference, OptionalOptionalRootedRootable}', + 'dom::bindings::utils::{CreateDOMGlobal, CreateInterfaceObjects2}', + 'dom::bindings::utils::{ConstantSpec, cx_for_dom_object}', + 'dom::bindings::utils::{dom_object_slot, DOM_OBJECT_SLOT, DOMClass}', + 'dom::bindings::utils::{DOMJSClass, JSCLASS_DOM_GLOBAL}', + 'dom::bindings::utils::{FindEnumStringIndex, GetArrayIndexFromId}', + 'dom::bindings::utils::{GetPropertyOnPrototype, GetProtoOrIfaceArray}', + 'dom::bindings::utils::{HasPropertyOnPrototype, IntVal}', + 'dom::bindings::utils::{jsid_to_str}', + 'dom::bindings::utils::global_object_for_js_object', + 'dom::bindings::utils::{Reflectable}', + 'dom::bindings::utils::{squirrel_away_unique}', + 'dom::bindings::utils::{ThrowingConstructor, unwrap, unwrap_jsmanaged}', + 'dom::bindings::utils::VoidVal', + 'dom::bindings::utils::get_dictionary_property', + 'dom::bindings::utils::{NativeProperties, NativePropertyHooks}', + 'dom::bindings::trace::JSTraceable', + 'dom::bindings::callback::{CallbackContainer,CallbackInterface,CallbackFunction}', + 'dom::bindings::callback::{CallSetup,ExceptionHandling}', + 'dom::bindings::callback::{WrapCallThisObject}', + 'dom::bindings::conversions::{FromJSValConvertible, ToJSValConvertible}', + 'dom::bindings::conversions::IDLInterface', + 'dom::bindings::conversions::{Default, Empty}', + 'dom::bindings::codegen::*', + 'dom::bindings::codegen::Bindings::*', + 'dom::bindings::codegen::RegisterBindings', + 'dom::bindings::codegen::UnionTypes::*', + 'dom::bindings::error::{FailureUnknown, Fallible, Error, ErrorResult}', + 'dom::bindings::error::throw_dom_exception', + 'dom::bindings::error::throw_type_error', + 'dom::bindings::proxyhandler', + 'dom::bindings::proxyhandler::{_obj_toString, defineProperty}', + 'dom::bindings::proxyhandler::{FillPropertyDescriptor, GetExpandoObject}', + 'dom::bindings::proxyhandler::{delete_, getPropertyDescriptor}', + 'dom::bindings::str::ByteString', + 'page::JSPageInfo', + 'libc', + 'servo_util::str::DOMString', + 'std::mem', + 'std::cmp', + 'std::ptr', + 'std::str', + 'std::num', + ]) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Store the final result. + self.root = curr + + def define(self): + return stripTrailingWhitespace(self.root.define()) + +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. + """ + self.descriptorProvider = descriptorProvider + self.member = member + self.extendedAttrs = 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 template for actually returning a value stored in + "${declName}". 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(): + typeDecl, template = "", "" + elif type.isPrimitive() and type.tag() in builtinNames: + result = CGGeneric(builtinNames[type.tag()]) + if type.nullable(): + raise TypeError("Nullable primitives are not supported here.") + + typeDecl, template = result.define(), "return Ok(${declName});" + elif type.isDOMString(): + if isMember: + # No need for a third element in the isMember case + typeDecl, template = "nsString", None + # Outparam + else: + typeDecl, template = "void", "retval = ${declName};" + elif type.isByteString(): + if isMember: + # No need for a third element in the isMember case + typeDecl, template = "nsCString", None + # Outparam + typeDecl, template = "void", "retval = ${declName};" + elif type.isEnum(): + enumName = type.unroll().inner.identifier.name + if type.nullable(): + enumName = CGTemplatedType("Nullable", + CGGeneric(enumName)).define() + typeDecl, template = enumName, "return ${declName};" + elif type.isGeckoInterface(): + iface = type.unroll().inner; + nativeType = self.descriptorProvider.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) + result = CGWrapper(CGGeneric("::".join(nativeType)), 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. + typeDecl, template = result.define(), "return ${declName}.forget();" + elif type.isCallback(): + typeDecl, template = \ + ("already_AddRefed<%s>" % type.unroll().identifier.name, + "return ${declName}.forget();") + elif type.isAny(): + typeDecl, template = "JSVal", "return Ok(${declName});" + elif type.isObject(): + typeDecl, template = "JSObject*", "return ${declName};" + elif type.isSpiderMonkeyInterface(): + if type.nullable(): + returnCode = "return ${declName}.IsNull() ? nullptr : ${declName}.Value().Obj();" + else: + returnCode = "return ${declName}.Obj();" + typeDecl, template = "JSObject*", returnCode + elif type.isSequence(): + # If we want to handle sequence-of-sequences return values, we're + # going to need to fix example codegen to not produce nsTArray<void> + # for the relevant argument... + assert not isMember + # Outparam. + if type.nullable(): + returnCode = ("if (${declName}.IsNull()) {\n" + " retval.SetNull();\n" + "} else {\n" + " retval.SetValue().SwapElements(${declName}.Value());\n" + "}") + else: + returnCode = "retval.SwapElements(${declName});" + typeDecl, template = "void", returnCode + elif type.isDate(): + result = CGGeneric("Date") + if type.nullable(): + result = CGTemplatedType("Nullable", result) + typeDecl, template = result.define(), "return ${declName};" + else: + raise TypeError("Don't know how to declare return value for %s" % type) + + if not 'infallible' in self.extendedAttrs: + if typeDecl: + typeDecl = "Fallible<%s>" % typeDecl + else: + typeDecl = "ErrorResult" + if not template: + template = "return Ok(());" + return typeDecl, template + + def getArgs(self, returnType, argList): + args = [self.getArg(arg) for arg in argList] + # Now the outparams + if returnType.isDOMString(): + args.append(Argument("nsString&", "retval")) + if returnType.isByteString(): + args.append(Argument("nsCString&", "retval")) + 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(), "retval")) + # 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.passJSBitsAsNeeded): + args.insert(0, Argument("JSContext*", "cx")) + # 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" or "Variadic" + """ + 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.isUnion(): + if type.nullable(): + type = type.inner + return str(type) + "::" + str(type), False, True + + if type.isGeckoInterface() and not type.isCallbackInterface(): + iface = type.unroll().inner + argIsPointer = type.nullable() + 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" + descriptor = self.descriptorProvider.getDescriptor(iface.identifier.name) + return (typeDecl % descriptor.argumentType, + False, False) + + if type.isSpiderMonkeyInterface(): + if self.jsObjectsArePtr: + return "JSObject*", False, False + + return type.name, True, True + + if type.isDOMString(): + declType = "DOMString" + 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 = "JSVal" + return declType, False, False + + if type.isObject(): + if isMember: + declType = "JSObject*" + else: + declType = "JS::Handle<JSObject*>" + return declType, False, False + + if type.isDictionary(): + typeName = CGDictionary.makeDictionaryName(type.inner) + return typeName, True, True + + if type.isDate(): + return "Date", False, True + + assert type.isPrimitive() + + return builtinNames[type.tag()], False, True + + def getArgType(self, type, optional, isMember): + """ + Get the type of an argument declaration. Returns the type CGThing, and + whether this should be a const ref. + + isMember can be False, "Sequence", or "Variadic" + """ + (decl, ref, handleNullable) = self.doGetArgType(type, optional, + isMember) + decl = CGGeneric(decl) + if handleNullable and type.nullable(): + decl = CGTemplatedType("Nullable", decl) + ref = True + if isMember == "Variadic": + arrayType = "Sequence" if self.variadicIsSequence else "nsTArray" + decl = CGTemplatedType(arrayType, decl) + ref = True + elif optional: + # Note: All variadic args claim to be optional, but we can just use + # empty arrays to represent them not being present. + decl = CGTemplatedType("Option", decl) + ref = False + 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="&") + + return Argument(decl.define(), arg.identifier.name) + +class CGCallback(CGClass): + def __init__(self, idlObject, descriptorProvider, baseName, methods, + getters=[], setters=[]): + self.baseName = baseName + self._deps = idlObject.getDeps() + name = idlObject.identifier.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)) + CGClass.__init__(self, name, + bases=[ClassBase(baseName)], + constructors=self.getConstructors(), + methods=realMethods+getters+setters, + decorators="#[deriving(PartialEq,Clone,Encodable)]") + + def getConstructors(self): + return [ClassConstructor( + [Argument("*mut JSObject", "aCallback")], + bodyInHeader=True, + visibility="pub", + explicit=False, + baseConstructors=[ + "%s::new(aCallback)" % self.baseName + ])] + + 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 == "*mut JSContext" + assert args[1].name == "aThisObj" and args[1].argType == "*mut JSObject" + 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()", "thisObjJS"] + argnames + argnamesWithoutThis = ["s.GetContext()", "ptr::mut_null()"] + 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", + "ReportExceptions")) + + args[0] = Argument('&' + args[0].argType, args[0].name, args[0].default) + method.args[2] = args[0] + + # And now insert our template argument. + argsWithoutThis = list(args) + args.insert(0, Argument("&JSRef<T>", "thisObj")) + + # And the self argument + method.args.insert(0, Argument(None, "&self")) + args.insert(0, Argument(None, "&self")) + argsWithoutThis.insert(0, Argument(None, "&self")) + + setupCall = ("let s = CallSetup::new(self, aExceptionHandling);\n" + "if s.GetContext().is_null() {\n" + " return Err(FailureUnknown);\n" + "}\n") + + bodyWithThis = string.Template( + setupCall+ + "let thisObjJS = WrapCallThisObject(s.GetContext(), thisObj);\n" + "if thisObjJS.is_null() {\n" + " return Err(FailureUnknown);\n" + "}\n" + "return ${methodName}(${callArgs});").substitute({ + "callArgs" : ", ".join(argnamesWithThis), + "methodName": 'self.' + method.name, + }) + bodyWithoutThis = string.Template( + setupCall + + "return ${methodName}(${callArgs});").substitute({ + "callArgs" : ", ".join(argnamesWithoutThis), + "methodName": 'self.' + method.name, + }) + return [ClassMethod(method.name+'_', method.returnType, args, + bodyInHeader=True, + templateArgs=["T: Reflectable"], + body=bodyWithThis, + visibility='pub'), + ClassMethod(method.name+'__', method.returnType, argsWithoutThis, + bodyInHeader=True, + body=bodyWithoutThis, + visibility='pub'), + method] + + def deps(self): + return self._deps + +# We're always fallible +def callbackGetterName(attr): + return "Get" + MakeNativeName(attr.identifier.name) + +def callbackSetterName(attr): + return "Set" + MakeNativeName(attr.identifier.name) + +class CGCallbackFunction(CGCallback): + def __init__(self, callback, descriptorProvider): + CGCallback.__init__(self, callback, descriptorProvider, + "CallbackFunction", + methods=[CallCallback(callback, descriptorProvider)]) + + def getConstructors(self): + return CGCallback.getConstructors(self) + +class CGCallbackFunctionImpl(CGGeneric): + def __init__(self, callback): + impl = string.Template("""impl CallbackContainer for ${type} { + fn new(callback: *mut JSObject) -> ${type} { + ${type}::new(callback) + } + + fn callback(&self) -> *mut JSObject { + self.parent.callback() + } +} + +impl ToJSValConvertible for ${type} { + fn to_jsval(&self, cx: *mut JSContext) -> JSVal { + self.callback().to_jsval(cx) + } +} +""").substitute({"type": callback.name}) + CGGeneric.__init__(self, impl) + +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()] + assert not iface.isJSImplemented() or not iface.ctor() + 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): + 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 = "priv" if needThisHandling else "pub" + 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= "return Err(FailureUnknown);\n" + self.body = self.getImpl() + + def getImpl(self): + replacements = { + "declRval": self.getRvalDecl(), + "returnResult": self.getResultConversion(), + "convertArgs": self.getArgConversions(), + "doCall": self.getCall(), + "setupCall": self.getCallSetup(), + } + if self.argCount > 0: + replacements["argCount"] = self.argCountStr + replacements["argvDecl"] = string.Template( + "let mut argv = Vec::from_elem(${argCount}, UndefinedValue());\n" + ).substitute(replacements) + else: + # Avoid weird 0-sized arrays + replacements["argvDecl"] = "" + + # Newlines and semicolons are in the values + pre = string.Template( + "${setupCall}" + "${declRval}" + "${argvDecl}").substitute(replacements) + body = string.Template( + "${convertArgs}" + "${doCall}" + "${returnResult}").substitute(replacements) + return CGList([ + CGGeneric(pre), + CGWrapper(CGIndenter(CGGeneric(body)), + pre="with_compartment(cx, self.parent.callback(), || {\n", + post="})") + ], "\n").define() + + def getResultConversion(self): + replacements = { + "val": "rval", + "declName": "rvalDecl", + } + + template, _, declType, needsRooting = getJSToNativeConversionTemplate( + self.retvalType, + self.descriptorProvider, + exceptionCode=self.exceptionCode, + isCallbackReturnValue="Callback", + # XXXbz we should try to do better here + sourceDescription="return value") + + convertType = instantiateJSToNativeConversionTemplate( + template, replacements, declType, "rvalDecl", needsRooting) + + assignRetval = string.Template( + self.getRetvalInfo(self.retvalType, + False)[1]).substitute(replacements) + return convertType.define() + "\n" + assignRetval + "\n" + + 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])] + # 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="loop {\n", + post="\nbreak;}\n") + for c in argConversions] + if self.argCount > 0: + argConversions.insert(0, self.getArgcDecl()) + # And slap them together. + return CGList(argConversions, "\n\n").define() + "\n\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 += ".clone().unwrap()" + + conversion = wrapForType("*argv.get_mut(%s)" % jsvalIndex, + result=argval, + successCode="continue;" if arg.variadic else "break;") + if arg.variadic: + conversion = string.Template( + "for (uint32_t idx = 0; idx < ${arg}.Length(); ++idx) {\n" + + CGIndenter(CGGeneric(conversion)).define() + "\n" + "}\n" + "break;").substitute({ "arg": arg.identifier.name }) + elif arg.optional and not arg.defaultValue: + conversion = ( + CGIfWrapper(CGGeneric(conversion), + "%s.is_some()" % arg.identifier.name).define() + + " else if (argc == %d) {\n" + " // This is our current trailing argument; reduce argc\n" + " argc -= 1;\n" + "} else {\n" + " *argv.get_mut(%d) = UndefinedValue();\n" + "}" % (i+1, i)) + return conversion + + 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", + "ReportExceptions")) + return args + # We want to allow the caller to pass in a "this" object, as + # well as a JSContext. + return [Argument("*mut JSContext", "cx"), + Argument("*mut JSObject", "aThisObj")] + args + + def getCallSetup(self): + if self.needThisHandling: + # It's been done for us already + return "" + callSetup = "CallSetup s(CallbackPreserveColor(), aRv" + if self.rethrowContentException: + # getArgs doesn't add the aExceptionHandling argument but does add + # aCompartment for us. + callSetup += ", RethrowContentExceptions, aCompartment" + else: + callSetup += ", aExceptionHandling" + callSetup += ");" + return string.Template( + "${callSetup}\n" + "JSContext* cx = s.GetContext();\n" + "if (!cx) {\n" + " return Err(FailureUnknown);\n" + "}\n").substitute({ + "callSetup": callSetup, + }) + + def getArgcDecl(self): + return CGGeneric("let mut argc = %su32;" % 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 "let mut rval = UndefinedValue();\n" + + def getCall(self): + replacements = { + "thisObj": self.getThisObj(), + "getCallable": self.getCallableDecl() + } + if self.argCount > 0: + replacements["argv"] = "argv.as_mut_ptr()" + replacements["argc"] = "argc" + else: + replacements["argv"] = "nullptr" + replacements["argc"] = "0" + return string.Template("${getCallable}" + "let ok = unsafe {\n" + " JS_CallFunctionValue(cx, ${thisObj}, callable,\n" + " ${argc}, ${argv}, &mut rval)\n" + "};\n" + "if ok == 0 {\n" + " return Err(FailureUnknown);\n" + "}\n").substitute(replacements) + +class CallCallback(CallbackMethod): + def __init__(self, callback, descriptorProvider): + CallbackMethod.__init__(self, callback.signatures()[0], "Call", + descriptorProvider, needThisHandling=True) + + def getThisObj(self): + return "aThisObj" + + def getCallableDecl(self): + return "let callable = ObjectValue(unsafe {&*self.parent.callback()});\n"; + +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 = jsName + CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation, rethrowContentException) + + def getThisObj(self): + if not self.singleOperation: + return "self.parent.callback()" + # This relies on getCallableDecl declaring a boolean + # isCallable in the case when we're a single-operation + # interface. + return "if isCallable { aThisObj } else { self.parent.callback() }" + + def getCallableDecl(self): + replacements = { + "methodName": self.methodName + } + getCallableFromProp = string.Template( + 'match self.parent.GetCallableProperty(cx, "${methodName}") {\n' + ' Err(_) => return Err(FailureUnknown),\n' + ' Ok(callable) => callable,\n' + '}').substitute(replacements) + if not self.singleOperation: + return 'JS::Rooted<JS::Value> callable(cx);\n' + getCallableFromProp + return ( + 'let isCallable = unsafe { JS_ObjectIsCallable(cx, self.parent.callback()) != 0 };\n' + 'let callable =\n' + + CGIndenter( + CGIfElseWrapper('isCallable', + CGGeneric('unsafe { ObjectValue(&*self.parent.callback()) }'), + CGGeneric(getCallableFromProp))).define() + ';\n') + +class CallbackOperation(CallbackOperationBase): + """ + Codegen actual WebIDL operations on callback interfaces. + """ + def __init__(self, method, signature, descriptor): + self.ensureASCIIName(method) + jsName = method.identifier.name + CallbackOperationBase.__init__(self, signature, + jsName, MakeNativeName(jsName), + descriptor, descriptor.interface.isSingleOperationInterface(), + rethrowContentException=descriptor.interface.isJSImplemented()) + +class CallbackGetter(CallbackMember): + def __init__(self, attr, descriptor): + self.ensureASCIIName(attr) + self.attrName = attr.identifier.name + CallbackMember.__init__(self, + (attr.type, []), + callbackGetterName(attr), + descriptor, + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented()) + + def getRvalDecl(self): + return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n" + + def getCall(self): + replacements = { + "attrName": self.attrName + } + return string.Template( + 'if (!JS_GetProperty(cx, mCallback, "${attrName}", &rval)) {\n' + ' return Err(FailureUnknown);\n' + '}\n').substitute(replacements); + +class CallbackSetter(CallbackMember): + def __init__(self, attr, descriptor): + self.ensureASCIIName(attr) + self.attrName = attr.identifier.name + CallbackMember.__init__(self, + (BuiltinTypes[IDLBuiltinType.Types.void], + [FakeArgument(attr.type, attr)]), + callbackSetterName(attr), + descriptor, + needThisHandling=False, + rethrowContentException=descriptor.interface.isJSImplemented()) + + def getRvalDecl(self): + # We don't need an rval + return "" + + def getCall(self): + replacements = { + "attrName": self.attrName, + "argv": "argv.handleAt(0)", + } + return string.Template( + 'MOZ_ASSERT(argv.length() == 1);\n' + 'if (!JS_SetProperty(cx, mCallback, "${attrName}", ${argv})) {\n' + ' return Err(FailureUnknown);\n' + '}\n').substitute(replacements) + + def getArgcDecl(self): + return None + +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 PrototypeList(config): + # Prototype ID enum. + protos = [d.name for d in config.getDescriptors(isCallback=False)] + proxies = [d.name for d in config.getDescriptors(proxy=True)] + + return CGList([ + CGGeneric(AUTOGENERATED_WARNING_COMMENT), + CGGeneric("pub static MAX_PROTO_CHAIN_LENGTH: uint = %d;\n\n" % config.maxProtoChainLength), + CGNamespacedEnum('id', 'ID', protos, [0], deriving="PartialEq"), + CGNamespacedEnum('proxies', 'Proxy', proxies, [0], deriving="PartialEq"), + ]) + + + @staticmethod + def RegisterBindings(config): + # TODO - Generate the methods we want + code = CGList([ + CGRegisterProtos(config), + CGRegisterProxyHandlers(config), + ], "\n") + + return CGImports(code, [], [ + 'dom::bindings::codegen', + 'dom::bindings::codegen::PrototypeList::proxies', + 'js::jsapi::JSContext', + 'js::jsapi::JSObject', + 'libc', + ]) + + @staticmethod + def InterfaceTypes(config): + descriptors = [d.name for d in config.getDescriptors(register=True, isCallback=False)] + curr = CGList([CGGeneric("pub use dom::%s::%s;\n" % (name.lower(), name)) for name in descriptors]) + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + return curr + + @staticmethod + def Bindings(config): + + descriptors = (set(d.name + "Binding" for d in config.getDescriptors(register=True)) | + set(d.unroll().module() for d in config.callbacks)) + curr = CGList([CGGeneric("pub mod %s;\n" % name) for name in sorted(descriptors)]) + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + return curr + + @staticmethod + def InheritTypes(config): + + descriptors = config.getDescriptors(register=True, isCallback=False) + allprotos = [CGGeneric("#![allow(unused_imports)]\n"), + CGGeneric("use dom::types::*;\n"), + CGGeneric("use dom::bindings::js::{JS, JSRef, Temporary};\n"), + CGGeneric("use dom::bindings::trace::JSTraceable;\n"), + CGGeneric("use dom::bindings::utils::Reflectable;\n"), + CGGeneric("use serialize::{Encodable, Encoder};\n"), + CGGeneric("use js::jsapi::JSTracer;\n\n")] + for descriptor in descriptors: + name = descriptor.name + protos = [CGGeneric('pub trait %s {}\n' % (name + 'Base'))] + for proto in descriptor.prototypeChain: + protos += [CGGeneric('impl %s for %s {}\n' % (proto + 'Base', + descriptor.concreteType))] + derived = [CGGeneric('pub trait %s { fn %s(&self) -> bool; }\n' % + (name + 'Derived', 'is_' + name.lower()))] + for protoName in descriptor.prototypeChain[1:-1]: + protoDescriptor = config.getDescriptor(protoName) + delegate = string.Template('''impl ${selfName} for ${baseName} { + fn ${fname}(&self) -> bool { + self.${parentName}.${fname}() + } +} +''').substitute({'fname': 'is_' + name.lower(), + 'selfName': name + 'Derived', + 'baseName': protoDescriptor.concreteType, + 'parentName': protoDescriptor.prototypeChain[-2].lower()}) + derived += [CGGeneric(delegate)] + derived += [CGGeneric('\n')] + + cast = [CGGeneric(string.Template('''pub trait ${castTraitName} { + #[inline(always)] + fn to_ref<'a, 'b, T: ${toBound}+Reflectable>(base: &'a JSRef<'b, T>) -> Option<&'a JSRef<'b, Self>> { + match base.deref().${checkFn}() { + true => unsafe { Some(base.transmute()) }, + false => None + } + } + + #[inline(always)] + fn to_mut_ref<'a, 'b, T: ${toBound}+Reflectable>(base: &'a mut JSRef<'b, T>) -> Option<&'a mut JSRef<'b, Self>> { + match base.deref().${checkFn}() { + true => unsafe { Some(base.transmute_mut()) }, + false => None + } + } + + #[inline(always)] + fn from_ref<'a, 'b, T: ${fromBound}>(derived: &'a JSRef<'b, T>) -> &'a JSRef<'b, Self> { + unsafe { derived.transmute() } + } + + #[inline(always)] + fn from_mut_ref<'a, 'b, T: ${fromBound}>(derived: &'a mut JSRef<'b, T>) -> &'a mut JSRef<'b, Self> { + unsafe { derived.transmute_mut() } + } + + #[inline(always)] + fn from_temporary<T: ${fromBound}+Reflectable>(derived: Temporary<T>) -> Temporary<Self> { + unsafe { derived.transmute() } + } +} +''').substitute({'checkFn': 'is_' + name.lower(), + 'castTraitName': name + 'Cast', + 'fromBound': name + 'Base', + 'toBound': name + 'Derived'})), + CGGeneric("impl %s for %s {}\n\n" % (name + 'Cast', name))] + + trace = [CGGeneric(string.Template('''impl JSTraceable for ${name} { + fn trace(&self, tracer: *mut JSTracer) { + unsafe { + self.encode(&mut *tracer).ok().expect("failed to encode"); + } + } +} +''').substitute({'name': name}))] + + allprotos += protos + derived + cast + trace + + curr = CGList(allprotos) + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + return curr + + @staticmethod + def UnionTypes(config): + + curr = UnionTypes(config.getDescriptors(), + config.getDictionaries(), + config.getCallbacks(), + config) + + # Add the auto-generated comment. + curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) + + # Done. + return curr |