Bug 1085293 - WebIDL Iterable Implementation and Tests; r=bz
authorKyle Machulis <kyle@nonpolynomial.com>
Mon, 12 Oct 2015 13:27:00 -0700
changeset 288463 96acfd0d21cf3aa1d09e96f0beb5fd1b62cc2793
parent 288462 91e9d59af87e92aeac5ba4874747a571ec0e28d0
child 288464 c84c350db752d7eab18d234b56ff8f7804256fe8
push id5392
push userraliiev@mozilla.com
push dateMon, 14 Dec 2015 20:08:23 +0000
treeherdermozilla-esr52@16ce8562a975 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1085293
milestone44.0a1
Bug 1085293 - WebIDL Iterable Implementation and Tests; r=bz
dom/bindings/Codegen.py
dom/bindings/Configuration.py
dom/bindings/IterableIterator.cpp
dom/bindings/IterableIterator.h
dom/bindings/moz.build
dom/bindings/parser/WebIDL.py
dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py
dom/bindings/test/TestInterfaceIterableDouble.cpp
dom/bindings/test/TestInterfaceIterableDouble.h
dom/bindings/test/TestInterfaceIterableSingle.cpp
dom/bindings/test/TestInterfaceIterableSingle.h
dom/bindings/test/TestInterfaceMaplike.cpp
dom/bindings/test/TestInterfaceMaplikeObject.cpp
dom/bindings/test/TestInterfaceSetlike.cpp
dom/bindings/test/TestInterfaceSetlikeNode.cpp
dom/bindings/test/mochitest.ini
dom/bindings/test/test_bug1123516_maplikesetlike.html
dom/bindings/test/test_iterable.html
dom/webidl/IterableIterator.webidl
dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl
dom/webidl/moz.build
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1116,17 +1116,20 @@ class CGHeaders(CGWrapper):
                         callbacks))
 
         # Now make sure we're not trying to include the header from inside itself
         declareIncludes.discard(prefix + ".h")
 
         # Now for non-callback descriptors make sure we include any
         # headers needed by Func declarations.
         for desc in descriptors:
-            if desc.interface.isExternal():
+            # If this is an iterator interface generated for a seperate
+            # iterable interface, skip generating type includes, as we have
+            # what we need in IterableIterator.h
+            if desc.interface.isExternal() or desc.interface.isIteratorInterface():
                 continue
 
             def addHeaderForFunc(func):
                 if func is None:
                     return
                 # Include the right class header, which we can only do
                 # if this is a class member function.
                 if not desc.headerIsDefault:
@@ -1143,26 +1146,25 @@ class CGHeaders(CGWrapper):
                 staticTypeOverride = PropertyDefiner.getStringAttr(m, "StaticClassOverride")
                 if staticTypeOverride:
                     bindingHeaders.add("/".join(staticTypeOverride.split("::")) + ".h")
             # getExtendedAttribute() returns a list, extract the entry.
             funcList = desc.interface.getExtendedAttribute("Func")
             if funcList is not None:
                 addHeaderForFunc(funcList[0])
 
-        for desc in descriptors:
-            if desc.interface.maplikeOrSetlike:
+            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.maplikeOrSetlike.keyType,
+                addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.keyType,
                                    desc, None))
-                if desc.interface.maplikeOrSetlike.valueType:
-                    addHeadersForType((desc.interface.maplikeOrSetlike.valueType,
+                if desc.interface.maplikeOrSetlikeOrIterable.valueType:
+                    addHeadersForType((desc.interface.maplikeOrSetlikeOrIterable.valueType,
                                        desc, None))
 
         for d in dictionaries:
             if d.parent:
                 declareIncludes.add(self.getDeclarationFilename(d.parent))
             bindingHeaders.add(self.getDeclarationFilename(d))
 
         for c in callbacks:
@@ -2192,23 +2194,28 @@ class MethodDefiner(PropertyDefiner):
                     "name": 'QueryInterface',
                     "methodInfo": False,
                     "length": 1,
                     "flags": "0",
                     "condition": MemberCondition(None, condition)
                 })
                 continue
 
+            # Iterable methods should be enumerable, maplike/setlike methods
+            # should not.
+            isMaplikeOrSetlikeMethod = (m.isMaplikeOrSetlikeOrIterableMethod() and
+                                        (m.maplikeOrSetlikeOrIterable.isMaplike() or
+                                         m.maplikeOrSetlikeOrIterable.isSetlike()))
             method = {
                 "name": m.identifier.name,
                 "methodInfo": not m.isStatic(),
                 "length": methodLength(m),
                 # Methods generated for a maplike/setlike declaration are not
                 # enumerable.
-                "flags": "JSPROP_ENUMERATE" if not m.isMaplikeOrSetlikeMethod() else "0",
+                "flags": "JSPROP_ENUMERATE" if not isMaplikeOrSetlikeMethod else "0",
                 "condition": PropertyDefiner.getControllingCondition(m, descriptor),
                 "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"),
                 "returnsPromise": m.returnsPromise(),
                 "hasIteratorAlias": "@@iterator" in m.aliases
             }
             if isChromeOnly(m):
                 self.chrome.append(method)
             else:
@@ -2242,29 +2249,33 @@ class MethodDefiner(PropertyDefiner):
                 "selfHostedName": "ArrayValues",
                 "length": 0,
                 "flags": "JSPROP_ENUMERATE",
                 "condition": MemberCondition(None, None)
             })
 
         # 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.maplikeOrSetlike:
+        if descriptor.interface.maplikeOrSetlikeOrIterable:
             if hasIterator(methods, self.regular):
-                raise TypeError("Cannot have maplike/setlike interface with "
+                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.isMaplikeOrSetlikeMethod() and
-                    ((m.maplikeOrSetlike.isMaplike() and
-                      m.identifier.name == "entries") or
-                     (m.maplikeOrSetlike.isSetlike() and
-                      m.identifier.name == "values"))):
+                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),
                     })
@@ -5805,18 +5816,20 @@ class CGArgumentConverter(CGThing):
             "declName": "arg%d" % index,
             "holderName": ("arg%d" % index) + "_holder",
             "obj": "obj",
             "passedToJSImpl": toStringBool(isJSImplementedDescriptor(descriptorProvider))
         }
         # If we have a method generated by the maplike/setlike portion of an
         # interface, arguments can possibly be undefined, but will need to be
         # converted to the key/value type of the backing object. In this case,
-        # use .get() instead of direct access to the argument.
-        if member.isMethod() and member.isMaplikeOrSetlikeMethod():
+        # use .get() instead of direct access to the argument. This won't
+        # matter for iterable since generated functions for those interface
+        # don't take arguments.
+        if member.isMethod() and member.isMaplikeOrSetlikeOrIterableMethod():
             self.replacementVariables["val"] = string.Template(
                 "args.get(${index})").substitute(replacer)
         else:
             self.replacementVariables["val"] = string.Template(
                 "args[${index}]").substitute(replacer)
         haveValueCheck = string.Template(
             "args.hasDefined(${index})").substitute(replacer)
         self.replacementVariables["haveValue"] = haveValueCheck
@@ -7106,20 +7119,26 @@ class CGPerSignatureCall(CGThing):
 
             cgThings.append(
                 CGIfWrapper(CGList(xraySteps),
                             "objIsXray"))
 
         # If this is a method that was generated by a maplike/setlike
         # interface, use the maplike/setlike generator to fill in the body.
         # Otherwise, use CGCallGenerator to call the native method.
-        if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeMethod():
-            cgThings.append(CGMaplikeOrSetlikeMethodGenerator(descriptor,
-                                                              idlNode.maplikeOrSetlike,
-                                                              idlNode.identifier.name))
+        if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeOrIterableMethod():
+            if (idlNode.maplikeOrSetlikeOrIterable.isMaplike() or
+                idlNode.maplikeOrSetlikeOrIterable.isSetlike()):
+                cgThings.append(CGMaplikeOrSetlikeMethodGenerator(descriptor,
+                                                                  idlNode.maplikeOrSetlikeOrIterable,
+                                                                  idlNode.identifier.name))
+            else:
+                cgThings.append(CGIterableMethodGenerator(descriptor,
+                                                          idlNode.maplikeOrSetlikeOrIterable,
+                                                          idlNode.identifier.name))
         else:
             cgThings.append(CGCallGenerator(
                 self.getErrorReport() if self.isFallible() else None,
                 self.getArguments(), argsPre, returnType,
                 self.extendedAttributes, descriptor, nativeMethodName,
                 static, argsPost=argsPost, resultVar=resultVar))
 
         if useCounterName:
@@ -7344,17 +7363,17 @@ class CGMethodCall(CGThing):
             # on anything.
             signature = signatures[0]
             self.cgRoot = CGList([getPerSignatureCall(signature)])
             requiredArgs = requiredArgCount(signature)
 
             # Skip required arguments check for maplike/setlike interfaces, as
             # they can have arguments which are not passed, and are treated as
             # if undefined had been explicitly passed.
-            if requiredArgs > 0 and not method.isMaplikeOrSetlikeMethod():
+            if requiredArgs > 0 and not method.isMaplikeOrSetlikeOrIterableMethod():
                 code = fill(
                     """
                     if (MOZ_UNLIKELY(args.length() < ${requiredArgs})) {
                       return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, "${methodName}");
                     }
                     """,
                     requiredArgs=requiredArgs,
                     methodName=methodName)
