Bug 904723, part 3 - Implement Array.from. r=till.
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 06 Jun 2014 11:15:22 -0400
changeset 207531 b221cb63e559f3842dacdf34f5dda54b23ad6e05
parent 207530 55f0e2c4340631727047cbc6df00f4c0b4649164
child 207532 0c9ff69d42634f70289a6c5ede71d33469b09ee7
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs904723
milestone32.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 904723, part 3 - Implement Array.from. r=till.
js/src/builtin/Array.js
js/src/builtin/Utilities.js
js/src/jsarray.cpp
js/src/tests/ecma_6/Array/from_basics.js
js/src/tests/ecma_6/Array/from_constructor.js
js/src/tests/ecma_6/Array/from_errors.js
js/src/tests/ecma_6/Array/from_iterable.js
js/src/tests/ecma_6/Array/from_length_setter.js
js/src/tests/ecma_6/Array/from_mapping.js
js/src/tests/ecma_6/Array/from_proxy.js
js/src/tests/ecma_6/Array/from_realms.js
js/src/tests/ecma_6/Array/from_string.js
js/src/tests/ecma_6/Array/from_surfaces.js
js/src/tests/ecma_6/Array/from_this.js
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -655,16 +655,79 @@ function ArrayValues() {
 function ArrayEntries() {
     return CreateArrayIterator(this, ITEM_KIND_KEY_AND_VALUE);
 }
 
 function ArrayKeys() {
     return CreateArrayIterator(this, ITEM_KIND_KEY);
 }
 
+/* ES6 rev 24 (2014 April 27) 22.1.2.1 */
+function ArrayFrom(arrayLike, mapfn=undefined, thisArg=undefined) {
+    // Step 1.
+    var C = this;
+
+    // Steps 2-3.
+    var items = ToObject(arrayLike);
+
+    // Steps 4-5.
+    var mapping = (mapfn !== undefined);
+    if (mapping && !IsCallable(mapfn))
+        ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(1, mapfn));
+
+    // All elements defined by this algorithm have the same attrs:
+    var attrs = ATTR_CONFIGURABLE | ATTR_ENUMERABLE | ATTR_WRITABLE;
+
+    // Steps 6-8.
+    if (items["@@iterator"] !== undefined) {
+        // Steps 8.a-c.
+        var A = IsConstructor(C) ? new C() : [];
+
+        // Step 8.f.
+        var k = 0;
+
+        // Steps 8.d-e and 8.g.i-vi.
+        for (var nextValue of items) {
+            // Steps 8.g.vii-viii.
+            var mappedValue = mapping ? callFunction(mapfn, thisArg, nextValue, k) : nextValue;
+
+            // Steps 8.g.ix-xi.
+            _DefineDataProperty(A, k++, mappedValue, attrs);
+        }
+
+        // Here we're at step 8.g.iv.1. Fall through; it's implemented below.
+    } else {
+        // Step 9 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 10-12.
+        // FIXME: Array operations should use ToLength (bug 924058).
+        var len = ToInteger(items.length);
+
+        // Steps 13-15.
+        var A = IsConstructor(C) ? new C(len) : NewDenseArray(len);
+
+        // Steps 16-17.
+        for (var k = 0; k < len; k++) {
+            // Steps 17.a-c.
+            var kValue = items[k];
+
+            // Steps 17.d-e.
+            var mappedValue = mapping ? callFunction(mapfn, thisArg, kValue, k) : kValue;
+
+            // Steps 17.f-g.
+            _DefineDataProperty(A, k, mappedValue, attrs);
+        }
+    }
+
+    // Steps 8.g.iv.1-3 and 18-20 are the same.
+    A.length = k;
+    return A;
+}
+
 #ifdef ENABLE_PARALLEL_JS
 
 /*
  * Strawman spec:
  *   http://wiki.ecmascript.org/doku.php?id=strawman:data_parallelism
  */
 
 /**
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -23,18 +23,23 @@
          JSMSG_EMPTY_ARRAY_REDUCE: false, JSMSG_CANT_CONVERT_TO: false,
 */
 
 #include "SelfHostingDefines.h"
 
 // Remove unsafe builtin functions.
 Object.defineProperty = null; // See bug 988416.
 
