Bug 1119217 - Implement %TypedArray%.prototype.{keys, values, entries}. r=till
authorTom Schuster <evilpies@gmail.com>
Sun, 11 Jan 2015 21:21:35 +0100
changeset 223165 93c6ac70dc60
parent 223164 86f23a66df4a
child 223166 010693d8f889
push id53842
push userevilpies@gmail.com
push dateSun, 11 Jan 2015 21:43:00 +0000
treeherdermozilla-inbound@93c6ac70dc60 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1119217
milestone37.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 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.