Bug 725907 - for-of improvements, part 2: Make for-of loops just call .iterator() instead of using the magic iteratorObject hook with an extra flag. r=bhackett.
☠☠ backed out by aadf6091245b ☠ ☠
authorJason Orendorff <jorendorff@mozilla.com>
Tue, 03 Jul 2012 16:34:56 -0500
changeset 98224 cb49c3730a9788ba010ef7a64d9db629bc436dbe
parent 98223 3a488b71b69a7586cb506510a2535f559e7db64b
child 98225 4313740f1adc9ae35a66ed4a9585800ee24f4d77
push id11424
push userjorendorff@mozilla.com
push dateTue, 03 Jul 2012 22:31:42 +0000
treeherdermozilla-inbound@24feaa8bd894 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs725907
milestone16.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 725907 - for-of improvements, part 2: Make for-of loops just call .iterator() instead of using the magic iteratorObject hook with an extra flag. r=bhackett.
js/src/jit-test/tests/collections/Map-constructor-4.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-gc.js
js/src/jit-test/tests/for-of/array-iterator-surfaces-1.js
js/src/jit-test/tests/for-of/non-iterable.js
js/src/jit-test/tests/for-of/proxy-1.js
js/src/jit-test/tests/for-of/proxy-2.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/jsapi.cpp
js/src/jsapi.h
js/src/jsarray.cpp
js/src/jsatom.tbl
js/src/jsinfer.cpp
js/src/jsiter.cpp
js/src/jsiter.h
js/src/jsobj.cpp
js/src/jsstr.cpp
js/src/jstypedarray.cpp
js/src/vm/ArgumentsObject.cpp
--- a/js/src/jit-test/tests/collections/Map-constructor-4.js
+++ b/js/src/jit-test/tests/collections/Map-constructor-4.js
@@ -1,6 +1,6 @@
 // Map(x) throws if x is not iterable (unless x is undefined).
 
 load(libdir + "asserts.js");
-var nonIterables = [null, true, 1, -0, 3.14, NaN, "", "xyzzy", {}, Math, this];
+var nonIterables = [null, true, 1, -0, 3.14, NaN, {}, Math, this];
 for (let k of nonIterables)
     assertThrowsInstanceOf(function () { Map(k); }, TypeError);
--- a/js/src/jit-test/tests/for-of/arguments-1.js
+++ b/js/src/jit-test/tests/for-of/arguments-1.js
@@ -1,10 +1,14 @@
 // for-of can iterate arguments objects.
 
+// Arguments objects do not have a .iterator() method by default.
+// Install one on Object.prototype.
+Object.prototype.iterator = Array.prototype.iterator;
+
 var s;
 function test() {
     for (var v of arguments)
         s += v;
 }
 
 s = '';
 test();
--- a/js/src/jit-test/tests/for-of/arguments-2.js
+++ b/js/src/jit-test/tests/for-of/arguments-2.js
@@ -1,11 +1,12 @@
 // for-of can iterate arguments objects after returning.
 
 function f() {
     return arguments;
 }
 
 var s = '';
 var args = f('a', 'b', 'c');
+Object.prototype.iterator = Array.prototype.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,10 +1,12 @@
 // for-of can iterate strict arguments objects.
 
+Object.prototype.iterator = Array.prototype.iterator;
+
 var s;
 function test() {
     "use strict";
     for (var v of arguments)
         s += v;
 }
 
 s = '';
--- a/js/src/jit-test/tests/for-of/arguments-4.js
+++ b/js/src/jit-test/tests/for-of/arguments-4.js
@@ -1,10 +1,12 @@
 // for-of can iterate arguments objects for other active frames.
 
