Bug 907077: Change for-of to conform to latest ES6 specification. r=jwalden
authorAndy Wingo <wingo@igalia.com>
Thu, 03 Oct 2013 13:37:57 +0100
changeset 149789 a4f96de49668a86063da67a8de77e4eb23e49b0d
parent 149788 b7c5cd333e9cae4e023bd980981fca3aeb59b668
child 149790 1065b60241f7a5c2c09d8bf2a3994a7de213b275
push id25402
push userphilringnalda@gmail.com
push dateFri, 04 Oct 2013 03:52:48 +0000
treeherdermozilla-central@33be4ad3a720 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs907077
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 907077: Change for-of to conform to latest ES6 specification. r=jwalden
content/html/content/test/test_formelements.html
content/html/content/test/test_htmlcollection.html
content/html/content/test/test_rowscollection.html
dom/bindings/Codegen.py
dom/bindings/parser/WebIDL.py
dom/bindings/test/test_forOf.html
js/src/Makefile.in
js/src/builtin/Iterator.js
js/src/builtin/Map.js
js/src/builtin/MapObject.cpp
js/src/builtin/Set.js
js/src/builtin/Utilities.js
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/frontend/SharedContext.h
js/src/frontend/SourceNotes.h
js/src/jit-test/lib/iteration.js
js/src/jit-test/tests/basic/bug770952.js
js/src/jit-test/tests/basic/spread-array.js
js/src/jit-test/tests/basic/spread-call-eval.js
js/src/jit-test/tests/basic/spread-call-funapply.js
js/src/jit-test/tests/basic/spread-call-length.js
js/src/jit-test/tests/basic/spread-call-maxarg.js
js/src/jit-test/tests/basic/spread-call-not-iterable.js
js/src/jit-test/tests/basic/spread-call.js
js/src/jit-test/tests/collections/Map-clear-iterators-1.js
js/src/jit-test/tests/collections/Map-clear-iterators-2.js
js/src/jit-test/tests/collections/Map-forEach.js
js/src/jit-test/tests/collections/Map-iterator-add-2.js
js/src/jit-test/tests/collections/Map-iterator-pairs-1.js
js/src/jit-test/tests/collections/Map-iterator-pairs-2.js
js/src/jit-test/tests/collections/Map-iterator-pairs-3.js
js/src/jit-test/tests/collections/Map-iterator-proxies-2.js
js/src/jit-test/tests/collections/Map-iterator-remove-2.js
js/src/jit-test/tests/collections/Map-iterator-remove-3.js
js/src/jit-test/tests/collections/Map-iterator-remove-4.js
js/src/jit-test/tests/collections/Map-iterator-remove-6.js
js/src/jit-test/tests/collections/Map-iterators-3.js
js/src/jit-test/tests/collections/Map-surfaces-1.js
js/src/jit-test/tests/collections/Map-values-2.js
js/src/jit-test/tests/collections/Set-clear-iterators-1.js
js/src/jit-test/tests/collections/Set-clear-iterators-2.js
js/src/jit-test/tests/collections/Set-clear-iterators-3.js
js/src/jit-test/tests/collections/Set-forEach.js
js/src/jit-test/tests/collections/Set-iterator-add-2.js
js/src/jit-test/tests/collections/Set-iterator-gc-1.js
js/src/jit-test/tests/collections/Set-iterator-proxies-2.js
js/src/jit-test/tests/collections/Set-iterator-remove-2.js
js/src/jit-test/tests/collections/Set-iterator-remove-3.js
js/src/jit-test/tests/collections/Set-iterator-remove-4.js
js/src/jit-test/tests/collections/Set-iterator-remove-6.js
js/src/jit-test/tests/collections/Set-surfaces-1.js
js/src/jit-test/tests/collections/Set-values-2.js
js/src/jit-test/tests/collections/iterator-1.js
js/src/jit-test/tests/collections/iterator-gc.js
js/src/jit-test/tests/collections/iterator-proto-1.js
js/src/jit-test/tests/collections/iterator-proto-2.js
js/src/jit-test/tests/collections/iterator-proto-surfaces.js
js/src/jit-test/tests/for-of/arguments-1.js
js/src/jit-test/tests/for-of/arguments-2.js
js/src/jit-test/tests/for-of/arguments-3.js
js/src/jit-test/tests/for-of/arguments-4.js
js/src/jit-test/tests/for-of/arguments-5.js
js/src/jit-test/tests/for-of/arguments-6.js
js/src/jit-test/tests/for-of/arguments-7.js
js/src/jit-test/tests/for-of/array-holes-4.js
js/src/jit-test/tests/for-of/array-iterator-surfaces-2.js
js/src/jit-test/tests/for-of/completion.js
js/src/jit-test/tests/for-of/generators-5.js
js/src/jit-test/tests/for-of/generators-6.js
js/src/jit-test/tests/for-of/manual-advance.js
js/src/jit-test/tests/for-of/next-arity.js
js/src/jit-test/tests/for-of/next-shenanigans.js
js/src/jit-test/tests/for-of/proxy-3.js
js/src/jit-test/tests/for-of/semantics-01.js
js/src/jit-test/tests/for-of/semantics-02.js
js/src/jit-test/tests/for-of/semantics-03.js
js/src/jit-test/tests/for-of/semantics-04.js
js/src/jit-test/tests/for-of/semantics-05.js
js/src/jit-test/tests/for-of/semantics-07.js
js/src/jit-test/tests/for-of/semantics-08.js
js/src/jit-test/tests/for-of/semantics-11.js
js/src/jit-test/tests/for-of/value-done-access.js
js/src/jit-test/tests/ion/bug800179.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/IonBuilder.cpp
js/src/js.msg
js/src/jsanalyze.cpp
js/src/jsapi.cpp
js/src/jsarray.cpp
js/src/jsiter.cpp
js/src/jsiter.h
js/src/jsopcode.cpp
js/src/jsopcode.tbl
js/src/jsstr.cpp
js/src/shell/js.cpp
js/src/tests/ecma_6/Generators/runtime.js
js/src/vm/CommonPropertyNames.h
js/src/vm/Interpreter.cpp
js/src/vm/SelfHosting.cpp
js/src/vm/TypedArrayObject.cpp
js/src/vm/Xdr.h
--- a/content/html/content/test/test_formelements.html
+++ b/content/html/content/test/test_formelements.html
@@ -47,14 +47,14 @@ is(names[3], "3", "Entry 4")
 is(names[4], "4", "Entry 5")
 is(names[5], "w", "Entry 6")
 is(names[6], "x", "Entry 7")
 is(names[7], "y", "Entry 8")
 is(names[8], "z", "Entry 9")
 is(names[9], "something", "Entry 10")
 is(names[10], "namedItem", "Entry 11")
 is(names[11], "item", "Entry 12")
-is(names[12], "iterator", "Entry 13")
+is(names[12], "@@iterator", "Entry 13")
 is(names[13], "length", "Entry 14")
 </script>
 </pre>
 </body>
 </html>
--- a/content/html/content/test/test_htmlcollection.html
+++ b/content/html/content/test/test_htmlcollection.html
@@ -35,14 +35,14 @@ is(names[2], "2", "Entry 3")
 is(names[3], "3", "Entry 4")
 is(names[4], "x", "Entry 5")
 is(names[5], "y", "Entry 6")
 is(names[6], "z", "Entry 7")
 is(names[7], "w", "Entry 8")
 is(names[8], "something", "Entry 9")
 is(names[9], "item", "Entry 10")
 is(names[10], "namedItem", "Entry 11")
-is(names[11], "iterator", "Entry 12")
+is(names[11], "@@iterator", "Entry 12")
 is(names[12], "length", "Entry 13")
 </script>
 </pre>
 </body>
 </html>
--- a/content/html/content/test/test_rowscollection.html
+++ b/content/html/content/test/test_rowscollection.html
@@ -47,14 +47,14 @@ is(names[4], "4", "Entry 5")
 is(names[5], "5", "Entry 6")
 is(names[6], "x", "Entry 7")
 is(names[7], "y", "Entry 8")
 is(names[8], "z", "Entry 9")
 is(names[9], "w", "Entry 10")
 is(names[10], "something", "Entry 11")
 is(names[11], "item", "Entry 12")
 is(names[12], "namedItem", "Entry 13")
-is(names[13], "iterator", "Entry 14")
+is(names[13], "@@iterator", "Entry 14")
 is(names[14], "length", "Entry 15")
 </script>
 </pre>
 </body>
 </html>
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1509,19 +1509,19 @@ class MethodDefiner(PropertyDefiner):
                        "condition": PropertyDefiner.getControllingCondition(m) }
             if isChromeOnly(m):
                 self.chrome.append(method)
             else:
                 self.regular.append(method)
 
         # FIXME Check for an existing iterator on the interface first.
         if any(m.isGetter() and m.isIndexed() for m in methods):
