| # This Source Code Form is subject to the terms of the Mozilla Public |
| # License, v. 2.0. If a copy of the MPL was not distributed with this |
| # file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| |
| # Common codegen classes. |
| |
| # fmt: off |
| |
| from __future__ import annotations |
| |
| from WebIDL import IDLUnionType |
| from WebIDL import IDLSequenceType |
| from collections import defaultdict |
| from itertools import groupby |
| from typing import cast, Optional, Any, Generic, TypeVar, TypeGuard |
| from collections.abc import Generator, Callable, Iterator, Iterable |
| from abc import abstractmethod |
| |
| import operator |
| import os |
| import re |
| from re import Match |
| import string |
| import textwrap |
| import functools |
| |
| from WebIDL import ( |
| BuiltinTypes, |
| IDLArgument, |
| IDLBuiltinType, |
| IDLCallback, |
| IDLDefaultDictionaryValue, |
| IDLDictionary, |
| IDLEmptySequenceValue, |
| IDLInterface, |
| IDLInterfaceMember, |
| IDLNullableType, |
| IDLNullValue, |
| IDLObject, |
| IDLPromiseType, |
| IDLType, |
| IDLTypedef, |
| IDLTypedefType, |
| IDLUndefinedValue, |
| IDLWrapperType, |
| IDLRecordType, |
| IDLAttribute, |
| IDLConst, |
| IDLInterfaceOrNamespace, |
| IDLValue, |
| IDLMethod, |
| IDLEnum, |
| IDLCallbackType, |
| IDLUnresolvedIdentifier, |
| IDLMaplikeOrSetlikeOrIterableBase, |
| IDLConstructor, |
| ) |
| |
| from configuration import ( |
| Configuration, |
| Descriptor, |
| DescriptorProvider, |
| MakeNativeName, |
| MemberIsLegacyUnforgeable, |
| assert_type, |
| getModuleFromObject, |
| getTypesFromCallback, |
| getTypesFromDescriptor, |
| getTypesFromDictionary, |
| iteratorNativeType, |
| ) |
| |
| AUTOGENERATED_WARNING_COMMENT = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n" |
| 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", |
| } |
| |
| 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(): |
| # pyrefly: ignore # missing-attribute |
| return any(map(lambda x: containsDomInterface(x), t.flatMemberTypes)) |
| if t.isDictionary(): |
| # pyrefly: ignore # missing-attribute, bad-argument-type |
| 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) |
| return False |
| |
| |
| def toStringBool(arg: bool) -> str: |
| return str(not not arg).lower() |
| |
| |
| def toBindingNamespace(arg: str) -> str: |
| """ |
| Namespaces are *_Bindings |
| |
| actual path is `codegen::Bindings::{toBindingModuleFile(name)}::{toBindingNamespace(name)}` |
| """ |
| return re.sub("((_workers)?$)", "_Binding\\1", MakeNativeName(arg)) |
| |
| |
| def toBindingModuleFile(arg: str) -> str: |
| """ |
| Module files are *Bindings |
| |
| actual path is `codegen::Bindings::{toBindingModuleFile(name)}::{toBindingNamespace(name)}` |
| """ |
| 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 |
| ] |
| |
| |
| # We'll want to insert the indent at the beginnings of lines, but we |
| # don't want to indent empty lines. So only indent lines that have a |
| # non-newline character on them. |
| lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE) |
| |
| |
| def indent(s: str, indentLevel: int = 2) -> str: |
| """ |
| Indent C++ code. |
| |
| Weird secret feature: this doesn't indent lines that start with # (such as |
| #include lines or #ifdef/#endif). |
| """ |
| if s == "": |
| return s |
| return re.sub(lineStartDetector, indentLevel * " ", s) |
| |
| @functools.cache |
| def dedent(s: str) -> str: |
| """ |
| Remove all leading whitespace from s, and remove a blank line |
| at the beginning. |
| """ |
| if s.startswith('\n'): |
| s = s[1:] |
| return textwrap.dedent(s) |
| |
| |
| # This works by transforming the fill()-template to an equivalent |
| # string.Template. |
| fill_multiline_substitution_re = re.compile(r"( *)\$\*{(\w+)}(\n)?") |
| |
| |
| @functools.cache |
| def compile_fill_template(template: str) -> tuple[string.Template, list[tuple[str, str, int]]]: |
| """ |
| Helper function for fill(). Given the template string passed to fill(), |
| do the reusable part of template processing and return a pair (t, |
| argModList) that can be used every time fill() is called with that |
| template argument. |
| |
| argsModList is list of tuples that represent modifications to be |
| made to args. Each modification has, in order: i) the arg name, |
| ii) the modified name, iii) the indent depth. |
| """ |
| t = dedent(template) |
| assert t.endswith("\n") or "\n" not in t |
| argModList = [] |
| |
| def replace(match: Match[str]) -> str: |
| """ |
| Replaces a line like ' $*{xyz}\n' with '${xyz_n}', |
| where n is the indent depth, and add a corresponding entry to |
| argModList. |
| |
| Note that this needs to close over argModList, so it has to be |
| defined inside compile_fill_template(). |
| """ |
| indentation, name, nl = match.groups() |
| depth = len(indentation) |
| |
| # Check that $*{xyz} appears by itself on a line. |
| prev = match.string[:match.start()] |
| if (prev and not prev.endswith("\n")) or nl is None: |
| raise ValueError(f"Invalid fill() template: $*{name} must appear by itself on a line") |
| |
| # Now replace this whole line of template with the indented equivalent. |
| 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: |
| """ |
| Convenience function for filling in a multiline template. |
| |
| `fill(template, name1=v1, name2=v2)` is a lot like |
| `string.Template(template).substitute({"name1": v1, "name2": v2})`. |
| |
| However, it's shorter, and has a few nice features: |
| |
| * If `template` is indented, fill() automatically dedents it! |
| This makes code using fill() with Python's multiline strings |
| much nicer to look at. |
| |
| * If `template` starts with a blank line, fill() strips it off. |
| (Again, convenient with multiline strings.) |
| |
| * fill() recognizes a special kind of substitution |
| of the form `$*{name}`. |
| |
| Use this to paste in, and automatically indent, multiple lines. |
| (Mnemonic: The `*` is for "multiple lines"). |
| |
| A `$*` substitution must appear by itself on a line, with optional |
| preceding indentation (spaces only). The whole line is replaced by the |
| corresponding keyword argument, indented appropriately. If the |
| argument is an empty string, no output is generated, not even a blank |
| line. |
| """ |
| |
| t, argModList = compile_fill_template(template) |
| # Now apply argModList to args |
| for (name, modified_name, depth) in argModList: |
| if not (args[name] == "" or args[name].endswith("\n")): |
| raise ValueError(f"Argument {name} with value {args[name]} is missing a newline") |
| args[modified_name] = indent(args[name], depth) |
| |
| return t.substitute(args) |
| |
| |
| class CGThing(): |
| """ |
| Abstract base class for things that spit out code. |
| """ |
| def __init__(self) -> None: |
| pass # Nothing for now |
| |
| @abstractmethod |
| def define(self) -> str: |
| """Produce code for a Rust file.""" |
| raise NotImplementedError |
| |
| |
| class CGMethodCall(CGThing): |
| """ |
| A class to generate selection of a method signature from a set of |
| signatures and generation of a call to that signature. |
| """ |
| 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: |
| # Special case: we can just do a per-signature method call |
| # here for our one signature and not worry about switching |
| # on anything. |
| signature = signatures[0] |
| self.cgRoot = CGList([getPerSignatureCall(signature)]) |
| requiredArgs = requiredArgCount(signature) |
| |
| if requiredArgs > 0: |
| code = ( |
| f"if argc < {requiredArgs} {{\n" |
| f" throw_type_error(*cx, \"Not enough arguments to {methodName}.\");\n" |
| " return false;\n" |
| "}") |
| self.cgRoot.prepend( |
| CGWrapper(CGGeneric(code), pre="\n", post="\n")) |
| |
| return |
| |
| # Need to find the right overload |
| maxArgCount = method.maxArgCount |
| allowedArgCounts = method.allowedArgCounts |
| |
| argCountCases = [] |
| for argCount in allowedArgCounts: |
| possibleSignatures = method.signaturesForArgCount(argCount) |
| if len(possibleSignatures) == 1: |
| # easy case! |
| signature = possibleSignatures[0] |
| argCountCases.append(CGCase(str(argCount), getPerSignatureCall(signature))) |
| continue |
| |
| distinguishingIndex = method.distinguishingIndexForArgCount(argCount) |
| |
| # We can't handle unions of non-object values at the distinguishing index. |
| 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()): |
| raise TypeError("No support for unions with non-object variants " |
| f"as distinguishing arguments yet: {args[distinguishingIndex].location}", |
| ) |
| |
| # Convert all our arguments up to the distinguishing index. |
| # Doesn't matter which of the possible signatures we use, since |
| # they all have the same types up to that point; just use |
| # possibleSignatures[0] |
| caseBody: list[CGThing] = [ |
| CGArgumentConverter(possibleSignatures[0][1][i], |
| i, "args", "argc", descriptor) |
| for i in range(0, distinguishingIndex)] |
| |
| # Select the right overload from our set. |
| 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 |
| |
| # First check for null or undefined |
| pickFirstSignature(f"{distinguishingArg}.get().is_null_or_undefined()", |
| lambda s: (s[1][distinguishingIndex].type.nullable() |
| or s[1][distinguishingIndex].type.isDictionary())) |
| |
| # Now check for distinguishingArg being an object that implements a |
| # non-callback interface. That includes typed arrays and |
| # arraybuffers. |
| interfacesSigs = [ |
| s for s in possibleSignatures |
| if (s[1][distinguishingIndex].type.isObject() |
| or s[1][distinguishingIndex].type.isUnion() |
| or s[1][distinguishingIndex].type.isNonCallbackInterface())] |
| # There might be more than one of these; we need to check |
| # which ones we unwrap to. |
| |
| if len(interfacesSigs) > 0: |
| # The spec says that we should check for "platform objects |
| # implementing an interface", but it's enough to guard on these |
| # being an object. The code for unwrapping non-callback |
| # interfaces and typed arrays will just bail out and move on to |
| # the next overload if the object fails to unwrap correctly. We |
| # could even not do the isObject() check up front here, but in |
| # cases where we have multiple object overloads it makes sense |
| # to do it only once instead of for each overload. That will |
| # also allow the unwrapping test to skip having to do codegen |
| # for the null-or-undefined case, which we already handled |
| # above. |
| caseBody.append(CGGeneric(f"if {distinguishingArg}.get().is_object() {{")) |
| for idx, sig in enumerate(interfacesSigs): |
| caseBody.append(CGIndenter(CGGeneric("'_block: {"))) |
| type = sig[1][distinguishingIndex].type |
| |
| # The argument at index distinguishingIndex can't possibly |
| # be unset here, because we've already checked that argc is |
| # large enough that we can examine this argument. |
| 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)) |
| |
| # Indent by 4, since we need to indent further than our "do" statement |
| caseBody.append(CGIndenter(testCode, 4)) |
| # If we got this far, we know we unwrapped to the right |
| # interface, so just do the call. Start conversion with |
| # distinguishingIndex + 1, since we already converted |
| # distinguishingIndex. |
| caseBody.append(CGIndenter( |
| getPerSignatureCall(sig, distinguishingIndex + 1), 4)) |
| caseBody.append(CGIndenter(CGGeneric("}"))) |
| |
| caseBody.append(CGGeneric("}")) |
| |
| # XXXbz Now we're supposed to check for distinguishingArg being |
| # an array or a platform object that supports indexed |
| # properties... skip that last for now. It's a bit of a pain. |
| pickFirstSignature(f"{distinguishingArg}.get().is_object() && is_array_like::<D>(*cx, {distinguishingArg})", |
| lambda s: |
| (s[1][distinguishingIndex].type.isSequence() |
| or s[1][distinguishingIndex].type.isObject())) |
| |
| # Check for vanilla JS objects |
| # XXXbz Do we need to worry about security wrappers? |
| 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())) |
| |
| # The remaining cases are mutually exclusive. The |
| # pickFirstSignature calls are what change caseBody |
| # Check for strings or enums |
| if pickFirstSignature(None, |
| lambda s: (s[1][distinguishingIndex].type.isString() |
| or s[1][distinguishingIndex].type.isEnum())): |
| pass |
| # Check for primitives |
| elif pickFirstSignature(None, |
| lambda s: s[1][distinguishingIndex].type.isPrimitive()): |
| pass |
| # Check for "any" |
| elif pickFirstSignature(None, |
| lambda s: s[1][distinguishingIndex].type.isAny()): |
| pass |
| else: |
| # Just throw; we have no idea what we're supposed to |
| # do with this. |
| caseBody.append(CGGeneric("throw_type_error(*cx, \"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, \"Not enough arguments to {methodName}.\");\n" |
| "return false;"))) |
| # XXXjdm Avoid unreachable statement warnings |
| # overloadCGThings.append( |
| # CGGeneric('panic!("We have an always-returning default case");\n' |
| # 'return false;')) |
| self.cgRoot = CGWrapper(CGList(overloadCGThings, "\n"), |
| pre="\n") |
| |
| def define(self) -> str: |
| return self.cgRoot.define() |
| |
| |
| def dictionaryHasSequenceMember(dictionary: IDLDictionary) -> bool: |
| return (any(typeIsSequenceOrHasSequenceMember(m.type) for m in |
| dictionary.members) |
| or (dictionary.parent |
| # pyrefly: ignore # bad-argument-type |
| and dictionaryHasSequenceMember(dictionary.parent))) |
| |
| |
| def typeIsSequenceOrHasSequenceMember(type: IDLType) -> bool: |
| if type.nullable(): |
| assert isinstance(type, IDLNullableType) |
| type = type.inner |
| if type.isSequence(): |
| return True |
| if type.isDictionary(): |
| # pyrefly: ignore # missing-attribute |
| 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}' |
| |
| |
| # Unfortunately, .capitalize() on a string will lowercase things inside the |
| # string, which we do not want. |
| def firstCap(string: str) -> str: |
| return f"{string[0].upper()}{string[1:]}" |
| |
| |
| class JSToNativeConversionInfo(): |
| """ |
| An object representing information about a JS-to-native conversion. |
| """ |
| def __init__(self, template: str | CGThing, default: str | None = None, declType: CGThing | None = None) -> None: |
| """ |
| template: A string representing the conversion code. This will have |
| template substitution performed on it as follows: |
| |
| ${val} is a handle to the JS::Value in question |
| |
| default: A string or None representing rust code for default value(if any). |
| |
| declType: A CGThing representing the native C++ type we're converting |
| to. This is allowed to be None if the conversion code is |
| supposed to be used as-is. |
| """ |
| 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: |
| """ |
| Get a template for converting a JS value to a native object based on the |
| given type and descriptor. If failureCode is given, then we're actually |
| testing whether we can convert the argument to the desired type. That |
| means that failures to convert due to the JS value being the wrong type of |
| value need to use failureCode instead of throwing exceptions. Failures to |
| convert that are due to JS exceptions (from toString or valueOf methods) or |
| out of memory conditions need to throw exceptions no matter what |
| failureCode is. |
| |
| If isDefinitelyObject is True, that means we know the value |
| isObject() and we have no need to recheck that. |
| |
| isMember is `False`, "Dictionary", "Union" or "Variadic", and affects |
| whether this function returns code suitable for an on-stack rooted binding |
| or suitable for storing in an appropriate larger structure. |
| |
| invalidEnumValueFatal controls whether an invalid enum value conversion |
| attempt will throw (if true) or simply return without doing anything (if |
| false). |
| |
| If defaultValue is not None, it's the IDL default value for this conversion |
| |
| If allowTreatNonObjectAsNull is true, then [TreatNonObjectAsNull] |
| extended attributes on nullable callback functions will be honored. |
| |
| The return value from this function is an object of JSToNativeConversionInfo consisting of four things: |
| |
| 1) A string representing the conversion code. This will have template |
| substitution performed on it as follows: |
| |
| ${val} replaced by an expression for the JS::Value in question |
| |
| 2) A string or None representing Rust code for the default value (if any). |
| |
| 3) A CGThing representing the native C++ type we're converting to |
| (declType). This is allowed to be None if the conversion code is |
| supposed to be used as-is. |
| |
| 4) A boolean indicating whether the caller has to root the result. |
| |
| """ |
| # We should not have a defaultValue if we know we're an object |
| assert not isDefinitelyObject or defaultValue is None |
| |
| isEnforceRange = type.hasEnforceRange() |
| isClamp = type.hasClamp() |
| if type.legacyNullToEmptyString: |
| treatNullAs = "EmptyString" |
| else: |
| treatNullAs = "Default" |
| |
| # If exceptionCode is not set, we'll just rethrow the exception we got. |
| # Note that we can't just set failureCode to exceptionCode, because setting |
| # failureCode will prevent pending exceptions from being set in cases when |
| # they really should be! |
| if exceptionCode is None: |
| exceptionCode = "return false;\n" |
| |
| if failureCode is None: |
| failOrPropagate = f"throw_type_error(*cx, &error);\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) |
| |
| # Helper functions for dealing with failures due to the JS value being the |
| # wrong type of value. |
| def onFailureNotAnObject(failureCode: str | None) -> CGThing: |
| return CGWrapper( |
| CGGeneric( |
| failureCode |
| or (f'throw_type_error(*cx, "{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, \"{firstCap(sourceDescription)} is not callable.\");\n' |
| f'{exceptionCode}')) |
| |
| # A helper function for handling default values. |
| 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") |
| |
| # A helper function for wrapping up the template body for |
| # possibly-nullable objecty stuff |
| def wrapObjectTemplate(templateBody: str, nullValue: str, isDefinitelyObject: bool, type: IDLType, |
| failureCode: str | None = None) -> str: |
| if not isDefinitelyObject: |
| # Handle the non-object cases by wrapping up the whole |
| # thing in an if cascade. |
| 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 |
| |
| # A helper function for types that implement FromJSValConvertible trait |
| def fromJSValTemplate(config: str, errorHandler: str, exceptionCode: str) -> str: |
| return f"""match FromJSValConvertible::from_jsval(*cx, ${{val}}, {config}) {{ |
| Ok(ConversionResult::Success(value)) => value, |
| Ok(ConversionResult::Failure(error)) => {{ |
| {errorHandler} |
| }} |
| _ => {{ |
| {exceptionCode} |
| }}, |
| }} |
| """ |
| |
| assert not (isEnforceRange and isClamp) # These are mutually exclusive |
| |
| 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 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 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() |
| # Per spec, what we're supposed to do is take the original |
| # Promise.resolve and call it with the original Promise as this |
| # value to make a Promise out of whatever value we actually have |
| # here. The question is which global we should use. There are |
| # a couple cases to consider: |
| # |
| # 1) Normal call to API with a Promise argument. This is a case the |
| # spec covers, and we should be using the current Realm's |
| # Promise. That means the current realm. |
| # 2) Promise return value from a callback or callback interface. |
| # This is in theory a case the spec covers but in practice it |
| # really doesn't define behavior here because it doesn't define |
| # what Realm we're in after the callback returns, which is when |
| # the argument conversion happens. We will use the current |
| # realm, which is the realm of the callable (which |
| # may itself be a cross-realm wrapper itself), which makes |
| # as much sense as anything else. In practice, such an API would |
| # once again be providing a Promise to signal completion of an |
| # operation, which would then not be exposed to anyone other than |
| # our own implementation code. |
| 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) # pyrefly: ignore # missing-attribute |
| |
| if descriptor.interface.isCallback(): |
| name = descriptor.nativeType |
| declType = CGWrapper(CGGeneric(f"{name}<D>"), pre="Rc<", post=">") |
| template = f"{name}::new(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, "{sourceDescription} does not ' |
| f'implement interface {descriptor.interface.identifier.name}.");\n' |
| f'{exceptionCode}') |
| else: |
| unwrapFailureCode = failureCode |
| |
| templateBody = fill( |
| """ |
| match ${function}($${val}, *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, "{sourceDescription} is not a typed array.");\n' |
| f'{exceptionCode}' |
| ) |
| else: |
| unwrapFailureCode = failureCode |
| |
| typeName = type.unroll().name # unroll because it may be nullable |
| |
| is_union_member = (isMember == "Union") |
| |
| if is_union_member: |
| 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 is_union_member: |
| 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") |
| # pyrefly: ignore # missing-attribute |
| enum = type.inner.identifier.name |
| if invalidEnumValueFatal: |
| handleInvalidEnumValueCode = failureCode or f"throw_type_error(*cx, &error); {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() |
| |
| # pyrefly: ignore # missing-attribute |
| 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>>") |
| # AutoRooter can trace properly inner raw GC thing pointers |
| 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: |
| # TODO: Need to root somehow |
| # https://github.com/servo/servo/issues/6382 |
| declType = CGGeneric("*mut JSObject") |
| |
| templateBody = wrapObjectTemplate(templateBody, default, |
| isDefinitelyObject, type, failureCode) |
| |
| return handleOptional(templateBody, declType, |
| handleDefault(default)) |
| |
| if type.isDictionary(): |
| # There are no nullable dictionaries |
| assert not type.nullable() or (isMember and isMember != "Dictionary") |
| |
| # pyrefly: ignore # missing-attribute |
| 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(): |
| # This one only happens for return values, and its easy: Just |
| # ignore the jsval. |
| 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: |
| """ |
| Take the templateBody and declType as returned by |
| getJSToNativeConversionInfo, a set of replacements as required by the |
| strings in such a templateBody, and a declName, and generate code to |
| convert into a stack Rust binding with that name. |
| """ |
| result = CGList([], "\n") |
| |
| conversion = CGGeneric(string.Template(templateBody).substitute(replacements)) |
| |
| if declType is not None: |
| newDecl = [ |
| CGGeneric("let "), |
| CGGeneric(declName), |
| CGGeneric(": "), |
| declType, |
| CGGeneric(" = "), |
| conversion, |
| CGGeneric(";"), |
| ] |
| result.append(CGList(newDecl)) |
| else: |
| result.append(conversion) |
| |
| if needsAutoRoot: |
| result.append(CGGeneric(f"auto_root!(in(*cx) let {declName} = {declName});")) |
| # Add an empty CGGeneric to get an extra newline after the argument |
| # conversion. |
| 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): |
| """ |
| A class that takes an IDL argument object, its index in the |
| argument list, and the argv and argc strings and generates code to |
| unwrap the argument to the right native type. |
| """ |
| 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: |
| """ |
| Reflect a Rust value into JS. |
| |
| * 'jsvalRef': a MutableHandleValue in which to store the result |
| of the conversion; |
| * 'result': the name of the variable in which the Rust value is stored; |
| * 'successCode': the code to run once we have done the conversion. |
| * 'pre': code to run before the conversion if rooting is necessary |
| """ |
| wrap = f"{pre}\n({result}).to_jsval(*cx, {jsvalRef});" |
| if successCode: |
| wrap += f"\n{successCode}" |
| return wrap |
| |
| |
| def typeNeedsCx(type: IDLType | None, retVal: bool = False) -> bool: |
| if type is None: |
| return False |
| 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 any(typeNeedsCx(t) for t in flatMemberTypes) |
| if retVal and type.isSpiderMonkeyInterface(): |
| return True |
| return type.isAny() or type.isObject() |
| |
| |
| 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") |
| |
| |
| # Returns a conversion behavior suitable for a type |
| 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(): |
| # Note: the actual behavior passed here doesn't matter for nullable |
| # strings. |
| 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 |
| |
| |
| # Returns a CGThing containing the type of the return value. |
| def getRetvalDeclarationForType(returnType: IDLType | None, descriptorProvider: DescriptorProvider) -> CGThing: |
| if returnType is None or returnType.isUndefined(): |
| # Nothing to declare |
| 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: |
| return builtin_return_type(returnType) |
| 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(): |
| # pyrefly: ignore # missing-attribute |
| 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( |
| # pyrefly: ignore # missing-attribute |
| returnType.unroll().inner.identifier.name) |
| result = CGGeneric(descriptor.returnType) |
| if returnType.nullable(): |
| result = CGWrapper(result, pre="Option<", post=">") |
| return result |
| if returnType.isCallback(): |
| # pyrefly: ignore # missing-attribute |
| 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() |
| # pyrefly: ignore # missing-attribute |
| 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]: |
| """ |
| A string representing the condition for a member to actually be exposed. |
| Any of the arguments can be None. If not None, they should have the |
| following types: |
| |
| pref: The name of the preference. |
| func: The name of the function. |
| exposed: One or more names of an exposed global. |
| secure: Requires secure context. |
| """ |
| 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]): |
| """ |
| A common superclass for defining things on prototype objects. |
| |
| Subclasses should implement generateArray to generate the actual arrays of |
| things we're defining. They should also set self.regular to the list of |
| things exposed to web pages. |
| """ |
| 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: |
| # We only need to generate id arrays for things that will end |
| # up used via ResolveProperty or EnumerateProperties. |
| return self.generateArray(self.regular, self.variableName()) |
| |
| @staticmethod |
| def getStringAttr(member: IDLInterfaceMember, name: str) -> str | None: |
| attr = member.getExtendedAttribute(name) |
| if attr is None: |
| return None |
| # It's a list of strings |
| assert len(attr) == 1 |
| assert attr[0] is not None |
| return attr[0] |
| |
| @staticmethod |
| def getControllingCondition(interfaceMember: 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: |
| """ |
| This method generates our various arrays. |
| |
| array is an array of interface members as passed to generateArray |
| |
| name is the name as passed to generateArray |
| |
| specTemplate is a template for each entry of the spec array |
| |
| specTerminator is a terminator for the spec array (inserted at the end |
| of the array), or None |
| |
| specType is the actual typename of our spec |
| |
| getDataTuple is a callback function that takes an array entry and |
| returns a tuple suitable for substitution into specTemplate. |
| """ |
| |
| # We generate an all-encompassing list of lists of specs, with each sublist |
| # representing a group of members that share a common pref name. That will |
| # make sure the order of the properties as exposed on the interface and |
| # interface prototype objects does not change when pref control is added to |
| # members while still allowing us to define all the members in the smallest |
| # number of JSAPI calls. |
| assert len(array) != 0 |
| specs = [] |
| prefableSpecs = [] |
| prefableTemplate = ' Guard::new(%s, (%s)[%d])' |
| if isinstance(specTemplate, str): |
| origTemplate = specTemplate |
| specTemplate = lambda _: origTemplate # noqa |
| |
| 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: |
| """ |
| Takes the same set of parameters as generateGuardedArray but instead |
| generates a single, flat array of type `&[specType]` that contains all |
| provided members. The provided members' conditions shall be homogeneous, |
| or else this method will fail. |
| """ |
| |
| # this method can't handle heterogeneous condition |
| groups = groupby(array, lambda m: getCondition(m, self.descriptor)) |
| assert len(list(groups)) == 1 |
| |
| if isinstance(specTemplate, str): |
| origTemplate = specTemplate |
| specTemplate = lambda _: origTemplate # noqa |
| |
| 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}") |
| |
| |
| # The length of a method is the minimum of the lengths of the |
| # argument lists of all its overloads. |
| 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): |
| """ |
| A class for defining methods on a prototype object. |
| """ |
| 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) |
| |
| # TODO: Separate the `(static, unforgeable, crossorigin) = (False, False, True)` case |
| # to a separate class or something. |
| |
| # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822 |
| # We should be able to check for special operations without an |
| # identifier. For now we check if the name starts with __ |
| |
| # Ignore non-static methods for callback interfaces |
| 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) |
| |
| # TODO: Once iterable is implemented, use tiebreak rules instead of |
| # failing. Also, may be more tiebreak rules to implement once spec bug |
| # is resolved. |
| # https://www.w3.org/Bugs/Public/show_bug.cgi?id=28592 |
| def hasIterator(methods: 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)) |
| |
| # Check whether we need to output an @@iterator due to having an indexed |
| # getter. We only do this while outputting non-static and |
| # non-unforgeable methods, since the @@iterator function will be |
| # neither. |
| if (not static |
| and not unforgeable |
| and 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", # Not enumerable, per spec. |
| "condition": "Condition::Satisfied"}) |
| |
| # Generate the keys/values/entries aliases for value iterables. |
| maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable |
| if (not static and not unforgeable and not crossorigin |
| and maplikeOrSetlikeOrIterable |
| and maplikeOrSetlikeOrIterable.isIterable() |
| and maplikeOrSetlikeOrIterable.isValueIterator()): |
| m = maplikeOrSetlikeOrIterable |
| |
| # Add our keys/values/entries/forEach |
| self.regular.append({ |
| "name": "keys", |
| "methodInfo": False, |
| "selfHostedName": "ArrayKeys", |
| "length": 0, |
| "flags": "JSPROP_ENUMERATE", |
| "condition": PropertyDefiner.getControllingCondition(m, |
| descriptor) |
| }) |
| self.regular.append({ |
| "name": "values", |
| "methodInfo": False, |
| "selfHostedName": "$ArrayValues", |
| "length": 0, |
| "flags": "JSPROP_ENUMERATE", |
| "condition": PropertyDefiner.getControllingCondition(m, |
| descriptor) |
| }) |
| self.regular.append({ |
| "name": "entries", |
| "methodInfo": False, |
| "selfHostedName": "ArrayEntries", |
| "length": 0, |
| "flags": "JSPROP_ENUMERATE", |
| "condition": PropertyDefiner.getControllingCondition(m, |
| descriptor) |
| }) |
| self.regular.append({ |
| "name": "forEach", |
| "methodInfo": False, |
| "selfHostedName": "ArrayForEach", |
| "length": 1, |
| "flags": "JSPROP_ENUMERATE", |
| "condition": PropertyDefiner.getControllingCondition(m, |
| descriptor) |
| }) |
| |
| 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), |
| "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"]) |
| # Go through an intermediate type here, because it's not |
| # easy to tell whether the methodinfo is a JSJitInfo or |
| # a JSTypedMethodJitInfo here. The compiler knows, though, |
| # so let it do the work. |
| jitinfo = (f"unsafe {{ {identifier}_methodinfo.get() }}" |
| " as *const _ as *const JSJitInfo") |
| accessor = f"Some(generic_method::<{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) |
| |
| # TODO: Separate the `(static, unforgeable, crossorigin) = (False, False, True)` case |
| # to a separate class or something. |
| |
| 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.hasLegacyLenientThis(): |
| accessor = f"generic_lenient_getter::<{exceptionToRejection}>" |
| else: |
| accessor = f"generic_getter::<{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.hasLegacyLenientThis(): |
| accessor = "generic_lenient_setter" |
| else: |
| accessor = "generic_setter" |
| 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]): |
| """ |
| A class for definining constants on the interface object |
| """ |
| 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) |
| |
| |
| # We'll want to insert the indent at the beginnings of lines, but we |
| # don't want to indent empty lines. So only indent lines that have a |
| # non-newline character on them. |
| lineStartDetector = re.compile("^(?=[^\n])", re.MULTILINE) |
| |
| |
| class CGIndenter(CGThing): |
| """ |
| A class that takes another CGThing and generates code that indents that |
| CGThing by some number of spaces. The default indent is two spaces. |
| """ |
| def __init__(self, child: 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): |
| """ |
| Generic CGThing that wraps other CGThings with pre and post text. |
| """ |
| 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: |
| # We don't use lineStartDetector because we don't want to |
| # insert whitespace at the beginning of our _first_ line. |
| defn = stripTrailingWhitespace( |
| defn.replace("\n", f"\n{' ' * len(self.pre)}")) |
| return f"{self.pre}{defn}{self.post}" |
| |
| |
| class CGRecord(CGThing): |
| """ |
| CGThing that wraps value CGThing in record with key type equal to keyType parameter |
| """ |
| 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): |
| """ |
| Generates the appropriate import/use statements. |
| """ |
| 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: |
| """ |
| Adds a set of imports. |
| """ |
| |
| 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) |
| |
| # Import the type names used in the callbacks that are being defined. |
| for c in callbacks: |
| types += relatedTypesForSignatures(c) |
| |
| # Import the type names used in the dictionaries that are being defined. |
| for d in dictionaries: |
| types += componentTypes(d) |
| |
| # Import the type names used in the typedefs that are being defined. |
| for t in typedefs: |
| if not t.innerType.isCallback(): |
| types += componentTypes(t.innerType) |
| |
| # Normalize the types we've collected and remove any ones which can't be imported. |
| types = removeWrapperAndNullableTypes(types) |
| |
| descriptorProvider = config.getDescriptorProvider() |
| extras = [] |
| for t in types: |
| # Importing these callbacks in the same module that defines them is an error. |
| if t.isCallback(): |
| if getIdentifier(t) in [c.identifier for c in callbacks]: |
| continue |
| # Importing these types in the same module that defines them is an error. |
| if t in dictionaries or 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) |
| 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: |
| """ |
| Static helper method to build multiple wrapped namespaces. |
| """ |
| if not namespaces: |
| return child |
| inner = CGNamespace.build(namespaces[1:], child, public=public) |
| return CGNamespace(namespaces[0], inner, public=public) |
| |
| |
| def 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] |
| # Pad out the list to the right length with ID::Last so we |
| # guarantee that all the lists are the same length. ID::Last |
| # is never the ID of any prototype, so it's safe to use as |
| # padding. |
| 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): |
| """ |
| Generate a DOMJSClass for a given descriptor |
| """ |
| 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): |
| """ |
| Generate a type assertion for inheritance |
| """ |
| 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": |
| # OffscreenCanvasRenderingContext2D embeds a CanvasRenderingContext2D |
| # instead of a Reflector as an optimization, |
| # but this is fine since CanvasRenderingContext2D |
| # also has a reflector |
| # |
| # FIXME *RenderingContext2D should use Inline |
| parentName = "crate::dom::canvasrenderingcontext2d::CanvasRenderingContext2D" |
| 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): |
| """ |
| Generate code for a list of GCThings. Just concatenates them together, with |
| an optional joiner string. "\n" is a common joiner. |
| """ |
| def __init__(self, children: Iterable[CGThing | None], joiner: str = "") -> None: |
| CGThing.__init__(self) |
| # Make a copy of the kids into a list, because if someone passes in a |
| # generator we won't be able to both declare and define ourselves, or |
| # define ourselves more than once! |
| self.children = list(children) |
| self.joiner = joiner |
| |
| def append(self, child: 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): |
| """ |
| A class that spits out a fixed string into the codegen. Can spit out a |
| separate string for the declaration too. |
| """ |
| 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(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]: |
| """ |
| Generate all the types we're dealing with. For each type, a tuple |
| containing type, descriptor, dictionary is yielded. The |
| descriptor can be None if the type does not come from a descriptor. |
| """ |
| for d in descriptors: |
| for t in getTypesFromDescriptor(d): |
| if t.isRecord(): |
| # pyrefly: ignore # missing-attribute |
| yield (t.inner, d) |
| yield (t, d) |
| for dictionary in dictionaries: |
| for t in getTypesFromDictionary(dictionary): |
| # pyrefly: ignore # missing-attribute |
| if t.isRecord(): |
| # pyrefly: ignore # missing-attribute |
| yield (t.inner, None) |
| yield (t, None) |
| for callback in callbacks: |
| for t in getTypesFromCallback(callback): |
| if t.isRecord(): |
| # pyrefly: ignore # missing-attribute |
| yield (t.inner, None) |
| yield (t, None) |
| for typedef in typedefs: |
| if typedef.innerType.isRecord(): |
| 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: |
| """ |
| Returns a CGList containing CGUnionStructs for every union. |
| """ |
| |
| imports = [ |
| 'crate::import::base::*', |
| 'crate::codegen::DomTypes::DomTypes', |
| 'crate::conversions::windowproxy_from_handlevalue', |
| 'crate::record::Record', |
| 'js::typedarray', |
| ] |
| |
| # Now find all the things we'll need as arguments and return values because |
| # we need to wrap or unwrap them. |
| unionStructs: dict[str, Any] = dict() |
| for (t, descriptor) in getAllTypes(descriptors, dictionaries, callbacks, typedefs): |
| # pyrefly: ignore # missing-attribute |
| 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) |
| ]) |
| |
| # Sort unionStructs by key, retrieve value |
| 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: |
| # No other interface will implement DeriveFrom<Foo> for this Foo, so avoid |
| # implementing it for itself. |
| 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::str::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(): |
| """ |
| A class for outputting the type and name of an 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}" |
| # XXXjdm Support default arguments somehow :/ |
| # if self.default is not None: |
| # string += " = " + self.default |
| return string |
| |
| def define(self) -> str: |
| return f'{self.argType} {self.name}' |
| |
| |
| class CGAbstractMethod(CGThing): |
| """ |
| An abstract class for generating code for a method. Subclasses |
| should override definition_body to create the actual code. |
| |
| descriptor is the descriptor for the interface the method is associated with |
| |
| name is the name of the method as a string |
| |
| returnType is the IDLType of the return value |
| |
| args is a list of Argument objects |
| |
| inline should be True to generate an inline method, whose body is |
| part of the declaration. |
| |
| alwaysInline should be True to generate an inline method annotated with |
| MOZ_ALWAYS_INLINE. |
| |
| If templateArgs is not None it should be a list of strings containing |
| template arguments, and the function will be templatized using those |
| arguments. |
| |
| docs is None or documentation for the method in a string. |
| |
| unsafe is used to add the decorator 'unsafe' to a function, giving as a result |
| an 'unsafe fn()' declaration. |
| """ |
| 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 = 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): |
| """ |
| A method for testing whether we should be exposing this interface object. |
| This can perform various tests depending on what conditions are specified |
| on the interface. |
| """ |
| def __init__(self, descriptor: Descriptor) -> None: |
| CGAbstractMethod.__init__(self, descriptor, |
| 'ConstructorEnabled', 'bool', |
| [Argument("SafeJSContext", "aCx"), |
| 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]}(aCx, aObj)") |
| |
| secure = iface.getExtendedAttribute("SecureContext") |
| if secure: |
| conditions.append(""" |
| unsafe { |
| let in_realm_proof = AlreadyInRealm::assert_for_cx(aCx); |
| D::GlobalScope::from_context(*aCx, InRealm::Already(&in_realm_proof)).is_secure_context() |
| } |
| """) |
| |
| return CGList((CGGeneric(cond) for cond in conditions), " &&\n") |
| |
| |
| def InitLegacyUnforgeablePropertiesOnHolder(descriptor: Descriptor, properties: PropertyArrays) -> CGThing: |
| """ |
| Define the unforgeable properties on the unforgeable holder for |
| the interface represented by descriptor. |
| |
| properties is a PropertyArrays instance. |
| """ |
| unforgeables = [] |
| |
| defineLegacyUnforgeableAttrs = "define_guarded_properties::<D>(cx, unforgeable_holder.handle(), %s, global);" |
| defineLegacyUnforgeableMethods = "define_guarded_methods::<D>(cx, 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: |
| """ |
| Copy the unforgeable properties from the unforgeable holder for |
| this interface to the instance object we have. |
| """ |
| if not descriptor.hasLegacyUnforgeableMembers: |
| return "" |
| copyCode = "" |
| |
| # For proxies, we want to define on the expando object, not directly on the |
| # reflector, so we can make sure we don't get confused by named getters. |
| if descriptor.proxy: |
| copyCode += """\ |
| rooted!(in(*cx) let mut expando = ptr::null_mut::<JSObject>()); |
| ensure_expando_object(*cx, obj.handle().into(), expando.handle_mut()); |
| """ |
| obj = "expando" |
| else: |
| obj = "obj" |
| |
| # We can't do the fast copy for globals, because we can't allocate the |
| # unforgeable holder for those with the right JSClass. Luckily, there |
| # aren't too many globals being created. |
| 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, {obj}.handle(), unforgeable_holder.handle())); |
| """ |
| |
| return copyCode |
| |
| |
| class CGWrapMethod(CGAbstractMethod): |
| """ |
| Class that generates the Foo_Binding::Wrap function for non-callback |
| interfaces. |
| """ |
| def __init__(self, descriptor: Descriptor) -> None: |
| assert not descriptor.interface.isCallback() |
| assert not descriptor.isGlobal() |
| args = [Argument('SafeJSContext', 'cx'), |
| Argument('&D::GlobalScope', 'scope'), |
| Argument('Option<HandleObject>', 'given_proto'), |
| Argument(f"Box<{descriptor.concreteType}>", 'object'), |
| Argument('CanGc', '_can_gc')] |
| 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: |
| unforgeable = CopyLegacyUnforgeablePropertiesToInstance(self.descriptor) |
| if self.descriptor.proxy: |
| if self.descriptor.isMaybeCrossOriginObject(): |
| proto = "ptr::null_mut()" |
| lazyProto = "true" # Our proxy handler will manage the prototype |
| else: |
| proto = "canonical_proto.get()" |
| lazyProto = "false" |
| |
| create = f""" |
| let handler: *const libc::c_void = |
| RegisterBindings::proxy_handlers::{self.descriptor.interface.identifier.name} |
| .load(std::sync::atomic::Ordering::Acquire); |
| rooted!(in(*cx) let obj = NewProxyObject( |
| *cx, |
| handler, |
| Handle::from_raw(UndefinedHandleValue), |
| {proto}, |
| ptr::null(), |
| {lazyProto}, |
| )); |
| assert!(!obj.is_null()); |
| SetProxyReservedSlot( |
| obj.get(), |
| 0, |
| &PrivateValue(raw.as_ptr() as *const libc::c_void), |
| ); |
| """ |
| else: |
| create = """ |
| rooted!(in(*cx) let mut proto = ptr::null_mut::<JSObject>()); |
| if let Some(given) = given_proto { |
| proto.set(*given); |
| if get_context_realm(*cx) != get_object_realm(*given) { |
| assert!(JS_WrapObject(*cx, proto.handle_mut())); |
| } |
| } else { |
| proto.set(*canonical_proto); |
| } |
| rooted!(in(*cx) let obj = JS_NewObjectWithGivenProto( |
| *cx, |
| &Class.get().base, |
| proto.handle(), |
| )); |
| assert!(!obj.is_null()); |
| JS_SetReservedSlot( |
| obj.get(), |
| DOM_OBJECT_SLOT, |
| &PrivateValue(raw.as_ptr() as *const libc::c_void), |
| ); |
| """ |
| if self.descriptor.weakReferenceable: |
| create += """ |
| let val = PrivateValue(ptr::null()); |
| JS_SetReservedSlot(obj.get(), DOM_WEAK_SLOT, &val); |
| """ |
| |
| return CGGeneric(f""" |
| let raw = Root::new(MaybeUnreflectedDom::from_box(object)); |
| |
| let scope = scope.reflector().get_jsobject(); |
| assert!(!scope.get().is_null()); |
| assert!(((*get_object_class(scope.get())).flags & JSCLASS_IS_GLOBAL) != 0); |
| let _ac = JSAutoRealm::new(*cx, scope.get()); |
| |
| rooted!(in(*cx) let mut canonical_proto = ptr::null_mut::<JSObject>()); |
| GetProtoObject::<D>(cx, scope, canonical_proto.handle_mut()); |
| assert!(!canonical_proto.is_null()); |
| |
| {create} |
| let root = raw.reflect_with(obj.get()); |
| |
| {unforgeable} |
| |
| DomRoot::from_ref(&*root)\ |
| """) |
| |
| |
| class CGWrapGlobalMethod(CGAbstractMethod): |
| """ |
| Class that generates the Foo_Binding::Wrap function for global interfaces. |
| """ |
| def __init__(self, descriptor: Descriptor, properties: PropertyArrays) -> None: |
| assert not descriptor.interface.isCallback() |
| assert descriptor.isGlobal() |
| args = [Argument('SafeJSContext', '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, obj.handle(), {array.variableName()}.get(), obj.handle());" |
| for (function, array) in pairs if array.length() > 0] |
| membersStr = "\n".join(members) |
| |
| 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, |
| &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()); |
| |
| let _ac = JSAutoRealm::new(*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, obj.handle(), canonical_proto.handle())); |
| let mut immutable = false; |
| assert!(JS_SetImmutablePrototype(*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): |
| """ |
| Class for codegen of an implementation of the IDLInterface trait. |
| """ |
| 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 }})" |
| return f""" |
| impl IDLInterface for {name} {{ |
| #[inline] |
| fn derives(class: &'static DOMClass) -> bool {{ |
| {check} |
| }} |
| }} |
| """ |
| |
| |
| class CGIteratorDerives(CGThing): |
| """ |
| Class for codegen of an implementation of the IteratorDerives trait. |
| """ |
| 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): |
| """ |
| Class for codegen of an implementation of the DomObjectWrap trait. |
| """ |
| 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( |
| SafeJSContext, |
| &GlobalScope, |
| Option<HandleObject>, |
| Box<Self>, |
| CanGc, |
| ) -> Root<Dom<Self>> = {bindingModule}::Wrap::<crate::DomTypeHolder>; |
| }} |
| """ |
| |
| |
| class CGDomObjectIteratorWrap(CGThing): |
| """ |
| Class for codegen of an implementation of the DomObjectIteratorWrap trait. |
| """ |
| 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( |
| SafeJSContext, |
| &GlobalScope, |
| Option<HandleObject>, |
| Box<IterableIterator<crate::DomTypeHolder, Self>>, |
| CanGc, |
| ) -> Root<Dom<IterableIterator<crate::DomTypeHolder, Self>>> = {bindingModule}::Wrap::<crate::DomTypeHolder>; |
| }} |
| """ |
| |
| |
| class CGAbstractExternMethod(CGAbstractMethod): |
| """ |
| Abstract base class for codegen of implementation-only (no |
| declaration) static methods. |
| """ |
| 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): |
| """ |
| Generate the CollectJSONAttributes method for an interface descriptor |
| """ |
| 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>( |
| SafeJSContext::from_ptr(cx), |
| HandleObject::from_raw(obj), |
| global)); |
| if is_satisfied { |
| rooted!(in(cx) let mut temp = UndefinedValue()); |
| if !get_${name}::<D>(cx, obj, this, JSJitGetterCallArgs { _base: temp.handle_mut().into() }) { |
| return false; |
| } |
| if !JS_DefineProperty(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): |
| """ |
| Generate the CreateInterfaceObjects method for an interface descriptor. |
| |
| properties should be a PropertyArrays instance. |
| """ |
| def __init__(self, descriptor: Descriptor, properties: PropertyArrays, haveUnscopables: bool, haveLegacyWindowAliases: bool) -> None: |
| args = [Argument('SafeJSContext', '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)" |
| else: |
| proto = "JS_NewPlainObject(*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, 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, 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))" |
| 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, 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, |
| 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));")) |
| 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, |
| 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, 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" # Not enumerable, per spec. |
| 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, |
| # XXX If we ever create non-enumerable properties that can |
| # be aliased, we should consider making the aliases |
| # match the enumerability of the property being aliased. |
| CGGeneric(fill( |
| """ |
| assert!(${defineFn}(*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, 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, global, &named_constructors, prototype.handle());")) |
| |
| if self.descriptor.hasLegacyUnforgeableMembers: |
| # We want to use the same JSClass and prototype as the object we'll |
| # end up defining the unforgeable properties on in the end, so that |
| # we can use JS_InitializePropertiesFromCompatibleNativeObject to do |
| # a fast copy. In the case of proxies that's null, because the |
| # expando object is a vanilla object, but in the case of other DOM |
| # objects it's whatever our class is. |
| # |
| # Also, for a global we can't use the global's class; just use |
| # nullpr and when we do the copy off the holder we'll take a slower |
| # path. This also means that we don't need to worry about matching |
| # the prototype. |
| 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, {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): |
| """ |
| A method for getting a per-interface object (a prototype object or interface |
| constructor object). |
| """ |
| def __init__(self, descriptor: Descriptor, name: str, idPrefix: str = "", pub: bool = False) -> None: |
| args = [Argument('SafeJSContext', '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): |
| """ |
| A method for getting the interface prototype object. |
| """ |
| 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): |
| """ |
| A method for getting the interface constructor object. |
| """ |
| 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): |
| """ |
| A method to create and cache the proxy trap for a given interface. |
| """ |
| 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)' |
| # The base class `BaseProxyHandler`'s `setImmutablePrototype` (not to be |
| # confused with ECMAScript's `[[SetImmutablePrototype]]`) always fails. |
| # This is the desired behavior, so we don't override it. |
| |
| customSet = 'None' |
| if self.descriptor.isMaybeCrossOriginObject(): |
| # `maybe_cross_origin_set_rawcx` doesn't support legacy platform objects' |
| # `[[Set]]` (https://heycam.github.io/webidl/#legacy-platform-object-set) (yet). |
| 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_void_ptr())\ |
| """) |
| |
| |
| class CGDefineDOMInterfaceMethod(CGAbstractMethod): |
| """ |
| A method for resolve hooks to try to lazily define the interface object for |
| a given interface. |
| """ |
| def __init__(self, descriptor: Descriptor) -> None: |
| assert descriptor.interface.hasInterfaceObject() |
| args = [ |
| Argument('SafeJSContext', '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) -> bool: |
| return (considerTypes |
| and (typeNeedsCx(returnType, True) |
| or any(typeNeedsCx(a.type) for a in arguments))) |
| |
| |
| class CGCallGenerator(CGThing): |
| """ |
| A class to generate an actual call to a C++ object. Assumes that the C++ |
| object is stored in a variable whose name is given by the |object| argument. |
| |
| errorResult should be a string for the value to return in case of an |
| exception from the native code, or None if no error reporting is needed. |
| """ |
| def __init__(self, |
| errorResult: str | None, |
| arguments: list[tuple[IDLArgument | FakeArgument, str]], |
| argsPre: list[str], |
| returnType: IDLType | None, |
| extendedAttributes: list[str], |
| descriptor: Descriptor, |
| nativeMethodName: str, |
| 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: |
| # XXXjdm Perhaps we should pass all nontrivial types by borrowed pointer |
| if a.type.isDictionary() and not type_needs_tracing(a.type): |
| name = f"&{name}" |
| args.append(CGGeneric(name)) |
| |
| needsCx = needCx(returnType, (a for (a, _) in arguments), True) |
| |
| if "cx" not in argsPre and needsCx: |
| args.prepend(CGGeneric("cx")) |
| if nativeMethodName in descriptor.inRealmMethods: |
| args.append(CGGeneric("InRealm::already(&AlreadyInRealm::assert_for_cx(cx))")) |
| if nativeMethodName in descriptor.canGcMethods: |
| args.append(CGGeneric("CanGc::note()")) |
| if rootType: |
| args.append(CGGeneric("retval.handle_mut()")) |
| |
| # Build up our actual call |
| self.cgRoot = CGList([], "\n") |
| |
| 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(CanGc::note());\n")) |
| |
| if isFallible: |
| if static: |
| glob = "global.upcast::<D::GlobalScope>()" |
| else: |
| glob = "&this.global_(InRealm::already(&AlreadyInRealm::assert_for_cx(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(cx, {glob}, e, CanGc::note());\n" |
| f" return{errorResult};\n" |
| " },\n" |
| "};")) |
| |
| def define(self) -> str: |
| return self.cgRoot.define() |
| |
| |
| class CGPerSignatureCall(CGThing): |
| """ |
| This class handles the guts of generating code for a particular |
| call signature. A call signature consists of four things: |
| |
| 1) A return type, which can be None to indicate that there is no |
| actual return value (e.g. this is an attribute setter) or an |
| IDLType if there's an IDL type involved (including |void|). |
| 2) An argument list, which is allowed to be empty. |
| 3) A name of a native method to call. |
| 4) Whether or not this method is static. |
| |
| We also need to know whether this is a method or a getter/setter |
| to do error reporting correctly. |
| |
| The idlNode parameter can be either a method or an attr. We can query |
| |idlNode.identifier| in both cases, so we can be agnostic between the two. |
| """ |
| # XXXbz For now each entry in the argument list is either an |
| # IDLArgument or a FakeArgument, but longer-term we may want to |
| # have ways of flagging things like JSContext* or optional_argc in |
| # there. |
| |
| def __init__(self, |
| returnType: 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 |
| cgThings.append(CGCallGenerator( |
| errorResult, |
| self.getArguments(), self.argsPre, returnType, |
| self.extendedAttributes, descriptor, nativeMethodName, |
| 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" |
| # Maplike methods have `any` return values in WebIDL, but our internal bindings |
| # use stronger types so we need to exclude them from being handled like other |
| # generated code. |
| 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): |
| """ |
| A class to generate code for a switch statement. |
| |
| Takes three constructor arguments: an expression, a list of cases, |
| and an optional default. |
| |
| Each case is a CGCase. The default is a CGThing for the body of |
| the default case, if any. |
| """ |
| def __init__(self, expression: str, cases: list[CGThing], 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): |
| """ |
| A class to generate code for a case statement. |
| |
| Takes three constructor arguments: an expression, a CGThing for |
| the body (allowed to be None if there is no body), and an optional |
| argument (defaulting to False) for whether to fall through. |
| """ |
| def __init__(self, expression: 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") |
| # bodyList.append(CGGeneric('panic!("fall through unsupported"); /* Fall through */')) |
| self.append(CGIndenter(bodyList)) |
| self.append(CGGeneric("}")) |
| |
| |
| class CGGetterCall(CGPerSignatureCall): |
| """ |
| A class to generate a native object getter call for a particular IDL |
| getter. |
| """ |
| def __init__(self, argsPre: 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(): |
| """ |
| A class that quacks like an IDLArgument. This is used to make |
| setters look like method calls or for special operations. |
| """ |
| def __init__(self, type: 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): |
| """ |
| A class to generate a native object setter call for a particular IDL |
| setter. |
| """ |
| 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: |
| # We have no return value |
| return "\ntrue" |
| |
| def getArgc(self) -> str: |
| return "1" |
| |
| |
| class CGAbstractStaticBindingMethod(CGAbstractMethod): |
| """ |
| Common class to generate the JSNatives for all our static methods, getters |
| and setters. This will generate the function declaration and unwrap the |
| global object. Subclasses are expected to override the generate_code |
| function to do the rest of the work. This function should return a |
| CGThing which is already properly indented. |
| """ |
| def __init__(self, descriptor: Descriptor, name: str, templateArgs: list[str] | None = None) -> None: |
| args = [ |
| Argument('*mut JSContext', '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: |
| # Figure out the name of our constructor for reporting purposes. |
| # For unnamed webidl constructors, identifier.name is "constructor" but |
| # the name JS sees is the interface name; for legacy factory functions |
| # identifier.name is the actual name. |
| ctorName = ctor.identifier.name |
| if ctorName == "constructor": |
| return descriptor.interface.identifier.name |
| return ctorName |
| |
| |
| class CGSpecializedMethod(CGAbstractExternMethod): |
| """ |
| A class for generating the C++ code for a specialized method that the JIT |
| can call with lower overhead. |
| """ |
| def __init__(self, descriptor: Descriptor, method: IDLMethod) -> None: |
| self.method = method |
| name = method.identifier.name |
| args = [Argument('*mut JSContext', '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 cx = SafeJSContext::from_ptr(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) |
| |
| |
| # https://searchfox.org/mozilla-central/rev/b220e40ff2ee3d10ce68e07d8a8a577d5558e2a2/dom/bindings/Codegen.py#10655-10684 |
| class CGMethodPromiseWrapper(CGAbstractExternMethod): |
| """ |
| A class for generating a wrapper around another method that will |
| convert exceptions to promises. |
| """ |
| |
| def __init__(self, descriptor: Descriptor, methodToWrap: IDLMethod) -> None: |
| self.method = methodToWrap |
| name = CGMethodPromiseWrapper.makeNativeName(descriptor, self.method) |
| args = [Argument('*mut JSContext', '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::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" |
| |
| |
| # https://searchfox.org/mozilla-central/rev/7279a1df13a819be254fd4649e07c4ff93e4bd45/dom/bindings/Codegen.py#11390-11419 |
| class CGGetterPromiseWrapper(CGAbstractExternMethod): |
| """ |
| A class for generating a wrapper around another getter that will |
| convert exceptions to promises. |
| """ |
| |
| 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 JSContext', '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::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; |
| rooted!(in(cx) let result = JS_NewPlainObject(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; |
| } |
| """ |
| |
| # Iterate the array in reverse: oldest ancestor first |
| 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): |
| """ |
| A class for generating the Rust code for an IDL static method. |
| """ |
| 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 cx = SafeJSContext::from_ptr(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): |
| """ |
| A class for generating the code for a specialized attribute getter |
| that the JIT can call with lower overhead. |
| """ |
| def __init__(self, descriptor: Descriptor, attr: IDLAttribute) -> None: |
| self.attr = attr |
| name = f'get_{descriptor.internalNameFor(attr.identifier.name)}' |
| args = [Argument('*mut JSContext', '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 cx = SafeJSContext::from_ptr(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): |
| """ |
| A class for generating the C++ code for an IDL static attribute getter. |
| """ |
| 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 cx = SafeJSContext::from_ptr(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): |
| """ |
| A class for generating the code for a specialized attribute setter |
| that the JIT can call with lower overhead. |
| """ |
| def __init__(self, descriptor: Descriptor, attr: IDLAttribute) -> None: |
| self.attr = attr |
| name = f'set_{descriptor.internalNameFor(attr.identifier.name)}' |
| args = [Argument('*mut JSContext', '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 cx = SafeJSContext::from_ptr(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): |
| """ |
| A class for generating the C++ code for an IDL static attribute setter. |
| """ |
| 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 cx = SafeJSContext::from_ptr(cx);\n") |
| checkForArg = CGGeneric( |
| "let args = CallArgs::from_vp(vp, argc);\n" |
| "if argc == 0 {\n" |
| f' throw_type_error(*cx, "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): |
| """ |
| A class for generating the code for an IDL attribute forwarding setter. |
| """ |
| 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] |
| # JS_GetProperty and JS_SetProperty can only deal with ASCII |
| assert all(ord(c) < 128 for c in attrName) |
| assert all(ord(c) < 128 for c in forwardToAttrName) |
| return CGGeneric(f""" |
| let cx = SafeJSContext::from_ptr(cx); |
| rooted!(in(*cx) let mut v = UndefinedValue()); |
| if !JS_GetProperty(*cx, HandleObject::from_raw(obj), {str_to_cstr_ptr(attrName)}, v.handle_mut()) {{ |
| return false; |
| }} |
| if !v.is_object() {{ |
| throw_type_error(*cx, "Value.{attrName} is not an object."); |
| return false; |
| }} |
| rooted!(in(*cx) let target_obj = v.to_object()); |
| JS_SetProperty(*cx, target_obj.handle(), {str_to_cstr_ptr(forwardToAttrName)}, HandleValue::from_raw(args.get(0))) |
| """) |
| |
| |
| class CGSpecializedReplaceableSetter(CGSpecializedSetter): |
| """ |
| A class for generating the code for an IDL replaceable attribute setter. |
| """ |
| 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) |
| # JS_DefineProperty can only deal with ASCII. |
| 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): |
| """ |
| A class for generating the JITInfo for a property that points to |
| our specialized getter and setter. |
| """ |
| 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: |
| """ |
| aliasSet is a JSJitInfo_AliasSet value, without the "JSJitInfo_AliasSet::" bit. |
| |
| args is None if we don't want to output argTypes for some |
| reason (e.g. we have overloads or we're not a method) and |
| otherwise an iterable of the arguments for this method. |
| """ |
| assert not movable or aliasSet != "AliasEverything" # Can't move write-aliasing things |
| assert not alwaysInSlot or movable # Things always in slots had better be movable |
| |
| 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({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), |
| # FIXME(nox): https://github.com/servo/servo/issues/10991 |
| 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 |
| # pyrefly: ignore # unknown-name |
| slotIndex = memberReservedSlot(self.member) # noqa: F821 FIXME: memberReservedSlot is not defined |
| # We'll statically assert that this is not too big in |
| # CGUpdateMemberSlotsMethod, in the case when |
| # isAlwaysInSlot is true. |
| else: |
| isLazilyCachedInSlot = False |
| slotIndex = "0" |
| |
| result = self.defineJitInfo(getterinfo, getter, "Getter", |
| getterinfal, movable, 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}" |
| # Setters are always fallible, since they have to do a typed unwrap. |
| result += self.defineJitInfo(setterinfo, setter, "Setter", |
| False, False, "AliasEverything", |
| False, False, "0", |
| # pyrefly: ignore # missing-attribute |
| [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) |
| |
| # Methods are infallible if they are infallible, have no arguments |
| # to unwrap, and have a return type that's infallible to wrap up for |
| # return. |
| sigs = self.member.signatures() |
| if len(sigs) != 1: |
| # Don't handle overloading. If there's more than one signature, |
| # one of them must take arguments. |
| methodInfal = False |
| args = None |
| movable = False |
| else: |
| sig = sigs[0] |
| # For methods that affect nothing, it's OK to set movable to our |
| # notion of infallible on the C++ side, without considering |
| # argument conversions, since argument conversions that can |
| # reliably throw would be effectful anyway and the jit doesn't |
| # move effectful things. |
| hasInfallibleImpl = "infallible" in self.descriptor.getExtendedAttributes(self.member) |
| movable = self.mayBeMovable() and hasInfallibleImpl |
| # XXXbz can we move the smarts about fallibility due to arg |
| # conversions into the JIT, using our new args stuff? |
| if (len(sig[1]) != 0): |
| # We have arguments or our return-value boxing can fail |
| methodInfal = False |
| else: |
| methodInfal = hasInfallibleImpl |
| # For now, only bother to output args if we're side-effect-free. |
| if self.member.affects == "Nothing": |
| args = sig[1] |
| else: |
| args = None |
| |
| aliasSet = self.aliasSet() |
| result = self.defineJitInfo(methodinfo, method, "Method", |
| methodInfal, movable, aliasSet, |
| False, False, "0", |
| [s[0] for s in sigs], args) |
| return result |
| raise TypeError("Illegal member type to CGPropertyJITInfo") |
| |
| def mayBeMovable(self) -> bool: |
| """ |
| Returns whether this attribute or method may be movable, just |
| based on Affects/DependsOn annotations. |
| """ |
| affects = self.member.affects |
| dependsOn = self.member.dependsOn |
| assert affects in IDLInterfaceMember.AffectsValues |
| assert dependsOn in IDLInterfaceMember.DependsOnValues |
| # Things that are DependsOn=DeviceState are not movable, because we |
| # don't want them coalesced with each other or loop-hoisted, since |
| # their return value can change even if nothing is going on from our |
| # point of view. |
| return (affects == "Nothing" |
| and (dependsOn != "Everything" and dependsOn != "DeviceState")) |
| |
| def aliasSet(self) -> str: |
| """Returns the alias set to store in the jitinfo. This may not be the |
| effective alias set the JIT uses, depending on whether we have enough |
| information about our args to allow the JIT to prove that effectful |
| argument conversions won't happen. |
| |
| """ |
| dependsOn = self.member.dependsOn |
| assert dependsOn in IDLInterfaceMember.DependsOnValues |
| |
| if dependsOn == "Nothing" or dependsOn == "DeviceState": |
| assert self.member.affects == "Nothing" |
| return "AliasNone" |
| |
| if dependsOn == "DOMState": |
| assert self.member.affects == "Nothing" |
| return "AliasDOMSets" |
| |
| return "AliasEverything" |
| |
| @staticmethod |
| def getJSReturnTypeTag(t: IDLType) -> str: |
| if t.nullable(): |
| # Sometimes it might return null, sometimes not |
| return "JSVAL_TYPE_UNKNOWN" |
| if t.isUndefined(): |
| # No return, every time |
| return "JSVAL_TYPE_UNDEFINED" |
| if t.isSequence(): |
| return "JSVAL_TYPE_OBJECT" |
| if t.isRecord(): |
| return "JSVAL_TYPE_OBJECT" |
| if t.isPromise(): |
| return "JSVAL_TYPE_OBJECT" |
| if t.isGeckoInterface(): |
| return "JSVAL_TYPE_OBJECT" |
| if t.isString(): |
| return "JSVAL_TYPE_STRING" |
| if t.isEnum(): |
| return "JSVAL_TYPE_STRING" |
| if t.isCallback(): |
| return "JSVAL_TYPE_OBJECT" |
| if t.isAny(): |
| # The whole point is to return various stuff |
| return "JSVAL_TYPE_UNKNOWN" |
| if t.isObject(): |
| return "JSVAL_TYPE_OBJECT" |
| if t.isSpiderMonkeyInterface(): |
| return "JSVAL_TYPE_OBJECT" |
| if t.isUnion(): |
| u = t.unroll() |
| assert isinstance(u, IDLUnionType) |
| if u.hasNullableType: |
| # Might be null or not |
| 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]: |
| # These all use JS_NumberValue, which can return int or double. |
| # But TI treats "double" as meaning "int or double", so we're |
| # good to return JSVAL_TYPE_DOUBLE here. |
| return "JSVAL_TYPE_DOUBLE" |
| if tag != IDLType.Tags.uint32: |
| raise TypeError(f"No idea what type {t} is.") |
| # uint32 is sometimes int and sometimes double. |
| return "JSVAL_TYPE_DOUBLE" |
| |
| @staticmethod |
| def getSingleReturnType(existingType: str, t: IDLType) -> str: |
| type = CGMemberJITInfo.getJSReturnTypeTag(t) |
| if existingType == "": |
| # First element of the list; just return its type |
| return type |
| |
| if type == existingType: |
| return existingType |
| if ((type == "JSVAL_TYPE_DOUBLE" |
| and existingType == "JSVAL_TYPE_INT32") |
| or (existingType == "JSVAL_TYPE_DOUBLE" |
| and type == "JSVAL_TYPE_INT32")): |
| # Promote INT32 to DOUBLE as needed |
| return "JSVAL_TYPE_DOUBLE" |
| # Different types |
| return "JSVAL_TYPE_UNKNOWN" |
| |
| @staticmethod |
| def getJSArgType(t: IDLType) -> str: |
| assert not t.isUndefined() |
| if t.nullable(): |
| # Sometimes it might return null, sometimes not |
| 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(): |
| # The whole point is to return various stuff |
| 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]: |
| # These all use JS_NumberValue, which can return int or double. |
| # But TI treats "double" as meaning "int or double", so we're |
| # good to return JSVAL_TYPE_DOUBLE here. |
| return "JSJitInfo_ArgType::Double as i32" |
| if tag != IDLType.Tags.uint32: |
| raise TypeError(f"No idea what type {t} is.") |
| # uint32 is sometimes int and sometimes double. |
| return "JSJitInfo_ArgType::Double as i32" |
| |
| @staticmethod |
| def getSingleArgType(existingType: str, t: IDLType) -> str: |
| type = CGMemberJITInfo.getJSArgType(t) |
| if existingType == "": |
| # First element of the list; just return its type |
| return type |
| |
| if type == existingType: |
| return existingType |
| return f"{existingType} | {type}" |
| |
| |
| # https://searchfox.org/mozilla-central/rev/9993372dd72daea851eba4600d5750067104bc15/dom/bindings/Codegen.py#12355-12374 |
| class CGStaticMethodJitinfo(CGGeneric): |
| """ |
| A class for generating the JITInfo for a promise-returning static method. |
| """ |
| |
| 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({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: |
| # Some enum values can be empty strings. Others might have weird |
| # characters in them. Deal with the former by returning "_empty", |
| # deal with possible name collisions from that by throwing if the |
| # enum value is actually "_empty", and throw on any value |
| # containing non-ASCII chars for now. Replace all chars other than |
| # [0-9A-Za-z_] with '_'. |
| if re.match("[^\x20-\x7E]", value): |
| raise SyntaxError(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 js::conversions::ConversionResult; |
| use js::conversions::FromJSValConvertible; |
| use js::conversions::ToJSValConvertible; |
| use js::jsapi::JSContext; |
| 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 JSContext, rval: MutableHandleValue) {{ |
| pairs[*self as usize].0.to_jsval(cx, rval); |
| }} |
| }} |
| |
| impl FromJSValConvertible for super::{ident} {{ |
| type Config = (); |
| unsafe fn 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( |
| format!("'{{}}' 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()] |
| # Finite<f32> or Finite<f64> cannot be used un a constant declaration. |
| # Remote the Finite type from restricted float and double tag declarations. |
| 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(): |
| # pyrefly: ignore # missing-attribute |
| name = type.inner.identifier.name |
| typeName = descriptorProvider.getDescriptor(name).returnType |
| elif type.isEnum(): |
| # pyrefly: ignore # missing-attribute |
| 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 JSContext, 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 = [] |
| |
| def get_name(memberType: IDLType) -> str: |
| if self.type.isGeckoInterface(): |
| # pyrefly: ignore # missing-attribute |
| 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}(SafeJSContext::from_ptr(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: |
| # "object" is not distinguishable from other types |
| assert not object or not (interfaceObject or arrayObject or callbackObject or mozMapObject) |
| templateBody = CGList([], "\n") |
| if arrayObject or callbackObject: |
| # An object can be both an sequence object and a callback or |
| # dictionary, but we shouldn't have both in the union's members |
| # because they are not distinguishable. |
| assert not (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("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 JSContext,\n" |
| " value: HandleValue,\n" |
| " _option: ())\n" |
| f" -> Result<ConversionResult<{self.type}{genericSuffix}>, ()> {{\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(): |
| # undefined does not require a conversion method, so we don't generate one |
| 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: SafeJSContext, 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: |
| """ Use with CGClass """ |
| def __init__(self, name: str | None, visibility: str) -> None: |
| self.name = name |
| self.visibility = visibility |
| |
| def declare(self, cgClass: CGClass) -> str: # pyrefly: ignore # bad-return |
| assert False |
| |
| def define(self, cgClass: CGClass) -> str: # pyrefly: ignore # bad-return |
| 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: |
| # Only in the header |
| 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: |
| """ |
| override indicates whether to flag the method as MOZ_OVERRIDE |
| """ |
| assert not override or virtual |
| assert not (override and static) |
| self.returnType = returnType |
| self.args = args |
| self.inline = 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: |
| # Override me or pass a string to constructor |
| 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: # pyrefly: ignore # bad-return |
| assert False |
| |
| |
| class ClassConstructor(ClassItem): |
| """ |
| Used for adding a constructor to a CGClass. |
| |
| args is a list of Argument objects that are the arguments taken by the |
| constructor. |
| |
| inline should be True if the constructor should be marked inline. |
| |
| bodyInHeader should be True if the body should be placed in the class |
| declaration in the header. |
| |
| visibility determines the visibility of the constructor (public, |
| protected, private), defaults to private. |
| |
| explicit should be True if the constructor should be marked explicit. |
| |
| baseConstructors is a list of strings containing calls to base constructors, |
| defaults to None. |
| |
| body contains a string with the code for the constructor, defaults to empty. |
| """ |
| def __init__(self, args: 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 |
| # We store our single destructor in a list, since all of our |
| # code wants lists of members. |
| self.destructors = [destructor] if destructor else [] |
| self.methods = methods |
| self.typedefs = typedefs |
| self.enums = enums |
| self.unions = unions |
| self.templateArgs = templateArgs |
| self.templateSpecialization = templateSpecialization |
| self.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 # XXjdm Can we support multiple inheritance? |
| |
| result += ' {\n' |
| |
| if self.bases: |
| self.members = [ClassMember("parent", self.bases[0].name, "pub")] + self.members |
| |
| result += CGIndenter(CGGeneric(self.extradeclarations), |
| len(self.indent)).define() |
| |
| def declareMembers(cgClass: 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): |
| """ |
| Base class for classes for calling an indexed or named special operation |
| (don't use this directly, use the derived classes below). |
| """ |
| 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) |
| |
| # We pass len(arguments) as the final argument so that the |
| # CGPerSignatureCall won't do any argument conversion of its own. |
| CGPerSignatureCall.__init__(self, returnType, [], arguments, nativeName, |
| False, descriptor, operation, |
| len(arguments)) |
| |
| if operation.isSetter(): |
| # arguments[0] is the index or name of the item that we're setting. |
| 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): |
| """ |
| Class to generate a call to an indexed getter. If templateValues is not None |
| the returned value will be wrapped with wrapForType using templateValues. |
| """ |
| def __init__(self, descriptor: Descriptor, templateValues: dict[str, Any] | None = None) -> None: |
| self.templateValues = templateValues |
| CGProxySpecialOperation.__init__(self, descriptor, 'IndexedGetter') |
| |
| |
| class CGProxyIndexedSetter(CGProxySpecialOperation): |
| """ |
| Class to generate a call to an indexed setter. |
| """ |
| def __init__(self, descriptor: Descriptor) -> None: |
| CGProxySpecialOperation.__init__(self, descriptor, 'IndexedSetter') |
| |
| |
| class CGProxyNamedOperation(CGProxySpecialOperation): |
| """ |
| Class to generate a call to a named operation. |
| """ |
| def __init__(self, descriptor: Descriptor, name: str) -> None: |
| CGProxySpecialOperation.__init__(self, descriptor, name) |
| |
| def define(self) -> str: |
| # Our first argument is the id we're getting. |
| 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): |
| """ |
| Class to generate a call to an named getter. If templateValues is not None |
| the returned value will be wrapped with wrapForType using templateValues. |
| """ |
| def __init__(self, descriptor: Descriptor, templateValues: dict[str, Any] | None = None) -> None: |
| self.templateValues = templateValues |
| CGProxySpecialOperation.__init__(self, descriptor, 'NamedGetter') |
| |
| |
| class CGProxyNamedPresenceChecker(CGProxyNamedGetter): |
| """ |
| Class to generate a call that checks whether a named property exists. |
| For now, we just delegate to CGProxyNamedGetter |
| """ |
| def __init__(self, descriptor: Descriptor) -> None: |
| CGProxyNamedGetter.__init__(self, descriptor) |
| |
| |
| class CGProxyNamedSetter(CGProxyNamedOperation): |
| """ |
| Class to generate a call to a named setter. |
| """ |
| def __init__(self, descriptor: Descriptor) -> None: |
| CGProxySpecialOperation.__init__(self, descriptor, 'NamedSetter') |
| |
| |
| class CGProxyNamedDeleter(CGProxyNamedOperation): |
| """ |
| Class to generate a call to a named deleter. |
| """ |
| def __init__(self, descriptor: Descriptor) -> None: |
| CGProxySpecialOperation.__init__(self, descriptor, 'NamedDeleter') |
| |
| def define(self) -> str: |
| # Our first argument is the id we're getting. |
| 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, \"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 JSContext', '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 |
| |
| # https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty |
| def getBody(self) -> str: |
| indexedGetter = self.descriptor.operations['IndexedGetter'] |
| |
| get = "let cx = SafeJSContext::from_ptr(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 _ac = JSAutoRealm::new(*cx, proxy.get()); |
| """) |
| |
| 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());' |
| } |
| |
| # See the similar-looking in CGDOMJSProxyHandler_get for the spec quote. |
| condition = "id.is_string() || id.is_int()" |
| if indexedGetter: |
| condition = f"index.is_none() && ({condition})" |
| # Once we start supporting OverrideBuiltins we need to make |
| # ResolveOwnProperty or EnumerateOwnProperties filter out named |
| # properties that shadow prototype properties. |
| namedGet = f""" |
| if {condition} {{ |
| let mut has_on_proto = false; |
| if !has_property_on_prototype(*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, 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 JSContext', '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 cx = SafeJSContext::from_ptr(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 _ac = JSAutoRealm::new(*cx, proxy.get()); |
| """) |
| |
| 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, {', '.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 JSContext', '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 cx = SafeJSContext::from_ptr(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 _ac = JSAutoRealm::new(*cx, proxy.get()); |
| """) |
| |
| 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, {', '.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 JSContext', '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 cx = SafeJSContext::from_ptr(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 _ac = JSAutoRealm::new(*cx, proxy.get()); |
| """) |
| |
| if self.descriptor.operations['IndexedGetter']: |
| body += dedent( |
| """ |
| for i in 0..(*unwrapped_proxy).Length() { |
| 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, cstring.as_ptr()); |
| rooted!(in(*cx) let rooted = jsstring); |
| rooted!(in(*cx) let mut rooted_jsid: jsid); |
| RUST_INTERNED_STRING_TO_JSID(*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, 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 JSContext', '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 cx = SafeJSContext::from_ptr(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) { |
| // There are no enumerable cross-origin props, so we're done. |
| return true; |
| } |
| |
| // Safe to enter the Realm of proxy now. |
| let _ac = JSAutoRealm::new(*cx, proxy.get()); |
| """) |
| |
| if self.descriptor.operations['IndexedGetter']: |
| body += dedent( |
| """ |
| for i in 0..(*unwrapped_proxy).Length() { |
| 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, 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 JSContext', '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 = "let cx = SafeJSContext::from_ptr(cx);\n" |
| |
| 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 _ac = JSAutoRealm::new(*cx, proxy.get()); |
| """) |
| |
| 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, 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, 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 JSContext', '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 |
| |
| # https://heycam.github.io/webidl/#LegacyPlatformObjectGetOwnProperty |
| 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 _ac = JSAutoRealm::new(*cx, proxy.get()); |
| """) |
| 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, expando.handle().into(), id, &mut hasProp) { |
| return false; |
| } |
| |
| if hasProp { |
| return JS_ForwardGetPropertyTo(*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()" |
| # From step 1: |
| # If O supports indexed properties and P is an array index, then: |
| # |
| # 3. Set ignoreNamedProps to true. |
| 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 cx = SafeJSContext::from_ptr(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, 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 JSContext', '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 cx = SafeJSContext::from_ptr(cx); |
| proxyhandler::maybe_cross_origin_get_prototype::<D>(cx, 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 JSContext', '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): |
| """ |
| Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw |
| 'this' unwrapping as it assumes that the unwrapped type is always known. |
| """ |
| def __init__(self, descriptor: 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): |
| """ |
| A hook to trace through our native object; used for GC and CC |
| """ |
| 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): |
| """ |
| JS-visible constructor for our objects |
| """ |
| def __init__(self, descriptor: Descriptor, constructor: IDLConstructor | None = None) -> None: |
| args = [Argument('*mut JSContext', '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 cx = SafeJSContext::from_ptr(cx); |
| let args = CallArgs::from_vp(vp, argc); |
| let global = D::GlobalScope::from_object(JS_CALLEE(*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>, |
| CanGc::note() |
| ) |
| """ |
| 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)", |
| "CanGc::note()" |
| ] |
| else: |
| args = [ |
| "global", |
| "Some(desired_proto)", |
| "CanGc::note()" |
| ] |
| |
| 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: SafeJSContext, args: &CallArgs, global: &D::GlobalScope, desired_proto: HandleObject| {{ |
| {constructor.define()} |
| }} |
| ) |
| """ |
| return CGList([CGGeneric(preamble), CGGeneric(constructorCall)]) |
| |
| |
| class CGClassFinalizeHook(CGAbstractClassHook): |
| """ |
| A hook for finalize, used to release our native object. |
| """ |
| 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, |
| inRealm: bool = False, |
| canGc: bool = False, |
| retval: bool = False |
| ) -> Iterable[tuple[str, str]]: |
| if typeNeedsCx(attribute_type, retval): |
| yield "cx", "SafeJSContext" |
| |
| if argument: |
| yield "value", argument_type(descriptor, argument) |
| |
| if inRealm: |
| yield "_comp", "InRealm" |
| |
| if canGc: |
| 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]]: |
| 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, |
| 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) |
| infallible = 'infallible' in descriptor.getExtendedAttributes(m, getter=True) |
| yield (name, |
| attribute_arguments( |
| m.type, |
| 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, |
| 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, |
| inRealm=name in descriptor.inRealmMethods, |
| canGc=name in descriptor.canGcMethods) |
| |
| # If this interface 'supports named properties', then we |
| # should be able to access 'supported property names' |
| # |
| # WebIDL, Second Draft, section 3.2.4.5 |
| # https://heycam.github.io/webidl/#idl-named-properties |
| if operation.isNamed(): |
| yield "SupportedPropertyNames", [], "Vec<DOMString>", False |
| else: |
| arguments = method_arguments(descriptor, rettype, arguments, |
| 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 JSContext'), arguments, False) |
| |
| methods = [] |
| 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) |
| args = list(method_arguments(descriptor, rettype, arguments)) |
| extra = [ |
| ("global", f"&D::{exposedGlobal}"), |
| ("proto", "Option<HandleObject>"), |
| ("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 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 = [] |
| |
| 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)) |
| |
| # If there are no constant members, don't make a module for constants |
| 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(CGProxyIsProxy(descriptor)) |
| 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)) |
| |
| # We want to prevent indexed deleters from compiling at all. |
| 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)) |
| |
| # cgThings.append(CGDOMJSProxyHandler(descriptor)) |
| # cgThings.append(CGIsMethod(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)) |
| |
| cgThings = CGList(cgThings, '\n') |
| |
| # Add imports |
| # These are inside the generated module |
| cgThings = CGImports(cgThings, descriptors=[descriptor], callbacks=[], |
| dictionaries=[], enums=[], typedefs=[], imports=[ |
| 'crate::import::module::*', |
| ], config=config) |
| |
| cgThings = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name), |
| cgThings, public=True), |
| post='\n') |
| |
| if reexports: |
| reexports = ', '.join([reexportedName(name) for name in reexports]) |
| namespace = toBindingNamespace(descriptor.name) |
| cgThings = CGList([CGGeneric(f'pub use self::{namespace}::{{{reexports}}};'), |
| cgThings], '\n') |
| |
| self.cgRoot = cgThings |
| |
| 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: |
| # Account for first value |
| entries = [f"{names[0]} = {first}"] + names[1:] |
| |
| # Append a Last. |
| entries.append(f'#[allow(dead_code)] Last = {first + len(entries)}') |
| |
| # Indent. |
| entries = [f' {e}' for e in entries] |
| |
| # Build the enum body. |
| 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) |
| |
| # Add some whitespace padding. |
| curr = CGWrapper(curr, pre='\n', post='\n') |
| |
| # Add the typedef |
| # typedef = '\ntypedef %s::%s %s;\n\n' % (namespace, enumName, enumName) |
| # curr = CGList([curr, CGGeneric(typedef)]) |
| |
| # Save the result. |
| self.node = curr |
| |
| def define(self) -> 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 |
| # Nothing else to do here |
| 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: 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" |
| |
| # We can't unconditionally derive Default here, because union types can have unique |
| # default values provided for each usage. Instead, whenever possible we re-use the empty() |
| # method that is generated. |
| 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, can_gc)? {{\n" |
| " ConversionResult::Success(v) => v,\n" |
| " ConversionResult::Failure(error) => {\n" |
| " throw_type_error(*cx, &error);\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(), "{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: SafeJSContext, val: HandleValue, can_gc: CanGc) \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(\"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 JSContext, value: HandleValue, _option: ())\n" |
| f" -> Result<ConversionResult<{actualType}>, ()> {{\n" |
| f" {selfName}::new(SafeJSContext::from_ptr(cx), value, CanGc::note())\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 JSContext, 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 JSContext, 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, "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" |
| " if get_dictionary_property(*cx, object.handle(), " |
| f'"{member.identifier.name}", ' |
| "rval.handle_mut(), can_gc)? && !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: |
| # Can't use Rust keywords as member names. |
| 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)) |
| # FIXME: pass in a valid descriptor somehow |
| # pyrefly: ignore # bad-argument-type |
| 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." |
| # FIXME: pass in a valid descriptor somehow |
| # pyrefly: ignore # bad-argument-type |
| 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( |
| "#[allow(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): |
| """ |
| Generate a type assertion for inheritance |
| """ |
| 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 codegen class for binding generation, specialized on the concrete |
| type that is used by handwritten code. Re-export all public types from |
| the generic bindings with type specialization applied. |
| """ |
| root: CGThing | None |
| def __init__(self, config: Configuration, prefix: str, webIDLFile: str) -> None: |
| descriptors = config.getDescriptors(webIDLFile=webIDLFile, |
| hasInterfaceObject=True) |
| # We also want descriptors that have an interface prototype object |
| # (isCallback=False), but we don't want to include a second copy |
| # of descriptors that we also matched in the previous line |
| # (hence hasInterfaceObject=False). |
| 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.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: SafeJSContext, 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>;" |
| )] |
| |
| # And make sure we have the right number of newlines at the end |
| curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n") |
| |
| # Add imports |
| # These are the global imports (outside of the generated module) |
| curr = CGImports(curr, descriptors=[], callbacks=[], |
| dictionaries=[], enums=[], typedefs=[], |
| imports=[ |
| 'crate::dom::bindings::import::module::*', |
| 'crate::dom::types::*'], |
| config=config) |
| |
| # Add the auto-generated comment. |
| curr = CGWrapper(curr, pre=f"{AUTOGENERATED_WARNING_COMMENT}{ALLOWED_WARNINGS}") |
| |
| # Store the final result. |
| self.root = curr |
| |
| def define(self) -> str: |
| if not self.root: |
| return "" |
| return stripTrailingWhitespace(self.root.define()) |
| |
| |
| class CGBindingRoot(CGThing): |
| """ |
| Root codegen class for binding generation. Instantiate the class, and call |
| declare or define to generate header or cpp code (respectively). |
| """ |
| root: CGThing | None |
| def __init__(self, config: Configuration, prefix: str, webIDLFile: str) -> None: |
| descriptors = config.getDescriptors(webIDLFile=webIDLFile, |
| hasInterfaceObject=True) |
| # We also want descriptors that have an interface prototype object |
| # (isCallback=False), but we don't want to include a second copy |
| # of descriptors that we also matched in the previous line |
| # (hence hasInterfaceObject=False). |
| 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 |
| |
| # Do codegen for all the enums. |
| cgthings: list = [CGEnum(e, config) for e in enums] |
| |
| # Do codegen for all the typedefs |
| 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(): |
| # Allow using the typedef's name for accessing variants. |
| 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)) |
| |
| # Do codegen for all the dictionaries. |
| cgthings.extend([CGDictionary(d, config.getDescriptorProvider(), config) |
| for d in dictionaries]) |
| |
| # Do codegen for all the callbacks. |
| cgthings.extend(CGList([CGCallbackFunction(c, config.getDescriptorProvider()), |
| CGCallbackFunctionImpl(c)], "\n") |
| for c in mainCallbacks) |
| |
| # Do codegen for all the descriptors |
| cgthings.extend([CGDescriptor(x, config, len(descriptors) == 1) for x in descriptors]) |
| |
| # Do codegen for all the callback interfaces. |
| cgthings.extend(CGList( |
| [ |
| CGCallbackInterface(x), |
| CGCallbackFunctionImpl(assert_type(x.interface, IDLInterface)) |
| ], |
| "\n" |
| ) for x in callbackDescriptors) |
| |
| # And make sure we have the right number of newlines at the end |
| curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n") |
| |
| # Add imports |
| # These are the global imports (outside of the generated module) |
| curr = CGImports(curr, descriptors=callbackDescriptors, callbacks=mainCallbacks, |
| dictionaries=dictionaries, enums=enums, typedefs=typedefs, |
| imports=['crate::import::base::*'], config=config) |
| |
| # Add the auto-generated comment. |
| curr = CGWrapper(curr, pre=f"{AUTOGENERATED_WARNING_COMMENT}{ALLOWED_WARNINGS}") |
| |
| # Store the final result. |
| 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: |
| """ |
| Certain IDL types, such as `sequence<any>` or `sequence<object>` need to be |
| traced and wrapped via (Custom)AutoRooter |
| """ |
| assert isinstance(t, IDLObject), (t, type(t)) |
| |
| if t.isType(): |
| if t.isSequence() and (t.inner.isAny() or t.inner.isObject()): |
| return True |
| # SpiderMonkey interfaces, we currently don't support any other except typed arrays |
| 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: list[IDLArgument], |
| passJSBits: bool = True, |
| trailing: tuple[str, str] | None = None, |
| inRealm: bool = False, |
| canGc: bool = False |
| ) -> Iterator[tuple[str, str]]: |
| if needCx(returnType, arguments, passJSBits): |
| 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: |
| yield "_comp", "InRealm" |
| |
| if canGc: |
| 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, name: str, signature: tuple[IDLType, list[IDLArgument]], extendedAttrs: dict[str, Any], |
| breakAfter: bool = True, passJSBitsAsNeeded: bool = True, visibility: str = "public", |
| unsafe: bool = False) -> None: |
| """ |
| If passJSBitsAsNeeded is false, we don't automatically pass in a |
| JSContext* or a JSObject* based on the return and argument types. |
| """ |
| self.descriptorProvider = descriptorProvider |
| self.member = member |
| self.extendedAttrs = extendedAttrs |
| self.passJSBitsAsNeeded = passJSBitsAsNeeded |
| breakAfterSelf = "\n" if breakAfter else "" |
| ClassMethod.__init__(self, name, |
| self.getReturnType(signature[0]), |
| self.getArgs(signature[0], signature[1]), |
| static=member.isStatic(), |
| # Mark our getters, which are attrs that |
| # have a non-void return type, as const. |
| const=(not member.isStatic() and member.isAttr() |
| and not signature[0].isUndefined()), |
| 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]) -> 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: IDLCallback, descriptorProvider: DescriptorProvider, baseName: str, methods: list[CallbackMethod]) -> None: |
| self.baseName = baseName |
| self._deps = idlObject.getDeps() |
| name = idlObject.identifier.name |
| # For our public methods that needThisHandling we want most of the |
| # same args and the same return type as what CallbackMember |
| # generates. So we want to take advantage of all its |
| # CGNativeMember infrastructure, but that infrastructure can't deal |
| # with templates and most especially template arguments. So just |
| # cheat and have CallbackMember compute all those things for us. |
| realMethods = [] |
| for method in methods: |
| if not method.needThisHandling: |
| realMethods.append(method) |
| else: |
| realMethods.extend(self.getMethodImpls(method)) |
| CGClass.__init__(self, name, |
| bases=[ClassBase(baseName)], |
| constructors=self.getConstructors(), |
| methods=realMethods, |
| 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) |
| # Strip out the JSContext*/JSObject* args |
| # that got added. |
| assert args[0].name == "cx" and args[0].argType == "SafeJSContext" |
| assert args[1].name == "aThisObj" and args[1].argType == "HandleValue" |
| args = args[2:] |
| # Record the names of all the arguments, so we can use them when we call |
| # the private method. |
| argnames = [arg.name for arg in args] + ["can_gc"] |
| argnamesWithThis = ["s.get_context()", "thisValue.handle()"] + argnames |
| argnamesWithoutThis = ["s.get_context()", "HandleValue::undefined()"] + argnames |
| # Now that we've recorded the argnames for our call to our private |
| # method, insert our optional argument for deciding whether the |
| # CallSetup should re-throw exceptions on aRv. |
| args.append(Argument("ExceptionHandling", "aExceptionHandling", |
| "ReportExceptions")) |
| |
| args.append(Argument("CanGc", "can_gc")) |
| method.args.append(Argument("CanGc", "can_gc")) |
| |
| # And now insert our template argument. |
| argsWithoutThis = list(args) |
| args.insert(0, Argument("&T", "thisObj")) |
| |
| # And the self argument |
| method.args.insert(0, Argument(None, "&self")) |
| args.insert(0, Argument(None, "&self")) |
| argsWithoutThis.insert(0, Argument(None, "&self")) |
| |
| setupCall = "let s = CallSetup::<D>::new(self, aExceptionHandling);\n" |
| |
| bodyWithThis = ( |
| f"{setupCall}rooted!(in(*s.get_context()) let mut thisValue: JSVal);\n" |
| "let wrap_result = wrap_call_this_value(s.get_context(), thisObj, thisValue.handle_mut());\n" |
| "if !wrap_result {\n" |
| " return Err(JSFailed);\n" |
| "}\n" |
| f"unsafe {{ self.{method.name}({', '.join(argnamesWithThis)}) }}") |
| bodyWithoutThis = ( |
| f"{setupCall}\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 |
| |
| |
| # We're always fallible |
| 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 JSContext, 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]], name: str, descriptorProvider: DescriptorProvider, needThisHandling: bool) -> None: |
| """ |
| needThisHandling is True if we need to be able to accept a specified |
| thisObj, False otherwise. |
| """ |
| |
| self.retvalType = sig[0] |
| self.originalSig = sig |
| args = sig[1] |
| self.argCount = len(args) |
| if self.argCount > 0: |
| # Check for variadic arguments |
| lastArg = args[self.argCount - 1] |
| if lastArg.variadic: |
| self.argCountStr = ( |
| f"{self.argCount - 1} + {lastArg.identifier.name}.len()").removeprefix("0 + ") |
| else: |
| self.argCountStr = f"{self.argCount}" |
| self.usingOutparam = returnTypeNeedsOutparam(self.retvalType) |
| self.needThisHandling = needThisHandling |
| # If needThisHandling, we generate ourselves as private and the caller |
| # will handle generating public versions that handle the "this" stuff. |
| visibility = "priv" if needThisHandling else "pub" |
| # We don't care, for callback codegen, whether our original member was |
| # a method or attribute or whatnot. Just always pass FakeMember() |
| # here. |
| CGNativeMember.__init__(self, descriptorProvider, FakeMember(), |
| name, (self.retvalType, args), |
| extendedAttrs={}, |
| passJSBitsAsNeeded=False, |
| unsafe=needThisHandling, |
| visibility=visibility) |
| # We have to do all the generation of our body now, because |
| # the caller relies on us throwing if we can't manage it. |
| self.exceptionCode = "return Err(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_vec!(let mut argv);\n" |
| f"argv.extend((0..{self.argCountStr}).map(|_| Heap::default()));\n" |
| ) if self.argCount > 0 else "" # Avoid weird 0-sized arrays |
| |
| # Newlines and semicolons are in the values |
| 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, |
| # XXXbz we should try to do better here |
| 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: |
| # Just reget the arglist from self.originalSig, because our superclasses |
| # just have way to many members they like to clobber, so I can't find a |
| # safe member name to store it in. |
| arglist = self.originalSig[1] |
| argConversions: list[str] = [self.getArgConversion(i, arg) for (i, arg) |
| in enumerate(arglist)] |
| # Do them back to front, so our argc modifications will work |
| # correctly, because we examine trailing arguments first. |
| argConversions.reverse() |
| argConversionsCG: list[CGThing] = [CGGeneric(c) for c in argConversions] |
| # If arg count is only 1 but it's optional and not default value, |
| # argc should be mutable. |
| 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)) |
| # And slap them together. |
| return CGList(argConversionsCG, "\n\n").define() + "\n\n" |
| |
| def getArgConversion(self, i: int, arg: IDLArgument) -> str: |
| argval = arg.identifier.name |
| |
| if arg.variadic: |
| argval = f"{argval}[idx].get()" |
| jsvalIndex = f"{i} + idx" |
| else: |
| jsvalIndex = f"{i}" |
| |
| conversion = wrapForType( |
| "argv_root.handle_mut()", result=argval, |
| successCode=("{\n" |
| f"let arg = &mut argv[{jsvalIndex.removeprefix('0 + ')}];\n" |
| "*arg = Heap::default();\n" |
| "arg.set(argv_root.get());\n" |
| "}"), |
| pre="rooted!(in(*cx) let mut argv_root = UndefinedValue());") |
| if arg.variadic: |
| conversion = ( |
| f"for idx in 0..{arg.identifier.name}.len() {{\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[{i}] = Heap::default();\n" |
| "}" |
| ) |
| return conversion |
| |
| def getArgs(self, returnType: IDLType, argList: list[IDLArgument]) -> list[Argument]: |
| args = CGNativeMember.getArgs(self, returnType, argList) |
| if not self.needThisHandling: |
| # Since we don't need this handling, we're the actual method that |
| # will be called, so we need an aRethrowExceptions argument. |
| args.append(Argument("ExceptionHandling", "aExceptionHandling", |
| "ReportExceptions")) |
| return args |
| # We want to allow the caller to pass in a "this" object, as |
| # well as a JSContext. |
| return [Argument("SafeJSContext", "cx"), |
| Argument("HandleValue", "aThisObj")] + args |
| |
| def getCallSetup(self) -> str: |
| if self.needThisHandling: |
| # It's been done for us already |
| 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]], 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 = "argv.as_ptr() as *const JSVal" |
| 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, 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): |
| """ |
| Common class for implementing various callback operations. |
| """ |
| def __init__(self, signature: tuple[IDLType, list[IDLArgument]], 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())" |
| # This relies on getCallableDecl declaring a boolean |
| # isCallable in the case when we're a single-operation |
| # interface. |
| return "if isCallable { aThisObj.get() } else { ObjectValue(self.callback()) }" |
| |
| def getCallableDecl(self) -> str: |
| getCallableFromProp = f'self.parent.get_callable_property(cx, "{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): |
| """ |
| Codegen actual WebIDL operations on callback interfaces. |
| """ |
| 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): |
| """ |
| Creates methods for *like interfaces. Unwrapping/wrapping |
| will be taken care of by the usual method generation machinery in |
| CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of |
| using CGCallGenerator. |
| """ |
| def __init__(self, descriptor: 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") |
| """ |
| setlike: |
| fn size(&self) -> usize; |
| fn add(&self, key: Self::Key); |
| fn has(&self, key: &Self::Key) -> bool; |
| fn clear(&self); |
| fn delete(&self, key: &Self::Key) -> bool; |
| maplike: |
| fn get(&self, key: Self::Key) -> Self::Value; |
| fn size(&self) -> usize; |
| fn set(&self, key: Self::Key, value: Self::Value); |
| fn has(&self, key: &Self::Key) -> bool; |
| fn clear(&self); |
| fn delete(&self, key: &Self::Key) -> bool; |
| like iterable: |
| keys/values/entries/forEach |
| """ |
| # like iterables are implemented seperatly as we are actually implementing them |
| if methodName in ["keys", "values", "entries", "forEach"]: |
| CGIterableMethodGenerator.__init__(self, descriptor, likeable, methodName) |
| elif methodName in ["size", "clear"]: # zero arguments |
| CGGeneric.__init__(self, fill( |
| """ |
| let result = ${trt}::${method}(this); |
| """, |
| trt=trait, |
| method=methodName.lower())) |
| elif methodName == "add": # special case one argumet |
| 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"]: # one argument |
| CGGeneric.__init__(self, fill( |
| """ |
| let result = ${trt}::${method}(this, arg0); |
| """, |
| trt=trait, |
| method=methodName)) |
| elif methodName == "set": # two arguments |
| 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): |
| """ |
| Creates methods for iterable interfaces. Unwrapping/wrapping |
| will be taken care of by the usual method generation machinery in |
| CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of |
| using CGCallGenerator. |
| """ |
| def __init__(self, descriptor: Descriptor, iterable: IDLMaplikeOrSetlikeOrIterableBase, methodName: str) -> None: |
| if methodName == "forEach": |
| CGGeneric.__init__(self, fill( |
| """ |
| if !IsCallable(arg0) { |
| throw_type_error(*cx, "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_vec!(let mut call_args); |
| 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, call_arg1.handle_mut()); |
| (*this).get_key_at_index(i).to_jsval(*cx, call_arg2.handle_mut()); |
| call_args[0] = call_arg1.handle().get(); |
| call_args[1] = call_arg2.handle().get(); |
| let call_args_handle = HandleValueArray::from(&call_args); |
| if !Call(*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(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(): |
| """ |
| Roots for global codegen. |
| |
| To generate code, call the method associated with the target, and then |
| call the appropriate define/declare method. |
| """ |
| |
| @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: |
| # Prototype ID enum. |
| interfaces = config.getDescriptors(isCallback=False, isNamespace=False) |
| protos = [d.name for d in interfaces] |
| 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: |
| # TODO - Generate the methods we want |
| 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 crate::dom::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: |
| # No other interface will implement DeriveFrom<Foo> for this Foo, so avoid |
| # implementing it for itself. |
| chain = chain[:-1] |
| |
| # Implement `DerivedFrom<Bar>` for `Foo`, for all `Bar` that `Foo` inherits from. |
| 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: |
| # If `name` is present in the hierarchy keys', that means some other interfaces |
| # derive from it and this enum variant should have an argument with its own |
| # TypeId enum. |
| 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) |
| |
| # Add the auto-generated comment. |
| curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) |
| |
| # Done. |
| return curr |
| |
| @staticmethod |
| def DomTypes(config: Configuration) -> CGThing: |
| curr = DomTypes(config.getDescriptors(), |
| config.getDescriptorProvider(), |
| config.getDictionaries(), |
| config.getCallbacks(), |
| config.typedefs, |
| config) |
| |
| # Add the auto-generated comment. |
| curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) |
| |
| # Done. |
| return curr |
| |
| @staticmethod |
| def DomTypeHolder(config: Configuration) -> CGThing: |
| curr = DomTypeHolder(config.getDescriptors(), |
| config.getDescriptorProvider(), |
| config.getDictionaries(), |
| config.getCallbacks(), |
| config.typedefs, |
| config) |
| |
| # Add the auto-generated comment. |
| curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) |
| |
| # Done. |
| 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)))) |