-// Cache builtin functions so using them doesn't require cloning the whole object they're 
+// Cache builtin functions so using them doesn't require cloning the whole object they're
 // installed on.
+//
+// WARNING: Do not make std_ references to builtin constructors (like Array and
+// Object) below. Setting `var std_Array = Array;`, for instance, would cause
+// the entire Array constructor, including its prototype and methods, to be
+// cloned into content compartments.
 var std_isFinite = isFinite;
 var std_isNaN = isNaN;
 var std_Array_indexOf = ArrayIndexOf;
 var std_Array_iterator = Array.prototype.iterator;
 var std_Array_join = Array.prototype.join;
 var std_Array_push = Array.prototype.push;
 var std_Array_pop = Array.prototype.pop;
 var std_Array_shift = Array.prototype.shift;
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2987,16 +2987,17 @@ static const JSFunctionSpec array_static
     JS_SELF_HOSTED_FN("lastIndexOf", "ArrayStaticLastIndexOf", 2,0),
     JS_SELF_HOSTED_FN("indexOf",     "ArrayStaticIndexOf", 2,0),
     JS_SELF_HOSTED_FN("forEach",     "ArrayStaticForEach", 2,0),
     JS_SELF_HOSTED_FN("map",         "ArrayStaticMap",   2,0),
     JS_SELF_HOSTED_FN("every",       "ArrayStaticEvery", 2,0),
     JS_SELF_HOSTED_FN("some",        "ArrayStaticSome",  2,0),
     JS_SELF_HOSTED_FN("reduce",      "ArrayStaticReduce", 2,0),
     JS_SELF_HOSTED_FN("reduceRight", "ArrayStaticReduceRight", 2,0),
