mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
copy generated `include!`d files from script_bindings's OUT_DIR, to script's OUT_DIR to allow Rust Analyzer to load them. This is done to bypass limitation of Rust Analyzer: https://github.com/rust-lang/rust-analyzer/issues/17040 Also build script will now be rerun only when there are actual changes to concrete bindings due to emitted `cargo::rerun-if-changed` (not for each change in script crate). Testing: It compiles so it works, I tested manually and RA now works as expected (although we need to from type alias to concrete union-types definitions) Fixes: https://servo.zulipchat.com/#narrow/channel/263398-general/topic/rust-analyzer.20failed.20to.20include.20codes.20in.20script_bindings --------- Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
8839 lines
347 KiB
Python
8839 lines
347 KiB
Python
# 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.
|
|
|
|
from collections import defaultdict
|
|
from itertools import groupby
|
|
|
|
import operator
|
|
import os
|
|
import re
|
|
import string
|
|
import textwrap
|
|
import functools
|
|
|
|
from WebIDL import (
|
|
BuiltinTypes,
|
|
IDLArgument,
|
|
IDLBuiltinType,
|
|
IDLDefaultDictionaryValue,
|
|
IDLEmptySequenceValue,
|
|
IDLInterface,
|
|
IDLInterfaceMember,
|
|
IDLNullableType,
|
|
IDLNullValue,
|
|
IDLObject,
|
|
IDLPromiseType,
|
|
IDLType,
|
|
IDLTypedefType,
|
|
IDLUndefinedValue,
|
|
IDLWrapperType,
|
|
)
|
|
|
|
from Configuration import (
|
|
MakeNativeName,
|
|
MemberIsLegacyUnforgeable,
|
|
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', '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", "become", "box", "break", "const", "continue",
|
|
"else", "enum", "extern", "false", "final", "fn", "for", "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", "type", "typeof", "unsafe", "unsized",
|
|
"use", "virtual", "where", "while", "yield"}
|
|
|
|
|
|
def genericsForType(t):
|
|
if containsDomInterface(t):
|
|
return ("<D: DomTypes>", "<D>")
|
|
return ("", "")
|
|
|
|
|
|
def isDomInterface(t, logging=False):
|
|
while isinstance(t, IDLNullableType) or isinstance(t, IDLWrapperType):
|
|
t = t.inner
|
|
if isinstance(t, IDLInterface):
|
|
return True
|
|
if t.isCallback() or t.isPromise():
|
|
return True
|
|
return t.isInterface() and (t.isGeckoInterface() or (t.isSpiderMonkeyInterface() and not t.isBufferSource()))
|
|
|
|
|
|
def containsDomInterface(t, logging=False):
|
|
if isinstance(t, IDLArgument):
|
|
t = t.type
|
|
if isinstance(t, IDLTypedefType):
|
|
t = t.innerType
|
|
while isinstance(t, IDLNullableType) or isinstance(t, IDLWrapperType):
|
|
t = t.inner
|
|
if t.isEnum():
|
|
return False
|
|
if t.isUnion():
|
|
return any(map(lambda x: containsDomInterface(x), t.flatMemberTypes))
|
|
if t.isDictionary():
|
|
return any(map(lambda x: containsDomInterface(x), t.members)) or (t.parent and containsDomInterface(t.parent))
|
|
if isDomInterface(t):
|
|
return True
|
|
if t.isSequence():
|
|
return containsDomInterface(t.inner)
|
|
return False
|
|
|
|
|
|
def toStringBool(arg):
|
|
return str(not not arg).lower()
|
|
|
|
|
|
def toBindingNamespace(arg):
|
|
"""
|
|
Namespaces are *_Bindings
|
|
|
|
actual path is `codegen::Bindings::{toBindingModuleFile(name)}::{toBindingNamespace(name)}`
|
|
"""
|
|
return re.sub("((_workers)?$)", "_Binding\\1", MakeNativeName(arg))
|
|
|
|
|
|
def toBindingModuleFile(arg):
|
|
"""
|
|
Module files are *Bindings
|
|
|
|
actual path is `codegen::Bindings::{toBindingModuleFile(name)}::{toBindingNamespace(name)}`
|
|
"""
|
|
return re.sub("((_workers)?$)", "Binding\\1", MakeNativeName(arg))
|
|
|
|
|
|
def toBindingModuleFileFromDescriptor(desc):
|
|
if desc.maybeGetSuperModule() is not None:
|
|
return toBindingModuleFile(desc.maybeGetSuperModule())
|
|
else:
|
|
return toBindingModuleFile(desc.name)
|
|
|
|
|
|
def stripTrailingWhitespace(text):
|
|
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):
|
|
assert type.isSequence() or type.isRecord()
|
|
return type.inner.inner if type.nullable() else type.inner
|
|
|
|
|
|
def wrapInNativeContainerType(type, inner):
|
|
if type.isSequence():
|
|
return CGWrapper(inner, pre="Vec<", post=">")
|
|
elif type.isRecord():
|
|
key = type.inner.keyType if type.nullable() else 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, indentLevel=2):
|
|
"""
|
|
Indent C++ code.
|
|
|
|
Weird secret feature: this doesn't indent lines that start with # (such as
|
|
#include lines or #ifdef/#endif).
|
|
"""
|
|
if s == "":
|
|
return s
|
|
return re.sub(lineStartDetector, indentLevel * " ", s)
|
|
|
|
|
|
# dedent() and fill() are often called on the same string multiple
|
|
# times. We want to memoize their return values so we don't keep
|
|
# recomputing them all the time.
|
|
def memoize(fn):
|
|
"""
|
|
Decorator to memoize a function of one argument. The cache just
|
|
grows without bound.
|
|
"""
|
|
cache = {}
|
|
|
|
@functools.wraps(fn)
|
|
def wrapper(arg):
|
|
retval = cache.get(arg)
|
|
if retval is None:
|
|
retval = cache[arg] = fn(arg)
|
|
return retval
|
|
return wrapper
|
|
|
|
|
|
@memoize
|
|
def dedent(s):
|
|
"""
|
|
Remove all leading whitespace from s, and remove a blank line
|
|
at the beginning.
|
|
"""
|
|
if s.startswith('\n'):
|
|
s = s[1:]
|
|
return textwrap.dedent(s)
|
|
|
|
|
|
# This works by transforming the fill()-template to an equivalent
|
|
# string.Template.
|
|
fill_multiline_substitution_re = re.compile(r"( *)\$\*{(\w+)}(\n)?")
|
|
|
|
|
|
@memoize
|
|
def compile_fill_template(template):
|
|
"""
|
|
Helper function for fill(). Given the template string passed to fill(),
|
|
do the reusable part of template processing and return a pair (t,
|
|
argModList) that can be used every time fill() is called with that
|
|
template argument.
|
|
|
|
argsModList is list of tuples that represent modifications to be
|
|
made to args. Each modification has, in order: i) the arg name,
|
|
ii) the modified name, iii) the indent depth.
|
|
"""
|
|
t = dedent(template)
|
|
assert t.endswith("\n") or "\n" not in t
|
|
argModList = []
|
|
|
|
def replace(match):
|
|
"""
|
|
Replaces a line like ' $*{xyz}\n' with '${xyz_n}',
|
|
where n is the indent depth, and add a corresponding entry to
|
|
argModList.
|
|
|
|
Note that this needs to close over argModList, so it has to be
|
|
defined inside compile_fill_template().
|
|
"""
|
|
indentation, name, nl = match.groups()
|
|
depth = len(indentation)
|
|
|
|
# Check that $*{xyz} appears by itself on a line.
|
|
prev = match.string[:match.start()]
|
|
if (prev and not prev.endswith("\n")) or nl is None:
|
|
raise ValueError(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, **args):
|
|
"""
|
|
Convenience function for filling in a multiline template.
|
|
|
|
`fill(template, name1=v1, name2=v2)` is a lot like
|
|
`string.Template(template).substitute({"name1": v1, "name2": v2})`.
|
|
|
|
However, it's shorter, and has a few nice features:
|
|
|
|
* If `template` is indented, fill() automatically dedents it!
|
|
This makes code using fill() with Python's multiline strings
|
|
much nicer to look at.
|
|
|
|
* If `template` starts with a blank line, fill() strips it off.
|
|
(Again, convenient with multiline strings.)
|
|
|
|
* fill() recognizes a special kind of substitution
|
|
of the form `$*{name}`.
|
|
|
|
Use this to paste in, and automatically indent, multiple lines.
|
|
(Mnemonic: The `*` is for "multiple lines").
|
|
|
|
A `$*` substitution must appear by itself on a line, with optional
|
|
preceding indentation (spaces only). The whole line is replaced by the
|
|
corresponding keyword argument, indented appropriately. If the
|
|
argument is an empty string, no output is generated, not even a blank
|
|
line.
|
|
"""
|
|
|
|
t, argModList = compile_fill_template(template)
|
|
# Now apply argModList to args
|
|
for (name, modified_name, depth) in argModList:
|
|
if not (args[name] == "" or args[name].endswith("\n")):
|
|
raise ValueError(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):
|
|
pass # Nothing for now
|
|
|
|
def define(self):
|
|
"""Produce code for a Rust file."""
|
|
raise NotImplementedError # Override me!
|
|
|
|
|
|
class CGMethodCall(CGThing):
|
|
"""
|
|
A class to generate selection of a method signature from a set of
|
|
signatures and generation of a call to that signature.
|
|
"""
|
|
def __init__(self, argsPre, nativeMethodName, static, descriptor, method):
|
|
CGThing.__init__(self)
|
|
|
|
methodName = f'\\"{descriptor.interface.identifier.name}.{method.identifier.name}\\"'
|
|
|
|
def requiredArgCount(signature):
|
|
arguments = signature[1]
|
|
if len(arguments) == 0:
|
|
return 0
|
|
requiredArgs = len(arguments)
|
|
while requiredArgs and arguments[requiredArgs - 1].optional:
|
|
requiredArgs -= 1
|
|
return requiredArgs
|
|
|
|
signatures = method.signatures()
|
|
|
|
def getPerSignatureCall(signature, argConversionStartsAt=0):
|
|
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 = [
|
|
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, filterLambda):
|
|
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 = []
|
|
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):
|
|
return self.cgRoot.define()
|
|
|
|
|
|
def dictionaryHasSequenceMember(dictionary):
|
|
return (any(typeIsSequenceOrHasSequenceMember(m.type) for m in
|
|
dictionary.members)
|
|
or (dictionary.parent
|
|
and dictionaryHasSequenceMember(dictionary.parent)))
|
|
|
|
|
|
def typeIsSequenceOrHasSequenceMember(type):
|
|
if type.nullable():
|
|
type = type.inner
|
|
if type.isSequence():
|
|
return True
|
|
if type.isDictionary():
|
|
return dictionaryHasSequenceMember(type.inner)
|
|
if type.isUnion():
|
|
return any(typeIsSequenceOrHasSequenceMember(m.type) for m in
|
|
type.flatMemberTypes)
|
|
return False
|
|
|
|
|
|
def union_native_type(t):
|
|
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):
|
|
return f"{string[0].upper()}{string[1:]}"
|
|
|
|
|
|
class JSToNativeConversionInfo():
|
|
"""
|
|
An object representing information about a JS-to-native conversion.
|
|
"""
|
|
def __init__(self, template, default=None, declType=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, descriptorProvider, failureCode=None,
|
|
isDefinitelyObject=False,
|
|
isMember=False,
|
|
isArgument=False,
|
|
isAutoRooted=False,
|
|
invalidEnumValueFatal=True,
|
|
defaultValue=None,
|
|
exceptionCode=None,
|
|
allowTreatNonObjectAsNull=False,
|
|
isCallbackReturnValue=False,
|
|
sourceDescription="value"):
|
|
"""
|
|
Get a template for converting a JS value to a native object based on the
|
|
given type and descriptor. If failureCode is given, then we're actually
|
|
testing whether we can convert the argument to the desired type. That
|
|
means that failures to convert due to the JS value being the wrong type of
|
|
value need to use failureCode instead of throwing exceptions. Failures to
|
|
convert that are due to JS exceptions (from toString or valueOf methods) or
|
|
out of memory conditions need to throw exceptions no matter what
|
|
failureCode is.
|
|
|
|
If isDefinitelyObject is True, that means we know the value
|
|
isObject() and we have no need to recheck that.
|
|
|
|
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, declType, default):
|
|
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):
|
|
return CGWrapper(
|
|
CGGeneric(
|
|
failureCode
|
|
or (f'throw_type_error(*cx, "{firstCap(sourceDescription)} is not an object.");\n'
|
|
f'{exceptionCode}')),
|
|
post="\n")
|
|
|
|
def onFailureNotCallable(failureCode):
|
|
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):
|
|
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, nullValue, isDefinitelyObject, type,
|
|
failureCode=None):
|
|
if not isDefinitelyObject:
|
|
# Handle the non-object cases by wrapping up the whole
|
|
# thing in an if cascade.
|
|
templateBody = (
|
|
"if ${val}.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
|
|
|
|
assert not (isEnforceRange and isClamp) # These are mutually exclusive
|
|
|
|
if type.isSequence() or type.isRecord():
|
|
innerInfo = getJSToNativeConversionInfo(innerContainerType(type),
|
|
descriptorProvider,
|
|
isMember="Sequence",
|
|
isAutoRooted=isAutoRooted)
|
|
declType = wrapInNativeContainerType(type, innerInfo.declType)
|
|
config = getConversionConfigForType(type, innerContainerType(type).hasEnforceRange(), isClamp, treatNullAs)
|
|
|
|
if type.nullable():
|
|
declType = CGWrapper(declType, pre="Option<", post=" >")
|
|
|
|
templateBody = (f"match FromJSValConvertible::from_jsval(*cx, ${{val}}, {config}) {{\n"
|
|
" Ok(ConversionResult::Success(value)) => value,\n"
|
|
" Ok(ConversionResult::Failure(error)) => {\n"
|
|
f"{indent(failOrPropagate, 8)}\n"
|
|
" }\n"
|
|
f" _ => {{ {exceptionCode} }},\n"
|
|
"}")
|
|
|
|
return handleOptional(templateBody, declType, handleDefault("None"))
|
|
|
|
if type.isUnion():
|
|
declType = CGGeneric(union_native_type(type))
|
|
if type.nullable():
|
|
declType = CGWrapper(declType, pre="Option<", post=" >")
|
|
|
|
templateBody = ("match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n"
|
|
" Ok(ConversionResult::Success(value)) => value,\n"
|
|
" Ok(ConversionResult::Failure(error)) => {\n"
|
|
f"{indent(failOrPropagate, 8)}\n"
|
|
" }\n"
|
|
f" _ => {{ {exceptionCode} }},\n"
|
|
"}")
|
|
|
|
dictionaries = [
|
|
memberType
|
|
for memberType in type.unroll().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 = fill(
|
|
"""
|
|
{ // Scope for our JSAutoRealm.
|
|
|
|
rooted!(in(*cx) let globalObj = CurrentGlobalOrNull(*cx));
|
|
let promiseGlobal = D::GlobalScope::from_object_maybe_wrapped(globalObj.handle().get(), *cx);
|
|
|
|
rooted!(in(*cx) let mut valueToResolve = $${val}.get());
|
|
if !JS_WrapValue(*cx, valueToResolve.handle_mut()) {
|
|
$*{exceptionCode}
|
|
}
|
|
D::Promise::new_resolved(&promiseGlobal, cx, valueToResolve.handle())
|
|
}
|
|
""",
|
|
exceptionCode=exceptionCode)
|
|
|
|
if isArgument:
|
|
declType = CGGeneric("&D::Promise")
|
|
else:
|
|
declType = CGGeneric("Rc<D::Promise>")
|
|
return handleOptional(templateBody, declType, handleDefault("None"))
|
|
|
|
if type.isGeckoInterface():
|
|
assert not isEnforceRange and not isClamp
|
|
|
|
descriptor = descriptorProvider.getDescriptor(
|
|
type.unroll().inner.identifier.name)
|
|
|
|
if descriptor.interface.isCallback():
|
|
name = descriptor.nativeType
|
|
declType = CGWrapper(CGGeneric(f"{name}<D>"), pre="Rc<", post=">")
|
|
template = f"{name}::new(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
|
|
|
|
if isMember == "Union":
|
|
typeName = f"Heap{typeName}"
|
|
|
|
templateBody = fill(
|
|
"""
|
|
match typedarray::${ty}::from($${val}.get().to_object()) {
|
|
Ok(val) => val,
|
|
Err(()) => {
|
|
$*{failureCode}
|
|
}
|
|
}
|
|
""",
|
|
ty=typeName,
|
|
failureCode=f"{unwrapFailureCode}\n",
|
|
)
|
|
|
|
if isMember == "Union":
|
|
templateBody = f"RootedTraceableBox::new({templateBody})"
|
|
|
|
declType = CGGeneric(f"typedarray::{typeName}")
|
|
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 = (
|
|
f"match FromJSValConvertible::from_jsval(*cx, ${{val}}, {nullBehavior}) {{\n"
|
|
" Ok(ConversionResult::Success(strval)) => strval,\n"
|
|
" Ok(ConversionResult::Failure(error)) => {\n"
|
|
f"{indent(failOrPropagate, 8)}\n"
|
|
" }\n"
|
|
f" _ => {{ {exceptionCode} }},\n"
|
|
"}")
|
|
|
|
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 = (
|
|
"match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n"
|
|
" Ok(ConversionResult::Success(strval)) => strval,\n"
|
|
" Ok(ConversionResult::Failure(error)) => {\n"
|
|
f"{indent(failOrPropagate, 8)}\n"
|
|
" }\n"
|
|
f" _ => {{ {exceptionCode} }},\n"
|
|
"}")
|
|
|
|
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 = (
|
|
"match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n"
|
|
" Ok(ConversionResult::Success(strval)) => strval,\n"
|
|
" Ok(ConversionResult::Failure(error)) => {\n"
|
|
f"{indent(failOrPropagate, 8)}\n"
|
|
" }\n"
|
|
f" _ => {{ {exceptionCode} }},\n"
|
|
"}")
|
|
|
|
if defaultValue is None:
|
|
default = None
|
|
elif isinstance(defaultValue, IDLNullValue):
|
|
assert type.nullable()
|
|
default = "None"
|
|
else:
|
|
assert defaultValue.type.tag() in (IDLType.Tags.domstring, IDLType.Tags.bytestring)
|
|
default = f'ByteString::new(b"{defaultValue.value}".to_vec())'
|
|
if type.nullable():
|
|
default = f"Some({default})"
|
|
|
|
declType = "ByteString"
|
|
if type.nullable():
|
|
declType = f"Option<{declType}>"
|
|
|
|
return handleOptional(conversionCode, CGGeneric(declType), default)
|
|
|
|
if type.isEnum():
|
|
assert not isEnforceRange and not isClamp
|
|
|
|
if type.nullable():
|
|
raise TypeError("We don't support nullable enumerated arguments "
|
|
"yet")
|
|
enum = type.inner.identifier.name
|
|
if invalidEnumValueFatal:
|
|
handleInvalidEnumValueCode = failureCode or f"throw_type_error(*cx, &error); {exceptionCode}"
|
|
else:
|
|
handleInvalidEnumValueCode = "return true;"
|
|
|
|
template = (
|
|
"match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {"
|
|
f" Err(_) => {{ {exceptionCode} }},\n"
|
|
" Ok(ConversionResult::Success(v)) => v,\n"
|
|
f" Ok(ConversionResult::Failure(error)) => {{ {handleInvalidEnumValueCode} }},\n"
|
|
"}")
|
|
|
|
if defaultValue is not None:
|
|
assert defaultValue.type.tag() == IDLType.Tags.domstring
|
|
default = f"{enum}::{getEnumValueName(defaultValue.value)}"
|
|
else:
|
|
default = None
|
|
|
|
return handleOptional(template, CGGeneric(enum), default)
|
|
|
|
if type.isCallback():
|
|
assert not isEnforceRange and not isClamp
|
|
assert not type.treatNonCallableAsNull()
|
|
assert not type.treatNonObjectAsNull() or type.nullable()
|
|
assert not type.treatNonObjectAsNull() or not type.treatNonCallableAsNull()
|
|
|
|
callback = type.unroll().callback
|
|
declType = CGGeneric(f"{callback.identifier.name}<D>")
|
|
finalDeclType = CGTemplatedType("Rc", declType)
|
|
|
|
conversion = CGCallbackTempRoot(declType.define())
|
|
|
|
if type.nullable():
|
|
declType = CGTemplatedType("Option", declType)
|
|
finalDeclType = CGTemplatedType("Option", finalDeclType)
|
|
conversion = CGWrapper(conversion, pre="Some(", post=")")
|
|
|
|
if allowTreatNonObjectAsNull and type.treatNonObjectAsNull():
|
|
if not isDefinitelyObject:
|
|
haveObject = "${val}.get().is_object()"
|
|
template = CGIfElseWrapper(haveObject,
|
|
conversion,
|
|
CGGeneric("None")).define()
|
|
else:
|
|
template = conversion
|
|
else:
|
|
template = CGIfElseWrapper("IsCallable(${val}.get().to_object())",
|
|
conversion,
|
|
onFailureNotCallable(failureCode)).define()
|
|
template = wrapObjectTemplate(
|
|
template,
|
|
"None",
|
|
isDefinitelyObject,
|
|
type,
|
|
failureCode)
|
|
|
|
if defaultValue is not None:
|
|
assert allowTreatNonObjectAsNull
|
|
assert type.treatNonObjectAsNull()
|
|
assert type.nullable()
|
|
assert isinstance(defaultValue, IDLNullValue)
|
|
default = "None"
|
|
else:
|
|
default = None
|
|
|
|
return JSToNativeConversionInfo(template, default, finalDeclType)
|
|
|
|
if type.isAny():
|
|
assert not isEnforceRange and not isClamp
|
|
assert isMember != "Union"
|
|
|
|
if isMember in ("Dictionary", "Sequence") or isAutoRooted:
|
|
templateBody = "${val}.get()"
|
|
|
|
if defaultValue is None:
|
|
default = None
|
|
elif isinstance(defaultValue, IDLNullValue):
|
|
default = "NullValue()"
|
|
elif isinstance(defaultValue, IDLUndefinedValue):
|
|
default = "UndefinedValue()"
|
|
else:
|
|
raise TypeError("Can't handle non-null, non-undefined default value here")
|
|
|
|
if not isAutoRooted:
|
|
templateBody = f"RootedTraceableBox::from_box(Heap::boxed({templateBody}))"
|
|
if default is not None:
|
|
default = f"RootedTraceableBox::from_box(Heap::boxed({default}))"
|
|
declType = CGGeneric("RootedTraceableBox<Heap<JSVal>>")
|
|
# 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")
|
|
|
|
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 = (
|
|
"match FromJSValConvertible::from_jsval(*cx, ${val}, ()) {\n"
|
|
" Ok(ConversionResult::Success(dictionary)) => dictionary,\n"
|
|
" Ok(ConversionResult::Failure(error)) => {\n"
|
|
f"{indent(failOrPropagate, 8)}\n"
|
|
" }\n"
|
|
f" _ => {{ {exceptionCode} }},\n"
|
|
"}")
|
|
|
|
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 = (
|
|
f"match FromJSValConvertible::from_jsval(*cx, ${{val}}, {conversionBehavior}) {{\n"
|
|
" Ok(ConversionResult::Success(v)) => v,\n"
|
|
" Ok(ConversionResult::Failure(error)) => {\n"
|
|
f"{indent(failOrPropagate, 8)}\n"
|
|
" }\n"
|
|
f" _ => {{ {exceptionCode} }}\n"
|
|
"}")
|
|
|
|
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, replacements,
|
|
declType, declName,
|
|
needsAutoRoot=False):
|
|
"""
|
|
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):
|
|
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.
|
|
"""
|
|
def __init__(self, argument, index, args, argc, descriptorProvider,
|
|
invalidEnumValueFatal=True):
|
|
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
|
|
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):
|
|
return self.converter.define()
|
|
|
|
|
|
def wrapForType(jsvalRef, result='result', successCode='true', pre=''):
|
|
"""
|
|
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, retVal=False):
|
|
if type is None:
|
|
return False
|
|
if type.nullable():
|
|
type = type.inner
|
|
if type.isSequence():
|
|
type = type.inner
|
|
if type.isUnion():
|
|
return any(typeNeedsCx(t) for t in type.unroll().flatMemberTypes)
|
|
if retVal and type.isSpiderMonkeyInterface():
|
|
return True
|
|
return type.isAny() or type.isObject()
|
|
|
|
|
|
def returnTypeNeedsOutparam(type):
|
|
if type.nullable():
|
|
type = type.inner
|
|
return type.isAny()
|
|
|
|
|
|
def outparamTypeFromReturnType(type):
|
|
if type.isAny():
|
|
return "MutableHandleValue"
|
|
raise f"Don't know how to handle {type} as an outparam"
|
|
|
|
|
|
# Returns a conversion behavior suitable for a type
|
|
def getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs):
|
|
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):
|
|
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, descriptorProvider):
|
|
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():
|
|
result = CGGeneric(returnType.unroll().inner.identifier.name)
|
|
if returnType.nullable():
|
|
result = CGWrapper(result, pre="Option<", post=">")
|
|
return result
|
|
if returnType.isPromise():
|
|
assert not returnType.nullable()
|
|
return CGGeneric("Rc<D::Promise>")
|
|
if returnType.isGeckoInterface():
|
|
descriptor = descriptorProvider.getDescriptor(
|
|
returnType.unroll().inner.identifier.name)
|
|
result = CGGeneric(descriptor.returnType)
|
|
if returnType.nullable():
|
|
result = CGWrapper(result, pre="Option<", post=">")
|
|
return result
|
|
if returnType.isCallback():
|
|
callback = returnType.unroll().callback
|
|
result = CGGeneric(f'Rc<{getModuleFromObject(callback)}::{callback.identifier.name}<D>>')
|
|
if returnType.nullable():
|
|
result = CGWrapper(result, pre="Option<", post=">")
|
|
return result
|
|
if returnType.isUnion():
|
|
result = CGGeneric(union_native_type(returnType))
|
|
if returnType.nullable():
|
|
result = CGWrapper(result, pre="Option<", post=">")
|
|
return result
|
|
if returnType.isAny():
|
|
return CGGeneric("JSVal")
|
|
if returnType.isObject() or returnType.isSpiderMonkeyInterface():
|
|
result = CGGeneric("NonNull<JSObject>")
|
|
if returnType.nullable():
|
|
result = CGWrapper(result, pre="Option<", post=">")
|
|
return result
|
|
if returnType.isSequence() or returnType.isRecord():
|
|
result = getRetvalDeclarationForType(innerContainerType(returnType), descriptorProvider)
|
|
result = wrapInNativeContainerType(returnType, result)
|
|
if returnType.nullable():
|
|
result = CGWrapper(result, pre="Option<", post=">")
|
|
return result
|
|
if returnType.isDictionary():
|
|
nullable = returnType.nullable()
|
|
dictName = returnType.inner.name if nullable else returnType.name
|
|
generic = "<D>" if containsDomInterface(returnType) else ""
|
|
result = CGGeneric(f"{dictName}{generic}")
|
|
if type_needs_tracing(returnType):
|
|
result = CGWrapper(result, pre="RootedTraceableBox<", post=">")
|
|
if nullable:
|
|
result = CGWrapper(result, pre="Option<", post=">")
|
|
return result
|
|
|
|
raise TypeError(f"Don't know how to declare return value for {returnType}")
|
|
|
|
|
|
def MemberCondition(pref, func, exposed, secure):
|
|
"""
|
|
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
|
|
|
|
|
|
class PropertyDefiner:
|
|
"""
|
|
A common superclass for defining things on prototype objects.
|
|
|
|
Subclasses should implement generateArray to generate the actual arrays of
|
|
things we're defining. They should also set self.regular to the list of
|
|
things exposed to web pages.
|
|
"""
|
|
def __init__(self, descriptor, name):
|
|
self.descriptor = descriptor
|
|
self.name = name
|
|
|
|
def variableName(self):
|
|
return f"s{self.name}"
|
|
|
|
def length(self):
|
|
return len(self.regular)
|
|
|
|
def __str__(self):
|
|
# We only need to generate id arrays for things that will end
|
|
# up used via ResolveProperty or EnumerateProperties.
|
|
return self.generateArray(self.regular, self.variableName())
|
|
|
|
@staticmethod
|
|
def getStringAttr(member, name):
|
|
attr = member.getExtendedAttribute(name)
|
|
if attr is None:
|
|
return None
|
|
# It's a list of strings
|
|
assert len(attr) == 1
|
|
assert attr[0] is not None
|
|
return attr[0]
|
|
|
|
@staticmethod
|
|
def getControllingCondition(interfaceMember, descriptor):
|
|
return MemberCondition(
|
|
PropertyDefiner.getStringAttr(interfaceMember,
|
|
"Pref"),
|
|
PropertyDefiner.getStringAttr(interfaceMember,
|
|
"Func"),
|
|
interfaceMember.exposureSet,
|
|
interfaceMember.getExtendedAttribute("SecureContext"))
|
|
|
|
def generateGuardedArray(self, array, name, specTemplate, specTerminator,
|
|
specType, getCondition, getDataTuple):
|
|
"""
|
|
This method generates our various arrays.
|
|
|
|
array is an array of interface members as passed to generateArray
|
|
|
|
name is the name as passed to generateArray
|
|
|
|
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])'
|
|
origTemplate = specTemplate
|
|
if isinstance(specTemplate, str):
|
|
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, name, specTemplate, specTerminator,
|
|
specType, getCondition, getDataTuple):
|
|
"""
|
|
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
|
|
|
|
origTemplate = specTemplate
|
|
if isinstance(specTemplate, str):
|
|
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):
|
|
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, name, static, unforgeable, crossorigin=False):
|
|
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 = [{"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()}
|
|
for m in methods]
|
|
|
|
# TODO: Once iterable is implemented, use tiebreak rules instead of
|
|
# failing. Also, may be more tiebreak rules to implement once spec bug
|
|
# is resolved.
|
|
# https://www.w3.org/Bugs/Public/show_bug.cgi?id=28592
|
|
def hasIterator(methods, regular):
|
|
return (any("@@iterator" in m.aliases for m in methods)
|
|
or any("@@iterator" == r["name"] for r in regular))
|
|
|
|
# Check whether we need to output an @@iterator due to having an indexed
|
|
# getter. We only do this while outputting non-static and
|
|
# non-unforgeable methods, since the @@iterator function will be
|
|
# neither.
|
|
if (not static
|
|
and not unforgeable
|
|
and not crossorigin
|
|
and descriptor.supportsIndexedProperties()): # noqa
|
|
if hasIterator(methods, self.regular): # noqa
|
|
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
|
|
|
|
def generateArray(self, array, name):
|
|
if len(array) == 0:
|
|
return ""
|
|
|
|
def condition(m, d):
|
|
return m["condition"]
|
|
|
|
def specData(m):
|
|
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, name, static, unforgeable, crossorigin=False):
|
|
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 = [
|
|
{
|
|
"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, name):
|
|
if len(array) == 0:
|
|
return ""
|
|
|
|
def getter(attr):
|
|
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):
|
|
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, d):
|
|
if m["name"] == "@@toStringTag":
|
|
return MemberCondition(pref=None, func=None, exposed=None, secure=None)
|
|
return PropertyDefiner.getControllingCondition(m["attr"], d)
|
|
|
|
def specData(attr):
|
|
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):
|
|
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):
|
|
"""
|
|
A class for definining constants on the interface object
|
|
"""
|
|
def __init__(self, descriptor, name):
|
|
PropertyDefiner.__init__(self, descriptor, name)
|
|
self.name = name
|
|
self.regular = [m for m in descriptor.interface.members if m.isConst()]
|
|
|
|
def generateArray(self, array, name):
|
|
if len(array) == 0:
|
|
return ""
|
|
|
|
def specData(const):
|
|
return (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, indentLevel=4):
|
|
CGThing.__init__(self)
|
|
self.child = child
|
|
self.indent = " " * indentLevel
|
|
|
|
def define(self):
|
|
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.
|
|
"""
|
|
def __init__(self, child, pre="", post="", reindent=False):
|
|
CGThing.__init__(self)
|
|
self.child = child
|
|
self.pre = pre
|
|
self.post = post
|
|
self.reindent = reindent
|
|
|
|
def define(self):
|
|
defn = self.child.define()
|
|
if self.reindent:
|
|
# We don't use lineStartDetector because we don't want to
|
|
# insert whitespace at the beginning of our _first_ line.
|
|
defn = stripTrailingWhitespace(
|
|
defn.replace("\n", 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, value):
|
|
CGThing.__init__(self)
|
|
assert keyType.isString()
|
|
self.keyType = keyType
|
|
self.value = value
|
|
|
|
def define(self):
|
|
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}>"
|
|
|
|
|
|
class CGImports(CGWrapper):
|
|
"""
|
|
Generates the appropriate import/use statements.
|
|
"""
|
|
def __init__(self, child, descriptors, callbacks, dictionaries, enums, typedefs, imports, config):
|
|
"""
|
|
Adds a set of imports.
|
|
"""
|
|
|
|
def componentTypes(type):
|
|
if type.isType() and type.nullable():
|
|
type = type.unroll()
|
|
if type.isUnion():
|
|
return type.flatMemberTypes
|
|
if type.isDictionary():
|
|
return [type] + getTypesFromDictionary(type)
|
|
if type.isSequence():
|
|
return componentTypes(type.inner)
|
|
return [type]
|
|
|
|
def isImportable(type):
|
|
if not type.isType():
|
|
assert (type.isInterface() or type.isDictionary()
|
|
or type.isEnum() or type.isNamespace())
|
|
return True
|
|
return not (type.builtin or type.isSequence() or type.isUnion())
|
|
|
|
def relatedTypesForSignatures(method):
|
|
types = []
|
|
for (returnType, arguments) in method.signatures():
|
|
types += componentTypes(returnType)
|
|
for arg in arguments:
|
|
types += componentTypes(arg.type)
|
|
|
|
return types
|
|
|
|
def getIdentifier(t):
|
|
if t.isType():
|
|
if t.nullable():
|
|
t = t.inner
|
|
if t.isCallback():
|
|
return t.callback.identifier
|
|
return t.identifier
|
|
assert t.isInterface() or t.isDictionary() or t.isEnum() or t.isNamespace()
|
|
return t.identifier
|
|
|
|
def removeWrapperAndNullableTypes(types):
|
|
normalized = []
|
|
for t in types:
|
|
while (t.isType() and t.nullable()) or isinstance(t, 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 t.isType() 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, child):
|
|
pre = CGWrapper(CGGeneric(condition), pre="if ", post=" {\n",
|
|
reindent=True)
|
|
CGWrapper.__init__(self, CGIndenter(child), pre=pre.define(),
|
|
post="\n}")
|
|
|
|
|
|
class CGTemplatedType(CGWrapper):
|
|
def __init__(self, templateName, child):
|
|
CGWrapper.__init__(self, child, pre=f"{templateName}<", post=">")
|
|
|
|
|
|
class CGNamespace(CGWrapper):
|
|
def __init__(self, namespace, child, public=False):
|
|
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, child, public=False):
|
|
"""
|
|
Static helper method to build multiple wrapped namespaces.
|
|
"""
|
|
if not namespaces:
|
|
return child
|
|
inner = CGNamespace.build(namespaces[1:], child, public=public)
|
|
return CGNamespace(namespaces[0], inner, public=public)
|
|
|
|
|
|
def DOMClassTypeId(desc):
|
|
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):
|
|
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):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
|
|
def define(self):
|
|
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",
|
|
"slots": "1",
|
|
"traceHook": f"{TRACE_HOOK_NAME}::<D>",
|
|
}
|
|
if self.descriptor.isGlobal():
|
|
assert not self.descriptor.weakReferenceable
|
|
args["enumerateHook"] = "Some(enumerate_global::<D>)"
|
|
args["flags"] = "JSCLASS_IS_GLOBAL | JSCLASS_DOM_GLOBAL | JSCLASS_FOREGROUND_FINALIZE"
|
|
args["slots"] = "JSCLASS_GLOBAL_SLOT_COUNT + 1"
|
|
args["resolveHook"] = "Some(resolve_global::<D>)"
|
|
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: None,
|
|
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):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
|
|
def define(self):
|
|
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):
|
|
return f'c"{s}"'
|
|
|
|
|
|
def str_to_cstr_ptr(s):
|
|
return f'c"{s}".as_ptr()'
|
|
|
|
|
|
class CGPrototypeJSClass(CGThing):
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
|
|
def define(self):
|
|
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):
|
|
assert descriptor.interface.hasInterfaceObject() and not descriptor.interface.isCallback()
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
|
|
def define(self):
|
|
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, joiner=""):
|
|
CGThing.__init__(self)
|
|
# Make a copy of the kids into a list, because if someone passes in a
|
|
# generator we won't be able to both declare and define ourselves, or
|
|
# define ourselves more than once!
|
|
self.children = list(children)
|
|
self.joiner = joiner
|
|
|
|
def append(self, child):
|
|
self.children.append(child)
|
|
|
|
def prepend(self, child):
|
|
self.children.insert(0, child)
|
|
|
|
def join(self, iterable):
|
|
return self.joiner.join(s for s in iterable if len(s) > 0)
|
|
|
|
def define(self):
|
|
return self.join(child.define() for child in self.children if child is not None)
|
|
|
|
def __len__(self):
|
|
return len(self.children)
|
|
|
|
|
|
class CGIfElseWrapper(CGList):
|
|
def __init__(self, condition, ifTrue, ifFalse):
|
|
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.
|
|
"""
|
|
def __init__(self, text):
|
|
self.text = text
|
|
|
|
def define(self):
|
|
return self.text
|
|
|
|
|
|
class CGCallbackTempRoot(CGGeneric):
|
|
def __init__(self, name):
|
|
CGGeneric.__init__(self, f"{name.replace('<D>', '::<D>')}::new(cx, ${{val}}.get().to_object())")
|
|
|
|
|
|
def getAllTypes(descriptors, dictionaries, callbacks, typedefs):
|
|
"""
|
|
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):
|
|
yield (t, d)
|
|
for dictionary in dictionaries:
|
|
for t in getTypesFromDictionary(dictionary):
|
|
yield (t, None)
|
|
for callback in callbacks:
|
|
for t in getTypesFromCallback(callback):
|
|
yield (t, None)
|
|
for typedef in typedefs:
|
|
yield (typedef.innerType, None)
|
|
|
|
|
|
def UnionTypes(descriptors, dictionaries, callbacks, typedefs, config):
|
|
"""
|
|
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()
|
|
for (t, descriptor) in getAllTypes(descriptors, dictionaries, callbacks, typedefs):
|
|
t = t.unroll()
|
|
if not t.isUnion():
|
|
continue
|
|
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
|
|
unionStructs = (i[1] for i in sorted(list(unionStructs.items()), key=operator.itemgetter(0)))
|
|
|
|
return CGImports(CGList(unionStructs, "\n\n"), descriptors=[], callbacks=[], dictionaries=[], enums=[],
|
|
typedefs=[], imports=imports, config=config)
|
|
|
|
|
|
def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs, config):
|
|
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):
|
|
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}>"]
|
|
|
|
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, descriptorProvider, dictionaries, callbacks, typedefs, config):
|
|
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, name, default=None, mutable=False):
|
|
self.argType = argType
|
|
self.name = name
|
|
self.default = default
|
|
self.mutable = mutable
|
|
|
|
def declare(self):
|
|
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):
|
|
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, name, returnType, args, inline=False,
|
|
alwaysInline=False, extern=False, unsafe=False, pub=False,
|
|
templateArgs=None, docs=None, doesNotPanic=False, extra_decorators=[]):
|
|
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):
|
|
return ', '.join([a.declare() for a in self.args])
|
|
|
|
def _template(self):
|
|
if self.templateArgs is None:
|
|
return ''
|
|
return f'<{", ".join(self.templateArgs)}>\n'
|
|
|
|
def _docs(self):
|
|
if self.docs is None:
|
|
return ''
|
|
|
|
lines = self.docs.splitlines()
|
|
return ''.join(f'/// {line}\n' for line in lines)
|
|
|
|
def _decorators(self):
|
|
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):
|
|
return f" -> {self.returnType}" if self.returnType != "void" else ""
|
|
|
|
def define(self):
|
|
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):
|
|
return (f"{self._docs()}{self._decorators()}"
|
|
f"fn {self.name}{self._template()}({self._argstring()}){self._returnType()}{{\n")
|
|
|
|
def definition_epilogue(self):
|
|
return "\n}\n"
|
|
|
|
def definition_body(self):
|
|
raise NotImplementedError # Override me!
|
|
|
|
|
|
class CGConstructorEnabled(CGAbstractMethod):
|
|
"""
|
|
A method for testing whether we should be exposing this interface object.
|
|
This can perform various tests depending on what conditions are specified
|
|
on the interface.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
CGAbstractMethod.__init__(self, descriptor,
|
|
'ConstructorEnabled', 'bool',
|
|
[Argument("SafeJSContext", "aCx"),
|
|
Argument("HandleObject", "aObj")],
|
|
templateArgs=['D: DomTypes'])
|
|
|
|
def definition_body(self):
|
|
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, properties):
|
|
"""
|
|
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):
|
|
"""
|
|
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):
|
|
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):
|
|
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, properties):
|
|
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, unsafe=True,
|
|
extra_decorators=['#[cfg_attr(crown, allow(crown::unrooted_must_root))]'],
|
|
templateArgs=['D: DomTypes'])
|
|
self.properties = properties
|
|
|
|
def definition_body(self):
|
|
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"""
|
|
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);
|
|
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):
|
|
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):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
|
|
def define(self):
|
|
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):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
|
|
def define(self):
|
|
iterableInterface = self.descriptor.interface.iterableInterface
|
|
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):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
|
|
def define(self):
|
|
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):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
|
|
def define(self):
|
|
assert self.descriptor.interface.isIteratorInterface()
|
|
name = self.descriptor.interface.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, name, returnType, args, doesNotPanic=False, templateArgs=None):
|
|
CGAbstractMethod.__init__(self, descriptor, name, returnType, args,
|
|
inline=False, extern=True, doesNotPanic=doesNotPanic, templateArgs=templateArgs)
|
|
|
|
|
|
class PropertyArrays():
|
|
def __init__(self, descriptor):
|
|
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():
|
|
return [
|
|
"static_methods",
|
|
"static_attrs",
|
|
"methods",
|
|
"unforgeable_methods",
|
|
"attrs",
|
|
"unforgeable_attrs",
|
|
"consts",
|
|
]
|
|
|
|
def variableNames(self):
|
|
names = {}
|
|
for array in self.arrayNames():
|
|
names[array] = getattr(self, array).variableName()
|
|
return names
|
|
|
|
def __str__(self):
|
|
define = ""
|
|
for array in self.arrayNames():
|
|
define += str(getattr(self, array))
|
|
return define
|
|
|
|
|
|
class CGCrossOriginProperties(CGThing):
|
|
def __init__(self, descriptor):
|
|
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):
|
|
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, toJSONMethod):
|
|
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):
|
|
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, properties, haveUnscopables, haveLegacyWindowAliases):
|
|
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):
|
|
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 = [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)
|
|
if self.descriptor.interface.ctor():
|
|
properties["length"] = methodLength(self.descriptor.interface.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):
|
|
if alias == "@@iterator":
|
|
symbolJSID = "RUST_SYMBOL_TO_JSID(GetWellKnownSymbol(*cx, SymbolCode::iterator), \
|
|
iteratorId.handle_mut())"
|
|
getSymbolJSID = 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):
|
|
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, name, idPrefix="", pub=False):
|
|
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):
|
|
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):
|
|
CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject",
|
|
"PrototypeList::ID", pub=True)
|
|
|
|
def definition_body(self):
|
|
return CGList([
|
|
CGGeneric("""\
|
|
/* Get the interface prototype object for this class. This will create the
|
|
object as needed. */"""),
|
|
CGGetPerInterfaceObject.definition_body(self),
|
|
])
|
|
|
|
|
|
class CGGetConstructorObjectMethod(CGGetPerInterfaceObject):
|
|
"""
|
|
A method for getting the interface constructor object.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
CGGetPerInterfaceObject.__init__(self, descriptor, "GetConstructorObject",
|
|
"PrototypeList::Constructor",
|
|
pub=True)
|
|
|
|
def definition_body(self):
|
|
return CGList([
|
|
CGGeneric("""\
|
|
/* Get the interface object for this class. This will create the object as
|
|
needed. */"""),
|
|
CGGetPerInterfaceObject.definition_body(self),
|
|
])
|
|
|
|
|
|
class CGDefineProxyHandler(CGAbstractMethod):
|
|
"""
|
|
A method to create and cache the proxy trap for a given interface.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
assert descriptor.proxy
|
|
CGAbstractMethod.__init__(self, descriptor, 'DefineProxyHandler',
|
|
'*const libc::c_void', [],
|
|
pub=True, unsafe=True, templateArgs=["D: DomTypes"])
|
|
|
|
def define(self):
|
|
return CGAbstractMethod.define(self)
|
|
|
|
def definition_body(self):
|
|
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):
|
|
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):
|
|
return CGAbstractMethod.define(self)
|
|
|
|
def definition_body(self):
|
|
return CGGeneric(
|
|
"define_dom_interface"
|
|
f"(cx, global, ProtoOrIfaceIndex::{self.variant}({self.id}),"
|
|
"CreateInterfaceObjects::<D>, ConstructorEnabled::<D>)"
|
|
)
|
|
|
|
|
|
def needCx(returnType, arguments, considerTypes):
|
|
return (considerTypes
|
|
and (typeNeedsCx(returnType, True)
|
|
or any(typeNeedsCx(a.type) for a in arguments)))
|
|
|
|
|
|
class CGCallGenerator(CGThing):
|
|
"""
|
|
A class to generate an actual call to a C++ object. Assumes that the C++
|
|
object is stored in a variable whose name is given by the |object| argument.
|
|
|
|
errorResult should be a string for the value to return in case of an
|
|
exception from the native code, or None if no error reporting is needed.
|
|
"""
|
|
def __init__(self, errorResult, arguments, argsPre, returnType,
|
|
extendedAttributes, descriptor, nativeMethodName,
|
|
static, object="this", hasCEReactions=False):
|
|
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):
|
|
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, argsPre, arguments, nativeMethodName, static,
|
|
descriptor, idlNode, argConversionStartsAt=0,
|
|
getter=False, setter=False):
|
|
CGThing.__init__(self)
|
|
self.returnType = returnType
|
|
self.descriptor = descriptor
|
|
self.idlNode = idlNode
|
|
self.extendedAttributes = descriptor.getExtendedAttributes(idlNode,
|
|
getter=getter,
|
|
setter=setter)
|
|
self.argsPre = argsPre
|
|
self.arguments = arguments
|
|
self.argCount = len(arguments)
|
|
cgThings = []
|
|
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 idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod():
|
|
if idlNode.maplikeOrSetlikeOrIterable.isMaplike() or \
|
|
idlNode.maplikeOrSetlikeOrIterable.isSetlike():
|
|
cgThings.append(CGMaplikeOrSetlikeMethodGenerator(descriptor,
|
|
idlNode.maplikeOrSetlikeOrIterable,
|
|
idlNode.identifier.name))
|
|
else:
|
|
cgThings.append(CGIterableMethodGenerator(descriptor,
|
|
idlNode.maplikeOrSetlikeOrIterable,
|
|
idlNode.identifier.name))
|
|
else:
|
|
hasCEReactions = idlNode.getExtendedAttribute("CEReactions")
|
|
cgThings.append(CGCallGenerator(
|
|
errorResult,
|
|
self.getArguments(), self.argsPre, returnType,
|
|
self.extendedAttributes, descriptor, nativeMethodName,
|
|
static, hasCEReactions=hasCEReactions))
|
|
|
|
self.cgRoot = CGList(cgThings, "\n")
|
|
|
|
def getArgs(self):
|
|
return "args" if self.argCount > 0 else ""
|
|
|
|
def getArgc(self):
|
|
return "argc"
|
|
|
|
def getArguments(self):
|
|
return [(a, process_arg(f"arg{i}", a)) for (i, a) in enumerate(self.arguments)]
|
|
|
|
def isFallible(self):
|
|
return 'infallible' not in self.extendedAttributes
|
|
|
|
def wrap_return_value(self):
|
|
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 (self.idlNode.isMethod() and self.idlNode.isMaplikeOrSetlikeOrIterableMethod())):
|
|
resultName = "retval"
|
|
return wrapForType(
|
|
'MutableHandleValue::from_raw(args.rval())',
|
|
result=resultName,
|
|
successCode='return true;',
|
|
)
|
|
|
|
def define(self):
|
|
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, cases, default=None):
|
|
CGList.__init__(self, [CGIndenter(c) for c in cases], "\n")
|
|
self.prepend(CGWrapper(CGGeneric(expression),
|
|
pre="match ", post=" {"))
|
|
if default is not None:
|
|
self.append(
|
|
CGIndenter(
|
|
CGWrapper(
|
|
CGIndenter(default),
|
|
pre="_ => {\n",
|
|
post="\n}"
|
|
)
|
|
)
|
|
)
|
|
|
|
self.append(CGGeneric("}"))
|
|
|
|
|
|
class CGCase(CGList):
|
|
"""
|
|
A class to generate code for a case statement.
|
|
|
|
Takes three constructor arguments: an expression, a CGThing for
|
|
the body (allowed to be None if there is no body), and an optional
|
|
argument (defaulting to False) for whether to fall through.
|
|
"""
|
|
def __init__(self, expression, body, fallThrough=False):
|
|
CGList.__init__(self, [], "\n")
|
|
self.append(CGWrapper(CGGeneric(expression), post=" => {"))
|
|
bodyList = CGList([body], "\n")
|
|
if fallThrough:
|
|
raise TypeError("fall through required but unsupported")
|
|
# bodyList.append(CGGeneric('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, returnType, nativeMethodName, descriptor, attr):
|
|
CGPerSignatureCall.__init__(self, returnType, argsPre, [],
|
|
nativeMethodName, attr.isStatic(), descriptor,
|
|
attr, getter=True)
|
|
|
|
|
|
class FakeArgument():
|
|
"""
|
|
A class that quacks like an IDLArgument. This is used to make
|
|
setters look like method calls or for special operations.
|
|
"""
|
|
def __init__(self, type, interfaceMember, allowTreatNonObjectAsNull=False):
|
|
self.type = type
|
|
self.optional = False
|
|
self.variadic = False
|
|
self.defaultValue = None
|
|
self._allowTreatNonObjectAsNull = allowTreatNonObjectAsNull
|
|
|
|
def allowTreatNonCallableAsNull(self):
|
|
return self._allowTreatNonObjectAsNull
|
|
|
|
|
|
class CGSetterCall(CGPerSignatureCall):
|
|
"""
|
|
A class to generate a native object setter call for a particular IDL
|
|
setter.
|
|
"""
|
|
def __init__(self, argsPre, argType, nativeMethodName, descriptor, attr):
|
|
CGPerSignatureCall.__init__(self, None, argsPre,
|
|
[FakeArgument(argType, attr, allowTreatNonObjectAsNull=True)],
|
|
nativeMethodName, attr.isStatic(), descriptor, attr,
|
|
setter=True)
|
|
|
|
def wrap_return_value(self):
|
|
# We have no return value
|
|
return "\ntrue"
|
|
|
|
def getArgc(self):
|
|
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, name, templateArgs=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):
|
|
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):
|
|
raise NotImplementedError # Override me!
|
|
|
|
|
|
def GetConstructorNameForReporting(descriptor, ctor):
|
|
# Figure out the name of our constructor for reporting purposes.
|
|
# For unnamed webidl constructors, identifier.name is "constructor" but
|
|
# the name JS sees is the interface name; for legacy factory functions
|
|
# identifier.name is the actual name.
|
|
ctorName = ctor.identifier.name
|
|
if ctorName == "constructor":
|
|
return descriptor.interface.identifier.name
|
|
return ctorName
|
|
|
|
|
|
class CGSpecializedMethod(CGAbstractExternMethod):
|
|
"""
|
|
A class for generating the C++ code for a specialized method that the JIT
|
|
can call with lower overhead.
|
|
"""
|
|
def __init__(self, descriptor, method):
|
|
self.method = method
|
|
name = method.identifier.name
|
|
args = [Argument('*mut JSContext', 'cx'),
|
|
Argument('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):
|
|
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, method):
|
|
if method.underlyingAttr:
|
|
return CGSpecializedGetter.makeNativeName(descriptor, method.underlyingAttr)
|
|
name = method.identifier.name
|
|
nativeName = descriptor.binaryNameFor(name)
|
|
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, methodToWrap):
|
|
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):
|
|
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, m):
|
|
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, methodToWrap):
|
|
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):
|
|
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, m):
|
|
return f'get_{descriptor.internalNameFor(m.identifier.name)}'
|
|
|
|
@staticmethod
|
|
def makeNativeName(descriptor, m):
|
|
return f"{CGGetterPromiseWrapper.makeOrigName(descriptor, m)}_promise_wrapper"
|
|
|
|
|
|
class CGDefaultToJSONMethod(CGSpecializedMethod):
|
|
def __init__(self, descriptor, method):
|
|
assert method.isDefaultToJSON()
|
|
CGSpecializedMethod.__init__(self, descriptor, method)
|
|
|
|
def definition_body(self):
|
|
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, method):
|
|
self.method = method
|
|
name = method.identifier.name
|
|
CGAbstractStaticBindingMethod.__init__(self, descriptor, name, templateArgs=["D: DomTypes"])
|
|
|
|
def generate_code(self):
|
|
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, attr):
|
|
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):
|
|
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, attr):
|
|
name = attr.identifier.name
|
|
nativeName = descriptor.binaryNameFor(name)
|
|
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, attr):
|
|
self.attr = attr
|
|
name = f'get_{attr.identifier.name}'
|
|
CGAbstractStaticBindingMethod.__init__(self, descriptor, name, templateArgs=["D: DomTypes"])
|
|
|
|
def generate_code(self):
|
|
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, attr):
|
|
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):
|
|
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, attr):
|
|
name = attr.identifier.name
|
|
nativeName = descriptor.binaryNameFor(name)
|
|
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, attr):
|
|
self.attr = attr
|
|
name = f'set_{attr.identifier.name}'
|
|
CGAbstractStaticBindingMethod.__init__(self, descriptor, name, templateArgs=["D: DomTypes"])
|
|
|
|
def generate_code(self):
|
|
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, attr):
|
|
CGSpecializedSetter.__init__(self, descriptor, attr)
|
|
|
|
def definition_body(self):
|
|
attrName = self.attr.identifier.name
|
|
forwardToAttrName = self.attr.getExtendedAttribute("PutForwards")[0]
|
|
# JS_GetProperty and JS_SetProperty can only deal with ASCII
|
|
assert all(ord(c) < 128 for c in attrName)
|
|
assert all(ord(c) < 128 for c in forwardToAttrName)
|
|
return 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, attr):
|
|
CGSpecializedSetter.__init__(self, descriptor, attr)
|
|
|
|
def definition_body(self):
|
|
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, member):
|
|
self.member = member
|
|
self.descriptor = descriptor
|
|
|
|
def defineJitInfo(self, infoName, opName, opType, infallible, movable,
|
|
aliasSet, alwaysInSlot, lazilyInSlot, slotIndex,
|
|
returnTypes, args):
|
|
"""
|
|
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):
|
|
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=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:
|
|
argTypes = f"{infoName}_argTypes"
|
|
args = [CGMemberJITInfo.getJSArgType(arg.type) for arg in args]
|
|
args.append("JSJitInfo_ArgType::ArgTypeListEnd as i32")
|
|
argTypesDecl = f"const {argTypes}: [i32; {len(args)}] = [ {', '.join(args)} ];\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=argTypes)
|
|
|
|
return f"""
|
|
static {infoName}: ThreadUnsafeOnceLock<JSJitInfo> = ThreadUnsafeOnceLock::new();
|
|
|
|
pub(crate) fn init_{infoName}<D: DomTypes>() {{
|
|
{infoName}.set({jitInfoInitializer(False)});
|
|
}}"""
|
|
|
|
def define(self):
|
|
if self.member.isAttr():
|
|
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")
|
|
if self.member.slotIndices is not None:
|
|
assert isAlwaysInSlot or self.member.getExtendedAttribute("Cached")
|
|
isLazilyCachedInSlot = not isAlwaysInSlot
|
|
slotIndex = memberReservedSlot(self.member) # noqa: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",
|
|
[BuiltinTypes[IDLBuiltinType.Types.undefined]],
|
|
None)
|
|
return result
|
|
if self.member.isMethod():
|
|
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):
|
|
"""
|
|
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):
|
|
"""Returns the alias set to store in the jitinfo. This may not be the
|
|
effective alias set the JIT uses, depending on whether we have enough
|
|
information about our args to allow the JIT to prove that effectful
|
|
argument conversions won't happen.
|
|
|
|
"""
|
|
dependsOn = self.member.dependsOn
|
|
assert dependsOn in IDLInterfaceMember.DependsOnValues
|
|
|
|
if dependsOn == "Nothing" or dependsOn == "DeviceState":
|
|
assert self.member.affects == "Nothing"
|
|
return "AliasNone"
|
|
|
|
if dependsOn == "DOMState":
|
|
assert self.member.affects == "Nothing"
|
|
return "AliasDOMSets"
|
|
|
|
return "AliasEverything"
|
|
|
|
@staticmethod
|
|
def getJSReturnTypeTag(t):
|
|
if t.nullable():
|
|
# Sometimes it might return null, sometimes not
|
|
return "JSVAL_TYPE_UNKNOWN"
|
|
if t.isUndefined():
|
|
# No return, every time
|
|
return "JSVAL_TYPE_UNDEFINED"
|
|
if t.isSequence():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isRecord():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isPromise():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isGeckoInterface():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isString():
|
|
return "JSVAL_TYPE_STRING"
|
|
if t.isEnum():
|
|
return "JSVAL_TYPE_STRING"
|
|
if t.isCallback():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isAny():
|
|
# The whole point is to return various stuff
|
|
return "JSVAL_TYPE_UNKNOWN"
|
|
if t.isObject():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isSpiderMonkeyInterface():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if t.isUnion():
|
|
u = t.unroll()
|
|
if u.hasNullableType:
|
|
# Might be null or not
|
|
return "JSVAL_TYPE_UNKNOWN"
|
|
return functools.reduce(CGMemberJITInfo.getSingleReturnType,
|
|
u.flatMemberTypes, "")
|
|
if t.isDictionary():
|
|
return "JSVAL_TYPE_OBJECT"
|
|
if 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, t):
|
|
type = CGMemberJITInfo.getJSReturnTypeTag(t)
|
|
if existingType == "":
|
|
# First element of the list; just return its type
|
|
return type
|
|
|
|
if type == existingType:
|
|
return existingType
|
|
if ((type == "JSVAL_TYPE_DOUBLE"
|
|
and existingType == "JSVAL_TYPE_INT32")
|
|
or (existingType == "JSVAL_TYPE_DOUBLE"
|
|
and type == "JSVAL_TYPE_INT32")):
|
|
# Promote INT32 to DOUBLE as needed
|
|
return "JSVAL_TYPE_DOUBLE"
|
|
# Different types
|
|
return "JSVAL_TYPE_UNKNOWN"
|
|
|
|
@staticmethod
|
|
def getJSArgType(t):
|
|
assert not t.isUndefined()
|
|
if t.nullable():
|
|
# Sometimes it might return null, sometimes not
|
|
return 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()
|
|
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, t):
|
|
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):
|
|
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):
|
|
# 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, config):
|
|
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):
|
|
return self.cgRoot.define()
|
|
|
|
|
|
def convertConstIDLValueToRust(value):
|
|
tag = value.type.tag()
|
|
if tag in [IDLType.Tags.int8, IDLType.Tags.uint8,
|
|
IDLType.Tags.int16, IDLType.Tags.uint16,
|
|
IDLType.Tags.int32, IDLType.Tags.uint32,
|
|
IDLType.Tags.int64, IDLType.Tags.uint64,
|
|
IDLType.Tags.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):
|
|
CGThing.__init__(self)
|
|
self.constant = constant
|
|
|
|
def define(self):
|
|
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, descriptorProvider):
|
|
if type.isGeckoInterface():
|
|
name = type.inner.identifier.name
|
|
typeName = descriptorProvider.getDescriptor(name).returnType
|
|
elif type.isEnum():
|
|
name = type.inner.identifier.name
|
|
typeName = name
|
|
elif type.isDictionary():
|
|
name = type.name
|
|
typeName = name
|
|
if containsDomInterface(type):
|
|
typeName += "<D>"
|
|
elif type.isSequence() or type.isRecord():
|
|
name = type.name
|
|
inner = getUnionTypeTemplateVars(innerContainerType(type), descriptorProvider)
|
|
typeName = wrapInNativeContainerType(type, CGGeneric(inner["typeName"])).define()
|
|
elif type.isByteString():
|
|
name = type.name
|
|
typeName = "ByteString"
|
|
elif type.isDOMString():
|
|
name = type.name
|
|
typeName = "DOMString"
|
|
elif type.isUSVString():
|
|
name = type.name
|
|
typeName = "USVString"
|
|
elif type.isPrimitive():
|
|
name = type.name
|
|
typeName = builtinNames[type.tag()]
|
|
elif type.isObject():
|
|
name = type.name
|
|
typeName = "Heap<*mut JSObject>"
|
|
elif is_typed_array(type):
|
|
name = type.name
|
|
typeName = f"typedarray::Heap{name}"
|
|
elif type.isCallback():
|
|
name = type.name
|
|
typeName = f"{name}<D>"
|
|
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, ty):
|
|
return name == "Clone" and containsDomInterface(ty)
|
|
|
|
|
|
class CGUnionStruct(CGThing):
|
|
def __init__(self, type, descriptorProvider, config):
|
|
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):
|
|
for t in self.type.flatMemberTypes:
|
|
if type_needs_tracing(t):
|
|
return True
|
|
return False
|
|
|
|
def manualImplClone(self, templateVars):
|
|
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, templateVars):
|
|
if t == "Clone":
|
|
return self.manualImplClone(templateVars)
|
|
raise f"Don't know how to impl {t} for union"
|
|
|
|
def define(self):
|
|
def getTypeWrapper(t):
|
|
if type_needs_tracing(t):
|
|
return "RootedTraceableBox"
|
|
if t.isCallback():
|
|
return "Rc"
|
|
return ""
|
|
|
|
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, descriptorProvider):
|
|
assert not type.nullable()
|
|
assert not type.hasNullableType
|
|
|
|
CGThing.__init__(self)
|
|
self.type = type
|
|
self.descriptorProvider = descriptorProvider
|
|
|
|
def membersNeedTracing(self):
|
|
for t in self.type.flatMemberTypes:
|
|
if type_needs_tracing(t):
|
|
return True
|
|
return False
|
|
|
|
def from_jsval(self):
|
|
memberTypes = self.type.flatMemberTypes
|
|
names = []
|
|
conversions = []
|
|
|
|
def get_name(memberType):
|
|
if self.type.isGeckoInterface():
|
|
return memberType.inner.identifier.name
|
|
|
|
return memberType.name
|
|
|
|
def get_match(name):
|
|
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 = 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 = 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()]
|
|
if stringTypes or numericTypes or booleanTypes:
|
|
assert len(stringTypes) <= 1
|
|
assert len(numericTypes) <= 1
|
|
assert len(booleanTypes) <= 1
|
|
|
|
def getStringOrPrimitiveConversion(memberType):
|
|
typename = get_name(memberType)
|
|
return CGGeneric(get_match(typename))
|
|
other = []
|
|
stringConversion = list(map(getStringOrPrimitiveConversion, stringTypes))
|
|
numericConversion = list(map(getStringOrPrimitiveConversion, numericTypes))
|
|
booleanConversion = list(map(getStringOrPrimitiveConversion, booleanTypes))
|
|
if stringConversion:
|
|
if booleanConversion:
|
|
other.append(CGIfWrapper("value.get().is_boolean()", booleanConversion[0]))
|
|
if numericConversion:
|
|
other.append(CGIfWrapper("value.get().is_number()", numericConversion[0]))
|
|
other.append(stringConversion[0])
|
|
elif numericConversion:
|
|
if booleanConversion:
|
|
other.append(CGIfWrapper("value.get().is_boolean()", booleanConversion[0]))
|
|
other.append(numericConversion[0])
|
|
else:
|
|
assert booleanConversion
|
|
other.append(booleanConversion[0])
|
|
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):
|
|
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):
|
|
from_jsval = self.from_jsval()
|
|
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, visibility):
|
|
self.name = name
|
|
self.visibility = visibility
|
|
|
|
def declare(self, cgClass):
|
|
assert False
|
|
|
|
def define(self, cgClass):
|
|
assert False
|
|
|
|
|
|
class ClassBase(ClassItem):
|
|
def __init__(self, name, visibility='pub'):
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def declare(self, cgClass):
|
|
return f'{self.visibility} {self.name}'
|
|
|
|
def define(self, cgClass):
|
|
# Only in the header
|
|
return ''
|
|
|
|
|
|
class ClassMethod(ClassItem):
|
|
def __init__(self, name, returnType, args, inline=False, static=False,
|
|
virtual=False, const=False, bodyInHeader=False,
|
|
templateArgs=None, visibility='public', body=None,
|
|
breakAfterReturnDecl="\n", unsafe=False,
|
|
breakAfterSelf="\n", override=False):
|
|
"""
|
|
override indicates whether to flag the method as MOZ_OVERRIDE
|
|
"""
|
|
assert not override or virtual
|
|
assert not (override and static)
|
|
self.returnType = returnType
|
|
self.args = args
|
|
self.inline = 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):
|
|
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):
|
|
# Override me or pass a string to constructor
|
|
assert self.body is not None
|
|
return self.body
|
|
|
|
def declare(self, cgClass):
|
|
|
|
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):
|
|
pass
|
|
|
|
|
|
class ClassConstructor(ClassItem):
|
|
"""
|
|
Used for adding a constructor to a CGClass.
|
|
|
|
args is a list of Argument objects that are the arguments taken by the
|
|
constructor.
|
|
|
|
inline should be True if the constructor should be marked inline.
|
|
|
|
bodyInHeader should be True if the body should be placed in the class
|
|
declaration in the header.
|
|
|
|
visibility determines the visibility of the constructor (public,
|
|
protected, private), defaults to private.
|
|
|
|
explicit should be True if the constructor should be marked explicit.
|
|
|
|
baseConstructors is a list of strings containing calls to base constructors,
|
|
defaults to None.
|
|
|
|
body contains a string with the code for the constructor, defaults to empty.
|
|
"""
|
|
def __init__(self, args, inline=False, bodyInHeader=False,
|
|
visibility="priv", explicit=False, baseConstructors=None,
|
|
body=""):
|
|
self.args = args
|
|
self.inline = False
|
|
self.bodyInHeader = bodyInHeader
|
|
self.explicit = explicit
|
|
self.baseConstructors = baseConstructors or []
|
|
self.body = body
|
|
ClassItem.__init__(self, None, visibility)
|
|
|
|
def getDecorators(self, declaring):
|
|
decorators = []
|
|
if self.explicit:
|
|
decorators.append('explicit')
|
|
if self.inline and declaring:
|
|
decorators.append('inline')
|
|
if decorators:
|
|
return f'{" ".join(decorators)} '
|
|
return ''
|
|
|
|
def getInitializationList(self, cgClass):
|
|
items = [str(c) for c in self.baseConstructors]
|
|
for m in cgClass.members:
|
|
if not m.static:
|
|
initialize = m.body
|
|
if initialize:
|
|
items.append(f"{m.name}({initialize})")
|
|
|
|
if len(items) > 0:
|
|
joinedItems = ",\n ".join(items)
|
|
return f'\n : {joinedItems}'
|
|
return ''
|
|
|
|
def getBody(self, cgClass):
|
|
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):
|
|
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):
|
|
if self.bodyInHeader:
|
|
return ''
|
|
|
|
args = ', '.join([a.define() for a in self.args])
|
|
|
|
body = f' {self.getBody()}'
|
|
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, type, visibility="priv", static=False,
|
|
body=None):
|
|
self.type = type
|
|
self.static = static
|
|
self.body = body
|
|
ClassItem.__init__(self, name, visibility)
|
|
|
|
def declare(self, cgClass):
|
|
return f'{self.visibility} {self.name}: {self.type},\n'
|
|
|
|
def define(self, cgClass):
|
|
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, bases=[], members=[], constructors=[],
|
|
destructor=None, methods=[],
|
|
typedefs=[], enums=[], unions=[], templateArgs=[],
|
|
templateSpecialization=[],
|
|
disallowCopyConstruction=False, indent='',
|
|
decorators='',
|
|
extradeclarations=''):
|
|
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):
|
|
className = self.name
|
|
if self.templateSpecialization:
|
|
className = f"{className}<{', '.join([str(a) for a in self.templateSpecialization])}>"
|
|
return className
|
|
|
|
def define(self):
|
|
result = ''
|
|
if self.templateArgs:
|
|
templateArgs = [a.declare() for a in self.templateArgs]
|
|
templateArgs = templateArgs[len(self.templateSpecialization):]
|
|
result = 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, memberList):
|
|
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(object):
|
|
def __init__(self):
|
|
self.visibility = "private"
|
|
|
|
def declare(self, cgClass):
|
|
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).
|
|
"""
|
|
def __init__(self, descriptor, operation):
|
|
nativeName = MakeNativeName(descriptor.binaryNameFor(operation))
|
|
operation = descriptor.operations[operation]
|
|
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):
|
|
args = [(a, process_arg(a.identifier.name, a)) for a in self.arguments]
|
|
return args
|
|
|
|
def wrap_return_value(self):
|
|
if not self.idlNode.isGetter() or self.templateValues is None:
|
|
return ""
|
|
|
|
wrap = CGGeneric(wrapForType(**self.templateValues))
|
|
wrap = CGIfWrapper("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, templateValues=None):
|
|
self.templateValues = templateValues
|
|
CGProxySpecialOperation.__init__(self, descriptor, 'IndexedGetter')
|
|
|
|
|
|
class CGProxyIndexedSetter(CGProxySpecialOperation):
|
|
"""
|
|
Class to generate a call to an indexed setter.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
CGProxySpecialOperation.__init__(self, descriptor, 'IndexedSetter')
|
|
|
|
|
|
class CGProxyNamedOperation(CGProxySpecialOperation):
|
|
"""
|
|
Class to generate a call to a named operation.
|
|
"""
|
|
def __init__(self, descriptor, name):
|
|
CGProxySpecialOperation.__init__(self, descriptor, name)
|
|
|
|
def define(self):
|
|
# 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, templateValues=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):
|
|
CGProxyNamedGetter.__init__(self, descriptor)
|
|
|
|
|
|
class CGProxyNamedSetter(CGProxyNamedOperation):
|
|
"""
|
|
Class to generate a call to a named setter.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
CGProxySpecialOperation.__init__(self, descriptor, 'NamedSetter')
|
|
|
|
|
|
class CGProxyNamedDeleter(CGProxyNamedOperation):
|
|
"""
|
|
Class to generate a call to a named deleter.
|
|
"""
|
|
def __init__(self, descriptor):
|
|
CGProxySpecialOperation.__init__(self, descriptor, 'NamedDeleter')
|
|
|
|
def define(self):
|
|
# 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):
|
|
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):
|
|
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):
|
|
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):
|
|
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):
|
|
return CGGeneric(self.getBody())
|
|
|
|
|
|
class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod):
|
|
def __init__(self, descriptor):
|
|
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):
|
|
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):
|
|
return CGGeneric(self.getBody())
|
|
|
|
|
|
class CGDOMJSProxyHandler_delete(CGAbstractExternMethod):
|
|
def __init__(self, descriptor):
|
|
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):
|
|
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):
|
|
return CGGeneric(self.getBody())
|
|
|
|
|
|
class CGDOMJSProxyHandler_ownPropertyKeys(CGAbstractExternMethod):
|
|
def __init__(self, descriptor):
|
|
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):
|
|
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):
|
|
return CGGeneric(self.getBody())
|
|
|
|
|
|
class CGDOMJSProxyHandler_getOwnEnumerablePropertyKeys(CGAbstractExternMethod):
|
|
def __init__(self, descriptor):
|
|
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):
|
|
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):
|
|
return CGGeneric(self.getBody())
|
|
|
|
|
|
class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod):
|
|
def __init__(self, descriptor):
|
|
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):
|
|
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):
|
|
return CGGeneric(self.getBody())
|
|
|
|
|
|
class CGDOMJSProxyHandler_get(CGAbstractExternMethod):
|
|
def __init__(self, descriptor):
|
|
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):
|
|
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):
|
|
return CGGeneric(self.getBody())
|
|
|
|
|
|
class CGDOMJSProxyHandler_getPrototype(CGAbstractExternMethod):
|
|
def __init__(self, descriptor):
|
|
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):
|
|
return dedent(
|
|
"""
|
|
let cx = SafeJSContext::from_ptr(cx);
|
|
proxyhandler::maybe_cross_origin_get_prototype::<D>(cx, proxy, GetProtoObject::<D>, proto)
|
|
""")
|
|
|
|
def definition_body(self):
|
|
return CGGeneric(self.getBody())
|
|
|
|
|
|
class CGDOMJSProxyHandler_className(CGAbstractExternMethod):
|
|
def __init__(self, descriptor):
|
|
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):
|
|
return str_to_cstr_ptr(self.descriptor.name)
|
|
|
|
def definition_body(self):
|
|
return CGGeneric(self.getBody())
|
|
|
|
|
|
class CGAbstractClassHook(CGAbstractExternMethod):
|
|
"""
|
|
Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw
|
|
'this' unwrapping as it assumes that the unwrapped type is always known.
|
|
"""
|
|
def __init__(self, descriptor, name, returnType, args, doesNotPanic=False):
|
|
CGAbstractExternMethod.__init__(self, descriptor, name, returnType,
|
|
args, templateArgs=['D: DomTypes'])
|
|
|
|
def definition_body_prologue(self):
|
|
return CGGeneric(f"""
|
|
let this = native_from_object_static::<{self.descriptor.concreteType}>(obj).unwrap();
|
|
""")
|
|
|
|
def definition_body(self):
|
|
return CGList([
|
|
self.definition_body_prologue(),
|
|
self.generate_code(),
|
|
])
|
|
|
|
def generate_code(self):
|
|
raise NotImplementedError # Override me!
|
|
|
|
|
|
def finalizeHook(descriptor, hookName, context):
|
|
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):
|
|
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):
|
|
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, constructor=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):
|
|
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))
|
|
|
|
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):
|
|
args = [Argument('*mut GCContext', '_cx'), Argument('*mut JSObject', 'obj')]
|
|
CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME,
|
|
'void', args)
|
|
|
|
def generate_code(self):
|
|
return CGGeneric(finalizeHook(self.descriptor, self.name, self.args[0].name))
|
|
|
|
|
|
class CGDOMJSProxyHandlerDOMClass(CGThing):
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
self.descriptor = descriptor
|
|
|
|
def define(self):
|
|
return 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, descriptorProvider):
|
|
CGThing.__init__(self)
|
|
|
|
def attribute_arguments(attribute_type, argument=None, inRealm=False, canGc=False, retval=False):
|
|
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():
|
|
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()):
|
|
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, leadingComma=True):
|
|
keywords = {"async"}
|
|
prefix = "" if not leadingComma else ", "
|
|
return prefix + ", ".join(
|
|
f"{name if name not in keywords else f'r#{name}'}: {type_}"
|
|
for name, type_ in arguments
|
|
)
|
|
|
|
def contains_unsafe_arg(arguments):
|
|
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, baseName=None):
|
|
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):
|
|
return self.cgRoot.define()
|
|
|
|
|
|
class CGWeakReferenceableTrait(CGThing):
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
assert descriptor.weakReferenceable
|
|
self.code = f"impl WeakReferenceable for {descriptor.interface.identifier.name} {{}}"
|
|
|
|
def define(self):
|
|
return self.code
|
|
|
|
|
|
class CGInitStatics(CGThing):
|
|
def __init__(self, descriptor):
|
|
CGThing.__init__(self)
|
|
|
|
def internal(method):
|
|
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):
|
|
return self.code
|
|
|
|
|
|
class CGDescriptor(CGThing):
|
|
def __init__(self, descriptor, config, soleDescriptor):
|
|
CGThing.__init__(self)
|
|
|
|
assert not descriptor.concrete or not descriptor.interface.isCallback()
|
|
|
|
reexports = []
|
|
|
|
def reexportedName(name):
|
|
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):
|
|
return self.cgRoot.define()
|
|
|
|
|
|
class CGNonNamespacedEnum(CGThing):
|
|
def __init__(self, enumName, names, first, comment="", deriving="", repr=""):
|
|
# 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):
|
|
return self.node.define()
|
|
|
|
|
|
class CGDictionary(CGThing):
|
|
def __init__(self, dictionary, descriptorProvider, config):
|
|
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 = [
|
|
(member,
|
|
getJSToNativeConversionInfo(member.type,
|
|
descriptorProvider,
|
|
isMember="Dictionary",
|
|
defaultValue=member.defaultValue,
|
|
exceptionCode="return Err(());\n"))
|
|
for member in dictionary.members]
|
|
|
|
def define(self):
|
|
if not self.generatable:
|
|
return ""
|
|
return f"{self.struct()}\n{self.impl()}"
|
|
|
|
def manualImplClone(self):
|
|
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):
|
|
if t == "Clone":
|
|
return self.manualImplClone()
|
|
raise f"Don't know how to impl {t} for dicts."
|
|
|
|
def struct(self):
|
|
d = self.dictionary
|
|
if d.parent:
|
|
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):
|
|
d = self.dictionary
|
|
if d.parent:
|
|
initParent = (
|
|
"{\n"
|
|
f" match {self.makeModuleName(d.parent)}::{self.makeClassName(d.parent)}::new(cx, val)? {{\n"
|
|
" ConversionResult::Success(v) => v,\n"
|
|
" ConversionResult::Failure(error) => {\n"
|
|
" throw_type_error(*cx, &error);\n"
|
|
" return Err(());\n"
|
|
" }\n"
|
|
" }\n"
|
|
"}"
|
|
)
|
|
else:
|
|
initParent = ""
|
|
|
|
def memberInit(memberInfo, isInitial):
|
|
member, _ = memberInfo
|
|
name = self.makeMemberName(member.identifier.name)
|
|
conversion = self.getMemberConversion(memberInfo, member.type)
|
|
if isInitial:
|
|
return CGGeneric(f"{name}: {conversion.define()},\n")
|
|
return CGGeneric(f"dictionary.{name} = {conversion.define()};\n")
|
|
|
|
def varInsert(varName, dictionaryName):
|
|
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(cx, obj.handle(), "{dictionaryName}", {varName}_js.handle()).unwrap();')
|
|
return CGGeneric(insertion)
|
|
|
|
def memberInsert(memberInfo):
|
|
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(m, True) for m 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) \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)\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):
|
|
return type_needs_tracing(self.dictionary)
|
|
|
|
@staticmethod
|
|
def makeDictionaryName(dictionary):
|
|
if isinstance(dictionary, IDLWrapperType):
|
|
return CGDictionary.makeDictionaryName(dictionary.inner)
|
|
else:
|
|
return dictionary.identifier.name
|
|
|
|
def makeClassName(self, dictionary):
|
|
return self.makeDictionaryName(dictionary)
|
|
|
|
@staticmethod
|
|
def makeModuleName(dictionary):
|
|
return getModuleFromObject(dictionary)
|
|
|
|
def getMemberType(self, memberInfo):
|
|
member, info = memberInfo
|
|
declType = info.declType
|
|
if member.optional and not member.defaultValue:
|
|
declType = CGWrapper(info.declType, pre="Option<", post=">")
|
|
return declType.define()
|
|
|
|
def getMemberConversion(self, memberInfo, memberType):
|
|
def indent(s):
|
|
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(), CanGc::note())? && !rval.is_undefined() {\n"
|
|
f"{indent(conversion)}\n"
|
|
" } else {\n"
|
|
f"{indent(default)}\n"
|
|
" }\n"
|
|
"}")
|
|
|
|
return CGGeneric(conversion)
|
|
|
|
def makeEmpty(self):
|
|
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:
|
|
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):
|
|
if dictionary.parent:
|
|
if self.hasRequiredFields(dictionary.parent):
|
|
return True
|
|
for member in dictionary.members:
|
|
if not member.optional:
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def makeMemberName(name):
|
|
# Can't use Rust keywords as member names.
|
|
if name in RUST_KEYWORDS:
|
|
return f"{name}_"
|
|
return name
|
|
|
|
@staticmethod
|
|
def getDictionaryDependencies(dictionary):
|
|
deps = set()
|
|
if dictionary.parent:
|
|
deps.add(dictionary.parent)
|
|
for member in dictionary.members:
|
|
if member.type.isDictionary():
|
|
deps.add(member.type.unroll().inner)
|
|
return deps
|
|
|
|
|
|
class CGInitAllStatics(CGAbstractMethod):
|
|
def __init__(self, config):
|
|
docs = "Initialize the static data used by the SpiderMonkey DOM bindings to implement JS interfaces."
|
|
descriptors = (config.getDescriptors(isCallback=False, register=True)
|
|
+ config.getDescriptors(isCallback=True, hasInterfaceObject=True, register=True))
|
|
CGAbstractMethod.__init__(self, None, 'InitAllStatics', 'void', [],
|
|
pub=True, docs=docs, templateArgs=["D: DomTypes"])
|
|
self.descriptors = descriptors
|
|
|
|
def definition_body(self):
|
|
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):
|
|
docs = "Create the global vtables used by the generated DOM bindings to implement JS proxies."
|
|
CGAbstractMethod.__init__(self, None, 'RegisterProxyHandlers', 'void', [],
|
|
pub=True, docs=docs, templateArgs=["D: DomTypes"])
|
|
self.descriptors = descriptors
|
|
|
|
def definition_body(self):
|
|
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):
|
|
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):
|
|
return self.root.define()
|
|
|
|
|
|
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.
|
|
"""
|
|
def __init__(self, config, prefix, webIDLFile):
|
|
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;"
|
|
),
|
|
]
|
|
|
|
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):
|
|
if not self.root:
|
|
return None
|
|
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).
|
|
"""
|
|
def __init__(self, config, prefix, webIDLFile):
|
|
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 = [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(x.interface)], "\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):
|
|
if not self.root:
|
|
return None
|
|
return stripTrailingWhitespace(self.root.define())
|
|
|
|
|
|
def type_needs_tracing(t):
|
|
assert isinstance(t, IDLObject), (t, type(t))
|
|
|
|
if t.isType():
|
|
if isinstance(t, IDLWrapperType):
|
|
return type_needs_tracing(t.inner)
|
|
|
|
if t.nullable():
|
|
return type_needs_tracing(t.inner)
|
|
|
|
if t.isAny():
|
|
return True
|
|
|
|
if t.isObject():
|
|
return True
|
|
|
|
if t.isSequence():
|
|
return type_needs_tracing(t.inner)
|
|
|
|
if t.isUnion():
|
|
return any(type_needs_tracing(member) for member in t.flatMemberTypes)
|
|
|
|
if is_typed_array(t):
|
|
return True
|
|
|
|
return False
|
|
|
|
if t.isDictionary():
|
|
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))
|
|
|
|
|
|
def is_typed_array(t):
|
|
assert isinstance(t, IDLObject), (t, type(t))
|
|
|
|
return t.isTypedArray() or t.isArrayBuffer() or t.isArrayBufferView()
|
|
|
|
|
|
def type_needs_auto_root(t):
|
|
"""
|
|
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
|
|
|
|
|
|
def argument_type(descriptorProvider, ty, optional=False, defaultValue=None, variadic=False):
|
|
info = getJSToNativeConversionInfo(
|
|
ty, descriptorProvider, isArgument=True,
|
|
isAutoRooted=type_needs_auto_root(ty))
|
|
declType = info.declType
|
|
|
|
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)
|
|
|
|
return declType.define()
|
|
|
|
|
|
def method_arguments(descriptorProvider, returnType, arguments, passJSBits=True, trailing=None,
|
|
inRealm=False, canGc=False):
|
|
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, rettype, infallible):
|
|
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, member, name, signature, extendedAttrs,
|
|
breakAfter=True, passJSBitsAsNeeded=True, visibility="public",
|
|
unsafe=False):
|
|
"""
|
|
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):
|
|
infallible = 'infallible' in self.extendedAttrs
|
|
typeDecl = return_type(self.descriptorProvider, type, infallible)
|
|
return typeDecl
|
|
|
|
def getArgs(self, returnType, argList):
|
|
return [Argument(arg[1], arg[0]) for arg in method_arguments(self.descriptorProvider,
|
|
returnType,
|
|
argList,
|
|
self.passJSBitsAsNeeded)]
|
|
|
|
|
|
class CGCallback(CGClass):
|
|
def __init__(self, idlObject, descriptorProvider, baseName, methods):
|
|
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, 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):
|
|
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):
|
|
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):
|
|
return self._deps
|
|
|
|
|
|
# We're always fallible
|
|
def callbackGetterName(attr, descriptor):
|
|
return f"Get{MakeNativeName(descriptor.binaryNameFor(attr.identifier.name))}"
|
|
|
|
|
|
def callbackSetterName(attr, descriptor):
|
|
return f"Set{MakeNativeName(descriptor.binaryNameFor(attr.identifier.name))}"
|
|
|
|
|
|
class CGCallbackFunction(CGCallback):
|
|
def __init__(self, callback, descriptorProvider):
|
|
CGCallback.__init__(self, callback, descriptorProvider,
|
|
"CallbackFunction<D>",
|
|
methods=[CallCallback(callback, descriptorProvider)])
|
|
|
|
def getConstructors(self):
|
|
return CGCallback.getConstructors(self)
|
|
|
|
|
|
class CGCallbackFunctionImpl(CGGeneric):
|
|
def __init__(self, callback):
|
|
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):
|
|
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):
|
|
pass
|
|
|
|
def isStatic(self):
|
|
return False
|
|
|
|
def isAttr(self):
|
|
return False
|
|
|
|
def isMethod(self):
|
|
return False
|
|
|
|
def getExtendedAttribute(self, name):
|
|
return None
|
|
|
|
|
|
class CallbackMember(CGNativeMember):
|
|
def __init__(self, sig, name, descriptorProvider, needThisHandling):
|
|
"""
|
|
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()
|
|
|
|
def getImpl(self):
|
|
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):
|
|
replacements = {
|
|
"val": "rval.handle()",
|
|
}
|
|
|
|
info = getJSToNativeConversionInfo(
|
|
self.retvalType,
|
|
self.descriptorProvider,
|
|
exceptionCode=self.exceptionCode,
|
|
isCallbackReturnValue="Callback",
|
|
# 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):
|
|
# 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 = [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()
|
|
argConversions = [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):
|
|
argConversions.insert(0, self.getArgcDecl(True))
|
|
elif self.argCount > 0:
|
|
argConversions.insert(0, self.getArgcDecl(False))
|
|
# And slap them together.
|
|
return CGList(argConversions, "\n\n").define() + "\n\n"
|
|
|
|
def getArgConversion(self, i, arg):
|
|
argval = arg.identifier.name
|
|
|
|
if arg.variadic:
|
|
argval = f"{argval}[idx].get()"
|
|
jsvalIndex = f"{i} + idx"
|
|
else:
|
|
jsvalIndex = f"{i}"
|
|
if arg.optional and not arg.defaultValue:
|
|
argval += ".unwrap()"
|
|
|
|
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'{arg.identifier.name}.is_some()', 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, argList):
|
|
args = CGNativeMember.getArgs(self, returnType, argList)
|
|
if not self.needThisHandling:
|
|
# Since we don't need this handling, we're the actual method that
|
|
# will be called, so we need an aRethrowExceptions argument.
|
|
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):
|
|
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):
|
|
if immutable:
|
|
return CGGeneric(f"let argc = {self.argCountStr};")
|
|
return CGGeneric(f"let mut argc = {self.argCountStr};")
|
|
|
|
@staticmethod
|
|
def ensureASCIIName(idlObject):
|
|
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, name, descriptorProvider, needThisHandling):
|
|
CallbackMember.__init__(self, sig, name, descriptorProvider,
|
|
needThisHandling)
|
|
|
|
def getRvalDecl(self):
|
|
if self.usingOutparam:
|
|
return ""
|
|
else:
|
|
return "rooted!(in(*cx) let mut rval = UndefinedValue());\n"
|
|
|
|
def getCall(self):
|
|
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, descriptorProvider):
|
|
self.callback = callback
|
|
CallbackMethod.__init__(self, callback.signatures()[0], "Call",
|
|
descriptorProvider, needThisHandling=True)
|
|
|
|
def getThisObj(self):
|
|
return "aThisObj.get()"
|
|
|
|
def getCallableDecl(self):
|
|
return "rooted!(in(*cx) let callable = ObjectValue(self.callback()));\n"
|
|
|
|
def getCallGuard(self):
|
|
if self.callback._treatNonObjectAsNull:
|
|
return "!IsCallable(self.callback()) || "
|
|
return ""
|
|
|
|
|
|
class CallbackOperationBase(CallbackMethod):
|
|
"""
|
|
Common class for implementing various callback operations.
|
|
"""
|
|
def __init__(self, signature, jsName, nativeName, descriptor, singleOperation):
|
|
self.singleOperation = singleOperation
|
|
self.methodName = jsName
|
|
CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation)
|
|
|
|
def getThisObj(self):
|
|
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):
|
|
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):
|
|
return ""
|
|
|
|
|
|
class CallbackOperation(CallbackOperationBase):
|
|
"""
|
|
Codegen actual WebIDL operations on callback interfaces.
|
|
"""
|
|
def __init__(self, method, signature, descriptor):
|
|
self.ensureASCIIName(method)
|
|
jsName = method.identifier.name
|
|
CallbackOperationBase.__init__(self, signature,
|
|
jsName,
|
|
MakeNativeName(descriptor.binaryNameFor(jsName)),
|
|
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, likeable, methodName):
|
|
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, iterable, methodName):
|
|
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):
|
|
return "_".join(m.group(0).upper() for m in re.finditer("[A-Z][a-z]*", s))
|
|
|
|
|
|
def process_arg(expr, arg):
|
|
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):
|
|
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):
|
|
mods = [
|
|
"crate::dom::bindings::codegen",
|
|
"crate::script_runtime::JSContext",
|
|
"js::rust::HandleObject",
|
|
]
|
|
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):
|
|
pairs = []
|
|
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))
|
|
mappings = [
|
|
CGGeneric(f'"{pair[0]}": "codegen::Bindings::{pair[1]}'
|
|
f'::{pair[2]}::DefineDOMInterface::<crate::DomTypeHolder>"')
|
|
for pair in pairs
|
|
]
|
|
return CGWrapper(
|
|
CGList(mappings, ",\n"),
|
|
pre="{\n",
|
|
post="\n}\n")
|
|
|
|
@staticmethod
|
|
def PrototypeList(config):
|
|
# 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):
|
|
# 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):
|
|
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):
|
|
|
|
def leafModule(d):
|
|
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):
|
|
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:
|
|
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):
|
|
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:
|
|
hierarchy[descriptor.interface.parent.identifier.name].append(name)
|
|
|
|
typeIdCode = []
|
|
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):
|
|
# 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):
|
|
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):
|
|
|
|
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):
|
|
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):
|
|
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):
|
|
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))))
|