Bug 1119217 - Implement %TypedArray%.prototype.{keys, values, entries}. r=till
authorTom Schuster <evilpies@gmail.com>
Sun, 11 Jan 2015 21:21:35 +0100
changeset 223223 93c6ac70dc60ec1442c97ee4d22648c3551286fe
parent 223222 86f23a66df4a6c5bccac46ffeb1be7801101af61
child 223224 010693d8f889809b9415019f0beb0845bc52b6f7
push id10769
push usercbook@mozilla.com
push dateMon, 12 Jan 2015 14:15:52 +0000
treeherderfx-team@0e9765732906 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1119217
milestone37.0a1
Bug 1119217 - Implement %TypedArray%.prototype.{keys, values, entries}. r=till
js/src/builtin/TypedArray.js
js/src/tests/ecma_6/TypedArray/entries.js
js/src/tests/ecma_6/TypedArray/keys.js
js/src/tests/ecma_6/TypedArray/values.js
js/src/vm/CommonPropertyNames.h
js/src/vm/TypedArrayObject.cpp
js/xpconnect/tests/chrome/test_xrayToJS.xul
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -1,12 +1,28 @@
 /* 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/. */
 
+// ES6 draft rev30 (2014/12/24) 22.2.3.6 %TypedArray%.prototype.entries()
+function TypedArrayEntries() {
+    // Step 1.
+    var O = this;
+
+    // Step 2-3.
+    if (!IsObject(O) || !IsTypedArray(O)) {
+        return callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayEntries");
+    }
+
+    // Step 4-6. Bug 1101256: detachment checks
+
+    // Step 7.
+    return CreateArrayIterator(O, ITEM_KIND_KEY_AND_VALUE);
+}
+
 // ES6 draft rev30 (2014/12/24) 22.2.3.7 %TypedArray%.prototype.every(callbackfn[, thisArg]).
 function TypedArrayEvery(callbackfn, thisArg = undefined) {
     // This function is not generic.
     if (!IsObject(this) || !IsTypedArray(this)) {
         return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, thisArg,
                             "TypedArrayEvery");
     }
 
@@ -246,16 +262,32 @@ function TypedArrayJoin(separator) {
         // Step 13.e.
         R = S + next;
     }
 
     // Step 14.
     return R;
 }
 
+// ES6 draft rev30 (2014/12/24) 22.2.3.15 %TypedArray%.prototype.keys()
+function TypedArrayKeys() {
+    // Step 1.
+    var O = this;
+
+    // Step 2-3.
+    if (!IsObject(O) || !IsTypedArray(O)) {
+        return callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayKeys");
+    }
+
+    // Step 4-6. Bug 1101256: detachment checks
+
+    // Step 7.
+    return CreateArrayIterator(O, ITEM_KIND_KEY);
+}
+
 // ES6 draft rev29 (2014/12/06) 22.2.3.16 %TypedArray%.prototype.lastIndexOf(searchElement [,fromIndex]).
 function TypedArrayLastIndexOf(searchElement, fromIndex = undefined) {
     // This function is not generic.
     if (!IsObject(this) || !IsTypedArray(this)) {
         return callFunction(CallTypedArrayMethodIfWrapped, this, searchElement, fromIndex,
                             "TypedArrayLastIndexOf");
     }
 
@@ -438,16 +470,32 @@ function TypedArraySome(callbackfn, this
         if (testResult)
             return true;
     }
 
     // Step 10.
     return false;
 }
 