@@ -12774,34 +12793,44 @@ class CGForwardDeclarations(CGWrapper):
     declare a class.
     """
     def __init__(self, config, descriptors, mainCallbacks, workerCallbacks,
                  dictionaries, callbackInterfaces, additionalDeclarations=[]):
         builder = ForwardDeclarationBuilder()
 
         # Needed for at least Wrap.
         for d in descriptors:
+            # If this is a generated iterator interface, we only create these
+            # in the generated bindings, and don't need to forward declare.
+            if d.interface.isIteratorInterface():
+                continue
             builder.add(d.nativeType)
             # If we're an interface and we have a maplike/setlike declaration,
             # we'll have helper functions exposed to the native side of our
             # bindings, which will need to show up in the header. If either of
             # our key/value types are interfaces, they'll be passed as
             # arguments to helper functions, and they'll need to be forward
             # declared in the header.
-            if d.interface.maplikeOrSetlike:
-                builder.forwardDeclareForType(d.interface.maplikeOrSetlike.keyType,
+            if d.interface.maplikeOrSetlikeOrIterable:
+                builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.keyType,
                                               config)
-                builder.forwardDeclareForType(d.interface.maplikeOrSetlike.valueType,
-                                              config)
+                if d.interface.maplikeOrSetlikeOrIterable.hasValueType():
+                    builder.forwardDeclareForType(d.interface.maplikeOrSetlikeOrIterable.valueType,
+                                                  config)
 
         # We just about always need NativePropertyHooks
         builder.addInMozillaDom("NativePropertyHooks", isStruct=True)
         builder.addInMozillaDom("ProtoAndIfaceCache")
         # Add the atoms cache type, even if we don't need it.
         for d in descriptors:
+            # Iterators have native types that are template classes, so
+            # creating an 'Atoms' cache type doesn't work for them, and is one
+            # of the cases where we don't need it anyways.
+            if d.interface.isIteratorInterface():
+                continue
             builder.add(d.nativeType + "Atoms", isStruct=True)
 
         for callback in mainCallbacks:
             builder.addInMozillaDom(callback.identifier.name)
             for t in getTypesFromCallback(callback):
                 builder.forwardDeclareForType(t, config,
                                               workerness='mainthreadonly')
 
@@ -12854,16 +12883,18 @@ class CGBindingRoot(CGThing):
         (unionHeaders, unionImplheaders, unionDeclarations, traverseMethods,
          unlinkMethods, unionStructs) = UnionTypes(unionTypes, config)
 
         bindingDeclareHeaders.update(dict.fromkeys(unionHeaders, True))
         bindingHeaders.update(dict.fromkeys(unionImplheaders, True))
 
         bindingDeclareHeaders["mozilla/dom/UnionMember.h"] = len(unionStructs) > 0
         bindingDeclareHeaders["mozilla/dom/BindingUtils.h"] = len(unionStructs) > 0
+        bindingDeclareHeaders["mozilla/dom/IterableIterator.h"] = any(d.interface.isIteratorInterface() or
+                                                                      d.interface.isIterable() for d in descriptors)
 
         def descriptorHasCrossOriginProperties(desc):
             def hasCrossOriginProperty(m):
                 props = memberProperties(m, desc)
                 return (props.isCrossOriginMethod or
                         props.isCrossOriginGetter or
                         props.isCrossOriginSetter)
 
@@ -12917,24 +12948,16 @@ class CGBindingRoot(CGThing):
         callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile,
                                                     isCallback=True)
         jsImplemented = config.getDescriptors(webIDLFile=webIDLFile,
                                               isJSImplemented=True)
         bindingDeclareHeaders["nsWeakReference.h"] = jsImplemented
         bindingHeaders["nsIGlobalObject.h"] = jsImplemented
         bindingHeaders["AtomList.h"] = hasNonEmptyDictionaries or jsImplemented or callbackDescriptors
 
-        def addHeaderBasedOnTypes(header, typeChecker):
-            bindingHeaders[header] = (
-                bindingHeaders.get(header, False) or
-                any(map(typeChecker,
-                        getAllTypes(descriptors + callbackDescriptors,
-                                    dictionaries,
-                                    mainCallbacks + workerCallbacks))))
-
         # Only mainthread things can have hasXPConnectImpls
         provider = config.getDescriptorProvider(False)
 
         def descriptorClearsPropsInSlots(descriptor):
             if not descriptor.wrapperCache:
                 return False
             return any(m.isAttr() and m.getExtendedAttribute("StoreInSlot")
                        for m in descriptor.interface.members)
@@ -14961,17 +14984,17 @@ def getMaplikeOrSetlikeErrorReturn(helpe
     return "return false;\n"
 
 
 def getMaplikeOrSetlikeBackingObject(descriptor, maplikeOrSetlike, helperImpl=None):
     """
     Generate code to get/create a JS backing object for a maplike/setlike
     declaration from the declaration slot.
     """
-    func_prefix = maplikeOrSetlike.maplikeOrSetlikeType.title()
+    func_prefix = maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title()
     ret = fill(
         """
         JS::Rooted<JSObject*> backingObj(cx);
         bool created = false;
         if (!Get${func_prefix}BackingObject(cx, obj, ${slot}, &backingObj, &created)) {
           $*{errorReturn}
         }
         if (created) {
@@ -15398,18 +15421,21 @@ class CGMaplikeOrSetlikeHelperFunctionGe
 class CGMaplikeOrSetlikeHelperGenerator(CGNamespace):
     """
     Declares and defines convenience methods for accessing backing objects on
     setlike/maplike interface. Generates function signatures, un/packs
     backing objects from slot, etc.
     """
     def __init__(self, descriptor, maplikeOrSetlike):
         self.descriptor = descriptor
+        # Since iterables are folded in with maplike/setlike, make sure we've
+        # got the right type here.
+        assert maplikeOrSetlike.isMaplike() or maplikeOrSetlike.isSetlike()
         self.maplikeOrSetlike = maplikeOrSetlike
-        self.namespace = "%sHelpers" % (self.maplikeOrSetlike.maplikeOrSetlikeType.title())
+        self.namespace = "%sHelpers" % (self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType.title())
         self.helpers = [
             CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
                                                       maplikeOrSetlike,
                                                       "Clear"),
             CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
                                                       maplikeOrSetlike,
                                                       "Delete",
                                                       needsKeyArg=True,
@@ -15431,16 +15457,36 @@ class CGMaplikeOrSetlikeHelperGenerator(
             self.helpers.append(
                 CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor,
                                                           maplikeOrSetlike,
                                                           "Add",
                                                           needsKeyArg=True))
         CGNamespace.__init__(self, self.namespace, CGList(self.helpers))
 
 
+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):
+        CGGeneric.__init__(self, fill(
+            """
+            typedef IterableIterator<${nativeType}> itrType;
+            nsRefPtr<itrType> result(new itrType(self,
+                                                 itrType::IterableIteratorType::${itrMethod},
+                                                 &${ifaceName}IteratorBinding::Wrap));
+            """,
+            nativeType=descriptor.nativeType,
+            ifaceName=descriptor.interface.identifier.name,
+            itrMethod=methodName.title()))
+
+
 class GlobalGenRoots():
     """
     Roots for global codegen.
 
     To generate code, call the method associated with the target, and then
     call the appropriate define/declare method.
     """
 
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -21,16 +21,17 @@ class Configuration:
         execfile(filename, glbl)
         config = glbl['DOMInterfaces']
 
         # Build descriptors for all the interfaces we have in the parse data.
         # This allows callers to specify a subset of interfaces by filtering
         # |parseData|.
         self.descriptors = []
         self.interfaces = {}
+        self.descriptorsByName = {}
         self.optimizedOutDescriptorNames = set()
         self.generatedEvents = generatedEvents
         self.maxProtoChainLength = 0
         for thing in parseData:
             if isinstance(thing, IDLImplementsStatement):
                 # Our build system doesn't support dep build involving
                 # addition/removal of "implements" statements that appear in a
                 # different .webidl file than their LHS interface.  Make sure we
@@ -81,25 +82,28 @@ class Configuration:
                                     " in Bindings.conf")
             elif len(entry) == 2:
                 if entry[0].get("workers", False) == entry[1].get("workers", False):
                     raise TypeError("The two entries for interface " + iface.identifier.name +
                                     " in Bindings.conf should not have the same value for 'workers'")
             else:
                 raise TypeError("Interface " + iface.identifier.name +
                                 " should have no more than two entries in Bindings.conf")
-            self.descriptors.extend([Descriptor(self, iface, x) for x in entry])
+            descs = [Descriptor(self, iface, x) for x in entry]
+            self.descriptors.extend(descs)
+            # Setting up descriptorsByName while iterating through interfaces
+            # means we can get the nativeType of iterable interfaces without
+            # having to do multiple loops.
+            for d in descs:
+                self.descriptorsByName.setdefault(d.interface.identifier.name,
+                                                  []).append(d)
 
         # Keep the descriptor list sorted for determinism.
         self.descriptors.sort(lambda x, y: cmp(x.name, y.name))
 
-        self.descriptorsByName = {}
-        for d in self.descriptors:
-            self.descriptorsByName.setdefault(d.interface.identifier.name,
-                                              []).append(d)
 
         self.descriptorsByFile = {}
         for d in self.descriptors:
             self.descriptorsByFile.setdefault(d.interface.filename(),
                                               []).append(d)
 
         self.enums = [e for e in parseData if e.isEnum()]
 
@@ -343,17 +347,28 @@ class Descriptor(DescriptorProvider):
         if self.workers:
             assert 'wantsXrays' not in desc
             self.wantsXrays = False
         else:
             self.wantsXrays = desc.get('wantsXrays', True)
 
         # Read the desc, and fill in the relevant defaults.
         ifaceName = self.interface.identifier.name
-        if self.interface.isExternal():
+        # 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 = ("mozilla::dom::IterableIterator<%s>"
+                                 % itrDesc.nativeType)
+
+        elif self.interface.isExternal():
             assert not self.workers
             nativeTypeDefault = "nsIDOM" + ifaceName
         elif self.interface.isCallback():
             nativeTypeDefault = "mozilla::dom::" + ifaceName
         else:
             if self.workers:
                 nativeTypeDefault = "mozilla::dom::workers::" + ifaceName
             else:
@@ -381,16 +396,18 @@ class Descriptor(DescriptorProvider):
             # test bindings, which don't export, will work correctly.
             basename = os.path.basename(self.interface.filename())
             headerDefault = basename.replace('.webidl', 'Binding.h')
         else:
             if self.workers:
                 headerDefault = "mozilla/dom/workers/bindings/%s.h" % ifaceName
             elif not self.interface.isExternal() and self.interface.getExtendedAttribute("HeaderFile"):
                 headerDefault = self.interface.getExtendedAttribute("HeaderFile")[0]
+            elif self.interface.isIteratorInterface():
+                headerDefault = "mozilla/dom/IterableIterator.h"
             else:
                 headerDefault = self.nativeType
                 headerDefault = headerDefault.replace("::", "/") + ".h"
         self.headerFile = desc.get('headerFile', headerDefault)
         self.headerIsDefault = self.headerFile == headerDefault
         if self.jsImplParent == self.nativeType:
             self.jsImplParentHeader = self.headerFile
         else:
@@ -509,16 +526,17 @@ class Descriptor(DescriptorProvider):
                 iface = self.interface
                 while iface:
                     iface.setUserData('hasProxyDescendant', True)
                     iface = iface.parent
 
         if desc.get('wantsQI', None) is not None:
             self._wantsQI = desc.get('wantsQI', None)
         self.wrapperCache = (not self.interface.isCallback() and
+                             not self.interface.isIteratorInterface() and
                              desc.get('wrapperCache', True))
 
         def make_name(name):
             return name + "_workers" if self.workers else name
         self.name = make_name(interface.identifier.name)
 
         # self.extendedAttributes is a dict of dicts, keyed on
         # all/getterOnly/setterOnly and then on member name. Values are an