-            self.regular.append({"name": 'iterator',
+            self.regular.append({"name": "@@iterator",
                                  "methodInfo": False,
-                                 "nativeName": "JS_ArrayIterator",
+                                 "selfHostedName": "ArrayIterator",
                                  "length": 0,
                                  "flags": "JSPROP_ENUMERATE",
                                  "condition": MemberCondition(None, None) })
 
         if not static:
             stringifier = descriptor.operations['Stringifier']
             if stringifier:
                 toStringDesc = { "name": "toString",
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -315,17 +315,17 @@ class IDLUnresolvedIdentifier(IDLObject)
 
         assert len(name) > 0
 
         if name[:2] == "__" and not allowDoubleUnderscore:
             raise WebIDLError("Identifiers beginning with __ are reserved",
                               [location])
         if name[0] == '_' and not allowDoubleUnderscore:
             name = name[1:]
-        if (name in ["constructor", "iterator", "toString", "toJSON"] and
+        if (name in ["constructor", "toString", "toJSON"] and
             not allowForbidden):
             raise WebIDLError("Cannot use reserved identifier '%s'" % (name),
                               [location])
 
         self.name = name
 
     def __str__(self):
         return self.QName()
--- a/dom/bindings/test/test_forOf.html
+++ b/dom/bindings/test/test_forOf.html
@@ -45,30 +45,22 @@ function runTestsForDocument(document, m
         if (x.nodeType != x.TEXT_NODE) {
             log += x.id + ";";
             if (x.id == "egg1")
                 basket.appendChild(egg3);
         }
     }
     is(log, "egg0;egg1;egg2;egg3;", "'for (x of div.childNodes)' should see elements added during iteration");
 
-    var iter1 = basket.childNodes.iterator();
-    var iter2 = basket.childNodes.iterator();
-    isnot(iter1, iter2, "nodelist.iterator() returns a new iterator each time");
-
     log = '';
     basket.appendChild(document.createTextNode("some text"));
     for (var x of basket.children)
         log += x.id + ";";
     is(log, "egg0;egg1;egg2;egg3;", "'for (x of div.children)' should iterate over child elements");
 
-    var iter1 = basket.children.iterator();
-    var iter2 = basket.children.iterator();
-    isnot(iter1, iter2, ".iterator() returns a new iterator each time");
-
     var count = 0;
     for (var x of document.getElementsByClassName("hazardous-materials"))
         count++;
     is(count, 0, "'for (x of emptyNodeList)' loop should run zero times");
 
     var log = '';
     for (var x of document.querySelectorAll("span"))
         log += x.id + ";";
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -643,16 +643,17 @@ export:: selfhosting
 selfhosting:: selfhosted.out.h
 
 selfhosting_srcs := \
   $(srcdir)/builtin/Utilities.js \
   $(srcdir)/builtin/Array.js \
   $(srcdir)/builtin/Date.js \
   $(srcdir)/builtin/Intl.js \
   $(srcdir)/builtin/IntlData.js \
+  $(srcdir)/builtin/Iterator.js \
   $(srcdir)/builtin/Number.js \
   $(srcdir)/builtin/ParallelArray.js \
   $(srcdir)/builtin/String.js \
   $(srcdir)/builtin/Set.js \
   $(srcdir)/builtin/Map.js \
   $(NULL)
 
 selfhosted_out_h_deps := \
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/Iterator.js
@@ -0,0 +1,97 @@
+/* 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/. */
+
+function IteratorIdentity() {
+    return this;
+}
+
+var LegacyIteratorWrapperMap = new std_WeakMap();
+
+function IteratorResult(value, done) {
+    var result = std_Object_create(null);
+    result.value = value;
+    result.done = done;
+    return result;
+}
+
+function LegacyIteratorNext(arg) {
+    var iter = callFunction(std_WeakMap_get, LegacyIteratorWrapperMap, this);
+    try {
+        return IteratorResult(iter.next(arg), false);
+    } catch (e) {
+        if (e instanceof std_StopIteration)
+            return IteratorResult(undefined, true);
+        throw e;
+    }
+}
+
+function LegacyIteratorThrow(exn) {
+    var iter = callFunction(std_WeakMap_get, LegacyIteratorWrapperMap, this);
+    try {
+        return IteratorResult(iter.throw(exn), false);
+    } catch (e) {
+        if (e instanceof std_StopIteration)
+            return IteratorResult(undefined, true);
+        throw e;
+    }
+}
+
+function LegacyIterator(iter) {
+    callFunction(std_WeakMap_set, LegacyIteratorWrapperMap, this, iter);
+}
+
+function LegacyGeneratorIterator(iter) {
+    callFunction(std_WeakMap_set, LegacyIteratorWrapperMap, this, iter);
+}
+
+var LegacyIteratorsInitialized = std_Object_create(null);
+
+function InitLegacyIterators() {
+    var props = std_Object_create(null);
+
+    props.next = std_Object_create(null);
+    props.next.value = LegacyIteratorNext;
+    props.next.enumerable = false;
+    props.next.configurable = true;
+    props.next.writable = true;
+
+    props[std_iterator] = std_Object_create(null);
+    props[std_iterator].value = IteratorIdentity;
+    props[std_iterator].enumerable = false;
+    props[std_iterator].configurable = true;
+    props[std_iterator].writable = true;
+
+    var LegacyIteratorProto = std_Object_create(GetIteratorPrototype(), props);
+    MakeConstructible(LegacyIterator, LegacyIteratorProto);
+
+    props.throw = std_Object_create(null);
+    props.throw.value = LegacyIteratorThrow;
+    props.throw.enumerable = false;
+    props.throw.configurable = true;
+    props.throw.writable = true;
+
+    var LegacyGeneratorIteratorProto = std_Object_create(GetIteratorPrototype(), props);
+    MakeConstructible(LegacyGeneratorIterator, LegacyGeneratorIteratorProto);
+
+    LegacyIteratorsInitialized.initialized = true;
+}
+
+function NewLegacyIterator(iter, wrapper) {
+    if (!LegacyIteratorsInitialized.initialized)
+        InitLegacyIterators();
+
+    return new wrapper(iter);
+}
+
+function LegacyIteratorShim() {
+    return NewLegacyIterator(ToObject(this), LegacyIterator);
+}
+
+function LegacyGeneratorIteratorShim() {
+    return NewLegacyIterator(ToObject(this), LegacyGeneratorIterator);
+}
+
+function ArrayIterator() {
+    return NewLegacyIterator(callFunction(std_Array_iterator, this), LegacyIterator);
+}
--- a/js/src/builtin/Map.js
+++ b/js/src/builtin/Map.js
@@ -19,18 +19,15 @@ function MapForEach(callbackfn, thisArg 
 
     /* Step 5. */
     if (!IsCallable(callbackfn))
         ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     /* Step 6-8. */
     var entries = std_Map_iterator.call(M);
     while (true) {
-        try {
-            var entry = std_Map_iterator_next.call(entries);
-        } catch (err) {
-            if (err instanceof StopIteration)
-                break;
-            throw err;
-        }
+        var result = std_Map_iterator_next.call(entries);
+        if (result.done)
+            break;
+        var entry = result.value;
         callFunction(callbackfn, thisArg, entry[1], entry[0], M);
     }
 }
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -20,16 +20,17 @@
 #include "jsobjinlines.h"
 
 using namespace js;
 
 using mozilla::DoubleIsInt32;
 using mozilla::IsNaN;
 using mozilla::OldMove;
 using mozilla::MoveRef;
+using mozilla::ArrayLength;
 using JS::DoubleNaNValue;
 
 
 /*** OrderedHashTable ****************************************************************************/
 
 /*
  * Define two collection templates, js::OrderedHashMap and js::OrderedHashSet.
  * They are like js::HashMap and js::HashSet except that:
@@ -884,16 +885,17 @@ const Class MapIteratorObject::class_ = 
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     MapIteratorObject::finalize
 };
 
 const JSFunctionSpec MapIteratorObject::methods[] = {
+    JS_SELF_HOSTED_FN("@@iterator", "IteratorIdentity", 0, 0),
     JS_FN("next", next, 0, 0),
     JS_FS_END
 };
 
 inline ValueMap::Range *
 MapIteratorObject::range()
 {
     return static_cast<ValueMap::Range *>(getSlot(RangeSlot).toPrivate());
@@ -960,45 +962,54 @@ MapIteratorObject::is(HandleValue v)
     return v.isObject() && v.toObject().hasClass(&class_);
 }
 
 bool
 MapIteratorObject::next_impl(JSContext *cx, CallArgs args)
 {
     MapIteratorObject &thisobj = args.thisv().toObject().as<MapIteratorObject>();
     ValueMap::Range *range = thisobj.range();
-    if (!range)
-        return js_ThrowStopIteration(cx);
-    if (range->empty()) {
+    RootedValue value(cx);
+    bool done;
+
+    if (!range || range->empty()) {
         js_delete(range);
         thisobj.setReservedSlot(RangeSlot, PrivateValue(nullptr));
-        return js_ThrowStopIteration(cx);
+        value.setUndefined();
+        done = true;
+    } else {
+        switch (thisobj.kind()) {
+          case MapObject::Keys:
+            value = range->front().key.get();
+            break;
+
+          case MapObject::Values:
+            value = range->front().value;
+            break;
+
+          case MapObject::Entries: {
+            Value pair[2] = { range->front().key.get(), range->front().value };
+            AutoValueArray root(cx, pair, ArrayLength(pair));
+
+            JSObject *pairobj = NewDenseCopiedArray(cx, ArrayLength(pair), pair);
+            if (!pairobj)
+                return false;
+            value.setObject(*pairobj);
+            break;
+          }
+        }
+        range->popFront();
+        done = false;
     }
 
-    switch (thisobj.kind()) {
-      case MapObject::Keys:
-        args.rval().set(range->front().key.get());
-        break;
-
-      case MapObject::Values:
-        args.rval().set(range->front().value);
-        break;
+    RootedObject result(cx, CreateItrResultObject(cx, value, done));
+    if (!result)
+        return false;
+    args.rval().setObject(*result);
 
-      case MapObject::Entries: {
-        Value pair[2] = { range->front().key.get(), range->front().value };
-        AutoValueArray root(cx, pair, 2);
-
-        JSObject *pairobj = NewDenseCopiedArray(cx, 2, pair);
-        if (!pairobj)
-            return false;
-        args.rval().setObject(*pairobj);
-        break;
-      }
-    }
-    range->popFront();
     return true;
 }
 
 bool
 MapIteratorObject::next(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod(cx, is, next_impl, args);
@@ -1072,17 +1083,17 @@ MapObject::initClass(JSContext *cx, JSOb
     if (proto) {
         // Define the "entries" method.
         JSFunction *fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
         if (!fun)
             return nullptr;
 
         // Define its alias.
         RootedValue funval(cx, ObjectValue(*fun));
-        if (!JS_DefineProperty(cx, proto, "iterator", funval, nullptr, nullptr, 0))
+        if (!JS_DefineProperty(cx, proto, js_std_iterator_str, funval, nullptr, nullptr, 0))
             return nullptr;
     }
     return proto;
 }
 
 template <class Range>
 static void
 MarkKey(Range &r, const HashableValue &key, JSTracer *trc)
@@ -1168,43 +1179,52 @@ MapObject::construct(JSContext *cx, unsi
     if (!map->init()) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     obj->setPrivate(map);
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.hasDefined(0)) {
-        ForOfIterator iter(cx, args[0]);
-        while (iter.next()) {
-            RootedObject pairobj(cx, ToObject(cx, iter.value()));
-            if (!pairobj)
+        ForOfIterator iter(cx);
+        if (!iter.init(args[0]))
+            return false;
+        RootedValue pairVal(cx);
+        RootedObject pairObj(cx);
+        while (true) {
+            bool done;
+            if (!iter.next(&pairVal, &done))
+                return false;
+            if (done)
+                break;
+            // FIXME: We're supposed to throw if pairVal isn't an object.  Bug
+            // 918341.
+            pairObj = ToObject(cx, pairVal);
+            if (!pairObj)
                 return false;
 
             RootedValue key(cx);
-            if (!JSObject::getElement(cx, pairobj, pairobj, 0, &key))
+            if (!JSObject::getElement(cx, pairObj, pairObj, 0, &key))
                 return false;
 
             AutoHashableValueRooter hkey(cx);
             if (!hkey.setValue(cx, key))
                 return false;
 
             RootedValue val(cx);
-            if (!JSObject::getElement(cx, pairobj, pairobj, 1, &val))
+            if (!JSObject::getElement(cx, pairObj, pairObj, 1, &val))
                 return false;
 
             RelocatableValue rval(val);
             if (!map->put(hkey, rval)) {
                 js_ReportOutOfMemory(cx);
                 return false;
             }
             WriteBarrierPost(cx->runtime(), map, hkey);
         }
-        if (!iter.close())
-            return false;
     }
 
     args.rval().setObject(*obj);
     return true;
 }
 
 bool
 MapObject::is(HandleValue v)
@@ -1451,16 +1471,17 @@ const Class SetIteratorObject::class_ = 
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     SetIteratorObject::finalize
 };
 
 const JSFunctionSpec SetIteratorObject::methods[] = {
+    JS_SELF_HOSTED_FN("@@iterator", "IteratorIdentity", 0, 0),
     JS_FN("next", next, 0, 0),
     JS_FS_END
 };
 
 inline ValueSet::Range *
 SetIteratorObject::range()
 {
     return static_cast<ValueSet::Range *>(getSlot(RangeSlot).toPrivate());
@@ -1526,42 +1547,50 @@ SetIteratorObject::is(HandleValue v)
     return v.isObject() && v.toObject().is<SetIteratorObject>();
 }
 
 bool
 SetIteratorObject::next_impl(JSContext *cx, CallArgs args)
 {
     SetIteratorObject &thisobj = args.thisv().toObject().as<SetIteratorObject>();
     ValueSet::Range *range = thisobj.range();
-    if (!range)
-        return js_ThrowStopIteration(cx);
-    if (range->empty()) {
+    RootedValue value(cx);
+    bool done;
+
+    if (!range || range->empty()) {
         js_delete(range);
         thisobj.setReservedSlot(RangeSlot, PrivateValue(nullptr));
-        return js_ThrowStopIteration(cx);
+        value.setUndefined();
+        done = true;
+    } else {
+        switch (thisobj.kind()) {
+          case SetObject::Values:
+            value = range->front().get();
+            break;
+
+          case SetObject::Entries: {
+            Value pair[2] = { range->front().get(), range->front().get() };
+            AutoValueArray root(cx, pair, 2);
+
+            JSObject *pairObj = NewDenseCopiedArray(cx, 2, pair);
+            if (!pairObj)
+              return false;
+            value.setObject(*pairObj);
+            break;
+          }
+        }
+        range->popFront();
+        done = false;
     }
 
-    switch (thisobj.kind()) {
-      case SetObject::Values:
-        args.rval().set(range->front().get());
-        break;
-
-      case SetObject::Entries: {
-        Value pair[2] = { range->front().get(), range->front().get() };
-        AutoValueArray root(cx, pair, 2);
+    RootedObject result(cx, CreateItrResultObject(cx, value, done));
+    if (!result)
+        return false;
+    args.rval().setObject(*result);
 
-        JSObject *pairobj = NewDenseCopiedArray(cx, 2, pair);
-        if (!pairobj)
-          return false;
-        args.rval().setObject(*pairobj);
-        break;
-      }
-    }
-
-    range->popFront();
     return true;
 }
 
 bool
 SetIteratorObject::next(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod(cx, is, next_impl, args);
@@ -1615,17 +1644,17 @@ SetObject::initClass(JSContext *cx, JSOb
         JSFunction *fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
         if (!fun)
             return nullptr;
 
         // Define its aliases.
         RootedValue funval(cx, ObjectValue(*fun));
         if (!JS_DefineProperty(cx, proto, "keys", funval, nullptr, nullptr, 0))
             return nullptr;
-        if (!JS_DefineProperty(cx, proto, "iterator", funval, nullptr, nullptr, 0))
+        if (!JS_DefineProperty(cx, proto, js_std_iterator_str, funval, nullptr, nullptr, 0))
             return nullptr;
     }
     return proto;
 }
 
 void
 SetObject::mark(JSTracer *trc, JSObject *obj)
 {
@@ -1657,29 +1686,35 @@ SetObject::construct(JSContext *cx, unsi
     if (!set->init()) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     obj->setPrivate(set);
 
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.hasDefined(0)) {
-        ForOfIterator iter(cx, args[0]);
-        while (iter.next()) {
-            AutoHashableValueRooter key(cx);
-            if (!key.setValue(cx, iter.value()))
+        RootedValue keyVal(cx);
+        ForOfIterator iter(cx);
+        if (!iter.init(args[0]))
+            return false;
+        AutoHashableValueRooter key(cx);
+        while (true) {
+            bool done;
+            if (!iter.next(&keyVal, &done))
+                return false;
+            if (done)
+                break;
+            if (!key.setValue(cx, keyVal))
                 return false;
             if (!set->put(key)) {
                 js_ReportOutOfMemory(cx);
                 return false;
             }
             WriteBarrierPost(cx->runtime(), set, key);
         }
-        if (!iter.close())
-            return false;
     }
 
     args.rval().setObject(*obj);
     return true;
 }
 
 bool
 SetObject::is(HandleValue v)
--- a/js/src/builtin/Set.js
+++ b/js/src/builtin/Set.js
@@ -19,18 +19,15 @@ function SetForEach(callbackfn, thisArg 
 
     /* Step 5-6. */
     if (!IsCallable(callbackfn))
         ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
 
     /* Step 7-8. */
     var values = std_Set_iterator.call(S);
     while (true) {
-        try {
-            var entry = std_Set_iterator_next.call(values);
-        } catch (err) {
-            if (err instanceof StopIteration)
-                break;
-            throw err;
-        }
-        callFunction(callbackfn, thisArg, entry, entry, S);
+        var result = std_Set_iterator_next.call(values);
+        if (result.done)
+            break;
+        var value = result.value;
+        callFunction(callbackfn, thisArg, value, value, S);
     }
 }
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -34,16 +34,17 @@
 #else
 #define assert(b, info)
 #endif
 
 /* cache built-in functions before applications can change them */
 var std_isFinite = isFinite;
 var std_isNaN = isNaN;
 var std_Array_indexOf = ArrayIndexOf;
+var std_Array_iterator = Array.prototype.iterator;
 var std_Array_join = Array.prototype.join;
 var std_Array_push = Array.prototype.push;
 var std_Array_shift = Array.prototype.shift;
 var std_Array_slice = Array.prototype.slice;
 var std_Array_sort = Array.prototype.sort;
 var std_Array_unshift = Array.prototype.unshift;
 var std_Boolean_toString = Boolean.prototype.toString;
 var Std_Date = Date;
@@ -66,25 +67,28 @@ var std_String_indexOf = String.prototyp
 var std_String_lastIndexOf = String.prototype.lastIndexOf;
 var std_String_match = String.prototype.match;
 var std_String_replace = String.prototype.replace;
 var std_String_split = String.prototype.split;
 var std_String_startsWith = String.prototype.startsWith;
 var std_String_substring = String.prototype.substring;
 var std_String_toLowerCase = String.prototype.toLowerCase;
 var std_String_toUpperCase = String.prototype.toUpperCase;
+var std_WeakMap = WeakMap;
 var std_WeakMap_get = WeakMap.prototype.get;
 var std_WeakMap_has = WeakMap.prototype.has;
 var std_WeakMap_set = WeakMap.prototype.set;
 var std_Map_has = Map.prototype.has;
 var std_Set_has = Set.prototype.has;
-var std_Map_iterator = Map().iterator;
-var std_Set_iterator = Set().iterator;
-var std_Map_iterator_next = Object.getPrototypeOf(Map().iterator()).next;
-var std_Set_iterator_next = Object.getPrototypeOf(Set().iterator()).next;
+var std_iterator = '@@iterator'; // FIXME: Change to be a symbol.
+var std_StopIteration = StopIteration;
+var std_Map_iterator = Map.prototype[std_iterator];
+var std_Set_iterator = Set.prototype[std_iterator];
+var std_Map_iterator_next = Object.getPrototypeOf(Map()[std_iterator]()).next;
+var std_Set_iterator_next = Object.getPrototypeOf(Set()[std_iterator]()).next;
 
 /********** List specification type **********/
 
 
 /* Spec: ECMAScript Language Specification, 5.1 edition, 8.8 */
 function List() {}
 {
   let ListProto = std_Object_create(null);
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -172,16 +172,18 @@ UpdateDepth(ExclusiveContext *cx, Byteco
     int nuses, ndefs;
     if (op == JSOP_ENTERBLOCK) {
         nuses = 0;
         ndefs = CurrentBlock(bce->topStmt).slotCount();
     } else if (op == JSOP_ENTERLET0) {
         nuses = ndefs = CurrentBlock(bce->topStmt).slotCount();
     } else if (op == JSOP_ENTERLET1) {
         nuses = ndefs = CurrentBlock(bce->topStmt).slotCount() + 1;
+    } else if (op == JSOP_ENTERLET2) {
+        nuses = ndefs = CurrentBlock(bce->topStmt).slotCount() + 2;
     } else {
         nuses = StackUses(nullptr, pc);
         ndefs = StackDefs(nullptr, pc);
     }
 
     bce->stackDepth -= nuses;
     JS_ASSERT(bce->stackDepth >= 0);
     bce->stackDepth += ndefs;
@@ -293,16 +295,17 @@ static const char * const statementName[
     js_with_statement_str,   /* WITH */
     "catch block",           /* CATCH */
     "try block",             /* TRY */
     js_finally_block_str,    /* FINALLY */
     js_finally_block_str,    /* SUBROUTINE */
     "do loop",               /* DO_LOOP */
     "for loop",              /* FOR_LOOP */
     "for/in loop",           /* FOR_IN_LOOP */
+    "for/of loop",           /* FOR_OF_LOOP */
     "while loop",            /* WHILE_LOOP */
 };
 
 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(statementName) == STMT_LIMIT);
 
 static const char *
 StatementName(StmtInfoBCE *topStmt)
 {
@@ -550,16 +553,20 @@ EmitNonLocalJumpFixup(ExclusiveContext *
             /* There's a With object on the stack that we need to pop. */
             FLUSH_POPS();
             if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
                 return false;
             if (Emit1(cx, bce, JSOP_LEAVEWITH) < 0)
                 return false;
             break;
 
+          case STMT_FOR_OF_LOOP:
+            npops += 2;
+            break;
+
           case STMT_FOR_IN_LOOP:
             FLUSH_POPS();
             if (!PopIterator(cx, bce))
                 return false;
             break;
 
           case STMT_SUBROUTINE:
             /*
@@ -572,33 +579,38 @@ EmitNonLocalJumpFixup(ExclusiveContext *
           default:;
         }
 
         if (stmt->isBlockScope) {
             FLUSH_POPS();
             unsigned blockObjCount = stmt->blockObj->slotCount();
             if (stmt->isForLetBlock) {
                 /*
-                 * For a for-let-in statement, pushing/popping the block is
+                 * For a for-let-in/of statement, pushing/popping the block is
                  * interleaved with JSOP_(END)ITER. Just handle both together
                  * here and skip over the enclosing STMT_FOR_IN_LOOP.
                  */
-                JS_ASSERT(stmt->down->type == STMT_FOR_IN_LOOP);
+                unsigned popCount = blockObjCount;
                 stmt = stmt->down;
                 if (stmt == toStmt)
                     break;
                 if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
                     return false;
                 if (Emit1(cx, bce, JSOP_LEAVEFORLETIN) < 0)
                     return false;
-                if (!PopIterator(cx, bce))
-                    return false;
+                if (stmt->type == STMT_FOR_OF_LOOP) {
+                    popCount += 2;
+                } else {
+                    JS_ASSERT(stmt->type == STMT_FOR_IN_LOOP);
+                    if (!PopIterator(cx, bce))
+                        return false;
+                }
                 if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
                     return false;
-                EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount);
+                EMIT_UINT16_IMM_OP(JSOP_POPN, popCount);
             } else {
                 /* There is a Block object with locals on the stack to pop. */
                 if (NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
                     return false;
                 EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, blockObjCount);
             }
         }
     }
@@ -1047,18 +1059,22 @@ static bool
 EmitEnterBlock(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSOp op)
 {
     JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
     if (!EmitObjectOp(cx, pn->pn_objbox, op, bce))
         return false;
 
     Rooted<StaticBlockObject*> blockObj(cx, &pn->pn_objbox->object->as<StaticBlockObject>());
 
-    int depth = bce->stackDepth -
-                (blockObj->slotCount() + ((op == JSOP_ENTERLET1) ? 1 : 0));
+    int extraSlots = (op == JSOP_ENTERLET1)
+                     ? 1
+                     : (op == JSOP_ENTERLET2)
+                     ? 2
+                     : 0;
+    int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots);
     JS_ASSERT(depth >= 0);
 
     blockObj->setStackDepth(depth);
 
     int depthPlusFixed = AdjustBlockSlot(cx, bce, depth);
     if (depthPlusFixed < 0)
         return false;
 
@@ -3513,20 +3529,21 @@ EmitAssignment(ExclusiveContext *cx, Byt
     }
 
     /* Now emit the right operand (it may affect the namespace). */
     if (rhs) {
         if (!EmitTree(cx, bce, rhs))
             return false;
     } else {
         /*
-         * The value to assign is the next enumeration value in a for-in loop.
-         * That value is produced by a JSOP_ITERNEXT op, previously emitted.
-         * If offset == 1, that slot is already at the top of the
-         * stack. Otherwise, rearrange the stack to put that value on top.
+         * The value to assign is the next enumeration value in a for-in or
+         * for-of loop.  That value has already been emitted: by JSOP_ITERNEXT
+         * in the for-in case, or via a GETPROP "value" on the result object in
+         * the for-of case.  If offset == 1, that slot is already at the top of
+         * the stack. Otherwise, rearrange the stack to put that value on top.
          */
         if (offset != 1 && Emit2(cx, bce, JSOP_PICK, offset - 1) < 0)
             return false;
     }
 
     /* If += etc., emit the binary operator with a source note. */
     if (op != JSOP_NOP) {
         /*
@@ -4230,16 +4247,180 @@ EmitWith(ExclusiveContext *cx, BytecodeE
     if (!EmitTree(cx, bce, pn->pn_right))
         return false;
     if (Emit1(cx, bce, JSOP_LEAVEWITH) < 0)
         return false;
     return PopStatementBCE(cx, bce);
 }
 
 static bool
+EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
+{
+    StmtInfoBCE stmtInfo(cx);
+    PushStatementBCE(bce, &stmtInfo, STMT_FOR_OF_LOOP, top);
+
+    ParseNode *forHead = pn->pn_left;
+    ParseNode *forBody = pn->pn_right;
+
+    ParseNode *pn1 = forHead->pn_kid1;
+    bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE);
+    JS_ASSERT_IF(letDecl, pn1->isLet());
+
+    Rooted<StaticBlockObject*>
+        blockObj(cx, letDecl ? &pn1->pn_objbox->object->as<StaticBlockObject>() : nullptr);
+    uint32_t blockObjCount = blockObj ? blockObj->slotCount() : 0;
+
+    // For-of loops run with two values on the stack: the iterator and the
+    // current result object.  If the loop also has a lexical block, those
+    // lexicals are deeper on the stack than the iterator.
+    for (uint32_t i = 0; i < blockObjCount; ++i) {
+        if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
+            return false;
+    }
+
+    // If the left part is 'var x', emit code to define x if necessary using a
+    // prolog opcode, but do not emit a pop.
+    if (pn1) {
+        ParseNode *decl = letDecl ? pn1->pn_expr : pn1;
+        JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET));
+        bce->emittingForInit = true;
+        if (!EmitVariables(cx, bce, decl, DefineVars))
+            return false;
+        bce->emittingForInit = false;
+    }
+
+    // Compile the object expression to the right of 'of'.
+    if (!EmitTree(cx, bce, forHead->pn_kid3))
+        return false;
+
+    // Convert iterable to iterator.
+    if (Emit1(cx, bce, JSOP_DUP) < 0)                          // OBJ OBJ
+        return false;
+    if (!EmitAtomOp(cx, cx->names().std_iterator, JSOP_CALLPROP, bce)) // OBJ @@ITERATOR
+        return false;
+    if (Emit1(cx, bce, JSOP_SWAP) < 0)                         // @@ITERATOR OBJ
+        return false;
+    if (Emit1(cx, bce, JSOP_NOTEARG) < 0)
+        return false;
+    if (EmitCall(cx, bce, JSOP_CALL, 0) < 0)                   // ITER
+        return false;
+    CheckTypeSet(cx, bce, JSOP_CALL);
+
+    // Push a dummy result so that we properly enter iteration midstream.
+    if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)                    // ITER RESULT
+        return false;
+
+    // Enter the block before the loop body, after evaluating the obj.
+    StmtInfoBCE letStmt(cx);
+    if (letDecl) {
+        PushBlockScopeBCE(bce, &letStmt, *blockObj, bce->offset());
+        letStmt.isForLetBlock = true;
+        if (!EmitEnterBlock(cx, bce, pn1, JSOP_ENTERLET2))
+            return false;
+    }
+
+    // Jump down to the loop condition to minimize overhead assuming at least
+    // one iteration, as the other loop forms do.  Annotate so IonMonkey can
+    // find the loop-closing jump.
+    int noteIndex = NewSrcNote(cx, bce, SRC_FOR_OF);
+    if (noteIndex < 0)
+        return false;
+    ptrdiff_t jmp = EmitJump(cx, bce, JSOP_GOTO, 0);
+    if (jmp < 0)
+        return false;
+
+    top = bce->offset();
+    SET_STATEMENT_TOP(&stmtInfo, top);
+    if (EmitLoopHead(cx, bce, nullptr) < 0)
+        return false;
+
+#ifdef DEBUG
+    int loopDepth = bce->stackDepth;
+#endif
+
+    // Emit code to assign result.value to the iteration variable.
+    if (Emit1(cx, bce, JSOP_DUP) < 0)                          // ITER RESULT RESULT
+        return false;
+    if (!EmitAtomOp(cx, cx->names().value, JSOP_GETPROP, bce)) // ITER RESULT VALUE
+        return false;
+    if (!EmitAssignment(cx, bce, forHead->pn_kid2, JSOP_NOP, nullptr)) // ITER RESULT VALUE
+        return false;
+    if (Emit1(cx, bce, JSOP_POP) < 0)                          // ITER RESULT
+        return false;
+
+    // The stack should be balanced around the assignment opcode sequence.
+    JS_ASSERT(bce->stackDepth == loopDepth);
+
+    // Emit code for the loop body.
+    if (!EmitTree(cx, bce, forBody))
+        return false;
+
+    // Set loop and enclosing "update" offsets, for continue.
+    StmtInfoBCE *stmt = &stmtInfo;
+    do {
+        stmt->update = bce->offset();
+    } while ((stmt = stmt->down) != nullptr && stmt->type == STMT_LABEL);
+
+    // COME FROM the beginning of the loop to here.
+    SetJumpOffsetAt(bce, jmp);
+    if (!EmitLoopEntry(cx, bce, nullptr))
+        return false;
+
+    if (Emit1(cx, bce, JSOP_POP) < 0)                          // ITER
+        return false;
+    if (Emit1(cx, bce, JSOP_DUP) < 0)                          // ITER ITER
+        return false;
+    if (Emit1(cx, bce, JSOP_DUP) < 0)                          // ITER ITER ITER
+        return false;
+    if (!EmitAtomOp(cx, cx->names().next, JSOP_CALLPROP, bce)) // ITER ITER NEXT
+        return false;
+    if (Emit1(cx, bce, JSOP_SWAP) < 0)                         // ITER NEXT ITER
+        return false;
+    if (Emit1(cx, bce, JSOP_NOTEARG) < 0)
+        return false;
+    if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)                    // ITER NEXT ITER UNDEFINED
+        return false;
+    if (Emit1(cx, bce, JSOP_NOTEARG) < 0)
+        return false;
+    if (EmitCall(cx, bce, JSOP_CALL, 1) < 0)                   // ITER RESULT
+        return false;
+    CheckTypeSet(cx, bce, JSOP_CALL);
+    if (Emit1(cx, bce, JSOP_DUP) < 0)                          // ITER RESULT RESULT
+        return false;
+    if (!EmitAtomOp(cx, cx->names().done, JSOP_GETPROP, bce))  // ITER RESULT DONE?
+        return false;
+
+    ptrdiff_t beq = EmitJump(cx, bce, JSOP_IFEQ, top - bce->offset()); // ITER RESULT
+    if (beq < 0)
+        return false;
+
+    JS_ASSERT(bce->stackDepth == loopDepth);
+
+    // Let Ion know where the closing jump of this loop is.
+    if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, beq - jmp))
+        return false;
+
+    // Fixup breaks and continues.
+    if (!PopStatementBCE(cx, bce))
+        return false;
+
+    if (letDecl) {
+        if (!PopStatementBCE(cx, bce))
+            return false;
+        if (Emit1(cx, bce, JSOP_LEAVEFORLETIN) < 0)
+            return false;
+    }
+
+    // Pop result, iter, and slots from the lexical block (if any).
+    EMIT_UINT16_IMM_OP(JSOP_POPN, blockObjCount + 2);
+
+    return true;
+}
+
+static bool
 EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
     StmtInfoBCE stmtInfo(cx);
     PushStatementBCE(bce, &stmtInfo, STMT_FOR_IN_LOOP, top);
 
     ParseNode *forHead = pn->pn_left;
     ParseNode *forBody = pn->pn_right;
 
@@ -4548,20 +4729,24 @@ EmitNormalFor(ExclusiveContext *cx, Byte
 
     /* Now fixup all breaks and continues. */
     return PopStatementBCE(cx, bce);
 }
 
 static inline bool
 EmitFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
 {
-    JS_ASSERT(pn->pn_left->isKind(PNK_FORIN) || pn->pn_left->isKind(PNK_FORHEAD));
-    return pn->pn_left->isKind(PNK_FORIN)
-           ? EmitForIn(cx, bce, pn, top)
-           : EmitNormalFor(cx, bce, pn, top);
+    if (pn->pn_left->isKind(PNK_FORIN)) {
+        // FIXME: Give for-of loops their own PNK.  Bug 922066.
+        if (pn->pn_iflags == JSITER_FOR_OF)
+            return EmitForOf(cx, bce, pn, top);
+        return EmitForIn(cx, bce, pn, top);
+    }
+    JS_ASSERT(pn->pn_left->isKind(PNK_FORHEAD));
+    return EmitNormalFor(cx, bce, pn, top);
 }
 
 static JS_NEVER_INLINE bool
 EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     cx->maybePause();
 
     FunctionBox *funbox = pn->pn_funbox;
@@ -4987,16 +5172,17 @@ EmitYieldStar(ExclusiveContext *cx, Byte
     if (Emit1(cx, bce, JSOP_NOTEARG) < 0)                        // EXCEPTION ITER THROW ITER
         return false;
     if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0)            // ITER THROW ITER EXCEPTION
         return false;
     if (Emit1(cx, bce, JSOP_NOTEARG) < 0)                        // ITER THROW ITER EXCEPTION
         return false;
     if (EmitCall(cx, bce, JSOP_CALL, 1) < 0)                     // ITER RESULT
         return false;
+    CheckTypeSet(cx, bce, JSOP_CALL);
     JS_ASSERT(bce->stackDepth == depth + 1);
     ptrdiff_t checkResult = -1;
     if (EmitBackPatchOp(cx, bce, &checkResult) < 0)              // goto checkResult
         return false;
 
     // Catch epilogue.
     if (!PopStatementBCE(cx, bce))
         return false;
@@ -5027,16 +5213,17 @@ EmitYieldStar(ExclusiveContext *cx, Byte
     if (Emit1(cx, bce, JSOP_NOTEARG) < 0)                        // RECEIVED ITER NEXT ITER
         return false;
     if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0)            // ITER NEXT ITER RECEIVED
         return false;
     if (Emit1(cx, bce, JSOP_NOTEARG) < 0)                        // ITER NEXT ITER RECEIVED
         return false;
     if (EmitCall(cx, bce, JSOP_CALL, 1) < 0)                     // ITER RESULT
         return false;
+    CheckTypeSet(cx, bce, JSOP_CALL);
     JS_ASSERT(bce->stackDepth == depth + 1);
 
     if (!BackPatch(cx, bce, checkResult, bce->code().end(), JSOP_GOTO)) // checkResult:
         return false;
     // if (!result.done) goto tryStart;                          // ITER RESULT
     if (Emit1(cx, bce, JSOP_DUP) < 0)                            // ITER RESULT RESULT
         return false;
     if (!EmitAtomOp(cx, cx->names().done, JSOP_GETPROP, bce))    // ITER RESULT DONE
@@ -6667,51 +6854,53 @@ CGConstList::finish(ConstArray *array)
 
     for (unsigned i = 0; i < length(); i++)
         array->vector[i] = list[i];
 }
 
 /*
  * We should try to get rid of offsetBias (always 0 or 1, where 1 is
  * JSOP_{NOP,POP}_LENGTH), which is used only by SRC_FOR.
+ *
+ * FIXME: Generate this using a higher-order macro.  Bug 922070.
  */
 const JSSrcNoteSpec js_SrcNoteSpec[] = {
 /*  0 */ {"null",           0},
 
 /*  1 */ {"if",             0},
 /*  2 */ {"if-else",        1},
 /*  3 */ {"cond",           1},
 
 /*  4 */ {"for",            3},
 
 /*  5 */ {"while",          1},
 /*  6 */ {"for-in",         1},
-/*  7 */ {"continue",       0},
-/*  8 */ {"break",          0},
-/*  9 */ {"break2label",    0},
-/* 10 */ {"switchbreak",    0},
-
-/* 11 */ {"tableswitch",    1},
-/* 12 */ {"condswitch",     2},
-
-/* 13 */ {"nextcase",       1},
-
-/* 14 */ {"assignop",       0},
-
-/* 15 */ {"hidden",         0},
-
-/* 16 */ {"catch",          0},
-
-/* 17 */ {"try",            1},
-
-/* 18 */ {"colspan",        1},
-/* 19 */ {"newline",        0},
-/* 20 */ {"setline",        1},
-
-/* 21 */ {"unused21",       0},
+/*  7 */ {"for-of",         1},
+/*  8 */ {"continue",       0},
+/*  9 */ {"break",          0},
+/* 10 */ {"break2label",    0},
+/* 11 */ {"switchbreak",    0},
+
+/* 12 */ {"tableswitch",    1},
+/* 13 */ {"condswitch",     2},
+
+/* 14 */ {"nextcase",       1},
+
+/* 15 */ {"assignop",       0},
+
+/* 16 */ {"hidden",         0},
+
+/* 17 */ {"catch",          0},
+
+/* 18 */ {"try",            1},
+
+/* 19 */ {"colspan",        1},
+/* 20 */ {"newline",        0},
+/* 21 */ {"setline",        1},
+
 /* 22 */ {"unused22",       0},
 /* 23 */ {"unused23",       0},
 
 /* 24 */ {"xdelta",         0},
 };
 
 JS_FRIEND_API(unsigned)
 js_SrcNoteLength(jssrcnote *sn)
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -3988,17 +3988,17 @@ Parser<FullParseHandler>::forStatement()
         /*
          * Parse the rest of the for/in or for/of head.
          *
          * Here pn1 is everything to the left of 'in' or 'of'. At the end of
          * this block, pn1 is a decl or nullptr, pn2 is the assignment target
          * that receives the enumeration value each iteration, and pn3 is the
          * rhs of 'in'.
          */
-        forStmt.type = STMT_FOR_IN_LOOP;
+        forStmt.type = isForOf ? STMT_FOR_OF_LOOP : STMT_FOR_IN_LOOP;
 
         /* Set iflags and rule out invalid combinations. */
         if (isForOf && isForEach) {
             report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP);
             return null();
         }
         iflags |= (isForOf ? JSITER_FOR_OF : JSITER_ENUMERATE);
 
