Bug 896608 - Implement ES6 %TypedArray%.{of, from}. r=evilpie, till
authorziyunfei <446240525@qq.com>
Wed, 14 Jan 2015 10:06:00 +0100
changeset 238440 828b434f69f76d2180d28473a200c8ba60eebce9
parent 238439 dd7c16482bbf740206cf0d60f04bf275a9ed8d1c
child 238441 3ed36d0888abeeb125295951260f28ed4a3406c2
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-esr52@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersevilpie, till
bugs896608
milestone38.0a1
Bug 896608 - Implement ES6 %TypedArray%.{of, from}. r=evilpie, till
js/src/builtin/TypedArray.js
js/src/builtin/Utilities.js
js/src/tests/ecma_6/TypedArray/from_basics.js
js/src/tests/ecma_6/TypedArray/from_constructor.js
js/src/tests/ecma_6/TypedArray/from_errors.js
js/src/tests/ecma_6/TypedArray/from_iterable.js
js/src/tests/ecma_6/TypedArray/from_mapping.js
js/src/tests/ecma_6/TypedArray/from_proxy.js
js/src/tests/ecma_6/TypedArray/from_realms.js
js/src/tests/ecma_6/TypedArray/from_string.js
js/src/tests/ecma_6/TypedArray/from_surfaces.js
js/src/tests/ecma_6/TypedArray/from_this.js
js/src/tests/ecma_6/TypedArray/of.js
js/src/vm/TypedArrayObject.cpp
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -535,8 +535,160 @@ function TypedArrayIncludes(searchElemen
 
         // Step d.
         k++;
     }
 
     // Step 11.
     return false;
 }