new file mode 100644
--- /dev/null
+++ b/dom/bindings/IterableIterator.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include "mozilla/dom/IterableIterator.h"
+
+namespace mozilla {
+namespace dom {
+
+// Due to IterableIterator being a templated class, we implement the necessary
+// CC bits in a superclass that IterableIterator then inherits from. This allows
+// us to put the macros outside of the header. The base class has pure virtual
+// functions for Traverse/Unlink that the templated subclasses will override.
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IterableIteratorBase)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IterableIteratorBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IterableIteratorBase)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IterableIteratorBase)
+  tmp->TraverseHelper(cb);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IterableIteratorBase)
+  tmp->UnlinkHelper();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IterableIteratorBase)
+NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/dom/bindings/IterableIterator.h
@@ -0,0 +1,192 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+/**
+ * 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.
+ *
+ * For iterable interfaces, the implementation class will need to contain three
+ * functions:
+ *
+ * - size_t GetIterableLength()
+ *   - Returns the number of elements available to iterate over
+ * - [type] GetKeyAtIndex(size_t index)
+ *   - Returns the key at the requested index
+ * - [type] GetValueAtIndex(size_t index)
+ *   - Returns the value at the requested index, or the key again if this is
+ *     a single type iterator.
+ *
+ * Examples of iterable interface implementations can be found in the bindings
+ * test directory.
+ */
+
+#ifndef mozilla_dom_IterableIterator_h
+#define mozilla_dom_IterableIterator_h
+
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "nsPIDOMWindow.h"
+#include "nsCOMPtr.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "jswrapper.h"
+#include "mozilla/dom/IterableIteratorBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class IterableIteratorBase : public nsISupports
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(IterableIteratorBase)
+  typedef enum {
+    Keys = 0,
+    Values,
+    Entries
+  } IterableIteratorType;
+
+  IterableIteratorBase() {}
+  virtual void UnlinkHelper() = 0;
+  virtual void TraverseHelper(nsCycleCollectionTraversalCallback& cb) = 0;
+protected:
+
+  virtual ~IterableIteratorBase() {}
+};
+
+template <typename T>
+class IterableIterator final : public IterableIteratorBase
+{
+public:
+
+  typedef bool (*WrapFunc)(JSContext* aCx,
+                           mozilla::dom::IterableIterator<T>* aObject,
+                           JS::Handle<JSObject*> aGivenProto,
+                           JS::MutableHandle<JSObject*> aReflector);
+  IterableIterator(T* aIterableObj, IterableIteratorType aIteratorType, WrapFunc aWrapFunc)
+    : mIteratorType(aIteratorType)
+    , mIterableObj(aIterableObj)
+    , mIndex(0)
+    , mWrapFunc(aWrapFunc)
+  {
+    MOZ_ASSERT(mIterableObj);
+    MOZ_ASSERT(mWrapFunc);
+  }
+
+  void
+  DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
+             bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv)
+  {
+    RootedDictionary<IterableKeyOrValueResult> dict(aCx);
+    dict.mDone = aDone;
+    dict.mValue = aValue;
+    JS::Rooted<JS::Value> dictValue(aCx);
+    if (!ToJSValue(aCx, dict, &dictValue)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+    aResult.set(&dictValue.toObject());
+  }
+
+  void
+  Next(JSContext* aCx, JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv)
+  {
+    JS::Rooted<JS::Value> value(aCx, JS::UndefinedValue());
+    if (mIndex >= mIterableObj->GetIterableLength()) {
+      DictReturn(aCx, aResult, true, value, aRv);
+      return;
+    }
+    switch (mIteratorType) {
+    case IterableIteratorType::Keys:
+    {
+      if (!ToJSValue(aCx, mIterableObj->GetKeyAtIndex(mIndex), &value)) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return;
+      }
+      DictReturn(aCx, aResult, false, value, aRv);
+      break;
+    }
+    case IterableIteratorType::Values:
+    {
+      if (!ToJSValue(aCx, mIterableObj->GetValueAtIndex(mIndex), &value)) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return;
+      }
+      DictReturn(aCx, aResult, false, value, aRv);
+      break;
+    }
+    case IterableIteratorType::Entries:
+    {
+      JS::Rooted<JS::Value> key(aCx);
+      if (!ToJSValue(aCx, mIterableObj->GetKeyAtIndex(mIndex), &key)) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return;
+      }
+      if (!ToJSValue(aCx, mIterableObj->GetValueAtIndex(mIndex), &value)) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return;
+      }
+      RootedDictionary<IterableKeyAndValueResult> 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(key, mozilla::fallible)) {
+        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+        return;
+      }
+      if (!dict.mValue.AppendElement(value, mozilla::fallible)) {
+        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+        return;
+      }
+      JS::Rooted<JS::Value> dictValue(aCx);
+      if (!ToJSValue(aCx, dict, &dictValue)) {
+        aRv.Throw(NS_ERROR_FAILURE);
+        return;
+      }
+      aResult.set(&dictValue.toObject());
+      break;
+    }
+    default:
+      MOZ_CRASH("Invalid iterator type!");
+    }
+    ++mIndex;
+  }
+  virtual ~IterableIterator() {}
+
+  bool
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aObj)
+  {
+    return (*mWrapFunc)(aCx, this, aGivenProto, aObj);
+  }
+
+protected:
+  // Tells whether this is a key, value, or entries iterator.
+  IterableIteratorType mIteratorType;
+  // Binding Implementation Object that we're iterating over.
+  nsRefPtr<T> mIterableObj;
+  // Current index of iteration.
+  uint32_t mIndex;
+  // Function pointer to binding-type-specific Wrap() call for this iterator.
+  WrapFunc mWrapFunc;
+
+  // 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<T>* tmp = this;
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIterableObj);
+  }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_IterableIterator_h
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -22,16 +22,17 @@ EXPORTS.mozilla.dom += [
     'CallbackInterface.h',
     'CallbackObject.h',
     'Date.h',
     'DOMJSClass.h',
     'DOMJSProxyHandler.h',
     'DOMString.h',
     'Errors.msg',
     'Exceptions.h',
+    'IterableIterator.h',
     'JSSlots.h',
     'MozMap.h',
     'NonRefcountedDOMObject.h',
     'Nullable.h',
     'PrimitiveConversions.h',
     'RootedDictionary.h',
     'StructuredClone.h',
     'ToJSValue.h',
@@ -69,36 +70,41 @@ LOCAL_INCLUDES += [
 
 UNIFIED_SOURCES += [
     'BindingUtils.cpp',
     'CallbackInterface.cpp',
     'CallbackObject.cpp',
     'Date.cpp',
     'DOMJSProxyHandler.cpp',
     'Exceptions.cpp',
+    'IterableIterator.cpp',
     'ToJSValue.cpp',
 ]
 
 SOURCES += [
     'StructuredClone.cpp',
 ]
 
 # Tests for maplike and setlike require bindings to be built, which means they
 # must be included in libxul. This breaks the "no test classes are exported"
 # rule stated in the test/ directory, but it's the only way this will work.
 # Test classes are only built in debug mode, and all tests requiring use of
 # them are only run in debug mode.
 if CONFIG['MOZ_DEBUG']:
     EXPORTS.mozilla.dom += [
+        "test/TestInterfaceIterableDouble.h",
+        "test/TestInterfaceIterableSingle.h",
         "test/TestInterfaceMaplike.h",
         "test/TestInterfaceMaplikeObject.h",
         "test/TestInterfaceSetlike.h",
         "test/TestInterfaceSetlikeNode.h"
         ]
     UNIFIED_SOURCES += [
+        "test/TestInterfaceIterableDouble.cpp",
+        "test/TestInterfaceIterableSingle.cpp",
         "test/TestInterfaceMaplike.cpp",
         "test/TestInterfaceMaplikeObject.cpp",
         "test/TestInterfaceSetlike.cpp",
         "test/TestInterfaceSetlikeNode.cpp",
         ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -535,16 +535,19 @@ class IDLExternalInterface(IDLObjectWith
 
     def finish(self, scope):
         IDLExposureMixins.finish(self, scope)
         pass
 
     def validate(self):
         pass
 
+    def isIteratorInterface(self):
+        return False
+
     def isExternal(self):
         return True
 
     def isInterface(self):
         return True
 
     def isConsequential(self):
         return False
@@ -635,17 +638,17 @@ class IDLInterface(IDLObjectWithScope, I
         assert isinstance(name, IDLUnresolvedIdentifier)
         assert isKnownNonPartial or not parent
         assert isKnownNonPartial or len(members) == 0
 
         self.parent = None
         self._callback = False
         self._finished = False
         self.members = []
-        self.maplikeOrSetlike = None
+        self.maplikeOrSetlikeOrIterable = None
         self._partialInterfaces = []
         self._extendedAttrDict = {}
         # namedConstructors needs deterministic ordering because bindings code
         # outputs the constructs in the order that namedConstructors enumerates
         # them.
         self.namedConstructors = list()
         self.implementedInterfaces = set()
         self._consequential = False
@@ -659,16 +662,19 @@ class IDLInterface(IDLObjectWithScope, I
         self.interfacesImplementingSelf = set()
         self._hasChildInterfaces = False
         self._isOnGlobalProtoChain = False
         # Tracking of the number of reserved slots we need for our
         # members and those of ancestor interfaces.
         self.totalMembersInSlots = 0
         # Tracking of the number of own own members we have in slots
         self._ownMembersInSlots = 0
+        # If this is an iterator interface, we need to know what iterable
+        # interface we're iterating for in order to get its nativeType.
+        self.iterableInterface = None
 
         IDLObjectWithScope.__init__(self, location, parentScope, name)
         IDLExposureMixins.__init__(self, location)
 
         if isKnownNonPartial:
             self.setNonPartial(location, parent, members)
 
     def __str__(self):
@@ -677,16 +683,23 @@ class IDLInterface(IDLObjectWithScope, I
     def ctor(self):
         identifier = IDLUnresolvedIdentifier(self.location, "constructor",
                                              allowForbidden=True)
         try:
             return self._lookupIdentifier(identifier)
         except:
             return None
 
+    def isIterable(self):
+        return (self.maplikeOrSetlikeOrIterable and
+                self.maplikeOrSetlikeOrIterable.isIterable())
+
+    def isIteratorInterface(self):
+        return self.iterableInterface is not None
+
     def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
         assert isinstance(scope, IDLScope)
         assert isinstance(originalObject, IDLInterfaceMember)
         assert isinstance(newObject, IDLInterfaceMember)
 
         retval = IDLScope.resolveIdentifierConflict(self, scope, identifier,
                                                     originalObject, newObject)
 
@@ -713,32 +726,32 @@ class IDLInterface(IDLObjectWithScope, I
             partial.finish(scope)
             self.addExtendedAttributes(partial.propagatedExtendedAttrs)
             self.members.extend(partial.members)
 
         # Generate maplike/setlike interface members. Since generated members
         # need to be treated like regular interface members, do this before
         # things like exposure setting.
         for member in self.members:
-            if member.isMaplikeOrSetlike():
+            if member.isMaplikeOrSetlikeOrIterable():
                 # Check that we only have one interface declaration (currently
                 # there can only be one maplike/setlike declaration per
                 # interface)
-                if self.maplikeOrSetlike:
+                if self.maplikeOrSetlikeOrIterable:
                     raise WebIDLError("%s declaration used on "
                                       "interface that already has %s "
                                       "declaration" %
-                                      (member.maplikeOrSetlikeType,
-                                       self.maplikeOrSetlike.maplikeOrSetlikeType),
-                                      [self.maplikeOrSetlike.location,
+                                      (member.maplikeOrSetlikeOrIterableType,
+                                       self.maplikeOrSetlike.maplikeOrSetlikeOrIterableType),
+                                      [self.maplikeOrSetlikeOrIterable.location,
                                        member.location])
-                self.maplikeOrSetlike = member
+                self.maplikeOrSetlikeOrIterable = member
                 # If we've got a maplike or setlike declaration, we'll be building all of
                 # our required methods in Codegen. Generate members now.
-                self.maplikeOrSetlike.expand(self.members, self.isJSImplemented())
+                self.maplikeOrSetlikeOrIterable.expand(self.members, self.isJSImplemented())
 
         # Now that we've merged in our partial interfaces, set the
         # _exposureGlobalNames on any members that don't have it set yet.  Note
         # that any partial interfaces that had [Exposed] set have already set up
         # _exposureGlobalNames on all the members coming from them, so this is
         # just implementing the "members default to interface that defined them"
         # and "partial interfaces default to interface they're a partial for"
         # rules from the spec.
@@ -879,46 +892,46 @@ class IDLInterface(IDLObjectWithScope, I
             if not self.exposureSet.issubset(iface.exposureSet):
                 raise WebIDLError("Interface %s is exposed in globals where its "
                                   "consequential interface %s is not exposed." %
                                   (self.identifier.name, iface.identifier.name),
                                   [self.location, iface.location])
 
             # If we have a maplike or setlike, and the consequential interface
             # also does, throw an error.
-            if iface.maplikeOrSetlike and self.maplikeOrSetlike:
-                raise WebIDLError("Maplike/setlike interface %s cannot have "
-                                  "maplike/setlike interface %s as a "
+            if iface.maplikeOrSetlikeOrIterable and self.maplikeOrSetlikeOrIterable:
+                raise WebIDLError("Maplike/setlike/iterable interface %s cannot have "
+                                  "maplike/setlike/iterable interface %s as a "
                                   "consequential interface" %
                                   (self.identifier.name,
                                    iface.identifier.name),
-                                  [self.maplikeOrSetlike.location,
-                                   iface.maplikeOrSetlike.location])
+                                  [self.maplikeOrSetlikeOrIterable.location,
+                                   iface.maplikeOrSetlikeOrIterable.location])
             additionalMembers = iface.originalMembers
             for additionalMember in additionalMembers:
                 for member in self.members:
                     if additionalMember.identifier.name == member.identifier.name:
                         raise WebIDLError(
                             "Multiple definitions of %s on %s coming from 'implements' statements" %
                             (member.identifier.name, self),
                             [additionalMember.location, member.location])
             self.members.extend(additionalMembers)
             iface.interfacesImplementingSelf.add(self)
 
         for ancestor in self.getInheritedInterfaces():
             ancestor.interfacesBasedOnSelf.add(self)
-            if (ancestor.maplikeOrSetlike is not None and
-                self.maplikeOrSetlike is not None):
+            if (ancestor.maplikeOrSetlikeOrIterable is not None and
+                self.maplikeOrSetlikeOrIterable is not None):
                 raise WebIDLError("Cannot have maplike/setlike on %s that "
                                   "inherits %s, which is already "
                                   "maplike/setlike" %
                                   (self.identifier.name,
                                    ancestor.identifier.name),
-                                  [self.maplikeOrSetlike.location,
-                                   ancestor.maplikeOrSetlike.location])
+                                  [self.maplikeOrSetlikeOrIterable.location,
+                                   ancestor.maplikeOrSetlikeOrIterable.location])
             for ancestorConsequential in ancestor.getConsequentialInterfaces():
                 ancestorConsequential.interfacesBasedOnSelf.add(self)
 
         # Deal with interfaces marked [Unforgeable], now that we have our full
         # member list, except unforgeables pulled in from parents.  We want to
         # do this before we set "originatingInterface" on our unforgeable
         # members.
         if self.getExtendedAttribute("Unforgeable"):
@@ -992,22 +1005,22 @@ class IDLInterface(IDLObjectWithScope, I
                 # attributes/methods of ancestor interfaces, with their
                 # corresponding getters, on our interface, but that gets pretty
                 # complicated and seems unnecessary.
                 self.members.append(unforgeableMember)
 
         # At this point, we have all of our members. If the current interface
         # uses maplike/setlike, check for collisions anywhere in the current
         # interface or higher in the inheritance chain.
-        if self.maplikeOrSetlike:
+        if self.maplikeOrSetlikeOrIterable:
             testInterface = self
             isAncestor = False
             while testInterface:
-                self.maplikeOrSetlike.checkCollisions(testInterface.members,
-                                                      isAncestor)
+                self.maplikeOrSetlikeOrIterable.checkCollisions(testInterface.members,
+                                                                isAncestor)
                 isAncestor = True
                 testInterface = testInterface.parent
 
         # Ensure that there's at most one of each {named,indexed}
         # {getter,setter,creator,deleter}, at most one stringifier,
         # and at most one legacycaller.  Note that this last is not
         # quite per spec, but in practice no one overloads
         # legacycallers.
@@ -3362,17 +3375,18 @@ class IDLUndefinedValue(IDLObject):
 
 
 class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins):
 
     Tags = enum(
         'Const',
         'Attr',
         'Method',
-        'MaplikeOrSetlike'
+        'MaplikeOrSetlike',
+        'Iterable'
     )
 
     Special = enum(
         'Static',
         'Stringifier'
     )
 
     AffectsValues = ("Nothing", "Everything")
@@ -3388,16 +3402,20 @@ class IDLInterfaceMember(IDLObjectWithId
         return self.tag == IDLInterfaceMember.Tags.Method
 
     def isAttr(self):
         return self.tag == IDLInterfaceMember.Tags.Attr
 
     def isConst(self):
         return self.tag == IDLInterfaceMember.Tags.Const
 
+    def isMaplikeOrSetlikeOrIterable(self):
+        return (self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike or
+                self.tag == IDLInterfaceMember.Tags.Iterable)
+
     def isMaplikeOrSetlike(self):
         return self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike
 
     def addExtendedAttributes(self, attrs):
         for attr in attrs:
             self.handleExtendedAttribute(attr)
             attrlist = attr.listValue()
             self._extendedAttrDict[attr.identifier()] = attrlist if len(attrlist) else True
@@ -3465,264 +3483,309 @@ class IDLInterfaceMember(IDLObjectWithId
 
     def _addAlias(self, alias):
         if alias in self.aliases:
             raise WebIDLError("Duplicate [Alias=%s] on attribute" % alias,
                               [self.location])
         self.aliases.append(alias)
 
 
-# MaplikeOrSetlike adds a trait to an interface, like map or iteration
-# functions. To handle them while still getting all of the generated binding
-# code taken care of, we treat them as macros that are expanded into members
-# based on parsed values.
-class IDLMaplikeOrSetlike(IDLInterfaceMember):
-
-    MaplikeOrSetlikeTypes = enum(
-        'maplike',
-        'setlike'
-    )
-
-    def __init__(self, location, identifier, maplikeOrSetlikeType,
-                 readonly, keyType, valueType):
-        IDLInterfaceMember.__init__(self, location, identifier,
-                                    IDLInterfaceMember.Tags.MaplikeOrSetlike)
-
+class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember):
+
+    def __init__(self, location, identifier, ifaceType, keyType, valueType, ifaceKind):
+        IDLInterfaceMember.__init__(self, location, identifier, ifaceKind)
         assert isinstance(keyType, IDLType)
-        assert isinstance(valueType, IDLType)
-        self.maplikeOrSetlikeType = maplikeOrSetlikeType
-        self.readonly = readonly
+        assert ifaceType in ['maplike', 'setlike', 'iterable']
+        if valueType is not None:
+            assert isinstance(valueType, IDLType)
         self.keyType = keyType
         self.valueType = valueType
-        self.slotIndex = None
+        self.maplikeOrSetlikeOrIterableType = ifaceType
         self.disallowedMemberNames = []
         self.disallowedNonMethodNames = []
 
-        # When generating JSAPI access code, we need to know the backing object
-        # type prefix to create the correct function. Generate here for reuse.
-        if self.isMaplike():
-            self.prefix = 'Map'
-        elif self.isSetlike():
-            self.prefix = 'Set'
-
-    def __str__(self):
-        return "declared '%s' with key '%s'" % (self.maplikeOrSetlikeType, self.keyType)
-
     def isMaplike(self):
-        return self.maplikeOrSetlikeType == "maplike"
+        return self.maplikeOrSetlikeOrIterableType == "maplike"
 
     def isSetlike(self):
-        return self.maplikeOrSetlikeType == "setlike"
+        return self.maplikeOrSetlikeOrIterableType == "setlike"
+
+    def isIterable(self):
+        return self.maplikeOrSetlikeOrIterableType == "iterable"
+
+    def hasValueType(self):
+        return self.valueType is not None
 
     def checkCollisions(self, members, isAncestor):
         for member in members:
             # Check that there are no disallowed members
             if (member.identifier.name in self.disallowedMemberNames and
-                not ((member.isMethod() and member.isMaplikeOrSetlikeMethod()) or
+                not ((member.isMethod() and member.isMaplikeOrSetlikeOrIterableMethod()) or
                      (member.isAttr() and member.isMaplikeOrSetlikeAttr()))):
                 raise WebIDLError("Member '%s' conflicts "
                                   "with reserved %s name." %
                                   (member.identifier.name,
-                                   self.maplikeOrSetlikeType),
+                                   self.maplikeOrSetlikeOrIterableType),
                                   [self.location, member.location])
             # Check that there are no disallowed non-method members
             if (isAncestor or (member.isAttr() or member.isConst()) and
                 member.identifier.name in self.disallowedNonMethodNames):
                 raise WebIDLError("Member '%s' conflicts "
                                   "with reserved %s method." %
                                   (member.identifier.name,
-                                   self.maplikeOrSetlikeType),
+                                   self.maplikeOrSetlikeOrIterableType),
                                   [self.location, member.location])
 
-    def expand(self, members, isJSImplemented):
-        """
-        In order to take advantage of all of the method machinery in Codegen,
-        we generate our functions as if they were part of the interface
-        specification during parsing.
-        """
-        def addMethod(name, allowExistingOperations, returnType, args=[],
-                      chromeOnly=False, isPure=False, affectsNothing=False):
-            """
-            Create an IDLMethod based on the parameters passed in. chromeOnly is only
-            True for read-only js implemented classes, to implement underscore
-            prefixed convenience functions would otherwise not be available,
-            unlike the case of C++ bindings. isPure is only True for
-            idempotent functions, so it is not valid for things like keys,
-            values, etc. that return a new object every time.
-
-            """
-
-            # Only add name to lists for collision checks if it's not chrome
-            # only.
-            if chromeOnly:
-                name = "__" + name
+    def addMethod(self, name, members, allowExistingOperations, returnType, args=[],
+                  chromeOnly=False, isPure=False, affectsNothing=False, newObject=False):
+        """
+        Create an IDLMethod based on the parameters passed in.
+
+        - members is the member list to add this function to, since this is
+          called during the member expansion portion of interface object
+          building.
+
+        - chromeOnly is only True for read-only js implemented classes, to
+        implement underscore prefixed convenience functions which would
+        otherwise not be available, unlike the case of C++ bindings.
+
+        - isPure is only True for idempotent functions, so it is not valid for
+        things like keys, values, etc. that return a new object every time.
+
+        - affectsNothing means that nothing changes due to this method, which
+          affects JIT optimization behavior
+
+        - newObject means the method creates and returns a new object.
+
+        """
+        # Only add name to lists for collision checks if it's not chrome
+        # only.
+        if chromeOnly:
+            name = "__" + name
+        else:
+            if not allowExistingOperations:
+                self.disallowedMemberNames.append(name)
             else:
-                if not allowExistingOperations:
-                    self.disallowedMemberNames.append(name)
-                else:
-                    self.disallowedNonMethodNames.append(name)
-
-            # If allowExistingOperations is True, and another operation exists
-            # with the same name as the one we're trying to add, don't add the
-            # maplike/setlike operation. However, if the operation is static,
-            # then fail by way of creating the function, which will cause a
-            # naming conflict, per the spec.
-            if allowExistingOperations:
-                for m in members:
-                    if m.identifier.name == name and m.isMethod() and not m.isStatic():
-                        return
-
-            method = IDLMethod(self.location,
-                               IDLUnresolvedIdentifier(self.location, name, allowDoubleUnderscore=chromeOnly),
-                               returnType, args, maplikeOrSetlike=self)
-
-            # We need to be able to throw from declaration methods
+                self.disallowedNonMethodNames.append(name)
+        # If allowExistingOperations is True, and another operation exists
+        # with the same name as the one we're trying to add, don't add the
+        # maplike/setlike operation. However, if the operation is static,
+        # then fail by way of creating the function, which will cause a
+        # naming conflict, per the spec.
+        if allowExistingOperations:
+            for m in members:
+                if m.identifier.name == name and m.isMethod() and not m.isStatic():
+                    return
+        method = IDLMethod(self.location,
+                           IDLUnresolvedIdentifier(self.location, name, allowDoubleUnderscore=chromeOnly),
+                           returnType, args, maplikeOrSetlikeOrIterable=self)
+        # We need to be able to throw from declaration methods
+        method.addExtendedAttributes(
+            [IDLExtendedAttribute(self.location, ("Throws",))])
+        if chromeOnly:
             method.addExtendedAttributes(
-                [IDLExtendedAttribute(self.location, ("Throws",))])
-            if chromeOnly:
-                method.addExtendedAttributes(
-                    [IDLExtendedAttribute(self.location, ("ChromeOnly",))])
-            if isPure:
-                method.addExtendedAttributes(
-                    [IDLExtendedAttribute(self.location, ("Pure",))])
-            # Following attributes are used for keys/values/entries. Can't mark
-            # them pure, since they return a new object each time they are run.
-            if affectsNothing:
-                method.addExtendedAttributes(
-                    [IDLExtendedAttribute(self.location, ("DependsOn", "Everything")),
-                     IDLExtendedAttribute(self.location, ("Affects", "Nothing"))])
-            members.append(method)
-
-        # Both maplike and setlike have a size attribute
-        members.append(IDLAttribute(self.location,
-                                    IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), "size"),
-                                    BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
-                                    True,
-                                    maplikeOrSetlike=self))
-        self.reserved_ro_names = ["size"]
-
-        # object entries()
-        addMethod("entries", False, BuiltinTypes[IDLBuiltinType.Types.object],
-                  affectsNothing=True)
-        # object keys()
-        addMethod("keys", False, BuiltinTypes[IDLBuiltinType.Types.object],
-                  affectsNothing=True)
-        # object values()
-        addMethod("values", False, BuiltinTypes[IDLBuiltinType.Types.object],
-                  affectsNothing=True)
-
-        # void forEach(callback(valueType, keyType), thisVal)
-        foreachArguments = [IDLArgument(self.location,
-                                        IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
-                                                                "callback"),
-                                        BuiltinTypes[IDLBuiltinType.Types.object]),
-                            IDLArgument(self.location,
-                                        IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
-                                                                "thisArg"),
-                                        BuiltinTypes[IDLBuiltinType.Types.any],
-                                        optional=True)]
-        addMethod("forEach", False, BuiltinTypes[IDLBuiltinType.Types.void],
-                  foreachArguments)
-
-        def getKeyArg():
-            return IDLArgument(self.location,
-                               IDLUnresolvedIdentifier(self.location, "key"),
-                               self.keyType)
-
-        # boolean has(keyType key)
-        addMethod("has", False, BuiltinTypes[IDLBuiltinType.Types.boolean],
-                  [getKeyArg()], isPure=True)
-
-        if not self.readonly:
-            # void clear()
-            addMethod("clear", True, BuiltinTypes[IDLBuiltinType.Types.void],
-                      [])
-            # boolean delete(keyType key)
-            addMethod("delete", True,
-                      BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()])
-
-        # Always generate underscored functions (e.g. __add, __clear) for js
-        # implemented interfaces as convenience functions.
-        if isJSImplemented:
-            # void clear()
-            addMethod("clear", True, BuiltinTypes[IDLBuiltinType.Types.void],
-                      [], chromeOnly=True)
-            # boolean delete(keyType key)
-            addMethod("delete", True,
-                      BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()],
-                      chromeOnly=True)
-
-        if self.isSetlike():
-            if not self.readonly:
-                # Add returns the set object it just added to.
-                # object add(keyType key)
-
-                addMethod("add", True,
-                          BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()])
-            if isJSImplemented:
-                addMethod("add", True,
-                          BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()],
-                          chromeOnly=True)
-            return
-
-        # If we get this far, we're a maplike declaration.
-
-        # valueType get(keyType key)
-        #
-        # Note that instead of the value type, we're using any here. The
-        # validity checks should happen as things are inserted into the map,
-        # and using any as the return type makes code generation much simpler.
-        #
-        # TODO: Bug 1155340 may change this to use specific type to provide
-        # more info to JIT.
-        addMethod("get", False, BuiltinTypes[IDLBuiltinType.Types.any],
-                  [getKeyArg()], isPure=True)
-
-        def getValueArg():
-            return IDLArgument(self.location,
-                               IDLUnresolvedIdentifier(self.location, "value"),
-                               self.valueType)
-
-        if not self.readonly:
-            addMethod("set", True, BuiltinTypes[IDLBuiltinType.Types.object],
-                      [getKeyArg(), getValueArg()])
-        if isJSImplemented:
-            addMethod("set", True, BuiltinTypes[IDLBuiltinType.Types.object],
-                      [getKeyArg(), getValueArg()], chromeOnly=True)
+                [IDLExtendedAttribute(self.location, ("ChromeOnly",))])
+        if isPure:
+            method.addExtendedAttributes(
+                [IDLExtendedAttribute(self.location, ("Pure",))])
+        # Following attributes are used for keys/values/entries. Can't mark
+        # them pure, since they return a new object each time they are run.
+        if affectsNothing:
+            method.addExtendedAttributes(
+                [IDLExtendedAttribute(self.location, ("DependsOn", "Everything")),
+                 IDLExtendedAttribute(self.location, ("Affects", "Nothing"))])
+        if newObject:
+            method.addExtendedAttributes(
+                [IDLExtendedAttribute(self.location, ("NewObject",))])
+        members.append(method)
 
     def resolve(self, parentScope):
         self.keyType.resolveType(parentScope)