@@ -4270,17 +4270,17 @@ Parser<SyntaxParseHandler>::forStatement
      * We can be sure that it's a for/in loop if there's still an 'in'
      * keyword here, even if JavaScript recognizes 'in' as an operator,
      * as we've excluded 'in' from being parsed in RelExpr by setting
      * pc->parsingForInit.
      */
     bool isForOf;
     if (lhsNode && matchInOrOf(&isForOf)) {
         /* Parse the rest of the for/in or for/of head. */
-        forStmt.type = STMT_FOR_IN_LOOP;
+        forStmt.type = isForOf ? STMT_FOR_OF_LOOP : STMT_FOR_IN_LOOP;
 
         /* Check that the left side of the 'in' or 'of' is valid. */
         if (!isForDecl &&
             lhsNode != SyntaxParseHandler::NodeName &&
             lhsNode != SyntaxParseHandler::NodeGetProp &&
             lhsNode != SyntaxParseHandler::NodeLValue)
         {
             JS_ALWAYS_FALSE(abortIfSyntaxParser());
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -358,16 +358,17 @@ enum StmtType {
     STMT_WITH,                  /* with statement */
     STMT_CATCH,                 /* catch block */
     STMT_TRY,                   /* try block */
     STMT_FINALLY,               /* finally block */
     STMT_SUBROUTINE,            /* gosub-target subroutine body */
     STMT_DO_LOOP,               /* do/while loop statement */
     STMT_FOR_LOOP,              /* for loop statement */
     STMT_FOR_IN_LOOP,           /* for/in loop statement */
+    STMT_FOR_OF_LOOP,           /* for/of loop statement */
     STMT_WHILE_LOOP,            /* while loop statement */
     STMT_LIMIT
 };
 
 /*
  * A comment on the encoding of the js::StmtType enum and StmtInfoBase
  * type-testing methods:
  *
--- a/js/src/frontend/SourceNotes.h
+++ b/js/src/frontend/SourceNotes.h
@@ -31,61 +31,65 @@ namespace js {
  * At most one "gettable" note (i.e., a note of type other than SRC_NEWLINE,
  * SRC_COLSPAN, SRC_SETLINE, and SRC_XDELTA) applies to a given bytecode.
  *
  * NB: the js_SrcNoteSpec array in BytecodeEmitter.cpp is indexed by this
  * enum, so its initializers need to match the order here.
  *
  * Don't forget to update XDR_BYTECODE_VERSION in vm/Xdr.h for all such
  * incompatible source note or other bytecode changes.
+ *
+ * FIXME: Use higher-order macro to force this to be in sync with
+ * js_SrcNoteSpec.  Bug 922070.
  */
 enum SrcNoteType {
     SRC_NULL        = 0,        /* terminates a note vector */
 
     SRC_IF          = 1,        /* JSOP_IFEQ bytecode is from an if-then */
     SRC_IF_ELSE     = 2,        /* JSOP_IFEQ bytecode is from an if-then-else */
     SRC_COND        = 3,        /* JSOP_IFEQ is from conditional ?: operator */
 
     SRC_FOR         = 4,        /* JSOP_NOP or JSOP_POP in for(;;) loop head */
 
     SRC_WHILE       = 5,        /* JSOP_GOTO to for or while loop condition
                                    from before loop, else JSOP_NOP at top of
                                    do-while loop */
     SRC_FOR_IN      = 6,        /* JSOP_GOTO to for-in loop condition from
                                    before loop */
-    SRC_CONTINUE    = 7,        /* JSOP_GOTO is a continue */
-    SRC_BREAK       = 8,        /* JSOP_GOTO is a break */
-    SRC_BREAK2LABEL = 9,        /* JSOP_GOTO for 'break label' */
-    SRC_SWITCHBREAK = 10,       /* JSOP_GOTO is a break in a switch */
+    SRC_FOR_OF      = 7,        /* JSOP_GOTO to for-of loop condition from
+                                   before loop */
+    SRC_CONTINUE    = 8,        /* JSOP_GOTO is a continue */
+    SRC_BREAK       = 9,        /* JSOP_GOTO is a break */
+    SRC_BREAK2LABEL = 10,       /* JSOP_GOTO for 'break label' */
+    SRC_SWITCHBREAK = 11,       /* JSOP_GOTO is a break in a switch */
 
-    SRC_TABLESWITCH = 11,       /* JSOP_TABLESWITCH, offset points to end of
+    SRC_TABLESWITCH = 12,       /* JSOP_TABLESWITCH, offset points to end of
                                    switch */
-    SRC_CONDSWITCH  = 12,       /* JSOP_CONDSWITCH, 1st offset points to end of
+    SRC_CONDSWITCH  = 13,       /* JSOP_CONDSWITCH, 1st offset points to end of
                                    switch, 2nd points to first JSOP_CASE */
 
-    SRC_NEXTCASE    = 13,       /* distance forward from one CASE in a
+    SRC_NEXTCASE    = 14,       /* distance forward from one CASE in a
                                    CONDSWITCH to the next */
 
-    SRC_ASSIGNOP    = 14,       /* += or another assign-op follows */
+    SRC_ASSIGNOP    = 15,       /* += or another assign-op follows */
 
-    SRC_HIDDEN      = 15,       /* opcode shouldn't be decompiled */
+    SRC_HIDDEN      = 16,       /* opcode shouldn't be decompiled */
 
-    SRC_CATCH       = 16,       /* catch block has guard */
+    SRC_CATCH       = 17,       /* catch block has guard */
 
-    SRC_TRY         = 17,       /* JSOP_TRY, offset points to goto at the
+    SRC_TRY         = 18,       /* JSOP_TRY, offset points to goto at the
                                    end of the try block. */
 
     /* All notes below here are "gettable".  See SN_IS_GETTABLE below. */
     SRC_LAST_GETTABLE = SRC_TRY,
 
-    SRC_COLSPAN     = 18,       /* number of columns this opcode spans */
-    SRC_NEWLINE     = 19,       /* bytecode follows a source newline */
-    SRC_SETLINE     = 20,       /* a file-absolute source line number note */
+    SRC_COLSPAN     = 19,       /* number of columns this opcode spans */
+    SRC_NEWLINE     = 20,       /* bytecode follows a source newline */
+    SRC_SETLINE     = 21,       /* a file-absolute source line number note */
 
-    SRC_UNUSED21    = 21,
     SRC_UNUSED22    = 22,
     SRC_UNUSED23    = 23,
 
     SRC_XDELTA      = 24        /* 24-31 are for extended delta notes */
 };
 
 /* A source note array is terminated by an all-zero element. */
 inline void
--- a/js/src/jit-test/lib/iteration.js
+++ b/js/src/jit-test/lib/iteration.js
@@ -1,14 +1,21 @@
 /* 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/. */
 
 
 load(libdir + "asserts.js");
 
+// FIXME: Import from std::iteration.
+const std_iterator = '@@iterator';
+
 if (typeof assertIteratorResult === 'undefined') {
     var assertIteratorResult = function assertIteratorResult(result, value, done) {
         assertEq(typeof result, "object");
+        var expectedProps = ['done', 'value'];
+        var actualProps = Object.getOwnPropertyNames(result);
+        actualProps.sort(), expectedProps.sort();
+        assertDeepEq(actualProps, expectedProps);
         assertDeepEq(result.value, value);
         assertDeepEq(result.done, done);
     }
 }
--- a/js/src/jit-test/tests/basic/bug770952.js
+++ b/js/src/jit-test/tests/basic/bug770952.js
@@ -1,5 +1,7 @@
 // |jit-test| error: TypeError
+load(libdir + "iteration.js");
+
 eval("var x; typeof x")
-Array.prototype.iterator = function () { for(y in x); };
+Array.prototype[std_iterator] = function () { for(y in x); };
 for (var v of ['a', 'b', 'c', 'd'])
     s = v;
--- a/js/src/jit-test/tests/basic/spread-array.js
+++ b/js/src/jit-test/tests/basic/spread-array.js
@@ -1,9 +1,10 @@
 load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 load(libdir + "eqArrayHelper.js");
 
 assertEqArray([...[1, 2, 3]], [1, 2, 3]);
 assertEqArray([1, ...[2, 3, 4], 5], [1, 2, 3, 4, 5]);
 assertEqArray([1, ...[], 2], [1, 2]);
 assertEqArray([1, ...[2, 3], 4, ...[5, 6]], [1, 2, 3, 4, 5, 6]);
 assertEqArray([1, ...[], 2], [1, 2]);
 assertEqArray([1,, ...[2]], [1,, 2]);
@@ -14,37 +15,34 @@ assertEqArray([,,...[1, 2, 3],,,,], [,,1
 assertEqArray([...[undefined]], [undefined]);
 
 // other iterable objects
 assertEqArray([...Int32Array([1, 2, 3])], [1, 2, 3]);
 assertEqArray([..."abc"], ["a", "b", "c"]);
 assertEqArray([...[1, 2, 3].iterator()], [1, 2, 3]);
 assertEqArray([...Set([1, 2, 3])], [1, 2, 3]);
 assertEqArray([...Map([["a", "A"], ["b", "B"], ["c", "C"]])].map(([k, v]) => k + v), ["aA", "bB", "cC"]);
-let itr = {
-  iterator: function() {
+let itr = {};
+itr[std_iterator] = function () {
     return {
-      i: 1,
-      next: function() {
-        if (this.i < 4)
-          return this.i++;
-        else
-          throw StopIteration;
-      }
+        i: 1,
+        next: function() {
+            if (this.i < 4)
+                return { value: this.i++, done: false };
+            else
+                return { value: undefined, done: true };
+        }
     };
-  }
-};
+}
 assertEqArray([...itr], [1, 2, 3]);
-let gen = {
-  iterator: function() {
+function* gen() {
     for (let i = 1; i < 4; i ++)
-      yield i;
-  }
-};
-assertEqArray([...gen], [1, 2, 3]);
+        yield i;
+}
+assertEqArray([...gen()], [1, 2, 3]);
 
 let a, b = [1, 2, 3];
 assertEqArray([...a=b], [1, 2, 3]);
 
 // According to the draft spec, null and undefined are to be treated as empty
 // arrays. However, they are not iterable. If the spec is not changed to be in
 // terms of iterables, these tests should be fixed.
 //assertEqArray([1, ...null, 2], [1, 2]);
--- a/js/src/jit-test/tests/basic/spread-call-eval.js
+++ b/js/src/jit-test/tests/basic/spread-call-eval.js
@@ -1,9 +1,10 @@
 load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 assertEq(eval(...[]), undefined);
 assertEq(eval(...["1 + 2"]), 3);
 
 let a = 10, b = 1;
 assertEq(eval(...["a + b"]), 11);
 
 (function() {
@@ -20,37 +21,34 @@ try {             // line0 + 1
   eval(...["("]); // line0 + 2
 } catch (e) {
   assertEq(e.lineNumber, line0 + 2);
 }
 
 // other iterable objects
 assertEq(eval(...["a + b"].iterator()), 11);
 assertEq(eval(...Set(["a + b"])), 11);
-let itr = {
-  iterator: function() {
+let itr = {};
+itr[std_iterator] = function() {
     return {
-      i: 0,
-      next: function() {
-        this.i++;
-        if (this.i == 1)
-          return "a + b";
-        else
-          throw StopIteration;
-      }
+        i: 0,
+        next: function() {
+            this.i++;
+            if (this.i == 1)
+                return { value: "a + b", done: false };
+            else
+                return { value: undefined, done: true };
+        }
     };
-  }
 };
 assertEq(eval(...itr), 11);
-let gen = {
-  iterator: function() {
+function* gen() {
     yield "a + b";
-  }
-};
-assertEq(eval(...gen), 11);
+}
+assertEq(eval(...gen()), 11);
 
 let c = ["C"], d = "D";
 assertEq(eval(...c=["c[0] + d"]), "c[0] + dD");
 
 // According to the draft spec, null and undefined are to be treated as empty
 // arrays. However, they are not iterable. If the spec is not changed to be in
 // terms of iterables, these tests should be fixed.
 //assertEq(eval("a + b", ...null), 11);
--- a/js/src/jit-test/tests/basic/spread-call-funapply.js
+++ b/js/src/jit-test/tests/basic/spread-call-funapply.js
@@ -1,44 +1,42 @@
 load(libdir + "asserts.js");
 load(libdir + "eqArrayHelper.js");
+load(libdir + "iteration.js");
 
 function checkCommon(f) {
   assertEqArray(f.apply(null, ...[[1, 2, 3]]), [1, 2, 3]);
   assertEqArray(f.apply(...[null], [1, 2, 3]), [1, 2, 3]);
   assertEqArray(f.apply(...[null], ...[[1, 2, 3]]), [1, 2, 3]);
   assertEqArray(f.apply(...[null, [1, 2, 3]]), [1, 2, 3]);
 
   // other iterable objects
   assertEqArray(f.apply(...Set([null, [1, 2, 3]])), [1, 2, 3]);
   assertEqArray(f.apply(...[null, [1, 2, 3]].iterator()), [1, 2, 3]);
-  let itr = {
-    iterator: function() {
+  let itr = {};
+  itr[std_iterator] = function() {
       return {
-        i: 0,
-        next: function() {
-          this.i++;
-          if (this.i == 1)
-            return null;
-          else if (this.i == 2)
-            return [1, 2, 3];
-          else
-            throw StopIteration;
-        }
+          i: 0,
+          next: function() {
+              this.i++;
+              if (this.i == 1)
+                  return { value: null, done: false };
+              else if (this.i == 2)
+                  return { value: [1, 2, 3], done: false };
+              else
+                  return { value: undefined, done: true };
+          }
       };
-    }
   };
   assertEqArray(f.apply(...itr), [1, 2, 3]);
-  let gen = {
-    iterator: function() {
+  function* gen() {
       yield null;
       yield [1, 2, 3];
-    }
-  };
-  assertEqArray(f.apply(...gen), [1, 2, 3]);
+  }
+  assertEqArray(f.apply(...gen()), [1, 2, 3]);
 
   let a;
   assertEqArray(f.apply(null, ...a=[[1, 2, 3]]), [1, 2, 3]);
 
   // According to the draft spec, null and undefined are to be treated as empty
   // arrays. However, they are not iterable. If the spec is not changed to be in
   // terms of iterables, these tests should be fixed.
   //assertEqArray(f.apply(null, ...null, [1, 2, 3]), [1, 2, 3]);
--- a/js/src/jit-test/tests/basic/spread-call-length.js
+++ b/js/src/jit-test/tests/basic/spread-call-length.js
@@ -1,8 +1,10 @@
+load(libdir + 'iteration.js');
+
 let makeCall    = farg => Function("f", "arg", "return f(" + farg + ");");
 let makeFunCall = farg => Function("f", "arg", "return f.call(null, " + farg + ");");
 let makeNew     = farg => Function("f", "arg", "return new f(" + farg + ").length;");
 
 function checkLength(f, makeFn) {
   assertEq(makeFn("...[1, 2, 3]")(f), 3);
   assertEq(makeFn("1, ...[2], 3")(f), 3);
   assertEq(makeFn("1, ...[2], ...[3]")(f), 3);
@@ -17,37 +19,34 @@ function checkLength(f, makeFn) {
   assertEq(makeFn("...[undefined]")(f), 1);
 
   // other iterable objects
   assertEq(makeFn("...arg")(f, Int32Array([1, 2, 3])), 3);
   assertEq(makeFn("...arg")(f, "abc"), 3);
   assertEq(makeFn("...arg")(f, [1, 2, 3].iterator()), 3);
   assertEq(makeFn("...arg")(f, Set([1, 2, 3])), 3);
   assertEq(makeFn("...arg")(f, Map([["a", "A"], ["b", "B"], ["c", "C"]])), 3);
-  let itr = {
-    iterator: function() {
+  let itr = {};
+  itr[std_iterator] = function() {
       return {
-        i: 1,
-        next: function() {
-          if (this.i < 4)
-            return this.i++;
-          else
-            throw StopIteration;
-        }
+          i: 1,
+          next: function() {
+              if (this.i < 4)
+                  return { value: this.i++, done: false };
+              else
+                  return { value: undefined, done: true };
+          }
       };
-    }
-  };
+  }
   assertEq(makeFn("...arg")(f, itr), 3);
-  let gen = {
-    iterator: function() {
+  function* gen() {
       for (let i = 1; i < 4; i ++)
-        yield i;
-    }
-  };
-  assertEq(makeFn("...arg")(f, gen), 3);
+          yield i;
+  }
+  assertEq(makeFn("...arg")(f, gen()), 3);
 }
 
 checkLength(function(x) arguments.length, makeCall);
 checkLength(function(x) arguments.length, makeFunCall);
 checkLength((x) => arguments.length, makeCall);
 checkLength((x) => arguments.length, makeFunCall);
 function lengthClass(x) {
   this.length = arguments.length;
--- a/js/src/jit-test/tests/basic/spread-call-maxarg.js
+++ b/js/src/jit-test/tests/basic/spread-call-maxarg.js
@@ -1,23 +1,30 @@
-let a = [];
-a.length = getMaxArgs() + 1;
 
-let f = function() {
-};
+var config = getBuildConfiguration();
 
-try {
-  f(...a);
-} catch (e) {
-  assertEq(e.message, "too many function arguments");
-}
+// FIXME: ASAN debug builds run this too slowly for now.  Re-enable
+// after bug 919948 lands.
+if (!(config.debug && config.asan)) {
+    let a = [];
+    a.length = getMaxArgs() + 1;
+
+    let f = function() {
+    };
 
-try {
-  new f(...a);
-} catch (e) {
-  assertEq(e.message, "too many constructor arguments");
-}
+    try {
+        f(...a);
+    } catch (e) {
+        assertEq(e.message, "too many function arguments");
+    }
 
-try {
-  eval(...a);
-} catch (e) {
-  assertEq(e.message, "too many function arguments");
+    try {
+        new f(...a);
+    } catch (e) {
+        assertEq(e.message, "too many constructor arguments");
+    }
+
+    try {
+        eval(...a);
+    } catch (e) {
+        assertEq(e.message, "too many function arguments");
+    }
 }
--- a/js/src/jit-test/tests/basic/spread-call-not-iterable.js
+++ b/js/src/jit-test/tests/basic/spread-call-not-iterable.js
@@ -1,16 +1,28 @@
 load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 assertThrowsInstanceOf(() => Math.sin(...true), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...false), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...new Date()), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...Function("")), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...function () {}), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...(x => x)), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...1), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...{}), TypeError);
-assertThrowsInstanceOf(() => Math.sin(...{ iterator: 10 }), TypeError);
-assertThrowsInstanceOf(() => Math.sin(...{ iterator: function() undefined }), TypeError);
-assertThrowsInstanceOf(() => Math.sin(...{ iterator: function() this }), TypeError);
-assertThrowsValue(() => Math.sin(...{ iterator: function() this, next: function() { throw 10; } }), 10);
+var foo = {}
+
+foo[std_iterator] = 10;
+assertThrowsInstanceOf(() => Math.sin(...foo), TypeError);
+
+foo[std_iterator] = function() undefined;
+assertThrowsInstanceOf(() => Math.sin(...foo), TypeError);
+
+foo[std_iterator] = function() this;
+assertThrowsInstanceOf(() => Math.sin(...foo), TypeError);
+
+foo[std_iterator] = function() this;
+foo.next = function() { throw 10; };
+assertThrowsValue(() => Math.sin(...foo), 10);
+
 assertThrowsInstanceOf(() => Math.sin(.../a/), TypeError);
 assertThrowsInstanceOf(() => Math.sin(...new Error()), TypeError);
--- a/js/src/jit-test/tests/basic/spread-call.js
+++ b/js/src/jit-test/tests/basic/spread-call.js
@@ -1,10 +1,11 @@
 load(libdir + "asserts.js");
 load(libdir + "eqArrayHelper.js");
+load(libdir + "iteration.js");
 
 let makeCall    = farg => Function("f", "arg", "return f(" + farg + ");");
 let makeFunCall = farg => Function("f", "arg", "return f.call(null, " + farg + ");");
 let makeNew     = farg => Function("f", "arg", "return new f(" + farg + ").value;");
 
 function checkCommon(f, makeFn) {
   assertEqArray(makeFn("...[1, 2, 3]")(f), [1, 2, 3]);
   assertEqArray(makeFn("1, ...[2], 3")(f), [1, 2, 3]);
@@ -13,37 +14,34 @@ function checkCommon(f, makeFn) {
   assertEqArray(makeFn("1, ...[], 2, 3")(f), [1, 2, 3]);
 
   // other iterable objects
   assertEqArray(makeFn("...arg")(f, Int32Array([1, 2, 3])), [1, 2, 3]);
   assertEqArray(makeFn("...arg")(f, "abc"), ["a", "b", "c"]);
   assertEqArray(makeFn("...arg")(f, [1, 2, 3].iterator()), [1, 2, 3]);
   assertEqArray(makeFn("...arg")(f, Set([1, 2, 3])), [1, 2, 3]);
   assertEqArray(makeFn("...arg")(f, Map([["a", "A"], ["b", "B"], ["c", "C"]])).map(([k, v]) => k + v), ["aA", "bB", "cC"]);
-  let itr = {
-    iterator: function() {
+  let itr = {};
+  itr[std_iterator] = function() {
       return {
-        i: 1,
-        next: function() {
-          if (this.i < 4)
-            return this.i++;
-          else
-            throw StopIteration;
-        }
+          i: 1,
+          next: function() {
+              if (this.i < 4)
+                  return { value: this.i++, done: false };
+              else
+                  return { value: undefined, done: true };
+          }
       };
-    }
   };
   assertEqArray(makeFn("...arg")(f, itr), [1, 2, 3]);
-  let gen = {
-    iterator: function() {
+  function gen() {
       for (let i = 1; i < 4; i ++)
-        yield i;
-    }
-  };
-  assertEqArray(makeFn("...arg")(f, gen), [1, 2, 3]);
+          yield i;
+  }
+  assertEqArray(makeFn("...arg")(f, gen()), [1, 2, 3]);
 
   assertEqArray(makeFn("...arg=[1, 2, 3]")(f), [1, 2, 3]);
 
   // According to the draft spec, null and undefined are to be treated as empty
   // arrays. However, they are not iterable. If the spec is not changed to be in
   // terms of iterables, these tests should be fixed.
   //assertEqArray(makeFn(1, ...null, 2, 3)(f), [1, 2, 3]);
   //assertEqArray(makeFn(1, ...undefined, 2, 3)(f), [1, 2, 3]);
--- a/js/src/jit-test/tests/collections/Map-clear-iterators-1.js
+++ b/js/src/jit-test/tests/collections/Map-clear-iterators-1.js
@@ -1,22 +1,22 @@
 // A Map iterator does not visit entries removed by clear().
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var m = Map();
-var it = m.iterator();
+var it = m[std_iterator]();
 m.clear();
-assertThrowsValue(it.next.bind(it), StopIteration);
+assertIteratorResult(it.next(), undefined, true);
 
 m = Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
-it = m.iterator();
-assertEq(it.next()[0], "a");
+it = m[std_iterator]();
+assertIteratorResult(it.next(), ["a", 1], false);
 m.clear();
-assertThrowsValue(it.next.bind(it), StopIteration);
+assertIteratorResult(it.next(), undefined, true);
 
 var log = "";
 m = Map([["a", 1], ["b", 2], ["c", 3], ["d", 4]]);
 for (var [k, v] of m) {
     log += k + v;
     if (k == "b")
         m.clear();
 }
--- a/js/src/jit-test/tests/collections/Map-clear-iterators-2.js
+++ b/js/src/jit-test/tests/collections/Map-clear-iterators-2.js
@@ -1,15 +1,12 @@
 // A Map iterator continues to visit entries added after a clear().
 
 load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var m = Map([["a", 1]]);
-var it = m.iterator();
-assertEq(it.next()[0], "a");
+var it = m[std_iterator]();
+assertIteratorResult(it.next(), ["a", 1], false);
 m.clear();
 m.set("b", 2);
-var pair = it.next()
-assertEq(pair[0], "b");
-assertEq(pair[1], 2);
-assertThrowsValue(it.next.bind(it), StopIteration);
-
-
+assertIteratorResult(it.next(), ["b", 2], false);
+assertIteratorResult(it.next(), undefined, true);
--- a/js/src/jit-test/tests/collections/Map-forEach.js
+++ b/js/src/jit-test/tests/collections/Map-forEach.js
@@ -1,32 +1,33 @@
 /* test Map.prototype.forEach */
 
 load(libdir + 'asserts.js');
+load(libdir + 'iteration.js');
 
 // testing success conditions of Map.prototype.forEach
 
 var testMap = new Map();
 
 function callback(value, key, map) {
     testMap.set(key, value);
     assertEq(map.has(key), true);
     assertEq(map.get(key), value);
 }
 
 var initialMap = new Map([['a', 1], ['b', 2.3], [false, undefined]]);
 initialMap.forEach(callback);
 
 // test that both the Maps are equal and are in same order
-var iterator = initialMap.iterator();
+var iterator = initialMap[std_iterator]();
 var count = 0;
 for (var [k, v] of testMap) {
     assertEq(initialMap.has(k), true);
     assertEq(initialMap.get(k), testMap.get(k));
-    assertEq(iterator.next()[1], testMap.get(k));
+    assertIteratorResult(iterator.next(), [k, testMap.get(k)], false);
     count++;
 }
 
 //check both the Maps we have are equal in size
 assertEq(initialMap.size, testMap.size);
 assertEq(initialMap.size, count);
 
 var x = { abc: 'test'};
@@ -47,12 +48,12 @@ var fn = 2;
 assertThrowsInstanceOf(function() {
     initialMap.forEach(fn);
 }, TypeError, "Map.prototype.forEach should raise TypeError if callback is not a function");
 
 // testing that Map#forEach uses internal next() function and does not stop when
 // StopIteration exception is thrown
 
 var m = new Map([["one", 1]]);
-Object.getPrototypeOf(m.iterator()).next = function () { throw "FAIL"; };
+Object.getPrototypeOf(m[std_iterator]()).next = function () { throw "FAIL"; };
 assertThrowsInstanceOf(function () {
   m.forEach(function () { throw StopIteration; });
 }, StopIteration, "Map.prototype.forEach should use intrinsic next method.");
--- a/js/src/jit-test/tests/collections/Map-iterator-add-2.js
+++ b/js/src/jit-test/tests/collections/Map-iterator-add-2.js
@@ -1,11 +1,10 @@
 // A Map iterator does not iterate over new entries added after it throws StopIteration.
 
-load(libdir + "asserts.js");
-load(libdir + "eqArrayHelper.js");
+load(libdir + "iteration.js");
 
 var map = Map();
-var iter0 = map.iterator(), iter1 = map.iterator();
-assertThrowsValue(function () { iter0.next(); }, StopIteration);  // closes iter0
+var iter0 = map[std_iterator](), iter1 = map[std_iterator]();
+assertIteratorResult(iter0.next(), undefined, true);  // closes iter0
 map.set(1, 2);
-assertThrowsValue(function () { iter0.next(); }, StopIteration);  // already closed
-assertEqArray(iter1.next(), [1, 2]);  // was not yet closed
+assertIteratorResult(iter0.next(), undefined, true);  // already closed
+assertIteratorResult(iter1.next(), [1, 2], false);  // was not yet closed
--- a/js/src/jit-test/tests/collections/Map-iterator-pairs-1.js
+++ b/js/src/jit-test/tests/collections/Map-iterator-pairs-1.js
@@ -1,13 +1,15 @@
 // mapiter.next() returns an actual array.
 
+load(libdir + "iteration.js");
+
 var key = {};
 var map = Map([[key, 'value']]);
-var entry = map.iterator().next();
+var entry = map[std_iterator]().next().value;
 assertEq(Array.isArray(entry), true);
 assertEq(Object.getPrototypeOf(entry), Array.prototype);
 assertEq(Object.isExtensible(entry), true);
 
 assertEq(entry.length, 2);
 var names = Object.getOwnPropertyNames(entry).sort();
 assertEq(names.length, 3);
 assertEq(names.join(","), "0,1,length");
--- a/js/src/jit-test/tests/collections/Map-iterator-pairs-2.js
+++ b/js/src/jit-test/tests/collections/Map-iterator-pairs-2.js
@@ -1,10 +1,13 @@
 // mapiter.next() returns a fresh array each time.
 
+load(libdir + "iteration.js");
+
 var map = Map([['a', 1], ['b', 2]]);
-var iter = map.iterator();
+var iter = map[std_iterator]();
 var a = iter.next(), b = iter.next();
-assertEq(a !== b, true);
-assertEq(a[0], 'a');
-assertEq(b[0], 'b');
-var a1 = map.iterator().next();
-assertEq(a !== a1, true);
+assertIteratorResult(a, ['a', 1], false);
+assertIteratorResult(b, ['b', 2], false);
+assertEq(a.value !== b.value, true);
+var a1 = map[std_iterator]().next();
+assertIteratorResult(a1, ['a', 1], false);
+assertEq(a.value !== a1.value, true);
--- a/js/src/jit-test/tests/collections/Map-iterator-pairs-3.js
+++ b/js/src/jit-test/tests/collections/Map-iterator-pairs-3.js
@@ -1,12 +1,13 @@
 // Modifying an array returned by mapiter.next() does not modify the Map.
 
+load(libdir + "iteration.js");
+
 var map = Map([['a', 1]]);
-var pair = map.iterator().next();
-assertEq(pair[0], 'a');
-pair[0] = 'b';
-pair[1] = 2;
-assertEq(pair[0], 'b');
-assertEq(pair[1], 2);
+var res = map[std_iterator]().next();
+assertIteratorResult(res, ['a', 1], false);
+res.value[0] = 'b';
+res.value[1] = 2;
+assertIteratorResult(res, ['b', 2], false);
 assertEq(map.get('a'), 1);
 assertEq(map.has('b'), false);
 assertEq(map.size, 1);
--- a/js/src/jit-test/tests/collections/Map-iterator-proxies-2.js
+++ b/js/src/jit-test/tests/collections/Map-iterator-proxies-2.js
@@ -1,19 +1,21 @@
 // map.iterator() and iter.next() are non-generic but work on cross-compartment wrappers.
 
 load(libdir + "asserts.js");
 load(libdir + "eqArrayHelper.js");
+load(libdir + "iteration.js");
+
 var g = newGlobal();
 
-var iterator_fn = Map.prototype.iterator;
+var iterator_fn = Map.prototype[std_iterator];
 assertThrowsInstanceOf(function () { iterator_fn.call({}); }, TypeError);
 assertThrowsInstanceOf(function () { iterator_fn.call(Set()); }, TypeError);
 var mapw = g.eval("Map([['x', 1], ['y', 2]])");
-assertEqArray(iterator_fn.call(mapw).next(), ["x", 1]);
+assertEqArray(iterator_fn.call(mapw).next().value, ["x", 1]);
 
-var next_fn = Map().iterator().next;
+var next_fn = Map()[std_iterator]().next;
 assertThrowsInstanceOf(function () { next_fn.call({}); }, TypeError);
-assertThrowsInstanceOf(function () { next_fn.call(Set().iterator()); }, TypeError);
-var iterw = mapw.iterator();
-assertEqArray(next_fn.call(iterw), ["x", 1]);
-assertEqArray(next_fn.call(iterw), ["y", 2]);
-assertThrowsValue(function () { next_fn.call(iterw); }, g.StopIteration);
+assertThrowsInstanceOf(function () { next_fn.call(Set()[std_iterator]()); }, TypeError);
+var iterw = mapw[std_iterator]();
+assertEqArray(next_fn.call(iterw).value, ["x", 1]);
+assertEqArray(next_fn.call(iterw).value, ["y", 2]);
+assertEq(next_fn.call(iterw).done, true);
--- a/js/src/jit-test/tests/collections/Map-iterator-remove-2.js
+++ b/js/src/jit-test/tests/collections/Map-iterator-remove-2.js
@@ -1,11 +1,13 @@
 // A map iterator can cope with removing the next entry.
 
+load(libdir + "iteration.js");
+
 var map = Map([['a', 0], ['b', 1], ['c', 2], ['d', 3]]);
-var iter = map.iterator();
+var iter = map[std_iterator]();
 var log = '';
 for (let [k, v] of iter) {
     log += k + v;
     if (k === 'b')
         map.delete('c');
 }
 assertEq(log, 'a0b1d3');
--- a/js/src/jit-test/tests/collections/Map-iterator-remove-3.js
+++ b/js/src/jit-test/tests/collections/Map-iterator-remove-3.js
@@ -1,12 +1,13 @@
 // A map iterator can cope with removing the next entry, then the current entry.
 
 load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var map = Map([['a', 0], ['b', 1], ['c', 2], ['d', 3]]);
-var iter = map.iterator();
-assertEq(iter.next()[0], 'a');
-assertEq(iter.next()[0], 'b');
+var iter = map[std_iterator]();
+assertIteratorResult(iter.next(), ['a', 0], false);
+assertIteratorResult(iter.next(), ['b', 1], false);
 map.delete('c');
 map.delete('b');
-assertEq(iter.next()[0], 'd');
-assertThrowsValue(function () { iter.next(); }, StopIteration);
+assertIteratorResult(iter.next(), ['d', 3], false);
+assertIteratorResult(iter.next(), undefined, true);
--- a/js/src/jit-test/tests/collections/Map-iterator-remove-4.js
+++ b/js/src/jit-test/tests/collections/Map-iterator-remove-4.js
@@ -1,32 +1,31 @@
 // Multiple live iterators on the same Map can cope with removing entries.
 
-load(libdir + "eqArrayHelper.js");
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 // Make a map.
 var map = Map();
 var SIZE = 7;
 for (var j = 0; j < SIZE; j++)
     map.set(j, j);
 
 // Make lots of iterators pointing to entry 2 of the map.
 var NITERS = 5;
 var iters = [];
 for (var i = 0; i < NITERS; i++) {
-    var iter = map.iterator();
-    assertEqArray(iter.next(), [0, 0]);
-    assertEqArray(iter.next(), [1, 1]);
+    var iter = map[std_iterator]();
+    assertIteratorResult(iter.next(), [0, 0], false);
+    assertIteratorResult(iter.next(), [1, 1], false);
     iters[i] = iter;
 }
 
 // Remove half of the map entries.
 for (var j = 0; j < SIZE; j += 2)
     map.delete(j);
 
 // Make sure all the iterators still work.
 for (var i = 0; i < NITERS; i++) {
     var iter = iters[i];
     for (var j = 3; j < SIZE; j += 2)
-        assertEqArray(iter.next(), [j, j]);
-    assertThrowsValue(function () { iter.next(); }, StopIteration);
+        assertIteratorResult(iter.next(), [j, j], false);
+    assertIteratorResult(iter.next(), undefined, true);
 }
--- a/js/src/jit-test/tests/collections/Map-iterator-remove-6.js
+++ b/js/src/jit-test/tests/collections/Map-iterator-remove-6.js
@@ -1,20 +1,21 @@
 // Removing many Map entries does not cause a live iterator to skip any of the
 // entries that were not removed. (Compacting a Map must not be observable to
 // script.)
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var map = Map();
 for (var i = 0; i < 32; i++)
     map.set(i, i);
-var iter = map.iterator();
-assertEq(iter.next()[0], 0);
+var iter = map[std_iterator]();
+assertIteratorResult(iter.next(), [0, 0], false);
 for (var i = 0; i < 30; i++)
     map.delete(i);
 assertEq(map.size, 2);
 for (var i = 32; i < 100; i++)
     map.set(i, i);  // eventually triggers compaction
 
 for (var i = 30; i < 100; i++)
-    assertEq(iter.next()[0], i);
-assertThrowsValue(function () { iter.next(); }, StopIteration);
+    assertIteratorResult(iter.next(), [i, i], false);
+
+assertIteratorResult(iter.next(), undefined, true);
--- a/js/src/jit-test/tests/collections/Map-iterators-3.js
+++ b/js/src/jit-test/tests/collections/Map-iterators-3.js
@@ -1,10 +1,10 @@
 // A closed Map iterator does not visit new entries added after a clear().
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var m = Map();
-var it = m.iterator();
-assertThrowsValue(it.next.bind(it), StopIteration);  // close the iterator
+var it = m[std_iterator]();
+assertIteratorResult(it.next(), undefined, true);  // close the iterator
 m.clear();
 m.set("a", 1);
-assertThrowsValue(it.next.bind(it), StopIteration);
+assertIteratorResult(it.next(), undefined, true);  // iterator still closed
--- a/js/src/jit-test/tests/collections/Map-surfaces-1.js
+++ b/js/src/jit-test/tests/collections/Map-surfaces-1.js
@@ -1,10 +1,12 @@
 // Map surfaces
 
+load(libdir + "iteration.js");
+
 var desc = Object.getOwnPropertyDescriptor(this, "Map");
 assertEq(desc.enumerable, false);
 assertEq(desc.configurable, true);
 assertEq(desc.writable, true);
 
 assertEq(typeof Map, 'function');
 assertEq(Object.keys(Map).length, 0);
 assertEq(Map.length, 1);
@@ -38,10 +40,10 @@ checkMethod("entries", 0);
 var desc = Object.getOwnPropertyDescriptor(Map.prototype, "size");
 assertEq(desc.enumerable, false);
 assertEq(desc.configurable, true);
 assertEq(typeof desc.get, 'function');
 assertEq(desc.get.length, 0);
 assertEq(desc.set, undefined);
 checkMethod("clear", 0);
 
-// Map.prototype.iterator and .entries are the same function object.
-assertEq(Map.prototype.iterator, Map.prototype.entries);
+// Map.prototype[@@iterator] and .entries are the same function object.
+assertEq(Map.prototype[std_iterator], Map.prototype.entries);
--- a/js/src/jit-test/tests/collections/Map-values-2.js
+++ b/js/src/jit-test/tests/collections/Map-values-2.js
@@ -1,18 +1,18 @@
 // map.keys() and map.values() return iterators over the key or the value,
 // respectively, of each key-value pair in the map.
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var data = [["one", 1], ["two", 2], ["three", 3], ["four", 4]];
 var m = Map(data);
 
 var ki = m.keys();
-assertEq(ki.next(), "one");
-assertEq(ki.next(), "two");
-assertEq(ki.next(), "three");
-assertEq(ki.next(), "four");
-assertThrowsValue(function () { ki.next(); }, StopIteration);
+assertIteratorResult(ki.next(), "one", false);
+assertIteratorResult(ki.next(), "two", false);
+assertIteratorResult(ki.next(), "three", false);
+assertIteratorResult(ki.next(), "four", false);
+assertIteratorResult(ki.next(), undefined, true);
 
 assertEq([k for (k of m.keys())].toSource(), ["one", "two", "three", "four"].toSource());
 assertEq([k for (k of m.values())].toSource(), [1, 2, 3, 4].toSource());
 assertEq([k for (k of m.entries())].toSource(), data.toSource());
--- a/js/src/jit-test/tests/collections/Set-clear-iterators-1.js
+++ b/js/src/jit-test/tests/collections/Set-clear-iterators-1.js
@@ -1,22 +1,22 @@
 // A Set iterator does not visit entries removed by clear().
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var s = Set();
-var it = s.iterator();
+var it = s[std_iterator]();
 s.clear();
-assertThrowsValue(it.next.bind(it), StopIteration);
+assertIteratorResult(it.next(), undefined, true);
 
 s = Set(["a", "b", "c", "d"]);
-it = s.iterator();
-assertEq(it.next()[0], "a");
+it = s[std_iterator]();
+assertIteratorResult(it.next(), "a", false);
 s.clear();
-assertThrowsValue(it.next.bind(it), StopIteration);
+assertIteratorResult(it.next(), undefined, true);
 
 var log = "";
 s = Set(["a", "b", "c", "d"]);
 for (var v of s) {
     log += v;
     if (v == "b")
         s.clear();
 }
--- a/js/src/jit-test/tests/collections/Set-clear-iterators-2.js
+++ b/js/src/jit-test/tests/collections/Set-clear-iterators-2.js
@@ -1,11 +1,11 @@
 // A Set iterator continues to visit entries added after a clear().
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var s = Set(["a"]);
-var it = s.iterator();
-assertEq(it.next(), "a");
+var it = s[std_iterator]();
+assertIteratorResult(it.next(), "a", false);
 s.clear();
 s.add("b");
-assertEq(it.next(), "b");
-assertThrowsValue(it.next.bind(it), StopIteration);
+assertIteratorResult(it.next(), "b", false);
+assertIteratorResult(it.next(), undefined, true);
--- a/js/src/jit-test/tests/collections/Set-clear-iterators-3.js
+++ b/js/src/jit-test/tests/collections/Set-clear-iterators-3.js
@@ -1,10 +1,10 @@
 // A closed Set iterator does not visit new entries added after a clear().
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var s = Set();
-var it = s.iterator();
-assertThrowsValue(it.next.bind(it), StopIteration);  // close the iterator
+var it = s[std_iterator]();
+assertIteratorResult(it.next(), undefined, true);  // close the iterator
 s.clear();
 s.add("a");
-assertThrowsValue(it.next.bind(it), StopIteration);
+assertIteratorResult(it.next(), undefined, true);
--- a/js/src/jit-test/tests/collections/Set-forEach.js
+++ b/js/src/jit-test/tests/collections/Set-forEach.js
@@ -1,31 +1,32 @@
 /* test Set.prototype.forEach */
 
 load(libdir + 'asserts.js');
+load(libdir + 'iteration.js');
 
 // testing success conditions of Set.prototype.forEach
 
 var testSet = new Set();
 
 function callback(value, key, set) {
     assertEq(value, key);
     testSet.add(value);
     assertEq(set.has(key), true);
 }
 
 var initialSet = new Set(['a', 1, undefined]);
 initialSet.forEach(callback);
 
 // test that both the Sets are equal and are in same order
-var iterator = initialSet.iterator();
+var iterator = initialSet[std_iterator]();
 var count = 0;
 for (var v of testSet) {
     assertEq(initialSet.has(v), true);
-    assertEq(iterator.next(), v);
+    assertIteratorResult(iterator.next(), v, false);
     count++;
 }
 
 //check both the Sets we have are equal in size
 assertEq(initialSet.size, testSet.size);
 assertEq(initialSet.size, count);
 
 var x = { abc: 'test'};
@@ -41,17 +42,8 @@ var m = new Map([['a', 1], ['b', 2.3], [
 assertThrowsInstanceOf(function() {
     Set.prototype.forEach.call(m, callback);
 }, TypeError, "Set.prototype.forEach should raise TypeError if not a used on a Set");
 
 var fn = 2;
 assertThrowsInstanceOf(function() {
     initialSet.forEach(fn);
 }, TypeError, "Set.prototype.forEach should raise TypeError if callback is not a function");
-
-// testing that Set#forEach uses internal next() function and does not stop when
-// StopIteration exception is thrown
-
-var s = new Set(["one", 1]);
-Object.getPrototypeOf(s.iterator()).next = function () { throw "FAIL"; };
-assertThrowsInstanceOf(function () {
-  s.forEach(function () { throw StopIteration; });
-}, StopIteration, "Set.prototype.forEach should use intrinsic next method.");
--- a/js/src/jit-test/tests/collections/Set-iterator-add-2.js
+++ b/js/src/jit-test/tests/collections/Set-iterator-add-2.js
@@ -1,10 +1,10 @@
 // A Set iterator does not iterate over new entries added after it throws StopIteration.
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var set = Set();
-var iter0 = set.iterator(), iter1 = set.iterator();
-assertThrowsValue(function () { iter0.next(); }, StopIteration);  // closes iter0
+var iter0 = set[std_iterator](), iter1 = set[std_iterator]();
+assertIteratorResult(iter0.next(), undefined, true);  // closes iter0
 set.add("x");
-assertThrowsValue(function () { iter0.next(); }, StopIteration);  // already closed
-assertEq(iter1.next(), "x");  // was not yet closed
+assertIteratorResult(iter0.next(), undefined, true);  // already closed
+assertIteratorResult(iter1.next(), "x", false);  // was not yet closed
--- a/js/src/jit-test/tests/collections/Set-iterator-gc-1.js
+++ b/js/src/jit-test/tests/collections/Set-iterator-gc-1.js
@@ -1,8 +1,10 @@
 // A Set iterator keeps the data alive.
 
 load(libdir + "referencesVia.js");
+load(libdir + "iteration.js");
+
 var key = {};
 var set = Set([key]);
-var iter = set.iterator();
+var iter = set[std_iterator]();
 referencesVia(iter, "**UNKNOWN SLOT 0**", set);
 referencesVia(set, "key", key);
--- a/js/src/jit-test/tests/collections/Set-iterator-proxies-2.js
+++ b/js/src/jit-test/tests/collections/Set-iterator-proxies-2.js
@@ -1,18 +1,20 @@
 // map.iterator() and iter.next() are non-generic but work on cross-compartment wrappers.
 
 load(libdir + "asserts.js");
+load(libdir + "iteration.js");
+
 var g = newGlobal();
 
-var iterator_fn = Set.prototype.iterator;
+var iterator_fn = Set.prototype[std_iterator];
 assertThrowsInstanceOf(function () { iterator_fn.call({}); }, TypeError);
 assertThrowsInstanceOf(function () { iterator_fn.call(Map()); }, TypeError);
 var setw = g.eval("Set(['x', 'y'])");
-assertEq(iterator_fn.call(setw).next(), "x");
+assertIteratorResult(iterator_fn.call(setw).next(), "x", false);
 
-var next_fn = Set().iterator().next;
+var next_fn = Set()[std_iterator]().next;
 assertThrowsInstanceOf(function () { next_fn.call({}); }, TypeError);
-assertThrowsInstanceOf(function () { next_fn.call(Map().iterator()); }, TypeError);
-var iterw = setw.iterator();
-assertEq(next_fn.call(iterw), "x");
-assertEq(next_fn.call(iterw), "y");
-assertThrowsValue(function () { next_fn.call(iterw); }, g.StopIteration);
+assertThrowsInstanceOf(function () { next_fn.call(Map()[std_iterator]()); }, TypeError);
+var iterw = setw[std_iterator]();
+assertIteratorResult(next_fn.call(iterw), "x", false);
+assertIteratorResult(next_fn.call(iterw), "y", false);
+assertIteratorResult(next_fn.call(iterw), undefined, true);
--- a/js/src/jit-test/tests/collections/Set-iterator-remove-2.js
+++ b/js/src/jit-test/tests/collections/Set-iterator-remove-2.js
@@ -1,11 +1,13 @@
 // A map iterator can cope with removing the next entry.
 
+load(libdir + "iteration.js");
+
 var set = Set("abcd");
-var iter = set.iterator();
+var iter = set[std_iterator]();
 var log = "";
 for (let x of iter) {
     log += x;
     if (x === "b")
         set.delete("c");
 }
 assertEq(log, "abd");
--- a/js/src/jit-test/tests/collections/Set-iterator-remove-3.js
+++ b/js/src/jit-test/tests/collections/Set-iterator-remove-3.js
@@ -1,12 +1,12 @@
 // A set iterator can cope with removing the next entry, then the current entry.
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var set = Set("abcd");
-var iter = set.iterator();
-assertEq(iter.next(), "a");
-assertEq(iter.next(), "b");
+var iter = set[std_iterator]();
+assertIteratorResult(iter.next(), "a", false);
+assertIteratorResult(iter.next(), "b", false);
 set.delete("c");
 set.delete("b");
-assertEq(iter.next(), "d");
-assertThrowsValue(function () { iter.next(); }, StopIteration);
+assertIteratorResult(iter.next(), "d", false);
+assertIteratorResult(iter.next(), undefined, true);
--- a/js/src/jit-test/tests/collections/Set-iterator-remove-4.js
+++ b/js/src/jit-test/tests/collections/Set-iterator-remove-4.js
@@ -1,31 +1,31 @@
 // Multiple live iterators on the same Set can cope with removing entries.
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 // Make a set.
 var set = Set();
 var SIZE = 7;
 for (var j = 0; j < SIZE; j++)
     set.add(j);
 
 // Make lots of iterators pointing to entry 2 of the set.
 var NITERS = 5;
 var iters = [];
 for (var i = 0; i < NITERS; i++) {
-    var iter = set.iterator();
-    assertEq(iter.next(), 0);
-    assertEq(iter.next(), 1);
+    var iter = set[std_iterator]();
+    assertIteratorResult(iter.next(), 0, false);
+    assertIteratorResult(iter.next(), 1, false);
     iters[i] = iter;
 }
 
 // Remove half of the set entries.
 for (var j = 0; j < SIZE; j += 2)
     set.delete(j);
 
 // Make sure all the iterators still work.
 for (var i = 0; i < NITERS; i++) {
     var iter = iters[i];
     for (var j = 3; j < SIZE; j += 2)
-        assertEq(iter.next(), j);
-    assertThrowsValue(function () { iter.next(); }, StopIteration);
+        assertIteratorResult(iter.next(), j, false);
+    assertIteratorResult(iter.next(), undefined, true);
 }
--- a/js/src/jit-test/tests/collections/Set-iterator-remove-6.js
+++ b/js/src/jit-test/tests/collections/Set-iterator-remove-6.js
@@ -1,20 +1,20 @@
 // Removing many Set entries does not cause a live iterator to skip any of the
 // entries that were not removed. (Compacting a Set must not be observable to
 // script.)
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var set = Set();
 for (var i = 0; i < 32; i++)
     set.add(i);
-var iter = set.iterator();
-assertEq(iter.next(), 0);
+var iter = set[std_iterator]();
+assertIteratorResult(iter.next(), 0, false);
 for (var i = 0; i < 30; i++)
     set.delete(i);
 assertEq(set.size, 2);
 for (var i = 32; i < 100; i++)
     set.add(i);  // eventually triggers compaction
 
 for (var i = 30; i < 100; i++)
-    assertEq(iter.next(), i);
-assertThrowsValue(function () { iter.next(); }, StopIteration);
+    assertIteratorResult(iter.next(), i, false);
+assertIteratorResult(iter.next(), undefined, true);
--- a/js/src/jit-test/tests/collections/Set-surfaces-1.js
+++ b/js/src/jit-test/tests/collections/Set-surfaces-1.js
@@ -1,10 +1,12 @@
 // Set surfaces
 
+load(libdir + "iteration.js");
+
 var desc = Object.getOwnPropertyDescriptor(this, "Set");
 assertEq(desc.enumerable, false);
 assertEq(desc.configurable, true);
 assertEq(desc.writable, true);
 
 assertEq(typeof Set, 'function');
 assertEq(Object.keys(Set).length, 0);
 assertEq(Set.length, 1);
@@ -38,9 +40,9 @@ assertEq(desc.enumerable, false);
 assertEq(desc.configurable, true);
 assertEq(typeof desc.get, 'function');
 assertEq(desc.get.length, 0);
 assertEq(desc.set, undefined);
 checkMethod("clear", 0);
 
 // Set.prototype.keys, .values, and .iterator are the same function object
 assertEq(Set.prototype.keys, Set.prototype.values);
-assertEq(Set.prototype.iterator, Set.prototype.values);
+assertEq(Set.prototype[std_iterator], Set.prototype.values);
--- a/js/src/jit-test/tests/collections/Set-values-2.js
+++ b/js/src/jit-test/tests/collections/Set-values-2.js
@@ -1,18 +1,18 @@
 // set.keys() and set.values() return iterators over the elements
 // and set.entries() returns an iterator that yields pairs [e, e].
 
-load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var data = [1, 2, 3, 4];
 var s = Set(data);
 
 var ki = s.keys();
-assertEq(ki.next(), 1);
-assertEq(ki.next(), 2);
-assertEq(ki.next(), 3);
-assertEq(ki.next(), 4);
-assertThrowsValue(function () { ki.next(); }, StopIteration);
+assertIteratorResult(ki.next(), 1, false);
+assertIteratorResult(ki.next(), 2, false);
+assertIteratorResult(ki.next(), 3, false);
+assertIteratorResult(ki.next(), 4, false);
+assertIteratorResult(ki.next(), undefined, true);
 
 assertEq([...s.keys()].toSource(), data.toSource());
 assertEq([...s.values()].toSource(), data.toSource());
 assertEq([...s.entries()].toSource(), [[1, 1], [2, 2], [3, 3], [4, 4]].toSource());
--- a/js/src/jit-test/tests/collections/iterator-1.js
+++ b/js/src/jit-test/tests/collections/iterator-1.js
@@ -1,12 +1,16 @@
 // collection.iterator() returns an Iterator object.
 
+load(libdir + "iteration.js");
+
 function test(obj, name) {
-    var iter = obj.iterator();
+    var iter = obj[std_iterator]();
     assertEq(typeof iter, "object");
     assertEq(iter instanceof Iterator, true);
     assertEq(iter.toString(), "[object " + obj.constructor.name + " Iterator]");
 }
 
-test([]);
+// FIXME: Until arrays are converted to use the new iteration protocol,
+// toString on this iterator doesn't work.  Bug 919948.
+// test([]);
 test(new Map);
 test(new Set);
--- a/js/src/jit-test/tests/collections/iterator-gc.js
+++ b/js/src/jit-test/tests/collections/iterator-gc.js
@@ -1,14 +1,16 @@
 // An iterator keeps its data alive.
 
+load(libdir + "iteration.js");
+
 load(libdir + "referencesVia.js");
 var key = {};
 
 function test(obj, edgeName) {
-    var iter = obj.iterator();
+    var iter = obj[std_iterator]();
     referencesVia(iter, "**UNKNOWN SLOT 0**", obj);
     referencesVia(obj, edgeName, key);
 }
 
 test([key],                 "element[0]");
 test(Map([[key, 'value']]), "key");
 test(Set([key]),            "key");
--- a/js/src/jit-test/tests/collections/iterator-proto-1.js
+++ b/js/src/jit-test/tests/collections/iterator-proto-1.js
@@ -1,13 +1,15 @@
 // All iterators of the same collection type share their immediate prototype.
 // Those prototype objects in turn inherit directly from Iterator.prototype.
 
+load(libdir + "iteration.js");
+
 function test(obj0, obj1) {
-    var iter0 = obj0.iterator(), iter1 = obj1.iterator();
+    var iter0 = obj0[std_iterator](), iter1 = obj1[std_iterator]();
     var proto = Object.getPrototypeOf(iter0);
     assertEq(Object.getPrototypeOf(iter1), proto);
     assertEq(Object.getPrototypeOf(proto), Iterator.prototype);
 }
 
 test([], [1]);
 test(Map(), Map([[1, 1]]));
 test(Set(), Set([1]));
--- a/js/src/jit-test/tests/collections/iterator-proto-2.js
+++ b/js/src/jit-test/tests/collections/iterator-proto-2.js
@@ -1,11 +1,13 @@
 // Iterators of different collection types have different prototypes.
 
-var aproto = Object.getPrototypeOf(Array().iterator());
-var mproto = Object.getPrototypeOf(Map().iterator());
-var sproto = Object.getPrototypeOf(Set().iterator());
+load(libdir + "iteration.js");
+
+var aproto = Object.getPrototypeOf(Array()[std_iterator]());
+var mproto = Object.getPrototypeOf(Map()[std_iterator]());
+var sproto = Object.getPrototypeOf(Set()[std_iterator]());
 assertEq(aproto !== mproto, true);
 assertEq(aproto !== sproto, true);
 assertEq(mproto !== sproto, true);
 assertEq(aproto.next !== mproto.next, true);
 assertEq(aproto.next !== sproto.next, true);
 assertEq(mproto.next !== sproto.next, true);
--- a/js/src/jit-test/tests/collections/iterator-proto-surfaces.js
+++ b/js/src/jit-test/tests/collections/iterator-proto-surfaces.js
@@ -1,22 +1,23 @@
 // Iterator prototype surfaces.
 
 load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 function test(constructor) {
-    var proto = Object.getPrototypeOf(constructor().iterator());
+    var proto = Object.getPrototypeOf(constructor()[std_iterator]());
     var names = Object.getOwnPropertyNames(proto);
-    assertEq(names.length, 1);
-    assertEq(names[0], 'next');
+    names.sort();
+    assertDeepEq(names, [std_iterator, 'next']);
 
     var desc = Object.getOwnPropertyDescriptor(proto, 'next');
     assertEq(desc.configurable, true);
     assertEq(desc.enumerable, false);
     assertEq(desc.writable, true);
 
-    assertEq(proto.iterator(), proto);
-    assertThrowsValue(function () { proto.next(); }, StopIteration);
+    assertEq(proto[std_iterator](), proto);
+    assertIteratorResult(proto.next(), undefined, true);
 }
 
 //test(Array);
 test(Map);
 test(Set);
--- a/js/src/jit-test/tests/for-of/arguments-1.js
+++ b/js/src/jit-test/tests/for-of/arguments-1.js
@@ -1,13 +1,15 @@
 // for-of can iterate arguments objects.
 
-// Arguments objects do not have a .iterator() method by default.
+load(libdir + "iteration.js");
+
+// Arguments objects do not have a .@@iterator() method by default.
 // Install one on Object.prototype.
-Object.prototype.iterator = Array.prototype.iterator;
+Object.prototype[std_iterator] = Array.prototype[std_iterator];
 
 var s;
 function test() {
     for (var v of arguments)
         s += v;
 }
 
 s = '';
--- a/js/src/jit-test/tests/for-of/arguments-2.js
+++ b/js/src/jit-test/tests/for-of/arguments-2.js
@@ -1,12 +1,14 @@
 // for-of can iterate arguments objects after returning.
 
+load(libdir + "iteration.js");
+
 function f() {
     return arguments;
 }
 
 var s = '';
 var args = f('a', 'b', 'c');
-Object.prototype.iterator = Array.prototype.iterator;
+Object.prototype[std_iterator] = Array.prototype[std_iterator];
 for (var v of args)
     s += v;
 assertEq(s, 'abc');
--- a/js/src/jit-test/tests/for-of/arguments-3.js
+++ b/js/src/jit-test/tests/for-of/arguments-3.js
@@ -1,11 +1,13 @@
 // for-of can iterate strict arguments objects.
 
-Object.prototype.iterator = Array.prototype.iterator;
+load(libdir + "iteration.js");
+
+Object.prototype[std_iterator] = Array.prototype[std_iterator];
 
 var s;
 function test() {
     "use strict";
     for (var v of arguments)
         s += v;
 }
 
--- a/js/src/jit-test/tests/for-of/arguments-4.js
+++ b/js/src/jit-test/tests/for-of/arguments-4.js
@@ -1,11 +1,13 @@
 // for-of can iterate arguments objects for other active frames.
 
-Object.prototype.iterator = Array.prototype.iterator;
+load(libdir + "iteration.js");
+
+Object.prototype[std_iterator] = Array.prototype[std_iterator];
 
 var s;
 function g(obj) {
     for (var v of obj)
         s += v;
 }
 
 function f() {
--- a/js/src/jit-test/tests/for-of/arguments-5.js
+++ b/js/src/jit-test/tests/for-of/arguments-5.js
@@ -1,11 +1,13 @@
 // for-of can iterate strict arguments objects in non-strict code.
 
-Object.prototype.iterator = Array.prototype.iterator;
+load(libdir + "iteration.js");
+
+Object.prototype[std_iterator] = Array.prototype[std_iterator];
 
 var s;
 function g(obj) {
     for (var v of obj)
         s += v;
 }
 
 function f() {
--- a/js/src/jit-test/tests/for-of/arguments-6.js
+++ b/js/src/jit-test/tests/for-of/arguments-6.js
@@ -1,11 +1,13 @@
 // Changing arguments.length affects a for-of loop iterating over arguments.
 
-Object.prototype.iterator = Array.prototype.iterator;
+load(libdir + "iteration.js");
+
+Object.prototype[std_iterator] = Array.prototype[std_iterator];
 
 var s;
 function f() {
     arguments.length = 2;
     for (var v of arguments)
         s += v;
 }
 
--- a/js/src/jit-test/tests/for-of/arguments-7.js
+++ b/js/src/jit-test/tests/for-of/arguments-7.js
@@ -1,11 +1,13 @@
 // Changing arguments.length during a for-of loop iterating over arguments affects the loop.
 
-Object.prototype.iterator = Array.prototype.iterator;
+load(libdir + "iteration.js");
+
+Object.prototype[std_iterator] = Array.prototype[std_iterator];
 
 var s;
 function f() {
     for (var v of arguments) {
         s += v;
         arguments.length--;
     }
 }
--- a/js/src/jit-test/tests/for-of/array-holes-4.js
+++ b/js/src/jit-test/tests/for-of/array-holes-4.js
@@ -1,11 +1,13 @@
 // for-of on an Array consults the prototype chain when it encounters a hole.
 
+load(libdir + "iteration.js");
+
 var m = {1: 'peek'};
 var a = [0, , 2, 3];
 a.__proto__ = m;
 var log = [];
-Object.prototype.iterator = Array.prototype.iterator;
+Object.prototype[std_iterator] = Array.prototype[std_iterator];
 for (var x of a)
     log.push(x);
 assertEq(log[1], 'peek');
 assertEq(log.join(), "0,peek,2,3");
--- a/js/src/jit-test/tests/for-of/array-iterator-surfaces-2.js
+++ b/js/src/jit-test/tests/for-of/array-iterator-surfaces-2.js
@@ -1,22 +1,24 @@
 // Superficial tests for iterators created by Array.prototype.iterator
 
-var proto = Object.getPrototypeOf([].iterator());
+load(libdir + "iteration.js");
+
+var proto = Object.getPrototypeOf([][std_iterator]());
 assertEq(Object.getPrototypeOf(proto), Iterator.prototype);
 
 function check(it) {
     assertEq(typeof it, 'object');
     assertEq(Object.getPrototypeOf(it), proto);
     assertEq(Object.getOwnPropertyNames(it).length, 0);
-    assertEq(it.iterator(), it);
+    assertEq(it[std_iterator](), it);
 
     // for-in enumerates the iterator's properties.
     it.x = 0;
     var s = '';
     for (var p in it)
         s += p + '.';
     assertEq(s, 'x.');
 }
 
-check([].iterator());
-check(Array.prototype.iterator.call({}));
-check(Array.prototype.iterator.call(undefined));
+check([][std_iterator]());
+check(Array.prototype[std_iterator].call({}));
+check(Array.prototype[std_iterator].call(undefined));
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/completion.js
@@ -0,0 +1,6 @@
+// The completion value of a for-of loop is the completion value of the
+// last evaluation of the body, or undefined.
+
+assertEq(eval("for (let x of [1, 2, 3]) { x }"), 3);
+assertEq(eval("for (let x of [1, 2, 3]) { x * 2 }"), 6);
+assertEq(eval("for (let x of []) { x }"), undefined);
--- a/js/src/jit-test/tests/for-of/generators-5.js
+++ b/js/src/jit-test/tests/for-of/generators-5.js
@@ -1,18 +1,20 @@
-// Breaking out of a for-of loop over a generator-iterator closes the iterator.
+// Breaking out of a for-of loop over a generator-iterator does not close the iterator.
+
+load(libdir + "iteration.js");
 
 function range(n) {
     for (var i = 0; i < n; i++)
         yield i;
 }
 
 var r = range(10);
 var s = '';
 for (var x of r) {
     s += x;
     if (x == 4)
         break;
 }
 s += '/';
 for (var y of r)
     s += y;
-assertEq(s, '01234/');
+assertEq(s, '01234/56789');
deleted file mode 100644
--- a/js/src/jit-test/tests/for-of/generators-6.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// Named break closes the right generator-iterators.
-
-function g() {
-    for (;;)
-        yield 1;
-}
-
-function isClosed(it) {
-    try {
-        it.next();
-        return false;
-    } catch (exc) {
-        if (exc !== StopIteration)
-            throw exc;
-        return true;
-    }
-}
-
-var a = g(), b = g(), c = g(), d = g(), e = g(), f = g();
-
-for (var aa of a) {
-    b_: for (var bb of b) {
-        c_: for (var cc of c) {
-            d_: for (var dd of d) {
-                e_: for (var ee of e) {
-                    for (var ff of f)
-                        break c_;
-                }
-            }
-        }
-        assertEq(isClosed(a), false);
-        assertEq(isClosed(b), false);
-        assertEq(isClosed(c), true);
-        assertEq(isClosed(d), true);
-        assertEq(isClosed(e), true);
-        assertEq(isClosed(f), true);
-        break b_;
-    }
-    assertEq(isClosed(a), false);
-    assertEq(isClosed(b), true);
-    break;
-}
-assertEq(isClosed(a), true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/manual-advance.js
@@ -0,0 +1,15 @@
+// Manually advancing the iterator.
+
+load(libdir + 'iteration.js');
+
+function* g(n) { for (var i=0; i<n; i++) yield i; }
+
+var inner = g(20);
+
+var n = 0;
+for (var x of inner) {
+    assertEq(x, n * 2);
+    assertIteratorResult(inner.next(), n * 2 + 1, false);
+    n++;
+}
+assertEq(n, 10);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/next-arity.js
@@ -0,0 +1,22 @@
+// For-of passes one arg to "next".
+
+load(libdir + 'iteration.js')
+
+var log = '';
+
+function Iter() {
+    function next() {
+        log += 'n';
+        assertEq(arguments.length, 1)
+        assertEq(arguments[0], undefined)
+        return { get value() { throw 42; }, done: true }
+    }
+
+    this[std_iterator] = function () { return this; }
+    this.next = next;
+}
+
+for (var x of new Iter())
+    throw 'not reached';
+
+assertEq(log, 'n');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/next-shenanigans.js
@@ -0,0 +1,41 @@
+// Test for-of with iter.next and monkeypatching.
+
+function* g(n) { for (var i=0; i<n; i++) yield i; }
+var GeneratorObjectPrototype = Object.getPrototypeOf(g).prototype;
+var GeneratorObjectPrototype_next = GeneratorObjectPrototype.next;
+
+// Monkeypatch next on an iterator.
+var inner = g(20);
+var n = 0;
+for (let x of inner) {
+    if (n == 0) {
+        assertEq(x, n++);
+    } else if (n == 1) {
+        assertEq(x, n++);
+        inner.next = function() { return { value: 42, done: false }; };
+    } else if (n == 2) {
+        assertEq(x, 42);
+        inner.next = function() { return { value: 100, done: true }; };
+    } else
+        throw 'not reached';
+}
+
+// Monkeypatch next on the prototype.
+var inner = g(20);
+var n = 0;
+for (let x of inner) {
+    if (n == 0) {
+        assertEq(x, n++);
+    } else if (n == 1) {
+        assertEq(x, n++);
+        GeneratorObjectPrototype.next =
+            function() { return { value: 42, done: false }; };
+    } else if (n == 2) {
+        n++;
+        assertEq(x, 42);
+        GeneratorObjectPrototype.next = GeneratorObjectPrototype_next;
+    } else if (n <= 20) {
+        assertEq(x, n++ - 1);
+    } else
+        throw 'not reached';
+}
--- a/js/src/jit-test/tests/for-of/proxy-3.js
+++ b/js/src/jit-test/tests/for-of/proxy-3.js
@@ -1,12 +1,13 @@
 // An exception thrown from a proxy trap while getting the .iterator method is propagated.
 
 load(libdir + "asserts.js");
+load(libdir + "iteration.js");
 
 var p = Proxy.create({
     getPropertyDescriptor: function (name) {
-        if (name == "iterator")
+        if (name == std_iterator)
             throw "fit";
         return undefined;
     }
 });
 assertThrowsValue(function () { for (var v of p) {} }, "fit");
--- a/js/src/jit-test/tests/for-of/semantics-01.js
+++ b/js/src/jit-test/tests/for-of/semantics-01.js
@@ -2,10 +2,12 @@
 // [[Get]] for properties named "iterator" and "next", and [[Call]]. These
 // "semantics" tests check that for-of really does appear to be implemented in
 // terms of those more basic operations, as required by the spec, even in
 // unusual cases.
 
 // Deleting Array.prototype.iterator makes for-of stop working on arrays.
 
 load(libdir + "asserts.js");
-delete Array.prototype.iterator;
+load(libdir + "iteration.js");
+
+delete Array.prototype[std_iterator];
 assertThrowsInstanceOf(function () { for (var x of []) ; }, TypeError);
--- a/js/src/jit-test/tests/for-of/semantics-02.js
+++ b/js/src/jit-test/tests/for-of/semantics-02.js
@@ -1,10 +1,12 @@
 // Replacing Array.prototype.iterator with something non-callable makes for-of throw.
 
 load(libdir + "asserts.js");
+load(libdir + "iteration.js");
+
 function test(v) {
-    Array.prototype.iterator = v;
+    Array.prototype[std_iterator] = v;
     assertThrowsInstanceOf(function () { for (var x of []) ; }, TypeError);
 }
 test(undefined);
 test(null);
 test({});
--- a/js/src/jit-test/tests/for-of/semantics-03.js
+++ b/js/src/jit-test/tests/for-of/semantics-03.js
@@ -1,11 +1,13 @@
 // Replacing Array.prototype.iterator with a generator affects for-of behavior.
 
-Array.prototype.iterator = function () {
+load(libdir + "iteration.js");
+
+Array.prototype[std_iterator] = function* () {
     for (var i = this.length; --i >= 0; )
         yield this[i];
 };
 
 var s = '';
 for (var v of ['a', 'b', 'c', 'd'])
     s += v;
 assertEq(s, 'dcba');
--- a/js/src/jit-test/tests/for-of/semantics-04.js
+++ b/js/src/jit-test/tests/for-of/semantics-04.js
@@ -1,15 +1,17 @@
 // Giving an Array an own .iterator property affects for-of.
 
+load(libdir + "asserts.js");
+load(libdir + "iteration.js");
+
 var a = [];
-a.iterator = function () {
+a[std_iterator] = function* () {
     yield 'o';
     yield 'k';
 };
 var s = '';
 for (var v of a)
     s += v;
 assertEq(s, 'ok');
 
-load(libdir + "asserts.js");
-a.iterator = undefined;
+a[std_iterator] = undefined;
 assertThrowsInstanceOf(function () { for (var v of a) ; }, TypeError);
--- a/js/src/jit-test/tests/for-of/semantics-05.js
+++ b/js/src/jit-test/tests/for-of/semantics-05.js
@@ -1,6 +1,8 @@
 // Deleting String.prototype.iterator makes for-of stop working on strings.
 
 load(libdir + "asserts.js");
-delete String.prototype.iterator;
+load(libdir + "iteration.js");
+
+delete String.prototype[std_iterator];
 assertThrowsInstanceOf(function () { for (var v of "abc") ; }, TypeError);
 assertThrowsInstanceOf(function () { for (var v of new String("abc")) ; }, TypeError);
--- a/js/src/jit-test/tests/for-of/semantics-07.js
+++ b/js/src/jit-test/tests/for-of/semantics-07.js
@@ -1,13 +1,15 @@
 // Deleting the .next method of an iterator in the middle of a for-of loop
 // causes a TypeError at the next iteration.
 
 load(libdir + "asserts.js");
-var iterProto = Object.getPrototypeOf([].iterator());
+load(libdir + "iteration.js");
+
+var iterProto = Object.getPrototypeOf([][std_iterator]());
 var s = '';
 assertThrowsInstanceOf(function () {
     for (var v of ['duck', 'duck', 'duck', 'goose', 'FAIL']) {
         s += v;
         if (v === 'goose')
             delete iterProto.next;
         s += '.';
     }
--- a/js/src/jit-test/tests/for-of/semantics-08.js
+++ b/js/src/jit-test/tests/for-of/semantics-08.js
@@ -1,7 +1,9 @@
-// A for-of loop exits if the iterator's .next method throws another compartment's StopIteration.
+// Results from another compartment are correctly interpreted by for-of.
+
+load(libdir + "iteration.js");
 
 var g = newGlobal();
-var it = g.eval("({ iterator: function () { return this; }, " +
-                "next: function () { throw StopIteration; } });");
+var it = g.eval("({ '" + std_iterator + "': function () { return this; }, " +
+                "next: function () { return { done: true } } });");
 for (x of it)
     throw 'FAIL';
--- a/js/src/jit-test/tests/for-of/semantics-11.js
+++ b/js/src/jit-test/tests/for-of/semantics-11.js
@@ -1,18 +1,20 @@
 // for-of on a proxy causes a predictable sequence of trap calls.
 
+load(libdir + "iteration.js");
+
 var s = '';
 
 var i = 0;
 var next_fn = Proxy.createFunction({}, function () {
     s += "n";
     if (i == 3)
-        throw StopIteration;
-    return i++;
+        return { value: undefined, done: true };
+    return { value: i++, done: false };
 });
 
 var it = Proxy.create({
     get: function (receiver, name) {
         if (name == 'toSource') {
             s += '?';
             return function () 'it';
         }
@@ -24,17 +26,17 @@ var it = Proxy.create({
 
 var iterator_fn = Proxy.createFunction({}, function () {
     s += 'i';
     return it;
 });
 
 var obj = Proxy.create({
     get: function (receiver, name) {
-        assertEq(name, "iterator");
+        assertEq(name, std_iterator);
         s += "I";
         return iterator_fn;
     }
 });
 
 for (var v of obj)
     s += v;
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/value-done-access.js
@@ -0,0 +1,23 @@
+// Test that each yield* loop just checks "done", and "value" is only
+// fetched once at the end.
+
+load(libdir + 'iteration.js');
+
+var log = "";
+
+function Iter(val, count) {
+    function next() {
+        return {
+            get done() { log += "d"; return count-- == 0; },
+            get value() { log += "v"; return val; }
+        }
+    }
+
+    this[std_iterator] = function() { return this; };
+    this.next = next;
+}
+
+for (var x of new Iter(42, 5))
+    assertEq(x, 42);
+
+assertEq(log, "dvdvdvdvdvd");
--- a/js/src/jit-test/tests/ion/bug800179.js
+++ b/js/src/jit-test/tests/ion/bug800179.js
@@ -1,9 +1,10 @@
-// |jit-test| error: is not iterable
+// |jit-test| error: TypeError
+
 try {
     x = []
     y = function() {}
     t = Uint8ClampedArray
     Object.defineProperty(x, 1, {
         get: (function() {
             for (v of t) {}
         })
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -2464,16 +2464,22 @@ BaselineCompiler::emit_JSOP_ENTERLET0()
 }
 
 bool
 BaselineCompiler::emit_JSOP_ENTERLET1()
 {
     return emitEnterBlock();
 }
 
+bool
+BaselineCompiler::emit_JSOP_ENTERLET2()
+{
+    return emitEnterBlock();
+}
+
 typedef bool (*LeaveBlockFn)(JSContext *, BaselineFrame *);
 static const VMFunction LeaveBlockInfo = FunctionInfo<LeaveBlockFn>(jit::LeaveBlock);
 
 bool
 BaselineCompiler::emitLeaveBlock()
 {
     // Call a stub to pop the block from the block chain.
     prepareVMCall();
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -143,16 +143,17 @@ namespace jit {
     _(JSOP_THROW)              \
     _(JSOP_TRY)                \
     _(JSOP_FINALLY)            \
     _(JSOP_GOSUB)              \
     _(JSOP_RETSUB)             \
     _(JSOP_ENTERBLOCK)         \
     _(JSOP_ENTERLET0)          \
     _(JSOP_ENTERLET1)          \
+    _(JSOP_ENTERLET2)          \
     _(JSOP_LEAVEBLOCK)         \
     _(JSOP_LEAVEBLOCKEXPR)     \
     _(JSOP_LEAVEFORLETIN)      \
     _(JSOP_EXCEPTION)          \
     _(JSOP_DEBUGGER)           \
     _(JSOP_ARGUMENTS)          \
     _(JSOP_RUNONCE)            \
     _(JSOP_REST)               \
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1237,16 +1237,17 @@ IonBuilder::snoopControlFlow(JSOp op)
           case SRC_CONTINUE:
             return processContinue(op);
 
           case SRC_SWITCHBREAK:
             return processSwitchBreak(op);
 
           case SRC_WHILE:
           case SRC_FOR_IN:
+          case SRC_FOR_OF:
             // while (cond) { }
             return whileOrForInLoop(sn);
 
           default:
             // Hard assert for now - make an error later.
             MOZ_ASSUME_UNREACHABLE("unknown goto case");
         }
         break;
@@ -2022,28 +2023,32 @@ IonBuilder::processDoWhileCondEnd(CFGSta
     MTest *test = MTest::New(vins, state.loop.entry, successor);
     current->end(test);
     return finishLoop(state, successor);
 }
 
 IonBuilder::ControlStatus
 IonBuilder::processWhileCondEnd(CFGState &state)
 {
-    JS_ASSERT(JSOp(*pc) == JSOP_IFNE);
+    JS_ASSERT(JSOp(*pc) == JSOP_IFNE || JSOp(*pc) == JSOP_IFEQ);
 
     // Balance the stack past the IFNE.
     MDefinition *ins = current->pop();
 
     // Create the body and successor blocks.
     MBasicBlock *body = newBlock(current, state.loop.bodyStart);
     state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1);
     if (!body || !state.loop.successor)
         return ControlStatus_Error;
 
-    MTest *test = MTest::New(ins, body, state.loop.successor);
+    MTest *test;
+    if (JSOp(*pc) == JSOP_IFNE)
+        test = MTest::New(ins, body, state.loop.successor);
+    else
+        test = MTest::New(ins, state.loop.successor, body);
     current->end(test);
 
     state.state = CFGState::WHILE_LOOP_BODY;
     state.stopAt = state.loop.bodyEnd;
     pc = state.loop.bodyStart;
     setCurrentAndSpecializePhis(body);
     return ControlStatus_Jumped;
 }
@@ -2605,17 +2610,17 @@ IonBuilder::whileOrForInLoop(jssrcnote *
     //    GOTO cond   ; SRC_WHILE (offset to IFNE)
     //    LOOPHEAD
     //    ...
     //  cond:
     //    LOOPENTRY
     //    ...
     //    IFNE        ; goes to LOOPHEAD
     // for (x in y) { } loops are similar; the cond will be a MOREITER.
-    JS_ASSERT(SN_TYPE(sn) == SRC_FOR_IN || SN_TYPE(sn) == SRC_WHILE);
+    JS_ASSERT(SN_TYPE(sn) == SRC_FOR_OF || SN_TYPE(sn) == SRC_FOR_IN || SN_TYPE(sn) == SRC_WHILE);
     int ifneOffset = js_GetSrcNoteOffset(sn, 0);
     jsbytecode *ifne = pc + ifneOffset;
     JS_ASSERT(ifne > pc);
 
     // Verify that the IFNE goes back to a loophead op.
     JS_ASSERT(JSOp(*GetNextPc(pc)) == JSOP_LOOPHEAD);
     JS_ASSERT(GetNextPc(pc) == ifne + GetJumpOffset(ifne));
 
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -264,17 +264,17 @@ MSG_DEF(JSMSG_NAME_AFTER_FOR_PAREN,   21
 MSG_DEF(JSMSG_IN_AFTER_FOR_NAME,      211, 0, JSEXN_SYNTAXERR, "missing 'in' or 'of' after for")
 MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE,  212, 2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")
 MSG_DEF(JSMSG_UNUSED213,              213, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_BAD_GENERATOR_YIELD,    214, 1, JSEXN_TYPEERR, "yield from closing generator {0}")
 MSG_DEF(JSMSG_BAD_GENERATOR_SYNTAX,   215, 1, JSEXN_SYNTAXERR, "{0} expression must be parenthesized")
 MSG_DEF(JSMSG_ARRAY_COMP_LEFTSIDE,    216, 0, JSEXN_SYNTAXERR, "invalid array comprehension left-hand side")
 MSG_DEF(JSMSG_UNUSED217,              217, 0, JSEXN_NONE, "")
 MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE,     218, 0, JSEXN_TYPEERR, "reduce of empty array with no initial value")
-MSG_DEF(JSMSG_UNUSED219,              219, 0, JSEXN_NONE, "")
+MSG_DEF(JSMSG_BAD_SYMBOL,             219, 1, JSEXN_TYPEERR, "{0} is not a well-known @@-symbol")
 MSG_DEF(JSMSG_BAD_DELETE_OPERAND,     220, 0, JSEXN_REFERENCEERR, "invalid delete operand")
 MSG_DEF(JSMSG_BAD_INCOP_OPERAND,      221, 0, JSEXN_REFERENCEERR, "invalid increment/decrement operand")
 MSG_DEF(JSMSG_UNEXPECTED_TYPE,        222, 2, JSEXN_TYPEERR, "{0} is {1}")
 MSG_DEF(JSMSG_LET_DECL_NOT_IN_BLOCK,  223, 0, JSEXN_SYNTAXERR, "let declaration not directly within block")
 MSG_DEF(JSMSG_BAD_OBJECT_INIT,        224, 0, JSEXN_SYNTAXERR, "invalid object initializer")
 MSG_DEF(JSMSG_CANT_SET_ARRAY_ATTRS,   225, 0, JSEXN_INTERNALERR, "can't set attributes on indexed array properties")
 MSG_DEF(JSMSG_EVAL_ARITY,             226, 0, JSEXN_TYPEERR, "eval accepts only one parameter")
 MSG_DEF(JSMSG_MISSING_FUN_ARG,        227, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -247,16 +247,17 @@ ScriptAnalysis::analyzeBytecode(JSContex
           case JSOP_DEFCONST:
           case JSOP_SETCONST:
             usesScopeChain_ = true; // Requires access to VarObj via ScopeChain.
             canTrackVars = false;
             break;
 
           case JSOP_EVAL:
           case JSOP_SPREADEVAL:
+          case JSOP_ENTERLET2:
           case JSOP_ENTERWITH:
             canTrackVars = false;
             break;
 
           case JSOP_TABLESWITCH: {
             unsigned defaultOffset = offset + GET_JUMP_OFFSET(pc);
             jsbytecode *pc2 = pc + JUMP_OFFSET_LEN;
             int32_t low = GET_JUMP_OFFSET(pc2);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4216,17 +4216,25 @@ JS_DefineFunctions(JSContext *cx, JSObje
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, objArg);
 
     RootedObject obj(cx, objArg);
     RootedObject ctor(cx);
 
     for (; fs->name; fs++) {
-        RootedAtom atom(cx, Atomize(cx, fs->name, strlen(fs->name)));
+        RootedAtom atom(cx);
+        // If the name starts with "@@", it must be a well-known symbol.
+        if (fs->name[0] != '@' || fs->name[1] != '@')
+            atom = Atomize(cx, fs->name, strlen(fs->name));
+        else if (strcmp(fs->name, "@@iterator") == 0)
+            // FIXME: This atom should be a symbol: bug 918828.
+            atom = cx->names().std_iterator;
+        else
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_SYMBOL, fs->name);
         if (!atom)
             return false;
 
         Rooted<jsid> id(cx, AtomToId(atom));
 
         /*
          * Define a generic arity N+1 static method for the arity N prototype
          * method if flags contains JSFUN_GENERIC_NATIVE.
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2887,16 +2887,17 @@ static const JSFunctionSpec array_method
     JS_SELF_HOSTED_FN("scatterPar",  "ArrayScatterPar",  5,0),
     JS_SELF_HOSTED_FN("filterPar",   "ArrayFilterPar",   2,0),
 #endif
 
     /* ES6 additions */
     JS_SELF_HOSTED_FN("find",        "ArrayFind",        1,0),
     JS_SELF_HOSTED_FN("findIndex",   "ArrayFindIndex",   1,0),
 
+    JS_SELF_HOSTED_FN("@@iterator",  "ArrayIterator",    0,0),
     JS_FN("iterator",           JS_ArrayIterator,   0,0),
     JS_FS_END
 };
 
 static const JSFunctionSpec array_static_methods[] = {
     JS_FN("isArray",            array_isArray,      1,0),
     JS_SELF_HOSTED_FN("lastIndexOf", "ArrayStaticLastIndexOf", 2,0),
     JS_SELF_HOSTED_FN("indexOf",     "ArrayStaticIndexOf", 2,0),
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -560,22 +560,22 @@ UpdateNativeIterator(NativeIterator *ni,
     // SuppressDeletedPropertyHelper will recognize the iterator as a match.
     ni->obj = obj;
 }
 
 bool
 js::GetIterator(JSContext *cx, HandleObject obj, unsigned flags, MutableHandleValue vp)
 {
     if (flags == JSITER_FOR_OF) {
-        // for-of loop. The iterator is simply |obj.iterator()|.
+        // for-of loop. The iterator is simply |obj[@@iterator]()|.
         RootedValue method(cx);
-        if (!JSObject::getProperty(cx, obj, obj, cx->names().iterator, &method))
+        if (!JSObject::getProperty(cx, obj, obj, cx->names().std_iterator, &method))
             return false;
 
-        // Throw if obj.iterator isn't callable. js::Invoke is about to check
+        // Throw if obj[@@iterator] isn't callable. js::Invoke is about to check
         // for this kind of error anyway, but it would throw an inscrutable
         // error message about |method| rather than this nice one about |obj|.
         if (!method.isObject() || !method.toObject().isCallable()) {
             RootedValue val(cx, ObjectOrNullValue(obj));
             char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, val, NullPtr());
             if (!bytes)
                 return false;
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_ITERABLE, bytes);
@@ -588,16 +588,18 @@ js::GetIterator(JSContext *cx, HandleObj
 
         JSObject *resultObj = ToObject(cx, vp);
         if (!resultObj)
             return false;
         vp.setObject(*resultObj);
         return true;
     }
 
+    JS_ASSERT(!(flags & JSITER_FOR_OF));
+
     Vector<Shape *, 8> shapes(cx);
     uint32_t key = 0;
 
     bool keysOnly = (flags == JSITER_ENUMERATE);
 
     if (obj) {
         if (JSIteratorOp op = obj->getClass()->ext.iteratorObject) {
             JSObject *iterobj = op(cx, obj, !(flags & JSITER_FOREACH));
@@ -720,16 +722,40 @@ JSObject *
 js::GetIteratorObject(JSContext *cx, HandleObject obj, uint32_t flags)
 {
     RootedValue value(cx);
     if (!GetIterator(cx, obj, flags, &value))
         return NULL;
     return &value.toObject();
 }
 
+JSObject *
+js::CreateItrResultObject(JSContext *cx, HandleValue value, bool done)
+{
+    // FIXME: We can cache the iterator result object shape somewhere.
+    AssertHeapIsIdle(cx);
+
+    RootedObject proto(cx, cx->global()->getOrCreateObjectPrototype(cx));
+    if (!proto)
+        return nullptr;
+
+    RootedObject obj(cx, NewObjectWithGivenProto(cx, &JSObject::class_, proto, cx->global()));
+    if (!obj)
+        return nullptr;
+
+    if (!JSObject::defineProperty(cx, obj, cx->names().value, value))
+        return nullptr;
+
+    RootedValue doneBool(cx, BooleanValue(done));
+    if (!JSObject::defineProperty(cx, obj, cx->names().done, doneBool))
+        return nullptr;
+
+    return obj;
+}
+
 bool
 js_ThrowStopIteration(JSContext *cx)
 {
     JS_ASSERT(!JS_IsExceptionPending(cx));
     RootedValue v(cx);
     if (js_FindClassObject(cx, JSProto_StopIteration, &v))
         cx->setPendingException(v);
     return false;
@@ -776,33 +802,25 @@ iterator_next_impl(JSContext *cx, CallAr
     if (!args.rval().toBoolean()) {
         js_ThrowStopIteration(cx);
         return false;
     }
 
     return js_IteratorNext(cx, thisObj, args.rval());
 }
 
-static bool
-iterator_iterator(JSContext *cx, unsigned argc, Value *vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().set(args.thisv());
-    return true;
-}
-
 bool
 iterator_next(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsIterator, iterator_next_impl>(cx, args);
 }
 
 static const JSFunctionSpec iterator_methods[] = {
-    JS_FN("iterator",  iterator_iterator,   0, 0),
+    JS_SELF_HOSTED_FN("@@iterator", "LegacyIteratorShim", 0, 0),
     JS_FN("next",      iterator_next,       0, 0),
     JS_FS_END
 };
 
 static JSObject *
 iterator_iteratorObject(JSContext *cx, HandleObject obj, bool keysonly)
 {
     return obj;
@@ -943,16 +961,17 @@ const Class ElementIteratorObject::class
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     NULL                     /* finalize    */
 };
 
 const JSFunctionSpec ElementIteratorObject::methods[] = {
+    JS_SELF_HOSTED_FN("@@iterator", "LegacyIteratorShim", 0, 0),
     JS_FN("next", next, 0, 0),
     JS_FS_END
 };
 
 static bool
 CloseLegacyGenerator(JSContext *cx, HandleObject genobj);
 
 bool
@@ -1297,16 +1316,48 @@ const Class StopIterationObject::class_ 
     JS_ConvertStub,
     NULL,                    /* finalize    */
     NULL,                    /* checkAccess */
     NULL,                    /* call        */
     stopiter_hasInstance,
     NULL                     /* construct   */
 };
 
+bool
+ForOfIterator::next(MutableHandleValue vp, bool *done)
+{
+    JS_ASSERT(iterator);
+
+    RootedValue method(cx);
+    if (!JSObject::getProperty(cx, iterator, iterator, cx->names().next, &method))
+        return false;
+
+    InvokeArgs args(cx);
+    if (!args.init(1))
+        return false;
+    args.setCallee(method);
+    args.setThis(ObjectValue(*iterator));
+    args[0].setUndefined();
+    if (!Invoke(cx, args))
+        return false;
+
+    RootedObject resultObj(cx, ToObject(cx, args.rval()));
+    if (!resultObj)
+        return false;
+    RootedValue doneVal(cx);
+    if (!JSObject::getProperty(cx, resultObj, resultObj, cx->names().done, &doneVal))
+        return false;
+    *done = ToBoolean(doneVal);
+    if (*done) {
+        vp.setUndefined();
+        return true;
+    }
+    return JSObject::getProperty(cx, resultObj, resultObj, cx->names().value, vp);
+}
+
 /*** Generators **********************************************************************************/
 
 template<typename T>
 static void
 FinalizeGenerator(FreeOp *fop, JSObject *obj)
 {
     JS_ASSERT(obj->is<T>());
     JSGenerator *gen = obj->as<T>().getGenerator();
@@ -1785,24 +1836,24 @@ NativeMethod(JSContext *cx, unsigned arg
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsObjectOfType<T>, Impl>(cx, args);
 }
 
 #define JSPROP_ROPERM   (JSPROP_READONLY | JSPROP_PERMANENT)
 #define JS_METHOD(name, T, impl, len, attrs) JS_FN(name, (NativeMethod<T,impl>), len, attrs)
 
 static const JSFunctionSpec star_generator_methods[] = {
-    JS_FN("iterator", iterator_iterator, 0, 0),
+    JS_SELF_HOSTED_FN("@@iterator", "IteratorIdentity", 0, 0),
     JS_METHOD("next", StarGeneratorObject, star_generator_next, 1, 0),
     JS_METHOD("throw", StarGeneratorObject, star_generator_throw, 1, 0),
     JS_FS_END
 };
 
 static const JSFunctionSpec legacy_generator_methods[] = {
-    JS_FN("iterator", iterator_iterator, 0, 0),
+    JS_SELF_HOSTED_FN("@@iterator", "LegacyGeneratorIteratorShim", 0, 0),
     // "send" is an alias for "next".
     JS_METHOD("next", LegacyGeneratorObject, legacy_generator_next, 1, JSPROP_ROPERM),
     JS_METHOD("send", LegacyGeneratorObject, legacy_generator_next, 1, JSPROP_ROPERM),
     JS_METHOD("throw", LegacyGeneratorObject, legacy_generator_throw, 1, JSPROP_ROPERM),
     JS_METHOD("close", LegacyGeneratorObject, legacy_generator_close, 0, JSPROP_ROPERM),
     JS_FS_END
 };
 
--- a/js/src/jsiter.h
+++ b/js/src/jsiter.h
@@ -231,33 +231,16 @@ extern bool
 js_IteratorNext(JSContext *cx, js::HandleObject iterobj, js::MutableHandleValue rval);
 
 extern bool
 js_ThrowStopIteration(JSContext *cx);
 
 namespace js {
 
 /*
- * Get the next value from an iterator object.
- *
- * On success, store the next value in *vp and return true; if there are no
- * more values, store the magic value JS_NO_ITER_VALUE in *vp and return true.
- */
-inline bool
-Next(JSContext *cx, HandleObject iter, MutableHandleValue vp)
-{
-    if (!js_IteratorMore(cx, iter, vp))
-        return false;
-    if (vp.toBoolean())
-        return js_IteratorNext(cx, iter, vp);
-    vp.setMagic(JS_NO_ITER_VALUE);
-    return true;
-}
-
-/*
  * Convenience class for imitating a JS level for-of loop. Typical usage:
  *
  *     ForOfIterator it(cx, iterable);
  *     while (it.next()) {
  *        if (!DoStuff(cx, it.value()))
  *            return false;
  *     }
  *     if (!it.close())
@@ -270,66 +253,40 @@ Next(JSContext *cx, HandleObject iter, M
  * and the failure is allowed to propagate on cx, as in this example if DoStuff
  * fails. In that case, ForOfIterator's destructor does all necessary cleanup.
  */
 class ForOfIterator
 {
   private:
     JSContext *cx;
     RootedObject iterator;
-    RootedValue currentValue;
-    bool ok;
-    bool closed;
 
     ForOfIterator(const ForOfIterator &) MOZ_DELETE;
     ForOfIterator &operator=(const ForOfIterator &) MOZ_DELETE;
 
   public:
-    ForOfIterator(JSContext *cx, const Value &iterable)
-        : cx(cx), iterator(cx, NULL), currentValue(cx), closed(false)
-    {
-        RootedValue iterv(cx, iterable);
-        ok = ValueToIterator(cx, JSITER_FOR_OF, &iterv);
-        iterator = ok ? &iterv.get().toObject() : NULL;
-    }
+    ForOfIterator(JSContext *cx) : cx(cx), iterator(cx) { }
 
-    ~ForOfIterator() {
-        if (!closed)
-            close();
-    }
-
-    bool next() {
-        JS_ASSERT(!closed);
-        ok = ok && Next(cx, iterator, &currentValue);
-        return ok && !currentValue.get().isMagic(JS_NO_ITER_VALUE);
+    bool init(HandleValue iterable) {
+        RootedValue iterv(cx, iterable);
+        if (!ValueToIterator(cx, JSITER_FOR_OF, &iterv))
+            return false;
+        iterator = &iterv.get().toObject();
+        return true;
     }
 
-    MutableHandleValue value() {
-        JS_ASSERT(ok);
-        JS_ASSERT(!closed);
-        return &currentValue;
-    }
+    bool next(MutableHandleValue val, bool *done);
+};
 
-    bool close() {
-        JS_ASSERT(!closed);
-        closed = true;
-        if (!iterator)
-            return false;
-        bool throwing = cx->isExceptionPending();
-        RootedValue exc(cx);
-        if (throwing) {
-            exc = cx->getPendingException();
-            cx->clearPendingException();
-        }
-        bool closedOK = CloseIterator(cx, iterator);
-        if (throwing && closedOK)
-            cx->setPendingException(exc);
-        return ok && !throwing && closedOK;
-    }
-};
+/*
+ * Create an object of the form { value: VALUE, done: DONE }.
+ * ES6 draft from 2013-09-05, section 25.4.3.4.
+ */
+extern JSObject *
+CreateItrResultObject(JSContext *cx, js::HandleValue value, bool done);
 
 } /* namespace js */
 
 /*
  * Generator state codes.
  */
 enum JSGeneratorState
 {
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -114,19 +114,21 @@ js_GetVariableBytecodeLength(jsbytecode 
       default:
         MOZ_ASSUME_UNREACHABLE("Unexpected op");
     }
 }
 
 static uint32_t
 NumBlockSlots(JSScript *script, jsbytecode *pc)
 {
-    JS_ASSERT(*pc == JSOP_ENTERBLOCK || *pc == JSOP_ENTERLET0 || *pc == JSOP_ENTERLET1);
+    JS_ASSERT(*pc == JSOP_ENTERBLOCK ||
+              *pc == JSOP_ENTERLET0 || *pc == JSOP_ENTERLET1 || *pc == JSOP_ENTERLET2);
     JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET0_LENGTH);
     JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET1_LENGTH);
+    JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET2_LENGTH);
 
     return script->getObject(GET_UINT32_INDEX(pc))->as<StaticBlockObject>().slotCount();
 }
 
 unsigned
 js::StackUses(JSScript *script, jsbytecode *pc)
 {
     JSOp op = (JSOp) *pc;
@@ -141,16 +143,18 @@ js::StackUses(JSScript *script, jsbyteco
       case JSOP_LEAVEBLOCK:
         return GET_UINT16(pc);
       case JSOP_LEAVEBLOCKEXPR:
         return GET_UINT16(pc) + 1;
       case JSOP_ENTERLET0:
         return NumBlockSlots(script, pc);
       case JSOP_ENTERLET1:
         return NumBlockSlots(script, pc) + 1;
+      case JSOP_ENTERLET2:
+        return NumBlockSlots(script, pc) + 2;
       default:
         /* stack: fun, this, [argc arguments] */
         JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
                   op == JSOP_FUNCALL || op == JSOP_FUNAPPLY);
         return 2 + GET_ARGC(pc);
     }
 }
 
@@ -158,17 +162,21 @@ unsigned
 js::StackDefs(JSScript *script, jsbytecode *pc)
 {
     JSOp op = (JSOp) *pc;
     const JSCodeSpec &cs = js_CodeSpec[op];
     if (cs.ndefs >= 0)
         return cs.ndefs;
 
     uint32_t n = NumBlockSlots(script, pc);
-    return op == JSOP_ENTERLET1 ? n + 1 : n;
+    if (op == JSOP_ENTERLET1)
+        return n + 1;
+    if (op == JSOP_ENTERLET2)
+        return n + 2;
+    return n;
 }
 
 static const char * const countBaseNames[] = {
     "interp"
 };
 
 JS_STATIC_ASSERT(JS_ARRAY_LENGTH(countBaseNames) == PCCounts::BASE_LIMIT);
 
@@ -1046,17 +1054,18 @@ GetBlockChainAtPC(JSContext *cx, JSScrip
 
     JSObject *blockChain = NULL;
     for (jsbytecode *p = start; p < pc; p += GetBytecodeLength(p)) {
         JSOp op = JSOp(*p);
 
         switch (op) {
           case JSOP_ENTERBLOCK:
           case JSOP_ENTERLET0:
-          case JSOP_ENTERLET1: {
+          case JSOP_ENTERLET1:
+          case JSOP_ENTERLET2: {
             JSObject *child = script->getObject(p);
             JS_ASSERT_IF(blockChain, child->as<BlockObject>().stackDepth() >=
                                      blockChain->as<BlockObject>().stackDepth());
             blockChain = child;
             break;
           }
           case JSOP_LEAVEBLOCK:
           case JSOP_LEAVEBLOCKEXPR:
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -395,23 +395,24 @@ OPDEF(JSOP_UNUSED183,     183,"unused183
 
 OPDEF(JSOP_CALLPROP,      184,"callprop",   NULL,     5,  1,  1, JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_TMPSLOT3)
 
 /* Enter a let block/expr whose slots are at the top of the stack. */
 OPDEF(JSOP_ENTERLET0,     185,"enterlet0",  NULL,     5, -1, -1,  JOF_OBJECT)
 
 /* Enter a let block/expr whose slots are 1 below the top of the stack. */
 OPDEF(JSOP_ENTERLET1,     186,"enterlet1",  NULL,     5, -1, -1,  JOF_OBJECT)
+/* Enter a let block/expr whose slots are 2 below the top of the stack. */
+OPDEF(JSOP_ENTERLET2,     187,"enterlet2",  NULL,     5, -1, -1,  JOF_OBJECT)
 
 /*
  * Opcode to hold 24-bit immediate integer operands.
  */
-OPDEF(JSOP_UINT24,        187,"uint24",     NULL,     4,  0,  1, JOF_UINT24)
+OPDEF(JSOP_UINT24,        188,"uint24",     NULL,     4,  0,  1, JOF_UINT24)
 
-OPDEF(JSOP_UNUSED188,     188,"unused188",   NULL,    1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED189,     189,"unused189",   NULL,    1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED190,     190,"unused190",   NULL,    1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED191,     191,"unused191",   NULL,    1,  0,  0,  JOF_BYTE)
 OPDEF(JSOP_UNUSED192,     192,"unused192",   NULL,    1,  0,  0,  JOF_BYTE)
 
 OPDEF(JSOP_CALLELEM,      193, "callelem",   NULL,    1,  2,  1, JOF_BYTE |JOF_ELEM|JOF_TYPESET|JOF_LEFTASSOC)
 
 /*
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -3653,16 +3653,17 @@ static const JSFunctionSpec string_metho
     JS_FN("strike",            str_strike,            0,0),
     JS_FN("small",             str_small,             0,0),
     JS_FN("big",               str_big,               0,0),
     JS_FN("blink",             str_blink,             0,0),
     JS_FN("sup",               str_sup,               0,0),
     JS_FN("sub",               str_sub,               0,0),
 #endif
 
+    JS_SELF_HOSTED_FN("@@iterator", "ArrayIterator",  0,0),
     JS_FN("iterator",          JS_ArrayIterator,      0,0),
     JS_FS_END
 };
 
 bool
 js_String(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1799,16 +1799,17 @@ SrcNotes(JSContext *cx, HandleScript scr
                    unsigned(js_GetSrcNoteOffset(sn, 2)));
             break;
 
           case SRC_IF_ELSE:
             Sprint(sp, " else %u", unsigned(js_GetSrcNoteOffset(sn, 0)));
             break;
 
           case SRC_FOR_IN:
+          case SRC_FOR_OF:
             Sprint(sp, " closingjump %u", unsigned(js_GetSrcNoteOffset(sn, 0)));
             break;
 
           case SRC_COND:
           case SRC_WHILE:
           case SRC_NEXTCASE:
             Sprint(sp, " offset %u", unsigned(js_GetSrcNoteOffset(sn, 0)));
             break;
--- a/js/src/tests/ecma_6/Generators/runtime.js
+++ b/js/src/tests/ecma_6/Generators/runtime.js
@@ -12,16 +12,18 @@ function assertSyntaxError(str) {
 }
 
 
 function f() { }
 function* g() { yield 1; }
 var GeneratorFunctionPrototype = Object.getPrototypeOf(g);
 var GeneratorFunction = GeneratorFunctionPrototype.constructor;
 var GeneratorObjectPrototype = GeneratorFunctionPrototype.prototype;
+// FIXME: This should be a symbol.
+var std_iterator = "@@iterator";
 
 
 // A generator function should have the same set of properties as any
 // other function.
 function TestGeneratorFunctionInstance() {
     var f_own_property_names = Object.getOwnPropertyNames(f);
     var g_own_property_names = Object.getOwnPropertyNames(g);
 
@@ -59,17 +61,17 @@ TestGeneratorFunctionPrototype();
 // Functions that we associate with generator objects are actually defined by
 // a common prototype.
 function TestGeneratorObjectPrototype() {
     assertEq(Object.getPrototypeOf(GeneratorObjectPrototype),
                Object.prototype);
     assertEq(Object.getPrototypeOf((function*(){yield 1}).prototype),
                GeneratorObjectPrototype);
 
-    var expected_property_names = ["iterator", "next", "throw", "constructor"];
+    var expected_property_names = ["next", "throw", "constructor", std_iterator];
     var found_property_names =
         Object.getOwnPropertyNames(GeneratorObjectPrototype);
 
     expected_property_names.sort();
     found_property_names.sort();
 
     assertDeepEq(found_property_names, expected_property_names);
 }
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -32,16 +32,17 @@
     macro(CollatorCompareGet, CollatorCompareGet, "Intl_Collator_compare_get") \
     macro(columnNumber, columnNumber, "columnNumber") \
     macro(compare, compare, "compare") \
     macro(configurable, configurable, "configurable") \
     macro(construct, construct, "construct") \
     macro(constructor, constructor, "constructor") \
     macro(currency, currency, "currency") \
     macro(currencyDisplay, currencyDisplay, "currencyDisplay") \
+    macro(std_iterator, std_iterator, "@@iterator") \
     macro(DateTimeFormat, DateTimeFormat, "DateTimeFormat") \
     macro(DateTimeFormatFormatGet, DateTimeFormatFormatGet, "Intl_DateTimeFormat_format_get") \
     macro(decodeURI, decodeURI, "decodeURI") \
     macro(decodeURIComponent, decodeURIComponent, "decodeURIComponent") \
     macro(defineProperty, defineProperty, "defineProperty") \
     macro(defineGetter, defineGetter, "__defineGetter__") \
     macro(defineSetter, defineSetter, "__defineSetter__") \
     macro(delete, delete_, "delete") \
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1480,17 +1480,16 @@ BEGIN_CASE(JSOP_UNUSED175)
 BEGIN_CASE(JSOP_UNUSED176)
 BEGIN_CASE(JSOP_UNUSED177)
 BEGIN_CASE(JSOP_UNUSED178)
 BEGIN_CASE(JSOP_UNUSED179)
 BEGIN_CASE(JSOP_UNUSED180)
 BEGIN_CASE(JSOP_UNUSED181)
 BEGIN_CASE(JSOP_UNUSED182)
 BEGIN_CASE(JSOP_UNUSED183)
-BEGIN_CASE(JSOP_UNUSED188)
 BEGIN_CASE(JSOP_UNUSED189)
 BEGIN_CASE(JSOP_UNUSED190)
 BEGIN_CASE(JSOP_UNUSED200)
 BEGIN_CASE(JSOP_UNUSED201)
 BEGIN_CASE(JSOP_UNUSED208)
 BEGIN_CASE(JSOP_UNUSED209)
 BEGIN_CASE(JSOP_UNUSED210)
 BEGIN_CASE(JSOP_UNUSED219)
@@ -3045,31 +3044,36 @@ BEGIN_CASE(JSOP_INITELEM_INC)
 END_CASE(JSOP_INITELEM_INC)
 
 BEGIN_CASE(JSOP_SPREAD)
 {
     int32_t count = regs.sp[-2].toInt32();
     RootedObject &arr = rootObject0;
     arr = &regs.sp[-3].toObject();
     const Value iterable = regs.sp[-1];
-    ForOfIterator iter(cx, iterable);
+    ForOfIterator iter(cx);
     RootedValue &iterVal = rootValue0;
-    while (iter.next()) {
+    iterVal.set(iterable);
+    if (!iter.init(iterVal))
+        goto error;
+    while (true) {
+        bool done;
+        if (!iter.next(&iterVal, &done))
+            goto error;
+        if (done)
+            break;
         if (count == INT32_MAX) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                                  JSMSG_SPREAD_TOO_LARGE);
             goto error;
         }
-        iterVal = iter.value();
         if (!JSObject::defineElement(cx, arr, count++, iterVal, nullptr, nullptr,
                                      JSPROP_ENUMERATE))
             goto error;
     }
-    if (!iter.close())
-        goto error;
     regs.sp[-2].setInt32(count);
     regs.sp--;
 }
 END_CASE(JSOP_SPREAD)
 
 {
 BEGIN_CASE(JSOP_GOSUB)
     PUSH_BOOLEAN(false);
@@ -3176,16 +3180,17 @@ BEGIN_CASE(JSOP_DEBUGGER)
       default:;
     }
 }
 END_CASE(JSOP_DEBUGGER)
 
 BEGIN_CASE(JSOP_ENTERBLOCK)
 BEGIN_CASE(JSOP_ENTERLET0)
 BEGIN_CASE(JSOP_ENTERLET1)
+BEGIN_CASE(JSOP_ENTERLET2)
 {
     StaticBlockObject &blockObj = script->getObject(regs.pc)->as<StaticBlockObject>();
 
     if (op == JSOP_ENTERBLOCK) {
         JS_ASSERT(regs.stackDepth() == blockObj.stackDepth());
         JS_ASSERT(regs.stackDepth() + blockObj.slotCount() <= script->nslots);
         Value *vp = regs.sp + blockObj.slotCount();
         SetValueRangeToUndefined(regs.sp, vp);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -450,16 +450,30 @@ js::intrinsic_HaveSameClass(JSContext *c
     JS_ASSERT(args.length() == 2);
     JS_ASSERT(args[0].isObject());
     JS_ASSERT(args[1].isObject());
 
     args.rval().setBoolean(args[0].toObject().getClass() == args[1].toObject().getClass());
     return true;
 }
 
+static bool
+intrinsic_GetIteratorPrototype(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    JS_ASSERT(args.length() == 0);
+
+    JSObject *obj = cx->global()->getOrCreateIteratorPrototype(cx);
+    if (!obj)
+        return false;
+
+    args.rval().setObject(*obj);
+    return true;
+}
+
 /*
  * ParallelTestsShouldPass(): Returns false if we are running in a
  * mode (such as --ion-eager) that is known to cause additional
  * bailouts or disqualifications for parallel array tests.
  *
  * This is needed because the parallel tests generally assert that,
  * under normal conditions, they will run without bailouts or
  * compilation failures, but this does not hold under "stress-testing"
@@ -528,16 +542,18 @@ const JSFunctionSpec intrinsic_functions
     JS_FN("DecompileArg",            intrinsic_DecompileArg,            2,0),
     JS_FN("RuntimeDefaultLocale",    intrinsic_RuntimeDefaultLocale,    0,0),
 
     JS_FN("UnsafePutElements",               intrinsic_UnsafePutElements,               3,0),
     JS_FN("UnsafeSetReservedSlot",   intrinsic_UnsafeSetReservedSlot,   3,0),
     JS_FN("UnsafeGetReservedSlot",   intrinsic_UnsafeGetReservedSlot,   2,0),
     JS_FN("HaveSameClass",           intrinsic_HaveSameClass,           2,0),
 
+    JS_FN("GetIteratorPrototype",    intrinsic_GetIteratorPrototype,    0,0),
+
     JS_FN("ForkJoin",                intrinsic_ForkJoin,                2,0),
     JS_FN("ForkJoinSlices",          intrinsic_ForkJoinSlices,          0,0),
     JS_FN("NewParallelArray",        intrinsic_NewParallelArray,        3,0),
     JS_FN("NewDenseArray",           intrinsic_NewDenseArray,           1,0),
     JS_FN("ShouldForceSequential",   intrinsic_ShouldForceSequential,   0,0),
     JS_FN("ParallelTestsShouldPass", intrinsic_ParallelTestsShouldPass, 0,0),
 
     // See builtin/Intl.h for descriptions of the intl_* functions.
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -3421,25 +3421,27 @@ const JSFunctionSpec ArrayBufferObject::
 
 /*
  * TypedArrayObject boilerplate
  */
 
 #ifndef RELEASE_BUILD
 # define IMPL_TYPED_ARRAY_STATICS(_typedArray)                                     \
 const JSFunctionSpec _typedArray##Object::jsfuncs[] = {                            \
+    JS_SELF_HOSTED_FN("@@iterator", "ArrayIterator", 0, 0),                        \
     JS_FN("iterator", JS_ArrayIterator, 0, 0),                                     \
     JS_FN("subarray", _typedArray##Object::fun_subarray, 2, JSFUN_GENERIC_NATIVE), \
     JS_FN("set", _typedArray##Object::fun_set, 2, JSFUN_GENERIC_NATIVE),           \
     JS_FN("move", _typedArray##Object::fun_move, 3, JSFUN_GENERIC_NATIVE),         \
     JS_FS_END                                                                      \
 }
 #else
 # define IMPL_TYPED_ARRAY_STATICS(_typedArray)                                     \
 const JSFunctionSpec _typedArray##Object::jsfuncs[] = {                            \
+    JS_SELF_HOSTED_FN("@@iterator", "ArrayIterator", 0, 0),                        \
     JS_FN("iterator", JS_ArrayIterator, 0, 0),                                     \
     JS_FN("subarray", _typedArray##Object::fun_subarray, 2, JSFUN_GENERIC_NATIVE), \
     JS_FN("set", _typedArray##Object::fun_set, 2, JSFUN_GENERIC_NATIVE),           \
     JS_FS_END                                                                      \
 }
 #endif
 
 #define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Name,NativeType)                                 \
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -17,17 +17,17 @@ namespace js {
  * Bytecode version number. Increment the subtrahend whenever JS bytecode
  * changes incompatibly.
  *
  * This version number is XDR'd near the front of xdr bytecode and
  * aborts deserialization if there is a mismatch between the current
  * and saved versions. If deserialization fails, the data should be
  * invalidated if possible.
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 152);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 153);
 
 class XDRBuffer {
   public:
     XDRBuffer(JSContext *cx)
       : context(cx), base(nullptr), cursor(nullptr), limit(nullptr) { }
 
     JSContext *cx() const {
         return context;