+
+// ES6 draft rev30 (2014/12/24) 22.2.2.1 %TypedArray%.from(source[, mapfn[, thisArg]]).
+function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) {
+    // Step 1.
+    var C = this;
+
+    // Step 2.
+    if (!IsConstructor(C))
+        ThrowError(JSMSG_NOT_CONSTRUCTOR, DecompileArg(1, C));
+
+    // Step 3.
+    var f = mapfn;
+
+    // Step 4.
+    if (f !== undefined && !IsCallable(f))
+        ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(1, f));
+
+    // Steps 5-6.
+    return TypedArrayFrom(C, undefined, source, f, thisArg);
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.2.1.1 TypedArrayFrom().
+function TypedArrayFrom(constructor, target, items, mapfn, thisArg) {
+    // Step 1.
+    var C = constructor;
+
+    // Step 2.
+    assert(C === undefined || target === undefined,
+           "Neither of 'constructor' and 'target' is undefined");
+
+    // Step 3.
+    assert(IsConstructor(C) || C === undefined,
+           "'constructor' is neither an constructor nor undefined");
+
+    // Step 4.
+    assert(target === undefined || IsTypedArray(target),
+           "'target' is neither a typed array nor undefined");
+
+    // Step 5.
+    assert(IsCallable(mapfn) || mapfn === undefined,
+           "'target' is neither a function nor undefined");
+
+    // Steps 6-7.
+    var mapping = mapfn !== undefined;
+    var T = thisArg;
+
+    // Steps 8-9.
+    var usingIterator = GetMethod(items, std_iterator);
+
+    // Step 10.
+    if (usingIterator !== undefined) {
+        // Steps 10.a-b.
+        var iterator = GetIterator(items, usingIterator);
+
+        // Step 10.c.
+        var values = new List();
+
+        // Steps 10.d-e.
+        while (true) {
+            // Steps 10.e.i-ii.
+            var next = iterator.next();
+            if (!IsObject(next))
+                ThrowError(JSMSG_NEXT_RETURNED_PRIMITIVE);
+
+            // Steps 10.e.iii-vi.
+            if (next.done)
+                break;
+            values.push(next.value);
+        }
+
+        // Step 10.f.
+        var len = values.length;
+
+        // Steps 10.g-h.
+        // There is no need to implement the 22.2.2.1.2 - TypedArrayAllocOrInit() method,
+        // since `%TypedArray%(object)` currently doesn't call this self-hosted TypedArrayFrom().
+        var targetObj = new C(len);
+
+        // Steps 10.i-j.
+        for (var k = 0; k < len; k++) {
+            // Steps 10.j.i-ii.
+            var kValue = values[k];
+
+            // Steps 10.j.iii-iv.
+            var mappedValue = mapping ? callFunction(mapfn, T, kValue, k) : kValue;
+
+            // Steps 10.j.v-vi.
+            targetObj[k] = mappedValue;
+        }
+
+        // Step 10.k.
+        // asserting that `values` is empty here would require removing them one by one from
+        // the list's start in the loop above. That would introduce unacceptable overhead.
+        // Additionally, the loop's logic is simple enough not to require the assert.
+
+        // Step 10.l.
+        return targetObj;
+    }
+
+    // Step 11 is an assertion: items is not an Iterator. Testing this is
+    // literally the very last thing we did, so we don't assert here.
+
+    // Steps 12-13.
+    var arrayLike = ToObject(items);
+
+    // Steps 14-16.
+    var len = ToLength(arrayLike.length);
+
+    // Steps 17-18.
+    // See comment for steps 10.g-h.
+    var targetObj = new C(len);
+
+    // Steps 19-20.
+    for (var k = 0; k < len; k++) {
+        // Steps 20.a-c.
+        var kValue = arrayLike[k];
+
+        // Steps 20.d-e.
+        var mappedValue = mapping ? callFunction(mapfn, T, kValue, k) : kValue;
+
+        // Steps 20.f-g.
+        targetObj[k] = mappedValue;
+    }
+
+    // Step 21.
+    return targetObj;
+}
+
+// ES6 draft rev30 (2014/12/24) 22.2.2.2 %TypedArray%.of(...items).
+function TypedArrayStaticOf(/*...items*/) {
+    // Step 1.
+    var len = arguments.length;
+
+    // Step 2.
+    var items = arguments;
+
+    // Step 3.
+    var C = this;
+
+    // Steps 4-5.
+    if (!IsConstructor(C))
+        ThrowError(JSMSG_NOT_CONSTRUCTOR, typeof C);
+
+    var newObj = new C(len);
+
+    // Steps 6-7.
+    for (var k = 0; k < len; k++)
+        newObj[k] = items[k]
+
+    // Step 8.
+    return newObj;
+}
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -38,17 +38,20 @@ var std_WeakMap = WeakMap;
 // StopIteration is a bare constructor without properties or methods.
 var std_StopIteration = StopIteration;
 
 
 /********** List specification type **********/
 
 
 /* Spec: ECMAScript Language Specification, 5.1 edition, 8.8 */