-        self.valueType.resolveType(parentScope)
+        if self.valueType:
+            self.valueType.resolveType(parentScope)
 
     def finish(self, scope):
         IDLInterfaceMember.finish(self, scope)
         if not self.keyType.isComplete():
             t = self.keyType.complete(scope)
 
             assert not isinstance(t, IDLUnresolvedType)
             assert not isinstance(t, IDLTypedefType)
             assert not isinstance(t.name, IDLUnresolvedIdentifier)
             self.keyType = t
-        if not self.valueType.isComplete():
+        if self.valueType and not self.valueType.isComplete():
             t = self.valueType.complete(scope)
 
             assert not isinstance(t, IDLUnresolvedType)
             assert not isinstance(t, IDLTypedefType)
             assert not isinstance(t.name, IDLUnresolvedIdentifier)
             self.valueType = t
 
     def validate(self):
         IDLInterfaceMember.validate(self)
 
     def handleExtendedAttribute(self, attr):
         IDLInterfaceMember.handleExtendedAttribute(self, attr)
 
     def _getDependentObjects(self):
-        return set([self.keyType, self.valueType])
-
+        if self.valueType:
+            return set([self.keyType, self.valueType])
+        return set([self.keyType])
+
+# Iterable adds ES6 iterator style functions and traits
+# (keys/values/entries/@@iterator) to an interface.
+class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase):
+
+    def __init__(self, location, identifier, keyType, valueType=None, scope=None):
+        IDLMaplikeOrSetlikeOrIterableBase.__init__(self, location, identifier,
+                                                   "iterable", keyType, valueType,
+                                                   IDLInterfaceMember.Tags.Iterable)
+        self.iteratorType = None
+
+    def __str__(self):
+        return "declared iterable with key '%s' and value '%s'" % (self.keyType, self.valueType)
+
+    def expand(self, members, isJSImplemented):
+        """
+        In order to take advantage of all of the method machinery in Codegen,
+        we generate our functions as if they were part of the interface
+        specification during parsing.
+        """
+        # object entries()
+        self.addMethod("entries", members, False, self.iteratorType,
+                       affectsNothing=True, newObject=True)
+        # object keys()
+        self.addMethod("keys", members, False, self.iteratorType,
+                       affectsNothing=True, newObject=True)
+        # object values()
+        self.addMethod("values", members, False, self.iteratorType,
+                       affectsNothing=True, newObject=True)
+
+# MaplikeOrSetlike adds ES6 map-or-set-like traits to an interface.
+class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase):
+
+    def __init__(self, location, identifier, maplikeOrSetlikeType,
+                 readonly, keyType, valueType):
+        IDLMaplikeOrSetlikeOrIterableBase.__init__(self, location, identifier, maplikeOrSetlikeType,
+                                                   keyType, valueType, IDLInterfaceMember.Tags.MaplikeOrSetlike)
+        self.readonly = readonly
+        self.slotIndex = None
+
+        # When generating JSAPI access code, we need to know the backing object
+        # type prefix to create the correct function. Generate here for reuse.
+        if self.isMaplike():
+            self.prefix = 'Map'
+        elif self.isSetlike():
+            self.prefix = 'Set'
+
+    def __str__(self):
+        return "declared '%s' with key '%s'" % (self.maplikeOrSetlikeOrIterableType, self.keyType)
+
+    def expand(self, members, isJSImplemented):
+        """
+        In order to take advantage of all of the method machinery in Codegen,
+        we generate our functions as if they were part of the interface
+        specification during parsing.
+        """
+        # Both maplike and setlike have a size attribute
+        members.append(IDLAttribute(self.location,
+                                    IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"), "size"),
+                                    BuiltinTypes[IDLBuiltinType.Types.unsigned_long],
+                                    True,
+                                    maplikeOrSetlike=self))
+        self.reserved_ro_names = ["size"]
+
+        # object entries()
+        self.addMethod("entries", members, False, BuiltinTypes[IDLBuiltinType.Types.object],
+                       affectsNothing=True)
+        # 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)
+
+        # void forEach(callback(valueType, keyType), thisVal)
+        foreachArguments = [IDLArgument(self.location,
+                                        IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
+                                                                "callback"),
+                                        BuiltinTypes[IDLBuiltinType.Types.object]),
+                            IDLArgument(self.location,
+                                        IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
+                                                                "thisArg"),
+                                        BuiltinTypes[IDLBuiltinType.Types.any],
+                                        optional=True)]
+        self.addMethod("forEach", members, False, BuiltinTypes[IDLBuiltinType.Types.void],
+                       foreachArguments)
+
+        def getKeyArg():
+            return IDLArgument(self.location,
+                               IDLUnresolvedIdentifier(self.location, "key"),
+                               self.keyType)
+
+        # boolean has(keyType key)
+        self.addMethod("has", members, False, BuiltinTypes[IDLBuiltinType.Types.boolean],
+                       [getKeyArg()], isPure=True)
+
+        if not self.readonly:
+            # void clear()
+            self.addMethod("clear", members, True, BuiltinTypes[IDLBuiltinType.Types.void],
+                           [])
+            # boolean delete(keyType key)
+            self.addMethod("delete", members, True,
+                           BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()])
+
+        # Always generate underscored functions (e.g. __add, __clear) for js
+        # implemented interfaces as convenience functions.
+        if isJSImplemented:
+            # void clear()
+            self.addMethod("clear", members, True, BuiltinTypes[IDLBuiltinType.Types.void],
+                           [], chromeOnly=True)
+            # boolean delete(keyType key)
+            self.addMethod("delete", members, True,
+                           BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()],
+                           chromeOnly=True)
+
+        if self.isSetlike():
+            if not self.readonly:
+                # Add returns the set object it just added to.
+                # object add(keyType key)
+
+                self.addMethod("add", members, True,
+                               BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()])
+            if isJSImplemented:
+                self.addMethod("add", members, True,
+                               BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()],
+                               chromeOnly=True)
+            return
+
+        # If we get this far, we're a maplike declaration.
+
+        # valueType get(keyType key)
+        #
+        # Note that instead of the value type, we're using any here. The
+        # validity checks should happen as things are inserted into the map,
+        # and using any as the return type makes code generation much simpler.
+        #
+        # TODO: Bug 1155340 may change this to use specific type to provide
+        # more info to JIT.
+        self.addMethod("get", members, False, BuiltinTypes[IDLBuiltinType.Types.any],
+                       [getKeyArg()], isPure=True)
+
+        def getValueArg():
+            return IDLArgument(self.location,
+                               IDLUnresolvedIdentifier(self.location, "value"),
+                               self.valueType)
+
+        if not self.readonly:
+            self.addMethod("set", members, True, BuiltinTypes[IDLBuiltinType.Types.object],
+                           [getKeyArg(), getValueArg()])
+        if isJSImplemented:
+            self.addMethod("set", members, True, BuiltinTypes[IDLBuiltinType.Types.object],
+                           [getKeyArg(), getValueArg()], chromeOnly=True)
 
 class IDLConst(IDLInterfaceMember):
     def __init__(self, location, identifier, type, value):
         IDLInterfaceMember.__init__(self, location, identifier,
                                     IDLInterfaceMember.Tags.Const)
 
         assert isinstance(type, IDLType)
         if type.isDictionary():