+// ES6 draft rev30 (2014/12/24) 22.2.3.30 %TypedArray%.prototype.values()
+function TypedArrayValues() {
+    // Step 1.
+    var O = this;
+
+    // Step 2-3.
+    if (!IsObject(O) || !IsTypedArray(O)) {
+        return callFunction(CallTypedArrayMethodIfWrapped, O, "TypedArrayValues");
+    }
+
+    // Step 4-6. Bug 1101256: detachment checks
+
+    // Step 7.
+    return CreateArrayIterator(O, ITEM_KIND_VALUE);
+}
+
 // Proposed for ES7:
 // https://github.com/tc39/Array.prototype.includes/blob/7c023c19a0/spec.md
 function TypedArrayIncludes(searchElement, fromIndex = 0) {
     // This function is not generic.
     if (!IsObject(this) || !IsTypedArray(this)) {
         return callFunction(CallTypedArrayMethodIfWrapped, this, searchElement,
                             fromIndex, "TypedArrayIncludes");
     }
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/entries.js
@@ -0,0 +1,49 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    assertEq(constructor.prototype.entries.length, 0);
+    assertEq(constructor.prototype.entries.name, "entries");
+
+    assertDeepEq([...new constructor(0).entries()], []);
+    assertDeepEq([...new constructor(1).entries()], [[0, 0]]);
+    assertDeepEq([...new constructor(2).entries()], [[0, 0], [1, 0]]);
+    assertDeepEq([...new constructor([15]).entries()], [[0, 15]]);
+
+    var arr = new constructor([1, 2, 3]);
+    var iterator = arr.entries();
+    assertDeepEq(iterator.next(), {value: [0, 1], done: false});
+    assertDeepEq(iterator.next(), {value: [1, 2], done: false});
+    assertDeepEq(iterator.next(), {value: [2, 3], done: false});
+    assertDeepEq(iterator.next(), {value: undefined, done: true});
+
+    // Called from other globals.
+    if (typeof newGlobal === "function") {
+        var entries = newGlobal()[constructor.name].prototype.entries;
+        assertDeepEq([...entries.call(new constructor(2))], [[0, 0], [1, 0]]);
+        arr = newGlobal()[constructor.name](2);
+        assertEq([...constructor.prototype.entries.call(arr)].toString(), "0,0,1,0");
+    }
+
+    // Throws if `this` isn't a TypedArray.
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    invalidReceivers.forEach(invalidReceiver => {
+        assertThrowsInstanceOf(() => {
+            constructor.prototype.entries.call(invalidReceiver);
+        }, TypeError, "Assert that entries fails if this value is not a TypedArray");
+    });
+    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
+    constructor.prototype.entries.call(new Proxy(new constructor(), {}));
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/keys.js
@@ -0,0 +1,49 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    assertEq(constructor.prototype.keys.length, 0);
+    assertEq(constructor.prototype.keys.name, "keys");
+
+    assertDeepEq([...new constructor(0).keys()], []);
+    assertDeepEq([...new constructor(1).keys()], [0]);
+    assertDeepEq([...new constructor(2).keys()], [0, 1]);
+    assertDeepEq([...new constructor([15]).keys()], [0]);
+
+    var arr = new constructor([1, 2, 3]);
+    var iterator = arr.keys();
+    assertDeepEq(iterator.next(), {value: 0, done: false});
+    assertDeepEq(iterator.next(), {value: 1, done: false});
+    assertDeepEq(iterator.next(), {value: 2, done: false});
+    assertDeepEq(iterator.next(), {value: undefined, done: true});
+
+    // Called from other globals.
+    if (typeof newGlobal === "function") {
+        var keys = newGlobal()[constructor.name].prototype.keys;
+        assertDeepEq([...keys.call(new constructor(2))], [0, 1]);
+        arr = newGlobal()[constructor.name](2);
+        assertEq([...constructor.prototype.keys.call(arr)].toString(), "0,1");
+    }
+
+    // Throws if `this` isn't a TypedArray.
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    invalidReceivers.forEach(invalidReceiver => {
+        assertThrowsInstanceOf(() => {
+            constructor.prototype.keys.call(invalidReceiver);
+        }, TypeError, "Assert that keys fails if this value is not a TypedArray");
+    });
+    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
+    constructor.prototype.keys.call(new Proxy(new constructor(), {}));
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/values.js
@@ -0,0 +1,50 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    assertEq(constructor.prototype.values.length, 0);
+    assertEq(constructor.prototype.values.name, "values");
+    assertEq(constructor.prototype.values, constructor.prototype[Symbol.iterator]);
+
+    assertDeepEq([...new constructor(0).values()], []);
+    assertDeepEq([...new constructor(1).values()], [0]);
+    assertDeepEq([...new constructor(2).values()], [0, 0]);
+    assertDeepEq([...new constructor([15]).values()], [15]);
+
+    var arr = new constructor([1, 2, 3]);
+    var iterator = arr.values();
+    assertDeepEq(iterator.next(), {value: 1, done: false});
+    assertDeepEq(iterator.next(), {value: 2, done: false});
+    assertDeepEq(iterator.next(), {value: 3, done: false});
+    assertDeepEq(iterator.next(), {value: undefined, done: true});
+
+    // Called from other globals.
+    if (typeof newGlobal === "function") {
+        var values = newGlobal()[constructor.name].prototype.values;
+        assertDeepEq([...values.call(new constructor([42, 36]))], [42, 36]);
+        arr = newGlobal()[constructor.name]([42, 36]);
+        assertEq([...constructor.prototype.values.call(arr)].toString(), "42,36");
+    }
+
+    // Throws if `this` isn't a TypedArray.
+    var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./];
+    invalidReceivers.forEach(invalidReceiver => {
+        assertThrowsInstanceOf(() => {
+            constructor.prototype.values.call(invalidReceiver);
+        }, TypeError, "Assert that values fails if this value is not a TypedArray");
+    });
+    // FIXME: Should throw exception if `this` is a proxy, see bug 1115361.
+    constructor.prototype.values.call(new Proxy(new constructor(), {}));
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -206,16 +206,17 @@
     macro(unsized, unsized, "unsized") \
     macro(unwatch, unwatch, "unwatch") \
     macro(url, url, "url") \
     macro(usage, usage, "usage") \
     macro(useGrouping, useGrouping, "useGrouping") \
     macro(useAsm, useAsm, "use asm") \
     macro(useStrict, useStrict, "use strict") \
     macro(value, value, "value") \
