Generate stub implementations for unimplemented dom interfaces

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-03-10 22:04:59 +01:00
parent d42fe20403
commit 0db032a4c9
5 changed files with 89 additions and 11 deletions

View file

@ -99,6 +99,15 @@ pub struct Preferences {
pub dom_serviceworker_timeout_seconds: i64,
pub dom_servo_helpers_enabled: bool,
pub dom_servoparser_async_html_tokenizer_enabled: bool,
/// When enabled, Servo will create stub implementations for all DOM methods and attributes
/// that are known but not implemented. When a page tries to use one of these then a warning
/// with the method name will be logged and some appropriate default behaviour will be chosen:
/// * Getting the value of a stub attribute will return `undefined`
/// * Setting the value of a stub attribute will do nothing
/// * Calling a stub method will return `undefined`
///
/// The warning is dispatched with target `dom-stubs`. Set `RUST_LOG=error,dom-stubs=warn` to see it.
pub dom_stub_unimplemented_features: bool,
pub dom_svg_enabled: bool,
pub dom_testable_crash_enabled: bool,
pub dom_testbinding_enabled: bool,
@ -272,6 +281,7 @@ impl Preferences {
dom_serviceworker_timeout_seconds: 60,
dom_servo_helpers_enabled: false,
dom_servoparser_async_html_tokenizer_enabled: false,
dom_stub_unimplemented_features: false,
dom_svg_enabled: false,
dom_testable_crash_enabled: false,
dom_testbinding_enabled: false,

View file

@ -417,10 +417,11 @@ class CGMethodCall(CGThing):
def getPerSignatureCall(signature, argConversionStartsAt=0):
signatureIndex = signatures.index(signature)
isUnimplemented = method.getExtendedAttribute("Unimplemented") is not None
return CGPerSignatureCall(signature[0], argsPre, signature[1],
f"{nativeMethodName}{'_' * signatureIndex}",
static, descriptor,
method, argConversionStartsAt)
method, isUnimplemented, argConversionStartsAt)
if len(signatures) == 1:
# Special case: we can just do a per-signature method call
@ -1614,11 +1615,23 @@ class PropertyDefiner:
assert attr[0] is not None
return attr[0]
@staticmethod
def hasAttr(member, name) -> bool:
attr = member.getExtendedAttribute(name)
return attr is not None
@staticmethod
def getControllingCondition(interfaceMember, descriptor):
prefCondition = PropertyDefiner.getStringAttr(interfaceMember, "Pref")
isUnimplemented = PropertyDefiner.hasAttr(interfaceMember, "Unimplemented")
if isUnimplemented:
if prefCondition is not None:
raise TypeError("Cannot preference-gate unimplemented features")
prefCondition = "dom_stub_unimplemented_features"
return MemberCondition(
PropertyDefiner.getStringAttr(interfaceMember,
"Pref"),
prefCondition,
PropertyDefiner.getStringAttr(interfaceMember,
"Func"),
interfaceMember.exposureSet,
@ -2801,7 +2814,10 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs,
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()]
nonConstMembers = [
m for m in descriptor.interface.members
if not m.isConst() and not m.getExtendedAttribute("Unimplemented")
]
ctor = descriptor.interface.ctor()
if (
nonConstMembers
@ -3928,7 +3944,7 @@ def needCx(returnType, arguments, considerTypes):
class CGCallGenerator(CGThing):
"""
A class to generate an actual call to a C++ object. Assumes that the C++
A class to generate an actual call to a Rust object. Assumes that the Rust
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
@ -3936,7 +3952,7 @@ class CGCallGenerator(CGThing):
"""
def __init__(self, errorResult, arguments, argsPre, returnType,
extendedAttributes, descriptor, nativeMethodName,
static, object="this", hasCEReactions=False):
static, isUnimplemented: bool, object="this", hasCEReactions=False):
CGThing.__init__(self)
assert errorResult is None or isinstance(errorResult, str)
@ -3974,6 +3990,17 @@ class CGCallGenerator(CGThing):
# Build up our actual call
self.cgRoot = CGList([], "\n")
if isUnimplemented:
# Generate a stub implementation that logs a warning and does nothing else
logMessage = (
f"log::warn!(target: \"dom-stubs\", \""
f"Attempt to use unimplemented method or attribute "
f"{descriptor.interface.identifier.name}::{nativeMethodName}"
f"\");"
)
self.cgRoot.append(CGGeneric(logMessage))
return
if rootType:
self.cgRoot.append(CGList([
CGGeneric("rooted!(in(*cx) let mut retval: "),
@ -4045,7 +4072,7 @@ class CGPerSignatureCall(CGThing):
# there.
def __init__(self, returnType, argsPre, arguments, nativeMethodName, static,
descriptor, idlNode, argConversionStartsAt=0,
descriptor, idlNode, isUnimplemented: bool, argConversionStartsAt=0,
getter=False, setter=False):
CGThing.__init__(self)
self.returnType = returnType
@ -4057,6 +4084,8 @@ class CGPerSignatureCall(CGThing):
self.argsPre = argsPre
self.arguments = arguments
self.argCount = len(arguments)
self.isUnimplemented = isUnimplemented
cgThings = []
cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgs(),
self.getArgc(), self.descriptor,
@ -4083,7 +4112,7 @@ class CGPerSignatureCall(CGThing):
errorResult,
self.getArguments(), self.argsPre, returnType,
self.extendedAttributes, descriptor, nativeMethodName,
static, hasCEReactions=hasCEReactions))
static, isUnimplemented, hasCEReactions=hasCEReactions))
self.cgRoot = CGList(cgThings, "\n")
@ -4100,6 +4129,14 @@ class CGPerSignatureCall(CGThing):
return 'infallible' not in self.extendedAttributes
def wrap_return_value(self):
if self.isUnimplemented:
# Unimplemented methods don't actually compute a result type, so we stub
# it out here
return (
"args.rval().set(UndefinedValue());\n"
"return true;"
)
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
@ -4170,9 +4207,10 @@ class CGGetterCall(CGPerSignatureCall):
getter.
"""
def __init__(self, argsPre, returnType, nativeMethodName, descriptor, attr):
isUnimplemented = attr.getExtendedAttribute("Unimplemented") is not None
CGPerSignatureCall.__init__(self, returnType, argsPre, [],
nativeMethodName, attr.isStatic(), descriptor,
attr, getter=True)
attr, isUnimplemented, getter=True)
class FakeArgument():
@ -4197,9 +4235,10 @@ class CGSetterCall(CGPerSignatureCall):
setter.
"""
def __init__(self, argsPre, argType, nativeMethodName, descriptor, attr):
isUnimplemented = attr.getExtendedAttribute("Unimplemented") is not None
CGPerSignatureCall.__init__(self, None, argsPre,
[FakeArgument(argType, attr, allowTreatNonObjectAsNull=True)],
nativeMethodName, attr.isStatic(), descriptor, attr,
nativeMethodName, attr.isStatic(), descriptor, attr, isUnimplemented,
setter=True)
def wrap_return_value(self):
@ -5826,8 +5865,9 @@ class CGProxySpecialOperation(CGPerSignatureCall):
# We pass len(arguments) as the final argument so that the
# CGPerSignatureCall won't do any argument conversion of its own.
isUnimplemented = False
CGPerSignatureCall.__init__(self, returnType, "", arguments, nativeName,
False, descriptor, operation,
False, descriptor, operation, isUnimplemented,
len(arguments))
if operation.isSetter():
@ -6687,6 +6727,11 @@ class CGInterfaceTrait(CGThing):
def members():
for m in descriptor.interface.members:
if m.getExtendedAttribute("Unimplemented"):
# Unimplemented methods or attributes generate a stub implementation and should
# therefore not be part of the trait
continue
if (m.isMethod()
and not m.isMaplikeOrSetlikeOrIterableMethod()
and (not m.isIdentifierLess() or (m.isStringifier() and not m.underlyingAttr))

View file

@ -5939,6 +5939,7 @@ class IDLAttribute(IDLInterfaceMember):
or identifier == "BinaryName"
or identifier == "NonEnumerable"
or identifier == "BindingTemplate"
or identifier == "Unimplemented"
):
# Known attributes that we don't need to do anything with here
pass
@ -7018,6 +7019,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
or identifier == "NonEnumerable"
or identifier == "Unexposed"
or identifier == "WebExtensionStub"
or identifier == "Unimplemented"
):
# Known attributes that we don't need to do anything with here
pass

20
third_party/WebIDL/dom-stubs.patch vendored Normal file
View file

@ -0,0 +1,20 @@
diff --git a/third_party/WebIDL/WebIDL.py b/third_party/WebIDL/WebIDL.py
index 40e118e378..437e3640a6 100644
--- a/third_party/WebIDL/WebIDL.py
+++ b/third_party/WebIDL/WebIDL.py
@@ -5938,6 +5938,7 @@ class IDLAttribute(IDLInterfaceMember):
or identifier == "BinaryName"
or identifier == "NonEnumerable"
or identifier == "BindingTemplate"
+ or identifier == "Unimplemented"
):
# Known attributes that we don't need to do anything with here
pass
@@ -7017,6 +7018,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
or identifier == "NonEnumerable"
or identifier == "Unexposed"
or identifier == "WebExtensionStub"
+ or identifier == "Unimplemented"
):
# Known attributes that we don't need to do anything with here
pass

View file

@ -8,6 +8,7 @@ patch < like-as-iterable.patch
patch < builtin-array.patch
patch < array-type.patch
patch < transferable.patch
patch < dom-stubs.patch
wget https://hg.mozilla.org/mozilla-central/archive/tip.zip/dom/bindings/parser/tests/ -O tests.zip
rm -r tests