@@ -4314,17 +4377,17 @@ class IDLMethod(IDLInterfaceMember, IDLS
         'Named',
         'Indexed'
     )
 
     def __init__(self, location, identifier, returnType, arguments,
                  static=False, getter=False, setter=False, creator=False,
                  deleter=False, specialType=NamedOrIndexed.Neither,
                  legacycaller=False, stringifier=False, jsonifier=False,
-                 maplikeOrSetlike=None):
+                 maplikeOrSetlikeOrIterable=None):
         # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up.
         IDLInterfaceMember.__init__(self, location, identifier,
                                     IDLInterfaceMember.Tags.Method)
 
         self._hasOverloads = False
 
         assert isinstance(returnType, IDLType)
 
@@ -4342,18 +4405,18 @@ class IDLMethod(IDLInterfaceMember, IDLS
         assert isinstance(deleter, bool)
         self._deleter = deleter
         assert isinstance(legacycaller, bool)
         self._legacycaller = legacycaller
         assert isinstance(stringifier, bool)
         self._stringifier = stringifier
         assert isinstance(jsonifier, bool)
         self._jsonifier = jsonifier
-        assert maplikeOrSetlike is None or isinstance(maplikeOrSetlike, IDLMaplikeOrSetlike)
-        self.maplikeOrSetlike = maplikeOrSetlike
+        assert maplikeOrSetlikeOrIterable is None or isinstance(maplikeOrSetlikeOrIterable, IDLMaplikeOrSetlikeOrIterableBase)
+        self.maplikeOrSetlikeOrIterable = maplikeOrSetlikeOrIterable
         self._specialType = specialType
         self._unforgeable = False
         self.dependsOn = "Everything"
         self.affects = "Everything"
         self.aliases = []
 
         if static and identifier.name == "prototype":
             raise WebIDLError("The identifier of a static operation must not be 'prototype'",
@@ -4425,22 +4488,22 @@ class IDLMethod(IDLInterfaceMember, IDLS
         return self._legacycaller
 
     def isStringifier(self):
         return self._stringifier
 
     def isJsonifier(self):
         return self._jsonifier
 
-    def isMaplikeOrSetlikeMethod(self):
+    def isMaplikeOrSetlikeOrIterableMethod(self):
         """
         True if this method was generated as part of a
         maplike/setlike/etc interface (e.g. has/get methods)
         """
-        return self.maplikeOrSetlike is not None
+        return self.maplikeOrSetlikeOrIterable is not None
 
     def isSpecial(self):
         return (self.isGetter() or
                 self.isSetter() or
                 self.isCreator() or
                 self.isDeleter() or
                 self.isLegacycaller() or
                 self.isStringifier() or
@@ -4453,17 +4516,17 @@ class IDLMethod(IDLInterfaceMember, IDLS
         """
         True if the method name started with __, and if the method is not a
         maplike/setlike method. Interfaces with maplike/setlike will generate
         methods starting with __ for chrome only backing object access in JS
         implemented interfaces, so while these functions use what is considered
         an non-identifier name, they actually DO have an identifier.
         """
         return (self.identifier.name[:2] == "__" and
-                not self.isMaplikeOrSetlikeMethod())
+                not self.isMaplikeOrSetlikeOrIterableMethod())
 
     def resolve(self, parentScope):
         assert isinstance(parentScope, IDLScope)
         IDLObjectWithIdentifier.resolve(self, parentScope)
         IDLScope.__init__(self, self.location, parentScope, self.identifier)
         for (returnType, arguments) in self.signatures():
             for argument in arguments:
                 argument.resolve(self)
@@ -4961,17 +5024,18 @@ class Tokenizer(object):
         ",": "COMMA",
         "=": "EQUALS",
         "<": "LT",
         ">": "GT",
         "ArrayBuffer": "ARRAYBUFFER",
         "SharedArrayBuffer": "SHAREDARRAYBUFFER",
         "or": "OR",
         "maplike": "MAPLIKE",
-        "setlike": "SETLIKE"
+        "setlike": "SETLIKE",
+        "iterable": "ITERABLE"
         }
 
     tokens.extend(keywords.values())
 
     def t_error(self, t):
         raise WebIDLError("Unrecognized Input",
                           [Location(lexer=self.lexer,
                                     lineno=self.lexer.lineno,
@@ -5115,18 +5179,19 @@ class Parser(Tokenizer):
                                       [location, p[0].location])
                 p[0].setNonPartial(location, parent, members)
                 return
         except Exception, ex:
             if isinstance(ex, WebIDLError):
                 raise ex
             pass
 
-        p[0] = IDLInterface(location, self.globalScope(), identifier, parent,
+        iface = IDLInterface(location, self.globalScope(), identifier, parent,
                             members, isKnownNonPartial=True)
+        p[0] = iface
 
     def p_InterfaceForwardDecl(self, p):
         """
             Interface : INTERFACE IDENTIFIER SEMICOLON
         """
         location = self.getLocation(p, 1)
         identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
 
@@ -5202,17 +5267,17 @@ class Parser(Tokenizer):
         """
             InterfaceMembers :
         """
         p[0] = []
 
     def p_InterfaceMember(self, p):
         """
             InterfaceMember : Const
-                            | AttributeOrOperationOrMaplikeOrSetlike
+                            | AttributeOrOperationOrMaplikeOrSetlikeOrIterable
         """
         p[0] = p[1]
 
     def p_Dictionary(self, p):
         """
             Dictionary : DICTIONARY IDENTIFIER Inheritance LBRACE DictionaryMembers RBRACE SEMICOLON
         """
         location = self.getLocation(p, 1)
@@ -5417,25 +5482,40 @@ class Parser(Tokenizer):
         p[0] = True
 
     def p_BooleanLiteralFalse(self, p):
         """
             BooleanLiteral : FALSE
         """
         p[0] = False
 
-    def p_AttributeOrOperationOrMaplikeOrSetlike(self, p):
-        """
-            AttributeOrOperationOrMaplikeOrSetlike : Attribute
-                                                   | Maplike
-                                                   | Setlike
-                                                   | Operation
+    def p_AttributeOrOperationOrMaplikeOrSetlikeOrIterable(self, p):
+        """
+            AttributeOrOperationOrMaplikeOrSetlikeOrIterable : Attribute
+                                                             | Maplike
+                                                             | Setlike
+                                                             | Iterable
+                                                             | Operation
         """
         p[0] = p[1]
 
+    def p_Iterable(self, p):
+        """
+            Iterable : ITERABLE LT Type GT SEMICOLON
+                     | ITERABLE LT Type COMMA Type GT SEMICOLON
+        """
+        location = self.getLocation(p, 2)
+        identifier = IDLUnresolvedIdentifier(location, "__iterable",
+                                             allowDoubleUnderscore=True)
+        keyType = p[3]
+        valueType = None
+        if (len(p) > 6):
+            valueType = p[5]
+        p[0] = IDLIterable(location, identifier, keyType, valueType, self.globalScope())
+
     def p_Setlike(self, p):
         """
             Setlike : ReadOnly SETLIKE LT Type GT SEMICOLON
         """
         readonly = p[1]
         maplikeOrSetlikeType = p[2]
         location = self.getLocation(p, 2)
         identifier = IDLUnresolvedIdentifier(location, "__setlike",
@@ -5798,16 +5878,17 @@ class Parser(Tokenizer):
                          | DELETER
                          | DICTIONARY
                          | ENUM
                          | EXCEPTION
                          | GETTER
                          | IMPLEMENTS
                          | INHERIT
                          | INTERFACE
+                         | ITERABLE
                          | LEGACYCALLER
                          | MAPLIKE
                          | PARTIAL
                          | REQUIRED
                          | SERIALIZER
                          | SETLIKE
                          | SETTER
                          | STATIC
@@ -6476,17 +6557,56 @@ class Parser(Tokenizer):
         # for tok in iter(self.lexer.token, None):
         #    print tok
 
         self._filename = filename
         self._productions.extend(self.parser.parse(lexer=self.lexer, tracking=True))
         self._filename = None
 
     def finish(self):
-        # First, finish all the IDLImplementsStatements.  In particular, we
+        # If we have interfaces that are iterable, create their
+        # iterator interfaces and add them to the productions array.
+        interfaceStatements = [p for p in self._productions if
+                               isinstance(p, IDLInterface)]
+
+        for iface in interfaceStatements:
+            iterable = None
+            # We haven't run finish() on the interface yet, so we don't know
+            # whether our interface is maplike/setlike/iterable or not. This
+            # means we have to loop through the members to see if we have an
+            # iterable member.
+            for m in iface.members:
+                if isinstance(m, IDLIterable):
+                    iterable = m
+                    break
+            if iterable:
+                itr_ident = IDLUnresolvedIdentifier(iface.location,
+                                                    iface.identifier.name + "Iterator")
+                itr_iface = IDLInterface(iface.location, self.globalScope(),
+                                         itr_ident, None, [],
+                                         isKnownNonPartial=True)
+                itr_iface.addExtendedAttributes([IDLExtendedAttribute(iface.location,
+                                                                      ("NoInterfaceObject", ))])
+                # Always append generated iterable interfaces and their
+                # matching implements statements after the interface they're a
+                # member of, otherwise nativeType generation won't work
+                # correctly.
+                itr_iface.iterableInterface = iface
+                self._productions.append(itr_iface)
+                iterable.iteratorType = IDLWrapperType(iface.location, itr_iface)
+                itrPlaceholder = IDLIdentifierPlaceholder(iface.location,
+                                                          IDLUnresolvedIdentifier(iface.location,
+                                                                                  "IterableIterator"))
+                implements = IDLImplementsStatement(iface.location,
+                                                    IDLIdentifierPlaceholder(iface.location,
+                                                                             itr_ident),
+                                                    itrPlaceholder)
+                self._productions.append(implements)
+
+        # Then, finish all the IDLImplementsStatements.  In particular, we
         # have to make sure we do those before we do the IDLInterfaces.
         # XXX khuey hates this bit and wants to nuke it from orbit.
         implementsStatements = [p for p in self._productions if
                                 isinstance(p, IDLImplementsStatement)]
         otherStatements = [p for p in self._productions if
                            not isinstance(p, IDLImplementsStatement)]
         for production in implementsStatements:
             production.finish(self.globalScope())
--- a/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py
+++ b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py
@@ -36,55 +36,77 @@ def WebIDLTest(parser, harness):
         except WebIDL.WebIDLError, e:
             harness.ok(True,
                        prefix + " - Interface failed as expected")
         except Exception, e:
             harness.ok(False,
                        prefix + " - Interface failed but not as a WebIDLError exception")
 
     iterableMembers = [(x, WebIDL.IDLMethod) for x in ["entries", "keys",
-                                                       "values", "forEach"]]
-    iterableMembers.extend([("size", WebIDL.IDLAttribute)])
-    setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has"]] +
+                                                       "values"]]
+    setROMembers = ([(x, WebIDL.IDLMethod) for x in ["has", "foreach"]] +
                     [("__setlike", WebIDL.IDLMaplikeOrSetlike)] +
                     iterableMembers)
+    setROMembers.extend([("size", WebIDL.IDLAttribute)])
     setRWMembers = ([(x, WebIDL.IDLMethod) for x in ["add",
                                                      "clear",
                                                      "delete"]] +
                     setROMembers)
     setROChromeMembers = ([(x, WebIDL.IDLMethod) for x in ["__add",
                                                            "__clear",
                                                            "__delete"]] +
                           setROMembers)
     setRWChromeMembers = ([(x, WebIDL.IDLMethod) for x in ["__add",
                                                            "__clear",
                                                            "__delete"]] +
                           setRWMembers)
-    mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has"]] +
+    mapROMembers = ([(x, WebIDL.IDLMethod) for x in ["get", "has", "foreach"]] +
                     [("__maplike", WebIDL.IDLMaplikeOrSetlike)] +
                     iterableMembers)
+    mapROMembers.extend([("size", WebIDL.IDLAttribute)])
     mapRWMembers = ([(x, WebIDL.IDLMethod) for x in ["set",
                                                      "clear",
                                                      "delete"]] + mapROMembers)
     mapRWChromeMembers = ([(x, WebIDL.IDLMethod) for x in ["__set",
                                                            "__clear",
                                                            "__delete"]] +
                           mapRWMembers)
 
-    disallowedMemberNames = ["keys", "entries", "values", "forEach", "has",
-                             "size"]
+    disallowedIterableNames = ["keys", "entries", "values"]
+    disallowedMemberNames = ["forEach", "has", "size"] + disallowedIterableNames
     mapDisallowedMemberNames = ["get"] + disallowedMemberNames
     disallowedNonMethodNames = ["clear", "delete"]
     mapDisallowedNonMethodNames = ["set"] + disallowedNonMethodNames
     setDisallowedNonMethodNames = ["add"] + disallowedNonMethodNames
 
     #
     # Simple Usage Tests
     #
 
+    shouldPass("Iterable (key only)",
+               """
+               interface Foo1 {
+               iterable<long>;
+               };
+               """, iterableMembers)
+
+    shouldPass("Iterable (key and value)",
+               """
+               interface Foo1 {
+               iterable<long, long>;
+               };
+               """, iterableMembers)
+
+    shouldPass("Maplike (readwrite)",
+               """
+               interface Foo1 {
+               maplike<long, long>;
+               };
+               """, mapRWMembers)
+
     shouldPass("Maplike (readwrite)",
                """
                interface Foo1 {
                maplike<long, long>;
                };
                """, mapRWMembers)
 
     shouldPass("Maplike (readonly)",
@@ -152,16 +174,32 @@ def WebIDLTest(parser, harness):
     shouldFail("Two maplike/setlikes on same interface",
                """
                interface Foo1 {
                setlike<long>;
                maplike<long, long>;
                };
                """)
 
+    shouldFail("Two iterable/setlikes on same interface",
+               """
+               interface Foo1 {
+               iterable<long>;
+               maplike<long, long>;
+               };
+               """)
+
+    shouldFail("Two iterables on same interface",
+               """
+               interface Foo1 {
+               iterable<long>;
+               iterable<long, long>;
+               };
+               """)
+
     shouldFail("Two maplike/setlikes in partials",
                """
                interface Foo1 {
                maplike<long, long>;
                };
                partial interface Foo1 {
                setlike<long>;
                };
@@ -172,16 +210,26 @@ def WebIDLTest(parser, harness):
                interface Foo1 {
                maplike<long, long>;
                };
                interface Foo2 : Foo1 {
                setlike<long>;
                };
                """)
 
+    shouldFail("Conflicting maplike/iterable across inheritance",
+               """
+               interface Foo1 {
+               maplike<long, long>;
+               };
+               interface Foo2 : Foo1 {
+               iterable<long>;
+               };
+               """)
+
     shouldFail("Conflicting maplike/setlikes across multistep inheritance",
                """
                interface Foo1 {
                maplike<long, long>;
                };
                interface Foo2 : Foo1 {
                };
                interface Foo3 : Foo2 {
@@ -278,16 +326,18 @@ def WebIDLTest(parser, harness):
         shouldFail("Conflicting static attribute: %s and %s" % (likeMember, conflictName),
                    """
                    interface Foo1 {
                    %s;
                    static attribute long %s;
                    };
                    """ % (likeMember, conflictName))
 
+    for member in disallowedIterableNames:
+        testConflictingMembers("iterable<long, long>", member, iterableMembers, False)
     for member in mapDisallowedMemberNames:
         testConflictingMembers("maplike<long, long>", member, mapRWMembers, False)
     for member in disallowedMemberNames:
         testConflictingMembers("setlike<long>", member, setRWMembers, False)
     for member in mapDisallowedNonMethodNames:
         testConflictingMembers("maplike<long, long>", member, mapRWMembers, True)
     for member in setDisallowedNonMethodNames:
         testConflictingMembers("setlike<long>", member, setRWMembers, True)
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableDouble.cpp
@@ -0,0 +1,82 @@
+/* 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/. */
+
+#include "mozilla/dom/TestInterfaceIterableDouble.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceIterableDouble, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceIterableDouble)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceIterableDouble)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceIterableDouble)
+NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceIterableDouble::TestInterfaceIterableDouble(nsPIDOMWindow* aParent)
+  : mParent(aParent)
+{
+  mValues.AppendElement(std::pair<nsString, nsString>(NS_LITERAL_STRING("a"),
+                                                      NS_LITERAL_STRING("b")));
+  mValues.AppendElement(std::pair<nsString, nsString>(NS_LITERAL_STRING("c"),
+                                                      NS_LITERAL_STRING("d")));
+  mValues.AppendElement(std::pair<nsString, nsString>(NS_LITERAL_STRING("e"),
+                                                      NS_LITERAL_STRING("f")));
+}
+
+//static
+already_AddRefed<TestInterfaceIterableDouble>
+TestInterfaceIterableDouble::Constructor(const GlobalObject& aGlobal,
+                                         ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!window) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<TestInterfaceIterableDouble> r = new TestInterfaceIterableDouble(window);
+  return r.forget();
+}
+
+JSObject*
+TestInterfaceIterableDouble::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return TestInterfaceIterableDoubleBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindow*
+TestInterfaceIterableDouble::GetParentObject() const
+{
+  return mParent;
+}
+
+size_t
+TestInterfaceIterableDouble::GetIterableLength()
+{
+  return mValues.Length();
+}
+
+nsAString&
+TestInterfaceIterableDouble::GetKeyAtIndex(uint32_t aIndex)
+{
+  MOZ_ASSERT(aIndex < mValues.Length());
+  return mValues.ElementAt(aIndex).first;
+}
+
+nsAString&
+TestInterfaceIterableDouble::GetValueAtIndex(uint32_t aIndex)
+{
+  MOZ_ASSERT(aIndex < mValues.Length());
+  return mValues.ElementAt(aIndex).second;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableDouble.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_dom_TestInterfaceIterableDouble_h
+#define mozilla_dom_TestInterfaceIterableDouble_h
+
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+// Implementation of test binding for webidl iterable interfaces, using
+// primitives for value type
+class TestInterfaceIterableDouble final : public nsISupports,
+                                          public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceIterableDouble)
+
+  explicit TestInterfaceIterableDouble(nsPIDOMWindow* aParent);
+  nsPIDOMWindow* GetParentObject() const;
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+  static already_AddRefed<TestInterfaceIterableDouble>
+    Constructor(const GlobalObject& aGlobal, ErrorResult& rv);
+
+  size_t GetIterableLength();
+  nsAString& GetKeyAtIndex(uint32_t aIndex);
+  nsAString& GetValueAtIndex(uint32_t aIndex);
+private:
+  virtual ~TestInterfaceIterableDouble() {}
+  nsCOMPtr<nsPIDOMWindow> mParent;
+  nsTArray<std::pair<nsString, nsString>> mValues;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceIterableDouble_h
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableSingle.cpp
@@ -0,0 +1,78 @@
+/* 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/. */
+
+#include "mozilla/dom/TestInterfaceIterableSingle.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceIterableSingle, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceIterableSingle)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceIterableSingle)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceIterableSingle)
+NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TestInterfaceIterableSingle::TestInterfaceIterableSingle(nsPIDOMWindow* aParent)
+  : mParent(aParent)
+{
+  for(int i = 0; i < 3; ++i) {
+    mValues.AppendElement(i);
+  }
+}
+
+//static
+already_AddRefed<TestInterfaceIterableSingle>
+TestInterfaceIterableSingle::Constructor(const GlobalObject& aGlobal,
+                                         ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!window) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<TestInterfaceIterableSingle> r = new TestInterfaceIterableSingle(window);
+  return r.forget();
+}
+
+JSObject*
+TestInterfaceIterableSingle::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return TestInterfaceIterableSingleBinding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindow*
+TestInterfaceIterableSingle::GetParentObject() const
+{
+  return mParent;
+}
+
+size_t
+TestInterfaceIterableSingle::GetIterableLength() const
+{
+  return mValues.Length();
+}
+
+uint32_t
+TestInterfaceIterableSingle::GetKeyAtIndex(uint32_t index) const
+{
+  MOZ_ASSERT(index < mValues.Length());
+  return mValues.ElementAt(index);
+}
+
+uint32_t
+TestInterfaceIterableSingle::GetValueAtIndex(uint32_t index) const
+{
+  return GetKeyAtIndex(index);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/TestInterfaceIterableSingle.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_dom_TestInterfaceIterableSingle_h
+#define mozilla_dom_TestInterfaceIterableSingle_h
+
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+
+// Implementation of test binding for webidl iterable interfaces, using
+// primitives for value type
+class TestInterfaceIterableSingle final : public nsISupports,
+                                          public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceIterableSingle)
+
+  explicit TestInterfaceIterableSingle(nsPIDOMWindow* aParent);
+  nsPIDOMWindow* GetParentObject() const;
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+  static already_AddRefed<TestInterfaceIterableSingle>
+    Constructor(const GlobalObject& aGlobal, ErrorResult& rv);
+
+  size_t GetIterableLength() const;
+  uint32_t GetKeyAtIndex(uint32_t aIndex) const;
+  uint32_t GetValueAtIndex(uint32_t aIndex) const;
+private:
+  virtual ~TestInterfaceIterableSingle() {}
+  nsCOMPtr<nsPIDOMWindow> mParent;
+  nsTArray<uint32_t> mValues;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TestInterfaceIterableSingle_h
--- a/dom/bindings/test/TestInterfaceMaplike.cpp
+++ b/dom/bindings/test/TestInterfaceMaplike.cpp
@@ -1,14 +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 http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/TestInterfaceMaplike.h"
-#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/dom/BindingUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceMaplike, mParent)
 
--- a/dom/bindings/test/TestInterfaceMaplikeObject.cpp
+++ b/dom/bindings/test/TestInterfaceMaplikeObject.cpp
@@ -1,15 +1,15 @@
 /* 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/. */
 
 #include "mozilla/dom/TestInterfaceMaplikeObject.h"
 #include "mozilla/dom/TestInterfaceMaplike.h"
-#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/dom/BindingUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceMaplikeObject, mParent)
 
--- a/dom/bindings/test/TestInterfaceSetlike.cpp
+++ b/dom/bindings/test/TestInterfaceSetlike.cpp
@@ -1,14 +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 http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/TestInterfaceSetlike.h"
-#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/dom/BindingUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceSetlike, mParent)
 
--- a/dom/bindings/test/TestInterfaceSetlikeNode.cpp
+++ b/dom/bindings/test/TestInterfaceSetlikeNode.cpp
@@ -1,14 +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 http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/TestInterfaceSetlikeNode.h"
-#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h"
+#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeIterableBinding.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/dom/BindingUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceSetlikeNode, mParent)
 
--- a/dom/bindings/test/mochitest.ini
+++ b/dom/bindings/test/mochitest.ini
@@ -63,8 +63,10 @@ skip-if = debug == false
 skip-if = debug == false
 [test_worker_UnwrapArg.html]
 [test_unforgeablesonexpando.html]
 [test_crossOriginWindowSymbolAccess.html]
 [test_bug1123516_maplikesetlike.html]
 skip-if = debug == false
 [test_jsimplemented_eventhandler.html]
 skip-if = debug == false
+[test_iterable.html]
+skip-if = debug == false
\ No newline at end of file
--- a/dom/bindings/test/test_bug1123516_maplikesetlike.html
+++ b/dom/bindings/test/test_bug1123516_maplikesetlike.html
@@ -52,17 +52,16 @@
                          owner = Object.getPrototypeOf(owner);
                      }
                  }
              }
 
              var m;
              var testSet;
              var testIndex;
-             var iterable;
              // Simple map creation and functionality test
              info("SimpleMap: Testing simple map creation and functionality");
              m = new TestInterfaceMaplike();
              ok(m, "SimpleMap: got a TestInterfaceMaplike object");
              testExistence("SimpleMap: ", m, maplike_rw_properties);
              is(m.size, 0, "SimpleMap: size should be zero");
              ok(!m.has("test"), "SimpleMap: maplike has should return false");
              is(m.get("test"), undefined, "SimpleMap: maplike get should return undefined on bogus lookup");
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/test_iterable.html
@@ -0,0 +1,112 @@
+<!-- Any copyright is dedicated to the Public Domain.
+- http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Test Iterable Interface</title>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  </head>
+  <body>
+    <script class="testbody" type="application/javascript">
+     SimpleTest.waitForExplicitFinish();
+     SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() {
+
+       base_properties = [["entries", "function", 0],
+                          ["keys", "function", 0],
+                          ["values", "function", 0]]
+       var testExistence = function testExistence(prefix, obj, properties) {
+         for (var [name, type, args] of properties) {
+           // Properties are somewhere up the proto chain, hasOwnProperty won't work
+           isnot(obj[name], undefined,
+                 `${prefix} object has property ${name}`);
+
+           is(typeof obj[name], type,
+              `${prefix} object property ${name} is a ${type}`);
+           // Check function length
+           if (type == "function") {
+             is(obj[name].length, args,
+                `${prefix} object property ${name} is length ${args}`);
+             is(obj[name].name, name,
+                `${prefix} object method name is ${name}`);
+           }
+
+           // Find where property is on proto chain, check for enumerablility there.
+           var owner = obj;
+           while (owner) {
+             var propDesc = Object.getOwnPropertyDescriptor(owner, name);
+             if (propDesc) {
+               ok(propDesc.enumerable,
+                  `${prefix} object property ${name} is enumerable`);
+               break;
+             }
+             owner = Object.getPrototypeOf(owner);
+           }
+         }
+       }
+
+       var itr;
+       // Simple single type iterable creation and functionality test
+       info("IterableSingle: Testing simple iterable creation and functionality");
+       itr = new TestInterfaceIterableSingle();
+       testExistence("IterableSingle: ", itr, base_properties);
+       var key_itr = itr.keys();
+       var value_itr = itr.values();
+       var entries_itr = itr.entries();
+       for (var i = 0; i < 3; ++i) {
+         var key = key_itr.next();
+         var value = value_itr.next();
+         var entry = entries_itr.next();
+         is(key.value, i, "IterableSingle: Key iterator value should be " + i);
+         is(value.value, key.value, "IterableSingle: Value iterator value should be " + key.value);
+         is(entry.value[0], i, "IterableSingle: Entry iterator value 0 should be " + i);
+         is(entry.value[1], i, "IterableSingle: Entry iterator value 1 should be " + i);
+       }
+       var key = key_itr.next();
+       var value = value_itr.next();
+       var entry = entries_itr.next();
+       is(key.value, undefined, "IterableSingle: Key iterator value should be undefined");
+       is(key.done, true, "IterableSingle: Key iterator done should be true");
+       is(value.value, undefined, "IterableSingle: Value iterator value should be undefined");
+       is(value.done, true, "IterableSingle: Value iterator done should be true");
+       is(entry.value, undefined, "IterableDouble: Entry iterator value should be undefined");
+       is(entry.done, true, "IterableSingle: Entry iterator done should be true");
+       is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
+          "[object TestInterfaceIterableSingleIteratorPrototype]",
+          "iterator prototype should have the right brand");
+
+       // Simple dual type iterable creation and functionality test
+       info("IterableDouble: Testing simple iterable creation and functionality");
+       itr = new TestInterfaceIterableDouble();
+       testExistence("IterableDouble: ", itr, base_properties);
+       var elements = [["a", "b"], ["c", "d"], ["e", "f"]]
+       var key_itr = itr.keys();
+       var value_itr = itr.values();
+       var entries_itr = itr.entries();
+       for (var i = 0; i < 3; ++i) {
+         var key = key_itr.next();
+         var value = value_itr.next();
+         var entry = entries_itr.next();
+         is(key.value, elements[i][0], "IterableDouble: Key iterator value should be " + elements[i][0]);
+         is(value.value, elements[i][1], "IterableDouble: Value iterator value should be " + elements[i][1]);
+         is(entry.value[0], elements[i][0], "IterableDouble: Entry iterator value 0 should be " + elements[i][0]);
+         is(entry.value[1], elements[i][1], "IterableDouble: Entry iterator value 1 should be " + elements[i][1]);
+       }
+       var key = key_itr.next();
+       var value = value_itr.next();
+       var entry = entries_itr.next()
+       is(key.value, undefined, "IterableDouble: Key iterator value should be undefined");
+       is(key.done, true, "IterableDouble: Key iterator done should be true");
+       is(value.value, undefined, "IterableDouble: Value iterator value should be undefined");
+       is(value.done, true, "IterableDouble: Value iterator done should be true");
+       is(entry.value, undefined, "IterableDouble: Entry iterator value should be undefined");
+       is(entry.done, true, "IterableDouble: Entry iterator done should be true");
+       is(Object.prototype.toString.call(Object.getPrototypeOf(key_itr)),
+          "[object TestInterfaceIterableDoubleIteratorPrototype]",
+          "iterator prototype should have the right brand");
+
+       SimpleTest.finish();
+     });
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/webidl/IterableIterator.webidl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+[NoInterfaceObject]
+interface IterableIterator
+{
+  [Throws]
+  object next();
+};
+
+dictionary IterableKeyOrValueResult {
+  any value;
+  boolean done = false;
+};
+
+dictionary IterableKeyAndValueResult {
+  sequence<any> value = [];
+  boolean done = false;
+};
--- a/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl
+++ b/dom/webidl/TestInterfaceJSMaplikeSetlikeIterable.webidl
@@ -40,8 +40,21 @@ interface TestInterfaceSetlike {
   setlike<DOMString>;
 };
 
 [Constructor(),
  Pref="dom.expose_test_interfaces"]
 interface TestInterfaceSetlikeNode {
   setlike<Node>;
 };
