From 9470d4fb94008abe720df3a1a17fc4245b3225b0 Mon Sep 17 00:00:00 2001 From: Cameron Kaiser Date: Tue, 20 Mar 2018 18:06:26 -0700 Subject: [PATCH] #488: basic support for DOM iterables, {NodeList,DOMTokenList}.forEach M1216751 M1290636 --- dom/bindings/Codegen.py | 114 +++++++++++----- dom/bindings/Configuration.py | 7 +- dom/bindings/IterableIterator.h | 231 ++++++++++---------------------- dom/bindings/parser/WebIDL.py | 125 +++++++++++++---- dom/webidl/DOMTokenList.webidl | 1 + dom/webidl/NodeList.webidl | 1 + 6 files changed, 252 insertions(+), 227 deletions(-) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index d86c44f2dc..f0f303bcd8 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -1136,11 +1136,13 @@ def addHeaderForFunc(func): if desc.interface.maplikeOrSetlikeOrIterable: # We need ToJSValue.h for maplike/setlike type conversions bindingHeaders.add("mozilla/dom/ToJSValue.h") - # Add headers for the key and value types of the maplike, since - # they'll be needed for convenience functions - addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.keyType, - desc, None)) - if desc.interface.maplikeOrSetlikeOrIterable.valueType: + # Add headers for the key and value types of the + # maplike/setlike/iterable, since they'll be needed for + # convenience functions + if desc.interface.maplikeOrSetlikeOrIterable.hasKeyType(): + addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.keyType, + desc, None)) + if desc.interface.maplikeOrSetlikeOrIterable.hasValueType(): addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.valueType, desc, None)) @@ -2269,34 +2271,50 @@ def hasIterator(methods, regular): "condition": MemberCondition() }) - # Generate the maplike/setlike iterator, if one wasn't already - # generated by a method. If we already have an @@iterator symbol, fail. - if descriptor.interface.maplikeOrSetlikeOrIterable: - if hasIterator(methods, self.regular): - raise TypeError("Cannot have maplike/setlike/iterable interface with " - "other members that generate @@iterator " - "on interface %s, such as indexed getters " - "or aliased functions." % - self.descriptor.interface.identifier.name) - for m in methods: - if (m.isMaplikeOrSetlikeOrIterableMethod() and - (((m.maplikeOrSetlikeOrIterable.isMaplike() or - (m.maplikeOrSetlikeOrIterable.isIterable() and - m.maplikeOrSetlikeOrIterable.hasValueType())) and - m.identifier.name == "entries") or - (((m.maplikeOrSetlikeOrIterable.isSetlike() or - (m.maplikeOrSetlikeOrIterable.isIterable() and - not m.maplikeOrSetlikeOrIterable.hasValueType()))) and - m.identifier.name == "values"))): - self.regular.append({ - "name": "@@iterator", - "methodName": m.identifier.name, - "length": methodLength(m), - "flags": "0", - "condition": PropertyDefiner.getControllingCondition(m, - descriptor), - }) - break + # Generate the keys/values/entries aliases for value iterables. + maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable + if (not static and + not unforgeable and + maplikeOrSetlikeOrIterable and + maplikeOrSetlikeOrIterable.isIterable() and + maplikeOrSetlikeOrIterable.isValueIterator()): + # 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) + }) if not static: stringifier = descriptor.operations['Stringifier'] @@ -13008,8 +13026,9 @@ def __init__(self, config, descriptors, mainCallbacks, workerCallbacks, # arguments to helper functions, and they'll need to be forward # declared in the header. if d.interface.maplikeOrSetlikeOrIterable: - builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.keyType, - config) + if d.interface.maplikeOrSetlikeOrIterable.hasKeyType(): + builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.keyType, + config) if d.interface.maplikeOrSetlikeOrIterable.hasValueType(): builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.valueType, config) @@ -15735,6 +15754,31 @@ class CGIterableMethodGenerator(CGGeneric): using CGCallGenerator. """ def __init__(self, descriptor, iterable, methodName): + if methodName == "forEach": + CGGeneric.__init__(self, fill( + """ + if (!JS::IsCallable(arg0)) { + ThrowErrorMessage(cx, MSG_NOT_CALLABLE, "Argument 1 of ${ifaceName}.forEach"); + return false; + } + JS::AutoValueArray<3> callArgs(cx); + callArgs[2].setObject(*obj); + JS::Rooted ignoredReturnVal(cx); + for (size_t i = 0; i < self->GetIterableLength(); ++i) { + if (!ToJSValue(cx, self->GetValueAtIndex(i), callArgs[0])) { + return false; + } + if (!ToJSValue(cx, self->GetKeyAtIndex(i), callArgs[1])) { + return false; + } + if (!JS::Call(cx, arg1, arg0, JS::HandleValueArray(callArgs), + &ignoredReturnVal)) { + return false; + } + } + """, + ifaceName=descriptor.interface.identifier.name)) + return CGGeneric.__init__(self, fill( """ typedef ${iterClass} itrType; diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py index ee1be46a50..09605d0ccd 100644 --- a/dom/bindings/Configuration.py +++ b/dom/bindings/Configuration.py @@ -893,8 +893,5 @@ def getAllTypes(descriptors, dictionaries, callbacks): def iteratorNativeType(descriptor): assert descriptor.interface.isIterable() iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable - if iterableDecl.valueType is None: - iterClass = "OneTypeIterableIterator" - else: - iterClass = "TwoTypeIterableIterator" - return "mozilla::dom::%s<%s>" % (iterClass, descriptor.nativeType) + assert iterableDecl.isPairIterator() + return "mozilla::dom::IterableIterator<%s>" % descriptor.nativeType diff --git a/dom/bindings/IterableIterator.h b/dom/bindings/IterableIterator.h index 64eaef70e7..1a56cec33f 100644 --- a/dom/bindings/IterableIterator.h +++ b/dom/bindings/IterableIterator.h @@ -6,19 +6,17 @@ /** * The IterableIterator class is used for WebIDL interfaces that have a - * iterable<> member defined. It handles the ES6 Iterator-like functions that - * are generated for the iterable interface. + * iterable<> member defined with two types (so a pair iterator). It handles + * the ES6 Iterator-like functions that are generated for the iterable + * interface. * - * For iterable interfaces, the implementation class will need to - * implement these two functions: + * For iterable interfaces with a pair iterator, the implementation class will + * need to implement these two functions: * * - size_t GetIterableLength() * - Returns the number of elements available to iterate over * - [type] GetValueAtIndex(size_t index) * - Returns the value at the requested index. - * - * If this is a two-type iterator, then the implementation class will also need to implement: - * * - [type] GetKeyAtIndex(size_t index) * - Returns the key at the requested index * @@ -60,170 +58,23 @@ class IterableIteratorBase : public nsISupports }; template -class IterableIterator : public IterableIteratorBase -{ -public: - explicit IterableIterator(T* aIterableObj) - : mIterableObj(aIterableObj) - { - MOZ_ASSERT(mIterableObj); - } - -protected: - static void - DictReturn(JSContext* aCx, JS::MutableHandle aResult, - bool aDone, JS::Handle aValue, ErrorResult& aRv) - { - RootedDictionary dict(aCx); - dict.mDone = aDone; - dict.mValue = aValue; - JS::Rooted dictValue(aCx); - if (!ToJSValue(aCx, dict, &dictValue)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - aResult.set(&dictValue.toObject()); - } - - static void - KeyAndValueReturn(JSContext* aCx, JS::Handle aKey, - JS::Handle aValue, - JS::MutableHandle aResult, ErrorResult& aRv) - { - RootedDictionary dict(aCx); - dict.mDone = false; - // Dictionary values are a Sequence, which is a FallibleTArray, so we need - // to check returns when appending. - if (!dict.mValue.AppendElement(aKey, mozilla::fallible)) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return; - } - if (!dict.mValue.AppendElement(aValue, mozilla::fallible)) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return; - } - JS::Rooted dictValue(aCx); - if (!ToJSValue(aCx, dict, &dictValue)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - aResult.set(&dictValue.toObject()); - } - -protected: - virtual ~IterableIterator() {} - - // Since we're templated on a binding, we need to possibly CC it, but can't do - // that through macros. So it happens here. - virtual void UnlinkHelper() final - { - mIterableObj = nullptr; - } - - virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override - { - IterableIterator* tmp = this; - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj); - } - - // Binding Implementation object that we're iterating over. - RefPtr mIterableObj; -}; - -template -class OneTypeIterableIterator final : public IterableIterator -{ -public: - typedef typename IterableIterator::IterableIteratorType IterableIteratorType; - using IterableIterator::DictReturn; - using IterableIterator::KeyAndValueReturn; - typedef bool (*WrapFunc)(JSContext* aCx, - OneTypeIterableIterator* aObject, - JS::Handle aGivenProto, - JS::MutableHandle aReflector); - - OneTypeIterableIterator(T* aIterableObj, - IterableIteratorType aIteratorType, - WrapFunc aWrapFunc) - : IterableIterator(aIterableObj) - , mIteratorType(aIteratorType) - , mWrapFunc(aWrapFunc) - , mIndex(0) - { - MOZ_ASSERT(mWrapFunc); - } - - void - Next(JSContext* aCx, JS::MutableHandle aResult, ErrorResult& aRv) - { - JS::Rooted value(aCx, JS::UndefinedValue()); - if (mIndex >= this->mIterableObj->GetIterableLength()) { - DictReturn(aCx, aResult, true, value, aRv); - return; - } - - switch (mIteratorType) { - case IterableIteratorType::Keys: - case IterableIteratorType::Values: - { - if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - DictReturn(aCx, aResult, false, value, aRv); - break; - } - case IterableIteratorType::Entries: - { - if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - KeyAndValueReturn(aCx, value, value, aResult, aRv); - break; - } - default: - MOZ_CRASH("Invalid iterator type!"); - } - ++mIndex; - } - - bool - WrapObject(JSContext* aCx, JS::Handle aGivenProto, JS::MutableHandle aObj) - { - return (*mWrapFunc)(aCx, this, aGivenProto, aObj); - } - -protected: - virtual ~OneTypeIterableIterator() {} - - // Tells whether this is a key, value, or entries iterator. - IterableIteratorType mIteratorType; - // Function pointer to binding-type-specific Wrap() call for this iterator. - WrapFunc mWrapFunc; - // Current index of iteration. - uint32_t mIndex; -}; - -template -class TwoTypeIterableIterator final : public IterableIterator +class IterableIterator final : public IterableIteratorBase { public: - typedef typename IterableIterator::IterableIteratorType IterableIteratorType; - using IterableIterator::DictReturn; - using IterableIterator::KeyAndValueReturn; typedef bool (*WrapFunc)(JSContext* aCx, - TwoTypeIterableIterator* aObject, + IterableIterator* aObject, JS::Handle aGivenProto, JS::MutableHandle aReflector); - TwoTypeIterableIterator(T* aIterableObj, IterableIteratorType aIteratorType, - WrapFunc aWrapFunc) - : IterableIterator(aIterableObj) + explicit IterableIterator(T* aIterableObj, + IterableIteratorType aIteratorType, + WrapFunc aWrapFunc) + : mIterableObj(aIterableObj) , mIteratorType(aIteratorType) , mWrapFunc(aWrapFunc) , mIndex(0) { + MOZ_ASSERT(mIterableObj); MOZ_ASSERT(mWrapFunc); } @@ -281,8 +132,64 @@ class TwoTypeIterableIterator final : public IterableIterator } protected: - virtual ~TwoTypeIterableIterator() {} + static void + DictReturn(JSContext* aCx, JS::MutableHandle aResult, + bool aDone, JS::Handle aValue, ErrorResult& aRv) + { + RootedDictionary dict(aCx); + dict.mDone = aDone; + dict.mValue = aValue; + JS::Rooted dictValue(aCx); + if (!ToJSValue(aCx, dict, &dictValue)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + aResult.set(&dictValue.toObject()); + } + + static void + KeyAndValueReturn(JSContext* aCx, JS::Handle aKey, + JS::Handle aValue, + JS::MutableHandle aResult, ErrorResult& aRv) + { + RootedDictionary dict(aCx); + dict.mDone = false; + // Dictionary values are a Sequence, which is a FallibleTArray, so we need + // to check returns when appending. + if (!dict.mValue.AppendElement(aKey, mozilla::fallible)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + if (!dict.mValue.AppendElement(aValue, mozilla::fallible)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + JS::Rooted dictValue(aCx); + if (!ToJSValue(aCx, dict, &dictValue)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + aResult.set(&dictValue.toObject()); + } + +protected: + virtual ~IterableIterator() {} + + // Since we're templated on a binding, we need to possibly CC it, but can't do + // that through macros. So it happens here. + virtual void UnlinkHelper() final + { + mIterableObj = nullptr; + } + + virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) override + { + IterableIterator* tmp = this; + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj); + } + // Binding Implementation object that we're iterating over. + RefPtr mIterableObj; // Tells whether this is a key, value, or entries iterator. IterableIteratorType mIteratorType; // Function pointer to binding-type-specific Wrap() call for this iterator. diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 1e30551275..045a4cf566 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -1089,7 +1089,7 @@ def finish(self, scope): def validate(self): # We don't support consequential unforgeable interfaces. Need to check - # this here, becaue in finish() an interface might not know yet that + # this here, because in finish() an interface might not know yet that # it's consequential. if self.getExtendedAttribute("Unforgeable") and self.isConsequential(): raise WebIDLError( @@ -1108,6 +1108,8 @@ def validate(self): self.identifier.name, locations) + indexedGetter = None + hasLengthAttribute = False for member in self.members: member.validate() @@ -1118,8 +1120,13 @@ def validate(self): [self.location, member.location]) # Check that PutForwards refers to another attribute and that no - # cycles exist in forwarded assignments. + # cycles exist in forwarded assignments. Also check for a + # integer-typed "length" attribute. if member.isAttr(): + if (member.identifier.name == "length" and + member.type.isInteger()): + hasLengthAttribute = True + iface = self attr = member putForwards = attr.getExtendedAttribute("PutForwards") @@ -1157,8 +1164,11 @@ def validate(self): putForwards = attr.getExtendedAttribute("PutForwards") # Check that the name of an [Alias] doesn't conflict with an - # interface member. + # interface member and whether we support indexed properties. if member.isMethod(): + if member.isGetter() and member.isIndexed(): + indexedGetter = member + for alias in member.aliases: if self.isOnGlobalProtoChain(): raise WebIDLError("[Alias] must not be used on a " @@ -1219,6 +1229,35 @@ def validate(self): "exposed conditionally", [self.location]) + # Value iterators are only allowed on interfaces with indexed getters, + # and pair iterators are only allowed on interfaces without indexed + # getters. + if self.isIterable(): + iterableDecl = self.maplikeOrSetlikeOrIterable + if iterableDecl.isValueIterator(): + if not indexedGetter: + raise WebIDLError("Interface with value iterator does not " + "support indexed properties", + [self.location]) + + if iterableDecl.valueType != indexedGetter.signatures()[0][0]: + raise WebIDLError("Iterable type does not match indexed " + "getter type", + [iterableDecl.location, + indexedGetter.location]) + + if not hasLengthAttribute: + raise WebIDLError('Interface with value iterator does not ' + 'have an integer-typed "length" attribute', + [self.location]) + else: + assert iterableDecl.isPairIterator() + if indexedGetter: + raise WebIDLError("Interface with pair iterator supports " + "indexed properties", + [self.location, iterableDecl.location, + indexedGetter.location]) + def isInterface(self): return True @@ -3408,7 +3447,10 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): def __init__(self, location, identifier, ifaceType, keyType, valueType, ifaceKind): IDLInterfaceMember.__init__(self, location, identifier, ifaceKind) - assert isinstance(keyType, IDLType) + if keyType is not None: + assert isinstance(keyType, IDLType) + else: + assert valueType is not None assert ifaceType in ['maplike', 'setlike', 'iterable'] if valueType is not None: assert isinstance(valueType, IDLType) @@ -3427,6 +3469,9 @@ def isSetlike(self): def isIterable(self): return self.maplikeOrSetlikeOrIterableType == "iterable" + def hasKeyType(self): + return self.keyType is not None + def hasValueType(self): return self.valueType is not None @@ -3451,7 +3496,8 @@ def checkCollisions(self, members, isAncestor): [self.location, member.location]) def addMethod(self, name, members, allowExistingOperations, returnType, args=[], - chromeOnly=False, isPure=False, affectsNothing=False, newObject=False): + chromeOnly=False, isPure=False, affectsNothing=False, newObject=False, + isIteratorAlias=False): """ Create an IDLMethod based on the parameters passed in. @@ -3511,16 +3557,20 @@ def addMethod(self, name, members, allowExistingOperations, returnType, args=[], if newObject: method.addExtendedAttributes( [IDLExtendedAttribute(self.location, ("NewObject",))]) + if isIteratorAlias: + method.addExtendedAttributes( + [IDLExtendedAttribute(self.location, ("Alias", "@@iterator"))]) members.append(method) def resolve(self, parentScope): - self.keyType.resolveType(parentScope) + if self.keyType: + self.keyType.resolveType(parentScope) if self.valueType: self.valueType.resolveType(parentScope) def finish(self, scope): IDLInterfaceMember.finish(self, scope) - if not self.keyType.isComplete(): + if self.keyType and not self.keyType.isComplete(): t = self.keyType.complete(scope) assert not isinstance(t, IDLUnresolvedType) @@ -3542,9 +3592,23 @@ def handleExtendedAttribute(self, attr): IDLInterfaceMember.handleExtendedAttribute(self, attr) def _getDependentObjects(self): + deps = set() + if self.keyType: + deps.add(self.keyType) if self.valueType: - return set([self.keyType, self.valueType]) - return set([self.keyType]) + deps.add(self.valueType) + return deps + + def getForEachArguments(self): + return [IDLArgument(self.location, + IDLUnresolvedIdentifier(BuiltinLocation(""), + "callback"), + BuiltinTypes[IDLBuiltinType.Types.object]), + IDLArgument(self.location, + IDLUnresolvedIdentifier(BuiltinLocation(""), + "thisArg"), + BuiltinTypes[IDLBuiltinType.Types.any], + optional=True)] # Iterable adds ES6 iterator style functions and traits # (keys/values/entries/@@iterator) to an interface. @@ -3565,9 +3629,15 @@ def expand(self, members, isJSImplemented): we generate our functions as if they were part of the interface specification during parsing. """ + # We only need to add entries/keys/values here if we're a pair iterator. + # Value iterators just copy these from %ArrayPrototype% instead. + if not self.isPairIterator(): + return + # object entries() self.addMethod("entries", members, False, self.iteratorType, - affectsNothing=True, newObject=True) + affectsNothing=True, newObject=True, + isIteratorAlias=True) # object keys() self.addMethod("keys", members, False, self.iteratorType, affectsNothing=True, newObject=True) @@ -3575,6 +3645,17 @@ def expand(self, members, isJSImplemented): self.addMethod("values", members, False, self.iteratorType, affectsNothing=True, newObject=True) + # void forEach(callback(valueType, keyType), optional any thisArg) + self.addMethod("forEach", members, False, + BuiltinTypes[IDLBuiltinType.Types.void], + self.getForEachArguments()) + + def isValueIterator(self): + return not self.isPairIterator() + + def isPairIterator(self): + return self.hasKeyType() + # MaplikeOrSetlike adds ES6 map-or-set-like traits to an interface. class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase): @@ -3611,26 +3692,17 @@ def expand(self, members, isJSImplemented): # object entries() self.addMethod("entries", members, False, BuiltinTypes[IDLBuiltinType.Types.object], - affectsNothing=True) + affectsNothing=True, isIteratorAlias=self.isMaplike()) # object keys() self.addMethod("keys", members, False, BuiltinTypes[IDLBuiltinType.Types.object], affectsNothing=True) # object values() self.addMethod("values", members, False, BuiltinTypes[IDLBuiltinType.Types.object], - affectsNothing=True) + affectsNothing=True, isIteratorAlias=self.isSetlike()) # void forEach(callback(valueType, keyType), thisVal) - foreachArguments = [IDLArgument(self.location, - IDLUnresolvedIdentifier(BuiltinLocation(""), - "callback"), - BuiltinTypes[IDLBuiltinType.Types.object]), - IDLArgument(self.location, - IDLUnresolvedIdentifier(BuiltinLocation(""), - "thisArg"), - BuiltinTypes[IDLBuiltinType.Types.any], - optional=True)] self.addMethod("forEach", members, False, BuiltinTypes[IDLBuiltinType.Types.void], - foreachArguments) + self.getForEachArguments()) def getKeyArg(): return IDLArgument(self.location, @@ -5436,10 +5508,13 @@ def p_Iterable(self, p): location = self.getLocation(p, 2) identifier = IDLUnresolvedIdentifier(location, "__iterable", allowDoubleUnderscore=True) - keyType = p[3] - valueType = None if (len(p) > 6): + keyType = p[3] valueType = p[5] + else: + keyType = None + valueType = p[3] + p[0] = IDLIterable(location, identifier, keyType, valueType, self.globalScope()) def p_Setlike(self, p): @@ -6509,7 +6584,7 @@ def finish(self): if isinstance(m, IDLIterable): iterable = m break - if iterable: + if iterable and iterable.isPairIterator(): def simpleExtendedAttr(str): return IDLExtendedAttribute(iface.location, (str, )) nextMethod = IDLMethod( diff --git a/dom/webidl/DOMTokenList.webidl b/dom/webidl/DOMTokenList.webidl index a6f1b925bc..2621ba936c 100644 --- a/dom/webidl/DOMTokenList.webidl +++ b/dom/webidl/DOMTokenList.webidl @@ -22,4 +22,5 @@ interface DOMTokenList { [Throws] boolean toggle(DOMString token, optional boolean force); stringifier DOMString (); + iterable; }; diff --git a/dom/webidl/NodeList.webidl b/dom/webidl/NodeList.webidl index c51c79bfcd..d6c981e11e 100644 --- a/dom/webidl/NodeList.webidl +++ b/dom/webidl/NodeList.webidl @@ -13,4 +13,5 @@ interface NodeList { getter Node? item(unsigned long index); readonly attribute unsigned long length; + iterable; };