+    JS_SELF_HOSTED_FN("from",        "ArrayFrom", 3,0),
     JS_FN("of",                 array_of,           0,0),
 
 #ifdef ENABLE_PARALLEL_JS
     JS_SELF_HOSTED_FN("build",       "ArrayStaticBuild", 2,0),
     /* Parallelizable and pure static methods. */
     JS_SELF_HOSTED_FN("buildPar",    "ArrayStaticBuildPar", 3,0),
 #endif
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_basics.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// Array.from copies arrays.
+var src = [1, 2, 3], copy = Array.from(src);
+assertEq(copy === src, false);
+assertEq(Array.isArray(copy), true);
+assertDeepEq(copy, src);
+
+// Non-element properties are not copied.
+var a = [0, 1];
+a.name = "lisa";
+assertDeepEq(Array.from(a), [0, 1]);
+
+// It's a shallow copy.
+src = [[0], [1]];
+copy = Array.from(src);
+assertEq(copy[0], src[0]);
+assertEq(copy[1], src[1]);
+
+// Array.from can copy non-iterable objects, if they're array-like.
+src = {0: "zero", 1: "one", length: 2};
+copy = Array.from(src);
+assertEq(Array.isArray(copy), true);
+assertDeepEq(copy, ["zero", "one"]);
+
+// Properties past the .length are not copied.
+src = {0: "zero", 1: "one", 2: "two", 9: "nine", name: "lisa", length: 2};
+assertDeepEq(Array.from(src), ["zero", "one"]);
+
+// If an object has neither an @@iterator method nor .length,
+// then it's treated as zero-length.
+assertDeepEq(Array.from({}), []);
+
+// Source object property order doesn't matter.
+src = {length: 2, 1: "last", 0: "first"};
+assertDeepEq(Array.from(src), ["first", "last"]);
+
+// Array.from does not preserve holes.
+assertDeepEq(Array.from(Array(3)), [undefined, undefined, undefined]);
+assertDeepEq(Array.from([, , 2, 3]), [undefined, undefined, 2, 3]);
+assertDeepEq(Array.from([0, , , ,]), [0, undefined, undefined, undefined]);
+
+// Even on non-iterable objects.
+assertDeepEq(Array.from({length: 4}), [undefined, undefined, undefined, undefined]);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_constructor.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// Array.from can be applied to any constructor.
+// For example, the Date builtin constructor.
+var d = Array.from.call(Date, ["A", "B"]);
+assertEq(Array.isArray(d), false);
+assertEq(Object.prototype.toString.call(d), "[object Date]");
+assertEq(Object.getPrototypeOf(d), Date.prototype);
+assertEq(d.length, 2);
+assertEq(d[0], "A");
+assertEq(d[1], "B");
+
+// Or Object.
+var obj = Array.from.call(Object, []);
+assertEq(Array.isArray(obj), false);
+assertEq(Object.getPrototypeOf(obj), Object.prototype);
+assertEq(Object.getOwnPropertyNames(obj).join(","), "length");
+assertEq(obj.length, 0);
+
+// Or any JS function.
+function C(arg) {
+    this.args = arguments;
+}
+var c = Array.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, 1);
+assertEq(c[0], "zero");
+
+// If the 'this' value passed to Array.from is not a constructor,
+// a plain Array is created.
+var arr = [3, 4, 5];
+var nonconstructors = [
+    {}, Math, Object.getPrototypeOf, undefined, 17,
+    () => ({})  // arrow functions are not constructors
+];
+for (var v of nonconstructors) {
+    obj = Array.from.call(v, arr);
+    assertEq(Array.isArray(obj), true);
+    assertDeepEq(obj, arr);
+}
+
+// Array.from does not get confused if global.Array is replaced with another
+// constructor.
+function NotArray() {
+}
+var RealArray = Array;
+NotArray.from = Array.from;
+Array = NotArray;
+assertEq(RealArray.from([1]) instanceof RealArray, true);
+assertEq(NotArray.from([1]) instanceof NotArray, true);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_errors.js
@@ -0,0 +1,134 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// Array.from throws if the argument is undefined or null.
+assertThrowsInstanceOf(() => Array.from(), TypeError);
+assertThrowsInstanceOf(() => Array.from(undefined), TypeError);
+assertThrowsInstanceOf(() => Array.from(null), TypeError);
+
+// Array.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 = Array.from;
+assertDeepEq(ObjectWithReadOnlyElement.from([]), new ObjectWithReadOnlyElement);
+assertThrowsInstanceOf(() => ObjectWithReadOnlyElement.from([1]), TypeError);
+
+// The same, but via preventExtensions.
+function InextensibleObject() {
+    Object.preventExtensions(this);
+}
+InextensibleObject.from = Array.from;
+assertThrowsInstanceOf(() => InextensibleObject.from([1]), TypeError);
+
+// We will now test this property, that Array.from throws if the .length can't
+// be assigned, using several different kinds of object.
+var obj;
+function init(self) {
+    obj = self;
+    self[0] = self[1] = self[2] = self[3] = 0;
+}
+
+function testUnsettableLength(C, Exc) {
+    if (Exc === undefined)
+        Exc = TypeError;  // the usual expected exception type
+    C.from = Array.from;
+
+    obj = null;
+    assertThrowsInstanceOf(() => C.from([]), Exc);
+    assertEq(obj instanceof C, true);
+    for (var i = 0; i < 4; i++)
+        assertEq(obj[0], 0);
+
+    obj = null;
+    assertThrowsInstanceOf(() => C.from([0, 10, 20, 30]), Exc);
+    assertEq(obj instanceof C, true);
+    for (var i = 0; i < 4; i++)
+        assertEq(obj[i], i * 10);
+}
+
+// Array.from throws if the new object's .length can't be assigned because
+// there is no .length and the object is inextensible.
+function InextensibleObject4() {
+    init(this);
+    Object.preventExtensions(this);
+}
+testUnsettableLength(InextensibleObject4);
+
+// Array.from throws if the new object's .length can't be assigned because it's
+// read-only.
+function ObjectWithReadOnlyLength() {
+    init(this);
+    Object.defineProperty(this, "length", {configurable: true, writable: false, value: 4});
+}
+testUnsettableLength(ObjectWithReadOnlyLength);
+
+// The same, but using a builtin type.
+Uint8Array.from = Array.from;
+assertThrowsInstanceOf(() => Uint8Array.from([]), TypeError);
+
+// Array.from throws if the new object's .length can't be assigned because it
+// inherits a readonly .length along the prototype chain.
+function ObjectWithInheritedReadOnlyLength() {
+    init(this);
+}
+Object.defineProperty(ObjectWithInheritedReadOnlyLength.prototype,
+                      "length",
+                      {configurable: true, writable: false, value: 4});
+testUnsettableLength(ObjectWithInheritedReadOnlyLength);
+
+// The same, but using an object with a .length getter but no setter.
+function ObjectWithGetterOnlyLength() {
+    init(this);
+    Object.defineProperty(this, "length", {configurable: true, get: () => 4});
+}
+testUnsettableLength(ObjectWithGetterOnlyLength);
+
+// The same, but with a setter that throws.
+function ObjectWithThrowingLengthSetter() {
+    init(this);
+    Object.defineProperty(this, "length", {
+        configurable: true,
+        get: () => 4,
+        set: () => { throw new RangeError("surprise!"); }
+    });
+}
+testUnsettableLength(ObjectWithThrowingLengthSetter, RangeError);
+
+// Array.from throws if mapfn is neither callable nor undefined.
+assertThrowsInstanceOf(() => Array.from([3, 4, 5], {}), TypeError);
+assertThrowsInstanceOf(() => Array.from([3, 4, 5], "also not a function"), TypeError);
+assertThrowsInstanceOf(() => Array.from([3, 4, 5], null), TypeError);
+
+// Even if the function would not have been called.
+assertThrowsInstanceOf(() => Array.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(() => Array.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(() => Array.from.call(C, arrayish, () => { throw exc; }), exc);
+assertEq(log, "lC0");
+assertEq(obj instanceof C, true);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_iterable.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// Array.from works on arguments objects.
+(function () {
+    assertDeepEq(Array.from(arguments), ["arg0", "arg1", undefined]);
+})("arg0", "arg1", undefined);
+
+// If an object has both .length and [@@iterator] properties, [@@iterator] is used.
+var a = ['a', 'e', 'i', 'o', 'u'];
+a["@@iterator"] = function* () {
+    for (var i = 5; i--; )
+        yield this[i];
+};
+
+var log = '';
+function f(x) {
+    log += x;
+    return x + x;
+}
+
+var b = Array.from(a, f);
+assertDeepEq(b, ['uu', 'oo', 'ii', 'ee', 'aa']);
+assertEq(log, 'uoiea');
+
+// 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 = Array.from(pa, f);
+assertDeepEq(b, ['uu', 'oo', 'ii', 'ee', 'aa']);
+assertEq(log, 'uoiea');
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_length_setter.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// Array.from calls a length setter if present.
+var hits = 0;
+function C() {}
+C.prototype = {set length(v) { hits++; }};
+C.from = Array.from;
+var copy = C.from(["A", "B"]);
+assertEq(hits, 1);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_mapping.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// If the mapfn argument to Array.from is undefined, don't map.
+assertDeepEq(Array.from([3, 4, 5], undefined), [3, 4, 5]);
+assertDeepEq(Array.from([4, 5, 6], undefined, Math), [4, 5, 6]);
+
+// mapfn is called with two arguments: value and index.
+var log = [];
+function f() {
+    log.push(Array.from(arguments));
+    return log.length;
+}
+assertDeepEq(Array.from(['a', 'e', 'i', 'o', 'u'], f), [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(Array.from({0: "zero", 1: "one", length: 2}, f), [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 = Array.from;
+var c = new C;
+c[0] = 1;
+c[1] = 2;
+c.length = 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(Array.from([0, 1, , 3], String), ["0", "1", "undefined", "3"]);
+var arraylike = {length: 4, "0": 0, "1": 1, "3": 3};
+assertDeepEq(Array.from(arraylike, String), ["0", "1", "undefined", "3"]);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_proxy.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// Two tests involving Array.from and a Proxy.
+var log = [];
+function LoggingProxy(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(target || [], h);
+}
+
+// When the new object created by Array.from is a Proxy,
+// Array.from calls handler.defineProperty to create new elements
+// but handler.set to set the length.
+LoggingProxy.from = Array.from;
+LoggingProxy.from([3, 4, 5]);
+assertDeepEq(log, ["define 0", "define 1", "define 2", "set length"]);
+
+// When the argument passed to Array.from is a Proxy, Array.from
+// calls handler.get on it.
+log = [];
+assertDeepEq(Array.from(new LoggingProxy([3, 4, 5])), [3, 4, 5]);
+assertDeepEq(log, ["get @@iterator", "get @@iterator",
+                   "get length", "get 0", "get length", "get 1", "get length", "get 2",
+                   "get length"]);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_realms.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+if (typeof newGlobal === 'function') {
+    // G.Array.from, where G is any global, produces an array whose prototype
+    // is G.Array.prototype.
+    var g = newGlobal();
+    var ga = g.Array.from([1, 2, 3]);
+    assertEq(ga instanceof g.Array, true);
+
+    // Even if G.Array is not passed in as the 'this' value to the call.
+    var from = g.Array.from
+    var ga2 = from([1, 2, 3]);
+    assertEq(ga2 instanceof g.Array, true);
+
+    // Array.from can be applied to a constructor from another realm.
+    var p = Array.from.call(g.Array, [1, 2, 3]);
+    assertEq(p instanceof g.Array, true);
+    var q = g.Array.from.call(Array, [3, 4, 5]);
+    assertEq(q instanceof Array, true);
+
+    // The default 'this' value received by a non-strict mapping function is
+    // that function's global, not Array.from's global or the caller's global.
+    var h = newGlobal(), result = undefined;
+    h.mainGlobal = this;
+    h.eval("function f() { mainGlobal.result = this; }");
+    g.Array.from.call(Array, [5, 6, 7], h.f);
+    // (Give each global in the test a name, for better error messages.)
+    this.name = "main";
+    g.name = "g";
+    h.name = "h";
+    assertEq(result.name, "h");
+}
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_string.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// Array.from on a string iterates over the string.
+assertDeepEq(Array.from("test string"),
+             ['t', 'e', 's', 't', ' ', 's', 't', 'r', 'i', 'n', 'g']);
+
+// Array.from on a string handles surrogate pairs correctly.
+var gclef = "\uD834\uDD1E"; // U+1D11E MUSICAL SYMBOL G CLEF
+assertDeepEq(Array.from(gclef), [gclef]);
+assertDeepEq(Array.from(gclef + " G"), [gclef, " ", "G"]);
+
+// Array.from on a string calls the @@iterator method.
+String.prototype["@@iterator"] = function* () { yield 1; yield 2; };
+assertDeepEq(Array.from("anything"), [1, 2]);
+
+// If the iterator method is deleted, Strings are still arraylike.
+delete String.prototype["@@iterator"];
+assertDeepEq(Array.from("works"), ['w', 'o', 'r', 'k', 's']);
+assertDeepEq(Array.from(gclef), ['\uD834', '\uDD1E']);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_surfaces.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// Check superficial features of Array.from.
+var desc = Object.getOwnPropertyDescriptor(Array, "from");
+assertEq(desc.configurable, true);
+assertEq(desc.enumerable, false);
+assertEq(desc.writable, true);
+assertEq(Array.from.length, 1);
+assertThrowsInstanceOf(() => new Array.from(), TypeError);  // not a constructor
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/from_this.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+// The third argument to Array.from is passed as the 'this' value to the
+// mapping function.
+var hits = 0, obj = {};
+function f(x) {
+    assertEq(this, obj);
+    hits++;
+}
+Array.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++;
+}
+Array.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++;
+}
+Array.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++;
+    };
+    Array.from("pq", mapfn, v);
+    assertEq(hits, 2);
+}
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);