from __future__ import annotations
import functools
import operator
import os
import re
import string
import textwrap
from abc import abstractmethod
from collections import defaultdict
from collections.abc import Callable, Generator, Iterable, Iterator
from enum import IntEnum
from itertools import groupby
from re import Match
from typing import Any, Generic, Optional, TypeGuard, TypeVar, cast
from configuration import (
Configuration,
Descriptor,
DescriptorProvider,
MakeNativeName,
MemberIsLegacyUnforgeable,
assert_type,
getModuleFromObject,
getTypesFromCallback,
getTypesFromDescriptor,
getTypesFromDictionary,
iteratorNativeType,
)
from WebIDL import (
BuiltinTypes,
IDLArgument,
IDLAttribute,
IDLBuiltinType,
IDLCallback,
IDLCallbackType,
IDLConst,
IDLConstructor,
IDLDefaultDictionaryValue,
IDLDictionary,
IDLEmptySequenceValue,
IDLEnum,
IDLInterface,
IDLInterfaceMember,
IDLInterfaceOrNamespace,
IDLMaplikeOrSetlikeOrIterableBase,
IDLMethod,
IDLNullableType,
IDLNullValue,
IDLObject,
IDLObjectWithIdentifier,
IDLPromiseType,
IDLRecordType,
IDLSequenceType,
IDLType,
IDLTypedef,
IDLTypedefType,
IDLUndefinedValue,
IDLUnionType,
IDLUnresolvedIdentifier,
IDLValue,
IDLWrapperType,
)
AUTOGENERATED_WARNING_COMMENT = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
_proto_ranges: dict[str, tuple[int, int]] = {}
ALLOWED_WARNING_LIST = [
'non_camel_case_types',
'non_upper_case_globals',
'unsafe_op_in_unsafe_fn',
'unused_imports',
'unused_variables',
'unused_assignments',
'unused_mut',
'clippy::approx_constant',
'clippy::enum_variant_names',
'clippy::let_unit_value',
'clippy::needless_return',
'clippy::too_many_arguments',
'clippy::unnecessary_cast',
'clippy::upper_case_acronyms'
]
ALLOWED_WARNINGS = f"#![allow({','.join(ALLOWED_WARNING_LIST)})]\n\n"
FINALIZE_HOOK_NAME = '_finalize'
TRACE_HOOK_NAME = '_trace'
CONSTRUCT_HOOK_NAME = '_constructor'
HASINSTANCE_HOOK_NAME = '_hasInstance'
RUST_KEYWORDS = {
"abstract",
"alignof",
"as",
"async",
"await",
"become",
"box",
"break",
"const",
"continue",
"crate",
"do",
"dyn",
"else",
"enum",
"extern",
"false",
"final",
"fn",
"for",
"gen",
"if",
"impl",
"in",
"let",
"loop",
"macro",
"match",
"mod",
"move",
"mut",
"offsetof",
"override",
"priv",
"proc",
"pub",
"pure",
"ref",
"return",
"static",
"self",
"sizeof",
"struct",
"super",
"true",
"trait",
"try",
"type",
"typeof",
"unsafe",
"unsized",
"use",
"virtual",
"where",
"while",
"yield",
}
EVENT_HANDLER_CALLBACKS = {
"EventHandlerNonNull",
"OnErrorEventHandlerNonNull",
"OnBeforeUnloadEventHandlerNonNull",
}
def isIDLType(obj: IDLObject) -> TypeGuard[IDLType]:
if obj.isType():
assert isinstance(obj, IDLType)
return True
return False
def genericsForType(t: IDLObject) -> tuple[str, str]:
if containsDomInterface(t):
return ("<D: DomTypes>", "<D>")
return ("", "")
def isDomInterface(t: IDLObject, logging: bool = False) -> bool:
while isinstance(t, IDLNullableType) or isinstance(t, IDLWrapperType):
t = t.inner
if isinstance(t, IDLInterface):
return True
assert isinstance(t, IDLType)
if t.isCallback() or t.isPromise():
return True
return t.isInterface() and (t.isSpiderMonkeyInterface() and not t.isBufferSource())
def containsDomInterface(t: IDLObject, logging: bool = False) -> bool:
if isinstance(t, IDLArgument):
t = t.type
if isinstance(t, IDLTypedefType):
t = t.inner
while isinstance(t, IDLNullableType) or isinstance(t, IDLWrapperType):
t = t.inner
if t.isEnum():
return False
if t.isUnion():
return any(map(lambda x: containsDomInterface(x), t.flatMemberTypes))
if t.isDictionary():
return any(map(lambda x: containsDomInterface(x), t.members)) or (t.parent and containsDomInterface(t.parent))
if isDomInterface(t):
return True
assert isinstance(t, IDLType)
if t.isSequence():
assert isinstance(t, IDLSequenceType)
return containsDomInterface(t.inner)
if t.isRecord():
assert isinstance(t, IDLRecordType)
return containsDomInterface(t.inner)
return False
def toStringBool(arg: bool) -> str:
return str(not not arg).lower()
def toBindingNamespace(arg: str) -> str:
return re.sub("((_workers)?$)", "_Binding\\1", MakeNativeName(arg))
def toBindingModuleFile(arg: str) -> str:
return re.sub("((_workers)?$)", "Binding\\1", MakeNativeName(arg))
def toBindingModuleFileFromDescriptor(desc: Descriptor) -> str:
isSuperModule = desc.maybeGetSuperModule()
if isSuperModule is not None:
return toBindingModuleFile(isSuperModule)
else:
return toBindingModuleFile(desc.name)
def stripTrailingWhitespace(text: str) -> str:
tail = '\n' if text.endswith('\n') else ''
lines = text.splitlines()
for i in range(len(lines)):
lines[i] = lines[i].rstrip()
joined_lines = '\n'.join(lines)
return f"{joined_lines}{tail}"
def innerContainerType(type: IDLType) -> IDLType:
assert type.isSequence() or type.isRecord()
assert isinstance(type, (IDLSequenceType, IDLRecordType, IDLNullableType))
return type.inner.inner if type.nullable() else type.inner
def wrapInNativeContainerType(type: IDLType, inner: CGThing) -> CGThing:
if type.isSequence():
return CGWrapper(inner, pre="Vec<", post=">")
elif type.isRecord():
if type.nullable():
assert isinstance(type, IDLNullableType)
key = type.inner.keyType
else:
assert isinstance(type, IDLRecordType)
key = type.keyType
return CGRecord(key, inner)
else:
raise TypeError(f"Unexpected container type {type}")
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.unrestricted_float: 'f32',
IDLType.Tags.float: 'Finite<f32>',
IDLType.Tags.unrestricted_double: 'f64',
IDLType.Tags.double: 'Finite<f64>',
IDLType.Tags.int8array: 'Int8Array',
IDLType.Tags.uint8array: 'Uint8Array',
IDLType.Tags.int16array: 'Int16Array',
IDLType.Tags.uint16array: 'Uint16Array',
IDLType.Tags.int32array: 'Int32Array',
IDLType.Tags.uint32array: 'Uint32Array',
IDLType.Tags.float32array: 'Float32Array',
IDLType.Tags.float64array: 'Float64Array',
IDLType.Tags.arrayBuffer: 'ArrayBuffer',
IDLType.Tags.arrayBufferView: 'ArrayBufferView',
IDLType.Tags.uint8clampedarray: 'Uint8ClampedArray',
}
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.unrestricted_float,
IDLType.Tags.unrestricted_double
]
lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE)
def indent(s: str, indentLevel: int = 2) -> str:
if s == "":
return s
return re.sub(lineStartDetector, indentLevel * " ", s)
@functools.cache
def dedent(s: str) -> str:
if s.startswith('\n'):
s = s[1:]
return textwrap.dedent(s)
fill_multiline_substitution_re = re.compile(r"( *)\$\*{(\w+)}(\n)?")
@functools.cache
def compile_fill_template(template: str) -> tuple[string.Template, list[tuple[str, str, int]]]:
t = dedent(template)
assert t.endswith("\n") or "\n" not in t
argModList = []
def replace(match: Match[str]) -> str:
indentation, name, nl = match.groups()
depth = len(indentation)
prev = match.string[:match.start()]
if (prev and not prev.endswith("\n")) or nl is None:
raise ValueError(f"Invalid fill() template: $*{name} must appear by itself on a line")
modified_name = f"{name}_{depth}"
argModList.append((name, modified_name, depth))
return f"${{{modified_name}}}"
t = re.sub(fill_multiline_substitution_re, replace, t)
return (string.Template(t), argModList)
def fill(template: str, **args: str) -> str:
t, argModList = compile_fill_template(template)
for (name, modified_name, depth) in argModList:
if not (args[name] == "" or args[name].endswith("\n")):
raise ValueError(f"Argument {name} with value {args[name]} is missing a newline")
args[modified_name] = indent(args[name], depth)
return t.substitute(args)
class CGThing():
def __init__(self) -> None:
pass
@abstractmethod
def define(self) -> str:
raise NotImplementedError
class CGMethodCall(CGThing):
cgRoot: CGThing
def __init__(self, argsPre: list[str], nativeMethodName: str, static: bool, descriptor: Descriptor, method: IDLMethod) -> None:
CGThing.__init__(self)
methodName = f'\\"{descriptor.interface.identifier.name}.{method.identifier.name}\\"'
def requiredArgCount(signature: tuple[IDLType, list[IDLArgument]]) -> int:
arguments = signature[1]
if len(arguments) == 0:
return 0
requiredArgs = len(arguments)
while requiredArgs and arguments[requiredArgs - 1].optional:
requiredArgs -= 1
return requiredArgs
signatures = method.signatures()
def getPerSignatureCall(signature: tuple[IDLType, list[IDLArgument | FakeArgument]], argConversionStartsAt: int = 0) -> CGThing:
signatureIndex = signatures.index(signature)
return CGPerSignatureCall(signature[0], argsPre, signature[1],
f"{nativeMethodName}{'_' * signatureIndex}",
static, descriptor,
method, argConversionStartsAt)
if len(signatures) == 1:
signature = signatures[0]
self.cgRoot = CGList([getPerSignatureCall(signature)])
requiredArgs = requiredArgCount(signature)
if requiredArgs > 0:
code = (
f"if argc < {requiredArgs} {{\n"
f" throw_type_error(cx.raw_cx(), c\"Not enough arguments to {methodName}.\");\n"
" return false;\n"
"}")
self.cgRoot.prepend(
CGWrapper(CGGeneric(code), pre="\n", post="\n"))
return
maxArgCount = method.maxArgCount
allowedArgCounts = method.allowedArgCounts
argCountCases = []
for argCount in allowedArgCounts:
possibleSignatures = method.signaturesForArgCount(argCount)
if len(possibleSignatures) == 1:
signature = possibleSignatures[0]
argCountCases.append(CGCase(str(argCount), getPerSignatureCall(signature)))
continue
distinguishingIndex = method.distinguishingIndexForArgCount(argCount)
for (returnType, args) in possibleSignatures:
type = args[distinguishingIndex].type
if type.isUnion():
if type.nullable():
type = type.inner
for type in type.flatMemberTypes:
if not (type.isObject() or
type.isNonCallbackInterface() or
type.isString() or
type.isNumeric() or
type.isBoolean()):
raise TypeError("No support for unions with variants of type "
"other than object, string, number and boolean "
f"as distinguishing arguments yet: {args[distinguishingIndex].location}",
)
caseBody: list[CGThing] = [
CGArgumentConverter(possibleSignatures[0][1][i],
i, "args", "argc", descriptor)
for i in range(0, distinguishingIndex)]
distinguishingArg = f"HandleValue::from_raw(args.get({distinguishingIndex}))"
def pickFirstSignature(condition: str | None, filterLambda: Callable[[Any], bool]) -> bool:
sigs = list(filter(filterLambda, possibleSignatures))
assert len(sigs) < 2
if len(sigs) > 0:
call = getPerSignatureCall(sigs[0], distinguishingIndex)
if condition is None:
caseBody.append(call)
else:
caseBody.append(CGGeneric(f"if {condition} {{"))
caseBody.append(CGIndenter(call))
caseBody.append(CGGeneric("}"))
return True
return False
pickFirstSignature(f"{distinguishingArg}.get().is_null_or_undefined()",
lambda s: (s[1][distinguishingIndex].type.nullable()
or s[1][distinguishingIndex].type.isDictionary()))
interfacesSigs = [
s for s in possibleSignatures
if (s[1][distinguishingIndex].type.isObject()
or s[1][distinguishingIndex].type.isUnion()
or s[1][distinguishingIndex].type.isNonCallbackInterface())]
if len(interfacesSigs) > 0:
for idx, sig in enumerate(interfacesSigs):
caseBody.append(CGGeneric("'_block: {"))
type = sig[1][distinguishingIndex].type
conditions = []
if type.isObject() or type.isNonCallbackInterface():
conditions.append("is_object()")
if type.isUnion():
if type.nullable():
innerType = type.inner
else:
innerType = type
for memberType in innerType.flatMemberTypes:
if memberType.isObject() or memberType.isNonCallbackInterface():
conditions.append("is_object()")
elif memberType.isString():
conditions.append("is_string()")
elif memberType.isNumeric():
conditions.append("is_number()")
elif memberType.isBoolean():
conditions.append("is_boolean()")
conditions = [f"{distinguishingArg}.get().{condition}" for condition in conditions]
conditions = " || ".join(conditions)
if conditions != "":
conditions = "if " + conditions
caseBody.append(CGIndenter(CGGeneric(f"{conditions} {{")))
info = getJSToNativeConversionInfo(
type, descriptor, failureCode="break '_block;", isDefinitelyObject=True)
template = info.template
declType = info.declType
testCode = instantiateJSToNativeConversionTemplate(
template,
{"val": distinguishingArg},
declType,
f"arg{distinguishingIndex}",
needsAutoRoot=type_needs_auto_root(type))
caseBody.append(CGIndenter(testCode, 8))
caseBody.append(CGIndenter(
getPerSignatureCall(sig, distinguishingIndex + 1), 8))
caseBody.append(CGIndenter(CGGeneric("}")))
caseBody.append(CGGeneric("}"))
pickFirstSignature(f"{distinguishingArg}.get().is_object() && is_array_like::<D>(cx, {distinguishingArg})",
lambda s:
(s[1][distinguishingIndex].type.isSequence()
or s[1][distinguishingIndex].type.isObject()))
pickFirstSignature(f"{distinguishingArg}.get().is_object()",
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()))
if pickFirstSignature(None,
lambda s: (s[1][distinguishingIndex].type.isString()
or s[1][distinguishingIndex].type.isEnum())):
pass
elif pickFirstSignature(None,
lambda s: s[1][distinguishingIndex].type.isPrimitive()):
pass
elif pickFirstSignature(None,
lambda s: s[1][distinguishingIndex].type.isAny()):
pass
else:
caseBody.append(CGGeneric("throw_type_error(cx.raw_cx(), c\"Could not convert JavaScript argument\");\n"
"return false;"))
argCountCases.append(CGCase(str(argCount),
CGList(caseBody, "\n")))
overloadCGThings: list[CGThing] = []
overloadCGThings.append(
CGGeneric(f"let argcount = cmp::min(argc, {maxArgCount});"))
overloadCGThings.append(
CGSwitch("argcount",
argCountCases,
CGGeneric(f"throw_type_error(cx.raw_cx(), c\"Not enough arguments to {methodName}.\");\n"
"return false;")))
self.cgRoot = CGWrapper(CGList(overloadCGThings, "\n"),
pre="\n")
def define(self) -> str:
return self.cgRoot.define()
def dictionaryHasSequenceMember(dictionary: IDLDictionary) -> bool:
for member in dictionary.members:
if typeIsSequenceOrHasSequenceMember(member.type):
return True
if dictionary.parent:
return dictionaryHasSequenceMember(dictionary.parent)
return False
def typeIsSequenceOrHasSequenceMember(type: IDLType) -> bool:
if type.nullable():
assert isinstance(type, IDLNullableType)
type = type.inner
if type.isSequence():
return True
if type.isDictionary():
return dictionaryHasSequenceMember(type.inner)
if type.isUnion():
assert isinstance(type, IDLUnionType)
assert type.flatMemberTypes is not None
return any(typeIsSequenceOrHasSequenceMember(m.type) for m in
type.flatMemberTypes)
return False
def union_native_type(t: IDLType) -> str:
name = t.unroll().name
generic = "::<D>" if containsDomInterface(t) else ""
return f'GenericUnionTypes::{name}{generic}'
def firstCap(string: str) -> str:
return f"{string[0].upper()}{string[1:]}"
class JSToNativeConversionInfo():
def __init__(self, template: str | CGThing, default: str | None = None, declType: CGThing | None = None) -> None:
assert isinstance(template, str)
assert declType is None or isinstance(declType, CGThing)
self.template = template
self.default = default
self.declType = declType
def getJSToNativeConversionInfo(type: IDLType, descriptorProvider: DescriptorProvider, failureCode: str | None = None,
isDefinitelyObject: bool = False,
isMember: bool | str = False,
isArgument: bool = False,
isAutoRooted: bool = False,
invalidEnumValueFatal: bool = True,
defaultValue: IDLValue | None = None,
exceptionCode: str | None = None,
allowTreatNonObjectAsNull: bool = False,
sourceDescription: str = "value") -> JSToNativeConversionInfo:
assert not isDefinitelyObject or defaultValue is None
isEnforceRange = type.hasEnforceRange()
isClamp = type.hasClamp()
if type.legacyNullToEmptyString:
treatNullAs = "EmptyString"
else:
treatNullAs = "Default"
if exceptionCode is None:
exceptionCode = "return false;\n"
if failureCode is None:
failOrPropagate = f"throw_type_error(cx.raw_cx(), error.as_ref());\n{exceptionCode}"
else:
failOrPropagate = failureCode
def handleOptional(template: str, declType: CGThing | None, default: str | None) -> JSToNativeConversionInfo:
assert (defaultValue is None) == (default is None)
return JSToNativeConversionInfo(template, default, declType)
def onFailureNotAnObject(failureCode: str | None) -> CGThing:
return CGWrapper(
CGGeneric(
failureCode
or (f'throw_type_error(cx.raw_cx(), c"{firstCap(sourceDescription)} is not an object.");\n'
f'{exceptionCode}')),
post="\n")
def onFailureNotCallable(failureCode: str | None) -> CGGeneric:
return CGGeneric(
failureCode
or (f'throw_type_error(cx.raw_cx(), c"{firstCap(sourceDescription)} is not callable.");\n'
f'{exceptionCode}'))
def handleDefault(nullValue: str) -> str | None:
if defaultValue is None:
return None
if isinstance(defaultValue, IDLNullValue):
assert type.nullable()
return nullValue
elif isinstance(defaultValue, IDLDefaultDictionaryValue):
assert type.isDictionary()
return nullValue
elif isinstance(defaultValue, IDLEmptySequenceValue):
assert type.isSequence()
return "Vec::new()"
raise TypeError("Can't handle non-null, non-empty sequence or non-empty dictionary default value here")
def wrapObjectTemplate(templateBody: str, nullValue: str, isDefinitelyObject: bool, type: IDLType,
failureCode: str | None = None) -> str:
if not isDefinitelyObject:
templateBody = (
"if ${val}.get().is_object() {\n"
f"{CGIndenter(CGGeneric(templateBody)).define()}\n")
if type.nullable():
templateBody += (
"} else if ${val}.get().is_null_or_undefined() {\n"
f" {nullValue}\n")
templateBody += (
"} else {\n"
f"{CGIndenter(onFailureNotAnObject(failureCode)).define()}"
"}")
return templateBody
def fromJSValTemplate(config: str, errorHandler: str, exceptionCode: str) -> str:
return f"""match FromJSValConvertible::safe_from_jsval(cx, ${{val}}, {config}) {{
Ok(ConversionResult::Success(value)) => value,
Ok(ConversionResult::Failure(error)) => {{
{errorHandler}
}}
_ => {{
{exceptionCode}
}},
}}
"""
assert not (isEnforceRange and isClamp)
if type.isSequence() or type.isRecord():
innerInfo = getJSToNativeConversionInfo(innerContainerType(type),
descriptorProvider,
isMember="Sequence",
isAutoRooted=isAutoRooted)
assert innerInfo.declType is not None
declType = wrapInNativeContainerType(type, innerInfo.declType)
config = getConversionConfigForType(type, innerContainerType(type).hasEnforceRange(), isClamp, treatNullAs)
if type.nullable():
declType = CGWrapper(declType, pre="Option<", post=" >")
templateBody = fromJSValTemplate(config, failOrPropagate, exceptionCode)
return handleOptional(templateBody, declType, handleDefault("None"))
if type.isUnion():
declType = CGGeneric(union_native_type(type))
if type.nullable():
declType = CGWrapper(declType, pre="Option<", post=" >")
assert isinstance(type, (IDLUnionType, IDLNullableType))
templateBody = fromJSValTemplate("()", failOrPropagate, exceptionCode)
flatMemberTypes = type.unroll().flatMemberTypes
assert flatMemberTypes is not None
dictionaries = [
memberType
for memberType in flatMemberTypes
if memberType.isDictionary()
]
if (defaultValue
and not isinstance(defaultValue, IDLNullValue)
and not isinstance(defaultValue, IDLDefaultDictionaryValue)):
tag = defaultValue.type.tag()
if tag is IDLType.Tags.bool:
boolean = "true" if defaultValue.value else "false"
default = f"{union_native_type(type)}::Boolean({boolean})"
elif tag is IDLType.Tags.usvstring:
default = f'{union_native_type(type)}::USVString(USVString("{defaultValue.value}".to_owned()))'
elif tag is IDLType.Tags.domstring:
default = f'{union_native_type(type)}::String(DOMString::from("{defaultValue.value}"))'
elif defaultValue.type.isEnum():
enum = defaultValue.type.inner.identifier.name
default = f"{union_native_type(type)}::{enum}({enum}::{getEnumValueName(defaultValue.value)})"
else:
raise NotImplementedError("We don't currently support default values that aren't \
null, boolean, string or default dictionary")
elif dictionaries:
if defaultValue:
assert isinstance(defaultValue, IDLDefaultDictionaryValue)
dictionary, = dictionaries
default = (
f"{union_native_type(type)}::{dictionary.name}("
f"{CGDictionary.makeModuleName(dictionary.inner)}::"
f"{CGDictionary.makeDictionaryName(dictionary.inner)}::empty())"
)
else:
default = None
else:
default = handleDefault("None")
return handleOptional(templateBody, declType, default)
if type.isPromise():
assert not type.nullable()
templateBody = fromJSValTemplate("()", failOrPropagate, exceptionCode)
if isArgument:
declType = CGGeneric("&D::Promise")
else:
declType = CGGeneric("Rc<D::Promise>")
return handleOptional(templateBody, declType, handleDefault("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 = CGWrapper(CGGeneric(f"{name}<D>"), pre="Rc<", post=">")
template = f"{name}::new(SafeJSContext::from_ptr(cx.raw_cx()), ${{val}}.get().to_object())"
if type.nullable():
declType = CGWrapper(declType, pre="Option<", post=">")
template = wrapObjectTemplate(f"Some({template})", "None",
isDefinitelyObject, type,
failureCode)
return handleOptional(template, declType, handleDefault("None"))
conversionFunction = "root_from_handlevalue"
descriptorType = descriptor.returnType
if isMember == "Variadic":
conversionFunction = "native_from_handlevalue"
descriptorType = descriptor.nativeType
elif isArgument:
descriptorType = descriptor.argumentType
elif descriptor.interface.identifier.name == "WindowProxy":
conversionFunction = "windowproxy_from_handlevalue::<D>"
if failureCode is None:
unwrapFailureCode = (
f'throw_type_error(cx.raw_cx(), c"{sourceDescription} does not '
f'implement interface {descriptor.interface.identifier.name}.");\n'
f'{exceptionCode}')
else:
unwrapFailureCode = failureCode
templateBody = fill(
"""
match ${function}($${val}, SafeJSContext::from_ptr(cx.raw_cx())) {
Ok(val) => val,
Err(()) => {
$*{failureCode}
}
}
""",
failureCode=unwrapFailureCode + "\n",
function=conversionFunction)
declType = CGGeneric(descriptorType)
if type.nullable():
templateBody = f"Some({templateBody})"
declType = CGWrapper(declType, pre="Option<", post=">")
templateBody = wrapObjectTemplate(templateBody, "None",
isDefinitelyObject, type, failureCode)
return handleOptional(templateBody, declType, handleDefault("None"))
if is_typed_array(type):
if failureCode is None:
unwrapFailureCode = (
f'throw_type_error(cx.raw_cx(), c"{sourceDescription} is not a typed array.");\n'
f'{exceptionCode}'
)
else:
unwrapFailureCode = failureCode
typeName = type.unroll().name
require_heap_root = (isMember in ["Union", "Dictionary"] or sourceDescription == "return value")
if require_heap_root:
typeName = f"Heap{typeName}"
map_call = ".map(RootedTraceableBox::new)"
else:
map_call = ""
templateBody = fill(
f"""
match typedarray::${{ty}}::from($${{val}}.get().to_object()){map_call} {{
Ok(val) => val,
Err(()) => {{
$*{{failureCode}}
}}
}}
""",
ty=typeName,
failureCode=f"{unwrapFailureCode}\n",
)
declType = CGGeneric(f"typedarray::{typeName}")
if require_heap_root:
declType = CGWrapper(declType, pre="RootedTraceableBox<", post=">")
if type.nullable():
templateBody = f"Some({templateBody})"
declType = CGWrapper(declType, pre="Option<", post=">")
templateBody = wrapObjectTemplate(
templateBody,
"None",
isDefinitelyObject,
type,
failureCode,
)
return handleOptional(templateBody, declType, handleDefault("None"))
elif type.isSpiderMonkeyInterface():
raise TypeError("Can't handle SpiderMonkey interface arguments other than typed arrays yet")
if type.isDOMString():
nullBehavior = getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs)
conversionCode = fromJSValTemplate(nullBehavior, failOrPropagate, exceptionCode)
if defaultValue is None:
default = None
elif isinstance(defaultValue, IDLNullValue):
assert type.nullable()
default = "None"
else:
assert defaultValue.type.tag() == IDLType.Tags.domstring
default = f'DOMString::from("{defaultValue.value}")'
if type.nullable():
default = f"Some({default})"
declType = "DOMString"
if type.nullable():
declType = f"Option<{declType}>"
return handleOptional(conversionCode, CGGeneric(declType), default)
if type.isUSVString():
assert not isEnforceRange and not isClamp
conversionCode = fromJSValTemplate("()", failOrPropagate, exceptionCode)
if defaultValue is None:
default = None
elif isinstance(defaultValue, IDLNullValue):
assert type.nullable()
default = "None"
else:
assert defaultValue.type.tag() in (IDLType.Tags.domstring, IDLType.Tags.usvstring)
default = f'USVString("{defaultValue.value}".to_owned())'
if type.nullable():
default = f"Some({default})"
declType = "USVString"
if type.nullable():
declType = f"Option<{declType}>"
return handleOptional(conversionCode, CGGeneric(declType), default)
if type.isByteString():
assert not isEnforceRange and not isClamp
conversionCode = fromJSValTemplate("()", failOrPropagate, exceptionCode)
if defaultValue is None:
default = None
elif isinstance(defaultValue, IDLNullValue):
assert type.nullable()
default = "None"
else:
assert defaultValue.type.tag() in (IDLType.Tags.domstring, IDLType.Tags.bytestring)
default = f'ByteString::new(b"{defaultValue.value}".to_vec())'
if type.nullable():
default = f"Some({default})"
declType = "ByteString"
if type.nullable():
declType = f"Option<{declType}>"
return handleOptional(conversionCode, CGGeneric(declType), default)
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 = failureCode or f"throw_type_error(cx.raw_cx(), error.as_ref()); {exceptionCode}"
else:
handleInvalidEnumValueCode = "return true;"
template = fromJSValTemplate("()", handleInvalidEnumValueCode, exceptionCode)
if defaultValue is not None:
assert defaultValue.type.tag() == IDLType.Tags.domstring
default = f"{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()
callback = type.unroll().callback
declType = CGGeneric(f"{callback.identifier.name}<D>")
finalDeclType = CGTemplatedType("Rc", declType)
conversion = CGCallbackTempRoot(declType.define())
if type.nullable():
declType = CGTemplatedType("Option", declType)
finalDeclType = CGTemplatedType("Option", finalDeclType)
conversion = CGWrapper(conversion, pre="Some(", post=")")
if allowTreatNonObjectAsNull and type.treatNonObjectAsNull():
if not isDefinitelyObject:
haveObject = "${val}.get().is_object()"
template = CGIfElseWrapper(haveObject,
conversion,
CGGeneric("None")).define()
else:
template = conversion
else:
template = CGIfElseWrapper("IsCallable(${val}.get().to_object())",
conversion,
onFailureNotCallable(failureCode)).define()
template = wrapObjectTemplate(
template,
"None",
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 JSToNativeConversionInfo(template, default, finalDeclType)
if type.isAny():
assert not isEnforceRange and not isClamp
assert isMember != "Union"
if isMember in ("Dictionary", "Sequence") or isAutoRooted:
templateBody = "${val}.get()"
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")
if not isAutoRooted:
templateBody = f"RootedTraceableBox::from_box(Heap::boxed({templateBody}))"
if default is not None:
default = f"RootedTraceableBox::from_box(Heap::boxed({default}))"
declType = CGGeneric("RootedTraceableBox<Heap<JSVal>>")
else:
declType = CGGeneric("JSVal")
return handleOptional(templateBody, declType, default)
declType = CGGeneric("HandleValue")
if defaultValue is None:
default = None
elif isinstance(defaultValue, IDLNullValue):
default = "HandleValue::null()"
elif isinstance(defaultValue, IDLUndefinedValue):
default = "HandleValue::undefined()"
else:
raise TypeError("Can't handle non-null, non-undefined default value here")
return handleOptional("${val}", declType, default)
if type.isObject():
assert not isEnforceRange and not isClamp
templateBody = "${val}.get().to_object()"
default = "ptr::null_mut()"
if isMember in ("Dictionary", "Union", "Sequence") and not isAutoRooted:
templateBody = f"RootedTraceableBox::from_box(Heap::boxed({templateBody}))"
default = "RootedTraceableBox::new(Heap::default())"
declType = CGGeneric("RootedTraceableBox<Heap<*mut JSObject>>")
else:
declType = CGGeneric("*mut JSObject")
templateBody = wrapObjectTemplate(templateBody, default,
isDefinitelyObject, type, failureCode)
return handleOptional(templateBody, declType,
handleDefault(default))
if type.isDictionary():
assert not type.nullable() or (isMember and isMember != "Dictionary")
typeName = f"{CGDictionary.makeModuleName(type.inner)}::{CGDictionary.makeDictionaryName(type.inner)}"
if containsDomInterface(type):
typeName += "<D>"
declType = CGGeneric(typeName)
empty = f"{typeName.replace('<D>', '')}::empty()"
if type_needs_tracing(type):
declType = CGTemplatedType("RootedTraceableBox", declType)
template = fromJSValTemplate("()", failOrPropagate, exceptionCode)
return handleOptional(template, declType, handleDefault(empty))
if type.isUndefined():
return JSToNativeConversionInfo("", None, None)
if not type.isPrimitive():
raise TypeError(f"Need conversion for argument type '{type}'")
conversionBehavior = getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs)
if failureCode is None:
failureCode = 'return false'
declType = CGGeneric(builtinNames[type.tag()])
if type.nullable():
declType = CGWrapper(declType, pre="Option<", post=">")
template = fromJSValTemplate(conversionBehavior, failOrPropagate, exceptionCode)
if defaultValue is not None:
if isinstance(defaultValue, IDLNullValue):
assert type.nullable()
defaultStr = "None"
else:
tag = defaultValue.type.tag()
if tag in [IDLType.Tags.float, IDLType.Tags.double]:
defaultStr = f"Finite::wrap({defaultValue.value})"
elif tag in numericTags:
defaultStr = str(defaultValue.value)
else:
assert tag == IDLType.Tags.bool
defaultStr = toStringBool(defaultValue.value)
if type.nullable():
defaultStr = f"Some({defaultStr})"
else:
defaultStr = None
return handleOptional(template, declType, defaultStr)
def instantiateJSToNativeConversionTemplate(templateBody: str, replacements: dict[str, Any],
declType: CGThing | None, declName: str,
needsAutoRoot: bool = False) -> CGThing:
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)
if needsAutoRoot:
result.append(CGGeneric(f"auto_root!(&in(cx) let {declName} = {declName});"))
result.append(CGGeneric(""))
return result
def convertConstIDLValueToJSVal(value: IDLValue) -> str | None:
if isinstance(value, IDLNullValue):
return "ConstantVal::NullVal"
tag = value.type.tag()
if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16,
IDLType.Tags.uint16, IDLType.Tags.int32]:
return f"ConstantVal::Int({value.value})"
if tag == IDLType.Tags.uint32:
return f"ConstantVal::Uint({value.value})"
if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]:
return f"ConstantVal::Double({value.value} as f64)"
if tag == IDLType.Tags.bool:
return "ConstantVal::Bool(true)" if value.value else "ConstantVal::BoolVal(false)"
if tag in [IDLType.Tags.unrestricted_float, IDLType.Tags.float,
IDLType.Tags.unrestricted_double, IDLType.Tags.double]:
return f"ConstantVal::Double({value.value} as f64)"
raise TypeError(f"Const value of unhandled type: {value.type}")
class CGArgumentConverter(CGThing):
converter: CGThing
def __init__(self, argument: IDLArgument | FakeArgument, index: int, args: str, argc: str, descriptorProvider: DescriptorProvider,
invalidEnumValueFatal: bool=True) -> None:
CGThing.__init__(self)
assert not argument.defaultValue or argument.optional
replacementVariables = {
"val": f"HandleValue::from_raw({args}.get({index}))",
}
info = getJSToNativeConversionInfo(
argument.type,
descriptorProvider,
invalidEnumValueFatal=invalidEnumValueFatal,
defaultValue=argument.defaultValue,
isMember="Variadic" if argument.variadic else False,
isAutoRooted=type_needs_auto_root(argument.type),
allowTreatNonObjectAsNull=argument.allowTreatNonCallableAsNull())
template = info.template
default = info.default
declType = info.declType
if not argument.variadic:
if argument.optional:
condition = f"{args}.get({index}).is_undefined()"
if argument.defaultValue:
assert default
template = CGIfElseWrapper(condition,
CGGeneric(default),
CGGeneric(template)).define()
else:
assert not default
assert declType is not None
declType = CGWrapper(declType, pre="Option<", post=">")
template = CGIfElseWrapper(condition,
CGGeneric("None"),
CGGeneric(f"Some({template})")).define()
else:
assert not default
arg = f"arg{index}"
self.converter = instantiateJSToNativeConversionTemplate(
template, replacementVariables, declType, arg,
needsAutoRoot=type_needs_auto_root(argument.type))
else:
assert argument.optional
variadicConversion = {
"val": f"HandleValue::from_raw({args}.get(variadicArg))",
}
innerConverter = [instantiateJSToNativeConversionTemplate(
template, variadicConversion, declType, "slot")]
arg = f"arg{index}"
if argument.type.isGeckoInterface():
init = f"rooted_vec!(let mut {arg})"
innerConverter.append(CGGeneric(f"{arg}.push(Dom::from_ref(&*slot));"))
else:
init = f"let mut {arg} = vec![]"
innerConverter.append(CGGeneric(f"{arg}.push(slot);"))
inner = CGIndenter(CGList(innerConverter, "\n"), 8).define()
sub = "" if index == 0 else f"- {index}"
self.converter = CGGeneric(f"""
{init};
if {argc} > {index} {{
{arg}.reserve({argc} as usize{sub});
for variadicArg in {index}..{argc} {{
{inner}
}}
}}""")
def define(self) -> str:
return self.converter.define()
def wrapForType(jsvalRef: str, result: str = 'result', successCode: str = 'true', pre: str = '') -> str:
wrap = f"{pre}\n({result}).to_jsval(cx.raw_cx(), {jsvalRef});"
if successCode:
wrap += f"\n{successCode}"
return wrap
class Context(IntEnum):
No = 0
OldCx = 1
Cx = 2
CurrentRealm = 3
def typeNeedsCx(type: IDLType | None, retVal: bool = False) -> Context:
if type is None:
return Context.No
if type.nullable():
assert isinstance(type, IDLNullableType)
type = type.inner
if type.isSequence():
assert isinstance(type, IDLSequenceType)
type = type.inner
if type.isUnion():
assert isinstance(type, IDLUnionType)
flatMemberTypes = type.unroll().flatMemberTypes
assert flatMemberTypes is not None
return max(typeNeedsCx(t) for t in flatMemberTypes)
if retVal and type.isSpiderMonkeyInterface():
return Context.OldCx
return Context.OldCx if type.isAny() or type.isObject() else Context.No
def returnTypeNeedsOutparam(type: IDLType | None) -> bool:
if type is None:
return False
if type.nullable():
assert isinstance(type, IDLNullableType)
type = type.inner
return type.isAny()
def outparamTypeFromReturnType(type: IDLType) -> str:
if type.isAny():
return "MutableHandleValue"
raise TypeError(f"Don't know how to handle {type} as an outparam")
def getConversionConfigForType(type: IDLType, isEnforceRange: bool, isClamp: bool, treatNullAs: str) -> str:
if type.isSequence() or type.isRecord():
return getConversionConfigForType(innerContainerType(type), isEnforceRange, isClamp, treatNullAs)
if type.isDOMString():
assert not isEnforceRange and not isClamp
treatAs = {
"Default": "StringificationBehavior::Default",
"EmptyString": "StringificationBehavior::Empty",
}
if treatNullAs not in treatAs:
raise TypeError(f"We don't support [TreatNullAs={treatNullAs}]")
if type.nullable():
return "StringificationBehavior::Default"
else:
return treatAs[treatNullAs]
if type.isPrimitive() and type.isInteger():
if isEnforceRange:
return "ConversionBehavior::EnforceRange"
elif isClamp:
return "ConversionBehavior::Clamp"
else:
return "ConversionBehavior::Default"
assert not isEnforceRange and not isClamp
return "()"
def builtin_return_type(returnType: IDLType) -> CGThing:
result = CGGeneric(builtinNames[returnType.tag()])
if returnType.nullable():
result = CGWrapper(result, pre="Option<", post=">")
return result
def getRetvalDeclarationForType(returnType: IDLType | None, descriptorProvider: DescriptorProvider) -> CGThing:
if returnType is None or returnType.isUndefined():
return CGGeneric("()")
if returnType.isPrimitive() and returnType.tag() in builtinNames:
return builtin_return_type(returnType)
if is_typed_array(returnType) and returnType.tag() in builtinNames:
arrayType = CGGeneric(f"Heap{builtinNames[returnType.tag()]}")
result = CGWrapper(arrayType, pre="RootedTraceableBox<", post=">")
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.isUSVString():
result = CGGeneric("USVString")
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.isPromise():
assert not returnType.nullable()
return CGGeneric("Rc<D::Promise>")
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():
callback = returnType.unroll().callback
result = CGGeneric(f'Rc<{getModuleFromObject(callback)}::{callback.identifier.name}<D>>')
if returnType.nullable():
result = CGWrapper(result, pre="Option<", post=">")
return result
if returnType.isUnion():
result = CGGeneric(union_native_type(returnType))
if returnType.nullable():
result = CGWrapper(result, pre="Option<", post=">")
return result
if returnType.isAny():
return CGGeneric("JSVal")
if returnType.isObject() or returnType.isSpiderMonkeyInterface():
result = CGGeneric("NonNull<JSObject>")
if returnType.nullable():
result = CGWrapper(result, pre="Option<", post=">")
return result
if returnType.isSequence() or returnType.isRecord():
result = getRetvalDeclarationForType(innerContainerType(returnType), descriptorProvider)
result = wrapInNativeContainerType(returnType, result)
if returnType.nullable():
result = CGWrapper(result, pre="Option<", post=">")
return result
if returnType.isDictionary():
nullable = returnType.nullable()
dictName = returnType.inner.name if nullable else returnType.name
generic = "<D>" if containsDomInterface(returnType) else ""
result = CGGeneric(f"{dictName}{generic}")
if type_needs_tracing(returnType):
result = CGWrapper(result, pre="RootedTraceableBox<", post=">")
if nullable:
result = CGWrapper(result, pre="Option<", post=">")
return result
raise TypeError(f"Don't know how to declare return value for {returnType}")
def MemberCondition(pref: str | None, func: str | None, exposed: set | None, secure: bool | None) -> list[str]:
assert pref is None or isinstance(pref, str)
assert func is None or isinstance(func, str)
assert exposed is None or isinstance(exposed, set)
assert func is None or pref is None or exposed is None or secure is None
conditions = []
if secure:
conditions.append('Condition::SecureContext()')
if pref:
conditions.append(f'Condition::Pref("{pref}")')
if func:
conditions.append(f'Condition::Func(D::{func})')
if exposed:
conditions.extend([
f"Condition::Exposed(Globals::{camel_to_upper_snake(i)})" for i in exposed
])
if len(conditions) == 0:
conditions.append("Condition::Satisfied")
return conditions
PropertyDefinerElementType = TypeVar('PropertyDefinerElementType')
class PropertyDefiner(Generic[PropertyDefinerElementType]):
name: str
regular: list[PropertyDefinerElementType]
def __init__(self, descriptor: Descriptor, name: str) -> None:
self.descriptor = descriptor
self.name = name
def variableName(self) -> str:
return f"s{self.name}"
def length(self) -> int:
return len(self.regular)
@abstractmethod
def generateArray(self, array: list[PropertyDefinerElementType], name: str) -> str:
raise NotImplementedError
def __str__(self) -> str:
return self.generateArray(self.regular, self.variableName())
@staticmethod
def getStringAttr(member: IDLInterfaceMember, name: str) -> str | None:
attr = member.getExtendedAttribute(name)
if attr is None:
return None
assert len(attr) == 1
assert attr[0] is not None
return attr[0]
@staticmethod
def getControllingCondition(interfaceMember: IDLInterfaceMember, descriptor: Descriptor) -> list[str]:
return MemberCondition(
PropertyDefiner.getStringAttr(interfaceMember,
"Pref"),
PropertyDefiner.getStringAttr(interfaceMember,
"Func"),
interfaceMember.exposureSet,
interfaceMember.getExtendedAttribute("SecureContext"))
def generateGuardedArray(
self,
array: list[PropertyDefinerElementType],
name: str,
specTemplate: Callable[[PropertyDefinerElementType], str] | str,
specTerminator: str | None,
specType: str,
getCondition: Callable[[PropertyDefinerElementType, Descriptor], list[str]],
getDataTuple: Callable[[PropertyDefinerElementType], tuple[str, ...]]
) -> str:
assert len(array) != 0
specs = []
prefableSpecs = []
prefableTemplate = ' Guard::new(%s, (%s)[%d])'
if isinstance(specTemplate, str):
origTemplate = specTemplate
specTemplate = lambda _: origTemplate
for cond, members in groupby(array, lambda m: getCondition(m, self.descriptor)):
currentSpecs = [specTemplate(m) % getDataTuple(m) for m in members]
if specTerminator:
currentSpecs.append(specTerminator)
joinedCurrentSpecs = ',\n'.join(currentSpecs)
specs.append(f"&Box::leak(Box::new([\n{joinedCurrentSpecs}]))[..]\n")
conds = ','.join(cond) if isinstance(cond, list) else cond
prefableSpecs.append(
prefableTemplate % (f"&[{conds}]", f"unsafe {{ {name}_specs.get() }}", len(specs) - 1)
)
joinedSpecs = ',\n'.join(specs)
specsArray = f"static {name}_specs: ThreadUnsafeOnceLock<&[&[{specType}]]> = ThreadUnsafeOnceLock::new();\n"
initSpecs = f"""
pub(crate) fn init_{name}_specs<D: DomTypes>() {{
{name}_specs.set(Box::leak(Box::new([{joinedSpecs}])));
}}"""
joinedPrefableSpecs = ',\n'.join(prefableSpecs)
prefArray = f"static {name}: ThreadUnsafeOnceLock<&[Guard<&[{specType}]>]> = ThreadUnsafeOnceLock::new();\n"
initPrefs = f"""
pub(crate) fn init_{name}_prefs<D: DomTypes>() {{
{name}.set(Box::leak(Box::new([{joinedPrefableSpecs}])));
}}"""
return f"{specsArray}{initSpecs}{prefArray}{initPrefs}"
def generateUnguardedArray(
self,
array: list[PropertyDefinerElementType],
name: str,
specTemplate: Callable[[PropertyDefinerElementType], str] | str,
specTerminator: str,
specType: str,
getCondition: Callable[[PropertyDefinerElementType, Descriptor], list[str]],
getDataTuple: Callable[[PropertyDefinerElementType], tuple[str, ...]]
) -> str:
groups = groupby(array, lambda m: getCondition(m, self.descriptor))
assert len(list(groups)) == 1
if isinstance(specTemplate, str):
origTemplate = specTemplate
specTemplate = lambda _: origTemplate
specsArray = [specTemplate(m) % getDataTuple(m) for m in array]
specsArray.append(specTerminator)
joinedSpecs = ',\n'.join(specsArray)
initialSpecs = f"static {name}: ThreadUnsafeOnceLock<&[{specType}]> = ThreadUnsafeOnceLock::new();\n"
initSpecs = f"""
pub(crate) fn init_{name}<D: DomTypes>() {{
{name}.set(Box::leak(Box::new([{joinedSpecs}])));
}}"""
return dedent(f"{initialSpecs}{initSpecs}")
def methodLength(method: IDLMethod) -> int:
signatures = method.signatures()
return min(
len([arg for arg in arguments if not arg.optional and not arg.variadic])
for (_, arguments) in signatures)
class MethodDefiner(PropertyDefiner):
def __init__(self, descriptor: Descriptor, name: str, static: bool, unforgeable: bool, crossorigin: bool = False) -> None:
assert not (static and unforgeable)
assert not (static and crossorigin)
assert not (unforgeable and crossorigin)
PropertyDefiner.__init__(self, descriptor, name)
if not descriptor.interface.isCallback() or static:
methods = [m for m in descriptor.interface.members if
m.isMethod() and m.isStatic() == static
and (bool(m.getExtendedAttribute("CrossOriginCallable")) or not crossorigin)
and not m.isIdentifierLess()
and (MemberIsLegacyUnforgeable(m, descriptor) == unforgeable or crossorigin)]
else:
methods = []
self.regular: list[dict[str, Any]] = []
for m in methods:
method = self.methodData(m, descriptor, crossorigin)
if m.isStatic():
method["nativeName"] = CGDictionary.makeMemberName(
descriptor.binaryNameFor(m.identifier.name, True)
)
self.regular.append(method)
def hasIterator(methods: list[IDLMethod], regular: list[dict[str, Any]]) -> bool:
return (any("@@iterator" in m.aliases for m in methods)
or any("@@iterator" == r["name"] for r in regular))
if (not static
and not unforgeable
and not crossorigin
and descriptor.supportsIndexedProperties()):
if hasIterator(methods, self.regular):
raise TypeError("Cannot have indexed getter/attr on "
f"interface {self.descriptor.interface.identifier.name} with other members "
"that generate @@iterator, such as "
"maplike/setlike or aliased functions.")
self.regular.append({"name": '@@iterator',
"methodInfo": False,
"selfHostedName": "$ArrayValues",
"length": 0,
"flags": "0", "condition": "Condition::Satisfied"})
maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
if (not static and not unforgeable and not crossorigin
and maplikeOrSetlikeOrIterable
and maplikeOrSetlikeOrIterable.isIterable()
and maplikeOrSetlikeOrIterable.isValueIterator()):
m = maplikeOrSetlikeOrIterable
self.regular.append({
"name": "keys",
"methodInfo": False,
"selfHostedName": "ArrayKeys",
"length": 0,
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor)
})
self.regular.append({
"name": "values",
"methodInfo": False,
"selfHostedName": "$ArrayValues",
"length": 0,
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor)
})
self.regular.append({
"name": "entries",
"methodInfo": False,
"selfHostedName": "ArrayEntries",
"length": 0,
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor)
})
self.regular.append({
"name": "forEach",
"methodInfo": False,
"selfHostedName": "ArrayForEach",
"length": 1,
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m,
descriptor)
})
isLegacyUnforgeableInterface = bool(descriptor.interface.getExtendedAttribute("LegacyUnforgeable"))
if not static and unforgeable == isLegacyUnforgeableInterface and not crossorigin:
stringifier = descriptor.operations['Stringifier']
if stringifier:
self.regular.append({
"name": "toString",
"nativeName": stringifier.identifier.name,
"length": 0,
"flags": "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(stringifier, descriptor)
})
self.unforgeable = unforgeable
self.crossorigin = crossorigin
@staticmethod
def methodData(m: IDLMethod, descriptor: Descriptor, crossorigin: bool) -> dict[str, Any]:
return {
"name": m.identifier.name,
"methodInfo": not m.isStatic(),
"length": methodLength(m),
"flags": "JSPROP_READONLY" if crossorigin else "JSPROP_ENUMERATE",
"condition": PropertyDefiner.getControllingCondition(m, descriptor),
"allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable") is not None,
"returnsPromise": m.returnsPromise(),
}
def generateArray(self, array: list[dict[str, Any]], name: str) -> str:
if len(array) == 0:
return ""
def condition(m: dict[str, Any], d: Descriptor) -> list[str]:
return m["condition"]
def specData(m: dict[str, Any]) -> tuple:
flags = m["flags"]
if self.unforgeable:
flags += " | JSPROP_PERMANENT | JSPROP_READONLY"
if flags != "0":
flags = f"({flags}) as u16"
if "selfHostedName" in m:
selfHostedName = str_to_cstr_ptr(m["selfHostedName"])
assert not m.get("methodInfo", True)
accessor = "None"
jitinfo = "ptr::null()"
else:
selfHostedName = "ptr::null()"
if m.get("methodInfo", True):
if m.get("returnsPromise", False):
exceptionToRejection = "true"
else:
exceptionToRejection = "false"
identifier = m.get("nativeName", m["name"])
if m.get("allowCrossOriginThis", False):
callPolicy = 'CrossOriginCallable'
elif self.descriptor.interface.hasDescendantWithCrossOriginMembers:
callPolicy = 'TargetClassMaybeCrossOrigin'
else:
callPolicy = 'Normal'
jitinfo = (f"unsafe {{ {identifier}_methodinfo.get() }}"
" as *const _ as *const JSJitInfo")
accessor = f"Some(generic_method::<D, call_policies::{callPolicy}, {exceptionToRejection}>)"
else:
if m.get("returnsPromise", False):
jitinfo = f"unsafe {{ {m.get('nativeName', m['name'])}_methodinfo.get() }}"
accessor = "Some(generic_static_promise_method)"
else:
jitinfo = "ptr::null()"
accessor = f'Some({m.get("nativeName", m["name"])}::<D>)'
if m["name"].startswith("@@"):
assert not self.crossorigin
name = f'JSPropertySpec_Name {{ symbol_: SymbolCode::{m["name"][2:]} as usize + 1 }}'
else:
name = f'JSPropertySpec_Name {{ string_: {str_to_cstr_ptr(m["name"])} }}'
return (name, accessor, jitinfo, m["length"], flags, selfHostedName)
specTemplate = (
' JSFunctionSpec {\n'
' name: %s,\n'
' call: JSNativeWrapper { op: %s, info: %s },\n'
' nargs: %s,\n'
' flags: %s,\n'
' selfHostedName: %s\n'
' }')
specTerminator = (
' JSFunctionSpec {\n'
' name: JSPropertySpec_Name { string_: ptr::null() },\n'
' call: JSNativeWrapper { op: None, info: ptr::null() },\n'
' nargs: 0,\n'
' flags: 0,\n'
' selfHostedName: ptr::null()\n'
' }')
if self.crossorigin:
return self.generateUnguardedArray(
array, name,
specTemplate, specTerminator,
'JSFunctionSpec',
condition, specData)
else:
return self.generateGuardedArray(
array, name,
specTemplate, specTerminator,
'JSFunctionSpec',
condition, specData)
class AttrDefiner(PropertyDefiner):
def __init__(self, descriptor: Descriptor, name: str, static: bool, unforgeable: bool, crossorigin: bool = False) -> None:
assert not (static and unforgeable)
assert not (static and crossorigin)
assert not (unforgeable and crossorigin)
PropertyDefiner.__init__(self, descriptor, name)
self.name = name
self.descriptor = descriptor
self.regular: list[dict[str, Any]] = [
{
"name": m.identifier.name,
"attr": m,
"flags": "JSPROP_ENUMERATE",
"kind": "JSPropertySpec_Kind::NativeAccessor",
}
for m in descriptor.interface.members if
m.isAttr() and m.isStatic() == static
and (MemberIsLegacyUnforgeable(m, descriptor) == unforgeable or crossorigin)
and (not crossorigin
or m.getExtendedAttribute("CrossOriginReadable")
or m.getExtendedAttribute("CrossOriginWritable"))
]
self.static = static
self.unforgeable = unforgeable
self.crossorigin = crossorigin
if not static and not unforgeable and not crossorigin and not (
descriptor.interface.isNamespace() or descriptor.interface.isCallback()
):
self.regular.append({
"name": "@@toStringTag",
"attr": None,
"flags": "JSPROP_READONLY",
"kind": "JSPropertySpec_Kind::Value",
})
def generateArray(self, array: list[dict[str, Any]], name: str) -> str:
if len(array) == 0:
return ""
def getter(attr: dict[str, Any]) -> str:
attr = attr['attr']
if self.crossorigin and not attr.getExtendedAttribute("CrossOriginReadable"):
return "JSNativeWrapper { op: None, info: ptr::null() }"
if self.static:
accessor = f'get_{self.descriptor.internalNameFor(attr.identifier.name)}::<D>'
jitinfo = "ptr::null()"
else:
if attr.type.isPromise():
exceptionToRejection = "true"
else:
exceptionToRejection = "false"
if attr.getExtendedAttribute("CrossOriginReadable"):
assert not attr.legacyLenientThis, \
"CrossOriginReadable && LenientThis: not supported"
callPolicy = 'CrossOriginCallable'
elif self.descriptor.interface.hasDescendantWithCrossOriginMembers:
if attr.legacyLenientThis:
callPolicy = 'LenientThisTargetClassMaybeCrossOrigin'
else:
callPolicy = 'TargetClassMaybeCrossOrigin'
elif attr.legacyLenientThis:
callPolicy = 'LenientThis'
else:
callPolicy = 'Normal'
accessor = f"generic_getter::<D, call_policies::{callPolicy}, {exceptionToRejection}>"
internalName = self.descriptor.internalNameFor(attr.identifier.name)
jitinfo = f"unsafe {{ {internalName}_getterinfo.get() }}"
return f"JSNativeWrapper {{ op: Some({accessor}), info: {jitinfo} }}"
def setter(attr: dict[str, Any]) -> str :
attr = attr['attr']
if ((self.crossorigin and not attr.getExtendedAttribute("CrossOriginWritable"))
or (attr.readonly
and not attr.getExtendedAttribute("PutForwards")
and not attr.getExtendedAttribute("Replaceable"))):
return "JSNativeWrapper { op: None, info: ptr::null() }"
if self.static:
accessor = f'set_{self.descriptor.internalNameFor(attr.identifier.name)}::<D>'
jitinfo = "ptr::null()"
else:
if attr.getExtendedAttribute("CrossOriginWritable"):
assert not attr.legacyLenientThis, \
"CrossOriginWritable && LenientThis: not supported"
callPolicy = 'CrossOriginCallable'
elif self.descriptor.interface.hasDescendantWithCrossOriginMembers:
if attr.legacyLenientThis:
callPolicy = 'LenientThisTargetClassMaybeCrossOrigin'
else:
callPolicy = 'TargetClassMaybeCrossOrigin'
elif attr.legacyLenientThis:
callPolicy = 'LenientThis'
else:
callPolicy = 'Normal'
accessor = f"generic_setter::<D, call_policies::{callPolicy}>"
internalName = self.descriptor.internalNameFor(attr.identifier.name)
jitinfo = f"unsafe {{ {internalName}_setterinfo.get() }}"
return f"JSNativeWrapper {{ op: Some({accessor}), info: {jitinfo} }}"
def condition(m: dict[str, Any], d: Descriptor) -> list[str]:
if m["name"] == "@@toStringTag":
return MemberCondition(pref=None, func=None, exposed=None, secure=None)
return PropertyDefiner.getControllingCondition(m["attr"], d)
def specData(attr: dict[str, Any]) -> tuple:
if attr["name"] == "@@toStringTag":
return (attr["name"][2:], attr["flags"], attr["kind"],
str_to_cstr_ptr(self.descriptor.interface.getClassName()))
flags = attr["flags"]
if self.unforgeable:
flags += " | JSPROP_PERMANENT"
return (str_to_cstr_ptr(attr["attr"].identifier.name), flags, attr["kind"], getter(attr),
setter(attr))
def template(m: dict[str, Any]) -> str:
if m["name"] == "@@toStringTag":
return """ JSPropertySpec {
name: JSPropertySpec_Name { symbol_: SymbolCode::%s as usize + 1 },
attributes_: (%s),
kind_: (%s),
u: JSPropertySpec_AccessorsOrValue {
value: JSPropertySpec_ValueWrapper {
type_: JSPropertySpec_ValueWrapper_Type::String,
__bindgen_anon_1: JSPropertySpec_ValueWrapper__bindgen_ty_1 {
string: %s,
}
}
}
}
"""
return """ JSPropertySpec {
name: JSPropertySpec_Name { string_: %s },
attributes_: (%s),
kind_: (%s),
u: JSPropertySpec_AccessorsOrValue {
accessors: JSPropertySpec_AccessorsOrValue_Accessors {
getter: JSPropertySpec_Accessor {
native: %s,
},
setter: JSPropertySpec_Accessor {
native: %s,
}
}
}
}
"""
if self.crossorigin:
return self.generateUnguardedArray(
array, name,
template,
' JSPropertySpec::ZERO',
'JSPropertySpec',
condition, specData)
else:
return self.generateGuardedArray(
array, name,
template,
' JSPropertySpec::ZERO',
'JSPropertySpec',
condition, specData)
class ConstDefiner(PropertyDefiner[IDLConst]):
def __init__(self, descriptor: Descriptor, name: str) -> None:
PropertyDefiner.__init__(self, descriptor, name)
self.name = name
self.regular: list[IDLConst] = [m for m in descriptor.interface.members if m.isConst()]
def generateArray(self, array: list[IDLConst], name: str) -> str:
if len(array) == 0:
return ""
def specData(const: IDLConst) -> tuple:
return (str_to_cstr(const.identifier.name),
convertConstIDLValueToJSVal(const.value))
return self.generateGuardedArray(
array,
name,
' ConstantSpec { name: %s, value: %s }',
None,
'ConstantSpec',
PropertyDefiner.getControllingCondition, specData)
lineStartDetector = re.compile("^(?=[^\n])", re.MULTILINE)
class CGIndenter(CGThing):
def __init__(self, child: CGThing, indentLevel: int = 4) -> None:
CGThing.__init__(self)
self.child = child
self.indent = " " * indentLevel
def define(self) -> str:
defn = self.child.define()
if defn != "":
return re.sub(lineStartDetector, self.indent, defn)
else:
return defn
class CGWrapper(CGThing):
child: CGThing
pre: str
post: str
reindent: bool
def __init__(self, child: CGThing, pre: str = "", post: str= "", reindent: bool = False) -> None:
CGThing.__init__(self)
self.child = child
self.pre = pre
self.post = post
self.reindent = reindent
def define(self) -> str:
defn = self.child.define()
if self.reindent:
defn = stripTrailingWhitespace(
defn.replace("\n", f"\n{' ' * len(self.pre)}"))
return f"{self.pre}{defn}{self.post}"
class CGRecord(CGThing):
def __init__(self, keyType: IDLType, value: CGThing) -> None:
CGThing.__init__(self)
assert keyType.isString()
self.keyType = keyType
self.value = value
def define(self) -> str:
if self.keyType.isByteString():
keyDef = "ByteString"
elif self.keyType.isDOMString():
keyDef = "DOMString"
elif self.keyType.isUSVString():
keyDef = "USVString"
else:
assert False
defn = f"{keyDef}, {self.value.define()}"
return f"Record<{defn}>"
TopLevelType = IDLInterfaceOrNamespace | IDLDictionary | IDLEnum | IDLType
class CGImports(CGWrapper):
def __init__(
self,
child: CGThing,
descriptors: list[Descriptor],
callbacks: list[IDLCallback],
dictionaries: list[IDLDictionary],
enums: list[IDLEnum],
typedefs: list[IDLTypedef],
imports: list[str],
config: Configuration,
) -> None:
def componentTypes(type: IDLType | IDLDictionary) -> list[IDLType | IDLDictionary]:
if isIDLType(type) and type.nullable():
type = type.unroll()
if type.isUnion():
assert isinstance(type, IDLUnionType)
assert type.flatMemberTypes is not None
return type.flatMemberTypes
if type.isDictionary():
assert isinstance(type, (IDLDictionary, IDLWrapperType))
return [type] + getTypesFromDictionary(type)
if isinstance(type, IDLType) and type.isSequence():
assert isinstance(type, IDLSequenceType)
return componentTypes(type.inner)
return [type]
def isImportable(type: TopLevelType) -> bool:
if not type.isType():
assert (type.isInterface() or type.isDictionary()
or type.isEnum() or type.isNamespace())
return True
assert isinstance(type, IDLType)
return not (type.builtin or type.isSequence() or type.isUnion())
def relatedTypesForSignatures(method: IDLMethod | IDLCallback) -> list[IDLType]:
types = []
for (returnType, arguments) in method.signatures():
types += componentTypes(returnType)
for arg in arguments:
types += componentTypes(arg.type)
return types
def getIdentifier(t: IDLObject) -> IDLUnresolvedIdentifier:
if isIDLType(t):
if t.isCallback():
assert isinstance(t, IDLCallbackType)
return t.callback.identifier
raise Exception(f"Don't know how to handle type without identifier: {t}")
assert t.isInterface() or t.isDictionary() or t.isEnum() or t.isNamespace()
assert isinstance(t, (IDLInterfaceOrNamespace, IDLDictionary, IDLEnum))
return t.identifier
def removeWrapperAndNullableTypes(
types: list[IDLType | IDLDictionary | IDLInterfaceOrNamespace]
) -> list[TopLevelType]:
normalized = []
for t in types:
while isIDLType(t) and (t.nullable() or isinstance(t, IDLWrapperType)):
assert isinstance(t, (IDLNullableType, IDLWrapperType))
t = t.inner
if isImportable(t):
normalized += [t]
return normalized
types = []
descriptorProvider = config.getDescriptorProvider()
for d in descriptors:
if not d.interface.isCallback():
types += [d.interface]
if d.interface.isIteratorInterface():
types += [d.interface.iterableInterface]
members = d.interface.members + d.interface.legacyFactoryFunctions
constructor = d.interface.ctor()
if constructor:
members += [constructor]
if d.proxy:
members += [o for o in list(d.operations.values()) if o]
for m in members:
if m.isMethod():
types += relatedTypesForSignatures(m)
if m.isStatic():
types += [
descriptorProvider.getDescriptor(iface).interface
for iface in d.interface.exposureSet
]
elif m.isAttr():
types += componentTypes(m.type)
for c in callbacks:
types += relatedTypesForSignatures(c)
for d in dictionaries:
types += componentTypes(d)
for t in typedefs:
if not t.innerType.isCallback():
types += componentTypes(t.innerType)
types = removeWrapperAndNullableTypes(types)
descriptorProvider = config.getDescriptorProvider()
extras = []
for t in types:
if t.isCallback():
if getIdentifier(t) in [c.identifier for c in callbacks]:
continue
if t.isDictionary() and t in dictionaries:
continue
if t.isEnum() and t in enums:
continue
if t.isInterface() or t.isNamespace():
name = getIdentifier(t).name
descriptor = descriptorProvider.getDescriptor(name)
parentName = descriptor.getParentName()
while parentName:
descriptor = descriptorProvider.getDescriptor(parentName)
if descriptor not in descriptors:
extras += [descriptor.bindingPath]
parentName = descriptor.getParentName()
elif isIDLType(t) and t.isRecord():
extras += ['crate::record::Record']
elif isinstance(t, IDLPromiseType):
pass
else:
if t.isEnum():
extras += [f'{getModuleFromObject(t)}::{getIdentifier(t).name}Values']
extras += [f'{getModuleFromObject(t)}::{getIdentifier(t).name}']
statements = []
statements.extend(f'use {i};' for i in sorted(set(imports + extras)))
joinedStatements = '\n'.join(statements)
CGWrapper.__init__(self, child,
pre=f'{joinedStatements}\n\n')
class CGIfWrapper(CGWrapper):
def __init__(self, condition: str, child: CGThing) -> None:
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: str, child: CGThing) -> None:
CGWrapper.__init__(self, child, pre=f"{templateName}<", post=">")
class CGNamespace(CGWrapper):
def __init__(self, namespace: str, child: CGThing, public: bool = False) -> None:
pub = "pub " if public else ""
pre = f"{pub}mod {namespace} {{\n"
post = f"}} // mod {namespace}"
CGWrapper.__init__(self, child, pre=pre, post=post)
@staticmethod
def build(namespaces: list[str], child: CGThing, public: bool = False) -> CGThing:
if not namespaces:
return child
inner = CGNamespace.build(namespaces[1:], child, public=public)
return CGNamespace(namespaces[0], inner, public=public)
def DOMClassTypeId(desc: Descriptor) -> str:
protochain = desc.prototypeChain
inner = ""
if desc.hasDescendants():
if desc.interface.getExtendedAttribute("Abstract"):
return "crate::codegen::InheritTypes::TopTypeId { abstract_: () }"
name = desc.interface.identifier.name
inner = f"(crate::codegen::InheritTypes::{name}TypeId::{name})"
elif len(protochain) == 1:
return "crate::codegen::InheritTypes::TopTypeId { alone: () }"
reversed_protochain = list(reversed(protochain))
for (child, parent) in zip(reversed_protochain, reversed_protochain[1:]):
inner = f"(crate::codegen::InheritTypes::{parent}TypeId::{child}{inner})"
return f"crate::codegen::InheritTypes::TopTypeId {{ {protochain[0].lower()}: {inner} }}"
def DOMClass(descriptor: Descriptor) -> str:
protoList = [f'PrototypeList::ID::{proto}' for proto in descriptor.prototypeChain]
protoList.extend(['PrototypeList::ID::Last'] * (descriptor.config.maxProtoChainLength - len(protoList)))
prototypeChainString = ', '.join(protoList)
mallocSizeOf = f"malloc_size_of_including_raw_self::<{descriptor.concreteType}>"
if descriptor.isGlobal():
globals_ = camel_to_upper_snake(descriptor.name)
else:
globals_ = 'EMPTY'
return f"""
DOMClass {{
interface_chain: [ {prototypeChainString} ],
depth: {descriptor.prototypeDepth},
type_id: {DOMClassTypeId(descriptor)},
malloc_size_of: {mallocSizeOf} as unsafe fn(&mut _, _) -> _,
global: Globals::{globals_},
}}"""
class CGDOMJSClass(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
def define(self) -> str:
parentName = self.descriptor.getParentName()
if not parentName:
parentName = "Reflector"
args = {
"domClass": DOMClass(self.descriptor),
"enumerateHook": "None",
"finalizeHook": f"{FINALIZE_HOOK_NAME}::<D>",
"flags": "JSCLASS_FOREGROUND_FINALIZE",
"name": str_to_cstr_ptr(self.descriptor.interface.identifier.name),
"resolveHook": "None",
"mayResolveHook": "None",
"slots": "1",
"traceHook": f"{TRACE_HOOK_NAME}::<D>",
}
if self.descriptor.isGlobal():
assert not self.descriptor.weakReferenceable
args["flags"] = "JSCLASS_IS_GLOBAL | JSCLASS_DOM_GLOBAL | JSCLASS_FOREGROUND_FINALIZE"
args["slots"] = "JSCLASS_GLOBAL_SLOT_COUNT + 1"
if self.descriptor.interface.getExtendedAttribute("NeedResolve"):
args["enumerateHook"] = "Some(enumerate_window::<D>)"
args["resolveHook"] = "Some(resolve_window::<D>)"
args["mayResolveHook"] = "Some(may_resolve_window::<D>)"
else:
args["enumerateHook"] = "Some(enumerate_global)"
args["resolveHook"] = "Some(resolve_global)"
args["mayResolveHook"] = "Some(may_resolve_global)"
args["traceHook"] = "js::jsapi::JS_GlobalObjectTraceHook"
elif self.descriptor.weakReferenceable:
args["slots"] = "2"
return f"""
static CLASS_OPS: ThreadUnsafeOnceLock<JSClassOps> = ThreadUnsafeOnceLock::new();
pub(crate) fn init_class_ops<D: DomTypes>() {{
CLASS_OPS.set(JSClassOps {{
addProperty: None,
delProperty: None,
enumerate: None,
newEnumerate: {args['enumerateHook']},
resolve: {args['resolveHook']},
mayResolve: {args['mayResolveHook']},
finalize: Some({args['finalizeHook']}),
call: None,
construct: None,
trace: Some({args['traceHook']}),
}});
}}
pub static Class: ThreadUnsafeOnceLock<DOMJSClass> = ThreadUnsafeOnceLock::new();
pub(crate) fn init_domjs_class<D: DomTypes>() {{
init_class_ops::<D>();
Class.set(DOMJSClass {{
base: JSClass {{
name: {args['name']},
flags: JSCLASS_IS_DOMJSCLASS | {args['flags']} |
((({args['slots']}) & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT)
/* JSCLASS_HAS_RESERVED_SLOTS({args['slots']}) */,
cOps: unsafe {{ CLASS_OPS.get() }},
spec: ptr::null(),
ext: ptr::null(),
oOps: ptr::null(),
}},
dom_class: {args['domClass']},
}});
}}
"""
class CGAssertInheritance(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
def define(self) -> str:
parent = self.descriptor.interface.parent
parentName = ""
if parent:
parentName = parent.identifier.name
else:
parentName = "Reflector<_>"
selfName = self.descriptor.interface.identifier.name
if selfName == "OffscreenCanvasRenderingContext2D":
parentName = "crate::dom::canvasrenderingcontext2d::CanvasRenderingContext2D"
if selfName == "WebGL2RenderingContext":
parentName = "crate::dom::webgl::webglrenderingcontext::WebGLRenderingContext"
args = {
"parentName": parentName,
"selfName": selfName,
}
return f"""
impl {args['selfName']} {{
fn __assert_parent_type(&self) {{
use crate::dom::bindings::inheritance::HasParent;
// If this type assertion fails, make sure the first field of your
// DOM struct is of the correct type -- it must be the parent class.
let _: &{args['parentName']} = self.as_parent();
}}
}}
"""
def str_to_cstr(s: str) -> str:
return f'c"{s}"'
def str_to_cstr_ptr(s: str) -> str:
return f'c"{s}".as_ptr()'
class CGPrototypeJSClass(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
def define(self) -> str:
name = str_to_cstr_ptr(f"{self.descriptor.interface.identifier.name}Prototype")
slotCount = 0
if self.descriptor.hasLegacyUnforgeableMembers:
slotCount += 1
slotCountStr = f"{slotCount} & JSCLASS_RESERVED_SLOTS_MASK" if slotCount > 0 else "0"
return f"""
static PrototypeClass: JSClass = JSClass {{
name: {name},
flags:
// JSCLASS_HAS_RESERVED_SLOTS()
({slotCountStr} ) << JSCLASS_RESERVED_SLOTS_SHIFT,
cOps: ptr::null(),
spec: ptr::null(),
ext: ptr::null(),
oOps: ptr::null(),
}};
"""
class CGInterfaceObjectJSClass(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
assert descriptor.interface.hasInterfaceObject() and not descriptor.interface.isCallback()
CGThing.__init__(self)
self.descriptor = descriptor
def define(self) -> str:
if self.descriptor.interface.isNamespace():
classString = self.descriptor.interface.getExtendedAttribute("ClassString")
if classString:
classString = classString[0]
else:
classString = "Object"
return f"""
static NAMESPACE_OBJECT_CLASS: NamespaceObjectClass = unsafe {{
NamespaceObjectClass::new({str_to_cstr(classString)})
}};
"""
if self.descriptor.interface.ctor():
constructorBehavior = f"InterfaceConstructorBehavior::call({CONSTRUCT_HOOK_NAME}::<D>)"
else:
constructorBehavior = "InterfaceConstructorBehavior::throw()"
name = self.descriptor.interface.identifier.name
representation = f'b"function {name}() {{\\n [native code]\\n}}"'
return f"""
static INTERFACE_OBJECT_CLASS: ThreadUnsafeOnceLock<NonCallbackInterfaceObjectClass> = ThreadUnsafeOnceLock::new();
pub(crate) fn init_interface_object<D: DomTypes>() {{
INTERFACE_OBJECT_CLASS.set(NonCallbackInterfaceObjectClass::new(
Box::leak(Box::new({constructorBehavior})),
{representation},
PrototypeList::ID::{name},
{self.descriptor.prototypeDepth},
));
}}
"""
class CGList(CGThing):
def __init__(self, children: Iterable[CGThing | None], joiner: str = "") -> None:
CGThing.__init__(self)
self.children = list(children)
self.joiner = joiner
def append(self, child: CGThing | None) -> None:
self.children.append(child)
def prepend(self, child: CGThing | None) -> None:
self.children.insert(0, child)
def join(self, iterable: Iterable[str]) -> str:
return self.joiner.join(s for s in iterable if len(s) > 0)
def define(self) -> str:
return self.join(child.define() for child in self.children if child is not None)
def __len__(self) -> int:
return len(self.children)
class CGIfElseWrapper(CGList):
def __init__(self, condition: str, ifTrue: CGGeneric | CGWrapper, ifFalse: CGGeneric) -> None:
if ifFalse.text.strip().startswith("if"):
elseBranch = CGWrapper(ifFalse, pre=" else ")
else:
elseBranch = CGWrapper(CGIndenter(ifFalse), pre=" else {\n", post="\n}")
kids = [CGIfWrapper(condition, ifTrue), elseBranch]
CGList.__init__(self, kids)
class CGGeneric(CGThing):
text: str
def __init__(self, text: str) -> None:
self.text = text
def define(self) -> str:
return self.text
class CGCallbackTempRoot(CGGeneric):
def __init__(self, name: str) -> None:
CGGeneric.__init__(self, f"{name.replace('<D>', '::<D>')}::new(SafeJSContext::from_ptr(cx.raw_cx()), ${{val}}.get().to_object())")
def getAllTypes(
descriptors: list[Descriptor],
dictionaries: list[IDLDictionary],
callbacks: list[IDLCallback],
typedefs: list[IDLTypedef]
) -> Generator[tuple[IDLType, Optional[Descriptor]], None, None]:
for d in descriptors:
for t in getTypesFromDescriptor(d):
if t.isRecord():
yield (t.inner, d)
yield (t, d)
for dictionary in dictionaries:
for t in getTypesFromDictionary(dictionary):
if t.isRecord():
yield (t.inner, None)
yield (t, None)
for callback in callbacks:
for t in getTypesFromCallback(callback):
if t.isRecord():
yield (t.inner, None)
yield (t, None)
for typedef in typedefs:
if typedef.innerType.isRecord():
assert isinstance(typedef.innerType, IDLRecordType)
yield (typedef.innerType.inner, None)
yield (typedef.innerType, None)
def UnionTypes(
descriptors: list[Descriptor],
dictionaries: list[IDLDictionary],
callbacks: list[IDLCallback],
typedefs: list[IDLTypedef],
config: Configuration
) -> CGThing:
imports = [
'crate::import::base::*',
'crate::codegen::DomTypes::DomTypes',
'crate::conversions::windowproxy_from_handlevalue',
'crate::record::Record',
'js::typedarray',
]
unionStructs: dict[str, Any] = dict()
for (t, descriptor) in getAllTypes(descriptors, dictionaries, callbacks, typedefs):
t = t.unroll()
if not t.isUnion():
continue
assert isinstance(t, IDLUnionType) and t.flatMemberTypes is not None
for memberType in t.flatMemberTypes:
if memberType.isDictionary() or memberType.isEnum() or memberType.isCallback():
memberModule = getModuleFromObject(memberType)
memberName = (memberType.callback.identifier.name
if memberType.isCallback() else memberType.inner.identifier.name)
imports.append(f"{memberModule}::{memberName}")
if memberType.isEnum():
imports.append(f"{memberModule}::{memberName}Values")
name = str(t)
if name not in unionStructs:
provider = descriptor or config.getDescriptorProvider()
unionStructs[name] = CGList([
CGUnionStruct(t, provider, config),
CGUnionConversionStruct(t, provider)
])
unionStructsCG = (i[1] for i in sorted(list(unionStructs.items()), key=operator.itemgetter(0)))
return CGImports(CGList(unionStructsCG, "\n\n"), descriptors=[], callbacks=[], dictionaries=[], enums=[],
typedefs=[], imports=imports, config=config)
def DomTypes(descriptors: list[Descriptor],
descriptorProvider: DescriptorProvider,
dictionaries: list[IDLDictionary],
callbacks: list[IDLCallback],
typedefs: list[IDLTypedef],
config: Configuration
) -> CGThing:
traits = [
"crate::interfaces::DomHelpers<Self>",
"js::rust::Trace",
"malloc_size_of::MallocSizeOf",
"Sized",
]
joinedTraits = ' + '.join(traits)
elements = [CGGeneric(f"pub trait DomTypes: {joinedTraits} where Self: 'static {{\n")]
def fixupInterfaceTypeReferences(typename: str) -> str:
return typename.replace("D::", "Self::")
for descriptor in descriptors:
iface_name = descriptor.interface.identifier.name
traits = []
traits += descriptor.additionalTraits
chain = descriptor.prototypeChain
upcast = descriptor.hasDescendants()
if not upcast:
chain = chain[:-1]
if chain:
traits += ["crate::inheritance::Castable"]
for parent in chain:
traits += [f"crate::conversions::DerivedFrom<Self::{parent}>"]
for marker in ["Serializable", "Transferable"]:
if descriptor.interface.getExtendedAttribute(marker):
traits += [f"crate::structuredclone::MarkedAs{marker}InIdl"]
iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
if iterableDecl:
if iterableDecl.isMaplike():
keytype = fixupInterfaceTypeReferences(
getRetvalDeclarationForType(iterableDecl.keyType, descriptor).define()
)
valuetype = fixupInterfaceTypeReferences(
getRetvalDeclarationForType(iterableDecl.valueType, descriptor).define()
)
traits += [f"crate::like::Maplike<Key={keytype}, Value={valuetype}>"]
if iterableDecl.isSetlike():
keytype = fixupInterfaceTypeReferences(
getRetvalDeclarationForType(iterableDecl.keyType, descriptor).define()
)
traits += [f"crate::like::Setlike<Key={keytype}>"]
if iterableDecl.hasKeyType():
traits += [
"crate::reflector::DomObjectIteratorWrap<Self>",
"crate::iterable::IteratorDerives",
]
if descriptor.weakReferenceable:
traits += ["crate::weakref::WeakReferenceable"]
if not descriptor.interface.isNamespace():
traits += [
"js::conversions::ToJSValConvertible",
"crate::reflector::MutDomObject",
"crate::reflector::DomObject",
"crate::reflector::DomGlobalGeneric<Self>",
"malloc_size_of::MallocSizeOf",
]
if descriptor.register:
if (
(descriptor.concrete or descriptor.hasDescendants())
and not descriptor.interface.isNamespace()
and not descriptor.interface.isIteratorInterface()
):
traits += [
"crate::conversions::IDLInterface",
"PartialEq",
]
if descriptor.concrete and not descriptor.isGlobal():
traits += ["crate::reflector::DomObjectWrap<Self>"]
if not descriptor.interface.isCallback() and not descriptor.interface.isIteratorInterface():
nonConstMembers = [m for m in descriptor.interface.members if not m.isConst()]
ctor = descriptor.interface.ctor()
if (
nonConstMembers
or (ctor and not ctor.isHTMLConstructor())
or descriptor.interface.legacyFactoryFunctions
):
namespace = f"{toBindingPath(descriptor)}"
traits += [f"crate::codegen::GenericBindings::{namespace}::{iface_name}Methods<Self>"]
isPromise = firstCap(iface_name) == "Promise"
elements += [
CGGeneric(" #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]\n"),
CGGeneric(
" #[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]\n"
if isPromise else ""
),
CGGeneric(f" type {firstCap(iface_name)}: {' + '.join(traits)};\n")
]
elements += [CGGeneric("}\n")]
imports = [
CGGeneric("use crate::root::DomRoot;\n"),
CGGeneric("use crate::domstring::DOMString;\n"),
]
return CGList(imports + elements)
def DomTypeHolder(descriptors: list[Descriptor],
descriptorProvider: DescriptorProvider,
dictionaries: list[IDLDictionary],
callbacks: list[IDLCallback],
typedefs: list[IDLTypedef],
config: Configuration
) -> CGThing:
elements = [
CGGeneric(
"#[derive(JSTraceable, MallocSizeOf, PartialEq)]\n"
"pub(crate) struct DomTypeHolder;\n"
"impl crate::DomTypes for DomTypeHolder {\n"
),
]
for descriptor in descriptors:
if descriptor.interface.isCallback() or descriptor.interface.isIteratorInterface():
continue
iface_name = descriptor.interface.identifier.name
path = f"crate::dom::{iface_name.lower()}::{firstCap(iface_name)}"
elements.append(CGGeneric(f" type {firstCap(iface_name)} = {path};\n"))
elements.append(CGGeneric("}\n"))
return CGList(elements)
class Argument():
def __init__(self, argType: str | None, name: str, default: str | None = None, mutable: bool = False) -> None:
self.argType = argType
self.name = name
self.default = default
self.mutable = mutable
def declare(self) -> str:
mut = 'mut ' if self.mutable else ''
argType = f': {self.argType}' if self.argType else ''
string = f"{mut}{self.name}{argType}"
return string
def define(self) -> str:
return f'{self.argType} {self.name}'
class CGAbstractMethod(CGThing):
def __init__(self, descriptor: Descriptor, name: str, returnType: str, args: list[Argument], inline: bool = False,
alwaysInline: bool = False, extern: bool = False, unsafe: bool = False, pub: bool = False,
templateArgs: list[str] | None = None, docs: str | None = None, doesNotPanic: bool = False,
extra_decorators: list[str] = []) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
self.name = CGDictionary.makeMemberName(name)
self.returnType = returnType
self.args = args
self.alwaysInline = alwaysInline
self.extern = extern
self.unsafe = extern or unsafe
self.templateArgs = templateArgs
self.pub = pub
self.docs = docs
self.catchPanic = self.extern and not doesNotPanic
self.extra_decorators = extra_decorators
def _argstring(self) -> str:
return ', '.join([a.declare() for a in self.args])
def _template(self) -> str:
if self.templateArgs is None:
return ''
return f'<{", ".join(self.templateArgs)}>\n'
def _docs(self) -> str:
if self.docs is None:
return ''
lines = self.docs.splitlines()
return ''.join(f'/// {line}\n' for line in lines)
def _decorators(self) -> str:
decorators = []
if self.alwaysInline:
decorators.append('#[inline]')
decorators.extend(self.extra_decorators)
if self.pub:
decorators.append('pub')
if self.unsafe:
decorators.append('unsafe')
if self.extern:
decorators.append('extern "C"')
if not decorators:
return ''
return f'{" ".join(decorators)} '
def _returnType(self) -> str:
return f" -> {self.returnType}" if self.returnType != "void" else ""
def define(self) -> str:
body = self.definition_body()
if self.catchPanic:
if self.returnType == "void":
pre = "wrap_panic(&mut || {\n"
post = "\n})"
elif "return" not in body.define() or self.name.startswith("_constructor"):
pre = (
"let mut result = false;\n"
"wrap_panic(&mut || result = {\n"
)
post = (
"\n});\n"
"result"
)
else:
pre = (
"let mut result = false;\n"
"wrap_panic(&mut || result = (|| {\n"
)
post = (
"\n})());\n"
"result"
)
body = CGWrapper(CGIndenter(body), pre=pre, post=post)
return CGWrapper(CGIndenter(body),
pre=self.definition_prologue(),
post=self.definition_epilogue()).define()
def definition_prologue(self) -> str:
return (f"{self._docs()}{self._decorators()}"
f"fn {self.name}{self._template()}({self._argstring()}){self._returnType()}{{\n")
def definition_epilogue(self) -> str:
return "\n}\n"
def definition_body(self) -> CGThing:
raise NotImplementedError
class CGConstructorEnabled(CGAbstractMethod):
def __init__(self, descriptor: Descriptor) -> None:
CGAbstractMethod.__init__(self, descriptor,
'ConstructorEnabled', 'bool',
[Argument("&mut JSContext", "cx"),
Argument("HandleObject", "aObj")],
templateArgs=['D: DomTypes'],
pub=True)
def definition_body(self) -> CGThing:
conditions = []
iface = self.descriptor.interface
bits = " | ".join(sorted(
f"Globals::{camel_to_upper_snake(i)}" for i in iface.exposureSet
))
conditions.append(f"is_exposed_in(aObj, {bits})")
pref = iface.getExtendedAttribute("Pref")
if pref:
assert isinstance(pref, list) and len(pref) == 1
conditions.append(f'pref!({pref[0]})')
func = iface.getExtendedAttribute("Func")
if func:
assert isinstance(func, list) and len(func) == 1
conditions.append(f"D::{func[0]}(cx.into(), aObj)")
secure = iface.getExtendedAttribute("SecureContext")
if secure:
conditions.append("""
{
let realm = CurrentRealm::assert(cx);
D::GlobalScope::from_current_realm(&realm).is_secure_context()
}
""")
return CGList((CGGeneric(cond) for cond in conditions), " &&\n")
def InitLegacyUnforgeablePropertiesOnHolder(descriptor: Descriptor, properties: PropertyArrays) -> CGThing:
unforgeables = []
defineLegacyUnforgeableAttrs = "define_guarded_properties::<D>(cx.into(), unforgeable_holder.handle(), %s, global);"
defineLegacyUnforgeableMethods = "define_guarded_methods::<D>(cx.into(), unforgeable_holder.handle(), %s, global);"
unforgeableMembers = [
(defineLegacyUnforgeableAttrs, properties.unforgeable_attrs),
(defineLegacyUnforgeableMethods, properties.unforgeable_methods),
]
for template, array in unforgeableMembers:
if array.length() > 0:
unforgeables.append(CGGeneric(template % f"unsafe {{ {array.variableName()}.get() }}"))
return CGList(unforgeables, "\n")
def CopyLegacyUnforgeablePropertiesToInstance(descriptor: Descriptor) -> str:
if not descriptor.hasLegacyUnforgeableMembers:
return ""
copyCode = ""
if descriptor.proxy:
copyCode += """\
rooted!(&in(cx) let mut expando = ptr::null_mut::<JSObject>());
ensure_expando_object(cx.raw_cx(), obj.handle().into(), expando.handle_mut());
"""
obj = "expando"
else:
obj = "obj"
if descriptor.isGlobal():
copyFunc = "JS_CopyOwnPropertiesAndPrivateFields"
else:
copyFunc = "JS_InitializePropertiesFromCompatibleNativeObject"
copyCode += f"""
let mut slot = UndefinedValue();
JS_GetReservedSlot(canonical_proto.get(), DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, &mut slot);
rooted!(&in(cx) let mut unforgeable_holder = ptr::null_mut::<JSObject>());
unforgeable_holder.handle_mut().set(slot.to_object());
assert!({copyFunc}(cx.raw_cx(), {obj}.handle(), unforgeable_holder.handle()));
"""
return copyCode
class CGWrapMethod(CGAbstractMethod):
def __init__(self, descriptor: Descriptor) -> None:
assert not descriptor.interface.isCallback()
assert not descriptor.isGlobal()
args = [Argument('&mut JSContext', 'cx'),
Argument('&D::GlobalScope', 'scope'),
Argument('Option<HandleObject>', 'given_proto'),
Argument(f"Box<{descriptor.concreteType}>", 'object')]
retval = f'DomRoot<{descriptor.concreteType}>'
CGAbstractMethod.__init__(self, descriptor, 'Wrap', retval, args,
pub=True, unsafe=True,
extra_decorators=['#[cfg_attr(crown, allow(crown::unrooted_must_root))]'],
templateArgs=['D: DomTypes'])
def definition_body(self) -> CGThing:
def python_bool_to_rust(b: bool) -> str:
if b:
return "true"
else:
return "false"
is_proxy = python_bool_to_rust(self.descriptor.proxy)
cross_origin = python_bool_to_rust(self.descriptor.proxy and self.descriptor.isMaybeCrossOriginObject())
weak_referenceable = python_bool_to_rust(self.descriptor.weakReferenceable)
if self.descriptor.proxy:
proxy_handler = f"Some(RegisterBindings::proxy_handlers::{self.descriptor.interface.identifier.name}.load(std::sync::atomic::Ordering::Acquire))"
else:
proxy_handler = "None"
prototype_id = f"PrototypeList::ID::{self.descriptor.name}"
proto_object_fn = "GetProtoObject::<D>"
if self.descriptor.proxy:
c = "None"
else:
c = "Some(&Class.get().base)"
return CGGeneric(f"""
let init = crate::wrap::WrapConfig {{
is_maybe_cross_origin_object: {cross_origin},
is_proxy: {is_proxy},
weak_referenceable: {weak_referenceable},
proxy_handler: {proxy_handler},
prototype_id: {prototype_id},
class: {c},
proto_object_fn: {proto_object_fn},
is_global: {python_bool_to_rust(self.descriptor.isGlobal())},
has_legacy_unforgeable_members: {python_bool_to_rust(self.descriptor.hasLegacyUnforgeableMembers)},
}};
crate::wrap::wrap::<_, D>(cx, scope, given_proto, object, init)
""")
class CGWrapGlobalMethod(CGAbstractMethod):
def __init__(self, descriptor: Descriptor, properties: PropertyArrays) -> None:
assert not descriptor.interface.isCallback()
assert descriptor.isGlobal()
args = [Argument('&mut JSContext', 'cx'),
Argument(f"Box<{descriptor.concreteType}>", 'object')]
retval = f'DomRoot<{descriptor.concreteType}>'
CGAbstractMethod.__init__(self, descriptor, 'Wrap', retval, args,
pub=True,
extra_decorators=['#[cfg_attr(crown, allow(crown::unrooted_must_root))]'],
templateArgs=['D: DomTypes'])
self.properties = properties
def definition_body(self) -> CGThing:
pairs = [
("define_guarded_properties", self.properties.attrs),
("define_guarded_methods", self.properties.methods),
("define_guarded_constants", self.properties.consts)
]
members = [f"{function}::<D>(cx.into(), obj.handle(), {array.variableName()}.get(), obj.handle());"
for (function, array) in pairs if array.length() > 0]
membersStr = "\n".join(members)
name = self.descriptor.name
return CGGeneric(f"""
unsafe {{
let raw = Root::new(MaybeUnreflectedDom::from_box(object));
let origin = (*raw.as_ptr()).upcast::<D::GlobalScope>().origin();
rooted!(&in(cx) let mut obj = ptr::null_mut::<JSObject>());
create_global_object::<D>(
cx.into(),
&Class.get().base,
raw.as_ptr() as *const libc::c_void,
{TRACE_HOOK_NAME}::<D>,
obj.handle_mut(),
origin,
{"true" if self.descriptor.useSystemCompartment else "false"});
assert!(!obj.is_null());
let root = raw.reflect_with(obj.get());
root.reflector().set_proto_id(PrototypeList::ID::{name} as u16);
let _ac = JSAutoRealm::new(cx.raw_cx(), obj.get());
rooted!(&in(cx) let mut canonical_proto = ptr::null_mut::<JSObject>());
GetProtoObject::<D>(cx, obj.handle(), canonical_proto.handle_mut());
assert!(JS_SetPrototype(cx.raw_cx(), obj.handle(), canonical_proto.handle()));
let mut immutable = false;
assert!(JS_SetImmutablePrototype(cx.raw_cx(), obj.handle(), &mut immutable));
assert!(immutable);
{membersStr}
{CopyLegacyUnforgeablePropertiesToInstance(self.descriptor)}
DomRoot::from_ref(&*root)
}}
""")
def toBindingPath(descriptor: Descriptor) -> str:
module = toBindingModuleFileFromDescriptor(descriptor)
namespace = toBindingNamespace(descriptor.interface.identifier.name)
return f"{module}::{namespace}"
class CGIDLInterface(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
def define(self) -> str:
interface = self.descriptor.interface
name = interface.identifier.name
bindingModule = f"crate::dom::bindings::codegen::GenericBindings::{toBindingPath(self.descriptor)}"
if (interface.getUserData("hasConcreteDescendant", False)
or interface.getUserData("hasProxyDescendant", False)):
depth = self.descriptor.prototypeDepth
check = f"class.interface_chain[{depth}] == PrototypeList::ID::{name}"
elif self.descriptor.proxy:
check = f"ptr::eq(class, unsafe {{ {bindingModule}::Class.get() }})"
else:
check = f"ptr::eq(class, unsafe {{ &{bindingModule}::Class.get().dom_class }})"
proto_first, proto_last = _proto_ranges.get(name, (0, 65535))
return f"""
impl IDLInterface for {name} {{
#[inline]
fn derives(class: &'static DOMClass) -> bool {{
{check}
}}
const PROTO_FIRST: u16 = {proto_first};
const PROTO_LAST: u16 = {proto_last};
}}
"""
class CGIteratorDerives(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
def define(self) -> str:
iterableInterface = self.descriptor.interface.iterableInterface
assert iterableInterface is not None
name = iterableInterface.identifier.name
bindingModule = f"crate::dom::bindings::codegen::Bindings::{toBindingPath(self.descriptor)}"
return f"""
impl crate::dom::bindings::iterable::IteratorDerives for {name} {{
#[inline]
fn derives(class: &'static DOMClass) -> bool {{
unsafe {{ ptr::eq(class, &{bindingModule}::Class.get().dom_class) }}
}}
}}
"""
class CGDomObjectWrap(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
def define(self) -> str:
ifaceName = self.descriptor.interface.identifier.name
bindingModule = f"crate::dom::bindings::codegen::GenericBindings::{toBindingPath(self.descriptor)}"
return f"""
impl DomObjectWrap<crate::DomTypeHolder> for {firstCap(ifaceName)} {{
const WRAP: unsafe fn(
&mut JSContext,
&GlobalScope,
Option<HandleObject>,
Box<Self>,
) -> Root<Dom<Self>> = {bindingModule}::Wrap::<crate::DomTypeHolder>;
}}
"""
class CGDomObjectIteratorWrap(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
def define(self) -> str:
assert self.descriptor.interface.isIteratorInterface()
iterableInterface = self.descriptor.interface.iterableInterface
assert iterableInterface is not None
name = iterableInterface.identifier.name
bindingModule = f"crate::dom::bindings::codegen::GenericBindings::{toBindingPath(self.descriptor)}"
return f"""
impl DomObjectIteratorWrap<crate::DomTypeHolder> for {name} {{
const ITER_WRAP: unsafe fn(
&mut JSContext,
&GlobalScope,
Option<HandleObject>,
Box<IterableIterator<crate::DomTypeHolder, Self>>,
) -> Root<Dom<IterableIterator<crate::DomTypeHolder, Self>>> = {bindingModule}::Wrap::<crate::DomTypeHolder>;
}}
"""
class CGAbstractExternMethod(CGAbstractMethod):
def __init__(self,
descriptor: Descriptor,
name: str,
returnType: str,
args: list[Argument],
doesNotPanic: bool = False,
templateArgs: list[str] | None = None
) -> None:
CGAbstractMethod.__init__(self, descriptor, name, returnType, args,
inline=False, extern=True, doesNotPanic=doesNotPanic, templateArgs=templateArgs)
class PropertyArrays():
def __init__(self, descriptor: Descriptor) -> None:
self.static_methods = MethodDefiner(descriptor, "StaticMethods",
static=True, unforgeable=False)
self.static_attrs = AttrDefiner(descriptor, "StaticAttributes",
static=True, unforgeable=False)
self.methods = MethodDefiner(descriptor, "Methods", static=False, unforgeable=False)
self.unforgeable_methods = MethodDefiner(descriptor, "LegacyUnforgeableMethods",
static=False, unforgeable=True)
self.attrs = AttrDefiner(descriptor, "Attributes", static=False, unforgeable=False)
self.unforgeable_attrs = AttrDefiner(descriptor, "LegacyUnforgeableAttributes",
static=False, unforgeable=True)
self.consts = ConstDefiner(descriptor, "Constants")
pass
@staticmethod
def arrayNames() -> list[str]:
return [
"static_methods",
"static_attrs",
"methods",
"unforgeable_methods",
"attrs",
"unforgeable_attrs",
"consts",
]
def variableNames(self) -> dict[str, PropertyDefiner]:
names = {}
for array in self.arrayNames():
names[array] = getattr(self, array).variableName()
return names
def __str__(self) -> str:
define = ""
for array in self.arrayNames():
define += str(getattr(self, array))
return define
class CGCrossOriginProperties(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
self.methods = MethodDefiner(descriptor, "CrossOriginMethods", static=False,
unforgeable=False, crossorigin=True)
self.attributes = AttrDefiner(descriptor, "CrossOriginAttributes", static=False,
unforgeable=False, crossorigin=True)
def define(self) -> str:
return f"{self.methods}{self.attributes}" + dedent(
"""
static CROSS_ORIGIN_PROPERTIES: ThreadUnsafeOnceLock<CrossOriginProperties> = ThreadUnsafeOnceLock::new();
pub(crate) fn init_cross_origin_properties<D: DomTypes>() {
CROSS_ORIGIN_PROPERTIES.set(CrossOriginProperties {
attributes: unsafe { sCrossOriginAttributes.get() },
methods: unsafe { sCrossOriginMethods.get() },
});
}
"""
)
class CGCollectJSONAttributesMethod(CGAbstractMethod):
def __init__(self, descriptor: Descriptor, toJSONMethod: IDLType | None) -> None:
args = [Argument('&mut JSContext', 'cx'),
Argument('RawHandleObject', 'obj'),
Argument('*mut libc::c_void', 'this'),
Argument('HandleObject', 'result')]
CGAbstractMethod.__init__(self, descriptor, 'CollectJSONAttributes',
'bool', args, pub=True, unsafe=True, templateArgs=["D: DomTypes"])
self.toJSONMethod = toJSONMethod
def definition_body(self) -> CGThing:
ret = """let incumbent_global = D::GlobalScope::incumbent().expect("no incumbent global");
let global = incumbent_global.reflector().get_jsobject();\n"""
interface = self.descriptor.interface
for m in interface.members:
if m.isAttr() and not m.isStatic() and m.type.isJSONType():
name = m.identifier.name
conditions = MemberCondition(None, None, m.exposureSet, None)
ret_conditions = f'&[{", ".join(conditions)}]'
ret += fill(
"""
let conditions = ${conditions};
let is_satisfied = conditions.iter().any(|c|
c.is_satisfied::<D>(
cx.into(),
HandleObject::from_raw(obj),
global));
if is_satisfied {
rooted!(&in(cx) let mut temp = UndefinedValue());
if !get_${name}::<D>(cx.raw_cx(), obj, this, JSJitGetterCallArgs { _base: temp.handle_mut().into() }) {
return false;
}
if !JS_DefineProperty(cx.raw_cx(), result,
${nameAsArray},
temp.handle(), JSPROP_ENUMERATE as u32) {
return false;
}
}
""",
name=name, nameAsArray=str_to_cstr_ptr(name), conditions=ret_conditions)
ret += 'true\n'
return CGGeneric(ret)
class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
def __init__(self, descriptor: Descriptor, properties: PropertyArrays, haveUnscopables: bool, haveLegacyWindowAliases: bool) -> None:
args = [Argument('&mut JSContext', 'cx'), Argument('HandleObject', 'global'),
Argument('*mut ProtoOrIfaceArray', 'cache')]
CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args,
unsafe=True, templateArgs=['D: DomTypes'])
self.properties = properties
self.haveUnscopables = haveUnscopables
self.haveLegacyWindowAliases = haveLegacyWindowAliases
def definition_body(self) -> CGThing:
name = self.descriptor.interface.identifier.name
if self.descriptor.interface.isNamespace():
if self.descriptor.interface.getExtendedAttribute("ProtoObjectHack"):
proto = "GetRealmObjectPrototype(cx.raw_cx())"
else:
proto = "JS_NewPlainObject(cx.raw_cx())"
if self.properties.static_methods.length():
methods = f"{self.properties.static_methods.variableName()}.get()"
else:
methods = "&[]"
if self.descriptor.interface.hasConstants():
constants = "sConstants.get()"
else:
constants = "&[]"
id = MakeNativeName(name)
return CGGeneric(f"""
rooted!(&in(cx) let proto = {proto});
assert!(!proto.is_null());
rooted!(&in(cx) let mut namespace = ptr::null_mut::<JSObject>());
create_namespace_object::<D>(cx.into(), global, proto.handle(), &NAMESPACE_OBJECT_CLASS,
{methods}, {constants}, {str_to_cstr(name)}, namespace.handle_mut());
assert!(!namespace.is_null());
assert!((*cache)[PrototypeList::Constructor::{id} as usize].is_null());
(*cache)[PrototypeList::Constructor::{id} as usize] = namespace.get();
<*mut JSObject>::post_barrier((*cache).as_mut_ptr().offset(PrototypeList::Constructor::{id} as isize),
ptr::null_mut(),
namespace.get());
""")
if self.descriptor.interface.isCallback():
assert not self.descriptor.interface.ctor() and self.descriptor.interface.hasConstants()
cName = str_to_cstr(name)
return CGGeneric(f"""
rooted!(&in(cx) let mut interface = ptr::null_mut::<JSObject>());
create_callback_interface_object::<D>(cx.into(), global, sConstants.get(), {cName}, interface.handle_mut());
assert!(!interface.is_null());
assert!((*cache)[PrototypeList::Constructor::{name} as usize].is_null());
(*cache)[PrototypeList::Constructor::{name} as usize] = interface.get();
<*mut JSObject>::post_barrier((*cache).as_mut_ptr().offset(PrototypeList::Constructor::{name} as isize),
ptr::null_mut(),
interface.get());
""")
parentName = self.descriptor.getParentName()
if not parentName:
if self.descriptor.interface.getExtendedAttribute("ExceptionClass"):
protoGetter = "GetRealmErrorPrototype"
elif self.descriptor.interface.isIteratorInterface():
protoGetter = "GetRealmIteratorPrototype"
else:
protoGetter = "GetRealmObjectPrototype"
getPrototypeProto = f"prototype_proto.set({protoGetter}(cx.raw_cx()))"
else:
getPrototypeProto = (
f"{toBindingNamespace(parentName)}::GetProtoObject::<D>(cx, global, prototype_proto.handle_mut())"
)
code: list = [CGGeneric(f"""
rooted!(&in(cx) let mut prototype_proto = ptr::null_mut::<JSObject>());
{getPrototypeProto};
assert!(!prototype_proto.is_null());""")]
if self.descriptor.hasNamedPropertiesObject():
assert not self.haveUnscopables
code.append(CGGeneric(f"""
rooted!(&in(cx) let mut prototype_proto_proto = prototype_proto.get());
D::{name}::create_named_properties_object(cx.into(), prototype_proto_proto.handle(), prototype_proto.handle_mut());
assert!(!prototype_proto.is_null());"""))
properties = {
"id": name,
"unscopables": "unscopable_names" if self.haveUnscopables else "&[]",
"legacyWindowAliases": "legacy_window_aliases" if self.haveLegacyWindowAliases else "&[]"
}
for arrayName in self.properties.arrayNames():
array = getattr(self.properties, arrayName)
if array.length():
properties[arrayName] = f"{array.variableName()}.get()"
else:
properties[arrayName] = "&[]"
if self.descriptor.isGlobal():
assert not self.haveUnscopables
proto_properties = {
"attrs": "&[]",
"consts": "&[]",
"id": name,
"methods": "&[]",
"unscopables": "&[]",
}
else:
proto_properties = properties
code.append(CGGeneric(f"""
rooted!(&in(cx) let mut prototype = ptr::null_mut::<JSObject>());
create_interface_prototype_object::<D>(cx.into(),
global,
prototype_proto.handle(),
&PrototypeClass,
{proto_properties['methods']},
{proto_properties['attrs']},
{proto_properties['consts']},
{proto_properties['unscopables']},
prototype.handle_mut());
assert!(!prototype.is_null());
assert!((*cache)[PrototypeList::ID::{proto_properties['id']} as usize].is_null());
(*cache)[PrototypeList::ID::{proto_properties['id']} as usize] = prototype.get();
<*mut JSObject>::post_barrier((*cache).as_mut_ptr().offset(PrototypeList::ID::{proto_properties['id']} as isize),
ptr::null_mut(),
prototype.get());
"""))
if self.descriptor.interface.hasInterfaceObject():
properties["name"] = str_to_cstr(name)
ctor = self.descriptor.interface.ctor()
if ctor:
properties["length"] = methodLength(ctor)
else:
properties["length"] = 0
parentName = self.descriptor.getParentName()
code.append(CGGeneric("rooted!(&in(cx) let mut interface_proto = ptr::null_mut::<JSObject>());"))
if parentName:
parentName = toBindingNamespace(parentName)
code.append(CGGeneric(f"""
{parentName}::GetConstructorObject::<D>(cx, global, interface_proto.handle_mut());"""))
else:
code.append(CGGeneric("interface_proto.set(GetRealmFunctionPrototype(cx.raw_cx()));"))
code.append(CGGeneric(f"""
assert!(!interface_proto.is_null());
rooted!(&in(cx) let mut interface = ptr::null_mut::<JSObject>());
create_noncallback_interface_object::<D>(cx.into(),
global,
interface_proto.handle(),
INTERFACE_OBJECT_CLASS.get(),
{properties['static_methods']},
{properties['static_attrs']},
{properties['consts']},
prototype.handle(),
{properties['name']},
{properties['length']},
{properties['legacyWindowAliases']},
interface.handle_mut());
assert!(!interface.is_null());"""))
if self.descriptor.shouldCacheConstructor():
code.append(CGGeneric(f"""
assert!((*cache)[PrototypeList::Constructor::{properties['id']} as usize].is_null());
(*cache)[PrototypeList::Constructor::{properties['id']} as usize] = interface.get();
<*mut JSObject>::post_barrier((*cache).as_mut_ptr().offset(PrototypeList::Constructor::{properties['id']} as isize),
ptr::null_mut(),
interface.get());
"""))
aliasedMembers = [m for m in self.descriptor.interface.members if m.isMethod() and m.aliases]
if aliasedMembers:
def defineAlias(alias: str) -> CGThing:
if alias == "@@iterator":
symbolJSID = "RUST_SYMBOL_TO_JSID(GetWellKnownSymbol(cx.raw_cx(), SymbolCode::iterator), \
iteratorId.handle_mut())"
getSymbolJSID: CGThing | None = CGGeneric(fill("rooted!(&in(cx) let mut iteratorId: jsid);\n${symbolJSID};\n",
symbolJSID=symbolJSID))
defineFn = "JS_DefinePropertyById2"
prop = "iteratorId.handle()"
enumFlags = "0" elif alias.startswith("@@"):
raise TypeError("Can't handle any well-known Symbol other than @@iterator")
else:
getSymbolJSID = None
defineFn = "JS_DefineProperty"
prop = f'"{alias}"'
enumFlags = "JSPROP_ENUMERATE"
if enumFlags != "0":
enumFlags = f"{enumFlags} as u32"
return CGList([
getSymbolJSID,
CGGeneric(fill(
"""
assert!(${defineFn}(cx.raw_cx(), prototype.handle(), ${prop}, aliasedVal.handle(), ${enumFlags}));
""",
defineFn=defineFn,
prop=prop,
enumFlags=enumFlags))
], "\n")
def defineAliasesFor(m: IDLMethod) -> CGThing:
return CGList([
CGGeneric(fill(
"""
assert!(JS_GetProperty(cx.raw_cx(), prototype.handle(),
${prop} as *const u8 as *const _,
aliasedVal.handle_mut()));
""",
prop=str_to_cstr_ptr(m.identifier.name)))
] + [defineAlias(alias) for alias in sorted(m.aliases)])
defineAliases = CGList([
CGGeneric(fill("""
// Set up aliases on the interface prototype object we just created.
""")),
CGGeneric("rooted!(&in(cx) let mut aliasedVal = UndefinedValue());\n\n")
] + [defineAliasesFor(m) for m in sorted(aliasedMembers)])
code.append(defineAliases)
constructors = self.descriptor.interface.legacyFactoryFunctions
if constructors:
decl = f"let named_constructors: [(ConstructorClassHook, &std::ffi::CStr, u32); {len(constructors)}]"
specs = []
for constructor in constructors:
hook = f"{CONSTRUCT_HOOK_NAME}_{constructor.identifier.name}"
name = str_to_cstr(constructor.identifier.name)
length = methodLength(constructor)
specs.append(CGGeneric(f"({hook}::<D> as ConstructorClassHook, {name}, {length})"))
values = CGIndenter(CGList(specs, "\n"), 4)
code.append(CGWrapper(values, pre=f"{decl} = [\n", post="\n];"))
code.append(CGGeneric("create_named_constructors(cx.into(), global, &named_constructors, prototype.handle());"))
if self.descriptor.hasLegacyUnforgeableMembers:
if self.descriptor.proxy or self.descriptor.isGlobal():
holderClass = "ptr::null()"
holderProto = "HandleObject::null()"
else:
holderClass = "&Class.get().base as *const JSClass"
holderProto = "prototype.handle()"
code.append(CGGeneric(f"""
rooted!(&in(cx) let mut unforgeable_holder = ptr::null_mut::<JSObject>());
unforgeable_holder.handle_mut().set(
JS_NewObjectWithoutMetadata(cx.raw_cx(), {holderClass}, {holderProto}));
assert!(!unforgeable_holder.is_null());
"""))
code.append(InitLegacyUnforgeablePropertiesOnHolder(self.descriptor, self.properties))
code.append(CGGeneric("""\
let val = ObjectValue(unforgeable_holder.get());
JS_SetReservedSlot(prototype.get(), DOM_PROTO_UNFORGEABLE_HOLDER_SLOT, &val)"""))
return CGList(code, "\n")
class CGGetPerInterfaceObject(CGAbstractMethod):
def __init__(self, descriptor: Descriptor, name: str, idPrefix: str = "", pub: bool = False) -> None:
args = [Argument('&mut JSContext', 'cx'),
Argument('HandleObject', 'global'),
Argument('MutableHandleObject', 'mut rval')]
CGAbstractMethod.__init__(self, descriptor, name,
'void', args, pub=pub, templateArgs=['D: DomTypes'])
self.id = f"{idPrefix}::{MakeNativeName(self.descriptor.name)}"
self.variant = self.id.split('::')[-2]
def definition_body(self) -> CGThing:
return CGGeneric(
"get_per_interface_object_handle"
f"(cx, global, ProtoOrIfaceIndex::{self.variant}({self.id}), CreateInterfaceObjects::<D>, rval)"
)
class CGGetProtoObjectMethod(CGGetPerInterfaceObject):
def __init__(self, descriptor: Descriptor) -> None:
CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject",
"PrototypeList::ID", pub=True)
def definition_body(self) -> CGThing:
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):
def __init__(self, descriptor: Descriptor) -> None:
CGGetPerInterfaceObject.__init__(self, descriptor, "GetConstructorObject",
"PrototypeList::Constructor",
pub=True)
def definition_body(self) -> CGThing:
return CGList([
CGGeneric("""\
/* Get the interface object for this class. This will create the object as
needed. */"""),
CGGetPerInterfaceObject.definition_body(self),
])
class CGDefineProxyHandler(CGAbstractMethod):
def __init__(self, descriptor: Descriptor) -> None:
assert descriptor.proxy
CGAbstractMethod.__init__(self, descriptor, 'DefineProxyHandler',
'*const libc::c_void', [],
pub=True, unsafe=True, templateArgs=["D: DomTypes"])
def define(self) -> str:
return CGAbstractMethod.define(self)
def definition_body(self) -> CGThing:
customDefineProperty = 'proxyhandler::define_property'
if self.descriptor.isMaybeCrossOriginObject() or self.descriptor.operations['IndexedSetter'] or \
self.descriptor.operations['NamedSetter']:
customDefineProperty = 'defineProperty::<D>'
customDelete = 'proxyhandler::delete'
if self.descriptor.isMaybeCrossOriginObject() or self.descriptor.operations['NamedDeleter']:
customDelete = 'delete::<D>'
customGetPrototypeIfOrdinary = 'Some(proxyhandler::get_prototype_if_ordinary)'
customGetPrototype = 'None'
customSetPrototype = 'None'
if self.descriptor.isMaybeCrossOriginObject():
customGetPrototypeIfOrdinary = 'Some(proxyhandler::maybe_cross_origin_get_prototype_if_ordinary_rawcx)'
customGetPrototype = 'Some(getPrototype::<D>)'
customSetPrototype = 'Some(proxyhandler::maybe_cross_origin_set_prototype_rawcx)'
customSet = 'None'
if self.descriptor.isMaybeCrossOriginObject():
assert not self.descriptor.operations['IndexedGetter']
assert not self.descriptor.operations['NamedGetter']
customSet = 'Some(proxyhandler::maybe_cross_origin_set_rawcx::<D>)'
getOwnEnumerablePropertyKeys = "own_property_keys::<D>"
if self.descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties") or \
self.descriptor.isMaybeCrossOriginObject():
getOwnEnumerablePropertyKeys = "getOwnEnumerablePropertyKeys::<D>"
return CGGeneric(f"""
init_proxy_handler_dom_class::<D>();
let traps = ProxyTraps {{
enter: None,
getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor::<D>),
defineProperty: Some({customDefineProperty}),
ownPropertyKeys: Some(own_property_keys::<D>),
delete_: Some({customDelete}),
enumerate: None,
getPrototypeIfOrdinary: {customGetPrototypeIfOrdinary},
getPrototype: {customGetPrototype},
setPrototype: {customSetPrototype},
setImmutablePrototype: None,
preventExtensions: Some(proxyhandler::prevent_extensions),
isExtensible: Some(proxyhandler::is_extensible),
has: None,
get: Some(get::<D>),
set: {customSet},
call: None,
construct: None,
hasOwn: Some(hasOwn::<D>),
getOwnEnumerablePropertyKeys: Some({getOwnEnumerablePropertyKeys}),
nativeCall: None,
objectClassIs: None,
className: Some(className),
fun_toString: None,
boxedValue_unbox: None,
defaultValue: None,
trace: Some({TRACE_HOOK_NAME}::<D>),
finalize: Some({FINALIZE_HOOK_NAME}::<D>),
objectMoved: None,
isCallable: None,
isConstructor: None,
}};
CreateProxyHandler(&traps, unsafe {{ Class.get() }} as *const _ as *const libc::c_void)\
""")
class CGDefineDOMInterfaceMethod(CGAbstractMethod):
def __init__(self, descriptor: Descriptor) -> None:
assert descriptor.interface.hasInterfaceObject()
args = [
Argument('&mut JSContext', 'cx'),
Argument('HandleObject', 'global'),
]
CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface',
'void', args, pub=True, templateArgs=['D: DomTypes'])
if self.descriptor.interface.isCallback() or self.descriptor.interface.isNamespace():
idPrefix = "PrototypeList::Constructor"
else:
idPrefix = "PrototypeList::ID"
self.id = f"{idPrefix}::{MakeNativeName(self.descriptor.name)}"
self.variant = self.id.split('::')[-2]
def define(self) -> str:
return CGAbstractMethod.define(self)
def definition_body(self) -> CGThing:
return CGGeneric(
"define_dom_interface"
f"(cx, global, ProtoOrIfaceIndex::{self.variant}({self.id}),"
"CreateInterfaceObjects::<D>, ConstructorEnabled::<D>)"
)
def needCx(returnType: IDLType | None, arguments: Iterable[IDLArgument | FakeArgument], considerTypes: bool) -> Context:
return max([typeNeedsCx(a.type) for a in arguments] + [typeNeedsCx(returnType, True)]) if considerTypes else Context.No
def isEventHandlerCallback(member: IDLAttribute) -> bool:
if not member.isAttr():
return False
if not member.type.isCallback():
return False
callback = member.type.unroll().callback
return callback.identifier.name in EVENT_HANDLER_CALLBACKS
class CGCallGenerator(CGThing):
def __init__(self,
errorResult: str | None,
arguments: list[tuple[IDLArgument | FakeArgument, str]],
argsPre: list[str],
returnType: IDLType | None,
extendedAttributes: list[str],
descriptor: Descriptor,
nativeMethodName: str,
is_implicit_cx_attribute: bool,
static: bool,
object: str = "this",
hasCEReactions: bool = False
) -> None:
CGThing.__init__(self)
assert errorResult is None or isinstance(errorResult, str)
isFallible = errorResult is not None
result = getRetvalDeclarationForType(returnType, descriptor)
if returnType and returnTypeNeedsOutparam(returnType):
rootType = result
result = CGGeneric("()")
else:
rootType = None
if isFallible:
result = CGWrapper(result, pre="Result<", post=", Error>")
args = CGList([CGGeneric(arg) for arg in argsPre], ", ")
for (a, name) in arguments:
if a.type.isDictionary() and not type_needs_tracing(a.type):
name = f"&{name}"
args.append(CGGeneric(name))
needsCx = False
match max([needCx(returnType, (a for (a, _) in arguments), True), Context.Cx if is_implicit_cx_attribute else Context.No]):
case Context.Cx:
descriptor.cxMethods.append(nativeMethodName)
case Context.CurrentRealm:
descriptor.realmMethods.append(nativeMethodName)
case Context.OldCx:
needsCx = True
case Context.No:
pass
self.cgRoot = CGList([], "\n")
if nativeMethodName in descriptor.cx_no_gcMethods or nativeMethodName in descriptor.cxMethods:
args.prepend(CGGeneric("cx"))
elif nativeMethodName in descriptor.realmMethods:
self.cgRoot.append(CGList([
CGGeneric("let mut realm = CurrentRealm::assert(cx);"),
CGGeneric("let cx = &mut realm;"),
]))
args.prepend(CGGeneric("cx"))
else:
if "cx" not in argsPre and needsCx:
args.prepend(CGGeneric("SafeJSContext::from_ptr(cx.raw_cx())"))
if nativeMethodName in descriptor.inRealmMethods:
args.append(CGGeneric("InRealm::already(&AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx.raw_cx())))"))
if nativeMethodName in descriptor.canGcMethods:
args.append(CGGeneric("CanGc::deprecated_note()"))
if rootType:
args.append(CGGeneric("retval.handle_mut()"))
if rootType:
self.cgRoot.append(CGList([
CGGeneric("rooted!(&in(cx) let mut retval: "),
rootType,
CGGeneric(");"),
]))
call = CGGeneric(nativeMethodName)
if static:
call = CGWrapper(call, pre=f"<D::{MakeNativeName(descriptor.interface.identifier.name)}>::")
else:
call = CGWrapper(call, pre=f"{object}.")
call = CGList([call, CGWrapper(args, pre="(", post=")")])
if hasCEReactions:
self.cgRoot.append(CGGeneric("<D as DomHelpers<D>>::push_new_element_queue();\n"))
self.cgRoot.append(CGList([
CGGeneric("let result: "),
result,
CGGeneric(" = "),
call,
CGGeneric(";"),
]))
if hasCEReactions:
self.cgRoot.append(CGGeneric("<D as DomHelpers<D>>::pop_current_element_queue(cx);\n"))
if isFallible:
if static:
glob = "global.upcast::<D::GlobalScope>()"
else:
glob = "&this.global_(InRealm::already(&AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx.raw_cx()))))"
self.cgRoot.append(CGGeneric(
"let result = match result {\n"
" Ok(result) => result,\n"
" Err(e) => {\n"
f" <D as DomHelpers<D>>::throw_dom_exception(SafeJSContext::from_ptr(cx.raw_cx()), {glob}, e, CanGc::deprecated_note());\n"
f" return{errorResult};\n"
" },\n"
"};"))
def define(self) -> str:
return self.cgRoot.define()
class CGPerSignatureCall(CGThing):
def __init__(self,
returnType: IDLType | None,
argsPre: list[str],
arguments: list[IDLArgument | FakeArgument],
nativeMethodName: str,
static: bool,
descriptor: Descriptor,
idlNode: IDLInterfaceMember,
argConversionStartsAt: int = 0,
getter: bool = False,
setter: bool = False) -> None:
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)
cgThings: list[CGThing] = []
cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgs(),
self.getArgc(), self.descriptor,
invalidEnumValueFatal=not setter) for
i in range(argConversionStartsAt, self.argCount)])
errorResult = None
if self.isFallible():
errorResult = " false"
if isinstance(idlNode, IDLMethod) and idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod():
assert idlNode.maplikeOrSetlikeOrIterable is not None
if idlNode.maplikeOrSetlikeOrIterable.isMaplike() or \
idlNode.maplikeOrSetlikeOrIterable.isSetlike():
cgThings.append(CGMaplikeOrSetlikeMethodGenerator(descriptor,
idlNode.maplikeOrSetlikeOrIterable,
idlNode.identifier.name))
else:
cgThings.append(CGIterableMethodGenerator(descriptor,
idlNode.maplikeOrSetlikeOrIterable,
idlNode.identifier.name))
else:
hasCEReactions = idlNode.getExtendedAttribute("CEReactions") or False
is_event_listener = isinstance(idlNode, IDLAttribute) and isEventHandlerCallback(idlNode)
is_implicit_cx_attribute = is_event_listener or descriptor.implicitCxSetters and idlNode.isAttr() and nativeMethodName.startswith('Set')
cgThings.append(CGCallGenerator(
errorResult,
self.getArguments(), self.argsPre, returnType,
self.extendedAttributes, descriptor, nativeMethodName,
is_implicit_cx_attribute, static, hasCEReactions=hasCEReactions))
self.cgRoot = CGList(cgThings, "\n")
def getArgs(self) -> str:
return "args" if self.argCount > 0 else ""
def getArgc(self) -> str:
return "argc"
def getArguments(self) -> list[tuple[IDLArgument | FakeArgument, str]]:
return [(a, process_arg(f"arg{i}", a)) for (i, a) in enumerate(self.arguments)]
def isFallible(self) -> bool:
return 'infallible' not in self.extendedAttributes
def wrap_return_value(self) -> str:
resultName = "result"
if returnTypeNeedsOutparam(self.returnType) and (
not (isinstance(self.idlNode, IDLMethod) and self.idlNode.isMethod() and self.idlNode.isMaplikeOrSetlikeOrIterableMethod())):
resultName = "retval"
return wrapForType(
'MutableHandleValue::from_raw(args.rval())',
result=resultName,
successCode='return true;',
)
def define(self) -> str:
return f"{self.cgRoot.define()}\n{self.wrap_return_value()}"
class CGSwitch(CGList):
def __init__(self, expression: str, cases: list[CGCase], default: CGThing | None = None) -> 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):
def __init__(self, expression: str, body: CGThing, fallThrough: bool = False) -> None:
CGList.__init__(self, [], "\n")
self.append(CGWrapper(CGGeneric(expression), post=" => {"))
bodyList = CGList([body], "\n")
if fallThrough:
raise TypeError("fall through required but unsupported")
self.append(CGIndenter(bodyList))
self.append(CGGeneric("}"))
class CGGetterCall(CGPerSignatureCall):
def __init__(self, argsPre: list[str], returnType: IDLType | None, nativeMethodName: str, descriptor: Descriptor, attr: IDLAttribute) -> None:
CGPerSignatureCall.__init__(self, returnType, argsPre, [],
nativeMethodName, attr.isStatic(), descriptor,
attr, getter=True)
class FakeArgument():
def __init__(self, type: IDLType, interfaceMember: IDLInterfaceMember, allowTreatNonObjectAsNull: bool = False) -> None:
self.type = type
self.optional = False
self.variadic = False
self.defaultValue = None
self._allowTreatNonObjectAsNull = allowTreatNonObjectAsNull
def allowTreatNonCallableAsNull(self) -> bool:
return self._allowTreatNonObjectAsNull
@property
def identifier(self) -> Any:
raise NotImplementedError("FakeArgument identifier has not been implemented")
class CGSetterCall(CGPerSignatureCall):
def __init__(self, argsPre: list[str], argType: IDLType, nativeMethodName: str, descriptor: Descriptor, attr: IDLAttribute) -> None:
CGPerSignatureCall.__init__(self, None, argsPre,
[FakeArgument(argType, attr, allowTreatNonObjectAsNull=True)],
nativeMethodName, attr.isStatic(), descriptor, attr,
setter=True)
def wrap_return_value(self) -> str:
return "\ntrue"
def getArgc(self) -> str:
return "1"
class CGAbstractStaticBindingMethod(CGAbstractMethod):
def __init__(self, descriptor: Descriptor, name: str, templateArgs: list[str] | None = None) -> None:
args = [
Argument('*mut RawJSContext', 'cx'),
Argument('libc::c_uint', 'argc'),
Argument('*mut JSVal', 'vp'),
]
CGAbstractMethod.__init__(self, descriptor, name, "bool", args, extern=True, templateArgs=templateArgs)
self.exposureSet = descriptor.interface.exposureSet
def definition_body(self) -> CGThing:
preamble = """\
let args = CallArgs::from_vp(vp, argc);
let global = D::GlobalScope::from_object(args.callee());
"""
if len(self.exposureSet) == 1:
preamble += f"let global = DomRoot::downcast::<D::{list(self.exposureSet)[0]}>(global).unwrap();\n"
return CGList([CGGeneric(preamble), self.generate_code()])
def generate_code(self) -> CGThing:
raise NotImplementedError
def GetConstructorNameForReporting(descriptor: Descriptor, ctor: IDLConstructor) -> str:
ctorName = ctor.identifier.name
if ctorName == "constructor":
return descriptor.interface.identifier.name
return ctorName
class CGSpecializedMethod(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor, method: IDLMethod) -> None:
self.method = method
name = method.identifier.name
args = [Argument('*mut RawJSContext', 'cx'),
Argument('RawHandleObject', '_obj'),
Argument('*mut libc::c_void', 'this'),
Argument('*const JSJitMethodCallArgs', 'args')]
CGAbstractExternMethod.__init__(self, descriptor, name, 'bool', args, templateArgs=["D: DomTypes"])
def definition_body(self) -> CGThing:
nativeName = CGSpecializedMethod.makeNativeName(self.descriptor,
self.method)
return CGWrapper(CGMethodCall([], nativeName, self.method.isStatic(),
self.descriptor, self.method),
pre="let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());\n"
"let cx = &mut cx;\n"
f"let this = &*(this as *const {self.descriptor.concreteType});\n"
"let args = &*args;\n"
"let argc = args.argc_;\n")
@staticmethod
def makeNativeName(descriptor: Descriptor, method: IDLMethod) -> str:
if method.underlyingAttr:
return CGSpecializedGetter.makeNativeName(descriptor, method.underlyingAttr)
name = method.identifier.name
nativeName = descriptor.binaryNameFor(name, method.isStatic())
if nativeName == name:
nativeName = descriptor.internalNameFor(name)
return MakeNativeName(nativeName)
class CGMethodPromiseWrapper(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor, methodToWrap: IDLMethod) -> None:
self.method = methodToWrap
name = CGMethodPromiseWrapper.makeNativeName(descriptor, self.method)
args = [Argument('*mut RawJSContext', 'cx'),
Argument('RawHandleObject', '_obj'),
Argument('*mut libc::c_void', 'this'),
Argument('*const JSJitMethodCallArgs', 'args')]
CGAbstractExternMethod.__init__(self, descriptor, name, 'bool', args, templateArgs=["D: DomTypes"])
def definition_body(self) -> CGThing:
return CGGeneric(fill(
"""
let ok = ${methodName}::<D>(${args});
if ok {
return true;
}
return exception_to_promise(cx, (*args).rval(), CanGc::deprecated_note());
""",
methodName=self.method.identifier.name,
args=", ".join(arg.name for arg in self.args),
))
@staticmethod
def makeNativeName(descriptor: Descriptor, m: IDLMethod) -> str:
return f"{m.identifier.name}_promise_wrapper"
class CGGetterPromiseWrapper(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor, methodToWrap: IDLMethod) -> None:
self.method = methodToWrap
name = CGGetterPromiseWrapper.makeNativeName(descriptor, self.method)
self.method_call = CGGetterPromiseWrapper.makeOrigName(descriptor, self.method)
args = [Argument('*mut RawJSContext', 'cx'),
Argument('RawHandleObject', '_obj'),
Argument('*mut libc::c_void', 'this'),
Argument('JSJitGetterCallArgs', 'args')]
CGAbstractExternMethod.__init__(self, descriptor, name, 'bool', args, templateArgs=["D: DomTypes"])
def definition_body(self) -> CGThing:
return CGGeneric(fill(
"""
let ok = ${methodName}::<D>(${args});
if ok {
return true;
}
return exception_to_promise(cx, args.rval(), CanGc::deprecated_note());
""",
methodName=self.method_call,
args=", ".join(arg.name for arg in self.args),
))
@staticmethod
def makeOrigName(descriptor: Descriptor, m: IDLInterfaceMember) -> str:
return f'get_{descriptor.internalNameFor(m.identifier.name)}'
@staticmethod
def makeNativeName(descriptor: Descriptor, m: IDLInterfaceMember) -> str:
return f"{CGGetterPromiseWrapper.makeOrigName(descriptor, m)}_promise_wrapper"
class CGDefaultToJSONMethod(CGSpecializedMethod):
def __init__(self, descriptor: Descriptor, method: IDLMethod) -> None:
assert method.isDefaultToJSON()
CGSpecializedMethod.__init__(self, descriptor, method)
def definition_body(self) -> CGThing:
ret = dedent("""
use crate::inheritance::HasParent;
let mut cx = JSContext::from_ptr(NonNull::new(cx).unwrap());
let cx = &mut cx;
rooted!(&in(cx) let result = JS_NewPlainObject(cx.raw_cx()));
if result.is_null() {
return false;
}
""")
jsonDescriptors = [self.descriptor]
interface = self.descriptor.interface.parent
while interface:
descriptor = self.descriptor.getDescriptor(interface.identifier.name)
if descriptor.hasDefaultToJSON:
jsonDescriptors.append(descriptor)
interface = interface.parent
parents = len(jsonDescriptors) - 1
form = """
if !${parentclass}CollectJSONAttributes::<D>(cx, _obj, this, result.handle()) {
return false;
}
"""
for descriptor in jsonDescriptors[:0:-1]:
ret += fill(form, parentclass=f"{toBindingNamespace(descriptor.name)}::")
parents -= 1
ret += fill(form, parentclass="")
ret += ('(*args).rval().set(ObjectValue(*result));\n'
'true\n')
return CGGeneric(ret)
class CGStaticMethod(CGAbstractStaticBindingMethod):
def __init__(self, descriptor: Descriptor, method: IDLMethod) -> None:
self.method = method
name = descriptor.binaryNameFor(method.identifier.name, True)
CGAbstractStaticBindingMethod.__init__(self, descriptor, name, templateArgs=["D: DomTypes"])
def generate_code(self) -> CGThing:
nativeName = CGSpecializedMethod.makeNativeName(self.descriptor,
self.method)
safeContext = CGGeneric("let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());\nlet cx = &mut cx;\n")
setupArgs = CGGeneric("let args = CallArgs::from_vp(vp, argc);\n")
call = CGMethodCall(["&global"], nativeName, True, self.descriptor, self.method)
return CGList([safeContext, setupArgs, call])
class CGSpecializedGetter(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor, attr: IDLAttribute) -> None:
self.attr = attr
name = f'get_{descriptor.internalNameFor(attr.identifier.name)}'
args = [Argument('*mut RawJSContext', 'cx'),
Argument('RawHandleObject', '_obj'),
Argument('*mut libc::c_void', 'this'),
Argument('JSJitGetterCallArgs', 'args')]
CGAbstractExternMethod.__init__(self, descriptor, name, "bool", args, templateArgs=["D: DomTypes"])
def definition_body(self) -> CGThing:
nativeName = CGSpecializedGetter.makeNativeName(self.descriptor,
self.attr)
return CGWrapper(CGGetterCall([], self.attr.type, nativeName,
self.descriptor, self.attr),
pre="let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());\n"
"let cx = &mut cx;\n"
f"let this = &*(this as *const {self.descriptor.concreteType});\n")
@staticmethod
def makeNativeName(descriptor: Descriptor, attr: IDLAttribute) -> str:
name = attr.identifier.name
nativeName = descriptor.binaryNameFor(name, attr.isStatic())
if nativeName == name:
nativeName = descriptor.internalNameFor(name)
nativeName = MakeNativeName(nativeName)
infallible = ('infallible' in
descriptor.getExtendedAttributes(attr, getter=True))
if attr.type.nullable() or not infallible:
return f"Get{nativeName}"
return nativeName
class CGStaticGetter(CGAbstractStaticBindingMethod):
def __init__(self, descriptor: Descriptor, attr: IDLAttribute) -> None:
self.attr = attr
name = f'get_{attr.identifier.name}'
CGAbstractStaticBindingMethod.__init__(self, descriptor, name, templateArgs=["D: DomTypes"])
def generate_code(self) -> CGThing:
nativeName = CGSpecializedGetter.makeNativeName(self.descriptor,
self.attr)
safeContext = CGGeneric("let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());\nlet cx = &mut cx;\n")
setupArgs = CGGeneric("let args = CallArgs::from_vp(vp, argc);\n")
call = CGGetterCall(["&global"], self.attr.type, nativeName, self.descriptor,
self.attr)
return CGList([safeContext, setupArgs, call])
class CGSpecializedSetter(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor, attr: IDLAttribute) -> None:
self.attr = attr
name = f'set_{descriptor.internalNameFor(attr.identifier.name)}'
args = [Argument('*mut RawJSContext', 'cx'),
Argument('RawHandleObject', 'obj'),
Argument('*mut libc::c_void', 'this'),
Argument('JSJitSetterCallArgs', 'args')]
CGAbstractExternMethod.__init__(self, descriptor, name, "bool", args, templateArgs=["D: DomTypes"])
def definition_body(self) -> CGThing:
nativeName = CGSpecializedSetter.makeNativeName(self.descriptor,
self.attr)
return CGWrapper(
CGSetterCall([], self.attr.type, nativeName, self.descriptor, self.attr),
pre=("let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());\n"
"let cx = &mut cx;\n"
f"let this = &*(this as *const {self.descriptor.concreteType});\n")
)
@staticmethod
def makeNativeName(descriptor: Descriptor, attr: IDLAttribute) -> str:
name = attr.identifier.name
nativeName = descriptor.binaryNameFor(name, attr.isStatic())
if nativeName == name:
nativeName = descriptor.internalNameFor(name)
return f"Set{MakeNativeName(nativeName)}"
class CGStaticSetter(CGAbstractStaticBindingMethod):
def __init__(self, descriptor: Descriptor, attr: IDLAttribute) -> None:
self.attr = attr
name = f'set_{attr.identifier.name}'
CGAbstractStaticBindingMethod.__init__(self, descriptor, name, templateArgs=["D: DomTypes"])
def generate_code(self) -> CGThing:
nativeName = CGSpecializedSetter.makeNativeName(self.descriptor,
self.attr)
safeContext = CGGeneric("let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());\nlet cx = &mut cx;\n")
checkForArg = CGGeneric(
"let args = CallArgs::from_vp(vp, argc);\n"
"if argc == 0 {\n"
f' throw_type_error(cx.raw_cx(), c"Not enough arguments to {self.attr.identifier.name} setter.");\n'
" return false;\n"
"}")
call = CGSetterCall(["&global"], self.attr.type, nativeName, self.descriptor,
self.attr)
return CGList([safeContext, checkForArg, call])
class CGSpecializedForwardingSetter(CGSpecializedSetter):
def __init__(self, descriptor: Descriptor, attr: IDLAttribute) -> None:
CGSpecializedSetter.__init__(self, descriptor, attr)
def definition_body(self) -> CGThing:
attrName = self.attr.identifier.name
forwardToAttr = self.attr.getExtendedAttribute("PutForwards")
assert forwardToAttr is not None
forwardToAttrName = forwardToAttr[0]
assert all(ord(c) < 128 for c in attrName)
assert all(ord(c) < 128 for c in forwardToAttrName)
return CGGeneric(f"""
let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());
let cx = &mut cx;
rooted!(&in(cx) let mut v = UndefinedValue());
if !JS_GetProperty(cx.raw_cx(), HandleObject::from_raw(obj), {str_to_cstr_ptr(attrName)}, v.handle_mut()) {{
return false;
}}
if !v.is_object() {{
throw_type_error(cx.raw_cx(), c"Value.{attrName} is not an object.");
return false;
}}
rooted!(&in(cx) let target_obj = v.to_object());
JS_SetProperty(cx.raw_cx(), target_obj.handle(), {str_to_cstr_ptr(forwardToAttrName)}, HandleValue::from_raw(args.get(0)))
""")
class CGSpecializedReplaceableSetter(CGSpecializedSetter):
def __init__(self, descriptor: Descriptor, attr: IDLAttribute) -> None:
CGSpecializedSetter.__init__(self, descriptor, attr)
def definition_body(self) -> CGThing:
assert self.attr.readonly
name = str_to_cstr_ptr(self.attr.identifier.name)
assert all(ord(c) < 128 for c in name)
return CGGeneric(f"""
JS_DefineProperty(cx, HandleObject::from_raw(obj), {name},
HandleValue::from_raw(args.get(0)), JSPROP_ENUMERATE as u32)""")
class CGMemberJITInfo(CGThing):
def __init__(self, descriptor: Descriptor, member: IDLInterfaceMember) -> None:
self.member = member
self.descriptor = descriptor
def defineJitInfo(self, infoName: str, opName: str, opType: str, infallible: bool, movable: bool,
aliasSet: str, alwaysInSlot: bool, lazilyInSlot: bool, slotIndex: str,
returnTypes: list[IDLType], args: list[IDLArgument] | None) -> str:
assert not movable or aliasSet != "AliasEverything" assert not alwaysInSlot or movable
def jitInfoInitializer(isTypedMethod: bool) -> str:
initializer = fill(
"""
JSJitInfo {
__bindgen_anon_1: JSJitInfo__bindgen_ty_1 {
${opKind}: ${opValue}
},
__bindgen_anon_2: JSJitInfo__bindgen_ty_2 {
protoID: PrototypeList::ID::${name} as u16,
},
__bindgen_anon_3: JSJitInfo__bindgen_ty_3 { depth: ${depth} },
_bitfield_align_1: [],
_bitfield_1: __BindgenBitfieldUnit::new(
new_jsjitinfo_bitfield_1!(
JSJitInfo_OpType::${opType} as u8,
JSJitInfo_AliasSet::${aliasSet} as u8,
JSValueType::${returnType} as u8,
${isInfallible},
${isMovable},
${isEliminatable},
${isAlwaysInSlot},
${isLazilyCachedInSlot},
${isTypedMethod},
${slotIndex},
).to_ne_bytes()
),
}
""",
opValue=f"Some({CGDictionary.makeMemberName(opName)}::<D>)",
name=self.descriptor.name,
depth=str(self.descriptor.interface.inheritanceDepth()),
opType=opType,
opKind=opType.lower(),
aliasSet=aliasSet,
returnType=functools.reduce(CGMemberJITInfo.getSingleReturnType, returnTypes,
""),
isInfallible=toStringBool(infallible),
isMovable=toStringBool(movable),
isEliminatable=toStringBool(False),
isAlwaysInSlot=toStringBool(alwaysInSlot),
isLazilyCachedInSlot=toStringBool(lazilyInSlot),
isTypedMethod=toStringBool(isTypedMethod),
slotIndex=slotIndex)
return initializer.rstrip()
if args is not None:
argTypesArray = f"{infoName}_argTypes"
argTypes = [CGMemberJITInfo.getJSArgType(arg.type) for arg in args]
argTypes.append("JSJitInfo_ArgType::ArgTypeListEnd as i32")
argTypesDecl = f"const {argTypesArray}: [i32; {len(argTypes)}] = [ {', '.join(argTypes)} ];\n"
return fill(
"""
$*{argTypesDecl}
static ${infoName}: ThreadUnsafeOnceLock<JSTypedMethodJitInfo> = ThreadUnsafeOnceLock::new();
pub(crate) fn init_${infoName}<D: DomTypes>() {
${infoName}.set(JSTypedMethodJitInfo {
base: ${jitInfoInit},
argTypes: &${argTypes} as *const _ as *const JSJitInfo_ArgType,
});
}
""",
argTypesDecl=argTypesDecl,
infoName=infoName,
jitInfoInit=indent(jitInfoInitializer(True)),
argTypes=argTypesArray)
return f"""
static {infoName}: ThreadUnsafeOnceLock<JSJitInfo> = ThreadUnsafeOnceLock::new();
pub(crate) fn init_{infoName}<D: DomTypes>() {{
{infoName}.set({jitInfoInitializer(False)});
}}"""
def define(self) -> str:
if self.member.isAttr():
assert isinstance(self.member, IDLAttribute)
internalMemberName = self.descriptor.internalNameFor(self.member.identifier.name)
getterinfo = f"{internalMemberName}_getterinfo"
getter = f"get_{internalMemberName}"
if self.member.type.isPromise():
getter = CGGetterPromiseWrapper.makeNativeName(self.descriptor, self.member)
getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True)
movable = self.mayBeMovable() and getterinfal
aliasSet = self.aliasSet()
isAlwaysInSlot = self.member.getExtendedAttribute("StoreInSlot") or False
if self.member.slotIndices is not None:
assert isAlwaysInSlot or self.member.getExtendedAttribute("Cached")
isLazilyCachedInSlot = not isAlwaysInSlot
slotIndex = memberReservedSlot(self.member) else:
isLazilyCachedInSlot = False
slotIndex = "0"
result = self.defineJitInfo(getterinfo, getter, "Getter",
getterinfal, movable, aliasSet,
isAlwaysInSlot, isLazilyCachedInSlot,
slotIndex,
[self.member.type], None)
if (not self.member.readonly or self.member.getExtendedAttribute("PutForwards")
or self.member.getExtendedAttribute("Replaceable")):
setterinfo = f"{internalMemberName}_setterinfo"
setter = f"set_{internalMemberName}"
result += self.defineJitInfo(setterinfo, setter, "Setter",
False, False, "AliasEverything",
False, False, "0",
[BuiltinTypes[IDLBuiltinType.Types.undefined]],
None)
return result
if self.member.isMethod():
assert isinstance(self.member, IDLMethod)
methodinfo = f"{self.member.identifier.name}_methodinfo"
method = f"{self.member.identifier.name}"
if self.member.returnsPromise():
method = CGMethodPromiseWrapper.makeNativeName(self.descriptor, self.member)
sigs = self.member.signatures()
if len(sigs) != 1:
methodInfal = False
args = None
movable = False
else:
sig = sigs[0]
hasInfallibleImpl = "infallible" in self.descriptor.getExtendedAttributes(self.member)
movable = self.mayBeMovable() and hasInfallibleImpl
if (len(sig[1]) != 0):
methodInfal = False
else:
methodInfal = hasInfallibleImpl
if self.member.affects == "Nothing":
args = sig[1]
else:
args = None
aliasSet = self.aliasSet()
result = self.defineJitInfo(methodinfo, method, "Method",
methodInfal, movable, aliasSet,
False, False, "0",
[s[0] for s in sigs], args)
return result
raise TypeError("Illegal member type to CGPropertyJITInfo")
def mayBeMovable(self) -> bool:
affects = self.member.affects
dependsOn = self.member.dependsOn
assert affects in IDLInterfaceMember.AffectsValues
assert dependsOn in IDLInterfaceMember.DependsOnValues
return (affects == "Nothing"
and (dependsOn != "Everything" and dependsOn != "DeviceState"))
def aliasSet(self) -> str:
dependsOn = self.member.dependsOn
assert dependsOn in IDLInterfaceMember.DependsOnValues
if dependsOn == "Nothing" or dependsOn == "DeviceState":
assert self.member.affects == "Nothing"
return "AliasNone"
if dependsOn == "DOMState":
assert self.member.affects == "Nothing"
return "AliasDOMSets"
return "AliasEverything"
@staticmethod
def getJSReturnTypeTag(t: IDLType) -> str:
if t.nullable():
return "JSVAL_TYPE_UNKNOWN"
if t.isUndefined():
return "JSVAL_TYPE_UNDEFINED"
if t.isSequence():
return "JSVAL_TYPE_OBJECT"
if t.isRecord():
return "JSVAL_TYPE_OBJECT"
if t.isPromise():
return "JSVAL_TYPE_OBJECT"
if t.isGeckoInterface():
return "JSVAL_TYPE_OBJECT"
if t.isString():
return "JSVAL_TYPE_STRING"
if t.isEnum():
return "JSVAL_TYPE_STRING"
if t.isCallback():
return "JSVAL_TYPE_OBJECT"
if t.isAny():
return "JSVAL_TYPE_UNKNOWN"
if t.isObject():
return "JSVAL_TYPE_OBJECT"
if t.isSpiderMonkeyInterface():
return "JSVAL_TYPE_OBJECT"
if t.isUnion():
u = t.unroll()
assert isinstance(u, IDLUnionType)
if u.hasNullableType:
return "JSVAL_TYPE_UNKNOWN"
assert u.flatMemberTypes is not None
return functools.reduce(CGMemberJITInfo.getSingleReturnType,
u.flatMemberTypes, "")
if t.isDictionary():
return "JSVAL_TYPE_OBJECT"
if not t.isPrimitive():
raise TypeError(f"No idea what type {t} is.")
tag = t.tag()
if tag == IDLType.Tags.bool:
return "JSVAL_TYPE_BOOLEAN"
if tag in [IDLType.Tags.int8, IDLType.Tags.uint8,
IDLType.Tags.int16, IDLType.Tags.uint16,
IDLType.Tags.int32]:
return "JSVAL_TYPE_INT32"
if tag in [IDLType.Tags.int64, IDLType.Tags.uint64,
IDLType.Tags.unrestricted_float, IDLType.Tags.float,
IDLType.Tags.unrestricted_double, IDLType.Tags.double]:
return "JSVAL_TYPE_DOUBLE"
if tag != IDLType.Tags.uint32:
raise TypeError(f"No idea what type {t} is.")
return "JSVAL_TYPE_DOUBLE"
@staticmethod
def getSingleReturnType(existingType: str, t: IDLType) -> str:
type = CGMemberJITInfo.getJSReturnTypeTag(t)
if existingType == "":
return type
if type == existingType:
return existingType
if ((type == "JSVAL_TYPE_DOUBLE"
and existingType == "JSVAL_TYPE_INT32")
or (existingType == "JSVAL_TYPE_DOUBLE"
and type == "JSVAL_TYPE_INT32")):
return "JSVAL_TYPE_DOUBLE"
return "JSVAL_TYPE_UNKNOWN"
@staticmethod
def getJSArgType(t: IDLType) -> str:
assert not t.isUndefined()
if t.nullable():
assert isinstance(t, IDLNullableType)
return f"JSJitInfo_ArgType::Null as i32 | {CGMemberJITInfo.getJSArgType(t.inner)}"
if t.isSequence():
return "JSJitInfo_ArgType::Object as i32"
if t.isGeckoInterface():
return "JSJitInfo_ArgType::Object as i32"
if t.isString():
return "JSJitInfo_ArgType::String as i32"
if t.isEnum():
return "JSJitInfo_ArgType::String as i32"
if t.isCallback():
return "JSJitInfo_ArgType::Object as i32"
if t.isAny():
return "JSJitInfo_ArgType::Any as i32"
if t.isObject():
return "JSJitInfo_ArgType::Object as i32"
if t.isSpiderMonkeyInterface():
return "JSJitInfo_ArgType::Object as i32"
if t.isUnion():
u = t.unroll()
assert isinstance(u, IDLUnionType) and u.flatMemberTypes is not None
type = "JSJitInfo_ArgType::Null as i32" if u.hasNullableType else ""
return functools.reduce(CGMemberJITInfo.getSingleArgType,
u.flatMemberTypes, type)
if t.isDictionary():
return "JSJitInfo_ArgType::Object as i32"
if not t.isPrimitive():
raise TypeError(f"No idea what type {t} is.")
tag = t.tag()
if tag == IDLType.Tags.bool:
return "JSJitInfo_ArgType::Boolean as i32"
if tag in [IDLType.Tags.int8, IDLType.Tags.uint8,
IDLType.Tags.int16, IDLType.Tags.uint16,
IDLType.Tags.int32]:
return "JSJitInfo_ArgType::Integer as i32"
if tag in [IDLType.Tags.int64, IDLType.Tags.uint64,
IDLType.Tags.unrestricted_float, IDLType.Tags.float,
IDLType.Tags.unrestricted_double, IDLType.Tags.double]:
return "JSJitInfo_ArgType::Double as i32"
if tag != IDLType.Tags.uint32:
raise TypeError(f"No idea what type {t} is.")
return "JSJitInfo_ArgType::Double as i32"
@staticmethod
def getSingleArgType(existingType: str, t: IDLType) -> str:
type = CGMemberJITInfo.getJSArgType(t)
if existingType == "":
return type
if type == existingType:
return existingType
return f"{existingType} | {type}"
class CGStaticMethodJitinfo(CGGeneric):
def __init__(self, method: IDLMethod) -> None:
CGGeneric.__init__(
self,
f"""
static {method.identifier.name}_methodinfo: ThreadUnsafeOnceLock<JSJitInfo> = ThreadUnsafeOnceLock::new();
pub(crate) fn init_{method.identifier.name}_methodinfo<D: DomTypes>() {{
{method.identifier.name}_methodinfo.set(JSJitInfo {{
__bindgen_anon_1: JSJitInfo__bindgen_ty_1 {{
staticMethod: Some({CGDictionary.makeMemberName(method.identifier.name)}::<D>)
}},
__bindgen_anon_2: JSJitInfo__bindgen_ty_2 {{
protoID: PrototypeList::ID::Last as u16,
}},
__bindgen_anon_3: JSJitInfo__bindgen_ty_3 {{ depth: 0 }},
_bitfield_align_1: [],
_bitfield_1: __BindgenBitfieldUnit::new(
new_jsjitinfo_bitfield_1!(
JSJitInfo_OpType::StaticMethod as u8,
JSJitInfo_AliasSet::AliasEverything as u8,
JSValueType::JSVAL_TYPE_OBJECT as u8,
false,
false,
false,
false,
false,
false,
0,
).to_ne_bytes()
),
}});
}}
"""
)
def getEnumValueName(value: str) -> str:
if re.match("[^\x20-\x7E]", value):
raise SyntaxError(f'Enum value "{value}" contains non-ASCII characters')
if re.match("^[0-9]", value):
value = '_' + value
value = re.sub(r'[^0-9A-Za-z_]', '_', value)
if re.match("^_[A-Z]|__", value):
raise SyntaxError(f'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: IDLEnum, config: Configuration) -> None:
CGThing.__init__(self)
ident = enum.identifier.name
enums = ",\n ".join(map(getEnumValueName, list(enum.values())))
derives = ["Copy", "Clone", "Debug", "JSTraceable", "MallocSizeOf", "PartialEq"]
enum_config = config.getEnumConfig(ident)
extra_derives = enum_config.get('derives', [])
derives = ', '.join(derives + extra_derives)
decl = f"""
#[repr(usize)]
#[derive({derives})]
pub enum {ident} {{
{enums}
}}
"""
pairs = ",\n ".join([f'("{val}", super::{ident}::{getEnumValueName(val)})'
for val in list(enum.values())])
inner = f"""
use crate::utils::find_enum_value;
use crate::cformat;
use js::conversions::ConversionResult;
use js::conversions::FromJSValConvertible;
use js::conversions::ToJSValConvertible;
use js::context::JSContext;
use js::context::RawJSContext;
use js::rust::HandleValue;
use js::rust::MutableHandleValue;
use js::jsval::JSVal;
pub(crate) const pairs: &[(&str, super::{ident})] = &[
{pairs},
];
impl super::{ident} {{
pub fn as_str(&self) -> &'static str {{
pairs[*self as usize].0
}}
}}
impl Default for super::{ident} {{
fn default() -> super::{ident} {{
pairs[0].1
}}
}}
impl std::str::FromStr for super::{ident} {{
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {{
pairs
.iter()
.find(|&&(key, _)| s == key)
.map(|&(_, ev)| ev)
.ok_or(())
}}
}}
impl ToJSValConvertible for super::{ident} {{
unsafe fn to_jsval(&self, cx: *mut RawJSContext, rval: MutableHandleValue) {{
pairs[*self as usize].0.to_jsval(cx, rval);
}}
}}
impl FromJSValConvertible for super::{ident} {{
type Config = ();
unsafe fn from_jsval(_cx: *mut RawJSContext, value: HandleValue, _option: ())
-> Result<ConversionResult<super::{ident}>, ()> {{
// TODO https://github.com/servo/mozjs/issues/749
let mut cx = crate::script_runtime::temp_cx();
Self::safe_from_jsval(&mut cx, value, _option)
}}
fn safe_from_jsval(cx: &mut JSContext, value: HandleValue, _option: ())
-> Result<ConversionResult<super::{ident}>, ()> {{
match find_enum_value(cx, value, pairs) {{
Err(_) => Err(()),
Ok((None, search)) => {{
Ok(ConversionResult::Failure(
cformat!("'{{}}' is not a valid enum value for enumeration '{ident}'.", search).into()
))
}}
Ok((Some(&value), _)) => Ok(ConversionResult::Success(value)),
}}
}}
}}
"""
self.cgRoot = CGList([
CGGeneric(decl),
CGNamespace.build([f"{ident}Values"],
CGIndenter(CGGeneric(inner)), public=True),
])
def define(self) -> str:
return self.cgRoot.define()
def convertConstIDLValueToRust(value: IDLConst) -> str:
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.unrestricted_float, IDLType.Tags.float,
IDLType.Tags.unrestricted_double, IDLType.Tags.double]:
return str(value.value)
if tag == IDLType.Tags.bool:
return toStringBool(value.value)
raise TypeError(f"Const value of unhandled type: {value.type}")
class CGConstant(CGThing):
def __init__(self, constant: IDLConst) -> None:
CGThing.__init__(self)
self.constant = constant
def define(self) -> str:
name = self.constant.identifier.name
value = convertConstIDLValueToRust(self.constant.value)
tag = self.constant.value.type.tag()
const_type = builtinNames[self.constant.value.type.tag()]
if tag == IDLType.Tags.float:
const_type = "f32"
elif tag == IDLType.Tags.double:
const_type = "f64"
return f"pub const {name}: {const_type} = {value};\n"
def getUnionTypeTemplateVars(type: IDLType, descriptorProvider: DescriptorProvider) -> dict[str, Any]:
if type.isGeckoInterface():
name = type.inner.identifier.name
typeName = descriptorProvider.getDescriptor(name).returnType
elif type.isEnum():
name = type.inner.identifier.name
typeName = name
elif type.isDictionary():
name = type.name
typeName = name
if containsDomInterface(type):
typeName += "<D>"
elif type.isSequence() or type.isRecord():
name = type.name
inner = getUnionTypeTemplateVars(innerContainerType(type), descriptorProvider)
typeName = wrapInNativeContainerType(type, CGGeneric(inner["typeName"])).define()
elif type.isByteString():
name = type.name
typeName = "ByteString"
elif type.isDOMString():
name = type.name
typeName = "DOMString"
elif type.isUSVString():
name = type.name
typeName = "USVString"
elif type.isPrimitive():
name = type.name
typeName = builtinNames[type.tag()]
elif type.isObject():
name = type.name
typeName = "Heap<*mut JSObject>"
elif is_typed_array(type):
name = type.name
typeName = f"typedarray::Heap{name}"
elif type.isCallback():
name = type.name
typeName = f"{name}<D>"
elif type.isUndefined():
return {
"name": type.name,
"typeName": "()",
"jsConversion": CGGeneric("if value.is_undefined() { Ok(Some(())) } else { Ok(None) }")
}
else:
raise TypeError(f"Can't handle {type} in unions yet")
info = getJSToNativeConversionInfo(
type, descriptorProvider, failureCode="return Ok(None);",
exceptionCode='return Err(());',
isDefinitelyObject=True,
isMember="Union")
template = info.template
jsConversion = string.Template(template).substitute({
"val": "value",
})
jsConversion = CGWrapper(CGGeneric(jsConversion), pre="Ok(Some(", post="))")
return {
"name": name,
"typeName": typeName,
"jsConversion": jsConversion,
}
def traitRequiresManualImpl(name: str, ty: IDLObject) -> bool:
return name == "Clone" and containsDomInterface(ty)
class CGUnionStruct(CGThing):
def __init__(self, type: IDLUnionType, descriptorProvider: DescriptorProvider, config: Configuration) -> None:
assert not type.nullable()
assert not type.hasNullableType
CGThing.__init__(self)
self.type = type
derivesList = config.getUnionConfig(str(type)).get('derives', [])
self.manualImpls = list(filter(lambda t: traitRequiresManualImpl(t, type), derivesList))
self.derives = list(filter(lambda t: not traitRequiresManualImpl(t, type), derivesList))
self.descriptorProvider = descriptorProvider
self.generic, self.genericSuffix = genericsForType(self.type)
def membersNeedTracing(self) -> bool:
assert self.type.flatMemberTypes is not None
for t in self.type.flatMemberTypes:
if type_needs_tracing(t):
return True
return False
def manualImplClone(self, templateVars: list[tuple[dict[str, Any], str]]) -> str:
arms = [f" {self.type}::{v['name']}(inner) => "
f"{self.type}::{v['name']}(inner.clone()),"
for (v, _) in templateVars]
arms = "\n".join(arms)
return f"""
#[allow(clippy::clone_on_copy)]
impl{self.generic} Clone for {self.type}{self.genericSuffix} {{
fn clone(&self) -> Self {{
match self {{
{arms}
}}
}}
}}
"""
def manualImpl(self, t: str, templateVars: list[tuple[dict[str, Any], str]]) -> str:
if t == "Clone":
return self.manualImplClone(templateVars)
raise ValueError(f"Don't know how to impl {t} for union")
def define(self) -> str:
def getTypeWrapper(t: IDLType) -> str:
if type_needs_tracing(t):
return "RootedTraceableBox"
if t.isCallback():
return "Rc"
return ""
assert self.type.flatMemberTypes is not None
templateVars = [(getUnionTypeTemplateVars(t, self.descriptorProvider),
getTypeWrapper(t)) for t in self.type.flatMemberTypes]
enumValues = [
f" {v['name']}({wrapper}<{v['typeName']}>)," if wrapper else f" {v['name']}({v['typeName']}),"
for (v, wrapper) in templateVars
]
enumConversions = [
f" {self.type}::{v['name']}(ref inner) => inner.to_jsval(cx, rval),"
for (v, _) in templateVars
]
joinedEnumValues = "\n".join(enumValues)
joinedEnumConversions = "\n".join(enumConversions)
derives = ["JSTraceable"] + self.derives
manualImpls = "\n".join(map(lambda t: self.manualImpl(t, templateVars), self.manualImpls))
return f"""
#[derive({", ".join(derives)})]
pub enum {self.type}{self.generic} {{
{joinedEnumValues}
}}
impl{self.generic} ToJSValConvertible for {self.type}{self.genericSuffix} {{
unsafe fn to_jsval(&self, cx: *mut RawJSContext, rval: MutableHandleValue) {{
match *self {{
{joinedEnumConversions}
}}
}}
}}
{manualImpls}
"""
class CGUnionConversionStruct(CGThing):
def __init__(self, type: IDLUnionType, descriptorProvider: DescriptorProvider) -> None:
assert not type.nullable()
assert not type.hasNullableType
CGThing.__init__(self)
self.type = type
self.descriptorProvider = descriptorProvider
def membersNeedTracing(self) -> bool:
assert self.type.flatMemberTypes is not None
for t in self.type.flatMemberTypes:
if type_needs_tracing(t):
return True
return False
def from_jsval(self) -> CGWrapper:
memberTypes = cast(list[IDLType], self.type.flatMemberTypes)
names = []
conversions: list[CGThing] = []
def get_name(memberType: IDLType) -> str:
if self.type.isGeckoInterface():
return memberType.inner.identifier.name
return memberType.name
def get_match(name: str) -> str:
generic = "::<D>" if containsDomInterface(self.type) else ""
return (
f"match {self.type}{generic}::TryConvertTo{name}(&mut cx, value) {{\n"
" Err(_) => return Err(()),\n"
f" Ok(Some(value)) => return Ok(ConversionResult::Success({self.type}::{name}(value))),\n"
" Ok(None) => (),\n"
"}\n")
interfaceMemberTypes = [t for t in memberTypes if t.isNonCallbackInterface()]
if len(interfaceMemberTypes) > 0:
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 = [t for t in memberTypes if t.isSequence()]
if len(arrayObjectMemberTypes) > 0:
assert len(arrayObjectMemberTypes) == 1
typeName = arrayObjectMemberTypes[0].name
arrayObject: CGThing | None = CGGeneric(get_match(typeName))
names.append(typeName)
else:
arrayObject = None
callbackMemberTypes = [t for t in memberTypes if t.isCallback() or t.isCallbackInterface()]
if len(callbackMemberTypes) > 0:
assert len(callbackMemberTypes) == 1
typeName = callbackMemberTypes[0].name
callbackObject: CGThing | None = CGGeneric(get_match(typeName))
else:
callbackObject = None
dictionaryMemberTypes = [t for t in memberTypes if t.isDictionary()]
if len(dictionaryMemberTypes) > 0:
assert len(dictionaryMemberTypes) == 1
typeName = dictionaryMemberTypes[0].name
dictionaryObject = CGGeneric(get_match(typeName))
names.append(typeName)
else:
dictionaryObject = None
objectMemberTypes = [t for t in memberTypes if t.isObject()]
if len(objectMemberTypes) > 0:
assert len(objectMemberTypes) == 1
typeName = objectMemberTypes[0].name
object = CGGeneric(get_match(typeName))
names.append(typeName)
else:
object = None
mozMapMemberTypes = [t for t in memberTypes if t.isRecord()]
if len(mozMapMemberTypes) > 0:
assert len(mozMapMemberTypes) == 1
typeName = mozMapMemberTypes[0].name
mozMapObject = CGGeneric(get_match(typeName))
names.append(typeName)
else:
mozMapObject = None
hasObjectTypes = object or interfaceObject or arrayObject or callbackObject or mozMapObject
if hasObjectTypes:
assert not object or not (interfaceObject or arrayObject or callbackObject or mozMapObject)
templateBody = CGList([], "\n")
if arrayObject or callbackObject:
assert not (arrayObject and callbackObject)
templateBody.append(arrayObject if arrayObject else callbackObject)
if interfaceObject:
assert not object
templateBody.append(interfaceObject)
elif object:
templateBody.append(object)
if mozMapObject:
templateBody.append(mozMapObject)
conversions.append(CGIfWrapper("value.get().is_object()", templateBody))
if dictionaryObject:
assert not object
conversions.append(dictionaryObject)
stringTypes = [t for t in memberTypes if t.isString() or t.isEnum()]
numericTypes = [t for t in memberTypes if t.isNumeric()]
booleanTypes = [t for t in memberTypes if t.isBoolean()]
numUndefinedVariants = [t.isUndefined() for t in memberTypes].count(True)
if stringTypes or numericTypes or booleanTypes or numUndefinedVariants != 0:
assert len(stringTypes) <= 1
assert len(numericTypes) <= 1
assert len(booleanTypes) <= 1
assert numUndefinedVariants <= 1
def getStringOrPrimitiveConversion(memberType: IDLType) -> CGThing:
typename = get_name(memberType)
return CGGeneric(get_match(typename))
other: list[CGThing] = []
stringConversion = list(map(getStringOrPrimitiveConversion, stringTypes))
numericConversion = list(map(getStringOrPrimitiveConversion, numericTypes))
booleanConversion = list(map(getStringOrPrimitiveConversion, booleanTypes))
undefinedConversion = CGGeneric("return Ok(ConversionResult::Success(Self::Undefined(())));")
if stringConversion:
if booleanConversion:
other.append(CGIfWrapper("value.get().is_boolean()", booleanConversion[0]))
if numericConversion:
other.append(CGIfWrapper("value.get().is_number()", numericConversion[0]))
if numUndefinedVariants != 0:
other.append(CGIfWrapper("value.get().is_undefined()", undefinedConversion))
other.append(stringConversion[0])
elif numericConversion:
if booleanConversion:
other.append(CGIfWrapper("value.get().is_boolean()", booleanConversion[0]))
if numUndefinedVariants != 0:
other.append(CGIfWrapper("value.get().is_undefined()", undefinedConversion))
other.append(numericConversion[0])
elif booleanConversion:
if numUndefinedVariants != 0:
other.append(CGIfWrapper("value.get().is_undefined()", undefinedConversion))
other.append(booleanConversion[0])
else:
assert numUndefinedVariants != 0
other.append(undefinedConversion)
conversions.append(CGList(other, "\n\n"))
conversions.append(CGGeneric(
f'Ok(ConversionResult::Failure(c"argument could not be converted to any of: {", ".join(names)}".into()))'
))
generic, genericSuffix = genericsForType(self.type)
method = CGWrapper(
CGIndenter(CGList(conversions, "\n\n")),
pre="unsafe fn from_jsval(_cx: *mut RawJSContext,\n"
" value: HandleValue,\n"
" _option: ())\n"
f" -> Result<ConversionResult<{self.type}{genericSuffix}>, ()> {{\n"
" // TODO https://github.com/servo/mozjs/issues/749\n"
" let mut cx = crate::script_runtime::temp_cx();\n",
post="\n}")
return CGWrapper(
CGIndenter(CGList([
CGGeneric("type Config = ();"),
method,
], "\n")),
pre=f"impl{generic} FromJSValConvertible for {self.type}{genericSuffix} {{\n",
post="\n}")
def try_method(self, t: IDLType) -> CGThing:
if t.isUndefined():
return CGGeneric("")
templateVars = getUnionTypeTemplateVars(t, self.descriptorProvider)
actualType = templateVars["typeName"]
if type_needs_tracing(t):
actualType = f"RootedTraceableBox<{actualType}>"
if t.isCallback():
actualType = f"Rc<{actualType}>"
returnType = f"Result<Option<{actualType}>, ()>"
jsConversion = templateVars["jsConversion"]
return CGWrapper(
CGIndenter(jsConversion, 4),
pre=f"unsafe fn TryConvertTo{t.name}(cx: &mut JSContext, value: HandleValue) -> {returnType} {{\n",
post="\n}")
def define(self) -> str:
from_jsval = self.from_jsval()
assert self.type.flatMemberTypes is not None
methods = CGIndenter(CGList([
self.try_method(t) for t in self.type.flatMemberTypes
], "\n\n"))
generic, genericSuffix = genericsForType(self.type)
return f"""
{from_jsval.define()}
impl{generic} {self.type}{genericSuffix} {{
{methods.define()}
}}
"""
class ClassItem:
def __init__(self, name: str | None, visibility: str) -> None:
self.name = name
self.visibility = visibility
def declare(self, cgClass: CGClass) -> str: assert False
def define(self, cgClass: CGClass) -> str: assert False
class ClassBase(ClassItem):
def __init__(self, name: str, visibility: str = 'pub') -> None:
ClassItem.__init__(self, name, visibility)
def declare(self, cgClass: CGClass) -> str:
return f'{self.visibility} {self.name}'
def define(self, cgClass: CGClass) -> str:
return ''
class ClassMethod(ClassItem):
body: str | None
def __init__(self,
name: str,
returnType: str,
args: list[Argument],
inline: bool = False,
static: bool = False,
virtual: bool = False,
const: bool = False,
bodyInHeader: bool = False,
templateArgs: list[str] | None = None,
visibility: str = 'public',
body: str | None = None,
breakAfterReturnDecl: str = "\n",
unsafe: bool = False,
breakAfterSelf: str = "\n",
override: bool = False
) -> None:
assert not override or virtual
assert not (override and static)
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
self.unsafe = unsafe
ClassItem.__init__(self, name, visibility)
def getDecorators(self, declaring: bool) -> str:
decorators = []
if self.inline:
decorators.append('inline')
if declaring:
if self.static:
decorators.append('static')
if self.virtual:
decorators.append('virtual')
if decorators:
return f'{" ".join(decorators)} '
return ''
def getBody(self) -> str:
assert self.body is not None
return self.body
def declare(self, cgClass: CGClass) -> str:
templateClause = f"<{', '.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 = f' {{\n{body}\n}}'
else:
body = ';'
visibility = f'{self.visibility} ' if self.visibility != 'priv' else ''
unsafe = "unsafe " if self.unsafe else ""
returnType = f" -> {self.returnType}" if self.returnType else ""
const = ' const' if self.const else ''
override = ' MOZ_OVERRIDE' if self.override else ''
return (
f"{self.getDecorators(True)}{self.breakAfterReturnDecl}"
f"{visibility}{unsafe}fn {self.name}{templateClause}({args})"
f"{returnType}{const}{override}{body}{self.breakAfterSelf}"
)
def define(self, cgClass: CGClass) -> str: assert False
class ClassConstructor(ClassItem):
def __init__(self, args: list[Argument], inline: bool = False, bodyInHeader: bool = False,
visibility: str = "priv", explicit: bool = False, baseConstructors: list[str] | None = None,
body: str = "") -> None:
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: bool) -> str:
decorators = []
if self.explicit:
decorators.append('explicit')
if self.inline and declaring:
decorators.append('inline')
if decorators:
return f'{" ".join(decorators)} '
return ''
def getInitializationList(self, cgClass: CGClass) -> str:
items = [str(c) for c in self.baseConstructors]
for m in cgClass.members:
if not m.static:
initialize = m.body
if initialize:
items.append(f"{m.name}({initialize})")
if len(items) > 0:
joinedItems = ",\n ".join(items)
return f'\n : {joinedItems}'
return ''
def getBody(self, cgClass: CGClass) -> str:
initializers = [f" parent: {self.baseConstructors[0]}"]
joinedInitializers = '\n'.join(initializers)
return (
f"{self.body}"
f"let mut ret = Rc::new({cgClass.name} {{\n"
f"{joinedInitializers}\n"
"});\n"
"// Note: callback cannot be moved after calling init.\n"
"match Rc::get_mut(&mut ret) {\n"
f" Some(ref mut callback) => callback.parent.init({self.args[0].name}, {self.args[1].name}),\n"
" None => unreachable!(),\n"
"};\n"
"ret"
)
def declare(self, cgClass: CGClass) -> str:
args = ', '.join([a.declare() for a in self.args])
body = f' {self.getBody(cgClass)}'
body = stripTrailingWhitespace(body.replace('\n', '\n '))
if len(body) > 0:
body += '\n'
body = f' {{\n{body}}}'
name = cgClass.getNameString().replace(': DomTypes', '')
return f"""
pub unsafe fn {self.getDecorators(True)}new({args}) -> Rc<{name}>{body}
"""
def define(self, cgClass: CGClass) -> str:
if self.bodyInHeader:
return ''
args = ', '.join([a.define() for a in self.args])
body = f' {self.getBody(cgClass)}'
trimmedBody = stripTrailingWhitespace(body.replace('\n', '\n '))
body = f'\n{trimmedBody}'
if len(body) > 0:
body += '\n'
className = cgClass.getNameString()
return f"""
{self.getDecorators(False)}
{className}::{className}({args}){self.getInitializationList(cgClass)}
{{{body}}}
"""
class ClassMember(ClassItem):
def __init__(self, name: str | None, type: str, visibility: str = "priv", static: bool = False,
body: str | None = None) -> None:
self.type = type
self.static = static
self.body = body
ClassItem.__init__(self, name, visibility)
def declare(self, cgClass: CGClass) -> str:
return f'{self.visibility} {self.name}: {self.type},\n'
def define(self, cgClass: CGClass) -> str:
if not self.static:
return ''
if self.body:
body = f" = {self.body}"
else:
body = ""
return f'{self.type} {cgClass.getNameString()}::{self.name}{body};\n'
class CGClass(CGThing):
def __init__(self,
name: str,
bases: list[ClassBase] = [],
members: list[ClassMember] = [],
constructors: list[ClassConstructor] = [],
destructor: ClassItem | None = None,
methods: list[ClassMethod] = [],
typedefs: list[ClassItem] = [],
enums: list[ClassItem] = [],
unions: list[ClassItem] =[],
templateArgs: list[Argument] | None = [],
templateSpecialization: list[str] = [],
disallowCopyConstruction: bool = False,
indent: str = '',
decorators: str = '',
extradeclarations: str = '') -> None:
CGThing.__init__(self)
self.name = name
self.bases = bases
self.members = members
self.constructors = constructors
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.disallowCopyConstruction = disallowCopyConstruction
self.indent = indent
self.decorators = decorators
self.extradeclarations = extradeclarations
def getNameString(self) -> str:
className = self.name
if self.templateSpecialization:
className = f"{className}<{', '.join([str(a) for a in self.templateSpecialization])}>"
return className
def define(self) -> str:
result = ''
if self.templateArgs:
templateArgs = [a.declare() for a in self.templateArgs]
templateArgs = templateArgs[len(self.templateSpecialization):]
result = f"{result}{self.indent}template <{','.join([str(a) for a in templateArgs])}>\n"
if self.templateSpecialization:
specialization = f"<{', '.join([str(a) for a in self.templateSpecialization])}>"
else:
specialization = ''
myself = ''
if self.decorators != '':
myself += f'{self.decorators}\n'
myself += f'{self.indent}pub struct {self.name}{specialization}'
result += myself
assert len(self.bases) == 1
result += ' {\n'
if self.bases:
parent_name = self.bases[0].name
assert parent_name is not None
self.members = [ClassMember("parent", parent_name, "pub")] + self.members
result += CGIndenter(CGGeneric(self.extradeclarations),
len(self.indent)).define()
def declareMembers(cgClass: CGClass, memberList: Iterable[ClassItem]) -> str:
result = ''
for member in memberList:
declaration = member.declare(cgClass)
declaration = CGIndenter(CGGeneric(declaration)).define()
result = f"{result}{declaration}"
return result
if self.disallowCopyConstruction:
class DisallowedCopyConstructor(ClassItem):
def __init__(self) -> None:
self.visibility = "private"
def declare(self, cgClass: CGClass) -> str:
name = cgClass.getNameString()
return (f"{name}(const {name}&) MOZ_DELETE;\n"
f"void operator=(const {name}) MOZ_DELETE;\n")
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 = f"{result}{memberString}"
result += f'{self.indent}}}\n\n'
result += f'impl{specialization} {self.name}{specialization.replace(": DomTypes", "")} {{\n'
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 = f"{result}{memberString}"
result += "}"
return result
class CGProxySpecialOperation(CGPerSignatureCall):
templateValues: dict[str, Any] | None
def __init__(self, descriptor: Descriptor, operationName: str) -> None:
nativeName = MakeNativeName(descriptor.binaryNameFor(operationName, False))
operation = descriptor.operations[operationName]
assert isinstance(operation, IDLMethod)
assert len(operation.signatures()) == 1
signature = operation.signatures()[0]
(returnType, arguments) = signature
if operation.isGetter() and not returnType.nullable():
returnType = IDLNullableType(returnType.location, returnType)
CGPerSignatureCall.__init__(self, returnType, [], arguments, nativeName,
False, descriptor, operation,
len(arguments))
if operation.isSetter():
argument = arguments[1]
info = getJSToNativeConversionInfo(
argument.type, descriptor,
exceptionCode="return false;")
template = info.template
declType = info.declType
templateValues = {
"val": "value.handle()",
}
self.cgRoot.prepend(instantiateJSToNativeConversionTemplate(
template, templateValues, declType, argument.identifier.name))
self.cgRoot.prepend(CGGeneric("rooted!(&in(cx) let value = desc.value_);"))
def getArguments(self) -> list[tuple[FakeArgument | IDLArgument, str]]:
args = [(a, process_arg(a.identifier.name, a)) for a in self.arguments]
return args
def wrap_return_value(self) -> str:
if isinstance(self.idlNode, IDLMethod) and not self.idlNode.isGetter() or self.templateValues is None:
return ""
wrap = CGGeneric(wrapForType(**self.templateValues))
wrap = CGIfWrapper("let Some(result) = result", wrap)
return f"\n{wrap.define()}"
class CGProxyIndexedGetter(CGProxySpecialOperation):
def __init__(self, descriptor: Descriptor, templateValues: dict[str, Any] | None = None) -> None:
self.templateValues = templateValues
CGProxySpecialOperation.__init__(self, descriptor, 'IndexedGetter')
class CGProxyIndexedSetter(CGProxySpecialOperation):
def __init__(self, descriptor: Descriptor) -> None:
CGProxySpecialOperation.__init__(self, descriptor, 'IndexedSetter')
class CGProxyNamedOperation(CGProxySpecialOperation):
def __init__(self, descriptor: Descriptor, name: str) -> None:
CGProxySpecialOperation.__init__(self, descriptor, name)
def define(self) -> str:
argName = self.arguments[0].identifier.name
return (f'let {argName} = jsid_to_string(cx, Handle::from_raw(id)).expect("Not a string-convertible JSID?");\n'
"let this = UnwrapProxy::<D>(proxy);\n"
"let this = &*this;\n"
f"{CGProxySpecialOperation.define(self)}")
class CGProxyNamedGetter(CGProxyNamedOperation):
def __init__(self, descriptor: Descriptor, templateValues: dict[str, Any] | None = None) -> None:
self.templateValues = templateValues
CGProxySpecialOperation.__init__(self, descriptor, 'NamedGetter')
class CGProxyNamedPresenceChecker(CGProxyNamedGetter):
def __init__(self, descriptor: Descriptor) -> None:
CGProxyNamedGetter.__init__(self, descriptor)
class CGProxyNamedSetter(CGProxyNamedOperation):
def __init__(self, descriptor: Descriptor) -> None:
CGProxySpecialOperation.__init__(self, descriptor, 'NamedSetter')
class CGProxyNamedDeleter(CGProxyNamedOperation):
def __init__(self, descriptor: Descriptor) -> None:
CGProxySpecialOperation.__init__(self, descriptor, 'NamedDeleter')
def define(self) -> str:
argName = self.arguments[0].identifier.name
return ("if !id.is_symbol() {\n"
f' let {argName} = match jsid_to_string(cx, Handle::from_raw(id)) {{\n'
" Some(val) => val,\n"
" None => {\n"
" throw_type_error(cx.raw_cx(), c\"Not a string-convertible JSID\");\n"
" return false;\n"
" }\n"
" };\n"
" let this = UnwrapProxy::<D>(proxy);\n"
" let this = &*this;\n"
f" {CGProxySpecialOperation.define(self)}"
"}\n")
class CGProxyUnwrap(CGAbstractMethod):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('RawHandleObject', 'obj')]
CGAbstractMethod.__init__(self, descriptor, "UnwrapProxy",
f'*const {descriptor.concreteType}', args,
alwaysInline=True, unsafe=True,
templateArgs=['D: DomTypes'])
def definition_body(self) -> CGThing:
return CGGeneric(f"""
let mut slot = UndefinedValue();
GetProxyReservedSlot(obj.get(), 0, &mut slot);
let box_ = slot.to_private() as *const {self.descriptor.concreteType};
return box_;""")
class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut RawJSContext', 'cx'), Argument('RawHandleObject', 'proxy'),
Argument('RawHandleId', 'id'),
Argument('RawMutableHandle<PropertyDescriptor>', 'mut desc'),
Argument('*mut bool', 'is_none')]
CGAbstractExternMethod.__init__(self, descriptor, "getOwnPropertyDescriptor",
"bool", args, templateArgs=['D: DomTypes'])
self.descriptor = descriptor
def getBody(self) -> str:
indexedGetter = self.descriptor.operations['IndexedGetter']
get = "let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());\n"
get += "let mut cx = CurrentRealm::assert(&mut cx);\n"
get += "let cx = &mut cx;\n"
if self.descriptor.isMaybeCrossOriginObject():
get += dedent(
"""
if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
if !proxyhandler::cross_origin_get_own_property_helper(
cx, proxy, CROSS_ORIGIN_PROPERTIES.get(), id, desc, &mut *is_none
) {
return false;
}
if *is_none {
return proxyhandler::cross_origin_property_fallback::<D>(cx, proxy, id, desc, &mut *is_none);
}
return true;
}
// Safe to enter the Realm of proxy now.
let mut cx = AutoRealm::new_from_handle(cx, Handle::from_raw(proxy));
let cx = &mut cx;
""")
if indexedGetter:
get += "let index = get_array_index_from_id(Handle::from_raw(id));\n"
attrs = "JSPROP_ENUMERATE"
if self.descriptor.operations['IndexedSetter'] is None:
attrs += " | JSPROP_READONLY"
fillDescriptor = ("set_property_descriptor(\n"
" MutableHandle::from_raw(desc),\n"
" rval.handle(),\n"
f" ({attrs}) as u32,\n"
" &mut *is_none\n"
");\n"
"return true;")
templateValues = {
'jsvalRef': 'rval.handle_mut()',
'successCode': fillDescriptor,
'pre': 'rooted!(&in(cx) let mut rval = UndefinedValue());'
}
get += ("if let Some(index) = index {\n"
" let this = UnwrapProxy::<D>(proxy);\n"
" let this = &*this;\n"
f"{CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()}\n"
"}\n")
if self.descriptor.supportsNamedProperties():
attrs = []
if not self.descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties"):
attrs.append("JSPROP_ENUMERATE")
if self.descriptor.operations['NamedSetter'] is None:
attrs.append("JSPROP_READONLY")
if attrs:
attrs = " | ".join(attrs)
else:
attrs = "0"
fillDescriptor = ("set_property_descriptor(\n"
" MutableHandle::from_raw(desc),\n"
" rval.handle(),\n"
f" ({attrs}) as u32,\n"
" &mut *is_none\n"
");\n"
"return true;")
templateValues = {
'jsvalRef': 'rval.handle_mut()',
'successCode': fillDescriptor,
'pre': 'rooted!(&in(cx) let mut rval = UndefinedValue());'
}
condition = "id.is_string() || id.is_int()"
if indexedGetter:
condition = f"index.is_none() && ({condition})"
namedGet = f"""
if {condition} {{
let mut has_on_proto = false;
if !has_property_on_prototype(cx.raw_cx(), proxy_lt, id_lt, &mut has_on_proto) {{
return false;
}}
if !has_on_proto {{
{CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues), 8).define()}
}}
}}
"""
else:
namedGet = ""
return f"""{get}\
rooted!(&in(cx) let mut expando = ptr::null_mut::<JSObject>());
get_expando_object(proxy, expando.handle_mut());
//if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) {{
let proxy_lt = Handle::from_raw(proxy);
let id_lt = Handle::from_raw(id);
if !expando.is_null() {{
rooted!(&in(cx) let mut ignored = ptr::null_mut::<JSObject>());
if !JS_GetPropertyDescriptorById(cx.raw_cx(), expando.handle().into(), id, desc, ignored.handle_mut().into(), is_none) {{
return false;
}}
if !*is_none {{
// Pretend the property lives on the wrapper.
return true;
}}
}}
{namedGet}\
true"""
def definition_body(self) -> CGThing:
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut RawJSContext', 'cx'), Argument('RawHandleObject', 'proxy'),
Argument('RawHandleId', 'id'),
Argument('RawHandle<PropertyDescriptor>', 'desc'),
Argument('*mut ObjectOpResult', 'opresult')]
CGAbstractExternMethod.__init__(self, descriptor, "defineProperty", "bool", args, templateArgs=['D: DomTypes'])
self.descriptor = descriptor
def getBody(self) -> str:
set = "let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());\n"
set += "let mut cx = CurrentRealm::assert(&mut cx);\n"
set += "let cx = &mut cx;\n"
if self.descriptor.isMaybeCrossOriginObject():
set += dedent(
"""
if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
return proxyhandler::report_cross_origin_denial::<D>(cx, id, "define");
}
// Safe to enter the Realm of proxy now.
let mut cx = AutoRealm::new_from_handle(cx, Handle::from_raw(proxy));
let cx = &mut cx;
""")
indexedSetter = self.descriptor.operations['IndexedSetter']
if indexedSetter:
set += ("let index = get_array_index_from_id(Handle::from_raw(id));\n"
"if let Some(index) = index {\n"
" let this = UnwrapProxy::<D>(proxy);\n"
" let this = &*this;\n"
f"{CGIndenter(CGProxyIndexedSetter(self.descriptor)).define()}"
" return (*opresult).succeed();\n"
"}\n")
elif self.descriptor.operations['IndexedGetter']:
set += ("if get_array_index_from_id(Handle::from_raw(id)).is_some() {\n"
" return (*opresult).failNoIndexedSetter();\n"
"}\n")
namedSetter = self.descriptor.operations['NamedSetter']
if namedSetter:
if self.descriptor.hasLegacyUnforgeableMembers:
raise TypeError("Can't handle a named setter on an interface that has "
"unforgeables. Figure out how that should work!")
set += ("if id.is_string() || id.is_int() {\n"
f"{CGIndenter(CGProxyNamedSetter(self.descriptor)).define()}"
" return (*opresult).succeed();\n"
"}\n")
elif self.descriptor.supportsNamedProperties():
set += ("if id.is_string() || id.is_int() {\n"
f"{CGIndenter(CGProxyNamedGetter(self.descriptor)).define()}"
" if result.is_some() {\n"
" return (*opresult).fail_no_named_setter();\n"
" }\n"
"}\n")
set += f"return proxyhandler::define_property(cx.raw_cx(), {', '.join(a.name for a in self.args[1:])});"
return set
def definition_body(self) -> CGThing:
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_delete(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut RawJSContext', 'cx'), Argument('RawHandleObject', 'proxy'),
Argument('RawHandleId', 'id'),
Argument('*mut ObjectOpResult', 'res')]
CGAbstractExternMethod.__init__(self, descriptor, "delete", "bool", args, templateArgs=['D: DomTypes'])
self.descriptor = descriptor
def getBody(self) -> str:
set = "let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());\n"
set += "let mut cx = CurrentRealm::assert(&mut cx);\n"
set += "let cx = &mut cx;\n"
if self.descriptor.isMaybeCrossOriginObject():
set += dedent(
"""
if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
return proxyhandler::report_cross_origin_denial::<D>(cx, id, "delete");
}
// Safe to enter the Realm of proxy now.
let mut cx = AutoRealm::new_from_handle(cx, Handle::from_raw(proxy));
let cx = &mut cx;
""")
if self.descriptor.operations['NamedDeleter']:
if self.descriptor.hasLegacyUnforgeableMembers:
raise TypeError("Can't handle a deleter on an interface that has "
"unforgeables. Figure out how that should work!")
set += CGProxyNamedDeleter(self.descriptor).define()
set += f"return proxyhandler::delete(cx.raw_cx(), {', '.join(a.name for a in self.args[1:])});"
return set
def definition_body(self) -> CGThing:
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut RawJSContext', 'cx'),
Argument('RawHandleObject', 'proxy'),
Argument('RawMutableHandleIdVector', 'props')]
CGAbstractExternMethod.__init__(self, descriptor, "own_property_keys", "bool", args,
templateArgs=['D: DomTypes'])
self.descriptor = descriptor
def getBody(self) -> str:
body = dedent(
"""
let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());
let mut cx = CurrentRealm::assert(&mut cx);
let cx = &mut cx;
let unwrapped_proxy = UnwrapProxy::<D>(proxy);
""")
if self.descriptor.isMaybeCrossOriginObject():
body += dedent(
"""
if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
return proxyhandler::cross_origin_own_property_keys(
cx, proxy, CROSS_ORIGIN_PROPERTIES.get(), props
);
}
// Safe to enter the Realm of proxy now.
let mut cx = AutoRealm::new_from_handle(cx, Handle::from_raw(proxy));
let cx = &mut cx;
""")
if self.descriptor.operations['IndexedGetter']:
if "Length" in self.descriptor.cxMethods:
length_call = "(*unwrapped_proxy).Length(cx)"
else:
length_call = "(*unwrapped_proxy).Length()"
body += dedent(
"""
for i in 0..""" + length_call + """ {
rooted!(&in(cx) let mut rooted_jsid: jsid);
int_to_jsid(i as i32, rooted_jsid.handle_mut());
AppendToIdVector(props, rooted_jsid.handle());
}
""")
if self.descriptor.supportsNamedProperties():
body += dedent(
"""
for name in (*unwrapped_proxy).SupportedPropertyNames() {
let cstring = CString::new(name).unwrap();
let jsstring = JS_AtomizeAndPinString(cx.raw_cx(), cstring.as_ptr());
rooted!(&in(cx) let rooted = jsstring);
rooted!(&in(cx) let mut rooted_jsid: jsid);
RUST_INTERNED_STRING_TO_JSID(cx.raw_cx(), rooted.handle().get(), rooted_jsid.handle_mut());
AppendToIdVector(props, rooted_jsid.handle());
}
""")
body += dedent(
"""
rooted!(&in(cx) let mut expando = ptr::null_mut::<JSObject>());
get_expando_object(proxy, expando.handle_mut());
if !expando.is_null() &&
!GetPropertyKeys(cx.raw_cx(), expando.handle(), JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props) {
return false;
}
true
""")
return body
def definition_body(self) -> CGThing:
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor) -> None:
assert (descriptor.operations["IndexedGetter"]
and descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties")
or descriptor.isMaybeCrossOriginObject())
args = [Argument('*mut RawJSContext', 'cx'),
Argument('RawHandleObject', 'proxy'),
Argument('RawMutableHandleIdVector', 'props')]
CGAbstractExternMethod.__init__(self, descriptor,
"getOwnEnumerablePropertyKeys", "bool", args, templateArgs=['D: DomTypes'])
self.descriptor = descriptor
def getBody(self) -> str:
body = dedent(
"""
let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());
let unwrapped_proxy = UnwrapProxy::<D>(proxy);
let mut cx = CurrentRealm::assert(&mut cx);
let cx = &mut cx;
""")
if self.descriptor.isMaybeCrossOriginObject():
body += dedent(
"""
if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
// There are no enumerable cross-origin props, so we're done.
return true;
}
// Safe to enter the Realm of proxy now.
let mut cx = AutoRealm::new_from_handle(cx, Handle::from_raw(proxy));
let cx = &mut cx;
""")
if self.descriptor.operations['IndexedGetter']:
if "Length" in self.descriptor.cxMethods:
length_call = "(*unwrapped_proxy).Length(cx)"
else:
length_call = "(*unwrapped_proxy).Length()"
body += dedent(
"""
for i in 0..""" + length_call + """ {
rooted!(&in(cx) let mut rooted_jsid: jsid);
int_to_jsid(i as i32, rooted_jsid.handle_mut());
AppendToIdVector(props, rooted_jsid.handle());
}
""")
body += dedent(
"""
rooted!(&in(cx) let mut expando = ptr::null_mut::<JSObject>());
get_expando_object(proxy, expando.handle_mut());
if !expando.is_null() &&
!GetPropertyKeys(cx.raw_cx(), expando.handle(), JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props) {
return false;
}
true
""")
return body
def definition_body(self) -> CGThing:
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut RawJSContext', 'cx'), Argument('RawHandleObject', 'proxy'),
Argument('RawHandleId', 'id'), Argument('*mut bool', 'bp')]
CGAbstractExternMethod.__init__(self, descriptor, "hasOwn", "bool", args, templateArgs=['D: DomTypes'])
self.descriptor = descriptor
def getBody(self) -> str:
indexedGetter = self.descriptor.operations['IndexedGetter']
indexed = dedent("""
let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());
let mut cx = CurrentRealm::assert(&mut cx);
let cx = &mut cx;
""")
if self.descriptor.isMaybeCrossOriginObject():
indexed += dedent(
"""
if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
return proxyhandler::cross_origin_has_own(
cx, proxy, CROSS_ORIGIN_PROPERTIES.get(), id, bp
);
}
// Safe to enter the Realm of proxy now.
let mut cx = AutoRealm::new_from_handle(cx, Handle::from_raw(proxy));
let cx = &mut cx;
""")
if indexedGetter:
indexed += ("let index = get_array_index_from_id(Handle::from_raw(id));\n"
"if let Some(index) = index {\n"
" let this = UnwrapProxy::<D>(proxy);\n"
" let this = &*this;\n"
f"{CGIndenter(CGProxyIndexedGetter(self.descriptor)).define()}\n"
" *bp = result.is_some();\n"
" return true;\n"
"}\n\n")
condition = "id.is_string() || id.is_int()"
if indexedGetter:
condition = f"index.is_none() && ({condition})"
if self.descriptor.supportsNamedProperties():
named = f"""
if {condition} {{
let mut has_on_proto = false;
if !has_property_on_prototype(cx.raw_cx(), proxy_lt, id_lt, &mut has_on_proto) {{
return false;
}}
if !has_on_proto {{
{CGIndenter(CGProxyNamedGetter(self.descriptor), 8).define()}
*bp = result.is_some();
return true;
}}
}}
"""
else:
named = ""
return f"""{indexed}\
rooted!(&in(cx) let mut expando = ptr::null_mut::<JSObject>());
let proxy_lt = Handle::from_raw(proxy);
let id_lt = Handle::from_raw(id);
get_expando_object(proxy, expando.handle_mut());
if !expando.is_null() {{
let ok = JS_HasPropertyById(cx.raw_cx(), expando.handle().into(), id, bp);
if !ok || *bp {{
return ok;
}}
}}
{named}\
*bp = false;
true"""
def definition_body(self) -> CGThing:
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_get(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut RawJSContext', 'cx'), Argument('RawHandleObject', 'proxy'),
Argument('RawHandleValue', 'receiver'), Argument('RawHandleId', 'id'),
Argument('RawMutableHandleValue', 'vp')]
CGAbstractExternMethod.__init__(self, descriptor, "get", "bool", args, templateArgs=['D: DomTypes'])
self.descriptor = descriptor
def getBody(self) -> str:
if self.descriptor.isMaybeCrossOriginObject():
maybeCrossOriginGet = dedent(
"""
if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
return proxyhandler::cross_origin_get::<D>(cx, proxy, receiver, id, vp);
}
// Safe to enter the Realm of proxy now.
let mut cx = AutoRealm::new_from_handle(cx, Handle::from_raw(proxy));
let cx = &mut cx;
""")
else:
maybeCrossOriginGet = ""
getFromExpando = """\
rooted!(&in(cx) let mut expando = ptr::null_mut::<JSObject>());
get_expando_object(proxy, expando.handle_mut());
if !expando.is_null() {
let mut hasProp = false;
if !JS_HasPropertyById(cx.raw_cx(), expando.handle().into(), id, &mut hasProp) {
return false;
}
if hasProp {
return JS_ForwardGetPropertyTo(cx.raw_cx(), expando.handle().into(), id, receiver, vp);
}
}"""
templateValues = {
'jsvalRef': 'vp_lt',
'successCode': 'return true;',
}
indexedGetter = self.descriptor.operations['IndexedGetter']
if indexedGetter:
getIndexedOrExpando = ("let index = get_array_index_from_id(id_lt);\n"
"if let Some(index) = index {\n"
" let this = UnwrapProxy::<D>(proxy);\n"
" let this = &*this;\n"
f"{CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()}")
trimmedGetFromExpando = stripTrailingWhitespace(getFromExpando.replace('\n', '\n '))
getIndexedOrExpando += f"""
// Even if we don't have this index, we don't forward the
// get on to our expando object.
}} else {{
{trimmedGetFromExpando}
}}
"""
else:
getIndexedOrExpando = f"{getFromExpando}\n"
if self.descriptor.supportsNamedProperties():
condition = "id.is_string() || id.is_int()"
if indexedGetter:
condition = f"index.is_none() && ({condition})"
getNamed = (f"if {condition} {{\n"
f"{CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define()}}}\n")
else:
getNamed = ""
return f"""
//MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
//"Should not have a XrayWrapper here");
let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());
let mut cx = CurrentRealm::assert(&mut cx);
let cx = &mut cx;
{maybeCrossOriginGet}
let proxy_lt = Handle::from_raw(proxy);
let mut vp_lt = MutableHandle::from_raw(vp);
let id_lt = Handle::from_raw(id);
let receiver_lt = Handle::from_raw(receiver);
{getIndexedOrExpando}
let mut found = false;
if !get_property_on_prototype(cx.raw_cx(), proxy_lt, receiver_lt, id_lt, &mut found, vp_lt.reborrow()) {{
return false;
}}
if found {{
return true;
}}
{getNamed}
vp.set(UndefinedValue());
true"""
def definition_body(self) -> CGThing:
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_getPrototype(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut RawJSContext', 'cx'), Argument('RawHandleObject', 'proxy'),
Argument('RawMutableHandleObject', 'proto')]
CGAbstractExternMethod.__init__(self, descriptor, "getPrototype", "bool", args, templateArgs=["D: DomTypes"])
assert descriptor.isMaybeCrossOriginObject()
self.descriptor = descriptor
def getBody(self) -> str:
return dedent(
"""
let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());
let mut realm = CurrentRealm::assert(&mut cx);
proxyhandler::maybe_cross_origin_get_prototype::<D>(&mut realm, proxy, GetProtoObject::<D>, proto)
""")
def definition_body(self) -> CGThing:
return CGGeneric(self.getBody())
class CGDOMJSProxyHandler_className(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut RawJSContext', 'cx'), Argument('RawHandleObject', '_proxy')]
CGAbstractExternMethod.__init__(self, descriptor, "className", "*const libc::c_char", args, doesNotPanic=True)
self.descriptor = descriptor
def getBody(self) -> str:
return str_to_cstr_ptr(self.descriptor.name)
def definition_body(self) -> CGThing:
return CGGeneric(self.getBody())
class CGAbstractClassHook(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor, name: str, returnType: str, args: list[Argument], doesNotPanic: bool = False) -> None:
CGAbstractExternMethod.__init__(self, descriptor, name, returnType,
args, templateArgs=['D: DomTypes'])
def definition_body_prologue(self) -> CGThing:
return CGGeneric(f"""
let this = native_from_object_static::<{self.descriptor.concreteType}>(obj).unwrap();
""")
def definition_body(self) -> CGThing:
return CGList([
self.definition_body_prologue(),
self.generate_code(),
])
def generate_code(self) -> CGThing:
raise NotImplementedError
def finalizeHook(descriptor: Descriptor, hookName: str, context: str) -> str:
if descriptor.isGlobal():
release = "finalize_global(obj, this);"
elif descriptor.weakReferenceable:
release = "finalize_weak_referenceable(obj, this);"
else:
release = "finalize_common(this);"
return release
class CGClassTraceHook(CGAbstractClassHook):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut JSTracer', 'trc'), Argument('*mut JSObject', 'obj')]
CGAbstractClassHook.__init__(self, descriptor, TRACE_HOOK_NAME, 'void',
args, doesNotPanic=True)
self.traceGlobal = descriptor.isGlobal()
def generate_code(self) -> CGThing:
body = [CGGeneric("if this.is_null() { return; } // GC during obj creation\n"
f"(*this).trace({self.args[0].name});")]
if self.traceGlobal:
body += [CGGeneric("trace_global(trc, obj);")]
return CGList(body, "\n")
class CGClassConstructHook(CGAbstractExternMethod):
def __init__(self, descriptor: Descriptor, constructor: IDLConstructor | None = None) -> None:
args = [Argument('*mut RawJSContext', 'cx'), Argument('u32', 'argc'), Argument('*mut JSVal', 'vp')]
name = CONSTRUCT_HOOK_NAME
if constructor:
name += f"_{constructor.identifier.name}"
else:
constructor = descriptor.interface.ctor()
assert constructor
CGAbstractExternMethod.__init__(self, descriptor, name, 'bool', args, templateArgs=['D: DomTypes'])
self.constructor = constructor
self.exposureSet = descriptor.interface.exposureSet
def definition_body(self) -> CGThing:
preamble = """let mut cx = JSContext::from_ptr(ptr::NonNull::new(cx).unwrap());
let cx = &mut cx;
let args = CallArgs::from_vp(vp, argc);
let global = D::GlobalScope::from_object(JS_CALLEE(cx.raw_cx(), vp).to_object());
"""
if self.constructor.isHTMLConstructor():
signatures = self.constructor.signatures()
assert len(signatures) == 1
constructorCall = f"""
<D as DomHelpers<D>>::call_html_constructor::<D::{self.descriptor.name}>(
cx,
&args,
&global,
PrototypeList::ID::{MakeNativeName(self.descriptor.name)},
CreateInterfaceObjects::<D>,
)
"""
else:
ctorName = GetConstructorNameForReporting(self.descriptor, self.constructor)
name = self.constructor.identifier.name
nativeName = MakeNativeName(self.descriptor.binaryNameFor(name, True))
if len(self.exposureSet) == 1:
args = [
f"global.downcast::<D::{list(self.exposureSet)[0]}>().unwrap()",
"Some(desired_proto)"
]
else:
args = [
"global",
"Some(desired_proto)",
]
if nativeName not in self.descriptor.cxMethods:
args += ['CanGc::from_cx(cx)']
constructor = CGMethodCall(args, nativeName, True, self.descriptor, self.constructor)
constructorCall = f"""
call_default_constructor::<D>(
cx,
&args,
&global,
PrototypeList::ID::{MakeNativeName(self.descriptor.name)},
\"{ctorName}\",
CreateInterfaceObjects::<D>,
|cx: &mut JSContext, args: &CallArgs, global: &D::GlobalScope, desired_proto: HandleObject| {{
{constructor.define()}
}}
)
"""
return CGList([CGGeneric(preamble), CGGeneric(constructorCall)])
class CGClassFinalizeHook(CGAbstractClassHook):
def __init__(self, descriptor: Descriptor) -> None:
args = [Argument('*mut GCContext', '_cx'), Argument('*mut JSObject', 'obj')]
CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME,
'void', args)
def generate_code(self) -> CGThing:
return CGGeneric(finalizeHook(self.descriptor, self.name, self.args[0].name))
class CGDOMJSProxyHandlerDOMClass(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
def define(self) -> str:
return f"""
pub static Class: ThreadUnsafeOnceLock<DOMClass> = ThreadUnsafeOnceLock::new();
pub(crate) fn init_proxy_handler_dom_class<D: DomTypes>() {{
Class.set({DOMClass(self.descriptor)});
}}
"""
class CGInterfaceTrait(CGThing):
def __init__(self, descriptor: Descriptor, descriptorProvider: DescriptorProvider) -> None:
CGThing.__init__(self)
def attribute_arguments(attribute_type: IDLType,
argument: IDLType | None = None,
cx_no_gc: bool = False,
cx: bool = False,
realm: bool = False,
inRealm: bool = False,
canGc: bool = False,
retval: bool = False
) -> Iterable[tuple[str, str]]:
if cx_no_gc:
yield "cx", "&JSContext"
elif cx:
yield "cx", "&mut JSContext"
elif realm:
yield "realm", "&mut CurrentRealm"
safe_cx = cx or cx_no_gc or realm
if typeNeedsCx(attribute_type, retval) and not safe_cx:
yield "cx", "SafeJSContext"
if argument:
yield "value", argument_type(descriptor, argument)
if inRealm and not safe_cx:
yield "_comp", "InRealm"
if canGc and not safe_cx:
yield "_can_gc", "CanGc"
if retval and returnTypeNeedsOutparam(attribute_type):
yield "retval", outparamTypeFromReturnType(attribute_type)
def members() -> Iterator[tuple[str, Iterable[tuple[str, str]], str, bool]]:
used_names = set()
for m in descriptor.interface.members:
if (m.isMethod()
and not m.isMaplikeOrSetlikeOrIterableMethod()
and (not m.isIdentifierLess() or (m.isStringifier() and not m.underlyingAttr))
and not m.isDefaultToJSON()):
name = CGSpecializedMethod.makeNativeName(descriptor, m)
infallible = 'infallible' in descriptor.getExtendedAttributes(m)
for idx, (rettype, arguments) in enumerate(m.signatures()):
rettype = cast(IDLType, rettype)
arguments = cast(list[IDLArgument], arguments)
arguments = method_arguments(descriptor, rettype, arguments,
cx_no_gc=name in descriptor.cx_no_gcMethods,
cx=name in descriptor.cxMethods,
realm=name in descriptor.realmMethods,
inRealm=name in descriptor.inRealmMethods,
canGc=name in descriptor.canGcMethods)
rettype = return_type(descriptor, rettype, infallible)
yield f"{name}{'_' * idx}", arguments, rettype, m.isStatic()
elif m.isAttr():
name = CGSpecializedGetter.makeNativeName(descriptor, m)
if name in used_names:
continue
used_names.add(name)
infallible = 'infallible' in descriptor.getExtendedAttributes(m, getter=True)
yield (name,
attribute_arguments(
m.type,
cx_no_gc=name in descriptor.cx_no_gcMethods,
cx=name in descriptor.cxMethods or isEventHandlerCallback(m),
realm=name in descriptor.realmMethods,
inRealm=name in descriptor.inRealmMethods,
canGc=name in descriptor.canGcMethods,
retval=True
),
return_type(descriptor, m.type, infallible),
m.isStatic())
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(
m.type,
m.type,
cx_no_gc=name in descriptor.cx_no_gcMethods,
cx=name in descriptor.cxMethods or descriptor.implicitCxSetters or isEventHandlerCallback(m),
realm=name in descriptor.realmMethods,
inRealm=name in descriptor.inRealmMethods,
canGc=name in descriptor.canGcMethods,
retval=False,
),
rettype,
m.isStatic())
if descriptor.proxy or descriptor.isGlobal():
for name, operation in descriptor.operations.items():
if not operation or operation.isStringifier():
continue
assert len(operation.signatures()) == 1
rettype, arguments = operation.signatures()[0]
infallible = 'infallible' in descriptor.getExtendedAttributes(operation)
if operation.isGetter():
if not rettype.nullable():
rettype = IDLNullableType(rettype.location, rettype)
arguments = method_arguments(descriptor, rettype, arguments,
cx_no_gc=name in descriptor.cx_no_gcMethods,
cx=name in descriptor.cxMethods,
realm=name in descriptor.realmMethods,
inRealm=name in descriptor.inRealmMethods,
canGc=name in descriptor.canGcMethods)
if operation.isNamed():
yield "SupportedPropertyNames", [], "Vec<DOMString>", False
else:
arguments = method_arguments(descriptor, rettype, arguments,
cx_no_gc=name in descriptor.cx_no_gcMethods,
cx=name in descriptor.cxMethods,
realm=name in descriptor.realmMethods,
inRealm=name in descriptor.inRealmMethods,
canGc=name in descriptor.canGcMethods)
rettype = return_type(descriptor, rettype, infallible)
yield name, arguments, rettype, False
def fmt(arguments: list[tuple[str, str]], leadingComma: bool = True) -> str:
prefix = "" if not leadingComma else ", "
return prefix + ", ".join(
f"r#{name}: {type_}"
for name, type_ in arguments
)
def contains_unsafe_arg(arguments: list[tuple[str, str]]) -> bool:
if not arguments or len(arguments) == 0:
return False
return functools.reduce((lambda x, y: x or y[1] == '*mut RawJSContext'), arguments, False)
methods: list[CGThing] = []
exposureSet = list(descriptor.interface.exposureSet)
exposedGlobal = "GlobalScope" if len(exposureSet) > 1 else exposureSet[0]
hasLength = False
for name, arguments, rettype, isStatic in members():
if name == "Length":
hasLength = True
arguments = list(arguments)
unsafe = 'unsafe ' if contains_unsafe_arg(arguments) else ''
returnType = f" -> {rettype}" if rettype != '()' else ''
selfArg = "&self" if not isStatic else ""
extra = [("global", f"&D::{exposedGlobal}")] if isStatic else []
if arguments and arguments[0][0] == "cx":
arguments = [arguments[0]] + extra + arguments[1:]
else:
arguments = extra + arguments
methods.append(CGGeneric(
f"{unsafe}fn {name}({selfArg}"
f"{fmt(arguments, leadingComma=not isStatic)}){returnType};\n"
))
def ctorMethod(ctor: IDLMethod, baseName: str | None = None) -> Iterator[CGThing]:
infallible = 'infallible' in descriptor.getExtendedAttributes(ctor)
for (i, (rettype, arguments)) in enumerate(ctor.signatures()):
name = (baseName or ctor.identifier.name) + ('_' * i)
cx = name in descriptor.cxMethods
args = list(method_arguments(descriptor, rettype, arguments, cx=cx))
extra = [
("global", f"&D::{exposedGlobal}"),
("proto", "Option<HandleObject>"),
]
if not cx:
extra += [("can_gc", "CanGc")]
if args and args[0][0] == "cx":
args = [args[0]] + extra + args[1:]
else:
args = extra + args
yield CGGeneric(
f"fn {name}({fmt(args, leadingComma=False)}) -> "
f"{return_type(descriptorProvider, rettype, infallible)};\n"
)
ctor = descriptor.interface.ctor()
if ctor and not ctor.isHTMLConstructor():
methods.extend(list(ctorMethod(ctor, "Constructor")))
for ctor in descriptor.interface.legacyFactoryFunctions:
methods.extend(list(ctorMethod(ctor)))
if descriptor.operations['IndexedGetter'] and not hasLength:
methods.append(CGGeneric("fn Length(&self) -> u32;\n"))
name = descriptor.interface.identifier.name
self.cgRoot = CGWrapper(CGIndenter(CGList(methods, "")),
pre=f"pub trait {name}Methods<D: DomTypes> {{\n",
post="}")
self.empty = not methods
def define(self) -> str:
return self.cgRoot.define()
class CGWeakReferenceableTrait(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
assert descriptor.weakReferenceable
self.code = f"impl WeakReferenceable for {descriptor.interface.identifier.name} {{}}"
def define(self) -> str:
return self.code
class CGForbidDrop(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
assert not descriptor.allowDropImpl
self.code = f"""
impl Drop for {firstCap(descriptor.interface.identifier.name)} {{
fn drop(&mut self) {{
}}
}}
"""
def define(self) -> str:
return self.code
class CGInitStatics(CGThing):
def __init__(self, descriptor: Descriptor) -> None:
CGThing.__init__(self)
def internal(method: IDLMethod) -> str:
return descriptor.internalNameFor(method.identifier.name)
properties = PropertyArrays(descriptor)
all_names = PropertyArrays.arrayNames()
arrays = [getattr(properties, name) for name in all_names]
nonempty = map(lambda x: x.variableName(), filter(lambda x: x.length() != 0, arrays))
specs = [[
f'init_{name}_specs::<D>();',
f'init_{name}_prefs::<D>();',
] for name in nonempty]
flat_specs = [x for xs in specs for x in xs]
specs = '\n'.join(flat_specs)
module = f"crate::codegen::GenericBindings::{toBindingPath(descriptor)}"
relevantMethods = [
m for m in descriptor.interface.members if m.isMethod()
] if not descriptor.interface.isCallback() else []
allOperations = descriptor.operations.keys()
relevantOperations = list(map(lambda x: f"__{x.lower()}", filter(lambda o: o != "Stringifier", allOperations)))
relevantMethods = filter(
lambda m: internal(m) not in relevantOperations,
relevantMethods,
)
relevantMethods = filter(
lambda x: (
not x.isStatic()
or any([r.isPromise() for r, _ in x.signatures()])
),
relevantMethods
)
methods = [f'{module}::init_{internal(m)}_methodinfo::<D>();' for m in relevantMethods]
getters = [
f'init_{internal(m)}_getterinfo::<D>();'
for m in descriptor.interface.members if m.isAttr() and not m.isStatic()
]
setters = [
f'init_{internal(m)}_setterinfo::<D>();'
for m in descriptor.interface.members
if m.isAttr() and (
not m.readonly
or m.getExtendedAttribute("PutForwards")
or m.getExtendedAttribute("Replaceable")
) and not m.isStatic()
]
methods = '\n'.join(methods)
getters = '\n'.join(getters)
setters = '\n'.join(setters)
crossorigin = [
"init_sCrossOriginMethods::<D>();",
"init_sCrossOriginAttributes::<D>();",
"init_cross_origin_properties::<D>();"
] if descriptor.isMaybeCrossOriginObject() else []
crossorigin_joined = '\n'.join(crossorigin)
interface = (
"init_interface_object::<D>();"
if descriptor.interface.hasInterfaceObject()
and not descriptor.interface.isNamespace()
and not descriptor.interface.isCallback()
and not descriptor.interface.getExtendedAttribute("Inline")
else ""
)
nonproxy = (
"init_domjs_class::<D>();"
if not descriptor.proxy
and descriptor.concrete
else ""
)
self.code = f"""
pub(crate) fn init_statics<D: DomTypes>() {{
{interface}
{nonproxy}
{methods}
{getters}
{setters}
{crossorigin_joined}
{specs}
}}
"""
def define(self) -> str:
return self.code
class CGDescriptor(CGThing):
def __init__(self, descriptor: Descriptor, config: Configuration, soleDescriptor: bool) -> None:
CGThing.__init__(self)
assert not descriptor.concrete or not descriptor.interface.isCallback()
reexports = []
def reexportedName(name: str) -> str:
if name.startswith(descriptor.name):
return name
if not soleDescriptor:
return f'{name} as {descriptor.name}{name}'
return name
cgThings: list[CGThing] = []
defaultToJSONMethod = None
unscopableNames = []
for m in descriptor.interface.members:
if (m.isMethod()
and (not m.isIdentifierLess() or m == descriptor.operations["Stringifier"])):
if m.getExtendedAttribute("Unscopable"):
assert not m.isStatic()
unscopableNames.append(m.identifier.name)
if m.isDefaultToJSON():
defaultToJSONMethod = m
elif m.isStatic():
assert descriptor.interface.hasInterfaceObject()
cgThings.append(CGStaticMethod(descriptor, m))
if m.returnsPromise():
cgThings.append(CGStaticMethodJitinfo(m))
elif not descriptor.interface.isCallback():
cgThings.append(CGSpecializedMethod(descriptor, m))
if m.returnsPromise():
cgThings.append(
CGMethodPromiseWrapper(descriptor, m)
)
cgThings.append(CGMemberJITInfo(descriptor, m))
elif m.isAttr():
if m.getExtendedAttribute("Unscopable"):
assert not m.isStatic()
unscopableNames.append(m.identifier.name)
if m.isStatic():
assert descriptor.interface.hasInterfaceObject()
cgThings.append(CGStaticGetter(descriptor, m))
elif not descriptor.interface.isCallback():
cgThings.append(CGSpecializedGetter(descriptor, m))
if m.type.isPromise():
cgThings.append(
CGGetterPromiseWrapper(descriptor, m)
)
if not m.readonly:
if m.isStatic():
assert descriptor.interface.hasInterfaceObject()
cgThings.append(CGStaticSetter(descriptor, m))
elif not descriptor.interface.isCallback():
cgThings.append(CGSpecializedSetter(descriptor, m))
elif m.getExtendedAttribute("PutForwards"):
cgThings.append(CGSpecializedForwardingSetter(descriptor, m))
elif m.getExtendedAttribute("Replaceable"):
cgThings.append(CGSpecializedReplaceableSetter(descriptor, m))
if (not m.isStatic() and not descriptor.interface.isCallback()):
cgThings.append(CGMemberJITInfo(descriptor, m))
if defaultToJSONMethod:
cgThings.append(CGDefaultToJSONMethod(descriptor, defaultToJSONMethod))
cgThings.append(CGMemberJITInfo(descriptor, defaultToJSONMethod))
if descriptor.concrete:
cgThings.append(CGClassFinalizeHook(descriptor))
cgThings.append(CGClassTraceHook(descriptor))
constMembers = [CGConstant(m) for m in descriptor.interface.members if m.isConst()]
if constMembers:
cgThings.append(CGNamespace.build([f"{descriptor.name}Constants"],
CGIndenter(CGList(constMembers)),
public=True))
reexports.append(f'{descriptor.name}Constants')
if descriptor.proxy:
cgThings.append(CGDefineProxyHandler(descriptor))
if descriptor.isMaybeCrossOriginObject():
cgThings.append(CGCrossOriginProperties(descriptor))
properties = PropertyArrays(descriptor)
if defaultToJSONMethod:
cgThings.append(CGCollectJSONAttributesMethod(descriptor, defaultToJSONMethod))
if descriptor.concrete:
if descriptor.proxy:
cgThings.append(CGProxyUnwrap(descriptor))
cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor))
cgThings.append(CGDOMJSProxyHandler_ownPropertyKeys(descriptor))
if descriptor.interface.getExtendedAttribute("LegacyUnenumerableNamedProperties") or \
descriptor.isMaybeCrossOriginObject():
cgThings.append(CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(descriptor))
cgThings.append(CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor))
cgThings.append(CGDOMJSProxyHandler_className(descriptor))
cgThings.append(CGDOMJSProxyHandler_get(descriptor))
cgThings.append(CGDOMJSProxyHandler_hasOwn(descriptor))
if descriptor.isMaybeCrossOriginObject() or descriptor.operations['IndexedSetter'] or \
descriptor.operations['NamedSetter']:
cgThings.append(CGDOMJSProxyHandler_defineProperty(descriptor))
assert not descriptor.operations['IndexedDeleter']
if descriptor.isMaybeCrossOriginObject() or descriptor.operations['NamedDeleter']:
cgThings.append(CGDOMJSProxyHandler_delete(descriptor))
if descriptor.isMaybeCrossOriginObject():
cgThings.append(CGDOMJSProxyHandler_getPrototype(descriptor))
pass
else:
cgThings.append(CGDOMJSClass(descriptor))
if descriptor.isGlobal():
cgThings.append(CGWrapGlobalMethod(descriptor, properties))
else:
cgThings.append(CGWrapMethod(descriptor))
reexports.append('Wrap')
haveUnscopables = False
if not descriptor.interface.isCallback() and not descriptor.interface.isNamespace():
if unscopableNames:
haveUnscopables = True
cgThings.append(
CGList([CGGeneric("const unscopable_names: &[&std::ffi::CStr] = &["),
CGIndenter(CGList([CGGeneric(str_to_cstr(name)) for
name in unscopableNames], ",\n")),
CGGeneric("];\n")], "\n"))
if not descriptor.interface.isCallback():
interfaceTrait = CGInterfaceTrait(descriptor, config.getDescriptorProvider())
cgThings.append(interfaceTrait)
if not interfaceTrait.empty:
reexports.append(f'{descriptor.name}Methods')
legacyWindowAliases = descriptor.interface.legacyWindowAliases
haveLegacyWindowAliases = len(legacyWindowAliases) != 0
if haveLegacyWindowAliases:
cgThings.append(
CGList([CGGeneric("const legacy_window_aliases: &[&std::ffi::CStr] = &["),
CGIndenter(CGList([CGGeneric(str_to_cstr(name)) for
name in legacyWindowAliases], ",\n")),
CGGeneric("];\n")], "\n"))
cgThings.append(CGGeneric(str(properties)))
if not descriptor.interface.getExtendedAttribute("Inline"):
if not descriptor.interface.isCallback() and not descriptor.interface.isNamespace():
cgThings.append(CGGetProtoObjectMethod(descriptor))
reexports.append('GetProtoObject')
cgThings.append(CGPrototypeJSClass(descriptor))
if descriptor.interface.hasInterfaceObject():
if descriptor.interface.ctor():
cgThings.append(CGClassConstructHook(descriptor))
for ctor in descriptor.interface.legacyFactoryFunctions:
cgThings.append(CGClassConstructHook(descriptor, ctor))
if not descriptor.interface.isCallback():
cgThings.append(CGInterfaceObjectJSClass(descriptor))
if descriptor.shouldHaveGetConstructorObjectMethod():
cgThings.append(CGGetConstructorObjectMethod(descriptor))
reexports.append('GetConstructorObject')
if descriptor.register:
cgThings.append(CGDefineDOMInterfaceMethod(descriptor))
reexports.append('DefineDOMInterface')
cgThings.append(CGConstructorEnabled(descriptor))
cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties, haveUnscopables,
haveLegacyWindowAliases))
cgThings.append(CGInitStatics(descriptor))
cgRoot: CGThing = CGList(cgThings, '\n')
cgRoot = CGImports(cgRoot, descriptors=[descriptor], callbacks=[],
dictionaries=[], enums=[], typedefs=[], imports=[
'crate::import::module::*',
], config=config)
cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name),
cgRoot, public=True),
post='\n')
if reexports:
reexports = ', '.join([reexportedName(name) for name in reexports])
namespace = toBindingNamespace(descriptor.name)
cgRoot = CGList([
CGGeneric(f'pub use self::{namespace}::{{{reexports}}};'),
cgRoot
], '\n')
self.cgRoot = cgRoot
def define(self) -> str:
return self.cgRoot.define()
class CGNonNamespacedEnum(CGThing):
def __init__(self, enumName: str, names: list[str], first: int, comment: str = "", deriving: str = "", repr: str = "") -> None:
entries = [f"{names[0]} = {first}"] + names[1:]
entries.append(f'Last = {first + len(entries)}')
entries = [f' {e}' for e in entries]
joinedEntries = ',\n'.join(entries)
enumstr = f"{comment}pub enum {enumName} {{\n{joinedEntries}\n}}\n"
if repr:
enumstr = f"#[repr({repr})]\n{enumstr}"
if deriving:
enumstr = f"#[derive({deriving})]\n{enumstr}"
curr = CGGeneric(enumstr)
curr = CGWrapper(curr, pre='\n', post='\n')
self.node = curr
def define(self) -> str:
return self.node.define()
class CGDictionary(CGThing):
def __init__(self, dictionary: IDLDictionary, descriptorProvider: DescriptorProvider, config: Configuration) -> None:
self.dictionary = dictionary
derivesList = config.getDictConfig(dictionary.identifier.name).get('derives', [])
self.manualImpls = list(filter(lambda t: traitRequiresManualImpl(t, self.dictionary), derivesList))
self.derives = list(filter(lambda t: not traitRequiresManualImpl(t, self.dictionary), derivesList))
if all(CGDictionary(d, descriptorProvider, config).generatable for
d in CGDictionary.getDictionaryDependencies(dictionary)):
self.generatable = True
else:
self.generatable = False
return
self.generic, self.genericSuffix = genericsForType(self.dictionary)
self.memberInfo: list[tuple[IDLArgument, JSToNativeConversionInfo]] = [
(member,
getJSToNativeConversionInfo(member.type,
descriptorProvider,
isMember="Dictionary",
defaultValue=member.defaultValue,
exceptionCode="return Err(());\n"))
for member in dictionary.members]
def define(self) -> str:
if not self.generatable:
return ""
return f"{self.struct()}\n{self.impl()}"
def manualImplClone(self) -> str:
members = []
for m in self.memberInfo:
memberName = self.makeMemberName(m[0].identifier.name)
members += [f" {memberName}: self.{memberName}.clone(),"]
if self.dictionary.parent:
members += [" parent: self.parent.clone(),"]
members = "\n".join(members)
return f"""
#[allow(clippy::clone_on_copy)]
impl{self.generic} Clone for {self.makeClassName(self.dictionary)}{self.genericSuffix} {{
fn clone(&self) -> Self {{
Self {{
{members}
}}
}}
}}
"""
def manualImpl(self, t: str) -> str:
if t == "Clone":
return self.manualImplClone()
raise ValueError(f"Don't know how to impl {t} for dicts.")
def struct(self) -> str:
d = self.dictionary
if d.parent:
assert isinstance(d.parent, IDLDictionary)
typeName = f"{self.makeModuleName(d.parent)}::{self.makeClassName(d.parent)}"
_, parentSuffix = genericsForType(d.parent)
typeName += parentSuffix
if type_needs_tracing(d.parent):
typeName = f"RootedTraceableBox<{typeName}>"
inheritance = f" pub parent: {typeName},\n"
else:
inheritance = ""
memberDecls = [f" pub {self.makeMemberName(m[0].identifier.name)}: {self.getMemberType(m)},"
for m in self.memberInfo]
derive = ["JSTraceable"] + self.derives
default = ""
mustRoot = ""
if self.membersNeedTracing():
mustRoot = "#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]\n"
if not self.hasRequiredFields(self.dictionary):
if d.parent:
inheritanceDefault = " parent: Default::default(),\n"
else:
inheritanceDefault = ""
if not self.membersNeedTracing():
impl = " Self::empty()\n"
else:
memberDefaults = [f" {self.makeMemberName(m[0].identifier.name)}: Default::default(),"
for m in self.memberInfo]
joinedDefaults = '\n'.join(memberDefaults)
impl = (
" Self {\n"
f" {inheritanceDefault}{joinedDefaults}"
" }\n"
)
default = (
f"impl{self.generic} Default for {self.makeClassName(d)}{self.genericSuffix} {{\n"
" fn default() -> Self {\n"
f"{impl}"
" }\n"
"}\n"
)
manualImpls = "\n".join(map(lambda t: self.manualImpl(t), self.manualImpls))
joinedMemberDecls = '\n'.join(memberDecls)
return (
f"#[derive({', '.join(derive)})]\n"
f"{mustRoot}"
f"pub struct {self.makeClassName(d)}{self.generic} {{\n"
f"{inheritance}"
f"{joinedMemberDecls}\n"
"}\n"
f"{manualImpls}"
f"{default}"
)
def impl(self) -> str:
d = self.dictionary
if d.parent:
assert isinstance(d.parent, IDLDictionary)
initParent = (
"{\n"
f" match {self.makeModuleName(d.parent)}::{self.makeClassName(d.parent)}::new(cx, val)? {{\n"
" ConversionResult::Success(v) => v,\n"
" ConversionResult::Failure(error) => {\n"
" throw_type_error(cx.raw_cx(), error.as_ref());\n"
" return Err(());\n"
" }\n"
" }\n"
"}"
)
else:
initParent = ""
def memberInit(memberInfo: tuple[IDLArgument, JSToNativeConversionInfo]) -> CGThing:
member, _ = memberInfo
name = self.makeMemberName(member.identifier.name)
conversion = self.getMemberConversion(memberInfo, member.type)
return CGGeneric(f"{name}: {conversion.define()},\n")
def varInsert(varName: str, dictionaryName: str) -> CGThing:
insertion = (
f"rooted!(in(cx) let mut {varName}_js = UndefinedValue());\n"
f"{varName}.to_jsval(cx, {varName}_js.handle_mut());\n"
f'set_dictionary_property(SafeJSContext::from_ptr(cx), obj.handle(), c"{dictionaryName}", {varName}_js.handle()).unwrap();')
return CGGeneric(insertion)
def memberInsert(memberInfo: tuple[IDLArgument, JSToNativeConversionInfo]) -> CGThing:
member, _ = memberInfo
name = self.makeMemberName(member.identifier.name)
if member.optional and not member.defaultValue:
insertion = CGIfWrapper(f"let Some(ref {name}) = self.{name}",
varInsert(name, member.identifier.name))
else:
insertion = CGGeneric(f"let {name} = &self.{name};\n"
f"{varInsert(name, member.identifier.name).define()}")
return CGGeneric(f"{insertion.define()}\n")
memberInserts = [memberInsert(m) for m in self.memberInfo]
if d.parent:
memberInserts = [CGGeneric("self.parent.to_jsobject(cx, obj.reborrow());\n")] + memberInserts
selfName = self.makeClassName(d)
if self.membersNeedTracing():
actualType = f"RootedTraceableBox<{selfName}{self.genericSuffix}>"
preInitial = f"let dictionary = RootedTraceableBox::new({selfName} {{\n"
postInitial = "});\n"
else:
actualType = f"{selfName}{self.genericSuffix}"
preInitial = f"let dictionary = {selfName} {{\n"
postInitial = "};\n"
initParent = f"parent: {initParent},\n" if initParent else ""
memberInits = CGList([memberInit(member) for member in self.memberInfo])
unsafe_if_necessary = "unsafe"
if not initParent and not memberInits:
unsafe_if_necessary = ""
return (
f"impl{self.generic} {selfName}{self.genericSuffix} {{\n"
f"{CGIndenter(CGGeneric(self.makeEmpty()), indentLevel=4).define()}\n"
" pub fn new(cx: &mut JSContext, val: HandleValue) \n"
f" -> Result<ConversionResult<{actualType}>, ()> {{\n"
f" {unsafe_if_necessary} {{\n"
" let object = if val.get().is_null_or_undefined() {\n"
" ptr::null_mut()\n"
" } else if val.get().is_object() {\n"
" val.get().to_object()\n"
" } else {\n"
" return Ok(ConversionResult::Failure(c\"Value is not an object.\".into()));\n"
" };\n"
" rooted!(&in(cx) let object = object);\n"
f"{CGIndenter(CGGeneric(preInitial), indentLevel=8).define()}"
f"{CGIndenter(CGGeneric(initParent), indentLevel=16).define()}"
f"{CGIndenter(memberInits, indentLevel=16).define()}"
f"{CGIndenter(CGGeneric(postInitial), indentLevel=8).define()}"
" Ok(ConversionResult::Success(dictionary))\n"
" }\n"
" }\n"
"}\n"
"\n"
f"impl{self.generic} FromJSValConvertible for {actualType} {{\n"
" type Config = ();\n"
" unsafe fn from_jsval(_cx: *mut RawJSContext, value: HandleValue, _option: ())\n"
f" -> Result<ConversionResult<{actualType}>, ()> {{\n"
" // TODO https://github.com/servo/mozjs/issues/749\n"
" let mut cx = crate::script_runtime::temp_cx();\n"
f" {selfName}::new(&mut cx, value)\n"
" }\n"
" fn safe_from_jsval(cx: &mut JSContext, value: HandleValue, _option: ())\n"
f" -> Result<ConversionResult<{actualType}>, ()> {{\n"
f" {selfName}::new(cx, value)\n"
" }\n"
"}\n"
"\n"
f"impl{self.generic} {selfName}{self.genericSuffix} {{\n"
" #[allow(clippy::wrong_self_convention)]\n"
" pub unsafe fn to_jsobject(&self, cx: *mut RawJSContext, mut obj: MutableHandleObject) {\n"
f"{CGIndenter(CGList(memberInserts), indentLevel=8).define()} }}\n"
"}\n"
"\n"
f"impl{self.generic} ToJSValConvertible for {selfName}{self.genericSuffix} {{\n"
" unsafe fn to_jsval(&self, cx: *mut RawJSContext, mut rval: MutableHandleValue) {\n"
" rooted!(in(cx) let mut obj = JS_NewObject(cx, ptr::null()));\n"
" self.to_jsobject(cx, obj.handle_mut());\n"
" rval.set(ObjectOrNullValue(obj.get()))\n"
" }\n"
"}\n"
)
def membersNeedTracing(self) -> bool:
return type_needs_tracing(self.dictionary)
@staticmethod
def makeDictionaryName(dictionary: IDLDictionary | IDLWrapperType) -> str:
if isinstance(dictionary, IDLWrapperType):
return CGDictionary.makeDictionaryName(dictionary.inner)
else:
assert isinstance(dictionary, IDLDictionary)
return dictionary.identifier.name
def makeClassName(self, dictionary: IDLDictionary | IDLWrapperType) -> str:
return self.makeDictionaryName(dictionary)
@staticmethod
def makeModuleName(dictionary: IDLDictionary | IDLWrapperType) -> str:
return getModuleFromObject(dictionary)
def getMemberType(self, memberInfo: tuple[IDLArgument, JSToNativeConversionInfo]) -> str:
member, info = memberInfo
assert info.declType is not None
declType = info.declType
if member.optional and not member.defaultValue:
declType = CGWrapper(info.declType, pre="Option<", post=">")
return declType.define()
def getMemberConversion(self, memberInfo: tuple[IDLArgument, JSToNativeConversionInfo], memberType: IDLType) -> CGThing:
def indent(s: str) -> str:
return CGIndenter(CGGeneric(s), 12).define()
member, info = memberInfo
templateBody = info.template
default = info.default
replacements = {"val": "rval.handle()"}
conversion = string.Template(templateBody).substitute(replacements)
assert (member.defaultValue is None) == (default is None)
if not member.optional:
assert default is None
default = (f'throw_type_error(cx.raw_cx(), c"Missing required member \\"{member.identifier.name}\\".");\n'
"return Err(());")
elif not default:
default = "None"
conversion = f"Some({conversion})"
conversion = (
"{\n"
" rooted!(&in(cx) let mut rval = UndefinedValue());\n"
f' if get_dictionary_property(cx, object.handle(), c"{member.identifier.name}", '
"rval.handle_mut())? && !rval.is_undefined() {\n"
f"{indent(conversion)}\n"
" } else {\n"
f"{indent(default)}\n"
" }\n"
"}")
return CGGeneric(conversion)
def makeEmpty(self) -> str:
if self.hasRequiredFields(self.dictionary):
return ""
parentTemplate = "parent: %s::%s::empty(),\n"
fieldTemplate = "%s: %s,\n"
functionTemplate = (
"pub fn empty() -> Self {\n"
" Self {\n"
"%s"
" }\n"
"}"
)
if self.membersNeedTracing():
parentTemplate = "dictionary.parent = %s::%s::empty();\n"
fieldTemplate = "dictionary.%s = %s;\n"
functionTemplate = (
"pub fn empty() -> RootedTraceableBox<Self> {\n"
" let mut dictionary = RootedTraceableBox::new(Self::default());\n"
"%s"
" dictionary\n"
"}"
)
s = ""
if self.dictionary.parent:
assert isinstance(self.dictionary.parent, IDLDictionary)
s += parentTemplate % (self.makeModuleName(self.dictionary.parent),
self.makeClassName(self.dictionary.parent))
for member, info in self.memberInfo:
if not member.optional:
return ""
default = info.default
if not default:
default = "None"
s += fieldTemplate % (self.makeMemberName(member.identifier.name), default)
return functionTemplate % CGIndenter(CGGeneric(s), 12).define()
def hasRequiredFields(self, dictionary: IDLDictionary) -> bool:
if dictionary.parent:
assert isinstance(dictionary.parent, IDLDictionary)
if self.hasRequiredFields(dictionary.parent):
return True
for member in dictionary.members:
if not member.optional:
return True
return False
@staticmethod
def makeMemberName(name: str) -> str:
if name in RUST_KEYWORDS:
return f"{name}_"
return name
@staticmethod
def getDictionaryDependencies(dictionary: IDLDictionary) -> set[IDLDictionary]:
deps = set()
if dictionary.parent:
assert isinstance(dictionary.parent, IDLDictionary)
deps.add(dictionary.parent)
for member in dictionary.members:
if member.type.isDictionary():
deps.add(member.type.unroll().inner)
return deps
class CGInitAllStatics(CGAbstractMethod):
def __init__(self, config: Configuration) -> None:
docs = "Initialize the static data used by the SpiderMonkey DOM bindings to implement JS interfaces."
descriptors = (config.getDescriptors(isCallback=False, register=True)
+ config.getDescriptors(isCallback=True, hasInterfaceObject=True, register=True))
CGAbstractMethod.__init__(self, None, 'InitAllStatics', 'void', [],
pub=True, docs=docs, templateArgs=["D: DomTypes"])
self.descriptors = descriptors
def definition_body(self) -> CGThing:
return CGList([
CGGeneric(f" GenericBindings::{toBindingModuleFileFromDescriptor(desc)}::{toBindingNamespace(desc.name)}"
"::init_statics::<D>();")
for desc in self.descriptors
], "\n")
class CGRegisterProxyHandlersMethod(CGAbstractMethod):
def __init__(self, descriptors: list[Descriptor]) -> None:
docs = "Create the global vtables used by the generated DOM bindings to implement JS proxies."
CGAbstractMethod.__init__(self, None, 'RegisterProxyHandlers', 'void', [],
pub=True, docs=docs, templateArgs=["D: DomTypes"])
self.descriptors = descriptors
def definition_body(self) -> CGThing:
body = [CGGeneric("unsafe {")]
body += [
CGGeneric(f"proxy_handlers::{desc.name}.store(\n"
f" GenericBindings::{toBindingModuleFile(desc.name)}::{toBindingNamespace(desc.name)}"
"::DefineProxyHandler::<D>() as *mut _,\n"
" std::sync::atomic::Ordering::Release,\n"
");")
for desc in self.descriptors
]
body += [CGGeneric("}")]
return CGList(body, "\n")
class CGRegisterProxyHandlers(CGThing):
def __init__(self, config: Configuration) -> None:
descriptors = config.getDescriptors(proxy=True)
body = "".join(
f" pub(crate) static {desc.name}: std::sync::atomic::AtomicPtr<libc::c_void> =\n"
" std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());\n"
for desc in descriptors
)
self.root = CGList([
CGGeneric(
"#[expect(non_upper_case_globals)]\n"
"pub(crate) mod proxy_handlers {\n"
f"{body}}}\n"
),
CGRegisterProxyHandlersMethod(descriptors),
], "\n")
def define(self) -> str:
return self.root.define()
class CGStructuredCloneMarker(CGThing):
def __init__(self, descriptor: Descriptor, marker: str) -> None:
CGThing.__init__(self)
self.descriptor = descriptor
self.marker = marker
self.marker_lower = marker.lower()
def define(self) -> str:
ifaceName = self.descriptor.interface.identifier.name
return f"""
impl script_bindings::structuredclone::MarkedAs{self.marker}InIdl for {ifaceName} {{
#[allow(path_statements)]
fn assert_{self.marker_lower}() {{
crate::dom::bindings::{self.marker_lower}::assert_{self.marker_lower}::<Self>;
}}
}}
"""
class CGConcreteBindingRoot(CGThing):
root: CGThing | None
def __init__(self, config: Configuration, prefix: str, webIDLFile: str) -> None:
descriptors = config.getDescriptors(webIDLFile=webIDLFile,
hasInterfaceObject=True)
descriptors.extend(config.getDescriptors(webIDLFile=webIDLFile,
hasInterfaceObject=False,
isCallback=False,
register=True))
dictionaries = config.getDictionaries(webIDLFile=webIDLFile)
mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile)
callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile,
isCallback=True)
enums = config.getEnums(webIDLFile)
typedefs = config.getTypedefs(webIDLFile)
if not (descriptors or dictionaries or mainCallbacks or callbackDescriptors or enums):
self.root = None
return
originalBinding = f"crate::dom::bindings::codegen::{prefix.replace('/', '::').replace('Concrete', 'Generic')}"
cgthings = []
for e in enums:
enumName = e.identifier.name
cgthings += [
CGGeneric(f"pub(crate) use {originalBinding}::{enumName} as {enumName};"),
CGGeneric(f"pub(crate) use {originalBinding}::{enumName}Values as {enumName}Values;"),
]
cgthings += [CGGeneric(
f"pub(crate) type {t.identifier.name} = "
f"{originalBinding}::{t.identifier.name}"
f"{'::<crate::DomTypeHolder>' if containsDomInterface(t.innerType) else ''};"
) for t in typedefs]
cgthings += [CGGeneric(
f"pub(crate) type {d.identifier.name} = "
f"{originalBinding}::{d.identifier.name}"
f"{'::<crate::DomTypeHolder>' if containsDomInterface(d) else ''};"
) for d in dictionaries]
cgthings += [CGGeneric(
f"pub(crate) type {c.identifier.name} = "
f"{originalBinding}::{c.identifier.name}<crate::DomTypeHolder>;"
) for c in mainCallbacks]
cgthings += [CGGeneric(f"pub(crate) use {originalBinding} as GenericBindings;")]
for d in descriptors:
ifaceName = d.interface.identifier.name
cgthings += [
CGGeneric(
f"pub(crate) use {originalBinding}::{firstCap(ifaceName)}_Binding as {firstCap(ifaceName)}_Binding;"
),
]
for marker in ["Serializable", "Transferable"]:
if d.interface.getExtendedAttribute(marker):
cgthings += [CGStructuredCloneMarker(d, marker)]
if d.concrete:
if not d.interface.isIteratorInterface():
cgthings.append(CGAssertInheritance(d))
else:
cgthings.append(CGIteratorDerives(d))
if (
(d.concrete or d.hasDescendants())
and not d.interface.isIteratorInterface()
):
cgthings.append(CGIDLInterface(d))
if d.interface.isIteratorInterface():
cgthings.append(CGDomObjectIteratorWrap(d))
elif d.concrete and not d.isGlobal():
cgthings.append(CGDomObjectWrap(d))
if d.weakReferenceable:
cgthings.append(CGWeakReferenceableTrait(d))
if (
not d.interface.isIteratorInterface() and
not d.interface.isCallback() and
not d.allowDropImpl
):
cgthings.append(CGForbidDrop(d))
if not d.interface.isCallback():
traitName = f"{ifaceName}Methods"
cgthings += [
CGGeneric(f"pub(crate) use self::{firstCap(ifaceName)}_Binding::{traitName} as {traitName};"),
]
if len(descriptors) == 1 and d.concrete:
cgthings += [CGGeneric(f"pub(crate) use self::{firstCap(ifaceName)}_Binding::Wrap;")]
if d.interface.hasInterfaceObject() and d.shouldHaveGetConstructorObjectMethod():
cgthings += [CGGeneric(f"""
pub(crate) fn GetConstructorObject(
cx: &mut JSContext, global: HandleObject, rval: MutableHandleObject
) {{
self::{firstCap(ifaceName)}_Binding::GetConstructorObject::<crate::DomTypeHolder>(cx, global, rval)
}}
""")]
constMembers = [m for m in d.interface.members if m.isConst()]
if constMembers:
constants = f"{ifaceName}Constants"
cgthings += [CGGeneric(f"pub(crate) use {originalBinding}::{constants} as {constants};")]
for c in callbackDescriptors:
ifaceName = c.interface.identifier.name
cgthings += [CGGeneric(
f"pub(crate) type {ifaceName} = {originalBinding}::{ifaceName}<crate::DomTypeHolder>;"
)]
curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
curr = CGImports(curr, descriptors=[], callbacks=[],
dictionaries=[], enums=[], typedefs=[],
imports=[
'crate::dom::bindings::import::module::*',
'crate::dom::types::*'],
config=config)
curr = CGWrapper(curr, pre=f"{AUTOGENERATED_WARNING_COMMENT}{ALLOWED_WARNINGS}")
self.root = curr
def define(self) -> str:
if not self.root:
return ""
return stripTrailingWhitespace(self.root.define())
class CGBindingRoot(CGThing):
root: CGThing | None
def __init__(self, config: Configuration, prefix: str, webIDLFile: str) -> None:
descriptors = config.getDescriptors(webIDLFile=webIDLFile,
hasInterfaceObject=True)
descriptors.extend(config.getDescriptors(webIDLFile=webIDLFile,
hasInterfaceObject=False,
isCallback=False,
register=True))
dictionaries = config.getDictionaries(webIDLFile=webIDLFile)
mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile)
callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile,
isCallback=True)
enums = config.getEnums(webIDLFile)
typedefs = config.getTypedefs(webIDLFile)
if not (descriptors or dictionaries or mainCallbacks or callbackDescriptors or enums):
self.root = None
return
cgthings: list = [CGEnum(e, config) for e in enums]
for t in typedefs:
typeName = getRetvalDeclarationForType(t.innerType, config.getDescriptorProvider())
name = t.identifier.name
type = typeName.define()
if t.innerType.isUnion() and not t.innerType.nullable():
typeDefinition = f"pub use self::{type.replace('::<D>', '')} as {name};"
else:
generic = "<D>" if containsDomInterface(t.innerType) else ""
replacedType = type.replace("D::", "<D as DomTypes>::")
typeDefinition = f"pub type {name}{generic} = {replacedType};"
cgthings.append(CGGeneric(typeDefinition))
cgthings.extend([CGDictionary(d, config.getDescriptorProvider(), config)
for d in dictionaries])
cgthings.extend(CGList([CGCallbackFunction(c, config.getDescriptorProvider()),
CGCallbackFunctionImpl(c)], "\n")
for c in mainCallbacks)
cgthings.extend([CGDescriptor(x, config, len(descriptors) == 1) for x in descriptors])
cgthings.extend(CGList(
[
CGCallbackInterface(x),
CGCallbackFunctionImpl(assert_type(x.interface, IDLInterface))
],
"\n"
) for x in callbackDescriptors)
curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
curr = CGImports(curr, descriptors=callbackDescriptors + descriptors, callbacks=mainCallbacks,
dictionaries=dictionaries, enums=enums, typedefs=typedefs,
imports=['crate::import::base::*'], config=config)
curr = CGWrapper(curr, pre=f"{AUTOGENERATED_WARNING_COMMENT}{ALLOWED_WARNINGS}")
self.root = curr
def define(self) -> str:
if not self.root:
return ""
return stripTrailingWhitespace(self.root.define())
def type_needs_tracing(t: IDLObject) -> bool:
assert isinstance(t, IDLObject), (t, type(t))
if t.isType():
assert isinstance(t, IDLType)
if isinstance(t, IDLWrapperType):
return type_needs_tracing(t.inner)
if t.nullable():
assert isinstance(t, IDLNullableType)
return type_needs_tracing(t.inner)
if t.isAny():
return True
if t.isObject():
return True
if t.isSequence() :
assert isinstance(t, IDLSequenceType)
return type_needs_tracing(t.inner)
if t.isUnion():
assert isinstance(t, IDLUnionType) and t.flatMemberTypes is not None
return any(type_needs_tracing(member) for member in t.flatMemberTypes)
if is_typed_array(t):
return True
return False
if t.isDictionary():
assert isinstance(t, IDLDictionary)
if t.parent and type_needs_tracing(t.parent):
return True
if any(type_needs_tracing(member.type) for member in t.members):
return True
return False
if t.isInterface():
return False
if t.isEnum():
return False
assert False, (t, type(t))
return False
def is_typed_array(t: IDLType) -> bool:
assert isinstance(t, IDLObject), (t, type(t))
return t.isTypedArray() or t.isArrayBuffer() or t.isArrayBufferView()
def type_needs_auto_root(t: IDLType) -> bool:
assert isinstance(t, IDLObject), (t, type(t))
if t.isType():
if t.isSequence() and (t.inner.isAny() or t.inner.isObject()):
return True
if is_typed_array(t):
return True
return False
DefaultValueType = IDLValue | IDLNullValue | IDLUndefinedValue | IDLDefaultDictionaryValue | IDLEmptySequenceValue
def argument_type(descriptorProvider: DescriptorProvider,
ty: IDLType,
optional: bool = False,
defaultValue: DefaultValueType | None = None,
variadic: bool = False
) -> str:
info = getJSToNativeConversionInfo(
ty, descriptorProvider, isArgument=True,
isAutoRooted=type_needs_auto_root(ty))
declType = info.declType
assert declType is not None
if variadic:
if ty.isGeckoInterface():
declType = CGWrapper(declType, pre="&[", post="]")
else:
declType = CGWrapper(declType, pre="Vec<", post=">")
elif optional and not defaultValue:
declType = CGWrapper(declType, pre="Option<", post=">")
if ty.isDictionary() and not type_needs_tracing(ty):
declType = CGWrapper(declType, pre="&")
if type_needs_auto_root(ty):
declType = CGTemplatedType("CustomAutoRooterGuard", declType)
assert declType is not None
return declType.define()
def method_arguments(descriptorProvider: DescriptorProvider,
returnType: IDLType,
arguments: Iterable[IDLArgument | FakeArgument],
passJSBits: bool = True,
trailing: tuple[str, str] | None = None,
cx_no_gc: bool = False,
cx: bool = False,
realm: bool = False,
inRealm: bool = False,
canGc: bool = False
) -> Iterator[tuple[str, str]]:
old_cx = False
match needCx(returnType, arguments, passJSBits):
case Context.Cx:
cx = True
case Context.CurrentRealm:
realm = True
case Context.OldCx:
old_cx = True
case Context.No:
pass
if cx_no_gc:
yield "cx", "&JSContext"
elif cx:
yield "cx", "&mut JSContext"
elif realm:
yield "realm", "&mut CurrentRealm"
safe_cx = cx or cx_no_gc or realm
if old_cx and not safe_cx:
yield "cx", "SafeJSContext"
for argument in arguments:
ty = argument_type(descriptorProvider, argument.type, argument.optional,
argument.defaultValue, argument.variadic)
yield CGDictionary.makeMemberName(argument.identifier.name), ty
if trailing:
yield trailing
if inRealm and not safe_cx:
yield "_comp", "InRealm"
if canGc and not safe_cx:
yield "_can_gc", "CanGc"
if returnTypeNeedsOutparam(returnType):
yield "rval", outparamTypeFromReturnType(returnType),
def return_type(descriptorProvider: DescriptorProvider, rettype: IDLType, infallible: bool) -> str:
result = getRetvalDeclarationForType(rettype, descriptorProvider)
if rettype and returnTypeNeedsOutparam(rettype):
result = CGGeneric("()")
if not infallible:
result = CGWrapper(result, pre="Fallible<", post=">")
return result.define()
class CGNativeMember(ClassMethod):
def __init__(self,
descriptorProvider: DescriptorProvider,
member: IDLMethod | IDLAttribute | FakeMember,
name: str,
signature: tuple[IDLType, list[IDLArgument | FakeArgument]],
extendedAttrs: dict[str, Any],
breakAfter: bool = True,
passJSBitsAsNeeded: bool = True,
visibility: str = "public",
unsafe: bool = False) -> None:
self.descriptorProvider = descriptorProvider
self.member = member
self.extendedAttrs = extendedAttrs
self.passJSBitsAsNeeded = passJSBitsAsNeeded
breakAfterSelf = "\n" if breakAfter else ""
ClassMethod.__init__(self, name,
self.getReturnType(signature[0]),
self.getArgs(signature[0], signature[1]),
static=member.isStatic(),
const=(not member.isStatic() and member.isAttr()
and not signature[0].isUndefined()),
breakAfterSelf=breakAfterSelf,
unsafe=unsafe,
visibility=visibility)
def getReturnType(self, type: IDLType) -> str:
infallible = 'infallible' in self.extendedAttrs
typeDecl = return_type(self.descriptorProvider, type, infallible)
return typeDecl
def getArgs(self, returnType: IDLType, argList: list[IDLArgument | FakeArgument]) -> list[Argument]:
return [Argument(arg[1], arg[0]) for arg in method_arguments(self.descriptorProvider,
returnType,
argList,
self.passJSBitsAsNeeded)]
class CGCallback(CGClass):
def __init__(self, idlObject: IDLObjectWithIdentifier, descriptorProvider: DescriptorProvider, baseName: str, methods: Iterable[CallbackMethod]) -> None:
self.baseName = baseName
self._deps = idlObject.getDeps()
name = idlObject.identifier.name
realMethods: list[CallbackMethod | ClassMethod] = []
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,
templateSpecialization=['D: DomTypes'],
decorators="#[derive(JSTraceable, MallocSizeOf, PartialEq)]\n"
"#[cfg_attr(crown, allow(crown::unrooted_must_root))]\n"
"#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_interior)]")
def getConstructors(self) -> list[ClassConstructor]:
return [ClassConstructor(
[Argument("SafeJSContext", "aCx"), Argument("*mut JSObject", "aCallback")],
bodyInHeader=True,
visibility="pub",
explicit=False,
baseConstructors=[
f"{self.baseName.replace('<D>', '')}::new()"
])]
def getMethodImpls(self, method: CallbackMethod) -> list[ClassMethod]:
assert method.needThisHandling
args = list(method.args)
assert args[0].name == "cx" and args[0].argType == "SafeJSContext"
assert args[1].name == "aThisObj" and args[1].argType == "HandleValue"
args = args[2:]
argnames = [arg.name for arg in args]
argnamesWithThis = ["cx", "thisValue.handle()"] + argnames
argnamesWithoutThis = ["cx", "HandleValue::undefined()"] + argnames
args.append(Argument("ExceptionHandling", "aExceptionHandling",
"ReportExceptions"))
argsWithoutThis = list(args)
args.insert(0, Argument("&T", "thisObj"))
method.args[0].argType = "&mut JSContext"
args.insert(0, Argument("&mut JSContext", "cx"))
argsWithoutThis.insert(0, Argument("&mut JSContext", "cx"))
method.args.insert(0, Argument(None, "&self"))
args.insert(0, Argument(None, "&self"))
argsWithoutThis.insert(0, Argument(None, "&self"))
bodyWithThis = (
"call_setup(cx, self, aExceptionHandling, |cx| {\n"
" rooted!(&in(cx) let mut thisValue: JSVal);\n"
" let wrap_result = wrap_call_this_value(cx, thisObj, thisValue.handle_mut());\n"
" if !wrap_result {\n"
" return Err(JSFailed);\n"
" }\n"
f" unsafe {{ self.{method.name}({', '.join(argnamesWithThis)}) }}"
"})")
bodyWithoutThis = (
"call_setup(cx, self, aExceptionHandling, |cx| {\n"
f" unsafe {{ self.{method.name}({', '.join(argnamesWithoutThis)}) }}"
"})")
return [ClassMethod(f'{method.name}_', method.returnType, args,
bodyInHeader=True,
templateArgs=["T: ThisReflector"],
body=bodyWithThis,
visibility='pub'),
ClassMethod(f'{method.name}__', method.returnType, argsWithoutThis,
bodyInHeader=True,
body=bodyWithoutThis,
visibility='pub'),
method]
def deps(self) -> set[str]:
return self._deps
def callbackGetterName(attr: IDLAttribute, descriptor: Descriptor) -> str:
return f"Get{MakeNativeName(descriptor.binaryNameFor(attr.identifier.name, attr.isStatic()))}"
def callbackSetterName(attr: IDLAttribute, descriptor: Descriptor) -> str:
return f"Set{MakeNativeName(descriptor.binaryNameFor(attr.identifier.name, attr.isStatic()))}"
class CGCallbackFunction(CGCallback):
def __init__(self, callback: IDLCallback, descriptorProvider: DescriptorProvider) -> None:
CGCallback.__init__(self, callback, descriptorProvider,
"CallbackFunction<D>",
methods=[CallCallback(callback, descriptorProvider)])
def getConstructors(self) -> list[ClassConstructor]:
return CGCallback.getConstructors(self)
class CGCallbackFunctionImpl(CGGeneric):
def __init__(self, callback: IDLCallback | IDLInterface) -> None:
type = f"{callback.identifier.name}<D>"
impl = (f"""
impl<D: DomTypes> CallbackContainer<D> for {type} {{
unsafe fn new(cx: SafeJSContext, callback: *mut JSObject) -> Rc<{type}> {{
{type.replace('<D>', '')}::new(cx, callback)
}}
fn callback_holder(&self) -> &CallbackObject<D> {{
self.parent.callback_holder()
}}
}}
impl<D: DomTypes> ToJSValConvertible for {type} {{
unsafe fn to_jsval(&self, cx: *mut RawJSContext, rval: MutableHandleValue) {{
self.callback().to_jsval(cx, rval);
}}
}}
""")
CGGeneric.__init__(self, impl)
class CGCallbackInterface(CGCallback):
def __init__(self, descriptor: Descriptor) -> None:
iface = descriptor.interface
attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()]
assert not attrs
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<D>", methods)
class FakeMember():
def __init__(self) -> None:
pass
def isStatic(self) -> bool:
return False
def isAttr(self) -> bool:
return False
def isMethod(self) -> bool:
return False
def getExtendedAttribute(self, name: str) -> None:
return None
class CallbackMember(CGNativeMember):
def __init__(self, sig: tuple[IDLType, list[IDLArgument | FakeArgument]], name: str, descriptorProvider: DescriptorProvider, needThisHandling: bool) -> None:
self.retvalType = sig[0]
self.originalSig = sig
args = sig[1]
self.argCount = len(args)
if self.argCount > 0:
lastArg = args[self.argCount - 1]
if lastArg.variadic:
self.argCountStr = (
f"{self.argCount - 1} + {lastArg.identifier.name}.len()").removeprefix("0 + ")
else:
self.argCountStr = f"{self.argCount}"
self.usingOutparam = returnTypeNeedsOutparam(self.retvalType)
self.needThisHandling = needThisHandling
visibility = "priv" if needThisHandling else "pub"
CGNativeMember.__init__(self, descriptorProvider, FakeMember(),
name, (self.retvalType, args),
extendedAttrs={},
passJSBitsAsNeeded=False,
unsafe=needThisHandling,
visibility=visibility)
self.exceptionCode = "return Err(JSFailed);\n"
self.body = self.getImpl()
@abstractmethod
def getRvalDecl(self) -> str:
raise NotImplementedError
@abstractmethod
def getCall(self) -> str:
raise NotImplementedError
def getImpl(self) -> str:
argvDecl = (
"rooted!(&in(cx) let mut argv = vec![]);\n"
f"argv.extend((0..{self.argCountStr}).map(|_| JSVal::default()));\n"
) if self.argCount > 0 else ""
pre = (
f"{self.getCallSetup()}"
f"{self.getRvalDecl()}"
f"{argvDecl}"
)
body = (
f"{self.getArgConversions()}"
f"{self.getCall()}"
f"{self.getResultConversion()}"
)
return f"{pre}\n{body}"
def getResultConversion(self) -> str:
replacements = {
"val": "rval.handle()",
}
info = getJSToNativeConversionInfo(
self.retvalType,
self.descriptorProvider,
exceptionCode=self.exceptionCode,
sourceDescription="return value")
template = info.template
declType = info.declType
if self.usingOutparam:
convertType = CGGeneric("")
else:
convertType = instantiateJSToNativeConversionTemplate(
template, replacements, declType, "retval")
if self.retvalType is None or self.retvalType.isUndefined() or self.usingOutparam:
retval = "()"
else:
retval = "retval"
return f"{convertType.define()}\nOk({retval})\n"
def getArgConversions(self) -> str:
arglist = self.originalSig[1]
argConversions: list[str] = [self.getArgConversion(i, arg) for (i, arg)
in enumerate(arglist)]
argConversions.reverse()
argConversionsCG: list[CGThing] = [CGGeneric(c) for c in argConversions]
if self.argCount == 1 and not (arglist[0].optional and not arglist[0].defaultValue):
argConversionsCG.insert(0, self.getArgcDecl(True))
elif self.argCount > 0:
argConversionsCG.insert(0, self.getArgcDecl(False))
return CGList(argConversionsCG, "\n\n").define() + "\n\n"
def getArgConversion(self, i: int, arg: IDLArgument | FakeArgument) -> str:
argval = arg.identifier.name
if arg.variadic:
argval = f"{argval}.get()"
jsvalIndex = f"{i} + idx"
else:
jsvalIndex = f"{i}"
conversion = wrapForType(
f"argv.handle_mut_at({jsvalIndex.removeprefix('0 + ')})",
result=argval,
successCode="",
pre="")
if arg.variadic:
argname = arg.identifier.name
conversion = (
f"for (idx, {argname}) in {argname}.iter().enumerate() {{\n"
f"{CGIndenter(CGGeneric(conversion)).define()}\n}}"
)
elif arg.optional and not arg.defaultValue:
conversion = (
f"{CGIfWrapper(f'let Some({arg.identifier.name}) = {arg.identifier.name}', CGGeneric(conversion)).define()}"
f" else if argc == {i + 1} {{\n"
" // This is our current trailing argument; reduce argc\n"
" argc -= 1;\n"
"} else {\n"
f" argv.set_index({i}, Default::default());\n"
"}"
)
return conversion
def getArgs(self, returnType: IDLType, argList: list[IDLArgument | FakeArgument]) -> list[Argument]:
args = CGNativeMember.getArgs(self, returnType, argList)
if not self.needThisHandling:
args.append(Argument("ExceptionHandling", "aExceptionHandling",
"ReportExceptions"))
return args
return [Argument("SafeJSContext", "cx"),
Argument("HandleValue", "aThisObj")] + args
def getCallSetup(self) -> str:
if self.needThisHandling:
return ""
return (
"CallSetup s(CallbackPreserveColor(), aRv, aExceptionHandling);\n"
"JSContext* cx = *s.get_context();\n"
"if (!cx) {\n"
" return Err(JSFailed);\n"
"}\n")
def getArgcDecl(self, immutable: bool) -> CGThing:
if immutable:
return CGGeneric(f"let argc = {self.argCountStr};")
return CGGeneric(f"let mut argc = {self.argCountStr};")
@staticmethod
def ensureASCIIName(idlObject: IDLInterfaceMember) -> None:
type = "attribute" if idlObject.isAttr() else "operation"
if re.match("[^\x20-\x7E]", idlObject.identifier.name):
raise SyntaxError(f'Callback {type} name "{idlObject.identifier.name}" contains non-ASCII '
f"characters. We can't handle that. {idlObject.location}")
if re.match('"', idlObject.identifier.name):
raise SyntaxError(f"Callback {type} name '{idlObject.identifier.name}' contains "
"double-quote character. We can't handle "
f"that. {idlObject.location}")
class CallbackMethod(CallbackMember):
def __init__(self, sig: tuple[IDLType, list[IDLArgument | FakeArgument]], name: str, descriptorProvider: DescriptorProvider, needThisHandling: bool) -> None:
CallbackMember.__init__(self, sig, name, descriptorProvider,
needThisHandling)
def getRvalDecl(self) -> str:
if self.usingOutparam:
return ""
else:
return "rooted!(&in(cx) let mut rval = UndefinedValue());\n"
@abstractmethod
def getCallableDecl(self) -> str:
raise NotImplementedError
@abstractmethod
def getThisObj(self) -> str:
raise NotImplementedError
@abstractmethod
def getCallGuard(self) -> str:
raise NotImplementedError
def getCall(self) -> str:
if self.argCount > 0:
argv = "std::ops::Deref::deref(&argv).as_ptr()"
argc = "argc"
else:
argv = "ptr::null_mut()"
argc = "0"
suffix = "" if self.usingOutparam else ".handle_mut()"
return (f"{self.getCallableDecl()}"
f"rooted!(&in(cx) let rootedThis = {self.getThisObj()});\n"
f"let ok = {self.getCallGuard()}Call(\n"
" cx.raw_cx(), rootedThis.handle(), callable.handle(),\n"
" &HandleValueArray {\n"
f" length_: {argc} as ::libc::size_t,\n"
f" elements_: {argv}\n"
f" }}, rval{suffix});\n"
"maybe_resume_unwind();\n"
"if !ok {\n"
" return Err(JSFailed);\n"
"}\n")
class CallCallback(CallbackMethod):
def __init__(self, callback: IDLCallback, descriptorProvider: DescriptorProvider) -> None:
self.callback = callback
CallbackMethod.__init__(self, callback.signatures()[0], "Call",
descriptorProvider, needThisHandling=True)
def getThisObj(self) -> str:
return "aThisObj.get()"
def getCallableDecl(self) -> str:
return "rooted!(&in(cx) let callable = ObjectValue(self.callback()));\n"
def getCallGuard(self) -> str:
if self.callback._treatNonObjectAsNull:
return "!IsCallable(self.callback()) || "
return ""
class CallbackOperationBase(CallbackMethod):
def __init__(self, signature: tuple[IDLType, list[IDLArgument | FakeArgument]], jsName: str, nativeName: str, descriptor: Descriptor, singleOperation: bool) -> None:
self.singleOperation = singleOperation
self.methodName = jsName
CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation)
def getThisObj(self) -> str:
if not self.singleOperation:
return "ObjectValue(self.callback())"
return "if isCallable { aThisObj.get() } else { ObjectValue(self.callback()) }"
def getCallableDecl(self) -> str:
getCallableFromProp = f'self.parent.get_callable_property(cx, c"{self.methodName}")?'
if not self.singleOperation:
return f'rooted!(&in(cx) let callable =\n{getCallableFromProp});\n'
callable = CGIndenter(
CGIfElseWrapper('isCallable', CGGeneric('ObjectValue(self.callback())'), CGGeneric(getCallableFromProp))
).define()
return ('let isCallable = IsCallable(self.callback());\n'
'rooted!(&in(cx) let callable =\n'
f"{callable});\n")
def getCallGuard(self) -> str:
return ""
class CallbackOperation(CallbackOperationBase):
def __init__(self, method: IDLMethod, signature: tuple[IDLType, list[IDLArgument | FakeArgument]], descriptor: Descriptor) -> None:
self.ensureASCIIName(method)
jsName = method.identifier.name
CallbackOperationBase.__init__(self, signature,
jsName,
MakeNativeName(descriptor.binaryNameFor(jsName, False)),
descriptor, descriptor.interface.isSingleOperationInterface())
class CGMaplikeOrSetlikeMethodGenerator(CGGeneric):
def __init__(self, descriptor: Descriptor, likeable: IDLMaplikeOrSetlikeOrIterableBase, methodName: str) -> None:
trait: str
if likeable.isSetlike():
trait = "Setlike"
elif likeable.isMaplike():
trait = "Maplike"
else:
raise TypeError("CGMaplikeOrSetlikeMethodGenerator is only for Setlike/Maplike")
if methodName in ["keys", "values", "entries", "forEach"]:
cgIterableMethod = CGIterableMethodGenerator(descriptor, likeable, methodName)
CGGeneric.__init__(self, cgIterableMethod.define())
elif methodName in ["size", "clear"]: CGGeneric.__init__(self, fill(
"""
let result = ${trt}::${method}(this);
""",
trt=trait,
method=methodName.lower()))
elif methodName == "add": CGGeneric.__init__(self, fill(
"""
${trt}::${method}(this, arg0);
// Returns itself per https://webidl.spec.whatwg.org/#es-set-add
let result = this;
""",
trt=trait,
method=methodName))
elif methodName in ["has", "delete", "get"]: CGGeneric.__init__(self, fill(
"""
let result = ${trt}::${method}(this, arg0);
""",
trt=trait,
method=methodName))
elif methodName == "set": CGGeneric.__init__(self, fill(
"""
${trt}::${method}(this, arg0, arg1);
// Returns itself per https://webidl.spec.whatwg.org/#es-map-set
let result = this;
""",
trt=trait,
method=methodName))
else:
raise TypeError(f"Do not know how to impl *like method: {methodName}")
class CGIterableMethodGenerator(CGGeneric):
def __init__(self, descriptor: Descriptor, iterable: IDLMaplikeOrSetlikeOrIterableBase, methodName: str) -> None:
if methodName == "forEach":
CGGeneric.__init__(self, fill(
"""
if !IsCallable(arg0) {
throw_type_error(cx.raw_cx(), c"Argument 1 of ${ifaceName}.forEach is not callable.");
return false;
}
rooted!(&in(cx) let arg0 = ObjectValue(arg0));
rooted!(&in(cx) let mut call_arg1 = UndefinedValue());
rooted!(&in(cx) let mut call_arg2 = UndefinedValue());
rooted!(&in(cx) let mut call_args = vec![]);
call_args.push(UndefinedValue());
call_args.push(UndefinedValue());
call_args.push(ObjectValue(*_obj));
rooted!(&in(cx) let mut ignoredReturnVal = UndefinedValue());
// This has to be a while loop since get_iterable_length() may change during
// the callback, and we need to avoid iterator invalidation.
//
// It is possible for this to loop infinitely, but that matches the spec
// and other browsers.
//
// https://heycam.github.io/webidl/#es-forEach
let mut i = 0;
while i < (*this).get_iterable_length() {
(*this).get_value_at_index(i).to_jsval(cx.raw_cx(), call_arg1.handle_mut());
(*this).get_key_at_index(i).to_jsval(cx.raw_cx(), call_arg2.handle_mut());
call_args.set_index(0, call_arg1.handle().get());
call_args.set_index(1, call_arg2.handle().get());
let call_args_handle = HandleValueArray::from(&call_args);
if !Call(cx.raw_cx(), arg1, arg0.handle(), &call_args_handle,
ignoredReturnVal.handle_mut()) {
return false;
}
i += 1;
}
let result = ();
""",
ifaceName=descriptor.interface.identifier.name))
return
CGGeneric.__init__(self, fill(
"""
let realm = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx.raw_cx()));
let result = ${iterClass}::new(this, IteratorType::${itrMethod}, InRealm::already(&realm));
""",
iterClass=iteratorNativeType(descriptor, True),
ifaceName=descriptor.interface.identifier.name,
itrMethod=methodName.title()))
def camel_to_upper_snake(s: str) -> str:
return "_".join(m.group(0).upper() for m in re.finditer("[A-Z][a-z]*", s))
def process_arg(expr: str, arg: IDLArgument | FakeArgument) -> str:
if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback():
if arg.variadic or arg.type.isSequence():
expr += ".r()"
elif arg.type.nullable() and arg.optional and not arg.defaultValue:
expr += ".as_ref().map(Option::as_deref)"
elif arg.type.nullable() or arg.optional and not arg.defaultValue:
expr += ".as_deref()"
else:
expr = f"&{expr}"
elif isinstance(arg.type, IDLPromiseType):
expr = f"&{expr}"
return expr
class GlobalGenRoots():
@staticmethod
def Globals(config: Configuration) -> CGThing:
global_descriptors = config.getDescriptors(isGlobal=True)
flags = [("EMPTY", 0)]
flags.extend(
(camel_to_upper_snake(d.name), 2 ** idx)
for (idx, d) in enumerate(global_descriptors)
)
global_flags = CGWrapper(CGIndenter(CGList([
CGGeneric(f"const {args[0]} = {args[1]};")
for args in flags
], "\n")), pre="#[derive(Clone, Copy)]\npub struct Globals: u8 {\n", post="\n}")
globals_ = CGWrapper(CGIndenter(global_flags), pre="bitflags::bitflags! {\n", post="\n}")
return CGList([
CGGeneric(AUTOGENERATED_WARNING_COMMENT),
globals_,
])
@staticmethod
def InterfaceObjectMap(config: Configuration) -> CGThing:
mods = [
"crate::dom::bindings::codegen",
"script_bindings::interfaces::Interface",
]
imports = CGList([CGGeneric(f"use {mod};") for mod in mods], "\n")
phf = CGGeneric("include!(concat!(env!(\"OUT_DIR\"), \"/InterfaceObjectMapPhf.rs\"));")
return CGList([
CGGeneric(AUTOGENERATED_WARNING_COMMENT),
CGList([imports, phf], "\n\n")
])
@staticmethod
def InterfaceObjectMapData(config: Configuration) -> CGThing:
pairs: list[tuple[str, str, str]] = []
for d in config.getDescriptors(hasInterfaceObject=True, isInline=False):
binding_mod = toBindingModuleFileFromDescriptor(d)
binding_ns = toBindingNamespace(d.name)
pairs.append((d.name, binding_mod, binding_ns))
for alias in d.interface.legacyWindowAliases:
pairs.append((alias, binding_mod, binding_ns))
for ctor in d.interface.legacyFactoryFunctions:
pairs.append((ctor.identifier.name, binding_mod, binding_ns))
pairs.sort(key=operator.itemgetter(0))
def bindingPath(pair: tuple[str, str, str]) -> str:
return f'codegen::Bindings::{pair[1]}::{pair[2]}'
mappings = [
CGGeneric(f'"{pair[0]}": ["{bindingPath(pair)}::DefineDOMInterface::<crate::DomTypeHolder>", '
f'"{bindingPath(pair)}::ConstructorEnabled::<crate::DomTypeHolder>"]')
for pair in pairs
]
return CGWrapper(
CGList(mappings, ",\n"),
pre="{\n",
post="\n}\n")
@staticmethod
def PrototypeList(config: Configuration) -> CGThing:
interfaces = config.getDescriptors(isCallback=False, isNamespace=False)
children: dict[str | None, list[str]] = {}
name_set = set()
for d in interfaces:
name = d.name
name_set.add(name)
parent_name = d.prototypeChain[-2] if len(d.prototypeChain) >= 2 else None
children.setdefault(parent_name, []).append(name)
for key in children:
children[key].sort()
protos: list[str] = []
proto_ranges: dict[str, tuple[int, int]] = {}
def dfs(name: str) -> None:
first_id = len(protos)
protos.append(name)
for child in children.get(name, []):
dfs(child)
proto_ranges[name] = (first_id, len(protos) - 1)
roots = sorted(children.get(None, []))
for root in roots:
dfs(root)
for d in interfaces:
if d.name not in proto_ranges:
first_id = len(protos)
protos.append(d.name)
proto_ranges[d.name] = (first_id, first_id)
global _proto_ranges
_proto_ranges = proto_ranges
constructors = sorted([MakeNativeName(d.name)
for d in config.getDescriptors(hasInterfaceObject=True)
if d.shouldHaveGetConstructorObjectMethod()])
return CGList([
CGGeneric(AUTOGENERATED_WARNING_COMMENT),
CGGeneric(f"pub const PROTO_OR_IFACE_LENGTH: usize = {len(protos) + len(constructors)};\n"),
CGGeneric(f"pub const MAX_PROTO_CHAIN_LENGTH: usize = {config.maxProtoChainLength};\n\n"),
CGGeneric("#[allow(clippy::enum_variant_names, dead_code)]"),
CGNonNamespacedEnum('ID', protos, 0, deriving="PartialEq, Copy, Clone", repr="u16"),
CGNonNamespacedEnum('Constructor', constructors, len(protos),
deriving="PartialEq, Copy, Clone", repr="u16"),
CGWrapper(CGIndenter(CGList([CGGeneric(f'"{name}"') for name in protos],
",\n"),
indentLevel=4),
pre=f"static INTERFACES: [&str; {len(protos)}] = [\n",
post="\n];\n\n"),
CGGeneric("pub fn proto_id_to_name(proto_id: u16) -> &'static str {\n"
" debug_assert!(proto_id < ID::Last as u16);\n"
" INTERFACES[proto_id as usize]\n"
"}\n\n"),
])
@staticmethod
def RegisterBindings(config: Configuration) -> CGThing:
code = CGList([
CGRegisterProxyHandlers(config),
CGInitAllStatics(config),
], "\n")
return CGImports(code, descriptors=[], callbacks=[], dictionaries=[], enums=[], typedefs=[], imports=[
'crate::codegen::GenericBindings',
'crate::DomTypes',
], config=config)
@staticmethod
def InterfaceTypes(config: Configuration) -> CGThing:
descriptors = sorted([MakeNativeName(d.name)
for d in config.getDescriptors(register=True,
isCallback=False,
isIteratorInterface=False)])
curr = CGList([CGGeneric(f"pub(crate) use crate::dom::{name.lower()}::{MakeNativeName(name)};\n")
for name in descriptors])
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
return curr
@staticmethod
def Bindings(config: Configuration) -> CGThing:
def leafModule(d: IDLObject) -> str:
return getModuleFromObject(d).split('::')[-1]
descriptors = config.getDescriptors(register=True, isIteratorInterface=False)
descriptors = (set(toBindingModuleFile(d.name) for d in descriptors if d.maybeGetSuperModule() is None)
| set(leafModule(d) for d in config.callbacks)
| set(leafModule(d) for d in config.getDictionaries()))
curr = CGList([CGGeneric(
"#[allow(clippy::derivable_impls)]\n"
f"pub mod {name};\n"
) for name in sorted(descriptors)])
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
return curr
@staticmethod
def ConcreteInheritTypes(config: Configuration) -> CGThing:
descriptors = config.getDescriptors(register=True, isCallback=False)
imports = [CGGeneric("use crate::dom::types::*;\n"),
CGGeneric("use script_bindings::codegen::InheritTypes::*;\n"),
CGGeneric("use crate::dom::bindings::conversions::{DerivedFrom, get_dom_class};\n"),
CGGeneric("use crate::dom::bindings::inheritance::Castable;\n"),
CGGeneric("use script_bindings::reflector::DomObject;\n\n")]
allprotos = []
topTypes = []
hierarchy = defaultdict(list)
for descriptor in descriptors:
name = descriptor.name
chain = descriptor.prototypeChain
upcast = descriptor.hasDescendants()
downcast = len(chain) != 1
if upcast and not downcast:
topTypes.append(name)
if not upcast:
chain = chain[:-1]
if chain:
allprotos.append(CGGeneric(f"impl Castable for {name} {{}}\n"))
for baseName in chain:
allprotos.append(CGGeneric(f"impl DerivedFrom<{baseName}> for {name} {{}}\n"))
if chain:
allprotos.append(CGGeneric("\n"))
if downcast:
assert descriptor.interface.parent is not None
hierarchy[descriptor.interface.parent.identifier.name].append(name)
typeIdCode = []
for base, derived in hierarchy.items():
if base in topTypes:
typeIdCode.append(CGGeneric(f"""
impl {base} {{
#[allow(dead_code)]
pub(crate) fn type_id(&self) -> &'static {base}TypeId {{
unsafe {{
&get_dom_class(self.reflector().get_jsobject().get())
.unwrap()
.type_id
.{base.lower()}
}}
}}
}}
"""))
curr = CGList(imports + typeIdCode + allprotos)
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
return curr
@staticmethod
def InheritTypes(config: Configuration) -> CGThing:
descriptors = config.getDescriptors(register=True, isCallback=False)
topTypes = []
hierarchy = defaultdict(list)
for descriptor in descriptors:
name = descriptor.name
upcast = descriptor.hasDescendants()
downcast = len(descriptor.prototypeChain) != 1
if upcast and not downcast:
topTypes.append(name)
if downcast:
assert descriptor.interface.parent is not None
hierarchy[descriptor.interface.parent.identifier.name].append(name)
typeIdCode: list = []
topTypeVariants = [
("ID used by abstract interfaces.", "pub abstract_: ()"),
("ID used by interfaces that are not castable.", "pub alone: ()"),
]
topTypeVariants += [
(f"ID used by interfaces that derive from {typeName}.",
f"pub {typeName.lower()}: {typeName}TypeId")
for typeName in topTypes
]
topTypeVariantsAsStrings = [CGGeneric(f"/// {variant[0]}\n{variant[1]},") for variant in topTypeVariants]
typeIdCode.append(CGWrapper(CGIndenter(CGList(topTypeVariantsAsStrings, "\n"), 4),
pre="#[derive(Copy)]\npub union TopTypeId {\n",
post="\n}\n\n"))
typeIdCode.append(CGGeneric("""\
impl Clone for TopTypeId {
fn clone(&self) -> Self { *self }
}
"""))
def type_id_variant(name: str) -> str:
return f"{name}({name}TypeId)" if name in hierarchy else name
for base, derived in hierarchy.items():
variants = []
if config.getDescriptor(base).concrete:
variants.append(CGGeneric(base))
variants += [CGGeneric(type_id_variant(derivedName)) for derivedName in derived]
derives = "Clone, Copy, Debug, PartialEq"
typeIdCode.append(CGWrapper(CGIndenter(CGList(variants, ",\n"), 4),
pre=f"#[derive({derives})]\npub enum {base}TypeId {{\n",
post="\n}\n\n"))
curr = CGList(typeIdCode)
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
return curr
@staticmethod
def ConcreteUnionTypes(config: Configuration) -> CGThing:
unions = set()
cgthings = []
allTypes = getAllTypes(
config.getDescriptors(), config.getDictionaries(), config.getCallbacks(), config.typedefs
)
for (t, descriptor) in allTypes:
t = t.unroll()
name = str(t)
if not t.isUnion() or name in unions:
continue
unions.add(name)
generic = "<crate::DomTypeHolder>" if containsDomInterface(t) else ""
cgthings += [CGGeneric(f"pub(crate) type {name} = "
f"script_bindings::codegen::GenericUnionTypes::{name}{generic};\n")]
curr = CGList(cgthings)
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
return curr
@staticmethod
def UnionTypes(config: Configuration) -> CGThing:
curr = UnionTypes(config.getDescriptors(),
config.getDictionaries(),
config.getCallbacks(),
config.typedefs,
config)
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
return curr
@staticmethod
def DomTypes(config: Configuration) -> CGThing:
curr = DomTypes(config.getDescriptors(),
config.getDescriptorProvider(),
config.getDictionaries(),
config.getCallbacks(),
config.typedefs,
config)
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
return curr
@staticmethod
def DomTypeHolder(config: Configuration) -> CGThing:
curr = DomTypeHolder(config.getDescriptors(),
config.getDescriptorProvider(),
config.getDictionaries(),
config.getCallbacks(),
config.typedefs,
config)
curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
return curr
@staticmethod
def SupportedDomApis(config: Configuration) -> CGThing:
descriptors = config.getDescriptors(isExposedConditionally=False)
base_path = os.path.dirname(__file__)
with open(os.path.join(base_path, 'apis.html.template')) as f:
base_template = f.read()
with open(os.path.join(base_path, 'api.html.template')) as f:
api_template = f.read()
with open(os.path.join(base_path, 'property.html.template')) as f:
property_template = f.read()
with open(os.path.join(base_path, 'interface.html.template')) as f:
interface_template = f.read()
apis = []
interfaces = []
for descriptor in descriptors:
props = []
for m in descriptor.interface.members:
if PropertyDefiner.getStringAttr(m, 'Pref') or \
PropertyDefiner.getStringAttr(m, 'Func') or \
PropertyDefiner.getStringAttr(m, 'Exposed') or \
m.getExtendedAttribute('SecureContext') or \
(m.isMethod() and m.isIdentifierLess()):
continue
bracket = '()' if m.isMethod() else ''
display = f"{m.identifier.name}{bracket}"
props += [property_template.replace('${name}', display)]
name = descriptor.interface.identifier.name
apis += [(api_template.replace('${interface}', name)
.replace('${properties}', '\n'.join(props)))]
interfaces += [interface_template.replace('${interface}', name)]
return CGGeneric((base_template.replace('${apis}', '\n'.join(apis))
.replace('${interfaces}', '\n'.join(interfaces))))
@staticmethod
def ContentEventHandlerNames(config: Configuration) -> CGThing:
handler_names: set[str] = set()
for descriptor in config.getDescriptors(isCallback=False, isIteratorInterface=False):
chain = descriptor.prototypeChain
if "HTMLElement" not in chain and "SVGElement" not in chain:
continue
for member in descriptor.interface.members:
if isEventHandlerCallback(member):
handler_names.add(member.identifier.name)
sorted_names = sorted(handler_names)
lines = [f' "{name}",' for name in sorted_names]
return CGGeneric("[\n" + "\n".join(lines) + "\n]\n")