+Object.prototype.iterator = Array.prototype.iterator;
+
 var s;
 function g(obj) {
     for (var v of obj)
         s += v;
 }
 
 function f() {
     g(arguments);
--- a/js/src/jit-test/tests/for-of/arguments-5.js
+++ b/js/src/jit-test/tests/for-of/arguments-5.js
@@ -1,10 +1,12 @@
 // for-of can iterate strict arguments objects in non-strict code.
 
+Object.prototype.iterator = Array.prototype.iterator;
+
 var s;
 function g(obj) {
     for (var v of obj)
         s += v;
 }
 
 function f() {
     "use strict";
--- a/js/src/jit-test/tests/for-of/arguments-6.js
+++ b/js/src/jit-test/tests/for-of/arguments-6.js
@@ -1,10 +1,12 @@
 // Changing arguments.length affects a for-of loop iterating over arguments.
 
+Object.prototype.iterator = Array.prototype.iterator;
+
 var s;
 function f() {
     arguments.length = 2;
     for (var v of arguments)
         s += v;
 }
 
 s = '';
--- a/js/src/jit-test/tests/for-of/arguments-7.js
+++ b/js/src/jit-test/tests/for-of/arguments-7.js
@@ -1,10 +1,12 @@
 // Changing arguments.length during a for-of loop iterating over arguments affects the loop.
 
+Object.prototype.iterator = Array.prototype.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,10 +1,11 @@
 // for-of on an Array consults the prototype chain when it encounters a hole.
 
 var m = {1: 'peek'};
 var a = [0, , 2, 3];
 a.__proto__ = m;
 var log = [];
+Object.prototype.iterator = Array.prototype.iterator;
 for (var x of a)
     log.push(x);
 assertEq(log[1], 'peek');
 assertEq(log.join(), "0,peek,2,3");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/array-iterator-gc.js
@@ -0,0 +1,12 @@
+// Array iterators keep the underlying array, arraylike object, or string alive.
+
+load(libdir + "referencesVia.js");
+
+function test(obj) {
+    var it = Array.prototype.iterator.call(obj);
+    assertEq(referencesVia(it, "**UNKNOWN SLOT 0**", obj), true);
+}
+
+test([]);
+test([1, 2, 3, 4]);
+test({});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/array-iterator-surfaces-1.js
@@ -0,0 +1,10 @@
+// Superficial tests of the Array.prototype.iterator builtin function and its workalikes.
+
+var constructors = [Array, String, Uint8Array, Uint8ClampedArray];
+for (var c of constructors) {
+    assertEq(c.prototype.iterator.length, 0);
+    var desc = Object.getOwnPropertyDescriptor(c.prototype, "iterator");
+    assertEq(desc.configurable, true);
+    assertEq(desc.enumerable, false);
+    assertEq(desc.writable, true);
+}
--- a/js/src/jit-test/tests/for-of/non-iterable.js
+++ b/js/src/jit-test/tests/for-of/non-iterable.js
@@ -1,19 +1,21 @@
 // Iterating over non-iterable values throws a TypeError.
 
 load(libdir + "asserts.js");
 
+function argsobj() { return arguments; }
+
 var misc = [
     {}, {x: 1}, Math, isNaN,
     Object.create(null),
-    Object.create(Array.prototype),
+    argsobj(0, 1, 2),
     null, undefined,
-    true, 0, 3.1416, "", "ponies",
-    new Boolean(true), new Number(0), new String("ponies")];
+    true, 0, 3.1416,
+    new Boolean(true), new Number(0)];
 
 for (var i = 0; i < misc.length; i++) {
     let v = misc[i];
     var testfn = function () {
         for (var _ of v)
             throw 'FAIL';
         throw 'BAD';
     };
--- a/js/src/jit-test/tests/for-of/proxy-1.js
+++ b/js/src/jit-test/tests/for-of/proxy-1.js
@@ -1,28 +1,28 @@
 // Basic for-of test with Proxy.
 
-function iter(arr) {
-    var i = 0;
-    return {
-        next: function () {
-            if (i < arr.length)
-                return arr[i++];
-            throw StopIteration;
+function iterableProxy(arr) {
+    return Proxy.create({
+        getPropertyDescriptor: function (name) {
+            for (var obj = arr; obj; obj = Object.getPrototypeOf(obj)) {
+                var desc = Object.getOwnPropertyDescriptor(obj, name);
+                if (desc)
+                    return desc;
+            }
+            return undefined;
         }
-    };
-}
-
-function iterableProxy(arr) {
-    return Proxy.create({iterate: function () { return iter(arr); }});
+    });
 }
 
 var s = '';
 var arr = ['a', 'b', 'c', 'd'];
 var p = iterableProxy(arr);
 
-// Test the same proxy twice. Its iterate method should be called each time.
+// Test the same proxy twice. Each time through the loop, the proxy handler's
+// getPropertyDescriptor method will be called 10 times (once for 'iterator',
+// five times for 'length', and once for each of the four elements).
 for (var i = 0; i < 2; i++) {
     var j = 0;
     for (var x of p)
         assertEq(x, arr[j++]);
     assertEq(j, arr.length);
 }
--- a/js/src/jit-test/tests/for-of/proxy-2.js
+++ b/js/src/jit-test/tests/for-of/proxy-2.js
@@ -1,12 +1,29 @@
-// Basic for-of test with Proxy whose iterate method is a generator.
+// Basic for-of test with Proxy whose iterator method is a generator.
 
 var arr = ['a', 'b', 'c', 'd'];
 var proxy = Proxy.create({
-    iterate: function () {
-        for (var i = 0; i < arr.length; i++)
-            yield arr[i];
+    getPropertyDescriptor: function (name) {
+        if (name == 'iterator') {
+            return {
+                configurable: false,
+                enumerable: false,
+                writeable: false,
+                value:  function () {
+                    for (var i = 0; i < arr.length; i++)
+                        yield arr[i];
+                }
+            };
+        }
+
+        // Otherwise, inherit the property from arr.
+        for (var obj = arr; obj; obj = Object.getPrototypeOf(obj)) {
+            var desc = Object.getOwnPropertyDescriptor(obj, name);
+            if (desc)
+                return desc;
+        }
+        return undefined;
     }
 });
 
 for (var i = 0; i < 2; i++)
     assertEq([v for (v of proxy)].join(","), "a,b,c,d");
--- a/js/src/jit-test/tests/for-of/proxy-3.js
+++ b/js/src/jit-test/tests/for-of/proxy-3.js
@@ -1,6 +1,12 @@
-// An exception thrown from an iterate trap is propagated.
+// An exception thrown from a proxy trap while getting the .iterator method is propagated.
 
 load(libdir + "asserts.js");
 
-var p = Proxy.create({iterate: function () { throw "fit"; }});
+var p = Proxy.create({
+    getPropertyDescriptor: function (name) {
+        if (name == "iterator")
+            throw "fit";
+        return undefined;
+    }
+});
 assertThrowsValue(function () { for (var v of p) {} }, "fit");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/semantics-01.js
@@ -0,0 +1,11 @@
+// for-of is defined in terms of basic operations on objects, particularly
+// [[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;
+assertThrowsInstanceOf(function () { for (var x of []) ; }, TypeError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/semantics-02.js
@@ -0,0 +1,10 @@
+// Replacing Array.prototype.iterator with something non-callable makes for-of throw.
+
+load(libdir + "asserts.js");
+function test(v) {
+    Array.prototype.iterator = v;
+    assertThrowsInstanceOf(function () { for (var x of []) ; }, TypeError);
+}
+test(undefined);
+test(null);
+test({});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/semantics-03.js
@@ -0,0 +1,11 @@
+// Replacing Array.prototype.iterator with a generator affects for-of behavior.
+
+Array.prototype.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');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/semantics-04.js
@@ -0,0 +1,15 @@
+// Giving an Array an own .iterator property affects for-of.
+
+var a = [];
+a.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;
+assertThrowsInstanceOf(function () { for (var v of a) ; }, TypeError);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/for-of/semantics-05.js
@@ -0,0 +1,6 @@
+// Deleting String.prototype.iterator makes for-of stop working on strings.
+
+load(libdir + "asserts.js");
+delete String.prototype.iterator;
+assertThrowsInstanceOf(function () { for (var v of "abc") ; }, TypeError);
+assertThrowsInstanceOf(function () { for (var v of new String("abc")) ; }, TypeError);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4459,32 +4459,29 @@ JS_NextProperty(JSContext *cx, JSObject 
         } else {
             *idp = ida->vector[--i];
             iterobj->setSlot(JSSLOT_ITER_INDEX, Int32Value(i));
         }
     }
     return JS_TRUE;
 }
 
-JS_PUBLIC_API(JSObject *)
-JS_NewElementIterator(JSContext *cx, JSObject *obj_)
-{
-    AssertNoGC(cx);
-    CHECK_REQUEST(cx);
-    assertSameCompartment(cx, obj_);
-
-    Rooted<JSObject*> obj(cx, obj_);
-    return ElementIteratorObject::create(cx, obj);
-}
-
-JS_PUBLIC_API(JSObject *)
-JS_ElementIteratorStub(JSContext *cx, JSHandleObject obj, JSBool keysonly)
-{
-    JS_ASSERT(!keysonly);
-    return JS_NewElementIterator(cx, obj);
+JS_PUBLIC_API(JSBool)
+JS_ArrayIterator(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    JSObject *target = NonNullObject(cx, args.thisv());
+    if (!target)
+        return false;
+    Rooted<JSObject*> iterobj(cx, target);
+    iterobj = ElementIteratorObject::create(cx, iterobj);
+    if (!iterobj)
+        return false;
+    vp->setObject(*iterobj);
+    return true;
 }
 
 JS_PUBLIC_API(jsval)
 JS_GetReservedSlot(JSObject *obj, uint32_t index)
 {
     return obj->getReservedSlot(index);
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3747,36 +3747,30 @@ struct JSClass {
                                          << JSCLASS_RESERVED_SLOTS_SHIFT)
 #define JSCLASS_RESERVED_SLOTS(clasp)   (((clasp)->flags                      \
                                           >> JSCLASS_RESERVED_SLOTS_SHIFT)    \
                                          & JSCLASS_RESERVED_SLOTS_MASK)
 
 #define JSCLASS_HIGH_FLAGS_SHIFT        (JSCLASS_RESERVED_SLOTS_SHIFT +       \
                                          JSCLASS_RESERVED_SLOTS_WIDTH)
 
-/*
- * Call the iteratorObject hook only to iterate over contents (for-of), not to
- * enumerate properties (for-in, for-each, Object.keys, etc.)
- */
-#define JSCLASS_FOR_OF_ITERATION        (1<<(JSCLASS_HIGH_FLAGS_SHIFT+0))
-
-#define JSCLASS_IS_ANONYMOUS            (1<<(JSCLASS_HIGH_FLAGS_SHIFT+1))
-#define JSCLASS_IS_GLOBAL               (1<<(JSCLASS_HIGH_FLAGS_SHIFT+2))
-#define JSCLASS_INTERNAL_FLAG2          (1<<(JSCLASS_HIGH_FLAGS_SHIFT+3))
-#define JSCLASS_INTERNAL_FLAG3          (1<<(JSCLASS_HIGH_FLAGS_SHIFT+4))
+#define JSCLASS_IS_ANONYMOUS            (1<<(JSCLASS_HIGH_FLAGS_SHIFT+0))
+#define JSCLASS_IS_GLOBAL               (1<<(JSCLASS_HIGH_FLAGS_SHIFT+1))
+#define JSCLASS_INTERNAL_FLAG2          (1<<(JSCLASS_HIGH_FLAGS_SHIFT+2))
+#define JSCLASS_INTERNAL_FLAG3          (1<<(JSCLASS_HIGH_FLAGS_SHIFT+3))
 
 /* Indicate whether the proto or ctor should be frozen. */
-#define JSCLASS_FREEZE_PROTO            (1<<(JSCLASS_HIGH_FLAGS_SHIFT+5))
-#define JSCLASS_FREEZE_CTOR             (1<<(JSCLASS_HIGH_FLAGS_SHIFT+6))
-
-#define JSCLASS_XPCONNECT_GLOBAL        (1<<(JSCLASS_HIGH_FLAGS_SHIFT+7))
+#define JSCLASS_FREEZE_PROTO            (1<<(JSCLASS_HIGH_FLAGS_SHIFT+4))
+#define JSCLASS_FREEZE_CTOR             (1<<(JSCLASS_HIGH_FLAGS_SHIFT+5))
+
+#define JSCLASS_XPCONNECT_GLOBAL        (1<<(JSCLASS_HIGH_FLAGS_SHIFT+6))
 
 /* Reserved for embeddings. */
-#define JSCLASS_USERBIT2                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+8))
-#define JSCLASS_USERBIT3                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+9))
+#define JSCLASS_USERBIT2                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+7))
+#define JSCLASS_USERBIT3                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+8))
 
 /*
  * Bits 26 through 31 are reserved for the CACHED_PROTO_KEY mechanism, see
  * below.
  */
 
 /* Global flags. */
 #define JSGLOBAL_FLAGS_CLEARED          0x1
@@ -4354,29 +4348,23 @@ JS_NewPropertyIterator(JSContext *cx, JS
  * Return true on success with *idp containing the id of the next enumerable
  * property to visit using iterobj, or JSID_IS_VOID if there is no such property
  * left to visit.  Return false on error.
  */
 extern JS_PUBLIC_API(JSBool)
 JS_NextProperty(JSContext *cx, JSObject *iterobj, jsid *idp);
 
 /*
- * Create an object to iterate over the elements of obj in for-of order. This
- * can be used to implement the iteratorObject hook for an array-like Class.
- */
-extern JS_PUBLIC_API(JSObject *)
-JS_NewElementIterator(JSContext *cx, JSObject *obj);
-
-/*
- * To make your array-like class iterable using the for-of loop, set the
- * JSCLASS_FOR_OF_ITERATION bit in the class's flags field and set its
- * .ext.iteratorObject hook to this function.
- */
-extern JS_PUBLIC_API(JSObject *)
-JS_ElementIteratorStub(JSContext *cx, JSHandleObject obj, JSBool keysonly);
+ * A JSNative that creates and returns a new iterator that iterates over the
+ * elements of |this|, up to |this.length|, in index order. This can be used to
+ * make any array-like object iterable. Just give the object an obj.iterator()
+ * method using this JSNative as the implementation.
+ */
+extern JS_PUBLIC_API(JSBool)
+JS_ArrayIterator(JSContext *cx, unsigned argc, jsval *vp);
 
 extern JS_PUBLIC_API(JSBool)
 JS_CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode,
                jsval *vp, unsigned *attrsp);
 
 extern JS_PUBLIC_API(jsval)
 JS_GetReservedSlot(JSObject *obj, uint32_t index);
 
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -1185,17 +1185,17 @@ array_trace(JSTracer *trc, JSObject *obj
     JS_ASSERT(obj->isDenseArray());
 
     uint32_t initLength = obj->getDenseArrayInitializedLength();
     MarkArraySlots(trc, initLength, obj->getDenseArrayElements(), "element");
 }
 
 Class js::ArrayClass = {
     "Array",
-    Class::NON_NATIVE | JSCLASS_HAS_CACHED_PROTO(JSProto_Array) | JSCLASS_FOR_OF_ITERATION,
+    Class::NON_NATIVE | JSCLASS_HAS_CACHED_PROTO(JSProto_Array),
     JS_PropertyStub,         /* addProperty */
     JS_PropertyStub,         /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     NULL,
@@ -1203,17 +1203,17 @@ Class js::ArrayClass = {
     NULL,           /* call        */
     NULL,           /* construct   */
     NULL,           /* hasInstance */
     array_trace,    /* trace       */
     {
         NULL,       /* equality    */
         NULL,       /* outerObject */
         NULL,       /* innerObject */
-        JS_ElementIteratorStub,
+        NULL,       /* iteratorObject  */
         NULL,       /* unused      */
         false,      /* isWrappedNative */
     },
     {
         array_lookupGeneric,
         array_lookupProperty,
         array_lookupElement,
         array_lookupSpecial,
@@ -1246,17 +1246,17 @@ Class js::ArrayClass = {
         array_typeOf,
         NULL,       /* thisObject     */
         NULL,       /* clear          */
     }
 };
 
 Class js::SlowArrayClass = {
     "Array",
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Array) | JSCLASS_FOR_OF_ITERATION,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Array),
     slowarray_addProperty,
     JS_PropertyStub,         /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     NULL,
@@ -1264,17 +1264,17 @@ Class js::SlowArrayClass = {
     NULL,           /* call        */
     NULL,           /* construct   */
     NULL,           /* hasInstance */
     NULL,           /* trace       */
     {
         NULL,       /* equality    */
         NULL,       /* outerObject */
         NULL,       /* innerObject */
-        JS_ElementIteratorStub,
+        NULL,       /* iteratorObject  */
         NULL,       /* unused      */
         false,      /* isWrappedNative */
     }
 };
 
 bool
 JSObject::allocateSlowArrayElements(JSContext *cx)
 {
@@ -3616,16 +3616,17 @@ static JSFunctionSpec array_methods[] = 
     JS_FN("forEach",            array_forEach,      1,JSFUN_GENERIC_NATIVE),
     JS_FN("map",                array_map,          1,JSFUN_GENERIC_NATIVE),
     JS_FN("reduce",             array_reduce,       1,JSFUN_GENERIC_NATIVE),
     JS_FN("reduceRight",        array_reduceRight,  1,JSFUN_GENERIC_NATIVE),
     JS_FN("filter",             array_filter,       1,JSFUN_GENERIC_NATIVE),
     JS_FN("some",               array_some,         1,JSFUN_GENERIC_NATIVE),
     JS_FN("every",              array_every,        1,JSFUN_GENERIC_NATIVE),
 
+    JS_FN("iterator",           JS_ArrayIterator,   0,0),
     JS_FS_END
 };
 
 static JSFunctionSpec array_static_methods[] = {
     JS_FN("isArray",            array_isArray,      1,0),
     JS_FS_END
 };
 
--- a/js/src/jsatom.tbl
+++ b/js/src/jsatom.tbl
@@ -48,17 +48,18 @@ DEFINE_ATOM(each, "each")
 DEFINE_ATOM(eval, "eval")
 DEFINE_ATOM(fileName, "fileName")
 DEFINE_ATOM(get, "get")
 DEFINE_ATOM(global, "global")
 DEFINE_ATOM(ignoreCase, "ignoreCase")
 DEFINE_ATOM(index, "index")
 DEFINE_ATOM(input, "input")
 DEFINE_ATOM(toISOString, "toISOString")
-DEFINE_ATOM(iterator, "__iterator__")
+DEFINE_ATOM(iterator, "iterator")
+DEFINE_ATOM(iteratorIntrinsic, "__iterator__")
 DEFINE_ATOM(join, "join")
 DEFINE_ATOM(lastIndex, "lastIndex")
 DEFINE_ATOM(length, "length")
 DEFINE_ATOM(lineNumber, "lineNumber")
 DEFINE_ATOM(message, "message")
 DEFINE_ATOM(multiline, "multiline")
 DEFINE_ATOM(name, "name")
 DEFINE_ATOM(next, "next")
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -3912,20 +3912,20 @@ ScriptAnalysis::analyzeTypesBytecode(JSC
          * 'for each' loops together, oh well.
          */
         if (!state.forTypes) {
           state.forTypes = TypeSet::make(cx, "forTypes");
           if (!state.forTypes)
               return false;
         }
 
-        if (GET_UINT8(pc) & JSITER_FOREACH)
+        if (GET_UINT8(pc) == JSITER_ENUMERATE)
+            state.forTypes->addType(cx, Type::StringType());
+        else
             state.forTypes->addType(cx, Type::UnknownType());
-        else
-            state.forTypes->addType(cx, Type::StringType());
         break;
       }
 
       case JSOP_ITERNEXT:
         state.forTypes->addSubset(cx, &pushed[0]);
         break;
 
       case JSOP_MOREITER:
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -47,18 +47,16 @@
 #include "vm/MethodGuard-inl.h"
 #include "vm/Stack-inl.h"
 #include "vm/String-inl.h"
 
 using namespace mozilla;
 using namespace js;
 using namespace js::gc;
 
-static JSObject *iterator_iterator(JSContext *cx, HandleObject obj, JSBool keysonly);
-
 Class js::ElementIteratorClass = {
     "ElementIterator",
     JSCLASS_HAS_RESERVED_SLOTS(ElementIteratorObject::NumSlots),
     JS_PropertyStub,         /* addProperty */
     JS_PropertyStub,         /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
@@ -69,17 +67,17 @@ Class js::ElementIteratorClass = {
     NULL,                    /* call        */
     NULL,                    /* construct   */
     NULL,                    /* hasInstance */
     NULL,                    /* trace       */
     {
         NULL,                /* equality       */
         NULL,                /* outerObject    */
         NULL,                /* innerObject    */
-        iterator_iterator,
+        NULL,                /* iteratorObject */
         NULL                 /* unused  */
     }
 };
 
 static const gc::AllocKind ITERATOR_FINALIZE_KIND = gc::FINALIZE_OBJECT2;
 
 void
 NativeIterator::mark(JSTracer *trc)
@@ -364,28 +362,18 @@ js::GetPropertyNames(JSContext *cx, JSOb
 
 size_t sCustomIteratorCount = 0;
 
 static inline bool
 GetCustomIterator(JSContext *cx, HandleObject obj, unsigned flags, Value *vp)
 {
     JS_CHECK_RECURSION(cx, return false);
 
-    /*
-     * for-of iteration does not fall back on __iterator__ or property
-     * enumeration. This is more conservative than the current proposed spec.
-     */
-    if (flags == JSITER_FOR_OF) {
-        js_ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_NOT_ITERABLE,
-                                 JSDVG_SEARCH_STACK, ObjectValue(*obj), NULL, NULL, NULL);
-        return false;
-    }
-
     /* Check whether we have a valid __iterator__ method. */
-    PropertyName *name = cx->runtime->atomState.iteratorAtom;
+    PropertyName *name = cx->runtime->atomState.iteratorIntrinsicAtom;
     if (!GetMethod(cx, obj, name, 0, vp))
         return false;
 
     /* If there is no custom __iterator__ method, we are done here. */
     if (!vp->isObject()) {
         vp->setUndefined();
         return true;
     }
@@ -601,40 +589,52 @@ UpdateNativeIterator(NativeIterator *ni,
     // Update the object for which the native iterator is associated, so
     // SuppressDeletedPropertyHelper will recognize the iterator as a match.
     ni->obj = obj;
 }
 
 bool
 GetIterator(JSContext *cx, HandleObject obj, unsigned flags, Value *vp)
 {
+    if (flags == JSITER_FOR_OF) {
+        // for-of loop. The iterator is simply |obj.iterator()|.
+        Value method;
+        if (!obj->getProperty(cx, obj, cx->runtime->atomState.iteratorAtom, &method))
+            return false;
+
+        // 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()) {
+            char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, ObjectOrNullValue(obj), NULL);
+            if (!bytes)
+                return false;
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_ITERABLE, bytes);
+            cx->free_(bytes);
+            return false;
+        }
+
+        if (!Invoke(cx, ObjectOrNullValue(obj), method, 0, NULL, vp))
+            return false;
+        return true;
+    }
+
     Vector<const Shape *, 8> shapes(cx);
     uint32_t key = 0;
 
     bool keysOnly = (flags == JSITER_ENUMERATE);
 
     if (obj) {
-        /* Enumerate Iterator.prototype directly. */
         if (JSIteratorOp op = obj->getClass()->ext.iteratorObject) {
-            /*
-             * Arrays and other classes representing iterable collections have
-             * the JSCLASS_FOR_OF_ITERATION flag. This flag means that the
-             * object responds to all other kinds of enumeration (for-in,
-             * for-each, Object.keys, Object.getOwnPropertyNames, etc.) in the
-             * default way, ignoring the hook. The hook is used only when
-             * iterating in the style of a for-of loop.
-             */
-            if (!(obj->getClass()->flags & JSCLASS_FOR_OF_ITERATION) || flags == JSITER_FOR_OF) {
-                JSObject *iterobj = op(cx, obj, !(flags & (JSITER_FOREACH | JSITER_FOR_OF)));
-                if (!iterobj)
-                    return false;
-                vp->setObject(*iterobj);
-                types::MarkIteratorUnknown(cx);
-                return true;
-            }
+            JSObject *iterobj = op(cx, obj, !(flags & JSITER_FOREACH));
+            if (!iterobj)
+                return false;
+            vp->setObject(*iterobj);
+            types::MarkIteratorUnknown(cx);
+            return true;
         }
 
         if (keysOnly) {
             /*
              * Check to see if this is the same as the most recent object which
              * was iterated over.  We don't explicitly check for shapeless
              * objects here, as they are not inserted into the cache and
              * will result in a miss.
@@ -733,22 +733,16 @@ GetIterator(JSContext *cx, HandleObject 
 
     if (shapes.length() == 2)
         cx->runtime->nativeIterCache.last = iterobj;
     return true;
 }
 
 }
 
-static JSObject *
-iterator_iterator(JSContext *cx, HandleObject obj, JSBool keysonly)
-{
-    return obj;
-}
-
 static JSBool
 Iterator(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() == 0) {
         js_ReportMissingArg(cx, args.calleev(), 0);
         return false;
     }
@@ -771,16 +765,24 @@ js_ThrowStopIteration(JSContext *cx)
 
     JS_ASSERT(!JS_IsExceptionPending(cx));
     if (js_FindClassObject(cx, NULL, JSProto_StopIteration, &v))
         cx->setPendingException(v);
     return JS_FALSE;
 }
 
 static JSBool
+iterator_iterator(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    args.rval() = args.thisv();
+    return true;
+}
+
+static JSBool
 iterator_next(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedObject thisObj(cx);
     if (!NonGenericMethodGuard(cx, args, iterator_next, &PropertyIteratorObject::class_,
                                thisObj.address()))
     {
@@ -795,23 +797,28 @@ iterator_next(JSContext *cx, unsigned ar
     if (!args.rval().toBoolean()) {
         js_ThrowStopIteration(cx);
         return false;
     }
 
     return js_IteratorNext(cx, thisObj, &args.rval());
 }
 
-#define JSPROP_ROPERM   (JSPROP_READONLY | JSPROP_PERMANENT)
-
 static JSFunctionSpec iterator_methods[] = {
-    JS_FN(js_next_str,      iterator_next,  0,JSPROP_ROPERM),
+    JS_FN("iterator",  iterator_iterator,   0, 0),
+    JS_FN("next",      iterator_next,       0, 0),
     JS_FS_END
 };
 
+JSObject *
+iterator_iteratorObject(JSContext *cx, HandleObject obj, JSBool keysonly)
+{
+    return obj;
+}
+
 void
 PropertyIteratorObject::trace(JSTracer *trc, JSObject *obj)
 {
     if (NativeIterator *ni = obj->asPropertyIterator().getNativeIterator())
         ni->mark(trc);
 }
 
 void
@@ -840,31 +847,27 @@ Class PropertyIteratorObject::class_ = {
     NULL,                    /* call        */
     NULL,                    /* construct   */
     NULL,                    /* hasInstance */
     trace,
     {
         NULL,                /* equality       */
         NULL,                /* outerObject    */
         NULL,                /* innerObject    */
-        iterator_iterator,
+        iterator_iteratorObject,
         NULL                 /* unused  */
     }
 };
 
 #if JS_HAS_GENERATORS
 static JSBool
 CloseGenerator(JSContext *cx, JSObject *genobj);
 #endif
 
-/*
- * Call ToObject(v).__iterator__(keyonly) if ToObject(v).__iterator__ exists.
- * Otherwise construct the default iterator.
- */
-JSBool
+bool
 js::ValueToIterator(JSContext *cx, unsigned flags, Value *vp)
 {
     /* JSITER_KEYVALUE must always come with JSITER_FOREACH */
     JS_ASSERT_IF(flags & JSITER_KEYVALUE, flags & JSITER_FOREACH);
 
     /*
      * Make sure the more/next state machine doesn't get stuck. A value might be
      * left in iterValue when a trace is left due to an operation time-out after
@@ -1285,17 +1288,17 @@ js_IteratorNext(JSContext *cx, JSObject 
 static JSBool
 stopiter_hasInstance(JSContext *cx, HandleObject obj, const Value *v, JSBool *bp)
 {
     *bp = IsStopIteration(*v);
     return JS_TRUE;
 }
 
 Class js::StopIterationClass = {
-    js_StopIteration_str,
+    "StopIteration",
     JSCLASS_HAS_CACHED_PROTO(JSProto_StopIteration) |
     JSCLASS_FREEZE_PROTO,
     JS_PropertyStub,         /* addProperty */
     JS_PropertyStub,         /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
@@ -1400,17 +1403,17 @@ Class js::GeneratorClass = {
     NULL,                    /* call        */
     NULL,                    /* construct   */
     NULL,                    /* hasInstance */
     generator_trace,
     {
         NULL,                /* equality       */
         NULL,                /* outerObject    */
         NULL,                /* innerObject    */
-        iterator_iterator,
+        iterator_iteratorObject,
         NULL                 /* unused */
     }
 };
 
 /*
  * Called from the JSOP_GENERATOR case in the interpreter, with fp referring
  * to the frame by which the generator function was activated.  Create a new
  * JSGenerator object, which contains its own StackFrame that we populate
@@ -1683,21 +1686,24 @@ generator_throw(JSContext *cx, unsigned 
 }
 
 static JSBool
 generator_close(JSContext *cx, unsigned argc, Value *vp)
 {
     return generator_op(cx, generator_close, JSGENOP_CLOSE, vp, argc);
 }
 
+#define JSPROP_ROPERM   (JSPROP_READONLY | JSPROP_PERMANENT)
+
 static JSFunctionSpec generator_methods[] = {
-    JS_FN(js_next_str,      generator_next,     0,JSPROP_ROPERM),
-    JS_FN(js_send_str,      generator_send,     1,JSPROP_ROPERM),
-    JS_FN(js_throw_str,     generator_throw,    1,JSPROP_ROPERM),
-    JS_FN(js_close_str,     generator_close,    0,JSPROP_ROPERM),
+    JS_FN("iterator",  iterator_iterator,  0, 0),
+    JS_FN("next",      generator_next,     0,JSPROP_ROPERM),
+    JS_FN("send",      generator_send,     1,JSPROP_ROPERM),
+    JS_FN("throw",     generator_throw,    1,JSPROP_ROPERM),
+    JS_FN("close",     generator_close,    0,JSPROP_ROPERM),
     JS_FS_END
 };
 
 #endif /* JS_HAS_GENERATORS */
 
 static bool
 InitIteratorClass(JSContext *cx, Handle<GlobalObject*> global)
 {
--- a/js/src/jsiter.h
+++ b/js/src/jsiter.h
@@ -165,26 +165,26 @@ bool
 EnumeratedIdVectorToIterator(JSContext *cx, HandleObject obj, unsigned flags, AutoIdVector &props, Value *vp);
 
 /*
  * Convert the value stored in *vp to its iteration object. The flags should
  * contain JSITER_ENUMERATE if js::ValueToIterator is called when enumerating
  * for-in semantics are required, and when the caller can guarantee that the
  * iterator will never be exposed to scripts.
  */
-extern JSBool
+bool
 ValueToIterator(JSContext *cx, unsigned flags, Value *vp);
 
-extern bool
+bool
 CloseIterator(JSContext *cx, JSObject *iterObj);
 
-extern bool
+bool
 UnwindIteratorForException(JSContext *cx, JSObject *obj);
 
-extern void
+void
 UnwindIteratorForUncatchableException(JSContext *cx, JSObject *obj);
 
 }
 
 extern bool
 js_SuppressDeletedProperty(JSContext *cx, js::HandleObject obj, jsid id);
 
 extern bool
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5118,17 +5118,17 @@ js_GetPropertyHelperInline(JSContext *cx
                 (op != JSOP_GETPROP && op != JSOP_GETELEM)) {
                 return JS_TRUE;
             }
 
             /*
              * XXX do not warn about missing __iterator__ as the function
              * may be called from JS_GetMethodById. See bug 355145.
              */
-            if (JSID_IS_ATOM(id, cx->runtime->atomState.iteratorAtom))
+            if (JSID_IS_ATOM(id, cx->runtime->atomState.iteratorIntrinsicAtom))
                 return JS_TRUE;
 
             /* Do not warn about tests like (obj[prop] == undefined). */
             if (cx->resolveFlags == RESOLVE_INFER) {
                 pc += js_CodeSpec[op].length;
                 if (Detecting(cx, pc))
                     return JS_TRUE;
             } else if (cx->resolveFlags & JSRESOLVE_DETECTING) {
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2970,16 +2970,17 @@ static JSFunctionSpec string_methods[] =
     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_FN("iterator",          JS_ArrayIterator,      0,0),
     JS_FS_END
 };
 
 JSBool
 js_String(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
--- a/js/src/jstypedarray.cpp
+++ b/js/src/jstypedarray.cpp
@@ -2877,16 +2877,17 @@ JSFunctionSpec ArrayBufferObject::jsfunc
 };
 
 /*
  * TypedArray boilerplate
  */
 
 #define IMPL_TYPED_ARRAY_STATICS(_typedArray)                                  \
 JSFunctionSpec _typedArray::jsfuncs[] = {                                      \
+    JS_FN("iterator", JS_ArrayIterator, 0, 0),                                 \
     JS_FN("subarray", _typedArray::fun_subarray, 2, JSFUN_GENERIC_NATIVE),     \
     JS_FN("set", _typedArray::fun_set, 2, JSFUN_GENERIC_NATIVE),               \
     JS_FN("move", _typedArray::fun_move, 3, JSFUN_GENERIC_NATIVE),             \
     JS_FS_END                                                                  \
 }
 
 #define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Name,NativeType)                                 \
   JS_FRIEND_API(JSObject *) JS_New ## Name ## Array(JSContext *cx, uint32_t nelements)       \
@@ -2942,17 +2943,16 @@ IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Floa
 }
 
 #define IMPL_TYPED_ARRAY_FAST_CLASS(_typedArray)                               \
 {                                                                              \
     #_typedArray,                                                              \
     JSCLASS_HAS_RESERVED_SLOTS(TypedArray::FIELD_MAX) |                        \
     JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |                        \
     JSCLASS_HAS_CACHED_PROTO(JSProto_##_typedArray) |                          \
-    JSCLASS_FOR_OF_ITERATION |                                                 \
     Class::NON_NATIVE,                                                         \
     JS_PropertyStub,         /* addProperty */                                 \
     JS_PropertyStub,         /* delProperty */                                 \
     JS_PropertyStub,         /* getProperty */                                 \
     JS_StrictPropertyStub,   /* setProperty */                                 \
     JS_EnumerateStub,                                                          \
     JS_ResolveStub,                                                            \
     JS_ConvertStub,                                                            \
@@ -2961,17 +2961,17 @@ IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Floa
     NULL,                    /* call        */                                 \
     NULL,                    /* construct   */                                 \
     NULL,                    /* hasInstance */                                 \
     _typedArray::obj_trace,  /* trace       */                                 \
     {                                                                          \
         NULL,       /* equality    */                                          \
         NULL,       /* outerObject */                                          \
         NULL,       /* innerObject */                                          \
-        JS_ElementIteratorStub,                                                \
+        NULL,       /* iteratorObject  */                                      \
         NULL,       /* unused      */                                          \
         false,      /* isWrappedNative */                                      \
     },                                                                         \
     {                                                                          \
         _typedArray::obj_lookupGeneric,                                        \
         _typedArray::obj_lookupProperty,                                       \
         _typedArray::obj_lookupElement,                                        \
         _typedArray::obj_lookupSpecial,                                        \
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -416,18 +416,17 @@ ArgumentsObject::trace(JSTracer *trc, JS
  * argument values, argument count, and callee function object stored in a
  * StackFrame with their corresponding property values in the frame's
  * arguments object.
  */
 Class js::NormalArgumentsObjectClass = {
     "Arguments",
     JSCLASS_NEW_RESOLVE | JSCLASS_IMPLEMENTS_BARRIERS |
     JSCLASS_HAS_RESERVED_SLOTS(NormalArgumentsObject::RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
-    JSCLASS_FOR_OF_ITERATION,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
     JS_PropertyStub,         /* addProperty */
     args_delProperty,
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     args_enumerate,
     reinterpret_cast<JSResolveOp>(args_resolve),
     JS_ConvertStub,
     ArgumentsObject::finalize,
@@ -435,33 +434,32 @@ Class js::NormalArgumentsObjectClass = {
     NULL,                    /* call        */
     NULL,                    /* construct   */
     NULL,                    /* hasInstance */
     ArgumentsObject::trace,
     {
         NULL,       /* equality    */
         NULL,       /* outerObject */
         NULL,       /* innerObject */
-        JS_ElementIteratorStub,
+        NULL,       /* iteratorObject  */
         NULL,       /* unused      */
         false,      /* isWrappedNative */
     }
 };
 
 /*
  * Strict mode arguments is significantly less magical than non-strict mode
  * arguments, so it is represented by a different class while sharing some
  * functionality.
  */
 Class js::StrictArgumentsObjectClass = {
     "Arguments",
     JSCLASS_NEW_RESOLVE | JSCLASS_IMPLEMENTS_BARRIERS |
     JSCLASS_HAS_RESERVED_SLOTS(StrictArgumentsObject::RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
-    JSCLASS_FOR_OF_ITERATION,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
     JS_PropertyStub,         /* addProperty */
     args_delProperty,
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     strictargs_enumerate,
     reinterpret_cast<JSResolveOp>(strictargs_resolve),
     JS_ConvertStub,
     ArgumentsObject::finalize,
@@ -469,13 +467,13 @@ Class js::StrictArgumentsObjectClass = {
     NULL,                    /* call        */
     NULL,                    /* construct   */
     NULL,                    /* hasInstance */
     ArgumentsObject::trace,
     {
         NULL,       /* equality    */
         NULL,       /* outerObject */
         NULL,       /* innerObject */
-        JS_ElementIteratorStub,
+        NULL,       /* iteratorObject  */
         NULL,       /* unused      */
         false,      /* isWrappedNative */
     }
 };