bindings: Fix support for interface members in setlike/maplike. (#35651)

#30151 added support for setlike and maplike declarations in WebIDL, but
the tests only validated if generator code worked with 'DOMString' as
the key type. The support for interface type as members was broken as
`DomRoot<T>` didn't satify the bounds `Eq` and `Hash` needed by the
`Key` and `Value` types in `Setlike` and `Maplike` traits respectively.
In addition, the splitting of bindings into a separate 'script_bindings'
crate had also broken support for this in CodegenRust.py, as the types
used within the definition of `DomTraits` were not referenced using
`Self::`.

This patch fixes the WebIDL code generator by doing a simple string
replacement on the return value of `getRetvalDeclarationForType` so that
the proper `Self::` is used. I'm not not sure if there is a better
approach to this as it seems most logic in CodegenRust.py uses the `D::`
prefix that is expected to be available only when compiling `script`
crate and not `script_bindings`.

This patch also adds the missing trait implementations for `DomRoot` and
ensures that the generated code works for both members of primitive and
interface types by splitting the existing `TestBinding{Map,Set}Like`
interfaces into `TestBinding{Map,Set}LikeWith{Primitive,Interface}`
tests.

Fixes #35542.

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
Mukilan Thiyagarajan 2025-02-27 12:34:11 +05:30 committed by GitHub
parent 1d62776102
commit 3cf4ef61ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 570 additions and 266 deletions

View file

@ -544,10 +544,12 @@ pub(crate) mod svggraphicselement;
pub(crate) mod svgsvgelement;
pub(crate) mod testbinding;
pub(crate) mod testbindingiterable;
pub(crate) mod testbindingmaplike;
pub(crate) mod testbindingmaplikewithinterface;
pub(crate) mod testbindingmaplikewithprimitive;
pub(crate) mod testbindingpairiterable;
pub(crate) mod testbindingproxy;
pub(crate) mod testbindingsetlike;
pub(crate) mod testbindingsetlikewithinterface;
pub(crate) mod testbindingsetlikewithprimitive;
pub(crate) mod testns;
pub(crate) mod testworklet;
pub(crate) mod testworkletglobalscope;

View file

@ -0,0 +1,100 @@
/* 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/. */
// check-tidy: no specs after this line
use dom_struct::dom_struct;
use indexmap::IndexMap;
use js::rust::HandleObject;
use super::bindings::error::Error;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::TestBindingMaplikeWithInterfaceBinding::TestBindingMaplikeWithInterfaceMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::like::Maplike;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use crate::dom::testbinding::TestBinding;
use crate::maplike;
use crate::script_runtime::CanGc;
/// maplike<DOMString, TestBinding>
#[dom_struct]
pub(crate) struct TestBindingMaplikeWithInterface {
reflector: Reflector,
#[custom_trace]
internal: DomRefCell<IndexMap<DOMString, DomRoot<TestBinding>>>,
}
impl TestBindingMaplikeWithInterface {
fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<TestBindingMaplikeWithInterface> {
reflect_dom_object_with_proto(
Box::new(TestBindingMaplikeWithInterface {
reflector: Reflector::new(),
internal: DomRefCell::new(IndexMap::new()),
}),
global,
proto,
can_gc,
)
}
}
impl TestBindingMaplikeWithInterfaceMethods<crate::DomTypeHolder>
for TestBindingMaplikeWithInterface
{
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> Fallible<DomRoot<TestBindingMaplikeWithInterface>> {
Ok(TestBindingMaplikeWithInterface::new(global, proto, can_gc))
}
fn SetInternal(&self, key: DOMString, value: &TestBinding) {
let value = DomRoot::from_ref(value);
self.internal.set(key, value)
}
fn ClearInternal(&self) {
self.internal.clear()
}
fn DeleteInternal(&self, key: DOMString) -> bool {
self.internal.delete(key)
}
fn HasInternal(&self, key: DOMString) -> bool {
self.internal.has(key)
}
fn GetInternal(&self, key: DOMString) -> Fallible<DomRoot<TestBinding>> {
// TODO: error type?
self.internal
.borrow()
.get(&key)
.ok_or_else(|| Error::Type(format!("No entry for key {key}")))
.cloned()
}
fn Size(&self) -> u32 {
self.internal.size()
}
}
// this error is wrong because if we inline Self::Key and Self::Value all errors are gone
// TODO: FIX THIS
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
impl Maplike for TestBindingMaplikeWithInterface {
type Key = DOMString;
type Value = DomRoot<TestBinding>;
maplike!(self, internal);
}

View file

@ -10,7 +10,7 @@ use js::rust::HandleObject;
use super::bindings::error::Error;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::TestBindingMaplikeBinding::TestBindingMaplikeMethods;
use crate::dom::bindings::codegen::Bindings::TestBindingMaplikeWithPrimitiveBinding::TestBindingMaplikeWithPrimitiveMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::like::Maplike;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
@ -22,20 +22,20 @@ use crate::script_runtime::CanGc;
/// maplike<DOMString, long>
#[dom_struct]
pub(crate) struct TestBindingMaplike {
pub(crate) struct TestBindingMaplikeWithPrimitive {
reflector: Reflector,
#[custom_trace]
internal: DomRefCell<IndexMap<DOMString, i32>>,
}
impl TestBindingMaplike {
impl TestBindingMaplikeWithPrimitive {
fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<TestBindingMaplike> {
) -> DomRoot<TestBindingMaplikeWithPrimitive> {
reflect_dom_object_with_proto(
Box::new(TestBindingMaplike {
Box::new(TestBindingMaplikeWithPrimitive {
reflector: Reflector::new(),
internal: DomRefCell::new(IndexMap::new()),
}),
@ -46,13 +46,15 @@ impl TestBindingMaplike {
}
}
impl TestBindingMaplikeMethods<crate::DomTypeHolder> for TestBindingMaplike {
impl TestBindingMaplikeWithPrimitiveMethods<crate::DomTypeHolder>
for TestBindingMaplikeWithPrimitive
{
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> Fallible<DomRoot<TestBindingMaplike>> {
Ok(TestBindingMaplike::new(global, proto, can_gc))
) -> Fallible<DomRoot<TestBindingMaplikeWithPrimitive>> {
Ok(TestBindingMaplikeWithPrimitive::new(global, proto, can_gc))
}
fn SetInternal(&self, key: DOMString, value: i32) {
@ -88,7 +90,7 @@ impl TestBindingMaplikeMethods<crate::DomTypeHolder> for TestBindingMaplike {
// this error is wrong because if we inline Self::Key and Self::Value all errors are gone
// TODO: FIX THIS
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
impl Maplike for TestBindingMaplike {
impl Maplike for TestBindingMaplikeWithPrimitive {
type Key = DOMString;
type Value = i32;

View file

@ -0,0 +1,71 @@
/* 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/. */
// check-tidy: no specs after this line
use dom_struct::dom_struct;
use indexmap::IndexSet;
use js::rust::HandleObject;
use super::bindings::like::Setlike;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::TestBindingSetlikeWithInterfaceBinding::TestBindingSetlikeWithInterfaceMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::dom::testbinding::TestBinding;
use crate::script_runtime::CanGc;
use crate::setlike;
// setlike<TestBinding>
#[dom_struct]
pub(crate) struct TestBindingSetlikeWithInterface {
reflector: Reflector,
#[custom_trace]
internal: DomRefCell<IndexSet<DomRoot<TestBinding>>>,
}
impl TestBindingSetlikeWithInterface {
fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<TestBindingSetlikeWithInterface> {
reflect_dom_object_with_proto(
Box::new(TestBindingSetlikeWithInterface {
reflector: Reflector::new(),
internal: DomRefCell::new(IndexSet::new()),
}),
global,
proto,
can_gc,
)
}
}
impl TestBindingSetlikeWithInterfaceMethods<crate::DomTypeHolder>
for TestBindingSetlikeWithInterface
{
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> Fallible<DomRoot<TestBindingSetlikeWithInterface>> {
Ok(TestBindingSetlikeWithInterface::new(global, proto, can_gc))
}
fn Size(&self) -> u32 {
self.internal.size()
}
}
// this error is wrong because if we inline Self::Key and Self::Value all errors are gone
// TODO: FIX THIS
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
impl Setlike for TestBindingSetlikeWithInterface {
type Key = DomRoot<TestBinding>;
setlike!(self, internal);
}

View file

@ -10,7 +10,7 @@ use js::rust::HandleObject;
use super::bindings::like::Setlike;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::TestBindingSetlikeBinding::TestBindingSetlikeMethods;
use crate::dom::bindings::codegen::Bindings::TestBindingSetlikeWithPrimitiveBinding::TestBindingSetlikeWithPrimitiveMethods;
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
use crate::dom::bindings::root::DomRoot;
@ -21,20 +21,20 @@ use crate::setlike;
// setlike<DOMString>
#[dom_struct]
pub(crate) struct TestBindingSetlike {
pub(crate) struct TestBindingSetlikeWithPrimitive {
reflector: Reflector,
#[custom_trace]
internal: DomRefCell<IndexSet<DOMString>>,
}
impl TestBindingSetlike {
impl TestBindingSetlikeWithPrimitive {
fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<TestBindingSetlike> {
) -> DomRoot<TestBindingSetlikeWithPrimitive> {
reflect_dom_object_with_proto(
Box::new(TestBindingSetlike {
Box::new(TestBindingSetlikeWithPrimitive {
reflector: Reflector::new(),
internal: DomRefCell::new(IndexSet::new()),
}),
@ -45,13 +45,15 @@ impl TestBindingSetlike {
}
}
impl TestBindingSetlikeMethods<crate::DomTypeHolder> for TestBindingSetlike {
impl TestBindingSetlikeWithPrimitiveMethods<crate::DomTypeHolder>
for TestBindingSetlikeWithPrimitive
{
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> Fallible<DomRoot<TestBindingSetlike>> {
Ok(TestBindingSetlike::new(global, proto, can_gc))
) -> Fallible<DomRoot<TestBindingSetlikeWithPrimitive>> {
Ok(TestBindingSetlikeWithPrimitive::new(global, proto, can_gc))
}
fn Size(&self) -> u32 {
@ -62,7 +64,7 @@ impl TestBindingSetlikeMethods<crate::DomTypeHolder> for TestBindingSetlike {
// this error is wrong because if we inline Self::Key and Self::Value all errors are gone
// TODO: FIX THIS
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
impl Setlike for TestBindingSetlike {
impl Setlike for TestBindingSetlikeWithPrimitive {
type Key = DOMString;
setlike!(self, internal);

View file

@ -2688,6 +2688,10 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs,
]
joinedTraits = ' + '.join(traits)
elements = [CGGeneric(f"pub(crate) 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 = []
@ -2711,11 +2715,17 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs,
iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
if iterableDecl:
if iterableDecl.isMaplike():
keytype = getRetvalDeclarationForType(iterableDecl.keyType, None).define()
valuetype = getRetvalDeclarationForType(iterableDecl.valueType, None).define()
keytype = fixupInterfaceTypeReferences(
getRetvalDeclarationForType(iterableDecl.keyType, descriptor).define()
)
valuetype = fixupInterfaceTypeReferences(
getRetvalDeclarationForType(iterableDecl.valueType, descriptor).define()
)
traits += [f"crate::dom::bindings::like::Maplike<Key={keytype}, Value={valuetype}>"]
if iterableDecl.isSetlike():
keytype = getRetvalDeclarationForType(iterableDecl.keyType, None).define()
keytype = fixupInterfaceTypeReferences(
getRetvalDeclarationForType(iterableDecl.keyType, descriptor).define()
)
traits += [f"crate::dom::bindings::like::Setlike<Key={keytype}>"]
if iterableDecl.hasKeyType():
traits += [
@ -2769,7 +2779,11 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs,
CGGeneric(f" type {firstCap(iface_name)}: {' + '.join(traits)};\n")
]
elements += [CGGeneric("}\n")]
return CGList([CGGeneric("use crate::dom::bindings::str::DOMString;\n")] + elements)
imports = [
CGGeneric("use crate::dom::bindings::root::DomRoot;\n"),
CGGeneric("use crate::dom::bindings::str::DOMString;\n"),
]
return CGList(imports + elements)
def DomTypeHolder(descriptors, descriptorProvider, dictionaries, callbacks, typedefs, config):

View file

@ -351,6 +351,14 @@ where
}
}
impl<T: DomObject> Eq for DomRoot<T> {}
impl<T: DomObject> Hash for DomRoot<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<T> Clone for DomRoot<T>
where
T: DomObject,

View file

@ -0,0 +1,20 @@
/* 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/. */
// This interface is entirely internal to Servo, and should not be accessible to
// web pages.
[Pref="dom_testbinding_enabled", Exposed=(Window,Worker)]
interface TestBindingMaplikeWithInterface {
[Throws]
constructor();
maplike<DOMString, TestBinding>;
undefined setInternal(DOMString aKey, TestBinding aValue);
undefined clearInternal();
boolean deleteInternal(DOMString aKey);
boolean hasInternal(DOMString aKey);
[Throws]
TestBinding getInternal(DOMString aKey);
};

View file

@ -6,7 +6,7 @@
// web pages.
[Pref="dom_testbinding_enabled", Exposed=(Window,Worker)]
interface TestBindingMaplike {
interface TestBindingMaplikeWithPrimitive {
[Throws]
constructor();

View file

@ -0,0 +1,14 @@
/* 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/. */
// This interface is entirely internal to Servo, and should not be accessible to
// web pages.
[Pref="dom_testbinding_enabled", Exposed=(Window,Worker)]
interface TestBindingSetlikeWithInterface {
[Throws]
constructor();
setlike<TestBinding>;
};

View file

@ -6,7 +6,7 @@
// web pages.
[Pref="dom_testbinding_enabled", Exposed=(Window,Worker)]
interface TestBindingSetlike {
interface TestBindingSetlikeWithPrimitive {
[Throws]
constructor();