+    macro(values, values, "values") \
     macro(valueOf, valueOf, "valueOf") \
     macro(var, var, "var") \
     macro(variable, variable, "variable") \
     macro(void0, void0, "(void 0)") \
     macro(watch, watch, "watch") \
     macro(WeakSet_add, WeakSet_add, "WeakSet_add") \
     macro(writable, writable, "writable") \
     macro(w, w, "w") \
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -254,17 +254,16 @@ class TypedArrayObjectTemplate : public 
 
         RootedFunction fun(cx);
         fun = NewFunction(cx, NullPtr(), ArrayBufferObject::createTypedArrayFromBuffer<NativeType>,
                           0, JSFunction::NATIVE_FUN, cx->global(), NullPtr());
         if (!fun)
             return false;
 
         cx->global()->setCreateArrayFromBuffer<NativeType>(fun);
-
         return true;
     }
 
     static inline const Class *instanceClass()
     {
         return TypedArrayObject::classForType(ArrayTypeID());
     }
 
@@ -690,16 +689,44 @@ TypedArrayObjectTemplate<T>::fromArray(J
 
 bool
 TypedArrayConstructor(JSContext *cx, unsigned argc, Value *vp)
 {
     JS_ReportError(cx, "%%TypedArray%% calling/constructing not implemented yet");
     return false;
 }
 
+static bool
+FinishTypedArrayInit(JSContext *cx, HandleObject ctor, HandleObject proto)
+{
+    // Define `values` and `@@iterator` manually, because they are supposed to be the same object.
+    RootedId name(cx, NameToId(cx->names().values));
+    RootedFunction fun(cx, GetSelfHostedFunction(cx, "TypedArrayValues", name, 0));
+    if (!fun)
+        return false;
+
+    RootedValue funValue(cx, ObjectValue(*fun));
+    if (!JSObject::defineProperty(cx, proto, cx->names().values, funValue, nullptr, nullptr, 0))
+        return false;
+
+#ifdef JS_HAS_SYMBOLS
+    RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
+    if (!JSObject::defineGeneric(cx, proto, iteratorId, funValue, nullptr, nullptr, 0))
+        return false;
+#else
+    if (!JSObject::defineProperty(cx, proto, cx->names().std_iterator, funValue, nullptr,
+                                  nullptr, 0))
+    {
+        return false;
+    }
+#endif
+
+    return true;
+}
+
 /*
  * These next 3 functions are brought to you by the buggy GCC we use to build
  * B2G ICS. Older GCC versions have a bug in which they fail to compile
  * reinterpret_casts of templated functions with the message: "insufficient
  * contextual information to determine type". JS_PSG needs to
  * reinterpret_cast<JSPropertyOp>, so this causes problems for us here.
  *
  * We could restructure all this code to make this nicer, but since ICS isn't
@@ -774,31 +801,35 @@ TypedArrayObject::subarray(JSContext *cx
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<TypedArrayObject::is,
                                 TypedArrayMethods<TypedArrayObject>::subarray>(cx, args);
 }
 
 /* static */ const JSFunctionSpec
 TypedArrayObject::protoFunctions[] = {
-    JS_SELF_HOSTED_SYM_FN(iterator, "ArrayValues", 0, 0),                          \
     JS_FN("subarray", TypedArrayObject::subarray, 2, 0),
     JS_FN("set", TypedArrayObject::set, 2, 0),
     JS_FN("copyWithin", TypedArrayObject::copyWithin, 2, 0),
     JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 2, 0),
     JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0),
     JS_SELF_HOSTED_FN("find", "TypedArrayFind", 2, 0),
     JS_SELF_HOSTED_FN("findIndex", "TypedArrayFindIndex", 2, 0),
     JS_SELF_HOSTED_FN("indexOf", "TypedArrayIndexOf", 2, 0),
     JS_SELF_HOSTED_FN("join", "TypedArrayJoin", 1, 0),
     JS_SELF_HOSTED_FN("lastIndexOf", "TypedArrayLastIndexOf", 2, 0),
     JS_SELF_HOSTED_FN("reduce", "TypedArrayReduce", 1, 0),
     JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0),
     JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0),
     JS_SELF_HOSTED_FN("some", "TypedArraySome", 2, 0),
