diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index bd41a214740..eb14901c777 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -30,6 +30,7 @@ from Configuration import ( getTypesFromCallback, getTypesFromDescriptor, getTypesFromDictionary, + iteratorNativeType ) AUTOGENERATED_WARNING_COMMENT = \ @@ -720,7 +721,9 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, raise TypeError("Can't handle array arguments yet") if type.isSequence(): - innerInfo = getJSToNativeConversionInfo(innerSequenceType(type), descriptorProvider) + innerInfo = getJSToNativeConversionInfo(innerSequenceType(type), + descriptorProvider, + isMember=isMember) declType = CGWrapper(innerInfo.declType, pre="Vec<", post=">") config = getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs) @@ -1839,6 +1842,9 @@ class CGImports(CGWrapper): if not d.interface.isCallback(): types += [d.interface] + if d.interface.isIteratorInterface(): + types += [d.interface.iterableInterface] + members = d.interface.members + d.interface.namedConstructors constructor = d.interface.ctor() if constructor: @@ -1945,7 +1951,7 @@ def DOMClass(descriptor): # padding. protoList.extend(['PrototypeList::ID::Last'] * (descriptor.config.maxProtoChainLength - len(protoList))) prototypeChainString = ', '.join(protoList) - heapSizeOf = 'heap_size_of_raw_self_and_children::<%s>' % descriptor.interface.identifier.name + heapSizeOf = 'heap_size_of_raw_self_and_children::<%s>' % descriptor.concreteType if descriptor.isGlobal(): globals_ = camel_to_upper_snake(descriptor.name) else: @@ -2519,7 +2525,7 @@ class CGIDLInterface(CGThing): def define(self): interface = self.descriptor.interface - name = self.descriptor.name + name = self.descriptor.concreteType if (interface.getUserData("hasConcreteDescendant", False) or interface.getUserData("hasProxyDescendant", False)): depth = self.descriptor.prototypeDepth @@ -3043,11 +3049,21 @@ class CGPerSignatureCall(CGThing): if self.isFallible(): errorResult = " false" - cgThings.append(CGCallGenerator( - errorResult, - self.getArguments(), self.argsPre, returnType, - self.extendedAttributes, descriptor, nativeMethodName, - static)) + if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod(): + if idlNode.maplikeOrSetlikeOrIterable.isMaplike() or \ + idlNode.maplikeOrSetlikeOrIterable.isSetlike(): + raise TypeError('Maplike/Setlike methods are not supported yet') + else: + cgThings.append(CGIterableMethodGenerator(descriptor, + idlNode.maplikeOrSetlikeOrIterable, + idlNode.identifier.name)) + else: + cgThings.append(CGCallGenerator( + errorResult, + self.getArguments(), self.argsPre, returnType, + self.extendedAttributes, descriptor, nativeMethodName, + static)) + self.cgRoot = CGList(cgThings, "\n") def getArgs(self): @@ -5084,6 +5100,7 @@ class CGInterfaceTrait(CGThing): def members(): for m in descriptor.interface.members: if (m.isMethod() and not m.isStatic() and + not m.isMaplikeOrSetlikeOrIterableMethod() and (not m.isIdentifierLess() or m.isStringifier())): name = CGSpecializedMethod.makeNativeName(descriptor, m) infallible = 'infallible' in descriptor.getExtendedAttributes(m) @@ -5213,6 +5230,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries 'dom::bindings::interface::{ConstantSpec, NonNullJSNative}', 'dom::bindings::interface::ConstantVal::{IntVal, UintVal}', 'dom::bindings::interface::is_exposed_in', + 'dom::bindings::iterable::{IteratorType, Iterable}', 'dom::bindings::js::{JS, Root, RootedReference}', 'dom::bindings::js::{OptionalRootedReference}', 'dom::bindings::reflector::{Reflectable}', @@ -5352,7 +5370,7 @@ class CGDescriptor(CGThing): public=True)) reexports.append(descriptor.name + 'Constants') - if descriptor.interface.hasInterfaceObject(): + if descriptor.interface.hasInterfaceObject() and descriptor.register: cgThings.append(CGDefineDOMInterfaceMethod(descriptor)) reexports.append('DefineDOMInterface') cgThings.append(CGConstructorEnabled(descriptor)) @@ -6360,6 +6378,53 @@ class CallbackSetter(CallbackMember): return None +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()); + let mut call_args = vec![UndefinedValue(), UndefinedValue(), ObjectValue(&**_obj)]; + rooted!(in(cx) let mut ignoredReturnVal = UndefinedValue()); + for i in 0..(*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 = HandleValueArray { length_: 3, elements_: call_args.as_ptr() }; + if !Call(cx, arg1, arg0.handle(), &call_args, + ignoredReturnVal.handle_mut()) { + return false; + } + } + + let result = (); + """, + ifaceName=descriptor.interface.identifier.name)) + return + CGGeneric.__init__(self, fill( + """ + let result = ${iterClass}::new(&*this, + IteratorType::${itrMethod}, + super::${ifaceName}IteratorBinding::Wrap); + """, + 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)) @@ -6458,7 +6523,9 @@ class GlobalGenRoots(): @staticmethod def InterfaceTypes(config): - descriptors = [d.name for d in config.getDescriptors(register=True, isCallback=False)] + descriptors = [d.name for d in config.getDescriptors(register=True, + isCallback=False, + isIteratorInterface=False)] curr = CGList([CGGeneric("pub use dom::%s::%s;\n" % (name.lower(), name)) for name in descriptors]) curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT) return curr @@ -6469,7 +6536,7 @@ class GlobalGenRoots(): def leafModule(d): return getModuleFromObject(d).split('::')[-1] - descriptors = config.getDescriptors(register=True) + descriptors = config.getDescriptors(register=True, isIteratorInterface=False) descriptors = (set(d.name + "Binding" for d in descriptors) | set(leafModule(d) for d in config.callbacks) | set(leafModule(d) for d in config.getDictionaries())) diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py index fd005bc6e05..c8f92472618 100644 --- a/components/script/dom/bindings/codegen/Configuration.py +++ b/components/script/dom/bindings/codegen/Configuration.py @@ -89,6 +89,8 @@ class Configuration: getter = lambda x: x.isGlobal() elif key == 'isExposedConditionally': getter = lambda x: x.interface.isExposedConditionally() + elif key == 'isIteratorInterface': + getter = lambda x: x.interface.isIteratorInterface() else: getter = lambda x: getattr(x, key) curr = filter(lambda x: getter(x) == val, curr) @@ -177,7 +179,19 @@ class Descriptor(DescriptorProvider): # Read the desc, and fill in the relevant defaults. ifaceName = self.interface.identifier.name - typeName = desc.get('nativeType', ifaceName) + nativeTypeDefault = ifaceName + + # For generated iterator interfaces for other iterable interfaces, we + # just use IterableIterator as the native type, templated on the + # nativeType of the iterable interface. That way we can have a + # templated implementation for all the duplicated iterator + # functionality. + if self.interface.isIteratorInterface(): + itrName = self.interface.iterableInterface.identifier.name + itrDesc = self.getDescriptor(itrName) + nativeTypeDefault = iteratorNativeType(itrDesc) + + typeName = desc.get('nativeType', nativeTypeDefault) # Callback types do not use JS smart pointers, so we should not use the # built-in rooting mechanisms for them. @@ -193,7 +207,10 @@ class Descriptor(DescriptorProvider): self.returnType = "Root<%s>" % typeName self.argumentType = "&%s" % typeName self.nativeType = "*const %s" % typeName - pathDefault = 'dom::types::%s' % typeName + if self.interface.isIteratorInterface(): + pathDefault = 'dom::bindings::iterable::IterableIterator' + else: + pathDefault = 'dom::types::%s' % typeName self.concreteType = typeName self.register = desc.get('register', True) @@ -427,3 +444,10 @@ def getTypesFromCallback(callback): types = [sig[0]] # Return type types.extend(arg.type for arg in sig[1]) # Arguments return types + + +def iteratorNativeType(descriptor, infer=False): + assert descriptor.interface.isIterable() + iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable + assert iterableDecl.isPairIterator() + return "IterableIterator%s" % ("" if infer else '<%s>' % descriptor.interface.identifier.name) diff --git a/components/script/dom/bindings/iterable.rs b/components/script/dom/bindings/iterable.rs new file mode 100644 index 00000000000..20eb84bffde --- /dev/null +++ b/components/script/dom/bindings/iterable.rs @@ -0,0 +1,161 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +#![allow(unsafe_code)] + +//! Implementation of `iterable<...>` and `iterable<..., ...>` WebIDL declarations. + +use dom::bindings::codegen::Bindings::IterableIteratorBinding::IterableKeyAndValueResult; +use dom::bindings::codegen::Bindings::IterableIteratorBinding::IterableKeyOrValueResult; +use dom::bindings::error::Fallible; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::{JS, Root}; +use dom::bindings::reflector::{Reflector, Reflectable, reflect_dom_object}; +use dom::bindings::trace::JSTraceable; +use js::conversions::ToJSValConvertible; +use js::jsapi::{JSContext, JSObject, MutableHandleValue, MutableHandleObject, HandleValue}; +use js::jsval::UndefinedValue; +use std::cell::Cell; +use std::ptr; + +/// The values that an iterator will iterate over. +#[derive(JSTraceable, HeapSizeOf)] +pub enum IteratorType { + /// The keys of the iterable object. + Keys, + /// The values of the iterable object. + Values, + /// The keys and values of the iterable object combined. + Entries, +} + +/// A DOM object that can be iterated over using a pair value iterator. +pub trait Iterable { + /// The type of the key of the iterator pair. + type Key: ToJSValConvertible; + /// The type of the value of the iterator pair. + type Value: ToJSValConvertible; + /// Return the number of entries that can be iterated over. + fn get_iterable_length(&self) -> u32; + /// Return the value at the provided index. + fn get_value_at_index(&self, index: u32) -> Self::Value; + /// Return the key at the provided index. + fn get_key_at_index(&self, index: u32) -> Self::Key; +} + +/// An iterator over the iterable entries of a given DOM interface. +//FIXME: #12811 prevents dom_struct with type parameters +//#[dom_struct] +#[must_root] +#[privatize] +#[derive(JSTraceable)] +#[derive(HeapSizeOf)] +pub struct IterableIterator { + reflector: Reflector, + iterable: JS, + type_: IteratorType, + index: Cell, +} + +impl Reflectable for IterableIterator { + fn reflector<'a>(&'a self) -> &'a Reflector { + &self.reflector + } + fn init_reflector(&mut self, obj: *mut JSObject) { + self.reflector.set_jsobject(obj); + } +} + +impl ToJSValConvertible for IterableIterator { + #[allow(unsafe_code)] + unsafe fn to_jsval(&self, + cx: *mut JSContext, + rval: MutableHandleValue) { + let object = Reflectable::reflector(self).get_jsobject(); + object.to_jsval(cx, rval) + } +} + +impl IterableIterator { + /// Create a new iterator instance for the provided iterable DOM interface. + pub fn new(iterable: &T, + type_: IteratorType, + wrap: fn(*mut JSContext, GlobalRef, Box>) + -> Root) -> Root { + let iterator = box IterableIterator { + reflector: Reflector::new(), + type_: type_, + iterable: JS::from_ref(iterable), + index: Cell::new(0), + }; + let global = iterable.global(); + reflect_dom_object(iterator, global.r(), wrap) + } + + /// Return the next value from the iterable object. + #[allow(non_snake_case)] + pub fn Next(&self, cx: *mut JSContext) -> Fallible<*mut JSObject> { + let index = self.index.get(); + rooted!(in(cx) let mut value = UndefinedValue()); + rooted!(in(cx) let mut rval = ptr::null_mut()); + if index >= self.iterable.get_iterable_length() { + return dict_return(cx, rval.handle_mut(), true, value.handle()) + .map(|_| rval.handle().get()); + } + let result = match self.type_ { + IteratorType::Keys => { + unsafe { + self.iterable.get_key_at_index(index).to_jsval(cx, value.handle_mut()); + } + dict_return(cx, rval.handle_mut(), false, value.handle()) + } + IteratorType::Values => { + unsafe { + self.iterable.get_value_at_index(index).to_jsval(cx, value.handle_mut()); + } + dict_return(cx, rval.handle_mut(), false, value.handle()) + } + IteratorType::Entries => { + rooted!(in(cx) let mut key = UndefinedValue()); + unsafe { + self.iterable.get_key_at_index(index).to_jsval(cx, key.handle_mut()); + self.iterable.get_value_at_index(index).to_jsval(cx, value.handle_mut()); + } + key_and_value_return(cx, rval.handle_mut(), key.handle(), value.handle()) + } + }; + self.index.set(index + 1); + result.map(|_| rval.handle().get()) + } +} + +fn dict_return(cx: *mut JSContext, + result: MutableHandleObject, + done: bool, + value: HandleValue) -> Fallible<()> { + let mut dict = unsafe { IterableKeyOrValueResult::empty(cx) }; + dict.done = done; + dict.value = value.get(); + rooted!(in(cx) let mut dict_value = UndefinedValue()); + unsafe { + dict.to_jsval(cx, dict_value.handle_mut()); + } + result.set(dict_value.to_object()); + Ok(()) +} + +fn key_and_value_return(cx: *mut JSContext, + result: MutableHandleObject, + key: HandleValue, + value: HandleValue) -> Fallible<()> { + let mut dict = unsafe { IterableKeyAndValueResult::empty(cx) }; + dict.done = false; + dict.value = Some(vec![key.get(), value.get()]); + rooted!(in(cx) let mut dict_value = UndefinedValue()); + unsafe { + dict.to_jsval(cx, dict_value.handle_mut()); + } + result.set(dict_value.to_object()); + Ok(()) +} diff --git a/components/script/dom/bindings/mod.rs b/components/script/dom/bindings/mod.rs index 19797a473d0..439016ee15b 100644 --- a/components/script/dom/bindings/mod.rs +++ b/components/script/dom/bindings/mod.rs @@ -136,6 +136,7 @@ pub mod global; pub mod guard; pub mod inheritance; pub mod interface; +pub mod iterable; pub mod js; pub mod num; pub mod proxyhandler; diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 1cf74390257..07e4d6227be 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -385,6 +385,7 @@ pub mod stylesheet; pub mod stylesheetlist; pub mod testbinding; pub mod testbindingiterable; +pub mod testbindingpairiterable; pub mod testbindingproxy; pub mod text; pub mod textdecoder; diff --git a/components/script/dom/testbindingpairiterable.rs b/components/script/dom/testbindingpairiterable.rs new file mode 100644 index 00000000000..9bceedd4980 --- /dev/null +++ b/components/script/dom/testbindingpairiterable.rs @@ -0,0 +1,54 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +// check-tidy: no specs after this line + +use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::TestBindingPairIterableBinding; +use dom::bindings::codegen::Bindings::TestBindingPairIterableBinding::TestBindingPairIterableMethods; +use dom::bindings::error::Fallible; +use dom::bindings::global::GlobalRef; +use dom::bindings::iterable::Iterable; +use dom::bindings::js::Root; +use dom::bindings::reflector::{Reflector, reflect_dom_object}; +use dom::bindings::str::DOMString; + +#[dom_struct] +pub struct TestBindingPairIterable { + reflector: Reflector, + map: DOMRefCell>, +} + +impl Iterable for TestBindingPairIterable { + type Key = DOMString; + type Value = u32; + fn get_iterable_length(&self) -> u32 { + self.map.borrow().len() as u32 + } + fn get_value_at_index(&self, index: u32) -> u32 { + self.map.borrow().iter().nth(index as usize).map(|a| &a.1).unwrap().clone() + } + fn get_key_at_index(&self, index: u32) -> DOMString { + self.map.borrow().iter().nth(index as usize).map(|a| &a.0).unwrap().clone() + } +} + +impl TestBindingPairIterable { + fn new(global: GlobalRef) -> Root { + reflect_dom_object(box TestBindingPairIterable { + reflector: Reflector::new(), + map: DOMRefCell::new(vec![]), + }, global, TestBindingPairIterableBinding::TestBindingPairIterableWrap) + } + + pub fn Constructor(global: GlobalRef) -> Fallible> { + Ok(TestBindingPairIterable::new(global)) + } +} + +impl TestBindingPairIterableMethods for TestBindingPairIterable { + fn Add(&self, key: DOMString, value: u32) { + self.map.borrow_mut().push((key, value)); + } +} diff --git a/components/script/dom/webidls/IterableIterator.webidl b/components/script/dom/webidls/IterableIterator.webidl new file mode 100644 index 00000000000..d975aa5645d --- /dev/null +++ b/components/script/dom/webidls/IterableIterator.webidl @@ -0,0 +1,16 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +dictionary IterableKeyOrValueResult { + any value; + boolean done = false; +}; + +dictionary IterableKeyAndValueResult { + sequence value; + boolean done = false; +}; diff --git a/components/script/dom/webidls/TestBindingPairIterable.webidl b/components/script/dom/webidls/TestBindingPairIterable.webidl new file mode 100644 index 00000000000..a7bc66c1be3 --- /dev/null +++ b/components/script/dom/webidls/TestBindingPairIterable.webidl @@ -0,0 +1,12 @@ +/* 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 http://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), Constructor] +interface TestBindingPairIterable { + void add(DOMString key, unsigned long value); + iterable; +}; diff --git a/tests/wpt/mozilla/tests/mozilla/iterable.html b/tests/wpt/mozilla/tests/mozilla/iterable.html index 12ab75db54c..d4fe1259b01 100644 --- a/tests/wpt/mozilla/tests/mozilla/iterable.html +++ b/tests/wpt/mozilla/tests/mozilla/iterable.html @@ -51,4 +51,45 @@ assert_array_equals(entry, expected[i++]); } }, "Iterators iterate over values"); + + test(function() { + var t = new TestBindingPairIterable(); + var empty = true; + t.forEach(function() { empty = false; }); + assert_true(empty); + }, "Empty pair iterator"); + + test(function() { + var t = new TestBindingPairIterable(); + 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())); + }, "Pair iterable iterators are iterators"); + + test(function() { + var t = new TestBindingPairIterable(); + t.add("first", 0); + t.add("second", 1); + t.add("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.add("fourth", 3); + assert_array_equals(collect(t.keys()), ["first", "second", "third", "fourth"]); + assert_array_equals(collect(t.values()), [0, 1, 2, 3]); + var expected = [["first", 0], ["second", 1], ["third", 2], ["fourth", 3]]; + var i = 0; + for (entry of t.entries()) { + assert_array_equals(entry, expected[i++]); + } + }, "Pair iterators iterate over key/value pairs"); +