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 svgsvgelement;
pub(crate) mod testbinding; pub(crate) mod testbinding;
pub(crate) mod testbindingiterable; pub(crate) mod testbindingiterable;
pub(crate) mod testbindingmaplike; pub(crate) mod testbindingmaplikewithinterface;
pub(crate) mod testbindingmaplikewithprimitive;
pub(crate) mod testbindingpairiterable; pub(crate) mod testbindingpairiterable;
pub(crate) mod testbindingproxy; pub(crate) mod testbindingproxy;
pub(crate) mod testbindingsetlike; pub(crate) mod testbindingsetlikewithinterface;
pub(crate) mod testbindingsetlikewithprimitive;
pub(crate) mod testns; pub(crate) mod testns;
pub(crate) mod testworklet; pub(crate) mod testworklet;
pub(crate) mod testworkletglobalscope; 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 super::bindings::error::Error;
use crate::dom::bindings::cell::DomRefCell; 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::error::Fallible;
use crate::dom::bindings::like::Maplike; use crate::dom::bindings::like::Maplike;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
@ -22,20 +22,20 @@ use crate::script_runtime::CanGc;
/// maplike<DOMString, long> /// maplike<DOMString, long>
#[dom_struct] #[dom_struct]
pub(crate) struct TestBindingMaplike { pub(crate) struct TestBindingMaplikeWithPrimitive {
reflector: Reflector, reflector: Reflector,
#[custom_trace] #[custom_trace]
internal: DomRefCell<IndexMap<DOMString, i32>>, internal: DomRefCell<IndexMap<DOMString, i32>>,
} }
impl TestBindingMaplike { impl TestBindingMaplikeWithPrimitive {
fn new( fn new(
global: &GlobalScope, global: &GlobalScope,
proto: Option<HandleObject>, proto: Option<HandleObject>,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<TestBindingMaplike> { ) -> DomRoot<TestBindingMaplikeWithPrimitive> {
reflect_dom_object_with_proto( reflect_dom_object_with_proto(
Box::new(TestBindingMaplike { Box::new(TestBindingMaplikeWithPrimitive {
reflector: Reflector::new(), reflector: Reflector::new(),
internal: DomRefCell::new(IndexMap::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( fn Constructor(
global: &GlobalScope, global: &GlobalScope,
proto: Option<HandleObject>, proto: Option<HandleObject>,
can_gc: CanGc, can_gc: CanGc,
) -> Fallible<DomRoot<TestBindingMaplike>> { ) -> Fallible<DomRoot<TestBindingMaplikeWithPrimitive>> {
Ok(TestBindingMaplike::new(global, proto, can_gc)) Ok(TestBindingMaplikeWithPrimitive::new(global, proto, can_gc))
} }
fn SetInternal(&self, key: DOMString, value: i32) { 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 // this error is wrong because if we inline Self::Key and Self::Value all errors are gone
// TODO: FIX THIS // TODO: FIX THIS
#[cfg_attr(crown, allow(crown::unrooted_must_root))] #[cfg_attr(crown, allow(crown::unrooted_must_root))]
impl Maplike for TestBindingMaplike { impl Maplike for TestBindingMaplikeWithPrimitive {
type Key = DOMString; type Key = DOMString;
type Value = i32; 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 super::bindings::like::Setlike;
use crate::dom::bindings::cell::DomRefCell; 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::error::Fallible;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
@ -21,20 +21,20 @@ use crate::setlike;
// setlike<DOMString> // setlike<DOMString>
#[dom_struct] #[dom_struct]
pub(crate) struct TestBindingSetlike { pub(crate) struct TestBindingSetlikeWithPrimitive {
reflector: Reflector, reflector: Reflector,
#[custom_trace] #[custom_trace]
internal: DomRefCell<IndexSet<DOMString>>, internal: DomRefCell<IndexSet<DOMString>>,
} }
impl TestBindingSetlike { impl TestBindingSetlikeWithPrimitive {
fn new( fn new(
global: &GlobalScope, global: &GlobalScope,
proto: Option<HandleObject>, proto: Option<HandleObject>,
can_gc: CanGc, can_gc: CanGc,
) -> DomRoot<TestBindingSetlike> { ) -> DomRoot<TestBindingSetlikeWithPrimitive> {
reflect_dom_object_with_proto( reflect_dom_object_with_proto(
Box::new(TestBindingSetlike { Box::new(TestBindingSetlikeWithPrimitive {
reflector: Reflector::new(), reflector: Reflector::new(),
internal: DomRefCell::new(IndexSet::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( fn Constructor(
global: &GlobalScope, global: &GlobalScope,
proto: Option<HandleObject>, proto: Option<HandleObject>,
can_gc: CanGc, can_gc: CanGc,
) -> Fallible<DomRoot<TestBindingSetlike>> { ) -> Fallible<DomRoot<TestBindingSetlikeWithPrimitive>> {
Ok(TestBindingSetlike::new(global, proto, can_gc)) Ok(TestBindingSetlikeWithPrimitive::new(global, proto, can_gc))
} }
fn Size(&self) -> u32 { 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 // this error is wrong because if we inline Self::Key and Self::Value all errors are gone
// TODO: FIX THIS // TODO: FIX THIS
#[cfg_attr(crown, allow(crown::unrooted_must_root))] #[cfg_attr(crown, allow(crown::unrooted_must_root))]
impl Setlike for TestBindingSetlike { impl Setlike for TestBindingSetlikeWithPrimitive {
type Key = DOMString; type Key = DOMString;
setlike!(self, internal); setlike!(self, internal);

View file

@ -2688,6 +2688,10 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs,
] ]
joinedTraits = ' + '.join(traits) joinedTraits = ' + '.join(traits)
elements = [CGGeneric(f"pub(crate) trait DomTypes: {joinedTraits} where Self: 'static {{\n")] elements = [CGGeneric(f"pub(crate) trait DomTypes: {joinedTraits} where Self: 'static {{\n")]
def fixupInterfaceTypeReferences(typename):
return typename.replace("D::", "Self::")
for descriptor in descriptors: for descriptor in descriptors:
iface_name = descriptor.interface.identifier.name iface_name = descriptor.interface.identifier.name
traits = [] traits = []
@ -2711,11 +2715,17 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs,
iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable
if iterableDecl: if iterableDecl:
if iterableDecl.isMaplike(): if iterableDecl.isMaplike():
keytype = getRetvalDeclarationForType(iterableDecl.keyType, None).define() keytype = fixupInterfaceTypeReferences(
valuetype = getRetvalDeclarationForType(iterableDecl.valueType, None).define() getRetvalDeclarationForType(iterableDecl.keyType, descriptor).define()
)
valuetype = fixupInterfaceTypeReferences(
getRetvalDeclarationForType(iterableDecl.valueType, descriptor).define()
)
traits += [f"crate::dom::bindings::like::Maplike<Key={keytype}, Value={valuetype}>"] traits += [f"crate::dom::bindings::like::Maplike<Key={keytype}, Value={valuetype}>"]
if iterableDecl.isSetlike(): 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}>"] traits += [f"crate::dom::bindings::like::Setlike<Key={keytype}>"]
if iterableDecl.hasKeyType(): if iterableDecl.hasKeyType():
traits += [ traits += [
@ -2769,7 +2779,11 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs,
CGGeneric(f" type {firstCap(iface_name)}: {' + '.join(traits)};\n") CGGeneric(f" type {firstCap(iface_name)}: {' + '.join(traits)};\n")
] ]
elements += [CGGeneric("}\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): 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> impl<T> Clone for DomRoot<T>
where where
T: DomObject, 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. // web pages.
[Pref="dom_testbinding_enabled", Exposed=(Window,Worker)] [Pref="dom_testbinding_enabled", Exposed=(Window,Worker)]
interface TestBindingMaplike { interface TestBindingMaplikeWithPrimitive {
[Throws] [Throws]
constructor(); 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. // web pages.
[Pref="dom_testbinding_enabled", Exposed=(Window,Worker)] [Pref="dom_testbinding_enabled", Exposed=(Window,Worker)]
interface TestBindingSetlike { interface TestBindingSetlikeWithPrimitive {
[Throws] [Throws]
constructor(); constructor();

View file

@ -1,23 +1,29 @@
[like.any.html] [like.any.html]
type: testharness type: testharness
prefs: [dom_testbinding_enabled:true] prefs: [dom_testbinding_enabled:true]
[Test defaulting arguments on setlike to undefined] [setlike<DOMString> - Default arguments for r/w methods is undefined]
expected: FAIL expected: FAIL
[Simple map creation and functionality test] [maplike<string, number> - 'get' with a bogus key should return undefined]
expected: FAIL expected: FAIL
[Test defaulting arguments on maplike to undefined] [maplike<string, TestBinding> - 'get' with a bogus key should return undefined]
expected: FAIL
[maplike<DOMString, number> - Default arguments for r/w methods is undefined]
expected: FAIL expected: FAIL
[like.any.worker.html] [like.any.worker.html]
type: testharness type: testharness
prefs: [dom_testbinding_enabled:true] prefs: [dom_testbinding_enabled:true]
[Test defaulting arguments on setlike to undefined] [setlike<DOMString> - Default arguments for r/w methods is undefined]
expected: FAIL expected: FAIL
[Simple map creation and functionality test] [maplike<string, number> - 'get' with a bogus key should return undefined]
expected: FAIL expected: FAIL
[Test defaulting arguments on maplike to undefined] [maplike<string, TestBinding> - 'get' with a bogus key should return undefined]
expected: FAIL
[maplike<DOMString, number> - Default arguments for r/w methods is undefined]
expected: FAIL expected: FAIL

View file

@ -13538,7 +13538,7 @@
] ]
], ],
"like.any.js": [ "like.any.js": [
"11e4d86325ce6216754a0e0b760c59beb812db85", "30d8007fb5cbef2847e36fe9b4cb9e6535aaf1e0",
[ [
"mozilla/like.any.html", "mozilla/like.any.html",
{ {

View file

@ -1,23 +1,30 @@
[like.any.html] [like.any.html]
type: testharness type: testharness
prefs: [dom_testbinding_enabled:true] prefs: [dom_testbinding_enabled:true]
[Test defaulting arguments on setlike to undefined] [setlike<DOMString> - Default arguments for r/w methods is undefined]
expected: FAIL expected: FAIL
[Simple map creation and functionality test] [maplike<string, number> - 'get' with a bogus key should return undefined]
expected: FAIL expected: FAIL
[Test defaulting arguments on maplike to undefined] [maplike<string, TestBinding> - 'get' with a bogus key should return undefined]
expected: FAIL expected: FAIL
[maplike<DOMString, number> - Default arguments for r/w methods is undefined]
expected: FAIL
[like.any.worker.html] [like.any.worker.html]
type: testharness type: testharness
prefs: [dom_testbinding_enabled:true] prefs: [dom_testbinding_enabled:true]
[Test defaulting arguments on setlike to undefined] [setlike<DOMString> - Default arguments for r/w methods is undefined]
expected: FAIL expected: FAIL
[Simple map creation and functionality test] [maplike<string, number> - 'get' with a bogus key should return undefined]
expected: FAIL expected: FAIL
[Test defaulting arguments on maplike to undefined] [maplike<string, TestBinding> - 'get' with a bogus key should return undefined]
expected: FAIL
[maplike<DOMString, number> - Default arguments for r/w methods is undefined]
expected: FAIL expected: FAIL

View file

@ -59,250 +59,308 @@ var testExistence = function testExistence(prefix, obj, properties) {
} }
}; };
let setLikeTests = [
{
setConstructor: TestBindingSetlikeWithPrimitive,
testValues: ["first", "second", "third", "fourth"],
memberType: "string"
},
{
setConstructor: TestBindingSetlikeWithInterface,
testValues: [new TestBinding(), new TestBinding(), new TestBinding(), new TestBinding()],
memberType: "TestBinding"
},
];
for (const { setConstructor, testValues, memberType } of setLikeTests) {
test(function () { test(function () {
var m = new TestBindingSetlike(); var s = new setConstructor();
assert_true(ok(m), "SimpleSet: got a TestBindingSetlike object"); assert_true(ok(s), `got a ${setConstructor.name} object`);
testExistence("SimpleSet: ", m, setlike_rw_properties); testExistence(setConstructor.name, s, setlike_rw_properties);
assert_equals(m.size, 0, "SimpleSet: size should be zero"); assert_equals(s.size, 0, "size of new set should be zero");
assert_true(!m.has("test"), "SimpleSet: maplike has should return false"); const testValue1 = testValues[0];
m1 = m.add("test"); assert_true(!s.has(testValue1), "has() should return false for bogus value");
assert_equals(m, m1, "SimpleSet: return from set should be map object"); s1 = s.add(testValue1);
assert_equals(m.size, 1, "SimpleSet: size should be 1"); assert_equals(s, s1, "set.add() should be a chainable method");
assert_true(m.has("test"), "SimpleSet: maplike has should return true"); assert_equals(s.size, 1, "size should be 1");
m.add("test2"); assert_true(s.has(testValue1), "has() should return true for the first test value");
assert_equals(m.size, 2, "SimpleSet: size should be 2"); const testValue2 = testValues[1];
testSet = ["test", "test2"]; s.add(testValue2);
assert_equals(s.size, 2, "size should be 2");
testIndex = 0; testIndex = 0;
m.forEach(function (v, k, o) { s.forEach(function (v, k, o) {
"use strict"; "use strict";
assert_equals(o, m, "SimpleSet: foreach obj is correct"); assert_equals(o, s, "forEach obj is correct");
assert_equals(k, testSet[testIndex], "SimpleSet: foreach set key: " + k + " = " + testSet[testIndex]); assert_equals(k, testValues[testIndex], "forEach set key: " + k + " = " + testValues[testIndex]);
testIndex += 1; testIndex += 1;
}); });
assert_equals(testIndex, 2, "SimpleSet: foreach ran correct number of times"); assert_equals(testIndex, 2, "forEach ran correct number of times");
assert_true(m.has("test2"), "SimpleSet: maplike has should return true"); assert_true(s.has(testValue2), "maplike has should return true for second key");
assert_equals(m.delete("test2"), true, "SimpleSet: maplike deletion should return true"); assert_equals(s.delete(testValue2), true, "maplike deletion should return true");
assert_equals(m.size, 1, "SimpleSet: size should be 1"); assert_equals(s.size, 1, "size should be 1");
iterable = false; iterable = false;
for (let e of m) { for (let e of s) {
iterable = true; iterable = true;
assert_equals(e, "test", "SimpleSet: iterable first array element should be key"); assert_equals(e, testValue1, "iterable first array element should be first test key");
} }
assert_equals(m[Symbol.iterator].length, 0, "SimpleSet: @@iterator symbol is correct length"); assert_equals(s[Symbol.iterator].length, 0, "@@iterator symbol is correct length");
assert_equals(m[Symbol.iterator].name, "values", "SimpleSet: @@iterator symbol has correct name"); assert_equals(s[Symbol.iterator].name, "values", "@@iterator symbol has correct name");
assert_equals(m[Symbol.iterator], m.values, 'SimpleSet: @@iterator is an alias for "values"'); assert_equals(s[Symbol.iterator], s.values, '@@iterator is an alias for "values"');
assert_true(ok(iterable), "SimpleSet: @@iterator symbol resolved correctly"); assert_true(ok(iterable), " @@iterator symbol resolved correctly");
for (let k of m.keys()) { for (let k of s.keys()) {
assert_equals(k, "test", "SimpleSet: first keys element should be 'test'"); assert_equals(k, testValue1, "first element of keys() should be the first test key");
} }
for (let v of m.values()) { for (let v of s.values()) {
assert_equals(v, "test", "SimpleSet: first values elements should be 'test'"); assert_equals(v, testValue1, "first element of values() should be the first test value");
} }
for (let e of m.entries()) { for (let e of s.entries()) {
assert_equals(e[0], "test", "SimpleSet: Entries first array element should be 'test'"); assert_equals(e[0], testValue1, "first element of entries() should be the first test value");
assert_equals(e[1], "test", "SimpleSet: Entries second array element should be 'test'"); assert_equals(e[1], testValue1, "second element of entries() should be the second test value");
} }
m.clear(); s.clear();
assert_equals(m.size, 0, "SimpleSet: size should be 0 after clear"); assert_equals(s.size, 0, "size should be 0 after clear");
}, "Simple set creation and functionality"); }, `setlike<${memberType}> - Basic set operations`);
test(function () { test(function () {
var m = new TestBindingSetlike(); // Test this override for forEach
s = new setConstructor();
s.add(testValues[0]);
s.forEach(function (v, k, o) {
"use strict";
assert_equals(o, s, "forEach obj is correct");
assert_equals(this, 5, "'this' value should be correct");
}, 5);
}, `setke<${memberType}> - Test 'this' override for 'forEach'`);
// some iterable test ported to *like interfaces
test(function () {
var s = new setConstructor();
var empty = true;
s.forEach(function () { empty = false; });
assert_true(empty);
}, `Empty setlike<${memberType}>`);
test(function () {
var s = new setConstructor();
function is_iterator(o) {
return o[Symbol.iterator]() === o;
}
assert_true(is_iterator(s.keys()));
assert_true(is_iterator(s.values()));
assert_true(is_iterator(s.entries()));
}, `setlike<${memberType}> are iterators`);
test(function () {
var s = new setConstructor();
s.add(testValues[0]);
s.add(testValues[1]);
s.add(testValues[2]);
assert_array_equals(collect(s.keys()), collect(s.values()));
assert_array_equals(collect(s.values()), testValues.slice(0, 3));
var i = 0;
for (entry of s.entries()) {
assert_array_equals(entry, [testValues[i], testValues[i]]);
i += 1;
}
s.add(testValues[3]);
assert_array_equals(collect(s.keys()), collect(s.values()));
assert_array_equals(collect(s.values()), testValues);
var i = 0;
for (entry of s.entries()) {
assert_array_equals(entry, [testValues[i], testValues[i]]);
i += 1;
}
}, `setlike<${memberType}> - Iterators iterate over values`);
}
test(function () {
var m = new TestBindingSetlikeWithPrimitive();
m.add(); m.add();
assert_equals(m.size, 1, "SetArgsDefault: should have 1 entry"); assert_equals(m.size, 1, "set should have 1 entry");
m.forEach(function (v, k) { m.forEach(function (v, k) {
"use strict"; "use strict";
assert_equals(typeof k, "string", "SetArgsDefault: key is a string"); assert_equals(typeof k, "string", "key must be a string");
assert_equals(k, "undefined", "SetArgsDefault: key is the string undefined"); assert_equals(k, "undefined", "key is the string undefined");
}); });
m.delete(); m.delete();
assert_equals(m.size, 0, "SetArgsDefault: should have 0 entries"); assert_equals(m.size, 0, "after deleting key, set should have 0 entries");
}, "Test defaulting arguments on setlike to undefined"); }, "setlike<DOMString> - Default arguments for r/w methods is undefined");
let mapLikeTests = [
{
mapConstructor: TestBindingMaplikeWithPrimitive,
testEntries: [["first", 1], ["second", 2], ["third", 3], ["fourth", 4]],
valueType: "number"
},
{
mapConstructor: TestBindingMaplikeWithInterface,
testEntries: [
["first", new TestBinding()],
["second", new TestBinding()],
["third", new TestBinding()],
["fourth", new TestBinding()],
],
valueType: "TestBinding"
},
];
for (const { mapConstructor, testEntries, valueType } of mapLikeTests) {
test(function () {
m = new mapConstructor();
assert_true(ok(m), `got a ${mapConstructor.name} object`);
assert_equals(m.get("test"), undefined, "get(bogusKey) is undefined");
}, `maplike<string, ${valueType}> - 'get' with a bogus key should return undefined`);
test(function () { test(function () {
// Simple map creation and functionality test // Simple map creation and functionality test
m = new TestBindingMaplike(); m = new mapConstructor();
assert_true(ok(m), "SimpleMap: got a TestBindingMaplike object"); assert_true(ok(m), `got a ${mapConstructor.name} object`);
testExistence("SimpleMap: ", m, maplike_rw_properties); testExistence(mapConstructor.name, m, maplike_rw_properties);
assert_equals(m.size, 0, "SimpleMap: size should be zero"); assert_equals(m.size, 0, "size of new map should be zero");
assert_true(!m.has("test"), "SimpleMap: maplike has should return false"); let [testKey1, testValue1] = testEntries[0];
assert_equals(m.get("test"), undefined, "SimpleMap: maplike get should return undefined on bogus lookup"); assert_true(!m.has(testKey1), "maplike has should return false for bogus key");
var m1 = m.set("test", 1); var m1 = m.set(testKey1, testValue1);
assert_equals(m, m1, "SimpleMap: return from set should be map object"); assert_equals(m, m1, "map.set should be a chainable method");
assert_equals(m.size, 1, "SimpleMap: size should be 1"); assert_equals(m.size, 1, "size should be 1");
assert_true(m.has("test"), "SimpleMap: maplike has should return true"); assert_true(m.has(testKey1), "has() should return true for key already added");
assert_equals(m.get("test"), 1, "SimpleMap: maplike get should return value entered"); assert_equals(m.get(testKey1), testValue1, "get(testKey1) should return the same value provided to set()");
m.set("test2", 2); let [testKey2, testValue2] = testEntries[1];
assert_equals(m.size, 2, "SimpleMap: size should be 2"); m.set(testKey2, testValue2);
testSet = [["test", 1], ["test2", 2]]; assert_equals(m.size, 2, "size should be 2");
testSet = testEntries.slice(0, 2);
testIndex = 0; testIndex = 0;
m.forEach(function (v, k, o) { m.forEach(function (v, k, o) {
"use strict"; "use strict";
assert_equals(o, m, "SimpleMap: foreach obj is correct"); assert_equals(o, m, "forEach obj is correct");
assert_equals(k, testSet[testIndex][0], "SimpleMap: foreach map key: " + k + " = " + testSet[testIndex][0]); assert_equals(k, testSet[testIndex][0], "forEach map key: " + k + " = " + testSet[testIndex][0]);
assert_equals(v, testSet[testIndex][1], "SimpleMap: foreach map value: " + v + " = " + testSet[testIndex][1]); assert_equals(v, testSet[testIndex][1], "forEach map value: " + v + " = " + testSet[testIndex][1]);
testIndex += 1; testIndex += 1;
}); });
assert_equals(testIndex, 2, "SimpleMap: foreach ran correct number of times"); assert_equals(testIndex, 2, "forEach ran correct number of times");
assert_true(m.has("test2"), "SimpleMap: maplike has should return true"); assert_true(m.has(testKey2), "has() should return true for second test key");
assert_equals(m.get("test2"), 2, "SimpleMap: maplike get should return value entered"); assert_equals(m.get(testKey2), testValue2, "get(testKey2) should return the same value provided to set()");
assert_equals(m.delete("test2"), true, "SimpleMap: maplike deletion should return boolean"); assert_equals(m.delete(testKey2), true, "maplike deletion should return boolean");
assert_equals(m.size, 1, "SimpleMap: size should be 1"); assert_equals(m.size, 1, "size should be 1");
var iterable = false; var iterable = false;
for (let e of m) { for (let e of m) {
iterable = true; iterable = true;
assert_equals(e[0], "test", "SimpleMap: iterable first array element should be key"); assert_equals(e[0], testKey1, "iterable's first array element should be the first test key");
assert_equals(e[1], 1, "SimpleMap: iterable second array element should be value"); assert_equals(e[1], testValue1, "iterable' second array element should be the first test value");
} }
assert_equals(m[Symbol.iterator].length, 0, "SimpleMap: @@iterator symbol is correct length"); assert_equals(m[Symbol.iterator].length, 0, "@@iterator symbol is correct length");
assert_equals(m[Symbol.iterator].name, "entries", "SimpleMap: @@iterator symbol has correct name"); assert_equals(m[Symbol.iterator].name, "entries", "@@iterator symbol has correct name");
assert_equals(m[Symbol.iterator], m.entries, 'SimpleMap: @@iterator is an alias for "entries"'); assert_equals(m[Symbol.iterator], m.entries, '@@iterator is an alias for "entries"');
assert_true(ok(iterable), "SimpleMap: @@iterator symbol resolved correctly"); assert_true(ok(iterable), "@@iterator symbol resolved correctly");
for (let k of m.keys()) { for (let k of m.keys()) {
assert_equals(k, "test", "SimpleMap: first keys element should be 'test'"); assert_equals(k, testKey1, "first element of keys() should be the first test key");
} }
for (let v of m.values()) { for (let v of m.values()) {
assert_equals(v, 1, "SimpleMap: first values elements should be 1"); assert_equals(v, testValue1, "first element of values() should be 1");
} }
for (let e of m.entries()) { for (let e of m.entries()) {
assert_equals(e[0], "test", "SimpleMap: entries first array element should be 'test'"); assert_equals(e[0], testKey1, "first element of entries() should have the first test key");
assert_equals(e[1], 1, "SimpleMap: entries second array element should be 1"); assert_equals(e[1], testValue1, "first element of entries() should have the second test value");
} }
m.clear(); m.clear();
assert_equals(m.size, 0, "SimpleMap: size should be 0 after clear"); assert_equals(m.size, 0, "size should be 0 after clear");
}, "Simple map creation and functionality test"); }, `maplike<string, ${valueType}> - Simple map creation and functionality test`);
test(function () { test(function () {
// Map convenience function test // Map convenience function test
m = new TestBindingMaplike(); m = new mapConstructor();
assert_true(ok(m), "MapConvenience: got a TestBindingMaplike object"); assert_true(ok(m), `got a ${mapConstructor.name} object`);
assert_equals(m.size, 0, "MapConvenience: size should be zero"); assert_equals(m.size, 0, "size should be zero");
assert_true(!m.hasInternal("test"), "MapConvenience: maplike hasInternal should return false"); assert_true(!m.hasInternal("test"), "hasInternal() should return false for bogus key");
// It's fine to let getInternal to return 0 if the key doesn't exist // It's fine to let getInternal to return 0 if the key doesn't exist
// because this API can only be used internally in C++ and we'd throw // because this API can only be used internally in C++ and we'd throw
// an error if the key doesn't exist. // an error if the key doesn't exist.
//SimpleTest.doesThrow(() => m.getInternal("test"), 0, "MapConvenience: maplike getInternal should throw if the key doesn't exist"); //SimpleTest.doesThrow(() => m.getInternal("test"), 0, "MapConvenience: maplike getInternal should throw if the key doesn't exist");
m.setInternal("test", 1); let [testKey1, testValue1] = testEntries[0];
assert_equals(m.size, 1, "MapConvenience: size should be 1"); m.setInternal(testKey1, testValue1);
assert_true(m.hasInternal("test"), "MapConvenience: maplike hasInternal should return true"); assert_equals(m.size, 1, "size should be 1 after adding first test key/value");
assert_equals(m.get("test"), 1, "MapConvenience: maplike get should return value entered"); assert_true(m.hasInternal(testKey1), "hasInternal() should return true");
assert_equals(m.getInternal("test"), 1, "MapConvenience: maplike getInternal should return value entered"); assert_equals(m.get(testKey1), testValue1, "get() should return the value set using setInternal()");
m.setInternal("test2", 2); assert_equals(m.getInternal(testKey1), testValue1, "getInternal() should return the value set using setInternal()");
let [testKey2, testValue2] = testEntries[1];
m.setInternal(testKey2, testValue2);
assert_equals(m.size, 2, "size should be 2"); assert_equals(m.size, 2, "size should be 2");
assert_true(m.hasInternal("test2"), "MapConvenience: maplike hasInternal should return true"); assert_true(m.hasInternal(testKey2), "hasInternal() should return true for newly added second test key");
assert_equals(m.get("test2"), 2, "MapConvenience: maplike get should return value entered"); assert_equals(m.get(testKey2), testValue2, "get(testKey2) should return the value set using setInternal");
assert_equals(m.getInternal("test2"), 2, "MapConvenience: maplike getInternal should return value entered"); assert_equals(m.getInternal(testKey2), testValue2, "getInternal(testKey2) should return the value set using setInternal()");
assert_equals(m.deleteInternal("test2"), true, "MapConvenience: maplike deleteInternal should return true"); assert_equals(m.deleteInternal(testKey2), true, "deleteInternal should return true when deleting existing key");
assert_equals(m.size, 1, "MapConvenience: size should be 1"); assert_equals(m.size, 1, "size should be 1");
m.clearInternal(); m.clearInternal();
assert_equals(m.size, 0, "MapConvenience: size should be 0 after clearInternal"); assert_equals(m.size, 0, "size should be 0 after clearInternal");
}, "Map convenience function test"); }, `Convenience methods for maplike<string, ${valueType}>`);
// JS implemented map creation convenience function test // JS implemented map creation convenience function test
test(function () { test(function () {
// Test this override for forEach // Test this override for forEach
m = new TestBindingMaplike(); m = new mapConstructor();
m.set("test", 1); m.set(testEntries[0][0], testEntries[1][1]);
m.forEach(function (v, k, o) { m.forEach(function (v, k, o) {
"use strict"; "use strict";
assert_equals(o, m, "ForEachThisOverride: foreach obj is correct"); assert_equals(o, m, "forEach obj is correct");
assert_equals(this, 5, "ForEachThisOverride: 'this' value should be correct"); assert_equals(this, 5, "'this' value should be correct");
}, 5); }, 5);
}, "Test this override for forEach"); }, `maplike<string, ${valueType}> - Test 'this' override for 'forEach'`);
test(function () {
// Test defaulting arguments on maplike to undefined
m = new TestBindingMaplike();
m.set();
assert_equals(m.size, 1, "MapArgsDefault: should have 1 entry");
m.forEach(function (v, k) {
"use strict";
assert_equals(typeof k, "string", "MapArgsDefault: key is a string");
assert_equals(k, "undefined", "MapArgsDefault: key is the string undefined");
assert_equals(v, 0, "MapArgsDefault: value is 0");
});
assert_equals(m.get(), 0, "MapArgsDefault: no argument to get() returns correct value");
m.delete();
assert_equals(m.size, 0, "MapArgsDefault: should have 0 entries");
}, "Test defaulting arguments on maplike to undefined");
// some iterable test ported to *like interfaces // some iterable test ported to *like interfaces
test(function () { test(function () {
var t = new TestBindingSetlike(); var t = new mapConstructor();
var empty = true; var empty = true;
t.forEach(function () { empty = false; }); t.forEach(function () { empty = false; });
assert_true(empty); assert_true(empty);
}, "Empty setlike"); }, `maplike<string, ${valueType}> - Empty maplike`);
test(function () { test(function () {
var t = new TestBindingSetlike(); var t = new mapConstructor();
function is_iterator(o) { function is_iterator(o) {
return o[Symbol.iterator]() === o; return o[Symbol.iterator]() === o;
} }
assert_true(is_iterator(t.keys())); assert_true(is_iterator(t.keys()));
assert_true(is_iterator(t.values())); assert_true(is_iterator(t.values()));
assert_true(is_iterator(t.entries())); assert_true(is_iterator(t.entries()));
}, "Setlike are iterators"); }, `maplike<string, ${valueType}> - Maplike are iterators`);
test(function () { test(function () {
var t = new TestBindingSetlike(); var t = new mapConstructor();
t.add("first"); t.set(testEntries[0][0], testEntries[0][1]);
t.add("second"); t.set(testEntries[1][0], testEntries[1][1]);
t.add("third"); t.set(testEntries[2][0], testEntries[2][1]);
assert_array_equals(collect(t.keys()), collect(t.values())); assert_array_equals(collect(t.keys()), [testEntries[0][0], testEntries[1][0], testEntries[2][0]]);
assert_array_equals(collect(t.values()), ["first", "second", "third"]); assert_array_equals(collect(t.values()), [testEntries[0][1], testEntries[1][1], testEntries[2][1]]);
var expected = [["first", "first"], ["second", "second"], ["third", "third"]]; var expected = testEntries.slice(0, 3);
var i = 0; var i = 0;
for (entry of t.entries()) { for (entry of t.entries()) {
assert_array_equals(entry, expected[i++]); assert_array_equals(entry, expected[i++]);
} }
t.add("fourth"); t.set(testEntries[3][0], testEntries[3][1]);
assert_array_equals(collect(t.keys()), collect(t.values())); assert_array_equals(collect(t.keys()), testEntries.map(entry => entry[0]));
assert_array_equals(collect(t.values()), ["first", "second", "third", "fourth"]); assert_array_equals(collect(t.values()), testEntries.map(entry => entry[1]));
var expected = [["first", "first"], ["second", "second"], ["third", "third"], ["fourth", "fourth"]]; var expected = testEntries.slice();
var i = 0; var i = 0;
for (entry of t.entries()) { for (entry of t.entries()) {
assert_array_equals(entry, expected[i++]); assert_array_equals(entry, expected[i++]);
} }
}, "Iterators iterate over values"); }, `maplike<string, ${valueType}> - Maplike iteratable over key/value pairs`);
test(function () {
var t = new TestBindingMaplike();
var empty = true;
t.forEach(function () { empty = false; });
assert_true(empty);
}, "Empty maplike");
test(function () {
var t = new TestBindingMaplike();
function is_iterator(o) {
return o[Symbol.iterator]() === o;
}
assert_true(is_iterator(t.keys()));
assert_true(is_iterator(t.values()));
assert_true(is_iterator(t.entries()));
}, "Maplike are iterators");
test(function () {
var t = new TestBindingMaplike();
t.set("first", 0);
t.set("second", 1);
t.set("third", 2);
assert_array_equals(collect(t.keys()), ["first", "second", "third"]);
assert_array_equals(collect(t.values()), [0, 1, 2]);
var expected = [["first", 0], ["second", 1], ["third", 2]];
var i = 0;
for (entry of t.entries()) {
assert_array_equals(entry, expected[i++]);
} }
t.set("fourth", 3); test(function () {
assert_array_equals(collect(t.keys()), ["first", "second", "third", "fourth"]); // Test defaulting arguments on maplike to undefined
assert_array_equals(collect(t.values()), [0, 1, 2, 3]); m = new TestBindingMaplikeWithPrimitive();
var expected = [["first", 0], ["second", 1], ["third", 2], ["fourth", 3]]; m.set();
var i = 0; assert_equals(m.size, 1, "should have 1 entry");
for (entry of t.entries()) { m.forEach(function (v, k) {
assert_array_equals(entry, expected[i++]); "use strict";
} assert_equals(typeof k, "string", "defaulted key must be a string");
}, "Maplike iterate over key/value pairs"); assert_equals(k, "undefined", "defaulted key must be the string undefined");
assert_equals(v, 0, "defaulted value must be 0");
});
assert_equals(m.get(), 0, "no argument to get() returns correct value");
m.delete();
assert_equals(m.size, 0, "MapArgsDefault: should have 0 entries");
}, "maplike<DOMString, number> - Default arguments for r/w methods is undefined");