+    JS_SELF_HOSTED_FN("entries", "TypedArrayEntries", 0, 0),
+    JS_SELF_HOSTED_FN("keys", "TypedArrayKeys", 0, 0),
+    // Both of these are actually defined to the same object in FinishTypedArrayInit.
+    JS_SELF_HOSTED_FN("values", "TypedArrayValues", 0, JSPROP_DEFINE_LATE),
+    JS_SELF_HOSTED_SYM_FN(iterator, "TypedArrayValues", 0, JSPROP_DEFINE_LATE),
 #ifdef NIGHTLY_BUILD
     JS_SELF_HOSTED_FN("includes", "TypedArrayIncludes", 2, 0),
 #endif
     JS_FS_END
 };
 
 /* static */ const JSFunctionSpec
 TypedArrayObject::staticFunctions[] = {
@@ -829,17 +860,17 @@ TypedArrayObject::sharedTypedArrayProtot
     nullptr,                /* construct */
     nullptr,                /* trace */
     {
         GenericCreateConstructor<TypedArrayConstructor, 3, JSFunction::FinalizeKind>,
         GenericCreatePrototype,
         TypedArrayObject::staticFunctions,
         TypedArrayObject::protoFunctions,
         TypedArrayObject::protoAccessors,
-        nullptr,
+        FinishTypedArrayInit,
         ClassSpec::DontDefineConstructor
     }
 };
 
 template<typename T>
 bool
 ArrayBufferObject::createTypedArrayFromBufferImpl(JSContext *cx, CallArgs args)
 {
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -174,17 +174,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     gPrototypeProperties['Array'].push('includes');
   }
   for (var c of typedArrayClasses) {
     gPrototypeProperties[c] = ["constructor", "BYTES_PER_ELEMENT"];
   }
   gPrototypeProperties['TypedArray'] =
     ["length", "buffer", "byteLength", "byteOffset", kIteratorSymbol, "subarray",
      "set", "copyWithin", "find", "findIndex", "indexOf", "lastIndexOf", "reverse",
-     "join", "every", "some", "reduce", "reduceRight"];
+     "join", "every", "some", "reduce", "reduceRight", "entries", "keys", "values"];
   if (isNightlyBuild) {
     gPrototypeProperties['TypedArray'].push('includes');
   }
   for (var c of errorObjectClasses) {
       gPrototypeProperties[c] = ["constructor", "name",
                                  // We don't actually resolve these empty data properties
                                  // onto the Xray prototypes, but we list them here to make
                                  // the test happy.