+
+[Constructor()]
+// Pref="dom.expose_test_interfaces"]
+interface TestInterfaceIterableSingle {
+  iterable<long>;
+};
+
+[Constructor()]
+//   Pref="dom.expose_test_interfaces"]
+interface TestInterfaceIterableDouble {
+  iterable<DOMString, DOMString>;
+};
+
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -266,16 +266,17 @@ WEBIDL_FILES = [
     'InputEvent.webidl',
     'InputMethod.webidl',
     'InputPort.webidl',
     'InputPortManager.webidl',
     'InspectorUtils.webidl',
     'InterAppConnection.webidl',
     'InterAppConnectionRequest.webidl',
     'InterAppMessagePort.webidl',
+    'IterableIterator.webidl',
     'KeyAlgorithm.webidl',
     'KeyboardEvent.webidl',
     'KeyEvent.webidl',
     'Keyframe.webidl',
     'KeyframeEffect.webidl',
     'KillSwitch.webidl',
     'LegacyQueryInterface.webidl',
     'LinkStyle.webidl',
@@ -674,17 +675,17 @@ WEBIDL_FILES += [
     'StyleSheetChangeEvent.webidl',
 ]
 
 # We only expose our prefable test interfaces in debug builds, just to be on
 # the safe side.
 if CONFIG['MOZ_DEBUG']:
     WEBIDL_FILES += ['TestInterfaceJS.webidl',
                      'TestInterfaceJSDictionaries.webidl',
-                     'TestInterfaceJSMaplikeSetlike.webidl']
+                     'TestInterfaceJSMaplikeSetlikeIterable.webidl']
 
 if CONFIG['MOZ_B2G_BT']:
     WEBIDL_FILES += [
         'BluetoothAdapter.webidl',
         'BluetoothClassOfDevice.webidl',
         'BluetoothDevice.webidl',
         'BluetoothDiscoveryHandle.webidl',
         'BluetoothGatt.webidl',