diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index d86c44f2d..f0f303bcd 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -1136,11 +1136,13 @@ class CGHeaders(CGWrapper): 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 @@ class MethodDefiner(PropertyDefiner): "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 @@ class CGForwardDeclarations(CGWrapper): # 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 ee1be46a5..09605d0cc 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 64eaef70e..1a56cec33 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,13 +58,77 @@ protected: }; template -class IterableIterator : public IterableIteratorBase +class IterableIterator final : public IterableIteratorBase { public: - explicit IterableIterator(T* aIterableObj) + typedef bool (*WrapFunc)(JSContext* aCx, + IterableIterator* aObject, + JS::Handle aGivenProto, + JS::MutableHandle aReflector); + + explicit IterableIterator(T* aIterableObj, + IterableIteratorType aIteratorType, + WrapFunc aWrapFunc) : mIterableObj(aIterableObj) + , mIteratorType(aIteratorType) + , mWrapFunc(aWrapFunc) + , mIndex(0) { MOZ_ASSERT(mIterableObj); + 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: + { + if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &value)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + DictReturn(aCx, aResult, false, value, aRv); + break; + } + 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: + { + JS::Rooted key(aCx); + if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &key)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + KeyAndValueReturn(aCx, key, 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: @@ -128,161 +190,6 @@ protected: // 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 -{ -public: - typedef typename IterableIterator::IterableIteratorType IterableIteratorType; - using IterableIterator::DictReturn; - using IterableIterator::KeyAndValueReturn; - typedef bool (*WrapFunc)(JSContext* aCx, - TwoTypeIterableIterator* aObject, - JS::Handle aGivenProto, - JS::MutableHandle aReflector); - - TwoTypeIterableIterator(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: - { - if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &value)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - DictReturn(aCx, aResult, false, value, aRv); - break; - } - 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: - { - JS::Rooted key(aCx); - if (!ToJSValue(aCx, this->mIterableObj->GetKeyAtIndex(mIndex), &key)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - if (!ToJSValue(aCx, this->mIterableObj->GetValueAtIndex(mIndex), &value)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - KeyAndValueReturn(aCx, key, 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 ~TwoTypeIterableIterator() {} - // 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 1e3055127..045a4cf56 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -1089,7 +1089,7 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins): 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 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins): self.identifier.name, locations) + indexedGetter = None + hasLengthAttribute = False for member in self.members: member.validate() @@ -1118,8 +1120,13 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins): [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 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins): 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 @@ class IDLInterface(IDLObjectWithScope, IDLExposureMixins): "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 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): 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 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): [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 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): 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 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): 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 @@ class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase): 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 @@ class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase): 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 @@ class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase): # 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 @@ class Parser(Tokenizer): 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 @@ class Parser(Tokenizer): 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 a6f1b925b..2621ba936 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 c51c79bfc..d6c981e11 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; };