diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js
index 378e972084..98e83e5af0 100644
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -345,6 +345,60 @@ function ArrayWith(index, value) {
return A;
}
+// ES2023 22.1.3.35 Array.prototype.toSpliced ( start, deleteCount, ...items )
+function ArrayToSpliced(start, deleteCount) {
+ // Step 1.
+ var O = ToObject(this);
+
+ // Step 2.
+ var len = ToLength(O.length);
+
+ // Step 3.
+ var relativeStart = ToInteger(start);
+ var actualStart =
+ relativeStart < 0
+ ? std_Math_max(len + relativeStart, 0)
+ : std_Math_min(relativeStart, len);
+
+ // Step 4.
+ var insertCount = arguments.length > 2 ? arguments.length - 2 : 0;
+
+ // Step 5.
+ var actualDeleteCount;
+ if (arguments.length === 0) {
+ actualDeleteCount = 0;
+ } else if (arguments.length === 1) {
+ actualDeleteCount = len - actualStart;
+ } else {
+ var dc = ToInteger(deleteCount);
+ actualDeleteCount = std_Math_min(std_Math_max(dc, 0), len - actualStart);
+ }
+
+ // Steps 6-8.
+ var newLen = len + insertCount - actualDeleteCount;
+ if (newLen > MAX_NUMERIC_INDEX) ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
+ var A = std_Array(newLen);
+
+ // Steps 9-10.
+ var k = 0;
+ for (; k < actualStart; k++) {
+ _DefineDataProperty(A, k, O[k]);
+ }
+
+ // Step 11.
+ for (var i = 2; i < arguments.length; i++) {
+ _DefineDataProperty(A, k++, arguments[i]);
+ }
+
+ // Steps 12-13.
+ for (var from = actualStart + actualDeleteCount; from < len; from++) {
+ _DefineDataProperty(A, k++, O[from]);
+ }
+
+ // Step 14.
+ return A;
+}
+
/* ES5 15.4.4.18. */
function ArrayForEach(callbackfn /*, thisArg*/) {
/* Step 1. */
diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp
index cde6d59b91..15c0471b6d 100644
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3209,6 +3209,7 @@ static const JSFunctionSpec array_methods[] = {
JS_SELF_HOSTED_FN("findLast", "ArrayFindLast", 1,0),
JS_SELF_HOSTED_FN("findLastIndex", "ArrayFindLastIndex", 1,0),
JS_SELF_HOSTED_FN("toReversed", "ArrayToReversed", 0,0),
+ JS_SELF_HOSTED_FN("toSpliced", "ArrayToSpliced", 2,0),
JS_SELF_HOSTED_FN("toSorted", "ArrayToSorted", 1,0),
JS_SELF_HOSTED_FN("with", "ArrayWith", 2,0),
@@ -3383,6 +3384,7 @@ array_proto_finish(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto)
!DefineProperty(cx, unscopables, cx->names().includes, value) ||
!DefineProperty(cx, unscopables, cx->names().keys, value) ||
!DefineProperty(cx, unscopables, cx->names().toReversed, value) ||
+ !DefineProperty(cx, unscopables, cx->names().toSpliced, value) ||
!DefineProperty(cx, unscopables, cx->names().values, value) ||
!DefineProperty(cx, unscopables, cx->names().toSorted, value) ||
!DefineProperty(cx, unscopables, cx->names().with, value))
diff --git a/js/src/tests/ecma_6/Array/toSpliced.html b/js/src/tests/ecma_6/Array/toSpliced.html
new file mode 100644
index 0000000000..9a8f886016
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/toSpliced.html
@@ -0,0 +1,59 @@
+
+
+
Array.prototype.toSpliced test
+
+
+
+
diff --git a/js/src/tests/ecma_6/Array/toSpliced.js b/js/src/tests/ecma_6/Array/toSpliced.js
new file mode 100644
index 0000000000..141e409776
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/toSpliced.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+assertEq(typeof Array.prototype.toSpliced, "function");
+assertEq(Array.prototype.toSpliced.length, 2);
+
+let desc = Object.getOwnPropertyDescriptor(Array.prototype, "toSpliced");
+assertEq(desc.writable, true);
+assertEq(desc.enumerable, false);
+assertEq(desc.configurable, true);
+
+assertThrowsInstanceOf(function () {
+ Array.prototype.toSpliced.call(null, 0, 0);
+}, TypeError);
+assertThrowsInstanceOf(function () {
+ Array.prototype.toSpliced.call(undefined, 0, 0);
+}, TypeError);
+
+// Non-mutating behavior.
+let original = [1, 2, 3, 4];
+let spliced = original.toSpliced(1, 2, "a", "b");
+assertEq(original !== spliced, true);
+assertEq(original.join(","), "1,2,3,4");
+assertEq(spliced.join(","), "1,a,b,4");
+assertEq(original.toSpliced().join(","), "1,2,3,4");
+
+// Start index clamping and negatives.
+assertEq([1, 2, 3].toSpliced(-1, 1, 9).join(","), "1,2,9");
+assertEq([1, 2, 3].toSpliced(-99, 1, 9).join(","), "9,2,3");
+assertEq([1, 2, 3].toSpliced(99, 1, 9).join(","), "1,2,3,9");
+
+// deleteCount handling.
+assertEq([1, 2, 3].toSpliced(1).join(","), "1");
+assertEq([1, 2, 3].toSpliced(1, -3, 9).join(","), "1,9,2,3");
+
+// Holes are materialized as undefined in copied segments.
+let sparse = [1, , 3, 4];
+let sparseResult = sparse.toSpliced(2, 1, "x");
+assertEq(sparseResult.length, 4);
+assertEq(sparseResult[0], 1);
+assertEq(sparseResult[1], undefined);
+assertEq(1 in sparseResult, true);
+assertEq(sparseResult[2], "x");
+assertEq(sparseResult[3], 4);
+
+// Generic behavior on array-like values.
+let arrayLike = { 0: "a", 1: "b", 3: "d", length: 4 };
+let arrayLikeResult = Array.prototype.toSpliced.call(arrayLike, 1, 2, "X", "Y");
+assertEq(Array.isArray(arrayLikeResult), true);
+assertEq(arrayLikeResult.join(","), "a,X,Y,d");
+
+// Element reads are ascending and skipped region is not read.
+let accessLog = [];
+let getterArr = {
+ length: 5,
+ get 0() {
+ accessLog.push(0);
+ return "z0";
+ },
+ get 1() {
+ accessLog.push(1);
+ throw new Error("must not be read");
+ },
+ get 2() {
+ accessLog.push(2);
+ throw new Error("must not be read");
+ },
+ get 3() {
+ accessLog.push(3);
+ return "z3";
+ },
+ get 4() {
+ accessLog.push(4);
+ return "z4";
+ },
+};
+let getterResult = Array.prototype.toSpliced.call(getterArr, 1, 2, "X");
+assertEq(accessLog.join(","), "0,3,4");
+assertEq(getterResult.join(","), "z0,X,z3,z4");
+
+// constructor / Symbol.species is ignored and constructor is not read.
+let constructorAccessed = false;
+let speciesIgnored = [1, 2, 3];
+Object.defineProperty(speciesIgnored, "constructor", {
+ get: function () {
+ constructorAccessed = true;
+ throw new Error("constructor should not be read");
+ },
+ configurable: true,
+});
+let speciesIgnoredResult = speciesIgnored.toSpliced(1, 1, 9);
+assertEq(constructorAccessed, false);
+assertEq(Array.isArray(speciesIgnoredResult), true);
+assertEq(speciesIgnoredResult.join(","), "1,9,3");
+
+// Length limit check happens before indexed reads.
+let indexedRead = false;
+let tooLong = {
+ length: 4294967296,
+ get 0() {
+ indexedRead = true;
+ throw new Error("index getter should not run");
+ },
+};
+assertThrowsInstanceOf(function () {
+ Array.prototype.toSpliced.call(tooLong, 0, 0);
+}, RangeError);
+assertEq(indexedRead, false);
+
+// @@unscopables must include toSpliced.
+assertEq(Array.prototype[Symbol.unscopables].toSpliced, true);
+
+if (typeof reportCompare === "function") reportCompare(0, 0);
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index 6466958917..64bab961cd 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -430,6 +430,7 @@
macro(toLocaleString, toLocaleString, "toLocaleString") \
macro(toReversed, toReversed, "toReversed") \
macro(toSource, toSource, "toSource") \
+ macro(toSpliced, toSpliced, "toSpliced") \
macro(toSorted, toSorted, "toSorted") \
macro(toString, toString, "toString") \
macro(toUTCString, toUTCString, "toUTCString") \