-function List() {}
+function List() {
+    this.length = 0;
+}
+
 {
   let ListProto = std_Object_create(null);
   ListProto.indexOf = std_Array_indexOf;
   ListProto.join = std_Array_join;
   ListProto.push = std_Array_push;
   ListProto.slice = std_Array_slice;
   ListProto.sort = std_Array_sort;
   MakeConstructible(List, ListProto);
@@ -98,12 +101,55 @@ function ToLength(v) {
 
     if (v <= 0)
         return 0;
 
     // Math.pow(2, 53) - 1 = 0x1fffffffffffff
     return std_Math_min(v, 0x1fffffffffffff);
 }
 
-// Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4.
+/* Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4 */
 function SameValueZero(x, y) {
     return x === y || (x !== x && y !== y);
 }
+
+/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.3.8 */
+function GetMethod(O, P) {
+    // Step 1.
+    assert(IsPropertyKey(P), "Invalid property key");
+
+    // Steps 2-3.
+    var func = ToObject(O)[P];
+
+    // Step 4.
+    if (func === undefined || func === null)
+        return undefined;
+
+    // Step 5.
+    if (!IsCallable(func))
+        ThrowError(JSMSG_NOT_FUNCTION, typeof func);
+
+    // Step 6.
+    return func;
+}
+
+/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.2.7 */
+function IsPropertyKey(argument) {
+    var type = typeof argument;
+    return type === "string" || type === "symbol";
+}
+
+/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.4.1 */
+function GetIterator(obj, method) {
+    // Steps 1-2.
+    if (arguments.length === 1)
+        method = GetMethod(obj, std_iterator);
+
+    // Steps 3-4.
+    var iterator = callFunction(method, obj);
+
+    // Step 5.
+    if (!IsObject(iterator))
+        ThrowError(JSMSG_NOT_ITERABLE, ToString(iterator));
+
+    // Step 6.
+    return iterator;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_basics.js
@@ -0,0 +1,54 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    // 'from' method is identical for all typed array constructors.
+    assertEq(constructors[0].from === constructor.from, true);
+
+    // %TypedArray%.from copies arrays.
+    var src = new constructor([1, 2, 3]), copy = constructor.from(src);
+    assertEq(copy === src, false);
+    assertEq(copy instanceof constructor, true);
+    assertDeepEq(copy, src);
+
+    // Non-element properties are not copied.
+    var a = new constructor([0, 1]);
+    a.name = "lisa";
+    assertDeepEq(constructor.from(a), new constructor([0, 1]));
+
+    // %TypedArray%.from can copy non-iterable objects, if they're array-like.
+    src = {0: 0, 1: 1, length: 2};
+    copy = constructor.from(src);
+    assertEq(copy instanceof constructor, true);
+    assertDeepEq(copy, new constructor([0, 1]));
+
+    // Properties past the .length are not copied.
+    src = {0: "0", 1: "1", 2: "two", 9: "nine", name: "lisa", length: 2};
+    assertDeepEq(constructor.from(src), new constructor([0, 1]));
+
+    // If an object has neither an @@iterator method nor .length,
+    // then it's treated as zero-length.
+    assertDeepEq(constructor.from({}), new constructor());
+
+    // Primitives will be coerced to primitive wrapper first.
+    assertDeepEq(constructor.from(1), new constructor());
+    assertDeepEq(constructor.from("123"), new constructor([1, 2, 3]));
+    assertDeepEq(constructor.from(true), new constructor());
+    assertDeepEq(constructor.from(Symbol()), new constructor());
+
+    // Source object property order doesn't matter.
+    src = {length: 2, 1: "1", 0: "0"};
+    assertDeepEq(constructor.from(src), new constructor([0, 1]));
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_constructor.js
@@ -0,0 +1,79 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    // %TypedArray%.from can be applied to any constructor.
+    // For example, the Date builtin constructor.
+    // Unlike Array.from, %TypedArray%.from doesn't set the 'length' property at the end.
+    var d = constructor.from.call(Date, ["A", "B"]);
+    assertEq(d instanceof constructor, false);
+    assertEq(Object.prototype.toString.call(d), "[object Date]");
+    assertEq(Object.getPrototypeOf(d), Date.prototype);
+    assertEq(d.length, undefined);
+    assertEq(d[0], "A");
+    assertEq(d[1], "B");
+
+    // Or RegExp.
+    var obj = constructor.from.call(RegExp, [1]);
+    assertEq(obj instanceof constructor, false);
+    assertEq(Object.getPrototypeOf(obj), RegExp.prototype);
+    assertEq(Object.getOwnPropertyNames(obj).join(","),
+             "0,lastIndex,source,global,ignoreCase,multiline,sticky");
+    assertEq(obj.length, undefined);
+
+    // Or any JS function.
+    function C(arg) {
+        this.args = arguments;
+    }
+    var c = constructor.from.call(C, {length: 1, 0: "zero"});
+    assertEq(c instanceof C, true);
+    assertEq(c.args.length, 1);
+    assertEq(c.args[0], 1);
+    assertEq(c.length, undefined);
+    assertEq(c[0], "zero");
+
+    // Note %TypedArray%.from(iterable) calls 'this' with an argument whose value is
+    // `[...iterable].length`, while Array.from(iterable) doesn't pass any argument.
+    assertEq(constructor.from.call(Object, []) instanceof Number, true);
+    assertDeepEq(constructor.from.call(Object, []), new Number(0));
+    assertEq(constructor.from.call(Number,[1, , "a"]) + 1, 4);
+    constructor.from.call(function(len){
+        assertEq(len, 3);
+    }, Array(3));
+
+    // If the 'this' value passed to %TypedArray.from is not a constructor,
+    // then an exception is thrown, while Array.from will use Array as it's constructor.
+    var arr = [3, 4, 5];
+    var nonconstructors = [
+        {}, Math, Object.getPrototypeOf, undefined, 17,
+        () => ({})  // arrow functions are not constructors
+    ];
+    for (var v of nonconstructors) {
+        assertThrowsInstanceOf(() => {
+            constructor.from.call(v, arr);
+        }, TypeError);
+    }
+
+    // %TypedArray%.from does not get confused if global constructors for typed arrays
+    // are replaced with another constructor.
+    function NotArray() {
+    }
+    var RealArray = constructor;
+    NotArray.from = constructor.from;
+    this[constructor.name] = NotArray;
+    assertEq(RealArray.from([1]) instanceof RealArray, true);
+    assertEq(NotArray.from([1]) instanceof NotArray, true);
+    this[constructor.name] = RealArray;
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_errors.js
@@ -0,0 +1,109 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    // %TypedArray%.from throws if the argument is undefined or null.
+    assertThrowsInstanceOf(() => constructor.from(), TypeError);
+    assertThrowsInstanceOf(() => constructor.from(undefined), TypeError);
+    assertThrowsInstanceOf(() => constructor.from(null), TypeError);
+
+    // %TypedArray%.from throws if an element can't be defined on the new object.
+    function ObjectWithReadOnlyElement() {
+        Object.defineProperty(this, "0", {value: null});
+        this.length = 0;
+    }
+    ObjectWithReadOnlyElement.from = constructor.from;
+    assertDeepEq(ObjectWithReadOnlyElement.from([]), new ObjectWithReadOnlyElement);
+    assertThrowsInstanceOf(() => ObjectWithReadOnlyElement.from([1]), TypeError);
+
+    // The same, but via preventExtensions.
+    function InextensibleObject() {
+        Object.preventExtensions(this);
+    }
+    InextensibleObject.from = constructor.from;
+    assertThrowsInstanceOf(() => InextensibleObject.from([1]), TypeError);
+
+    // The same, but via a readonly property on its __proto__.
+    function ObjectWithReadOnlyElementOnProto() {
+        return Object.create({
+            get 0(){}
+        });
+    }
+    ObjectWithReadOnlyElementOnProto.from = constructor.from;
+    assertThrowsInstanceOf(() => ObjectWithReadOnlyElementOnProto.from([1]), TypeError);
+
+    // Unlike Array.from, %TypedArray%.from doesn't get or set the length property.
+    function ObjectWithThrowingLengthGetterSetter() {
+        Object.defineProperty(this, "length", {
+            configurable: true,
+            get() { throw new RangeError("getter!"); },
+            set() { throw new RangeError("setter!"); }
+        });
+    }
+    ObjectWithThrowingLengthGetterSetter.from = constructor.from;
+    assertEq(ObjectWithThrowingLengthGetterSetter.from(["foo"])[0], "foo");
+
+    // %TypedArray%.from throws if mapfn is neither callable nor undefined.
+    assertThrowsInstanceOf(() => constructor.from([3, 4, 5], {}), TypeError);
+    assertThrowsInstanceOf(() => constructor.from([3, 4, 5], "also not a function"), TypeError);
+    assertThrowsInstanceOf(() => constructor.from([3, 4, 5], null), TypeError);
+
+    // Even if the function would not have been called.
+    assertThrowsInstanceOf(() => constructor.from([], JSON), TypeError);
+
+    // If mapfn is not undefined and not callable, the error happens before anything else.
+    // Before calling the constructor, before touching the arrayLike.
+    var log = "";
+    function C() {
+        log += "C";
+        obj = this;
+    }
+    var p = new Proxy({}, {
+        has: function () { log += "1"; },
+        get: function () { log += "2"; },
+        getOwnPropertyDescriptor: function () { log += "3"; }
+    });
+    assertThrowsInstanceOf(() => constructor.from.call(C, p, {}), TypeError);
+    assertEq(log, "");
+
+    // If mapfn throws, the new object has already been created.
+    var arrayish = {
+        get length() { log += "l"; return 1; },
+        get 0() { log += "0"; return "q"; }
+    };
+    log = "";
+    var exc = {surprise: "ponies"};
+    assertThrowsValue(() => constructor.from.call(C, arrayish, () => { throw exc; }), exc);
+    assertEq(log, "lC0");
+    assertEq(obj instanceof C, true);
+
+    // It's a TypeError if the @@iterator property is a primitive (except null and undefined).
+    for (var primitive of ["foo", 17, Symbol(), true]) {
+        assertThrowsInstanceOf(() => constructor.from({[Symbol.iterator] : primitive}), TypeError);
+    }
+    assertDeepEq(constructor.from({[Symbol.iterator]: null}), new constructor());
+    assertDeepEq(constructor.from({[Symbol.iterator]: undefined}), new constructor());
+
+    // It's a TypeError if the iterator's .next() method returns a primitive.
+    for (var primitive of [undefined, null, "foo", 17, Symbol(), true]) {
+        assertThrowsInstanceOf(
+            () => constructor.from({
+                [Symbol.iterator]() {
+                    return {next() { return primitive; }};
+                }
+            }),
+        TypeError);
+    }
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_iterable.js
@@ -0,0 +1,61 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    // %TypedArray%.from works on arguments objects.
+    (function () {
+        assertDeepEq(constructor.from(arguments), new constructor(["0", "1", undefined]));
+    })("0", "1", undefined);
+
+    // If an object has both .length and [@@iterator] properties, [@@iterator] is used.
+    var a = ['0', '1', '2', '3', '4'];
+    a[Symbol.iterator] = function* () {
+        for (var i = 5; i--; )
+            yield this[i];
+    };
+
+    var log = '';
+    function f(x) {
+        log += x;
+        return x + x;
+    }
+
+    var b = constructor.from(a, f);
+    assertDeepEq(b, new constructor(['44', '33', '22', '11', '00']));
+    assertEq(log, '43210');
+
+    // In fact, if [@@iterator] is present, .length isn't queried at all.
+    var pa = new Proxy(a, {
+        has: function (target, id) {
+            if (id === "length")
+                throw new Error(".length should not be queried (has)");
+            return id in target;
+        },
+        get: function (target, id) {
+            if (id === "length")
+                throw new Error(".length should not be queried (get)");
+            return target[id];
+        },
+        getOwnPropertyDescriptor: function (target, id) {
+            if (id === "length")
+                throw new Error(".length should not be queried (getOwnPropertyDescriptor)");
+            return Object.getOwnPropertyDescriptor(target, id)
+        }
+    });
+    log = "";
+    b = constructor.from(pa, f);
+    assertDeepEq(b, new constructor(['44', '33', '22', '11', '00']));
+    assertEq(log, '43210');
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_mapping.js
@@ -0,0 +1,56 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    // If the mapfn argument to %TypedArray%.from is undefined, don't map.
+    assertDeepEq(constructor.from([3, 4, 5], undefined), new constructor([3, 4, 5]));
+    assertDeepEq(constructor.from([4, 5, 6], undefined, Math), new constructor([4, 5, 6]));
+
+    // mapfn is called with two arguments: value and index.
+    var log = [];
+    function f(...args) {
+        log.push(args);
+        return log.length;
+    }
+    assertDeepEq(constructor.from(['a', 'e', 'i', 'o', 'u'], f), new constructor([1, 2, 3, 4, 5]));
+    assertDeepEq(log, [['a', 0], ['e', 1], ['i', 2], ['o', 3], ['u', 4]]);
+
+    // If the object to be copied is non-iterable, mapfn is still called with two
+    // arguments.
+    log = [];
+    assertDeepEq(constructor.from({0: "zero", 1: "one", length: 2}, f), new constructor([1, 2]));
+    assertDeepEq(log, [["zero", 0], ["one", 1]]);
+
+    // If the object to be copied is iterable and the constructor is not Array,
+    // mapfn is still called with two arguments.
+    log = [];
+    function C() {}
+    C.from = constructor.from;
+    var c = new C;
+    c[0] = 1;
+    c[1] = 2;
+    assertDeepEq(C.from(["zero", "one"], f), c);
+    assertDeepEq(log, [["zero", 0], ["one", 1]]);
+
+    // The mapfn is called even if the value to be mapped is undefined.
+    assertDeepEq(constructor.from([0, 1, , 3], String), new constructor(["0", "1", "undefined", "3"]));
+    var arraylike = {length: 4, "0": 0, "1": 1, "3": 3};
+    assertDeepEq(constructor.from(arraylike, String), new constructor(["0", "1", "undefined", "3"]));
+}
+
+// %TypedArray%.from(obj, map) is not exactly the same as %TypedArray%.from(obj).map(mapFn).
+assertDeepEq(Int8Array.from([150], v => v / 2), new Int8Array([75]));
+// Uncomment the following line when we implement the .map method.
+// assertDeepEq(Int8Array.from([150]).map(v => v / 2), new Int8Array([-53]));
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_proxy.js
@@ -0,0 +1,69 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    // Two tests involving %TypedArray%.from and a Proxy.
+    var log = [];
+    function LoggingProxy(target) {
+        log.push("target", target);
+        var h = {
+            defineProperty: function (t, id) {
+                log.push("define", id);
+                return undefined;
+            },
+            has: function (t, id) {
+                log.push("has", id);
+                return id in t;
+            },
+            get: function (t, id) {
+                log.push("get", id);
+                return t[id];
+            },
+            set: function (t, id, v) {
+                log.push("set", id);
+                t[id] = v;
+            }
+        };
+        return new Proxy(Object(target), h);
+    }
+
+    // Unlike Array.from, %TypedArray%.from uses [[Put]] instead of [[DefineOwnProperty]].
+    // Hence, it calls handler.set to create new elements rather than handler.defineProperty.
+    // Additionally, it doesn't set the length property at the end.
+    LoggingProxy.from = constructor.from;
+    LoggingProxy.from([3, 4, 5]);
+    assertDeepEq(log, ["target", 3, "set", "0", "set", "1", "set", "2"]);
+
+    // When the argument passed to %TypedArray%.from is a Proxy, %TypedArray%.from
+    // calls handler.get on it.
+    log = [];
+    assertDeepEq(constructor.from(new LoggingProxy([3, 4, 5])), new constructor([3, 4, 5]));
+    assertDeepEq(log, ["target", [3, 4, 5],
+                       "get", Symbol.iterator,
+                       "get", "length", "get", "0",
+                       "get", "length", "get", "1",
+                       "get", "length", "get", "2",
+                       "get", "length"]);
+
+    // Array-like iteration only gets the length once.
+    log = [];
+    var arr = [5, 6, 7];
+    arr[Symbol.iterator] = undefined;
+    assertDeepEq(constructor.from(new LoggingProxy(arr)), new constructor([5, 6, 7]));
+    assertDeepEq(log, ["target", [5, 6, 7],
+                       "get", Symbol.iterator,
+                       "get", "length",
+                       "get", "0", "get", "1", "get", "2"]);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_realms.js
@@ -0,0 +1,44 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    if (typeof newGlobal !== 'function')
+        break;
+
+    // G[constructor.name].from, where G is any global, produces an array whose prototype
+    // is G[constructor.name].prototype.
+    var g = newGlobal();
+    var ga = g[constructor.name].from([1, 2, 3]);
+    assertEq(ga instanceof g[constructor.name], true);
+
+    // %TypedArray%.from can be applied to a constructor from another realm.
+    var p = constructor.from.call(g[constructor.name], [1, 2, 3]);
+    assertEq(p instanceof g[constructor.name], true);
+    var q = g[constructor.name].from.call(constructor, [3, 4, 5]);
+    assertEq(q instanceof constructor, true);
+
+    // The default 'this' value received by a non-strict mapping function is
+    // that function's global, not %TypedArray%.from's global or the caller's global.
+    var h = newGlobal(), result = undefined;
+    h.mainGlobal = this;
+    h.eval("function f() { mainGlobal.result = this; }");
+    g[constructor.name].from.call(constructor, [5, 6, 7], h.f);
+    // (Give each global in the test a name, for better error messages.  But use
+    // globalName, because window.name is complicated.)
+    this.globalName = "main";
+    g.globalName = "g";
+    h.globalName = "h";
+    assertEq(result.globalName, "h");
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_string.js
@@ -0,0 +1,23 @@
+// %TypedArray%.from called on Array should also handle strings correctly.
+var from = Int8Array.from.bind(Array);
+
+// %TypedArray%.from on a string iterates over the string.
+assertDeepEq(from("test string"),
+             ['t', 'e', 's', 't', ' ', 's', 't', 'r', 'i', 'n', 'g']);
+
+// %TypedArray%.from on a string handles surrogate pairs correctly.
+var gclef = "\uD834\uDD1E"; // U+1D11E MUSICAL SYMBOL G CLEF
+assertDeepEq(from(gclef), [gclef]);
+assertDeepEq(from(gclef + " G"), [gclef, " ", "G"]);
+
+// %TypedArray%.from on a string calls the @@iterator method.
+String.prototype[Symbol.iterator] = function* () { yield 1; yield 2; };
+assertDeepEq(from("anything"), [1, 2]);
+
+// If the iterator method is deleted, Strings are still arraylike.
+delete String.prototype[Symbol.iterator];
+assertDeepEq(from("works"), ['w', 'o', 'r', 'k', 's']);
+assertDeepEq(from(gclef), ['\uD834', '\uDD1E']);
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_surfaces.js
@@ -0,0 +1,24 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    // Check superficial features of %TypeArray%.from.
+    var desc = Object.getOwnPropertyDescriptor(constructor.__proto__, "from");
+    assertEq(desc.configurable, true);
+    assertEq(desc.enumerable, false);
+    assertEq(desc.writable, true);
+    assertEq(constructor.from.length, 1);
+    assertThrowsInstanceOf(() => new constructor.from(), TypeError);  // not a constructor
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/from_this.js
@@ -0,0 +1,71 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    // The third argument to %TypedArray%.from is passed as the 'this' value to the
+    // mapping function.
+    var hits = 0, obj = {};
+    function f(x) {
+        assertEq(this, obj);
+        hits++;
+    }
+    constructor.from(["a", "b", "c"], f, obj);
+    assertEq(hits, 3);
+
+    // Without an argument, undefined is passed...
+    hits = 0;
+    function gs(x) {
+        "use strict";
+        assertEq(this, undefined);
+        hits++;
+    }
+    constructor.from("def", gs);
+    assertEq(hits, 3);
+
+    // ...and if the mapping function is non-strict, that means the global is
+    // passed.
+    var global = this;
+    hits = 0;
+    function g(x) {
+        assertEq(this, global);
+        hits++;
+    }
+    constructor.from("ghi", g);
+    assertEq(hits, 3);
+
+    // A primitive value can be passed.
+    for (var v of [0, "str", undefined]) {
+        hits = 0;
+        var mapfn = function h(x) {
+            "use strict";
+            assertEq(this, v);
+            hits++;
+        };
+        constructor.from("pq", mapfn, v);
+        assertEq(hits, 2);
+    }
+
+    // ...and if the mapping function is non-strict, primitive values will
+    // be wrapped to objects.
+    for (var v of [0, "str", true]) {
+        hits = 0;
+        var mapfn = function h(x) {
+            assertDeepEq(this, Object(v));
+            hits++;
+        };
+        constructor.from("pq", mapfn, v);
+        assertEq(hits, 2);
+    }
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/TypedArray/of.js
@@ -0,0 +1,100 @@
+const constructors = [
+    Int8Array,
+    Uint8Array,
+    Uint8ClampedArray,
+    Int16Array,
+    Uint16Array,
+    Int32Array,
+    Uint32Array,
+    Float32Array,
+    Float64Array
+];
+
+for (var constructor of constructors) {
+    assertEq(constructor.of.length, 0);
+
+    assertDeepEq(Object.getOwnPropertyDescriptor(constructor.__proto__, "of"), {
+        configurable: true,
+        enumerable: false,
+        value: constructor.of,
+        writable: true
+    });
+
+    // Basic tests.
+    assertEq(constructor.of().constructor, constructor);
+    assertEq(constructor.of() instanceof constructor, true);
+    assertDeepEq(constructor.of(10), new constructor([10]));
+    assertDeepEq(constructor.of(1, 2, 3), new constructor([1, 2, 3]));
+    assertDeepEq(constructor.of("1", "2", "3"), new constructor([1, 2, 3]));
+
+    // This method can be transplanted to other constructors.
+    assertDeepEq(constructor.of.call(Array, 1, 2, 3), [1, 2, 3]);
+
+    var hits = 0;
+    assertDeepEq(constructor.of.call(function(len) {
+        assertEq(arguments.length, 1);
+        assertEq(len, 3);
+        hits++;
+        return {};
+    }, "a", "b", "c"), {
+        0: "a",
+        1: "b",
+        2: "c"
+    });
+    assertEq(hits, 1);
+
+    // Behavior across compartments.
+    if (typeof newGlobal === "function") {
+        var newC = newGlobal()[constructor.name];
+        assertEq(newC.of() instanceof newC, true);
+        assertEq(newC.of() instanceof constructor, false);
+        assertEq(newC.of.call(constructor) instanceof constructor, true);
+    }
+
+    // Throws if `this` isn't a constructor.
+    var invalidConstructors = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
+                               constructor.of, () => {}];
+    invalidConstructors.forEach(C => {
+        assertThrowsInstanceOf(() => {
+            constructor.of.call(C);
+        }, TypeError);
+    });
+
+    // FIXME: Should throw if `this` is a method definition or a getter/setter function, see bug 1059908.
+    constructor.of.call({method() {}}.method);
+    constructor.of.call(Object.getOwnPropertyDescriptor({get getter() {}}, "getter").get);
+
+    // Generators are also legal constructors.
+    assertEq(constructor.of.call(function*(len) {
+        return len;
+    }, "a", "b", "c").next().value, 3);
+
+    // An exception might be thrown in a strict assignment to the new object's indexed properties.
+    assertThrowsInstanceOf(() => {
+        constructor.of.call(function() {
+            return {get 0() {}};
+        }, "a");
+    }, TypeError);
+
+    assertThrowsInstanceOf(() => {
+        constructor.of.call(function() {
+            return Object("1");
+        }, "a");
+    }, TypeError);
+
+    assertThrowsInstanceOf(() => {
+        constructor.of.call(function() {
+            return Object.create({
+                set 0(v) {
+                    throw new TypeError;
+                }
+            });
+        }, "a");
+    }, TypeError);
+}
+
+assertDeepEq(Float32Array.of(0.1, null, undefined, NaN), new Float32Array([0.1, 0, NaN, NaN]));
+assertDeepEq(Float64Array.of(0.1, null, undefined, NaN), new Float64Array([0.1, 0, NaN, NaN]));
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -828,17 +828,18 @@ TypedArrayObject::protoFunctions[] = {
 #ifdef NIGHTLY_BUILD
     JS_SELF_HOSTED_FN("includes", "TypedArrayIncludes", 2, 0),
 #endif
     JS_FS_END
 };
 
 /* static */ const JSFunctionSpec
 TypedArrayObject::staticFunctions[] = {
-    // Coming soon...
+    JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0),
+    JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0),
     JS_FS_END
 };
 
 /* static */ const Class
 TypedArrayObject::sharedTypedArrayPrototypeClass = {
     // Actually ({}).toString.call(%TypedArray%.prototype) should throw,
     // because %TypedArray%.prototype lacks the the typed array internal
     // slots.  (It's not clear this is desirable -- particularly applied to