From d220907468a6c224e7027da15359d46c7c4b3aef Mon Sep 17 00:00:00 2001 From: Basix Date: Tue, 14 Sep 2021 01:25:55 +0900 Subject: [PATCH 01/73] add structured-clone entry points --- packages/core-js-compat/src/data.mjs | 2 ++ packages/core-js/features/strcutured-clone.js | 3 +++ packages/core-js/modules/web.structured-clone.js | 12 ++++++++++++ packages/core-js/stable/structured-clone.js | 7 +++++++ packages/core-js/web/structured-clone.js | 5 +++++ tests/compat/tests.js | 5 ++++- 6 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 packages/core-js/features/strcutured-clone.js create mode 100644 packages/core-js/modules/web.structured-clone.js create mode 100644 packages/core-js/stable/structured-clone.js create mode 100644 packages/core-js/web/structured-clone.js diff --git a/packages/core-js-compat/src/data.mjs b/packages/core-js-compat/src/data.mjs index bda20c3ab54c..cce7bc17d8c5 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -1850,6 +1850,8 @@ export const data = { node: '12.0', // '11.0', safari: '12.1', }, + 'web.structured-clone': { + }, 'web.timers': { android: '1.5', chrome: '1', diff --git a/packages/core-js/features/strcutured-clone.js b/packages/core-js/features/strcutured-clone.js new file mode 100644 index 000000000000..1b48415eed2d --- /dev/null +++ b/packages/core-js/features/strcutured-clone.js @@ -0,0 +1,3 @@ +var parent = require('../stable/structured-clone'); + +module.exports = parent; diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js new file mode 100644 index 000000000000..5ca68010d8a8 --- /dev/null +++ b/packages/core-js/modules/web.structured-clone.js @@ -0,0 +1,12 @@ +var $ = require('../internals/export'); +var getBuiltIn = require('../internals/get-built-in'); +var structuredCloneImpl = require('../internals/structured-clone'); +var WeakMap = getBuiltIn('WeakMap'); + +$({ global: true, enumerable: true }, { + structuredClone: function structuredClone(value/* , { transfer } */) { + var transfer = arguments.length > 1 && arguments[1] !== undefined ? arguments[1].transfer : undefined; + + return structuredCloneImpl(new WeakMap(), value, transfer || []); + } +}); diff --git a/packages/core-js/stable/structured-clone.js b/packages/core-js/stable/structured-clone.js new file mode 100644 index 000000000000..da04d8964f56 --- /dev/null +++ b/packages/core-js/stable/structured-clone.js @@ -0,0 +1,7 @@ +require('../modules/es.weak-map'); +require('../modules/es.map'); +require('../modules/es.set'); +require('../modules/web.structured-clone'); +var path = require('../internals/path'); + +module.exports = path.structuredClone; diff --git a/packages/core-js/web/structured-clone.js b/packages/core-js/web/structured-clone.js new file mode 100644 index 000000000000..efa6a0b914e8 --- /dev/null +++ b/packages/core-js/web/structured-clone.js @@ -0,0 +1,5 @@ + +require('../modules/web.structured-clone'); +var path = require('../internals/path'); + +module.exports = path.structuredClone; diff --git a/tests/compat/tests.js b/tests/compat/tests.js index 22ea4670af90..c7f538e96f4e 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1678,5 +1678,8 @@ GLOBAL.tests = { 'web.url.to-json': [URL_AND_URL_SEARCH_PARAMS_SUPPORT, function () { return URL.prototype.toJSON; }], - 'web.url-search-params': URL_AND_URL_SEARCH_PARAMS_SUPPORT + 'web.url-search-params': URL_AND_URL_SEARCH_PARAMS_SUPPORT, + 'web.structured-clone': function () { + return typeof structuredClone === 'function'; + } }; From ebc8a322ab87911ec7372f8f25541dc20989000e Mon Sep 17 00:00:00 2001 From: Basix Date: Wed, 15 Sep 2021 00:05:15 +0900 Subject: [PATCH 02/73] add cloning of primitives, wrappers, Date, RegExp, Map, Set Actually we should be checking the existance of internal slots, and toString comes close, but @@toStringTag can be overriden. --- .../core-js/internals/structured-clone.js | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 packages/core-js/internals/structured-clone.js diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js new file mode 100644 index 000000000000..e8e38a745c3b --- /dev/null +++ b/packages/core-js/internals/structured-clone.js @@ -0,0 +1,57 @@ +/* eslint-disable es/no-map -- safe */ +/* eslint-disable es/no-set -- safe */ +/* eslint-disable no-new-wrappers -- safe */ +/* eslint-disable es/no-bigint -- safe */ +'use const'; +var isSymbol = require('./is-symbol'); + +function createDataCloneError(message) { + if (typeof DOMException === 'function') { + return new DOMException(message, 'DataCloneError'); + } + return new Error(message); +} + +function structuredCloneInternal(weakmap, value) { + if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable'); + if (typeof value !== 'function' && typeof value !== 'object') return value; + if (weakmap.has(value)) return weakmap.get(value); // effectively preserves circular references + + if (value instanceof Boolean) return new Boolean(value.valueOf()); + if (typeof BigInt === 'function' && value instanceof BigInt) return Object(value.valueOf()); + if (value instanceof Number) return new Number(value.valueOf()); + if (value instanceof String) return new String(value.valueOf()); + if (value instanceof Date) return new Date(value.valueOf()); + + if (value instanceof RegExp) return new RegExp(value); // Not cloning [[RegExpMatcher]], but it will be created + + // TODO: SharedArrayBuffer, ArrayBuffer, DataView, etc + + if (typeof Map === 'function' && value instanceof Map) { + var map = new Map(); + value.forEach(function (v, key) { + map.set(structuredCloneInternal(key), structuredCloneInternal(weakmap, v)); + }); + return map; + } + + if (typeof Set === 'function' && value instanceof Set) { + var set = new Set(); + value.forEach(function (v) { + set.add(structuredCloneInternal(weakmap, v)); + }); + return set; + } +} + +/** + * Tries best to replicate structuredClone behaviour. + * + * @param {WeakMap} weakmap cache map + * @param {any} value object to clone + * @param {Array} transfer transferables, if any + */ +module.exports = function (weakmap, value, transfer) { + // TODO: Implement transfer behaviours. Couldn't find a reliable way to do this. + return structuredCloneInternal(weakmap, value); +}; From 3bb20f1374ed02fb606f416e80ac4fe47d8e2c62 Mon Sep 17 00:00:00 2001 From: Basix Date: Wed, 15 Sep 2021 02:11:06 +0900 Subject: [PATCH 03/73] add cloning of array and object --- .../core-js/internals/structured-clone.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index e8e38a745c3b..f66077b00875 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -4,6 +4,9 @@ /* eslint-disable es/no-bigint -- safe */ 'use const'; var isSymbol = require('./is-symbol'); +var isArray = require('./is-array'); +var isObject = require('./is-object'); +var getOwnPropertyNames = require('./object-get-own-property-names'); function createDataCloneError(message) { if (typeof DOMException === 'function') { @@ -15,6 +18,7 @@ function createDataCloneError(message) { function structuredCloneInternal(weakmap, value) { if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable'); if (typeof value !== 'function' && typeof value !== 'object') return value; + if (value === null) return null; if (weakmap.has(value)) return weakmap.get(value); // effectively preserves circular references if (value instanceof Boolean) return new Boolean(value.valueOf()); @@ -42,6 +46,22 @@ function structuredCloneInternal(weakmap, value) { }); return set; } + + if (isArray(value)) { + return value.map(function (v) { + return structuredCloneInternal(weakmap, v); + }); + } + + if (isObject(value)) { + var rv = {}; + getOwnPropertyNames.f(value).forEach(function (k) { + rv[structuredCloneInternal(weakmap, k)] = structuredCloneInternal(weakmap, value[k]); + }); + return rv; + } + + return value; // TODO: REMOVE THIS } /** From d807b2e2cef6fb559dcd7c9a2a842554e7ef2a1f Mon Sep 17 00:00:00 2001 From: Basix Date: Wed, 15 Sep 2021 02:22:04 +0900 Subject: [PATCH 04/73] cache cloned value in a weakmap --- .../core-js/internals/structured-clone.js | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index f66077b00875..261ac3714343 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -21,47 +21,44 @@ function structuredCloneInternal(weakmap, value) { if (value === null) return null; if (weakmap.has(value)) return weakmap.get(value); // effectively preserves circular references - if (value instanceof Boolean) return new Boolean(value.valueOf()); - if (typeof BigInt === 'function' && value instanceof BigInt) return Object(value.valueOf()); - if (value instanceof Number) return new Number(value.valueOf()); - if (value instanceof String) return new String(value.valueOf()); - if (value instanceof Date) return new Date(value.valueOf()); + var cloned; - if (value instanceof RegExp) return new RegExp(value); // Not cloning [[RegExpMatcher]], but it will be created + if (value instanceof Boolean) cloned = new Boolean(value.valueOf()); + else if (typeof BigInt === 'function' && value instanceof BigInt) cloned = Object(value.valueOf()); + else if (value instanceof Number) cloned = new Number(value.valueOf()); + else if (value instanceof String) cloned = new String(value.valueOf()); + else if (value instanceof Date) cloned = new Date(value.valueOf()); + + else if (value instanceof RegExp) cloned = new RegExp(value); // Not cloning [[RegExpMatcher]], but it will be created // TODO: SharedArrayBuffer, ArrayBuffer, DataView, etc - if (typeof Map === 'function' && value instanceof Map) { + else if (typeof Map === 'function' && value instanceof Map) { var map = new Map(); value.forEach(function (v, key) { map.set(structuredCloneInternal(key), structuredCloneInternal(weakmap, v)); }); - return map; - } - - if (typeof Set === 'function' && value instanceof Set) { + cloned = map; + } else if (typeof Set === 'function' && value instanceof Set) { var set = new Set(); value.forEach(function (v) { set.add(structuredCloneInternal(weakmap, v)); }); - return set; - } - - if (isArray(value)) { - return value.map(function (v) { + cloned = set; + } else if (isArray(value)) { + cloned = value.map(function (v) { return structuredCloneInternal(weakmap, v); }); - } - - if (isObject(value)) { + } else if (isObject(value)) { var rv = {}; getOwnPropertyNames.f(value).forEach(function (k) { rv[structuredCloneInternal(weakmap, k)] = structuredCloneInternal(weakmap, value[k]); }); - return rv; + cloned = rv; } - return value; // TODO: REMOVE THIS + weakmap.set(value, cloned); + return cloned; } /** From 235e6f74d1a44d9746b90e5006eb10ae0ac5dc43 Mon Sep 17 00:00:00 2001 From: Basix Date: Wed, 15 Sep 2021 18:54:23 +0900 Subject: [PATCH 05/73] export structuredCloneInternal directly If transfer behaviour is not replicatable, we don't need wrapping function at all. --- .../core-js/internals/structured-clone.js | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 261ac3714343..e4859132bd63 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -15,7 +15,13 @@ function createDataCloneError(message) { return new Error(message); } -function structuredCloneInternal(weakmap, value) { +/** + * Tries best to replicate structuredClone behaviour. + * + * @param {WeakMap} weakmap cache map + * @param {any} value object to clone + */ +module.exports = function structuredCloneInternal(weakmap, value) { if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable'); if (typeof value !== 'function' && typeof value !== 'object') return value; if (value === null) return null; @@ -59,16 +65,4 @@ function structuredCloneInternal(weakmap, value) { weakmap.set(value, cloned); return cloned; -} - -/** - * Tries best to replicate structuredClone behaviour. - * - * @param {WeakMap} weakmap cache map - * @param {any} value object to clone - * @param {Array} transfer transferables, if any - */ -module.exports = function (weakmap, value, transfer) { - // TODO: Implement transfer behaviours. Couldn't find a reliable way to do this. - return structuredCloneInternal(weakmap, value); }; From 5d7c03c78f0e1eee8667ac727add16f30d1e29b8 Mon Sep 17 00:00:00 2001 From: Basix Date: Wed, 15 Sep 2021 19:57:34 +0900 Subject: [PATCH 06/73] refactor structuredCloneInternal using classof fixed infinite recursion --- .../core-js/internals/structured-clone.js | 91 ++++++++++++------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index e4859132bd63..3c24aa8d98d1 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -4,9 +4,9 @@ /* eslint-disable es/no-bigint -- safe */ 'use const'; var isSymbol = require('./is-symbol'); -var isArray = require('./is-array'); -var isObject = require('./is-object'); +var toObject = require('./to-object'); var getOwnPropertyNames = require('./object-get-own-property-names'); +var classof = require('./classof'); function createDataCloneError(message) { if (typeof DOMException === 'function') { @@ -27,42 +27,65 @@ module.exports = function structuredCloneInternal(weakmap, value) { if (value === null) return null; if (weakmap.has(value)) return weakmap.get(value); // effectively preserves circular references - var cloned; + var cloned, i, deep; - if (value instanceof Boolean) cloned = new Boolean(value.valueOf()); - else if (typeof BigInt === 'function' && value instanceof BigInt) cloned = Object(value.valueOf()); - else if (value instanceof Number) cloned = new Number(value.valueOf()); - else if (value instanceof String) cloned = new String(value.valueOf()); - else if (value instanceof Date) cloned = new Date(value.valueOf()); - - else if (value instanceof RegExp) cloned = new RegExp(value); // Not cloning [[RegExpMatcher]], but it will be created + switch (classof(value)) { + case 'Boolean': + case 'BigInt': + case 'Number': + case 'String': + cloned = toObject(value.valueOf()); + break; + case 'Date': + cloned = new Date(value.valueOf()); + break; + case 'RegExp': + cloned = new RegExp(value); + break; + case 'Map': + cloned = new Map(); + deep = true; + break; + case 'Set': + cloned = new Set(); + deep = true; + break; + case 'Array': + cloned = []; + deep = true; + break; + case 'Object': + cloned = {}; + deep = true; + break; + default: + throw createDataCloneError('Uncloneable type: ' + classof(value)); + } - // TODO: SharedArrayBuffer, ArrayBuffer, DataView, etc + weakmap.set(value, cloned); - else if (typeof Map === 'function' && value instanceof Map) { - var map = new Map(); - value.forEach(function (v, key) { - map.set(structuredCloneInternal(key), structuredCloneInternal(weakmap, v)); - }); - cloned = map; - } else if (typeof Set === 'function' && value instanceof Set) { - var set = new Set(); - value.forEach(function (v) { - set.add(structuredCloneInternal(weakmap, v)); - }); - cloned = set; - } else if (isArray(value)) { - cloned = value.map(function (v) { - return structuredCloneInternal(weakmap, v); - }); - } else if (isObject(value)) { - var rv = {}; - getOwnPropertyNames.f(value).forEach(function (k) { - rv[structuredCloneInternal(weakmap, k)] = structuredCloneInternal(weakmap, value[k]); - }); - cloned = rv; + if (deep) switch (classof(value)) { + case 'Map': + value.forEach(function (v, k) { + cloned.set(structuredCloneInternal(weakmap, k), structuredCloneInternal(weakmap, v)); + }); + break; + case 'Set': + value.forEach(function (v) { + cloned.add(structuredCloneInternal(weakmap, v)); + }); + break; + case 'Array': + for (i = 0; i < value.length; i++) cloned.push(structuredCloneInternal(weakmap, value[i])); + break; + case 'Object': + var properties = getOwnPropertyNames.f(value); + for (i = 0; i < properties.length; i++) { + cloned[structuredCloneInternal(weakmap, properties[i])] = + structuredCloneInternal(weakmap, value[properties[i]]); + } + break; } - weakmap.set(value, cloned); return cloned; }; From 25476cc822ff1fe8300b0cb5d3c8083c78280d5a Mon Sep 17 00:00:00 2001 From: Basix Date: Wed, 15 Sep 2021 23:55:33 +0900 Subject: [PATCH 07/73] treat array as object this conforms to the spec more closely, and it also copies non-indexed key as well --- packages/core-js/internals/structured-clone.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 3c24aa8d98d1..65cbdf6ff3ed 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -76,8 +76,6 @@ module.exports = function structuredCloneInternal(weakmap, value) { }); break; case 'Array': - for (i = 0; i < value.length; i++) cloned.push(structuredCloneInternal(weakmap, value[i])); - break; case 'Object': var properties = getOwnPropertyNames.f(value); for (i = 0; i < properties.length; i++) { From 8fae5ef9ba570deba4f821a987fa99a35b1fc3ba Mon Sep 17 00:00:00 2001 From: Basix Date: Thu, 16 Sep 2021 00:36:09 +0900 Subject: [PATCH 08/73] add tests copied from web-platform-tests with adjustments to work with QUnit. --- tests/tests/web.structured-clone.js | 318 ++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 tests/tests/web.structured-clone.js diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js new file mode 100644 index 000000000000..8b904c29408e --- /dev/null +++ b/tests/tests/web.structured-clone.js @@ -0,0 +1,318 @@ +/* eslint-disable max-nested-callbacks -- teehee */ +/* eslint-disable no-restricted-globals -- wpt */ +/* global structuredClone -- global test */ + +// Originally from: https://github.com/web-platform-tests/wpt/blob/4b35e758e2fc4225368304b02bcec9133965fd1a/IndexedDB/structured-clone.any.js +// Copyright © web-platform-tests contributors. Available under the 3-Clause BSD License. + +QUnit.module('structuredClone', () => { + QUnit.test('identity', assert => { + assert.isFunction(structuredClone, 'structuredClone is a function'); + assert.name(structuredClone, 'structuredClone'); + }); + + function cloneTest(value, verifyFunc) { + verifyFunc(value, structuredClone(value)); + } + + // Specialization of cloneTest() for objects, with common asserts. + function cloneObjectTest(assert, value, verifyFunc) { + cloneTest(value, (orig, clone) => { + assert.notEqual(orig, clone, 'clone should have different reference'); + assert.equal(typeof clone, 'object', 'clone should be an object'); + assert.equal(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone), 'clone should have same prototype'); + verifyFunc(orig, clone); + }); + } + + function cloneFailureTest(assert, value) { + assert.throws(() => structuredClone(value), typeof DOMException === 'function' ? DOMException : Error); + } + + // + // ECMAScript types + // + + // Primitive values: Undefined, Null, Boolean, Number, BigInt, String + const booleans = [false, true]; + const numbers = [ + NaN, + -Infinity, + -Number.MAX_VALUE, + -0xFFFFFFFF, + -0x80000000, + -0x7FFFFFFF, + -1, + -Number.MIN_VALUE, + -0, + 0, + 1, + Number.MIN_VALUE, + 0x7FFFFFFF, + 0x80000000, + 0xFFFFFFFF, + Number.MAX_VALUE, + Infinity, + ]; + // FIXME: Crashes + /* const bigints = [ + -12345678901234567890n, + -1n, + 0n, + 1n, + 12345678901234567890n, + ]; */ + const strings = [ + '', + 'this is a sample string', + 'null(\0)', + ]; + + QUnit.test('primitives', assert => { + [undefined, null].concat(booleans, numbers, /* bigints,*/ strings) + .forEach(value => cloneTest(value, (orig, clone) => { + assert.same(orig, clone, 'primitives should be same after cloned'); + })); + }); + + // "Primitive" Objects (Boolean, Number, BigInt, String) + QUnit.test('primitive objects', assert => { + [].concat(booleans, numbers, strings) + .forEach(value => cloneObjectTest(assert, Object(value), (orig, clone) => { + assert.same(orig.valueOf(), clone.valueOf(), 'primitive wrappers should have same value'); + })); + }); + + // Dates + QUnit.test('Date', assert => { + [ + new Date(-1e13), + new Date(-1e12), + new Date(-1e9), + new Date(-1e6), + new Date(-1e3), + new Date(0), + new Date(1e3), + new Date(1e6), + new Date(1e9), + new Date(1e12), + new Date(1e13), + ].forEach(value => cloneTest(value, (orig, clone) => { + assert.notEqual(orig, clone); + assert.equal(typeof clone, 'object'); + assert.equal(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone)); + assert.equal(orig.valueOf(), clone.valueOf()); + })); + }); + + // Regular Expressions + QUnit.test('RegExp', assert => { + [ + new RegExp(), + /abc/, + /abc/g, + /abc/i, + /abc/gi, + /abc/, + /abc/g, + /abc/i, + /abc/gi, + // /abc/giuy, -- Crashes + ].forEach((value, i) => cloneObjectTest(assert, value, (orig, clone) => { + assert.equal(orig.toString(), clone.toString(), `regex ${ i }`); + })); + }); + + // ArrayBuffer + QUnit.test('ArrayBuffer', assert => { // Crashes + cloneObjectTest(assert, new Uint8Array([0, 1, 254, 255]).buffer, (orig, clone) => { + assert.arrayEqual(new Uint8Array(orig), new Uint8Array(clone)); + }); + }); + + // TODO SharedArrayBuffer + + // Array Buffer Views + QUnit.test('ArrayBufferView', assert => { + [ + new Uint8Array([]), + new Uint8Array([0, 1, 254, 255]), + new Uint16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]), + new Uint32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), + new Int8Array([0, 1, 254, 255]), + new Int16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]), + new Int32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), + new Uint8ClampedArray([0, 1, 254, 255]), + new Float32Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]), + new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, + Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]), + ].forEach(value => cloneObjectTest(assert, value, (orig, clone) => { + assert.arrayEqual(orig, clone); + })); + }); + + // Map + QUnit.test('Map', assert => { + cloneObjectTest(assert, new Map([[1, 2], [3, 4]]), (orig, clone) => { + assert.deepEqual(orig.keys(), clone.keys()); + assert.deepEqual(orig.values(), clone.values()); + }); + }); + + // Set + QUnit.test('Set', assert => { + cloneObjectTest(assert, new Set([1, 2, 3, 4]), (orig, clone) => { + assert.deepEqual(orig.values(), clone.values()); + }); + }); + + // Error + QUnit.test('Error', assert => { + [ + new Error(), + new Error('abc', 'def'), + new EvalError(), + new EvalError('ghi', 'jkl'), + new RangeError(), + new RangeError('ghi', 'jkl'), + new ReferenceError(), + new ReferenceError('ghi', 'jkl'), + new SyntaxError(), + new SyntaxError('ghi', 'jkl'), + new TypeError(), + new TypeError('ghi', 'jkl'), + new URIError(), + new URIError('ghi', 'jkl'), + ].forEach(value => cloneObjectTest(assert, value, (orig, clone) => { + assert.equal(orig.name, clone.name); + assert.equal(orig.message, clone.message); + })); + }); + + // Arrays + QUnit.test('Array', assert => { + [ + [], + [1, 2, 3], + Object.assign( + ['foo', 'bar'], + { 10: true, 11: false, 20: 123, 21: 456, 30: null }), + Object.assign( + ['foo', 'bar'], + { a: true, b: false, foo: 123, bar: 456, '': null }), + ].forEach((value, i) => cloneObjectTest(assert, value, (orig, clone) => { + assert.deepEqual(value, clone, `array content should be same: ${ i }`); + assert.deepEqual(Object.keys(value), Object.keys(clone), `array key should be same: ${ i }`); + Object.keys(orig).forEach(key => { + assert.equal(orig[key], clone[key], `Property ${ key }`); + }); + })); + }); + + // Objects + QUnit.test('Object', assert => { + cloneObjectTest(assert, { foo: true, bar: false }, (orig, clone) => { + assert.deepEqual(Object.keys(orig), Object.keys(clone)); + Object.keys(orig).forEach(key => { + assert.equal(orig[key], clone[key], `Property ${ key }`); + }); + }); + }); + + // + // [Serializable] Platform objects + // + + // TODO: Test these additional interfaces: + // * DOMQuad + // * DOMException + // * RTCCertificate + + // Geometry types + // FIXME: PhantomJS Can't run this test due to unsupported API. + QUnit.test.skip('Geometry types', assert => { + [ + new DOMMatrix(), + new DOMMatrixReadOnly(), + new DOMPoint(), + new DOMPointReadOnly(), + new DOMRect(), + new DOMRectReadOnly(), + ].forEach(value => cloneObjectTest(assert, value, (orig, clone) => { + Object.keys(Object.getPrototypeOf(orig)).forEach(key => { + assert.equal(orig[key], clone[key], `Property ${ key }`); + }); + })); + }); + + // ImageData + // FIXME: PhantomJS Can't run this test due to unsupported API. + QUnit.test.skip('ImageData', assert => { // Crashes + const imageData = new ImageData(8, 8); + for (let i = 0; i < 256; ++i) { + imageData.data[i] = i; + } + cloneObjectTest(assert, imageData, (orig, clone) => { + assert.equal(orig.width, clone.width); + assert.equal(orig.height, clone.height); + assert.arrayEqual(orig.data, clone.data); + }); + }); + + // Blob + QUnit.test('Blob', assert => { + cloneObjectTest( + assert, + new Blob(['This is a test.'], { type: 'a/b' }), + (orig, clone) => { + assert.equal(orig.size, clone.size); + assert.equal(orig.type, clone.type); + // assert.equal(await orig.text(), await clone.text()); + }); + }); + + // File + // FIXME: PhantomJS Can't run this test due to unsupported API. + QUnit.test.skip('File', assert => { + cloneObjectTest( + assert, + new File(['This is a test.'], 'foo.txt', { type: 'c/d' }), + (orig, clone) => { + assert.equal(orig.size, clone.size); + assert.equal(orig.type, clone.type); + assert.equal(orig.name, clone.name); + assert.equal(orig.lastModified, clone.lastModified); + // assert.equal(await orig.text(), await clone.text()); + }); + }); + + // FileList - exposed in Workers, but not constructable. + QUnit.test('FileList', assert => { + if ('document' in self) { + // TODO: Test with populated list. + cloneObjectTest( + assert, + Object.assign(document.createElement('input'), + { type: 'file', multiple: true }).files, + (orig, clone) => { + assert.equal(orig.length, clone.length); + }); + } + }); + + // + // Non-serializable types + // + QUnit.test('Non-serializable types', assert => { + [ + // ECMAScript types + function () { return 1; }, + Symbol('desc'), + + // Non-[Serializable] platform objects + self, + new Event(''), + new MessageChannel(), + ].forEach(cloneFailureTest.bind(null, assert)); + }); +}); From 3c5342a1de84153737046091b2b5a1ec0ee81501 Mon Sep 17 00:00:00 2001 From: Basix Date: Thu, 16 Sep 2021 01:14:46 +0900 Subject: [PATCH 09/73] implement Error cloning I'm not 100% sure stack cloning would work everywhere, but it seems to work on Firefox and Chrome and Node.js. --- .../core-js/internals/structured-clone.js | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 65cbdf6ff3ed..075bc0bbe03f 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -1,7 +1,5 @@ /* eslint-disable es/no-map -- safe */ /* eslint-disable es/no-set -- safe */ -/* eslint-disable no-new-wrappers -- safe */ -/* eslint-disable es/no-bigint -- safe */ 'use const'; var isSymbol = require('./is-symbol'); var toObject = require('./to-object'); @@ -50,6 +48,16 @@ module.exports = function structuredCloneInternal(weakmap, value) { cloned = new Set(); deep = true; break; + case 'Error': + case 'EvalError': + case 'RangeError': + case 'ReferenceError': + case 'SyntaxError': + case 'TypeError': + case 'URIError': + cloned = value.constructor(value.message.toString()); + deep = true; // clone stack after storing in the weakmap + break; case 'Array': cloned = []; deep = true; @@ -75,6 +83,19 @@ module.exports = function structuredCloneInternal(weakmap, value) { cloned.add(structuredCloneInternal(weakmap, v)); }); break; + case 'Error': + // Attempt to clone the stack. + if ( + !Object.prototype.hasOwnProperty.call(value, 'stack') && // Chrome, Safari + !Object.prototype.hasOwnProperty.call(Error.prototype, 'stack') // Firefox + ) break; + try { + cloned.stack = structuredCloneInternal(weakmap, value.stack); + } catch (error) { + if (classof(error) === 'TypeError') return cloned; // Stack cloning not avaliable. + throw error; // Unexpected error while cloning. + } + break; case 'Array': case 'Object': var properties = getOwnPropertyNames.f(value); From 0ebcceaf4c4bdad163c445ebd9273745bc39e429 Mon Sep 17 00:00:00 2001 From: Basix Date: Thu, 16 Sep 2021 21:36:13 +0900 Subject: [PATCH 10/73] apply suggestions from review --- ...trcutured-clone.js => structured-clone.js} | 0 .../core-js/internals/structured-clone.js | 61 +++++++++---------- .../core-js/modules/web.structured-clone.js | 2 +- packages/core-js/stable/structured-clone.js | 1 - packages/core-js/web/structured-clone.js | 3 +- tests/compat/tests.js | 8 +-- 6 files changed, 35 insertions(+), 40 deletions(-) rename packages/core-js/features/{strcutured-clone.js => structured-clone.js} (100%) diff --git a/packages/core-js/features/strcutured-clone.js b/packages/core-js/features/structured-clone.js similarity index 100% rename from packages/core-js/features/strcutured-clone.js rename to packages/core-js/features/structured-clone.js diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 075bc0bbe03f..f606264c2baa 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -1,10 +1,13 @@ -/* eslint-disable es/no-map -- safe */ -/* eslint-disable es/no-set -- safe */ -'use const'; -var isSymbol = require('./is-symbol'); -var toObject = require('./to-object'); -var getOwnPropertyNames = require('./object-get-own-property-names'); -var classof = require('./classof'); +'use strict'; +var isSymbol = require('../internals/is-symbol'); +var getOwnPropertyNames = require('../internals/object-get-own-property-names'); +var classof = require('../internals/classof'); +var getBuiltin = require('../internals/get-built-in'); +var isObject = require('../internals/is-object'); +var has = require('../internals/has'); + +var Set = getBuiltin('Set'); +var Map = getBuiltin('Map'); function createDataCloneError(message) { if (typeof DOMException === 'function') { @@ -16,23 +19,23 @@ function createDataCloneError(message) { /** * Tries best to replicate structuredClone behaviour. * - * @param {WeakMap} weakmap cache map + * @param {Map} map cache map * @param {any} value object to clone */ -module.exports = function structuredCloneInternal(weakmap, value) { +module.exports = function structuredCloneInternal(map, value) { if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable'); - if (typeof value !== 'function' && typeof value !== 'object') return value; - if (value === null) return null; - if (weakmap.has(value)) return weakmap.get(value); // effectively preserves circular references + if (!isObject(value)) return value; + if (map.has(value)) return map.get(value); // effectively preserves circular references var cloned, i, deep; + var type = classof(value); - switch (classof(value)) { + switch (type) { case 'Boolean': case 'BigInt': case 'Number': case 'String': - cloned = toObject(value.valueOf()); + cloned = Object(value.valueOf()); break; case 'Date': cloned = new Date(value.valueOf()); @@ -49,12 +52,6 @@ module.exports = function structuredCloneInternal(weakmap, value) { deep = true; break; case 'Error': - case 'EvalError': - case 'RangeError': - case 'ReferenceError': - case 'SyntaxError': - case 'TypeError': - case 'URIError': cloned = value.constructor(value.message.toString()); deep = true; // clone stack after storing in the weakmap break; @@ -67,41 +64,39 @@ module.exports = function structuredCloneInternal(weakmap, value) { deep = true; break; default: - throw createDataCloneError('Uncloneable type: ' + classof(value)); + throw createDataCloneError('Uncloneable type: ' + type); } - weakmap.set(value, cloned); + map.set(value, cloned); - if (deep) switch (classof(value)) { + if (deep) switch (type) { case 'Map': value.forEach(function (v, k) { - cloned.set(structuredCloneInternal(weakmap, k), structuredCloneInternal(weakmap, v)); + cloned.set(structuredCloneInternal(map, k), structuredCloneInternal(map, v)); }); break; case 'Set': value.forEach(function (v) { - cloned.add(structuredCloneInternal(weakmap, v)); + cloned.add(structuredCloneInternal(map, v)); }); break; case 'Error': // Attempt to clone the stack. if ( - !Object.prototype.hasOwnProperty.call(value, 'stack') && // Chrome, Safari - !Object.prototype.hasOwnProperty.call(Error.prototype, 'stack') // Firefox + !has(value, 'stack') && // Chrome, Safari + !has(Error.prototype, 'stack') // Firefox ) break; try { - cloned.stack = structuredCloneInternal(weakmap, value.stack); - } catch (error) { - if (classof(error) === 'TypeError') return cloned; // Stack cloning not avaliable. - throw error; // Unexpected error while cloning. + cloned.stack = structuredCloneInternal(map, value.stack); + } catch (_) { + return cloned; // Stack cloning not avaliable. } break; case 'Array': case 'Object': var properties = getOwnPropertyNames.f(value); for (i = 0; i < properties.length; i++) { - cloned[structuredCloneInternal(weakmap, properties[i])] = - structuredCloneInternal(weakmap, value[properties[i]]); + cloned[properties[i]] = structuredCloneInternal(map, value[properties[i]]); } break; } diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 5ca68010d8a8..2b1f22362dc9 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -3,7 +3,7 @@ var getBuiltIn = require('../internals/get-built-in'); var structuredCloneImpl = require('../internals/structured-clone'); var WeakMap = getBuiltIn('WeakMap'); -$({ global: true, enumerable: true }, { +$({ global: true, enumerable: true, sham: true }, { structuredClone: function structuredClone(value/* , { transfer } */) { var transfer = arguments.length > 1 && arguments[1] !== undefined ? arguments[1].transfer : undefined; diff --git a/packages/core-js/stable/structured-clone.js b/packages/core-js/stable/structured-clone.js index da04d8964f56..c340088480fb 100644 --- a/packages/core-js/stable/structured-clone.js +++ b/packages/core-js/stable/structured-clone.js @@ -1,4 +1,3 @@ -require('../modules/es.weak-map'); require('../modules/es.map'); require('../modules/es.set'); require('../modules/web.structured-clone'); diff --git a/packages/core-js/web/structured-clone.js b/packages/core-js/web/structured-clone.js index efa6a0b914e8..c340088480fb 100644 --- a/packages/core-js/web/structured-clone.js +++ b/packages/core-js/web/structured-clone.js @@ -1,4 +1,5 @@ - +require('../modules/es.map'); +require('../modules/es.set'); require('../modules/web.structured-clone'); var path = require('../internals/path'); diff --git a/tests/compat/tests.js b/tests/compat/tests.js index c7f538e96f4e..3ab22253901c 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1671,6 +1671,9 @@ GLOBAL.tests = { 'web.queue-microtask': function () { return Object.getOwnPropertyDescriptor(GLOBAL, 'queueMicrotask').value; }, + 'web.structured-clone': function () { + return typeof structuredClone === 'function'; + }, 'web.timers': function () { return !/MSIE .\./.test(USERAGENT); }, @@ -1678,8 +1681,5 @@ GLOBAL.tests = { 'web.url.to-json': [URL_AND_URL_SEARCH_PARAMS_SUPPORT, function () { return URL.prototype.toJSON; }], - 'web.url-search-params': URL_AND_URL_SEARCH_PARAMS_SUPPORT, - 'web.structured-clone': function () { - return typeof structuredClone === 'function'; - } + 'web.url-search-params': URL_AND_URL_SEARCH_PARAMS_SUPPORT }; From 14b3a78029b9af9986159cb1165b974bc94937d8 Mon Sep 17 00:00:00 2001 From: Basix Date: Sun, 26 Sep 2021 01:18:46 +0900 Subject: [PATCH 11/73] implement arraybuffer cloning --- .../core-js/internals/array-buffer-is-deatched.js | 13 +++++++++++++ packages/core-js/internals/structured-clone.js | 10 +++++++++- packages/core-js/stable/structured-clone.js | 1 + packages/core-js/web/structured-clone.js | 1 + 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 packages/core-js/internals/array-buffer-is-deatched.js diff --git a/packages/core-js/internals/array-buffer-is-deatched.js b/packages/core-js/internals/array-buffer-is-deatched.js new file mode 100644 index 000000000000..eb0f19f82378 --- /dev/null +++ b/packages/core-js/internals/array-buffer-is-deatched.js @@ -0,0 +1,13 @@ +'use strict'; +var NATIVE_ARRAY_BUFFER = require('../internals/array-buffer-native'); + +module.exports = function (arrayBuffer) { + if (!NATIVE_ARRAY_BUFFER) return false; // core-js implementation doesn't seem to have deatched state + if (arrayBuffer.byteLength !== 0) return false; + try { + arrayBuffer.slice(0, 0); // https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice + return false; + } catch (error) { + return true; + } +}; diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index f606264c2baa..37bfce871a03 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -5,6 +5,7 @@ var classof = require('../internals/classof'); var getBuiltin = require('../internals/get-built-in'); var isObject = require('../internals/is-object'); var has = require('../internals/has'); +var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); var Set = getBuiltin('Set'); var Map = getBuiltin('Map'); @@ -19,7 +20,7 @@ function createDataCloneError(message) { /** * Tries best to replicate structuredClone behaviour. * - * @param {Map} map cache map + * @param {Map} map cache map * @param {any} value object to clone */ module.exports = function structuredCloneInternal(map, value) { @@ -43,6 +44,13 @@ module.exports = function structuredCloneInternal(map, value) { case 'RegExp': cloned = new RegExp(value); break; + case 'ArrayBuffer': + if (!isArrayBufferDetached()) throw createDataCloneError('ArrayBuffer is deatched'); + // falls through + case 'SharedArrayBuffer': + case 'Blob': + cloned = value.slice(0); + break; case 'Map': cloned = new Map(); deep = true; diff --git a/packages/core-js/stable/structured-clone.js b/packages/core-js/stable/structured-clone.js index c340088480fb..07e0c9874677 100644 --- a/packages/core-js/stable/structured-clone.js +++ b/packages/core-js/stable/structured-clone.js @@ -1,5 +1,6 @@ require('../modules/es.map'); require('../modules/es.set'); +require('../modules/es.array-buffer.slice'); require('../modules/web.structured-clone'); var path = require('../internals/path'); diff --git a/packages/core-js/web/structured-clone.js b/packages/core-js/web/structured-clone.js index c340088480fb..07e0c9874677 100644 --- a/packages/core-js/web/structured-clone.js +++ b/packages/core-js/web/structured-clone.js @@ -1,5 +1,6 @@ require('../modules/es.map'); require('../modules/es.set'); +require('../modules/es.array-buffer.slice'); require('../modules/web.structured-clone'); var path = require('../internals/path'); From 3cb770db0fe9b79e1803068416d5817b6b727fe1 Mon Sep 17 00:00:00 2001 From: Basix Date: Sun, 26 Sep 2021 01:20:40 +0900 Subject: [PATCH 12/73] implement SharedArrayBuffer cloning CAVEATS: this will *not* shrare memory as you might expect for strucutred cloning. --- packages/core-js/internals/structured-clone.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 37bfce871a03..517764264cfc 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -48,7 +48,6 @@ module.exports = function structuredCloneInternal(map, value) { if (!isArrayBufferDetached()) throw createDataCloneError('ArrayBuffer is deatched'); // falls through case 'SharedArrayBuffer': - case 'Blob': cloned = value.slice(0); break; case 'Map': From 5381facdc9ce69910ee6d333b2cc9140bdb1ee63 Mon Sep 17 00:00:00 2001 From: Basix Date: Sun, 26 Sep 2021 01:23:21 +0900 Subject: [PATCH 13/73] implement Blob cloning although the type is different the interface is more or less the same. --- packages/core-js/internals/structured-clone.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 517764264cfc..37bfce871a03 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -48,6 +48,7 @@ module.exports = function structuredCloneInternal(map, value) { if (!isArrayBufferDetached()) throw createDataCloneError('ArrayBuffer is deatched'); // falls through case 'SharedArrayBuffer': + case 'Blob': cloned = value.slice(0); break; case 'Map': From 50a18277de06bf3a8c95a2c0837b2aa6c4bec694 Mon Sep 17 00:00:00 2001 From: Basix Date: Sun, 26 Sep 2021 01:36:31 +0900 Subject: [PATCH 14/73] implement DataView cloning --- packages/core-js/internals/structured-clone.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 37bfce871a03..b55331defda2 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -9,6 +9,7 @@ var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); var Set = getBuiltin('Set'); var Map = getBuiltin('Map'); +var DataView = getBuiltin('DataView'); function createDataCloneError(message) { if (typeof DOMException === 'function') { @@ -51,6 +52,10 @@ module.exports = function structuredCloneInternal(map, value) { case 'Blob': cloned = value.slice(0); break; + case 'DataView': + // this is safe, since arraybuffer cannot have circular references + cloned = new DataView(structuredCloneInternal(value.buffer), value.byteOffset, value.byteLength); + break; case 'Map': cloned = new Map(); deep = true; From 40eccb772254079908dbc0be253d86300dfbfb3e Mon Sep 17 00:00:00 2001 From: Basix Date: Sat, 2 Oct 2021 21:00:06 +0900 Subject: [PATCH 15/73] update compat data --- packages/core-js-compat/src/data.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/core-js-compat/src/data.mjs b/packages/core-js-compat/src/data.mjs index cce7bc17d8c5..09cf91eaf6aa 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -1851,6 +1851,9 @@ export const data = { safari: '12.1', }, 'web.structured-clone': { + deno: '1.14', + node: '17.0.0', + firefox: '94' }, 'web.timers': { android: '1.5', From da59491d17d3f762d94430c843d73e51c71d5d4e Mon Sep 17 00:00:00 2001 From: Basix Date: Tue, 19 Oct 2021 00:27:32 +0900 Subject: [PATCH 16/73] implement TypedArray cloning --- packages/core-js/internals/structured-clone.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index b55331defda2..c1261e3b5100 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -56,6 +56,20 @@ module.exports = function structuredCloneInternal(map, value) { // this is safe, since arraybuffer cannot have circular references cloned = new DataView(structuredCloneInternal(value.buffer), value.byteOffset, value.byteLength); break; + case 'Int8Array': + case 'Uint8Array': + case 'Uint8ClampedArray': + case 'Int16Array': + case 'Uint16Array': + case 'Int32Array': + case 'Uint32Array': + case 'Float32Array': + case 'Float64Array': + case 'BigInt64Array': + case 'BigUint64Array': + // TypedArray + cloned = new value.constructor(value); + break; case 'Map': cloned = new Map(); deep = true; From 071ffae5953e6fa9de9d2e8bbf5def742b11f07b Mon Sep 17 00:00:00 2001 From: Basix Date: Tue, 19 Oct 2021 01:05:12 +0900 Subject: [PATCH 17/73] use map for caching since the map itself does not live longer then the function, it is ok --- packages/core-js/modules/web.structured-clone.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 2b1f22362dc9..914fe97d945e 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -1,12 +1,12 @@ var $ = require('../internals/export'); var getBuiltIn = require('../internals/get-built-in'); var structuredCloneImpl = require('../internals/structured-clone'); -var WeakMap = getBuiltIn('WeakMap'); +var Map = getBuiltIn('Map'); $({ global: true, enumerable: true, sham: true }, { structuredClone: function structuredClone(value/* , { transfer } */) { var transfer = arguments.length > 1 && arguments[1] !== undefined ? arguments[1].transfer : undefined; - return structuredCloneImpl(new WeakMap(), value, transfer || []); + return structuredCloneImpl(new Map(), value, transfer || []); } }); From e6d0a2716ac1c4fdaf6d4fd2ee3daf576dbf90de Mon Sep 17 00:00:00 2001 From: Basix Date: Mon, 18 Oct 2021 16:18:36 +0000 Subject: [PATCH 18/73] rename has --- packages/core-js/internals/structured-clone.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index c1261e3b5100..8f8afc95ef58 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -4,7 +4,7 @@ var getOwnPropertyNames = require('../internals/object-get-own-property-names'); var classof = require('../internals/classof'); var getBuiltin = require('../internals/get-built-in'); var isObject = require('../internals/is-object'); -var has = require('../internals/has'); +var hasOwn = require('../internals/has-own-property'); var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); var Set = getBuiltin('Set'); @@ -110,8 +110,8 @@ module.exports = function structuredCloneInternal(map, value) { case 'Error': // Attempt to clone the stack. if ( - !has(value, 'stack') && // Chrome, Safari - !has(Error.prototype, 'stack') // Firefox + !hasOwn(value, 'stack') && // Chrome, Safari + !hasOwn(Error.prototype, 'stack') // Firefox ) break; try { cloned.stack = structuredCloneInternal(map, value.stack); From 5800aeaffaae644fcd03810fed99d63a981352a4 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 29 Oct 2021 16:21:18 +0700 Subject: [PATCH 19/73] some fixes --- .eslintrc.js | 5 +- packages/core-js-compat/src/data.mjs | 4 +- .../src/modules-by-versions.mjs | 1 + .../internals/array-buffer-is-deatched.js | 7 +-- .../core-js/internals/structured-clone.js | 39 ++++++--------- .../core-js/modules/web.structured-clone.js | 13 +++-- packages/core-js/web/index.js | 1 + tests/commonjs.mjs | 2 + tests/tests/web.structured-clone.js | 50 ++++++++++--------- 9 files changed, 66 insertions(+), 56 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index bc24157c47dc..41fca105a55d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1119,11 +1119,12 @@ module.exports = { 'tests/compat/**', ], globals: { - compositeKey: READONLY, - compositeSymbol: READONLY, AsyncIterator: READONLY, Iterator: READONLY, Observable: READONLY, + compositeKey: READONLY, + compositeSymbol: READONLY, + structuredClone: READONLY, }, }, { diff --git a/packages/core-js-compat/src/data.mjs b/packages/core-js-compat/src/data.mjs index 09cf91eaf6aa..b3f7a3b4a619 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -1852,8 +1852,8 @@ export const data = { }, 'web.structured-clone': { deno: '1.14', - node: '17.0.0', - firefox: '94' + firefox: '94', + node: '17.0', }, 'web.timers': { android: '1.5', diff --git a/packages/core-js-compat/src/modules-by-versions.mjs b/packages/core-js-compat/src/modules-by-versions.mjs index 2440ecd67d3c..b6f0f2dde692 100644 --- a/packages/core-js-compat/src/modules-by-versions.mjs +++ b/packages/core-js-compat/src/modules-by-versions.mjs @@ -133,5 +133,6 @@ export default { 'web.dom-exception.constructor', 'web.dom-exception.stack', 'web.dom-exception.to-string-tag', + 'web.structured-clone', ], }; diff --git a/packages/core-js/internals/array-buffer-is-deatched.js b/packages/core-js/internals/array-buffer-is-deatched.js index eb0f19f82378..b15344c6dca1 100644 --- a/packages/core-js/internals/array-buffer-is-deatched.js +++ b/packages/core-js/internals/array-buffer-is-deatched.js @@ -1,11 +1,12 @@ -'use strict'; var NATIVE_ARRAY_BUFFER = require('../internals/array-buffer-native'); module.exports = function (arrayBuffer) { - if (!NATIVE_ARRAY_BUFFER) return false; // core-js implementation doesn't seem to have deatched state + // core-js implementation doesn't have deatched state + if (!NATIVE_ARRAY_BUFFER) return false; if (arrayBuffer.byteLength !== 0) return false; try { - arrayBuffer.slice(0, 0); // https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice + // https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice + arrayBuffer.slice(0, 0); return false; } catch (error) { return true; diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 8f8afc95ef58..0ec7e36d8bd4 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -1,15 +1,15 @@ 'use strict'; var isSymbol = require('../internals/is-symbol'); -var getOwnPropertyNames = require('../internals/object-get-own-property-names'); var classof = require('../internals/classof'); var getBuiltin = require('../internals/get-built-in'); var isObject = require('../internals/is-object'); var hasOwn = require('../internals/has-own-property'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); +var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); var Set = getBuiltin('Set'); var Map = getBuiltin('Map'); -var DataView = getBuiltin('DataView'); function createDataCloneError(message) { if (typeof DOMException === 'function') { @@ -27,9 +27,10 @@ function createDataCloneError(message) { module.exports = function structuredCloneInternal(map, value) { if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable'); if (!isObject(value)) return value; - if (map.has(value)) return map.get(value); // effectively preserves circular references + // effectively preserves circular references + if (map.has(value)) return map.get(value); - var cloned, i, deep; + var cloned, deep, key; var type = classof(value); switch (type) { @@ -46,15 +47,17 @@ module.exports = function structuredCloneInternal(map, value) { cloned = new RegExp(value); break; case 'ArrayBuffer': - if (!isArrayBufferDetached()) throw createDataCloneError('ArrayBuffer is deatched'); + if (isArrayBufferDetached(value)) throw createDataCloneError('ArrayBuffer is deatched'); // falls through case 'SharedArrayBuffer': - case 'Blob': cloned = value.slice(0); break; + case 'Blob': + cloned = value.slice(0, value.size, value.type); + break; case 'DataView': - // this is safe, since arraybuffer cannot have circular references - cloned = new DataView(structuredCloneInternal(value.buffer), value.byteOffset, value.byteLength); + // eslint-disable-next-line es/no-typed-arrays -- ok + cloned = new DataView(structuredCloneInternal(map, value.buffer), value.byteOffset, value.byteLength); break; case 'Int8Array': case 'Uint8Array': @@ -67,8 +70,8 @@ module.exports = function structuredCloneInternal(map, value) { case 'Float64Array': case 'BigInt64Array': case 'BigUint64Array': - // TypedArray - cloned = new value.constructor(value); + // this is safe, since arraybuffer cannot have circular references + cloned = new value.constructor(structuredCloneInternal(map, value.buffer), value.byteOffset, value.length); break; case 'Map': cloned = new Map(); @@ -108,22 +111,12 @@ module.exports = function structuredCloneInternal(map, value) { }); break; case 'Error': - // Attempt to clone the stack. - if ( - !hasOwn(value, 'stack') && // Chrome, Safari - !hasOwn(Error.prototype, 'stack') // Firefox - ) break; - try { - cloned.stack = structuredCloneInternal(map, value.stack); - } catch (_) { - return cloned; // Stack cloning not avaliable. - } + if (ERROR_STACK_INSTALLABLE) createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(map, value.stack)); break; case 'Array': case 'Object': - var properties = getOwnPropertyNames.f(value); - for (i = 0; i < properties.length; i++) { - cloned[properties[i]] = structuredCloneInternal(map, value[properties[i]]); + for (key in value) if (hasOwn(value, key)) { + cloned[key] = structuredCloneInternal(map, value[key]); } break; } diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 914fe97d945e..ba073b67b3a9 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -1,12 +1,19 @@ var $ = require('../internals/export'); +var global = require('../internals/global'); var getBuiltIn = require('../internals/get-built-in'); +var anObject = require('../internals/an-object'); var structuredCloneImpl = require('../internals/structured-clone'); + var Map = getBuiltIn('Map'); +var TypeError = global.TypeError; $({ global: true, enumerable: true, sham: true }, { - structuredClone: function structuredClone(value/* , { transfer } */) { - var transfer = arguments.length > 1 && arguments[1] !== undefined ? arguments[1].transfer : undefined; + structuredClone: function structuredClone(value /* , { transfer } */) { + var options = arguments.length > 1 ? anObject(arguments[1]) : undefined; + var transfer = options && options.transfer; + + if (transfer !== undefined) throw new TypeError('Transfer option is not supported'); - return structuredCloneImpl(new Map(), value, transfer || []); + return structuredCloneImpl(new Map(), value); } }); diff --git a/packages/core-js/web/index.js b/packages/core-js/web/index.js index 79a4ec54ee46..69bf1f429251 100644 --- a/packages/core-js/web/index.js +++ b/packages/core-js/web/index.js @@ -5,6 +5,7 @@ require('../modules/web.dom-exception.stack'); require('../modules/web.dom-exception.to-string-tag'); require('../modules/web.immediate'); require('../modules/web.queue-microtask'); +require('../modules/web.structured-clone'); require('../modules/web.timers'); require('../modules/web.url'); require('../modules/web.url.to-json'); diff --git a/tests/commonjs.mjs b/tests/commonjs.mjs index c749fb8b83e5..008a03076aa6 100644 --- a/tests/commonjs.mjs +++ b/tests/commonjs.mjs @@ -554,6 +554,7 @@ for (PATH of ['core-js-pure', 'core-js']) { ok(typeof load(NS, 'set-timeout') == 'function'); ok(typeof load(NS, 'set-interval') == 'function'); ok(typeof load(NS, 'set-immediate') == 'function'); + ok(load(NS, 'structured-clone')(42) === 42); ok(typeof load(NS, 'clear-immediate') == 'function'); ok(typeof load(NS, 'queue-microtask') == 'function'); ok(typeof load(NS, 'url') == 'function'); @@ -847,6 +848,7 @@ for (PATH of ['core-js-pure', 'core-js']) { ok(load('web/dom-collections')); ok(load('web/immediate')); ok(load('web/queue-microtask')); + ok(load('web/structured-clone')(42) === 42); ok(load('web/timers')); ok(load('web/url')); ok(load('web/url-search-params')); diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 8b904c29408e..ae54e76519d0 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -1,9 +1,12 @@ /* eslint-disable max-nested-callbacks -- teehee */ /* eslint-disable no-restricted-globals -- wpt */ -/* global structuredClone -- global test */ // Originally from: https://github.com/web-platform-tests/wpt/blob/4b35e758e2fc4225368304b02bcec9133965fd1a/IndexedDB/structured-clone.any.js // Copyright © web-platform-tests contributors. Available under the 3-Clause BSD License. +import { DESCRIPTORS } from '../helpers/constants'; + +const { from } = Array; +const { assign, getPrototypeOf, keys } = Object; QUnit.module('structuredClone', () => { QUnit.test('identity', assert => { @@ -20,7 +23,7 @@ QUnit.module('structuredClone', () => { cloneTest(value, (orig, clone) => { assert.notEqual(orig, clone, 'clone should have different reference'); assert.equal(typeof clone, 'object', 'clone should be an object'); - assert.equal(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone), 'clone should have same prototype'); + assert.equal(getPrototypeOf(orig), getPrototypeOf(clone), 'clone should have same prototype'); verifyFunc(orig, clone); }); } @@ -100,7 +103,7 @@ QUnit.module('structuredClone', () => { ].forEach(value => cloneTest(value, (orig, clone) => { assert.notEqual(orig, clone); assert.equal(typeof clone, 'object'); - assert.equal(Object.getPrototypeOf(orig), Object.getPrototypeOf(clone)); + assert.equal(getPrototypeOf(orig), getPrototypeOf(clone)); assert.equal(orig.valueOf(), clone.valueOf()); })); }); @@ -124,7 +127,7 @@ QUnit.module('structuredClone', () => { }); // ArrayBuffer - QUnit.test('ArrayBuffer', assert => { // Crashes + if (DESCRIPTORS) QUnit.test('ArrayBuffer', assert => { // Crashes cloneObjectTest(assert, new Uint8Array([0, 1, 254, 255]).buffer, (orig, clone) => { assert.arrayEqual(new Uint8Array(orig), new Uint8Array(clone)); }); @@ -133,7 +136,7 @@ QUnit.module('structuredClone', () => { // TODO SharedArrayBuffer // Array Buffer Views - QUnit.test('ArrayBufferView', assert => { + if (DESCRIPTORS) QUnit.test('ArrayBufferView', assert => { [ new Uint8Array([]), new Uint8Array([0, 1, 254, 255]), @@ -144,8 +147,9 @@ QUnit.module('structuredClone', () => { new Int32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), new Uint8ClampedArray([0, 1, 254, 255]), new Float32Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]), - new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, - Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]), + // TODO: + // new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, + // Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]), ].forEach(value => cloneObjectTest(assert, value, (orig, clone) => { assert.arrayEqual(orig, clone); })); @@ -154,15 +158,15 @@ QUnit.module('structuredClone', () => { // Map QUnit.test('Map', assert => { cloneObjectTest(assert, new Map([[1, 2], [3, 4]]), (orig, clone) => { - assert.deepEqual(orig.keys(), clone.keys()); - assert.deepEqual(orig.values(), clone.values()); + assert.deepEqual(from(orig.keys()), from(clone.keys())); + assert.deepEqual(from(orig.values()), from(clone.values())); }); }); // Set QUnit.test('Set', assert => { cloneObjectTest(assert, new Set([1, 2, 3, 4]), (orig, clone) => { - assert.deepEqual(orig.values(), clone.values()); + assert.deepEqual(from(orig.values()), from(clone.values())); }); }); @@ -194,16 +198,16 @@ QUnit.module('structuredClone', () => { [ [], [1, 2, 3], - Object.assign( + assign( ['foo', 'bar'], { 10: true, 11: false, 20: 123, 21: 456, 30: null }), - Object.assign( + assign( ['foo', 'bar'], { a: true, b: false, foo: 123, bar: 456, '': null }), ].forEach((value, i) => cloneObjectTest(assert, value, (orig, clone) => { assert.deepEqual(value, clone, `array content should be same: ${ i }`); - assert.deepEqual(Object.keys(value), Object.keys(clone), `array key should be same: ${ i }`); - Object.keys(orig).forEach(key => { + assert.deepEqual(keys(value), keys(clone), `array key should be same: ${ i }`); + keys(orig).forEach(key => { assert.equal(orig[key], clone[key], `Property ${ key }`); }); })); @@ -212,8 +216,8 @@ QUnit.module('structuredClone', () => { // Objects QUnit.test('Object', assert => { cloneObjectTest(assert, { foo: true, bar: false }, (orig, clone) => { - assert.deepEqual(Object.keys(orig), Object.keys(clone)); - Object.keys(orig).forEach(key => { + assert.deepEqual(keys(orig), keys(clone)); + keys(orig).forEach(key => { assert.equal(orig[key], clone[key], `Property ${ key }`); }); }); @@ -230,7 +234,7 @@ QUnit.module('structuredClone', () => { // Geometry types // FIXME: PhantomJS Can't run this test due to unsupported API. - QUnit.test.skip('Geometry types', assert => { + QUnit.skip('Geometry types', assert => { [ new DOMMatrix(), new DOMMatrixReadOnly(), @@ -239,7 +243,7 @@ QUnit.module('structuredClone', () => { new DOMRect(), new DOMRectReadOnly(), ].forEach(value => cloneObjectTest(assert, value, (orig, clone) => { - Object.keys(Object.getPrototypeOf(orig)).forEach(key => { + keys(getPrototypeOf(orig)).forEach(key => { assert.equal(orig[key], clone[key], `Property ${ key }`); }); })); @@ -247,7 +251,7 @@ QUnit.module('structuredClone', () => { // ImageData // FIXME: PhantomJS Can't run this test due to unsupported API. - QUnit.test.skip('ImageData', assert => { // Crashes + QUnit.skip('ImageData', assert => { // Crashes const imageData = new ImageData(8, 8); for (let i = 0; i < 256; ++i) { imageData.data[i] = i; @@ -273,7 +277,7 @@ QUnit.module('structuredClone', () => { // File // FIXME: PhantomJS Can't run this test due to unsupported API. - QUnit.test.skip('File', assert => { + QUnit.skip('File', assert => { cloneObjectTest( assert, new File(['This is a test.'], 'foo.txt', { type: 'c/d' }), @@ -287,12 +291,12 @@ QUnit.module('structuredClone', () => { }); // FileList - exposed in Workers, but not constructable. - QUnit.test('FileList', assert => { + QUnit.skip('FileList', assert => { if ('document' in self) { // TODO: Test with populated list. cloneObjectTest( assert, - Object.assign(document.createElement('input'), + assign(document.createElement('input'), { type: 'file', multiple: true }).files, (orig, clone) => { assert.equal(orig.length, clone.length); @@ -313,6 +317,6 @@ QUnit.module('structuredClone', () => { self, new Event(''), new MessageChannel(), - ].forEach(cloneFailureTest.bind(null, assert)); + ].forEach(it => cloneFailureTest(assert, it)); }); }); From e149ca8decf8ad1b8dafeca6f0c5c1dc2d7e680d Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 29 Oct 2021 18:10:05 +0700 Subject: [PATCH 20/73] clone `File` and `ImageData` --- .../core-js/internals/structured-clone.js | 33 ++++++++++++++++--- tests/tests/web.structured-clone.js | 18 ++++------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 0ec7e36d8bd4..9f84a220d71a 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -52,12 +52,13 @@ module.exports = function structuredCloneInternal(map, value) { case 'SharedArrayBuffer': cloned = value.slice(0); break; - case 'Blob': - cloned = value.slice(0, value.size, value.type); - break; case 'DataView': // eslint-disable-next-line es/no-typed-arrays -- ok - cloned = new DataView(structuredCloneInternal(map, value.buffer), value.byteOffset, value.byteLength); + cloned = new DataView( + structuredCloneInternal(map, value.buffer), + value.byteOffset, + value.byteLength + ); break; case 'Int8Array': case 'Uint8Array': @@ -71,7 +72,11 @@ module.exports = function structuredCloneInternal(map, value) { case 'BigInt64Array': case 'BigUint64Array': // this is safe, since arraybuffer cannot have circular references - cloned = new value.constructor(structuredCloneInternal(map, value.buffer), value.byteOffset, value.length); + cloned = new value.constructor( + structuredCloneInternal(map, value.buffer), + value.byteOffset, + value.length + ); break; case 'Map': cloned = new Map(); @@ -93,6 +98,24 @@ module.exports = function structuredCloneInternal(map, value) { cloned = {}; deep = true; break; + case 'Blob': + cloned = value.slice(0, value.size, value.type); + break; + case 'File': + cloned = new File( + [value], + value.name, + { type: value.type, lastModified: value.lastModified } + ); + break; + case 'ImageData': + cloned = new ImageData( + structuredCloneInternal(map, value.data), + value.width, + value.height, + { colorSpace: value.colorSpace } + ); + break; default: throw createDataCloneError('Uncloneable type: ' + type); } diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index ae54e76519d0..73730b8e3a8d 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -147,9 +147,7 @@ QUnit.module('structuredClone', () => { new Int32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), new Uint8ClampedArray([0, 1, 254, 255]), new Float32Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]), - // TODO: - // new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, - // Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]), + new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]), ].forEach(value => cloneObjectTest(assert, value, (orig, clone) => { assert.arrayEqual(orig, clone); })); @@ -249,9 +247,7 @@ QUnit.module('structuredClone', () => { })); }); - // ImageData - // FIXME: PhantomJS Can't run this test due to unsupported API. - QUnit.skip('ImageData', assert => { // Crashes + if (typeof ImageData == 'function') QUnit.test('ImageData', assert => { const imageData = new ImageData(8, 8); for (let i = 0; i < 256; ++i) { imageData.data[i] = i; @@ -259,25 +255,24 @@ QUnit.module('structuredClone', () => { cloneObjectTest(assert, imageData, (orig, clone) => { assert.equal(orig.width, clone.width); assert.equal(orig.height, clone.height); + assert.equal(orig.colorSpace, clone.colorSpace); assert.arrayEqual(orig.data, clone.data); }); }); - // Blob - QUnit.test('Blob', assert => { + if (typeof Blob == 'function') QUnit.test('Blob', assert => { cloneObjectTest( assert, new Blob(['This is a test.'], { type: 'a/b' }), (orig, clone) => { assert.equal(orig.size, clone.size); assert.equal(orig.type, clone.type); + // TODO: async // assert.equal(await orig.text(), await clone.text()); }); }); - // File - // FIXME: PhantomJS Can't run this test due to unsupported API. - QUnit.skip('File', assert => { + if (typeof File == 'function') QUnit.test('File', assert => { cloneObjectTest( assert, new File(['This is a test.'], 'foo.txt', { type: 'c/d' }), @@ -286,6 +281,7 @@ QUnit.module('structuredClone', () => { assert.equal(orig.type, clone.type); assert.equal(orig.name, clone.name); assert.equal(orig.lastModified, clone.lastModified); + // TODO: async // assert.equal(await orig.text(), await clone.text()); }); }); From 34b6f80a8387c6d5dad6cc428de858a49035db00 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 29 Oct 2021 18:53:55 +0700 Subject: [PATCH 21/73] improve tests --- tests/tests/web.structured-clone.js | 125 ++++++++++++++++------------ 1 file changed, 70 insertions(+), 55 deletions(-) diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 73730b8e3a8d..f5b1a53bf8cd 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -1,9 +1,7 @@ -/* eslint-disable max-nested-callbacks -- teehee */ -/* eslint-disable no-restricted-globals -- wpt */ - // Originally from: https://github.com/web-platform-tests/wpt/blob/4b35e758e2fc4225368304b02bcec9133965fd1a/IndexedDB/structured-clone.any.js // Copyright © web-platform-tests contributors. Available under the 3-Clause BSD License. -import { DESCRIPTORS } from '../helpers/constants'; +import { DESCRIPTORS, GLOBAL } from '../helpers/constants'; +import { fromSource } from '../helpers/helpers'; const { from } = Array; const { assign, getPrototypeOf, keys } = Object; @@ -12,6 +10,7 @@ QUnit.module('structuredClone', () => { QUnit.test('identity', assert => { assert.isFunction(structuredClone, 'structuredClone is a function'); assert.name(structuredClone, 'structuredClone'); + assert.arity(structuredClone, 1); }); function cloneTest(value, verifyFunc) { @@ -57,14 +56,15 @@ QUnit.module('structuredClone', () => { Number.MAX_VALUE, Infinity, ]; - // FIXME: Crashes - /* const bigints = [ + + const bigints = fromSource(`[ -12345678901234567890n, -1n, 0n, 1n, 12345678901234567890n, - ]; */ + ]`) || []; + const strings = [ '', 'this is a sample string', @@ -72,23 +72,25 @@ QUnit.module('structuredClone', () => { ]; QUnit.test('primitives', assert => { - [undefined, null].concat(booleans, numbers, /* bigints,*/ strings) - .forEach(value => cloneTest(value, (orig, clone) => { - assert.same(orig, clone, 'primitives should be same after cloned'); - })); + const primitives = [undefined, null].concat(booleans, numbers, bigints, strings); + + for (const value of primitives) cloneTest(value, (orig, clone) => { + assert.same(orig, clone, 'primitives should be same after cloned'); + }); }); // "Primitive" Objects (Boolean, Number, BigInt, String) QUnit.test('primitive objects', assert => { - [].concat(booleans, numbers, strings) - .forEach(value => cloneObjectTest(assert, Object(value), (orig, clone) => { - assert.same(orig.valueOf(), clone.valueOf(), 'primitive wrappers should have same value'); - })); + const primitives = [].concat(booleans, numbers, bigints, strings); + + for (const value of primitives) cloneObjectTest(assert, Object(value), (orig, clone) => { + assert.same(orig.valueOf(), clone.valueOf(), 'primitive wrappers should have same value'); + }); }); // Dates QUnit.test('Date', assert => { - [ + const dates = [ new Date(-1e13), new Date(-1e12), new Date(-1e9), @@ -100,17 +102,19 @@ QUnit.module('structuredClone', () => { new Date(1e9), new Date(1e12), new Date(1e13), - ].forEach(value => cloneTest(value, (orig, clone) => { + ]; + + for (const date of dates) cloneTest(date, (orig, clone) => { assert.notEqual(orig, clone); assert.equal(typeof clone, 'object'); assert.equal(getPrototypeOf(orig), getPrototypeOf(clone)); assert.equal(orig.valueOf(), clone.valueOf()); - })); + }); }); // Regular Expressions QUnit.test('RegExp', assert => { - [ + const regexes = [ new RegExp(), /abc/, /abc/g, @@ -120,10 +124,14 @@ QUnit.module('structuredClone', () => { /abc/g, /abc/i, /abc/gi, - // /abc/giuy, -- Crashes - ].forEach((value, i) => cloneObjectTest(assert, value, (orig, clone) => { - assert.equal(orig.toString(), clone.toString(), `regex ${ i }`); - })); + ]; + + const giuy = fromSource('/abc/giuy'); + if (giuy) regexes.push(giuy); + + for (const regex of regexes) cloneObjectTest(assert, regex, (orig, clone) => { + assert.equal(orig.toString(), clone.toString(), `regex ${ regex }`); + }); }); // ArrayBuffer @@ -137,7 +145,7 @@ QUnit.module('structuredClone', () => { // Array Buffer Views if (DESCRIPTORS) QUnit.test('ArrayBufferView', assert => { - [ + const arrays = [ new Uint8Array([]), new Uint8Array([0, 1, 254, 255]), new Uint16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]), @@ -148,9 +156,11 @@ QUnit.module('structuredClone', () => { new Uint8ClampedArray([0, 1, 254, 255]), new Float32Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]), new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]), - ].forEach(value => cloneObjectTest(assert, value, (orig, clone) => { + ]; + + for (const array of arrays) cloneObjectTest(assert, array, (orig, clone) => { assert.arrayEqual(orig, clone); - })); + }); }); // Map @@ -170,7 +180,7 @@ QUnit.module('structuredClone', () => { // Error QUnit.test('Error', assert => { - [ + const errors = [ new Error(), new Error('abc', 'def'), new EvalError(), @@ -185,15 +195,17 @@ QUnit.module('structuredClone', () => { new TypeError('ghi', 'jkl'), new URIError(), new URIError('ghi', 'jkl'), - ].forEach(value => cloneObjectTest(assert, value, (orig, clone) => { + ]; + + for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { assert.equal(orig.name, clone.name); assert.equal(orig.message, clone.message); - })); + }); }); // Arrays QUnit.test('Array', assert => { - [ + const arrays = [ [], [1, 2, 3], assign( @@ -202,22 +214,24 @@ QUnit.module('structuredClone', () => { assign( ['foo', 'bar'], { a: true, b: false, foo: 123, bar: 456, '': null }), - ].forEach((value, i) => cloneObjectTest(assert, value, (orig, clone) => { - assert.deepEqual(value, clone, `array content should be same: ${ i }`); - assert.deepEqual(keys(value), keys(clone), `array key should be same: ${ i }`); - keys(orig).forEach(key => { + ]; + + for (const array of arrays) cloneObjectTest(assert, array, (orig, clone) => { + assert.deepEqual(orig, clone, `array content should be same: ${ array }`); + assert.deepEqual(keys(orig), keys(clone), `array key should be same: ${ array }`); + for (const key of keys(orig)) { assert.equal(orig[key], clone[key], `Property ${ key }`); - }); - })); + } + }); }); // Objects QUnit.test('Object', assert => { cloneObjectTest(assert, { foo: true, bar: false }, (orig, clone) => { assert.deepEqual(keys(orig), keys(clone)); - keys(orig).forEach(key => { + for (const key of keys(orig)) { assert.equal(orig[key], clone[key], `Property ${ key }`); - }); + } }); }); @@ -233,18 +247,20 @@ QUnit.module('structuredClone', () => { // Geometry types // FIXME: PhantomJS Can't run this test due to unsupported API. QUnit.skip('Geometry types', assert => { - [ + const array = [ new DOMMatrix(), new DOMMatrixReadOnly(), new DOMPoint(), new DOMPointReadOnly(), new DOMRect(), new DOMRectReadOnly(), - ].forEach(value => cloneObjectTest(assert, value, (orig, clone) => { - keys(getPrototypeOf(orig)).forEach(key => { + ]; + + for (const typ of array) cloneObjectTest(assert, typ, (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { assert.equal(orig[key], clone[key], `Property ${ key }`); - }); - })); + } + }); }); if (typeof ImageData == 'function') QUnit.test('ImageData', assert => { @@ -288,15 +304,13 @@ QUnit.module('structuredClone', () => { // FileList - exposed in Workers, but not constructable. QUnit.skip('FileList', assert => { - if ('document' in self) { + if ('document' in GLOBAL) { // TODO: Test with populated list. cloneObjectTest( assert, - assign(document.createElement('input'), - { type: 'file', multiple: true }).files, - (orig, clone) => { - assert.equal(orig.length, clone.length); - }); + assign(document.createElement('input'), { type: 'file', multiple: true }).files, + (orig, clone) => assert.equal(orig.length, clone.length), + ); } }); @@ -304,15 +318,16 @@ QUnit.module('structuredClone', () => { // Non-serializable types // QUnit.test('Non-serializable types', assert => { - [ + const nons = [ // ECMAScript types function () { return 1; }, Symbol('desc'), + GLOBAL, + ]; + + if (typeof Event == 'function') nons.push(new Event('')); + if (typeof MessageChannel == 'function') nons.push(new MessageChannel()); - // Non-[Serializable] platform objects - self, - new Event(''), - new MessageChannel(), - ].forEach(it => cloneFailureTest(assert, it)); + for (const it of nons) cloneFailureTest(assert, it); }); }); From ae44dee6283e6a0a2e5bc7085a9cccf51060f0b5 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 29 Oct 2021 20:10:13 +0700 Subject: [PATCH 22/73] clone `DOMException`, fix some specific `Error` cases --- .../core-js/internals/structured-clone.js | 47 +++++++++++++++++-- tests/tests/web.structured-clone.js | 31 +++++++++++- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 9f84a220d71a..216206b5b796 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -1,13 +1,22 @@ 'use strict'; -var isSymbol = require('../internals/is-symbol'); -var classof = require('../internals/classof'); +var global = require('../internals/global'); var getBuiltin = require('../internals/get-built-in'); var isObject = require('../internals/is-object'); +var isSymbol = require('../internals/is-symbol'); +var classof = require('../internals/classof'); var hasOwn = require('../internals/has-own-property'); var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); +var Date = global.Date; +var Error = global.Error; +var EvalError = global.EvalError; +var RangeError = global.RangeError; +var ReferenceError = global.ReferenceError; +var SyntaxError = global.SyntaxError; +var TypeError = global.TypeError; +var URIError = global.URIError; var Set = getBuiltin('Set'); var Map = getBuiltin('Map'); @@ -30,7 +39,7 @@ module.exports = function structuredCloneInternal(map, value) { // effectively preserves circular references if (map.has(value)) return map.get(value); - var cloned, deep, key; + var C, cloned, deep, key; var type = classof(value); switch (type) { @@ -87,7 +96,32 @@ module.exports = function structuredCloneInternal(map, value) { deep = true; break; case 'Error': - cloned = value.constructor(value.message.toString()); + switch (value.name) { + case 'Error': + C = Error; + break; + case 'EvalError': + C = EvalError; + break; + case 'RangeError': + C = RangeError; + break; + case 'ReferenceError': + C = ReferenceError; + break; + case 'SyntaxError': + C = SyntaxError; + break; + case 'TypeError': + C = TypeError; + break; + case 'URIError': + C = URIError; + break; + default: + C = Error; + } + cloned = C(value.message); deep = true; // clone stack after storing in the weakmap break; case 'Array': @@ -101,6 +135,10 @@ module.exports = function structuredCloneInternal(map, value) { case 'Blob': cloned = value.slice(0, value.size, value.type); break; + case 'DOMException': + cloned = new (getBuiltin('DOMException'))(value.message, value.name); + deep = true; // clone stack after storing in the weakmap + break; case 'File': cloned = new File( [value], @@ -134,6 +172,7 @@ module.exports = function structuredCloneInternal(map, value) { }); break; case 'Error': + case 'DOMException': if (ERROR_STACK_INSTALLABLE) createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(map, value.stack)); break; case 'Array': diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index f5b1a53bf8cd..e5aa01bc922d 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -198,9 +198,24 @@ QUnit.module('structuredClone', () => { ]; for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - assert.equal(orig.name, clone.name); + assert.equal(orig.constructor === AggregateError ? Error : orig.constructor, clone.constructor); + assert.equal(orig.name === 'AggregateError' ? 'Error' : orig.name, clone.name); assert.equal(orig.message, clone.message); + assert.equal(orig.stack, clone.stack); }); + + const aggregates = [ + new AggregateError([1, 2]), + new AggregateError([1, 2], 42), + ]; + + for (const error of aggregates) { + const clone = structuredClone(error); + assert.equal(Error, clone.constructor); + assert.equal('Error', clone.name); + assert.equal(error.message, clone.message); + assert.equal(error.stack, clone.stack); + } }); // Arrays @@ -288,6 +303,20 @@ QUnit.module('structuredClone', () => { }); }); + if (typeof DOMException == 'function') QUnit.test('DOMException', assert => { + const errors = [ + new DOMException(), + new DOMException('foo', 'DataCloneError'), + ]; + + for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { + assert.equal(orig.name, clone.name); + assert.equal(orig.message, clone.message); + assert.equal(orig.code, clone.code); + assert.equal(orig.stack, clone.stack); + }); + }); + if (typeof File == 'function') QUnit.test('File', assert => { cloneObjectTest( assert, From 8977c69fe47054fccc6222eab309e734e20a0007 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 29 Oct 2021 20:52:17 +0700 Subject: [PATCH 23/73] some improvements --- packages/core-js-compat/src/data.mjs | 2 +- .../core-js/internals/structured-clone.js | 46 +++++++++++++------ tests/tests/web.structured-clone.js | 1 - 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/packages/core-js-compat/src/data.mjs b/packages/core-js-compat/src/data.mjs index b3f7a3b4a619..ab136d9dbb8a 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -1852,7 +1852,7 @@ export const data = { }, 'web.structured-clone': { deno: '1.14', - firefox: '94', + firefox: '94', // BUGGY node: '17.0', }, 'web.timers': { diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 216206b5b796..efc7d0c3cada 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -1,6 +1,7 @@ 'use strict'; var global = require('../internals/global'); var getBuiltin = require('../internals/get-built-in'); +var uncurryThis = require('../internals/function-uncurry-this'); var isObject = require('../internals/is-object'); var isSymbol = require('../internals/is-symbol'); var classof = require('../internals/classof'); @@ -9,6 +10,7 @@ var createNonEnumerableProperty = require('../internals/create-non-enumerable-pr var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); +var Object = global.Object; var Date = global.Date; var Error = global.Error; var EvalError = global.EvalError; @@ -19,13 +21,22 @@ var TypeError = global.TypeError; var URIError = global.URIError; var Set = getBuiltin('Set'); var Map = getBuiltin('Map'); +var MapPrototype = Map.prototype; +var mapHas = uncurryThis(MapPrototype.has); +var mapGet = uncurryThis(MapPrototype.get); +var mapSet = uncurryThis(MapPrototype.set); +var setAdd = uncurryThis(Set.prototype.add); +var bolleanValueOf = uncurryThis(true.valueOf); +var numberValueOf = uncurryThis(1.0.valueOf); +var stringValueOf = uncurryThis(''.valueOf); +var getTime = uncurryThis(Date.prototype.getTime); -function createDataCloneError(message) { +var createDataCloneError = function (message) { if (typeof DOMException === 'function') { return new DOMException(message, 'DataCloneError'); } return new Error(message); -} +}; /** * Tries best to replicate structuredClone behaviour. @@ -33,37 +44,44 @@ function createDataCloneError(message) { * @param {Map} map cache map * @param {any} value object to clone */ -module.exports = function structuredCloneInternal(map, value) { +var structuredCloneInternal = module.exports = function (map, value) { if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable'); if (!isObject(value)) return value; // effectively preserves circular references - if (map.has(value)) return map.get(value); + if (mapHas(map, value)) return mapGet(map, value); var C, cloned, deep, key; var type = classof(value); switch (type) { - case 'Boolean': case 'BigInt': + // can be a 3rd party polyfill + cloned = Object(value.valueOf()); + break; + case 'Boolean': + cloned = Object(bolleanValueOf(value)); + break; case 'Number': + cloned = Object(numberValueOf(value)); + break; case 'String': - cloned = Object(value.valueOf()); + cloned = Object(stringValueOf(value)); break; case 'Date': - cloned = new Date(value.valueOf()); + cloned = new Date(getTime(value)); break; case 'RegExp': cloned = new RegExp(value); break; case 'ArrayBuffer': - if (isArrayBufferDetached(value)) throw createDataCloneError('ArrayBuffer is deatched'); - // falls through case 'SharedArrayBuffer': + if (isArrayBufferDetached(value)) throw createDataCloneError('ArrayBuffer is deatched'); cloned = value.slice(0); break; case 'DataView': // eslint-disable-next-line es/no-typed-arrays -- ok cloned = new DataView( + // this is safe, since arraybuffer cannot have circular references structuredCloneInternal(map, value.buffer), value.byteOffset, value.byteLength @@ -80,8 +98,8 @@ module.exports = function structuredCloneInternal(map, value) { case 'Float64Array': case 'BigInt64Array': case 'BigUint64Array': - // this is safe, since arraybuffer cannot have circular references - cloned = new value.constructor( + cloned = new global[type]( + // this is safe, since arraybuffer cannot have circular references structuredCloneInternal(map, value.buffer), value.byteOffset, value.length @@ -158,17 +176,17 @@ module.exports = function structuredCloneInternal(map, value) { throw createDataCloneError('Uncloneable type: ' + type); } - map.set(value, cloned); + mapSet(map, value, cloned); if (deep) switch (type) { case 'Map': value.forEach(function (v, k) { - cloned.set(structuredCloneInternal(map, k), structuredCloneInternal(map, v)); + mapSet(cloned, structuredCloneInternal(map, k), structuredCloneInternal(map, v)); }); break; case 'Set': value.forEach(function (v) { - cloned.add(structuredCloneInternal(map, v)); + setAdd(cloned, structuredCloneInternal(map, v)); }); break; case 'Error': diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index e5aa01bc922d..be11a22cfaa0 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -348,7 +348,6 @@ QUnit.module('structuredClone', () => { // QUnit.test('Non-serializable types', assert => { const nons = [ - // ECMAScript types function () { return 1; }, Symbol('desc'), GLOBAL, From 928491126b51edf9fc753e5e4bd9207a567afe69 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 29 Oct 2021 22:32:43 +0700 Subject: [PATCH 24/73] clone geometry types --- .../core-js/internals/structured-clone.js | 24 ++++- tests/tests/web.structured-clone.js | 89 +++++++++++++++---- 2 files changed, 94 insertions(+), 19 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index efc7d0c3cada..ecda95c1c44b 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -140,7 +140,7 @@ var structuredCloneInternal = module.exports = function (map, value) { C = Error; } cloned = C(value.message); - deep = true; // clone stack after storing in the weakmap + deep = true; // clone stack after storing in the map break; case 'Array': cloned = []; @@ -155,7 +155,27 @@ var structuredCloneInternal = module.exports = function (map, value) { break; case 'DOMException': cloned = new (getBuiltin('DOMException'))(value.message, value.name); - deep = true; // clone stack after storing in the weakmap + deep = true; // clone stack after storing in the map + break; + case 'DOMPoint': + case 'DOMPointReadOnly': + cloned = global[type].fromPoint(value); + break; + case 'DOMQuad': + cloned = new DOMQuad( + structuredCloneInternal(map, value.p1), + structuredCloneInternal(map, value.p2), + structuredCloneInternal(map, value.p3), + structuredCloneInternal(map, value.p4) + ); + break; + case 'DOMRect': + case 'DOMRectReadOnly': + cloned = global[type].fromRect(value); + break; + case 'DOMMatrix': + case 'DOMMatrixReadOnly': + cloned = global[type].fromMatrix(value); break; case 'File': cloned = new File( diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index be11a22cfaa0..ea4b897171c2 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -255,28 +255,83 @@ QUnit.module('structuredClone', () => { // // TODO: Test these additional interfaces: - // * DOMQuad - // * DOMException // * RTCCertificate // Geometry types - // FIXME: PhantomJS Can't run this test due to unsupported API. - QUnit.skip('Geometry types', assert => { - const array = [ - new DOMMatrix(), - new DOMMatrixReadOnly(), - new DOMPoint(), - new DOMPointReadOnly(), - new DOMRect(), - new DOMRectReadOnly(), - ]; + if (typeof DOMMatrix == 'function' && typeof DOMMatrix.fromMatrix == 'function') { + QUnit.test('Geometry types, DOMMatrix', assert => { + cloneObjectTest(assert, new DOMMatrix(), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } - for (const typ of array) cloneObjectTest(assert, typ, (orig, clone) => { - for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); - } + if (typeof DOMMatrixReadOnly == 'function' && typeof DOMMatrixReadOnly.fromMatrix == 'function') { + QUnit.test('Geometry types, DOMMatrixReadOnly', assert => { + cloneObjectTest(assert, new DOMMatrixReadOnly(), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); }); - }); + } + + if (typeof DOMPoint == 'function' && typeof DOMPoint.fromPoint == 'function') { + QUnit.test('Geometry types, DOMPoint', assert => { + cloneObjectTest(assert, new DOMPoint(1, 2, 3, 4), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMPointReadOnly == 'function' && typeof DOMPointReadOnly.fromPoint == 'function') { + QUnit.test('Geometry types, DOMPointReadOnly', assert => { + cloneObjectTest(assert, new DOMPointReadOnly(1, 2, 3, 4), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMPoint == 'function' && typeof DOMQuad == 'function') { + QUnit.test('Geometry types, DOMQuad', assert => { + cloneObjectTest(assert, new DOMQuad( + new DOMPoint(1, 2, 3, 4), + new DOMPoint(2, 2, 3, 4), + new DOMPoint(1, 3, 3, 4), + new DOMPoint(1, 2, 4, 4), + ), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.deepEqual(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMRect == 'function' && typeof DOMRect.fromRect == 'function') { + QUnit.test('Geometry types, DOMRect', assert => { + cloneObjectTest(assert, new DOMRect(1, 2, 3, 4), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMRectReadOnly == 'function' && typeof DOMRectReadOnly.fromRect == 'function') { + QUnit.test('Geometry types, DOMRectReadOnly', assert => { + cloneObjectTest(assert, new DOMRectReadOnly(1, 2, 3, 4), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } if (typeof ImageData == 'function') QUnit.test('ImageData', assert => { const imageData = new ImageData(8, 8); From 300491ea13277f7535860dc5898c28db1d93227a Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 29 Oct 2021 23:25:27 +0700 Subject: [PATCH 25/73] add clone of `AudioData` and `VideoFrame` --- packages/core-js/internals/structured-clone.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index ecda95c1c44b..a4086a965928 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -177,6 +177,11 @@ var structuredCloneInternal = module.exports = function (map, value) { case 'DOMMatrixReadOnly': cloned = global[type].fromMatrix(value); break; + case 'AudioData': + case 'VideoFrame': + // reference to the same media resource as the original + cloned = value.clone(); + break; case 'File': cloned = new File( [value], From 53d7619626c056fb2e0497ba77af933055f2159e Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 29 Oct 2021 23:47:22 +0700 Subject: [PATCH 26/73] add pure version tests --- tests/pure/web.structured-clone.js | 424 ++++++++++++++++++++++++++++ tests/tests/web.structured-clone.js | 1 + 2 files changed, 425 insertions(+) create mode 100644 tests/pure/web.structured-clone.js diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js new file mode 100644 index 000000000000..081e53ad3971 --- /dev/null +++ b/tests/pure/web.structured-clone.js @@ -0,0 +1,424 @@ +// Originally from: https://github.com/web-platform-tests/wpt/blob/4b35e758e2fc4225368304b02bcec9133965fd1a/IndexedDB/structured-clone.any.js +// Copyright © web-platform-tests contributors. Available under the 3-Clause BSD License. +/* eslint-disable es/no-typed-arrays -- safe */ +import { GLOBAL } from '../helpers/constants'; +import { fromSource } from '../helpers/helpers'; + +import structuredClone from 'core-js-pure/stable/structured-clone'; +import from from 'core-js-pure/es/array/from'; +import assign from 'core-js-pure/es/object/assign'; +import getPrototypeOf from 'core-js-pure/es/object/get-prototype-of'; +import keys from 'core-js-pure/es/object/keys'; +import Symbol from 'core-js-pure/es/symbol'; +import Map from 'core-js-pure/es/map'; +import Set from 'core-js-pure/es/set'; +import AggregateError from 'core-js-pure/es/aggregate-error'; + +QUnit.module('structuredClone', () => { + QUnit.test('identity', assert => { + assert.isFunction(structuredClone, 'structuredClone is a function'); + assert.name(structuredClone, 'structuredClone'); + assert.arity(structuredClone, 1); + }); + + function cloneTest(value, verifyFunc) { + verifyFunc(value, structuredClone(value)); + } + + // Specialization of cloneTest() for objects, with common asserts. + function cloneObjectTest(assert, value, verifyFunc) { + cloneTest(value, (orig, clone) => { + assert.notEqual(orig, clone, 'clone should have different reference'); + assert.equal(typeof clone, 'object', 'clone should be an object'); + assert.equal(getPrototypeOf(orig), getPrototypeOf(clone), 'clone should have same prototype'); + verifyFunc(orig, clone); + }); + } + + function cloneFailureTest(assert, value) { + assert.throws(() => structuredClone(value), typeof DOMException === 'function' ? DOMException : Error); + } + + // + // ECMAScript types + // + + // Primitive values: Undefined, Null, Boolean, Number, BigInt, String + const booleans = [false, true]; + const numbers = [ + NaN, + -Infinity, + -Number.MAX_VALUE, + -0xFFFFFFFF, + -0x80000000, + -0x7FFFFFFF, + -1, + -Number.MIN_VALUE, + -0, + 0, + 1, + Number.MIN_VALUE, + 0x7FFFFFFF, + 0x80000000, + 0xFFFFFFFF, + Number.MAX_VALUE, + Infinity, + ]; + + const bigints = fromSource(`[ + -12345678901234567890n, + -1n, + 0n, + 1n, + 12345678901234567890n, + ]`) || []; + + const strings = [ + '', + 'this is a sample string', + 'null(\0)', + ]; + + QUnit.test('primitives', assert => { + const primitives = [undefined, null].concat(booleans, numbers, bigints, strings); + + for (const value of primitives) cloneTest(value, (orig, clone) => { + assert.same(orig, clone, 'primitives should be same after cloned'); + }); + }); + + // "Primitive" Objects (Boolean, Number, BigInt, String) + QUnit.test('primitive objects', assert => { + const primitives = [].concat(booleans, numbers, bigints, strings); + + for (const value of primitives) cloneObjectTest(assert, Object(value), (orig, clone) => { + assert.same(orig.valueOf(), clone.valueOf(), 'primitive wrappers should have same value'); + }); + }); + + // Dates + QUnit.test('Date', assert => { + const dates = [ + new Date(-1e13), + new Date(-1e12), + new Date(-1e9), + new Date(-1e6), + new Date(-1e3), + new Date(0), + new Date(1e3), + new Date(1e6), + new Date(1e9), + new Date(1e12), + new Date(1e13), + ]; + + for (const date of dates) cloneTest(date, (orig, clone) => { + assert.notEqual(orig, clone); + assert.equal(typeof clone, 'object'); + assert.equal(getPrototypeOf(orig), getPrototypeOf(clone)); + assert.equal(orig.valueOf(), clone.valueOf()); + }); + }); + + // Regular Expressions + QUnit.test('RegExp', assert => { + const regexes = [ + new RegExp(), + /abc/, + /abc/g, + /abc/i, + /abc/gi, + /abc/, + /abc/g, + /abc/i, + /abc/gi, + ]; + + const giuy = fromSource('/abc/giuy'); + if (giuy) regexes.push(giuy); + + for (const regex of regexes) cloneObjectTest(assert, regex, (orig, clone) => { + assert.equal(orig.toString(), clone.toString(), `regex ${ regex }`); + }); + }); + + // ArrayBuffer + if (typeof Uint8Array == 'function') QUnit.test('ArrayBuffer', assert => { // Crashes + cloneObjectTest(assert, new Uint8Array([0, 1, 254, 255]).buffer, (orig, clone) => { + assert.arrayEqual(new Uint8Array(orig), new Uint8Array(clone)); + }); + }); + + // TODO SharedArrayBuffer + + // Array Buffer Views + if (typeof Uint8ClampedArray == 'function') QUnit.test('ArrayBufferView', assert => { + const arrays = [ + new Uint8Array([]), + new Uint8Array([0, 1, 254, 255]), + new Uint16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]), + new Uint32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), + new Int8Array([0, 1, 254, 255]), + new Int16Array([0x0000, 0x0001, 0xFFFE, 0xFFFF]), + new Int32Array([0x00000000, 0x00000001, 0xFFFFFFFE, 0xFFFFFFFF]), + new Uint8ClampedArray([0, 1, 254, 255]), + new Float32Array([-Infinity, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, Infinity, NaN]), + new Float64Array([-Infinity, -Number.MAX_VALUE, -Number.MIN_VALUE, 0, Number.MIN_VALUE, Number.MAX_VALUE, Infinity, NaN]), + ]; + + for (const array of arrays) cloneObjectTest(assert, array, (orig, clone) => { + assert.arrayEqual(orig, clone); + }); + }); + + // Map + QUnit.test('Map', assert => { + cloneObjectTest(assert, new Map([[1, 2], [3, 4]]), (orig, clone) => { + assert.deepEqual(from(orig.keys()), from(clone.keys())); + assert.deepEqual(from(orig.values()), from(clone.values())); + }); + }); + + // Set + QUnit.test('Set', assert => { + cloneObjectTest(assert, new Set([1, 2, 3, 4]), (orig, clone) => { + assert.deepEqual(from(orig.values()), from(clone.values())); + }); + }); + + // Error + QUnit.test('Error', assert => { + const errors = [ + new Error(), + new Error('abc', 'def'), + new EvalError(), + new EvalError('ghi', 'jkl'), + new RangeError(), + new RangeError('ghi', 'jkl'), + new ReferenceError(), + new ReferenceError('ghi', 'jkl'), + new SyntaxError(), + new SyntaxError('ghi', 'jkl'), + new TypeError(), + new TypeError('ghi', 'jkl'), + new URIError(), + new URIError('ghi', 'jkl'), + ]; + + for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { + assert.equal(orig.constructor === AggregateError ? Error : orig.constructor, clone.constructor); + assert.equal(orig.name === 'AggregateError' ? 'Error' : orig.name, clone.name); + assert.equal(orig.message, clone.message); + assert.equal(orig.stack, clone.stack); + }); + + const aggregates = [ + new AggregateError([1, 2]), + new AggregateError([1, 2], 42), + ]; + + for (const error of aggregates) { + const clone = structuredClone(error); + assert.equal(Error, clone.constructor); + assert.equal('Error', clone.name); + assert.equal(error.message, clone.message); + assert.equal(error.stack, clone.stack); + } + }); + + // Arrays + QUnit.test('Array', assert => { + const arrays = [ + [], + [1, 2, 3], + assign( + ['foo', 'bar'], + { 10: true, 11: false, 20: 123, 21: 456, 30: null }), + assign( + ['foo', 'bar'], + { a: true, b: false, foo: 123, bar: 456, '': null }), + ]; + + for (const array of arrays) cloneObjectTest(assert, array, (orig, clone) => { + assert.deepEqual(orig, clone, `array content should be same: ${ array }`); + assert.deepEqual(keys(orig), keys(clone), `array key should be same: ${ array }`); + for (const key of keys(orig)) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + + // Objects + QUnit.test('Object', assert => { + cloneObjectTest(assert, { foo: true, bar: false }, (orig, clone) => { + assert.deepEqual(keys(orig), keys(clone)); + for (const key of keys(orig)) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + + // + // [Serializable] Platform objects + // + + // TODO: Test these additional interfaces: + // * RTCCertificate + + // Geometry types + if (typeof DOMMatrix == 'function' && typeof DOMMatrix.fromMatrix == 'function') { + QUnit.test('Geometry types, DOMMatrix', assert => { + cloneObjectTest(assert, new DOMMatrix(), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMMatrixReadOnly == 'function' && typeof DOMMatrixReadOnly.fromMatrix == 'function') { + QUnit.test('Geometry types, DOMMatrixReadOnly', assert => { + cloneObjectTest(assert, new DOMMatrixReadOnly(), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMPoint == 'function' && typeof DOMPoint.fromPoint == 'function') { + QUnit.test('Geometry types, DOMPoint', assert => { + cloneObjectTest(assert, new DOMPoint(1, 2, 3, 4), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMPointReadOnly == 'function' && typeof DOMPointReadOnly.fromPoint == 'function') { + QUnit.test('Geometry types, DOMPointReadOnly', assert => { + cloneObjectTest(assert, new DOMPointReadOnly(1, 2, 3, 4), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMPoint == 'function' && typeof DOMQuad == 'function') { + QUnit.test('Geometry types, DOMQuad', assert => { + cloneObjectTest(assert, new DOMQuad( + new DOMPoint(1, 2, 3, 4), + new DOMPoint(2, 2, 3, 4), + new DOMPoint(1, 3, 3, 4), + new DOMPoint(1, 2, 4, 4), + ), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.deepEqual(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMRect == 'function' && typeof DOMRect.fromRect == 'function') { + QUnit.test('Geometry types, DOMRect', assert => { + cloneObjectTest(assert, new DOMRect(1, 2, 3, 4), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof DOMRectReadOnly == 'function' && typeof DOMRectReadOnly.fromRect == 'function') { + QUnit.test('Geometry types, DOMRectReadOnly', assert => { + cloneObjectTest(assert, new DOMRectReadOnly(1, 2, 3, 4), (orig, clone) => { + for (const key of keys(getPrototypeOf(orig))) { + assert.equal(orig[key], clone[key], `Property ${ key }`); + } + }); + }); + } + + if (typeof ImageData == 'function') QUnit.test('ImageData', assert => { + const imageData = new ImageData(8, 8); + for (let i = 0; i < 256; ++i) { + imageData.data[i] = i; + } + cloneObjectTest(assert, imageData, (orig, clone) => { + assert.equal(orig.width, clone.width); + assert.equal(orig.height, clone.height); + assert.equal(orig.colorSpace, clone.colorSpace); + assert.arrayEqual(orig.data, clone.data); + }); + }); + + if (typeof Blob == 'function') QUnit.test('Blob', assert => { + cloneObjectTest( + assert, + new Blob(['This is a test.'], { type: 'a/b' }), + (orig, clone) => { + assert.equal(orig.size, clone.size); + assert.equal(orig.type, clone.type); + // TODO: async + // assert.equal(await orig.text(), await clone.text()); + }); + }); + + if (typeof DOMException == 'function') QUnit.test('DOMException', assert => { + const errors = [ + new DOMException(), + new DOMException('foo', 'DataCloneError'), + ]; + + for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { + assert.equal(orig.name, clone.name); + assert.equal(orig.message, clone.message); + assert.equal(orig.code, clone.code); + assert.equal(orig.stack, clone.stack); + }); + }); + + if (typeof File == 'function') QUnit.test('File', assert => { + cloneObjectTest( + assert, + new File(['This is a test.'], 'foo.txt', { type: 'c/d' }), + (orig, clone) => { + assert.equal(orig.size, clone.size); + assert.equal(orig.type, clone.type); + assert.equal(orig.name, clone.name); + assert.equal(orig.lastModified, clone.lastModified); + // TODO: async + // assert.equal(await orig.text(), await clone.text()); + }); + }); + + // FileList - exposed in Workers, but not constructable. + QUnit.skip('FileList', assert => { + if ('document' in GLOBAL) { + // TODO: Test with populated list. + cloneObjectTest( + assert, + assign(document.createElement('input'), { type: 'file', multiple: true }).files, + (orig, clone) => assert.equal(orig.length, clone.length), + ); + } + }); + + // + // Non-serializable types + // + QUnit.test('Non-serializable types', assert => { + const nons = [ + function () { return 1; }, + Symbol('desc'), + GLOBAL, + ]; + + if (typeof Event == 'function') nons.push(new Event('')); + if (typeof MessageChannel == 'function') nons.push(new MessageChannel()); + + for (const it of nons) cloneFailureTest(assert, it); + }); +}); diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index ea4b897171c2..506cf92dabec 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -11,6 +11,7 @@ QUnit.module('structuredClone', () => { assert.isFunction(structuredClone, 'structuredClone is a function'); assert.name(structuredClone, 'structuredClone'); assert.arity(structuredClone, 1); + assert.looksNative(structuredClone); }); function cloneTest(value, verifyFunc) { From 4707b7374d42db97e227b74968abd9f39f1dafdf Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sat, 30 Oct 2021 12:24:48 +0700 Subject: [PATCH 27/73] forced usage of polyfill in the pure version since native `structuredClone` can't work with wrapped `Map` / `Set` --- packages/core-js/modules/web.structured-clone.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index ba073b67b3a9..b4d58355030b 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -1,3 +1,4 @@ +var IS_PURE = require('../internals/is-pure'); var $ = require('../internals/export'); var global = require('../internals/global'); var getBuiltIn = require('../internals/get-built-in'); @@ -7,7 +8,7 @@ var structuredCloneImpl = require('../internals/structured-clone'); var Map = getBuiltIn('Map'); var TypeError = global.TypeError; -$({ global: true, enumerable: true, sham: true }, { +$({ global: true, enumerable: true, sham: true, forced: IS_PURE }, { structuredClone: function structuredClone(value /* , { transfer } */) { var options = arguments.length > 1 ? anObject(arguments[1]) : undefined; var transfer = options && options.transfer; From 2040a05880357349b7aec520955e77e20c553b70 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 31 Oct 2021 20:33:36 +0700 Subject: [PATCH 28/73] return original `SharedArrayBuffer`, unify `ArrayBufferView` logic --- packages/core-js/internals/structured-clone.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index a4086a965928..3c53625376ad 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -76,17 +76,10 @@ var structuredCloneInternal = module.exports = function (map, value) { case 'ArrayBuffer': case 'SharedArrayBuffer': if (isArrayBufferDetached(value)) throw createDataCloneError('ArrayBuffer is deatched'); - cloned = value.slice(0); + // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original + cloned = type === 'SharedArrayBuffer' ? value : value.slice(0); break; case 'DataView': - // eslint-disable-next-line es/no-typed-arrays -- ok - cloned = new DataView( - // this is safe, since arraybuffer cannot have circular references - structuredCloneInternal(map, value.buffer), - value.byteOffset, - value.byteLength - ); - break; case 'Int8Array': case 'Uint8Array': case 'Uint8ClampedArray': @@ -102,7 +95,7 @@ var structuredCloneInternal = module.exports = function (map, value) { // this is safe, since arraybuffer cannot have circular references structuredCloneInternal(map, value.buffer), value.byteOffset, - value.length + type === 'DataView' ? value.byteLength : value.length ); break; case 'Map': From bbaddcec7f846fea7943039263dfec8ac9059567 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 31 Oct 2021 22:23:48 +0700 Subject: [PATCH 29/73] don't create `Map` cache on cloning primitives --- .../core-js/internals/structured-clone.js | 27 ++++++++++--------- .../core-js/modules/web.structured-clone.js | 6 ++--- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 3c53625376ad..770f3e8ca573 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -44,11 +44,13 @@ var createDataCloneError = function (message) { * @param {Map} map cache map * @param {any} value object to clone */ -var structuredCloneInternal = module.exports = function (map, value) { +var structuredCloneInternal = module.exports = function (value, map) { if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable'); if (!isObject(value)) return value; // effectively preserves circular references - if (mapHas(map, value)) return mapGet(map, value); + if (map) { + if (mapHas(map, value)) return mapGet(map, value); + } else map = new Map(); var C, cloned, deep, key; var type = classof(value); @@ -93,7 +95,7 @@ var structuredCloneInternal = module.exports = function (map, value) { case 'BigUint64Array': cloned = new global[type]( // this is safe, since arraybuffer cannot have circular references - structuredCloneInternal(map, value.buffer), + structuredCloneInternal(value.buffer, map), value.byteOffset, type === 'DataView' ? value.byteLength : value.length ); @@ -156,10 +158,10 @@ var structuredCloneInternal = module.exports = function (map, value) { break; case 'DOMQuad': cloned = new DOMQuad( - structuredCloneInternal(map, value.p1), - structuredCloneInternal(map, value.p2), - structuredCloneInternal(map, value.p3), - structuredCloneInternal(map, value.p4) + structuredCloneInternal(value.p1, map), + structuredCloneInternal(value.p2, map), + structuredCloneInternal(value.p3, map), + structuredCloneInternal(value.p4, map) ); break; case 'DOMRect': @@ -172,7 +174,6 @@ var structuredCloneInternal = module.exports = function (map, value) { break; case 'AudioData': case 'VideoFrame': - // reference to the same media resource as the original cloned = value.clone(); break; case 'File': @@ -184,7 +185,7 @@ var structuredCloneInternal = module.exports = function (map, value) { break; case 'ImageData': cloned = new ImageData( - structuredCloneInternal(map, value.data), + structuredCloneInternal(value.data, map), value.width, value.height, { colorSpace: value.colorSpace } @@ -199,22 +200,22 @@ var structuredCloneInternal = module.exports = function (map, value) { if (deep) switch (type) { case 'Map': value.forEach(function (v, k) { - mapSet(cloned, structuredCloneInternal(map, k), structuredCloneInternal(map, v)); + mapSet(cloned, structuredCloneInternal(k, map), structuredCloneInternal(v, map)); }); break; case 'Set': value.forEach(function (v) { - setAdd(cloned, structuredCloneInternal(map, v)); + setAdd(cloned, structuredCloneInternal(v, map)); }); break; case 'Error': case 'DOMException': - if (ERROR_STACK_INSTALLABLE) createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(map, value.stack)); + if (ERROR_STACK_INSTALLABLE) createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map)); break; case 'Array': case 'Object': for (key in value) if (hasOwn(value, key)) { - cloned[key] = structuredCloneInternal(map, value[key]); + cloned[key] = structuredCloneInternal(value[key], map); } break; } diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index b4d58355030b..4c15867fc0dd 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -1,11 +1,9 @@ var IS_PURE = require('../internals/is-pure'); var $ = require('../internals/export'); var global = require('../internals/global'); -var getBuiltIn = require('../internals/get-built-in'); var anObject = require('../internals/an-object'); -var structuredCloneImpl = require('../internals/structured-clone'); +var structuredCloneInternal = require('../internals/structured-clone'); -var Map = getBuiltIn('Map'); var TypeError = global.TypeError; $({ global: true, enumerable: true, sham: true, forced: IS_PURE }, { @@ -15,6 +13,6 @@ $({ global: true, enumerable: true, sham: true, forced: IS_PURE }, { if (transfer !== undefined) throw new TypeError('Transfer option is not supported'); - return structuredCloneImpl(new Map(), value); + return structuredCloneInternal(value); } }); From 1b81767f5e43232594a56902be3fe948c3578a73 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 31 Oct 2021 23:36:25 +0700 Subject: [PATCH 30/73] fix FF implementation --- packages/core-js-compat/src/data.mjs | 3 ++- packages/core-js/internals/structured-clone.js | 6 ++++-- packages/core-js/modules/web.structured-clone.js | 15 +++++++++++++-- tests/compat/tests.js | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/core-js-compat/src/data.mjs b/packages/core-js-compat/src/data.mjs index ab136d9dbb8a..f27eec7a4fee 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -1852,7 +1852,8 @@ export const data = { }, 'web.structured-clone': { deno: '1.14', - firefox: '94', // BUGGY + // current FF implementation can't clone errors + // firefox: '94', node: '17.0', }, 'web.timers': { diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 770f3e8ca573..3770970fb571 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -10,6 +10,7 @@ var createNonEnumerableProperty = require('../internals/create-non-enumerable-pr var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); +var n$StructuredClone = global.structuredClone; var Object = global.Object; var Date = global.Date; var Error = global.Error; @@ -79,7 +80,7 @@ var structuredCloneInternal = module.exports = function (value, map) { case 'SharedArrayBuffer': if (isArrayBufferDetached(value)) throw createDataCloneError('ArrayBuffer is deatched'); // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original - cloned = type === 'SharedArrayBuffer' ? value : value.slice(0); + cloned = type === 'SharedArrayBuffer' ? n$StructuredClone ? n$StructuredClone(value) : value : value.slice(0); break; case 'DataView': case 'Int8Array': @@ -192,7 +193,8 @@ var structuredCloneInternal = module.exports = function (value, map) { ); break; default: - throw createDataCloneError('Uncloneable type: ' + type); + if (n$StructuredClone) cloned = n$StructuredClone(value); + else throw createDataCloneError('Uncloneable type: ' + type); } mapSet(map, value, cloned); diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 4c15867fc0dd..80ba53e83684 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -1,17 +1,28 @@ var IS_PURE = require('../internals/is-pure'); var $ = require('../internals/export'); var global = require('../internals/global'); +var fails = require('../internals/fails'); var anObject = require('../internals/an-object'); var structuredCloneInternal = require('../internals/structured-clone'); +var n$StructuredClone = global.structuredClone; var TypeError = global.TypeError; -$({ global: true, enumerable: true, sham: true, forced: IS_PURE }, { +var FORCED = IS_PURE || fails(function () { + // current FF implementation can't clone errors + // https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 + return n$StructuredClone(Error('a')).message !== 'a'; +}); + +$({ global: true, enumerable: true, sham: true, forced: FORCED }, { structuredClone: function structuredClone(value /* , { transfer } */) { var options = arguments.length > 1 ? anObject(arguments[1]) : undefined; var transfer = options && options.transfer; - if (transfer !== undefined) throw new TypeError('Transfer option is not supported'); + if (transfer !== undefined) { + if (!IS_PURE && n$StructuredClone) return n$StructuredClone(value, options); + throw TypeError('Transfer option is not supported'); + } return structuredCloneInternal(value); } diff --git a/tests/compat/tests.js b/tests/compat/tests.js index 3ab22253901c..479eae24579d 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1672,7 +1672,7 @@ GLOBAL.tests = { return Object.getOwnPropertyDescriptor(GLOBAL, 'queueMicrotask').value; }, 'web.structured-clone': function () { - return typeof structuredClone === 'function'; + return structuredClone(Error('a')).message === 'a'; }, 'web.timers': function () { return !/MSIE .\./.test(USERAGENT); From f4bb633f183720da427392584b483ee846876054 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Mon, 1 Nov 2021 17:31:23 +0700 Subject: [PATCH 31/73] use native structured clone from `PerformanceMark.detail` --- .../internals/structured-clone-from-mark.js | 22 +++++++++++++++++++ .../core-js/internals/structured-clone.js | 14 +++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 packages/core-js/internals/structured-clone-from-mark.js diff --git a/packages/core-js/internals/structured-clone-from-mark.js b/packages/core-js/internals/structured-clone-from-mark.js new file mode 100644 index 000000000000..8bb2d73f5607 --- /dev/null +++ b/packages/core-js/internals/structured-clone-from-mark.js @@ -0,0 +1,22 @@ +var global = require('../internals/global'); +var call = require('../internals/function-call'); +var fails = require('../internals/fails'); +var uid = require('../internals/uid'); + +var performance = global.performance; +var mark = performance && performance.mark; +var clearMarks = performance && performance.clearMarks; +var MARK = uid('structuredClone'); + +var structuredClone = function (value) { + var clone = call(mark, performance, MARK, { detail: value }).detail; + call(clearMarks, performance, MARK); + return clone; +}; + +module.exports = !fails(function () { + // eslint-disable-next-line es/no-set -- safe + var set = new Set([42]); + var clone = structuredClone(set); + return clone === set || !set.has(42); +}) && structuredClone; diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 3770970fb571..10777ee56248 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -1,16 +1,19 @@ 'use strict'; +var IS_PURE = require('../internals/is-pure'); var global = require('../internals/global'); var getBuiltin = require('../internals/get-built-in'); var uncurryThis = require('../internals/function-uncurry-this'); +var fails = require('../internals/fails'); var isObject = require('../internals/is-object'); var isSymbol = require('../internals/is-symbol'); var classof = require('../internals/classof'); var hasOwn = require('../internals/has-own-property'); var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); +var structuredCloneFromMark = require('../internals/structured-clone-from-mark'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); -var n$StructuredClone = global.structuredClone; +var n$StructuredClone = global.structuredClone || structuredCloneFromMark; var Object = global.Object; var Date = global.Date; var Error = global.Error; @@ -32,11 +35,15 @@ var numberValueOf = uncurryThis(1.0.valueOf); var stringValueOf = uncurryThis(''.valueOf); var getTime = uncurryThis(Date.prototype.getTime); +var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !fails(function () { + // current Safari implementation can't clone errors + return structuredCloneFromMark(Error('a')).message !== 'a'; +}); + var createDataCloneError = function (message) { if (typeof DOMException === 'function') { return new DOMException(message, 'DataCloneError'); - } - return new Error(message); + } return Error(message); }; /** @@ -48,6 +55,7 @@ var createDataCloneError = function (message) { var structuredCloneInternal = module.exports = function (value, map) { if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable'); if (!isObject(value)) return value; + if (USE_STRUCTURED_CLONE_FROM_MARK) return structuredCloneFromMark(value); // effectively preserves circular references if (map) { if (mapHas(map, value)) return mapGet(map, value); From fddef17eaa4b77bb26002db5bbc840c69aabaec2 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Mon, 1 Nov 2021 18:38:57 +0700 Subject: [PATCH 32/73] some stylistic changes --- .../internals/structured-clone-from-mark.js | 22 -------------- .../core-js/internals/structured-clone.js | 29 +++++++++++++++---- 2 files changed, 24 insertions(+), 27 deletions(-) delete mode 100644 packages/core-js/internals/structured-clone-from-mark.js diff --git a/packages/core-js/internals/structured-clone-from-mark.js b/packages/core-js/internals/structured-clone-from-mark.js deleted file mode 100644 index 8bb2d73f5607..000000000000 --- a/packages/core-js/internals/structured-clone-from-mark.js +++ /dev/null @@ -1,22 +0,0 @@ -var global = require('../internals/global'); -var call = require('../internals/function-call'); -var fails = require('../internals/fails'); -var uid = require('../internals/uid'); - -var performance = global.performance; -var mark = performance && performance.mark; -var clearMarks = performance && performance.clearMarks; -var MARK = uid('structuredClone'); - -var structuredClone = function (value) { - var clone = call(mark, performance, MARK, { detail: value }).detail; - call(clearMarks, performance, MARK); - return clone; -}; - -module.exports = !fails(function () { - // eslint-disable-next-line es/no-set -- safe - var set = new Set([42]); - var clone = structuredClone(set); - return clone === set || !set.has(42); -}) && structuredClone; diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 10777ee56248..a17931baf0c4 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -2,18 +2,18 @@ var IS_PURE = require('../internals/is-pure'); var global = require('../internals/global'); var getBuiltin = require('../internals/get-built-in'); +var call = require('../internals/function-call'); var uncurryThis = require('../internals/function-uncurry-this'); var fails = require('../internals/fails'); +var uid = require('../internals/uid'); var isObject = require('../internals/is-object'); var isSymbol = require('../internals/is-symbol'); var classof = require('../internals/classof'); var hasOwn = require('../internals/has-own-property'); var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); -var structuredCloneFromMark = require('../internals/structured-clone-from-mark'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); -var n$StructuredClone = global.structuredClone || structuredCloneFromMark; var Object = global.Object; var Date = global.Date; var Error = global.Error; @@ -26,6 +26,9 @@ var URIError = global.URIError; var Set = getBuiltin('Set'); var Map = getBuiltin('Map'); var MapPrototype = Map.prototype; +var performance = global.performance; +var mark = performance && performance.mark; +var clearMarks = performance && performance.clearMarks; var mapHas = uncurryThis(MapPrototype.has); var mapGet = uncurryThis(MapPrototype.get); var mapSet = uncurryThis(MapPrototype.set); @@ -34,6 +37,21 @@ var bolleanValueOf = uncurryThis(true.valueOf); var numberValueOf = uncurryThis(1.0.valueOf); var stringValueOf = uncurryThis(''.valueOf); var getTime = uncurryThis(Date.prototype.getTime); +var PERFORMANCE_MARK = uid('structuredClone'); + +var structuredCloneFromMark = (function (structuredClone) { + return !fails(function () { + var set = new global.Set([42]); + var cloned = structuredClone(set); + return cloned === set || !set.has(42); + }) && structuredClone; +})(function (value) { + var cloned = call(mark, performance, PERFORMANCE_MARK, { detail: value }).detail; + call(clearMarks, performance, PERFORMANCE_MARK); + return cloned; +}); + +var nativeRestrictedStructuredClone = global.structuredClone || structuredCloneFromMark; var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !fails(function () { // current Safari implementation can't clone errors @@ -87,8 +105,9 @@ var structuredCloneInternal = module.exports = function (value, map) { case 'ArrayBuffer': case 'SharedArrayBuffer': if (isArrayBufferDetached(value)) throw createDataCloneError('ArrayBuffer is deatched'); - // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original - cloned = type === 'SharedArrayBuffer' ? n$StructuredClone ? n$StructuredClone(value) : value : value.slice(0); + cloned = type === 'ArrayBuffer' ? value.slice(0) + // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original + : nativeRestrictedStructuredClone ? nativeRestrictedStructuredClone(value) : value; break; case 'DataView': case 'Int8Array': @@ -201,7 +220,7 @@ var structuredCloneInternal = module.exports = function (value, map) { ); break; default: - if (n$StructuredClone) cloned = n$StructuredClone(value); + if (nativeRestrictedStructuredClone) cloned = nativeRestrictedStructuredClone(value); else throw createDataCloneError('Uncloneable type: ' + type); } From 1675881c873ede487aae336cae4c528cd34d482b Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Mon, 1 Nov 2021 20:59:58 +0700 Subject: [PATCH 33/73] make tests pass in Edge (some builtins are functions, but not constructors) --- .../core-js/internals/structured-clone.js | 6 ++- tests/pure/web.structured-clone.js | 33 ++++++--------- tests/tests/web.structured-clone.js | 40 ++++++++----------- 3 files changed, 34 insertions(+), 45 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index a17931baf0c4..3d76ef810585 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -59,9 +59,11 @@ var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !fails(function () { }); var createDataCloneError = function (message) { - if (typeof DOMException === 'function') { + try { return new DOMException(message, 'DataCloneError'); - } return Error(message); + } catch (error) { + return TypeError(message); + } }; /** diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index 081e53ad3971..b83a7fbf3e32 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -35,13 +35,7 @@ QUnit.module('structuredClone', () => { }); } - function cloneFailureTest(assert, value) { - assert.throws(() => structuredClone(value), typeof DOMException === 'function' ? DOMException : Error); - } - - // // ECMAScript types - // // Primitive values: Undefined, Null, Boolean, Number, BigInt, String const booleans = [false, true]; @@ -258,12 +252,7 @@ QUnit.module('structuredClone', () => { }); }); - // // [Serializable] Platform objects - // - - // TODO: Test these additional interfaces: - // * RTCCertificate // Geometry types if (typeof DOMMatrix == 'function' && typeof DOMMatrix.fromMatrix == 'function') { @@ -341,7 +330,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof ImageData == 'function') QUnit.test('ImageData', assert => { + if (fromSource('new ImageData(8, 8)')) QUnit.test('ImageData', assert => { const imageData = new ImageData(8, 8); for (let i = 0; i < 256; ++i) { imageData.data[i] = i; @@ -354,7 +343,7 @@ QUnit.module('structuredClone', () => { }); }); - if (typeof Blob == 'function') QUnit.test('Blob', assert => { + if (fromSource('new Blob(["test"])')) QUnit.test('Blob', assert => { cloneObjectTest( assert, new Blob(['This is a test.'], { type: 'a/b' }), @@ -366,7 +355,8 @@ QUnit.module('structuredClone', () => { }); }); - if (typeof DOMException == 'function') QUnit.test('DOMException', assert => { + // TODO: remove check after https://github.com/zloirock/core-js/pull/991 + if (fromSource('new DOMException')) QUnit.test('DOMException', assert => { const errors = [ new DOMException(), new DOMException('foo', 'DataCloneError'), @@ -380,7 +370,7 @@ QUnit.module('structuredClone', () => { }); }); - if (typeof File == 'function') QUnit.test('File', assert => { + if (fromSource('new File(["test"], "foo.txt")')) QUnit.test('File', assert => { cloneObjectTest( assert, new File(['This is a test.'], 'foo.txt', { type: 'c/d' }), @@ -406,9 +396,7 @@ QUnit.module('structuredClone', () => { } }); - // // Non-serializable types - // QUnit.test('Non-serializable types', assert => { const nons = [ function () { return 1; }, @@ -416,9 +404,14 @@ QUnit.module('structuredClone', () => { GLOBAL, ]; - if (typeof Event == 'function') nons.push(new Event('')); - if (typeof MessageChannel == 'function') nons.push(new MessageChannel()); + const event = fromSource('new Event("")'); + const channel = fromSource('new MessageChannel'); - for (const it of nons) cloneFailureTest(assert, it); + if (event) nons.push(event); + if (channel) nons.push(channel); + + for (const it of nons) { + assert.throws(() => structuredClone(it), fromSource('new DOMException') ? DOMException : TypeError); + } }); }); diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 506cf92dabec..9922d4ef6d8f 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -1,6 +1,6 @@ // Originally from: https://github.com/web-platform-tests/wpt/blob/4b35e758e2fc4225368304b02bcec9133965fd1a/IndexedDB/structured-clone.any.js // Copyright © web-platform-tests contributors. Available under the 3-Clause BSD License. -import { DESCRIPTORS, GLOBAL } from '../helpers/constants'; +import { GLOBAL } from '../helpers/constants'; import { fromSource } from '../helpers/helpers'; const { from } = Array; @@ -28,13 +28,7 @@ QUnit.module('structuredClone', () => { }); } - function cloneFailureTest(assert, value) { - assert.throws(() => structuredClone(value), typeof DOMException === 'function' ? DOMException : Error); - } - - // // ECMAScript types - // // Primitive values: Undefined, Null, Boolean, Number, BigInt, String const booleans = [false, true]; @@ -136,7 +130,7 @@ QUnit.module('structuredClone', () => { }); // ArrayBuffer - if (DESCRIPTORS) QUnit.test('ArrayBuffer', assert => { // Crashes + if (typeof Uint8Array == 'function') QUnit.test('ArrayBuffer', assert => { // Crashes cloneObjectTest(assert, new Uint8Array([0, 1, 254, 255]).buffer, (orig, clone) => { assert.arrayEqual(new Uint8Array(orig), new Uint8Array(clone)); }); @@ -145,7 +139,7 @@ QUnit.module('structuredClone', () => { // TODO SharedArrayBuffer // Array Buffer Views - if (DESCRIPTORS) QUnit.test('ArrayBufferView', assert => { + if (typeof Uint8ClampedArray == 'function') QUnit.test('ArrayBufferView', assert => { const arrays = [ new Uint8Array([]), new Uint8Array([0, 1, 254, 255]), @@ -251,12 +245,7 @@ QUnit.module('structuredClone', () => { }); }); - // // [Serializable] Platform objects - // - - // TODO: Test these additional interfaces: - // * RTCCertificate // Geometry types if (typeof DOMMatrix == 'function' && typeof DOMMatrix.fromMatrix == 'function') { @@ -334,7 +323,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof ImageData == 'function') QUnit.test('ImageData', assert => { + if (fromSource('new ImageData(8, 8)')) QUnit.test('ImageData', assert => { const imageData = new ImageData(8, 8); for (let i = 0; i < 256; ++i) { imageData.data[i] = i; @@ -347,7 +336,7 @@ QUnit.module('structuredClone', () => { }); }); - if (typeof Blob == 'function') QUnit.test('Blob', assert => { + if (fromSource('new Blob(["test"])')) QUnit.test('Blob', assert => { cloneObjectTest( assert, new Blob(['This is a test.'], { type: 'a/b' }), @@ -359,7 +348,8 @@ QUnit.module('structuredClone', () => { }); }); - if (typeof DOMException == 'function') QUnit.test('DOMException', assert => { + // TODO: remove DOMException constructor check after https://github.com/zloirock/core-js/pull/991 + if (fromSource('new DOMException')) QUnit.test('DOMException', assert => { const errors = [ new DOMException(), new DOMException('foo', 'DataCloneError'), @@ -373,7 +363,7 @@ QUnit.module('structuredClone', () => { }); }); - if (typeof File == 'function') QUnit.test('File', assert => { + if (fromSource('new File(["test"], "foo.txt")')) QUnit.test('File', assert => { cloneObjectTest( assert, new File(['This is a test.'], 'foo.txt', { type: 'c/d' }), @@ -399,9 +389,7 @@ QUnit.module('structuredClone', () => { } }); - // // Non-serializable types - // QUnit.test('Non-serializable types', assert => { const nons = [ function () { return 1; }, @@ -409,9 +397,15 @@ QUnit.module('structuredClone', () => { GLOBAL, ]; - if (typeof Event == 'function') nons.push(new Event('')); - if (typeof MessageChannel == 'function') nons.push(new MessageChannel()); + const event = fromSource('new Event("")'); + const channel = fromSource('new MessageChannel'); - for (const it of nons) cloneFailureTest(assert, it); + if (event) nons.push(event); + if (channel) nons.push(channel); + + for (const it of nons) { + // TODO: remove DOMException constructor check after https://github.com/zloirock/core-js/pull/991 + assert.throws(() => structuredClone(it), fromSource('new DOMException') ? DOMException : TypeError); + } }); }); From 104a7b510406d651a612352757c18c138d01b36f Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Mon, 1 Nov 2021 21:19:43 +0700 Subject: [PATCH 34/73] use `PerformanceMark` instead of `performance.mark` --- packages/core-js/internals/structured-clone.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 3d76ef810585..e2d3b1f77b43 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -2,7 +2,6 @@ var IS_PURE = require('../internals/is-pure'); var global = require('../internals/global'); var getBuiltin = require('../internals/get-built-in'); -var call = require('../internals/function-call'); var uncurryThis = require('../internals/function-uncurry-this'); var fails = require('../internals/fails'); var uid = require('../internals/uid'); @@ -26,9 +25,7 @@ var URIError = global.URIError; var Set = getBuiltin('Set'); var Map = getBuiltin('Map'); var MapPrototype = Map.prototype; -var performance = global.performance; -var mark = performance && performance.mark; -var clearMarks = performance && performance.clearMarks; +var PerformanceMark = global.PerformanceMark; var mapHas = uncurryThis(MapPrototype.has); var mapGet = uncurryThis(MapPrototype.get); var mapSet = uncurryThis(MapPrototype.set); @@ -46,9 +43,7 @@ var structuredCloneFromMark = (function (structuredClone) { return cloned === set || !set.has(42); }) && structuredClone; })(function (value) { - var cloned = call(mark, performance, PERFORMANCE_MARK, { detail: value }).detail; - call(clearMarks, performance, PERFORMANCE_MARK); - return cloned; + return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail; }); var nativeRestrictedStructuredClone = global.structuredClone || structuredCloneFromMark; From 48981fd84e6f68f86442dd11bc4b4562ad96bbfa Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Mon, 1 Nov 2021 22:44:54 +0700 Subject: [PATCH 35/73] fix tests: some engines have `DOMQuad`, but haven't `DOMPoint.fromPoint` --- tests/pure/web.structured-clone.js | 2 +- tests/tests/web.structured-clone.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index b83a7fbf3e32..f38fc88d1897 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -295,7 +295,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMPoint == 'function' && typeof DOMQuad == 'function') { + if (typeof DOMQuad == 'function' && typeof DOMPoint == 'function' && typeof DOMPoint.fromPoint == 'function') { QUnit.test('Geometry types, DOMQuad', assert => { cloneObjectTest(assert, new DOMQuad( new DOMPoint(1, 2, 3, 4), diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 9922d4ef6d8f..47d018b4c188 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -288,7 +288,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMPoint == 'function' && typeof DOMQuad == 'function') { + if (typeof DOMQuad == 'function' && typeof DOMPoint == 'function' && typeof DOMPoint.fromPoint == 'function') { QUnit.test('Geometry types, DOMQuad', assert => { cloneObjectTest(assert, new DOMQuad( new DOMPoint(1, 2, 3, 4), From fc14c99d6bf53efbf29e004177f22b9349410766 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Mon, 1 Nov 2021 23:50:57 +0700 Subject: [PATCH 36/73] use constructors as fallbacks for some geometry types to make them cloneable in FF31-68 --- packages/core-js/internals/structured-clone.js | 12 +++++++++--- tests/pure/web.structured-clone.js | 8 ++++---- tests/tests/web.structured-clone.js | 8 ++++---- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index e2d3b1f77b43..613a0b2d2a18 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -179,7 +179,9 @@ var structuredCloneInternal = module.exports = function (value, map) { break; case 'DOMPoint': case 'DOMPointReadOnly': - cloned = global[type].fromPoint(value); + cloned = global[type].fromPoint + ? global[type].fromPoint(value) + : new global[type](value.x, value.y, value.z, value.w); break; case 'DOMQuad': cloned = new DOMQuad( @@ -191,11 +193,15 @@ var structuredCloneInternal = module.exports = function (value, map) { break; case 'DOMRect': case 'DOMRectReadOnly': - cloned = global[type].fromRect(value); + cloned = global[type].fromRect + ? global[type].fromRect(value) + : new global[type](value.x, value.y, value.width, value.height); break; case 'DOMMatrix': case 'DOMMatrixReadOnly': - cloned = global[type].fromMatrix(value); + cloned = global[type].fromMatrix + ? global[type].fromMatrix(value) + : new global[type](value); break; case 'AudioData': case 'VideoFrame': diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index f38fc88d1897..15c080a2c93b 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -255,7 +255,7 @@ QUnit.module('structuredClone', () => { // [Serializable] Platform objects // Geometry types - if (typeof DOMMatrix == 'function' && typeof DOMMatrix.fromMatrix == 'function') { + if (typeof DOMMatrix == 'function') { QUnit.test('Geometry types, DOMMatrix', assert => { cloneObjectTest(assert, new DOMMatrix(), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { @@ -275,7 +275,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMPoint == 'function' && typeof DOMPoint.fromPoint == 'function') { + if (typeof DOMPoint == 'function') { QUnit.test('Geometry types, DOMPoint', assert => { cloneObjectTest(assert, new DOMPoint(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { @@ -295,7 +295,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMQuad == 'function' && typeof DOMPoint == 'function' && typeof DOMPoint.fromPoint == 'function') { + if (typeof DOMQuad == 'function' && typeof DOMPoint == 'function') { QUnit.test('Geometry types, DOMQuad', assert => { cloneObjectTest(assert, new DOMQuad( new DOMPoint(1, 2, 3, 4), @@ -310,7 +310,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMRect == 'function' && typeof DOMRect.fromRect == 'function') { + if (typeof DOMRect == 'function') { QUnit.test('Geometry types, DOMRect', assert => { cloneObjectTest(assert, new DOMRect(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 47d018b4c188..5c25bcf48cd0 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -248,7 +248,7 @@ QUnit.module('structuredClone', () => { // [Serializable] Platform objects // Geometry types - if (typeof DOMMatrix == 'function' && typeof DOMMatrix.fromMatrix == 'function') { + if (typeof DOMMatrix == 'function') { QUnit.test('Geometry types, DOMMatrix', assert => { cloneObjectTest(assert, new DOMMatrix(), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { @@ -268,7 +268,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMPoint == 'function' && typeof DOMPoint.fromPoint == 'function') { + if (typeof DOMPoint == 'function') { QUnit.test('Geometry types, DOMPoint', assert => { cloneObjectTest(assert, new DOMPoint(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { @@ -288,7 +288,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMQuad == 'function' && typeof DOMPoint == 'function' && typeof DOMPoint.fromPoint == 'function') { + if (typeof DOMQuad == 'function' && typeof DOMPoint == 'function') { QUnit.test('Geometry types, DOMQuad', assert => { cloneObjectTest(assert, new DOMQuad( new DOMPoint(1, 2, 3, 4), @@ -303,7 +303,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMRect == 'function' && typeof DOMRect.fromRect == 'function') { + if (typeof DOMRect == 'function') { QUnit.test('Geometry types, DOMRect', assert => { cloneObjectTest(assert, new DOMRect(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { From 44ae9b18068d0a37aa4aae5a6ee8ab546231532a Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Tue, 2 Nov 2021 00:35:32 +0700 Subject: [PATCH 37/73] fix tests for `DOMRect` in FF30- --- tests/pure/web.structured-clone.js | 2 +- tests/tests/web.structured-clone.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index 15c080a2c93b..b16f72281999 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -310,7 +310,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMRect == 'function') { + if (fromSource('new DOMRect(1, 2, 3, 4)')) { QUnit.test('Geometry types, DOMRect', assert => { cloneObjectTest(assert, new DOMRect(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 5c25bcf48cd0..5e4e9b62a0e2 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -303,7 +303,7 @@ QUnit.module('structuredClone', () => { }); } - if (typeof DOMRect == 'function') { + if (fromSource('new DOMRect(1, 2, 3, 4)')) { QUnit.test('Geometry types, DOMRect', assert => { cloneObjectTest(assert, new DOMRect(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { From eb0354970500d323a8ce359f824c86c370c5c64b Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Tue, 2 Nov 2021 01:57:10 +0700 Subject: [PATCH 38/73] drop some dead code from tests --- tests/pure/web.structured-clone.js | 8 ++++---- tests/tests/web.structured-clone.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index b16f72281999..5d7a0ea7ed3c 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -200,10 +200,10 @@ QUnit.module('structuredClone', () => { ]; for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - assert.equal(orig.constructor === AggregateError ? Error : orig.constructor, clone.constructor); - assert.equal(orig.name === 'AggregateError' ? 'Error' : orig.name, clone.name); - assert.equal(orig.message, clone.message); - assert.equal(orig.stack, clone.stack); + assert.equal(orig.constructor, clone.constructor, `constructor ${ orig.name }`); + assert.equal(orig.name, clone.name, `name ${ orig.name }`); + assert.equal(orig.message, clone.message, `message ${ orig.name }`); + assert.equal(orig.stack, clone.stack, `stack ${ orig.name }`); }); const aggregates = [ diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 5e4e9b62a0e2..8798eddacee2 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -193,10 +193,10 @@ QUnit.module('structuredClone', () => { ]; for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - assert.equal(orig.constructor === AggregateError ? Error : orig.constructor, clone.constructor); - assert.equal(orig.name === 'AggregateError' ? 'Error' : orig.name, clone.name); - assert.equal(orig.message, clone.message); - assert.equal(orig.stack, clone.stack); + assert.equal(orig.constructor, clone.constructor, `constructor ${ orig.name }`); + assert.equal(orig.name, clone.name, `name ${ orig.name }`); + assert.equal(orig.message, clone.message, `message ${ orig.name }`); + assert.equal(orig.stack, clone.stack, `stack ${ orig.name }`); }); const aggregates = [ From d354dd32f0575527ec7cc36d4ef00574765541eb Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Tue, 2 Nov 2021 18:48:47 +0700 Subject: [PATCH 39/73] clone `FileList` --- .../core-js/internals/structured-clone.js | 9 ++++++- tests/pure/web.structured-clone.js | 25 +++++++++++-------- tests/tests/web.structured-clone.js | 25 +++++++++++-------- 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index 613a0b2d2a18..a4ee639c5f98 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -76,8 +76,8 @@ var structuredCloneInternal = module.exports = function (value, map) { if (mapHas(map, value)) return mapGet(map, value); } else map = new Map(); - var C, cloned, deep, key; var type = classof(value); + var C, cloned, deep, dataTransfer, i, length, key; switch (type) { case 'BigInt': @@ -214,6 +214,13 @@ var structuredCloneInternal = module.exports = function (value, map) { { type: value.type, lastModified: value.lastModified } ); break; + case 'FileList': + dataTransfer = new DataTransfer(); + for (i = 0, length = value.length; i < length; i++) { + dataTransfer.items.add(structuredCloneInternal(value[i], map)); + } + cloned = dataTransfer.files; + break; case 'ImageData': cloned = new ImageData( structuredCloneInternal(value.data, map), diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index 5d7a0ea7ed3c..f7903443f637 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -384,16 +384,21 @@ QUnit.module('structuredClone', () => { }); }); - // FileList - exposed in Workers, but not constructable. - QUnit.skip('FileList', assert => { - if ('document' in GLOBAL) { - // TODO: Test with populated list. - cloneObjectTest( - assert, - assign(document.createElement('input'), { type: 'file', multiple: true }).files, - (orig, clone) => assert.equal(orig.length, clone.length), - ); - } + // FileList + if (fromSource('new File(["test"], "foo.txt")') && fromSource('new DataTransfer()')) QUnit.test('FileList', assert => { + const transfer = new DataTransfer(); + transfer.items.add(new File(['test'], 'foo.txt')); + cloneObjectTest( + assert, + transfer.files, + (orig, clone) => { + assert.equal(1, clone.length); + assert.equal(orig[0].size, clone[0].size); + assert.equal(orig[0].type, clone[0].type); + assert.equal(orig[0].name, clone[0].name); + assert.equal(orig[0].lastModified, clone[0].lastModified); + }, + ); }); // Non-serializable types diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 8798eddacee2..f45d62589711 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -377,16 +377,21 @@ QUnit.module('structuredClone', () => { }); }); - // FileList - exposed in Workers, but not constructable. - QUnit.skip('FileList', assert => { - if ('document' in GLOBAL) { - // TODO: Test with populated list. - cloneObjectTest( - assert, - assign(document.createElement('input'), { type: 'file', multiple: true }).files, - (orig, clone) => assert.equal(orig.length, clone.length), - ); - } + // FileList + if (fromSource('new File(["test"], "foo.txt")') && fromSource('new DataTransfer()')) QUnit.test('FileList', assert => { + const transfer = new DataTransfer(); + transfer.items.add(new File(['test'], 'foo.txt')); + cloneObjectTest( + assert, + transfer.files, + (orig, clone) => { + assert.equal(1, clone.length); + assert.equal(orig[0].size, clone[0].size); + assert.equal(orig[0].type, clone[0].type); + assert.equal(orig[0].name, clone[0].name); + assert.equal(orig[0].lastModified, clone[0].lastModified); + }, + ); }); // Non-serializable types From 92755dcb9c29e22462b7d5e02e965ceb9aec587a Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Wed, 3 Nov 2021 00:36:44 +0700 Subject: [PATCH 40/73] Chrome 82- swaps `.name` and `.message` of cloned `DOMException` --- packages/core-js/internals/structured-clone.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js index a4ee639c5f98..ab435a220a01 100644 --- a/packages/core-js/internals/structured-clone.js +++ b/packages/core-js/internals/structured-clone.js @@ -36,6 +36,7 @@ var stringValueOf = uncurryThis(''.valueOf); var getTime = uncurryThis(Date.prototype.getTime); var PERFORMANCE_MARK = uid('structuredClone'); +// Chrome 78+, Safari 14.1+ var structuredCloneFromMark = (function (structuredClone) { return !fails(function () { var set = new global.Set([42]); @@ -46,9 +47,15 @@ var structuredCloneFromMark = (function (structuredClone) { return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail; }); +// + FF94+ var nativeRestrictedStructuredClone = global.structuredClone || structuredCloneFromMark; var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !fails(function () { + // Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` + if (typeof DOMException == 'function') { + var test = structuredCloneFromMark(new DOMException('a', 'DataCloneError')); + if (test.name !== 'DataCloneError' || test.message !== 'a') return true; + } // current Safari implementation can't clone errors return structuredCloneFromMark(Error('a')).message !== 'a'; }); @@ -208,11 +215,7 @@ var structuredCloneInternal = module.exports = function (value, map) { cloned = value.clone(); break; case 'File': - cloned = new File( - [value], - value.name, - { type: value.type, lastModified: value.lastModified } - ); + cloned = new File([value], value.name, value); break; case 'FileList': dataTransfer = new DataTransfer(); From 5341988ae395a70e651189e2bc21e54f9da170e5 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 4 Nov 2021 01:56:38 +0700 Subject: [PATCH 41/73] move `internals/structured-clone` content to `modules/web.structured-clone` --- .../core-js/internals/structured-clone.js | 266 ------------------ .../core-js/modules/web.structured-clone.js | 263 ++++++++++++++++- 2 files changed, 259 insertions(+), 270 deletions(-) delete mode 100644 packages/core-js/internals/structured-clone.js diff --git a/packages/core-js/internals/structured-clone.js b/packages/core-js/internals/structured-clone.js deleted file mode 100644 index ab435a220a01..000000000000 --- a/packages/core-js/internals/structured-clone.js +++ /dev/null @@ -1,266 +0,0 @@ -'use strict'; -var IS_PURE = require('../internals/is-pure'); -var global = require('../internals/global'); -var getBuiltin = require('../internals/get-built-in'); -var uncurryThis = require('../internals/function-uncurry-this'); -var fails = require('../internals/fails'); -var uid = require('../internals/uid'); -var isObject = require('../internals/is-object'); -var isSymbol = require('../internals/is-symbol'); -var classof = require('../internals/classof'); -var hasOwn = require('../internals/has-own-property'); -var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); -var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); -var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); - -var Object = global.Object; -var Date = global.Date; -var Error = global.Error; -var EvalError = global.EvalError; -var RangeError = global.RangeError; -var ReferenceError = global.ReferenceError; -var SyntaxError = global.SyntaxError; -var TypeError = global.TypeError; -var URIError = global.URIError; -var Set = getBuiltin('Set'); -var Map = getBuiltin('Map'); -var MapPrototype = Map.prototype; -var PerformanceMark = global.PerformanceMark; -var mapHas = uncurryThis(MapPrototype.has); -var mapGet = uncurryThis(MapPrototype.get); -var mapSet = uncurryThis(MapPrototype.set); -var setAdd = uncurryThis(Set.prototype.add); -var bolleanValueOf = uncurryThis(true.valueOf); -var numberValueOf = uncurryThis(1.0.valueOf); -var stringValueOf = uncurryThis(''.valueOf); -var getTime = uncurryThis(Date.prototype.getTime); -var PERFORMANCE_MARK = uid('structuredClone'); - -// Chrome 78+, Safari 14.1+ -var structuredCloneFromMark = (function (structuredClone) { - return !fails(function () { - var set = new global.Set([42]); - var cloned = structuredClone(set); - return cloned === set || !set.has(42); - }) && structuredClone; -})(function (value) { - return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail; -}); - -// + FF94+ -var nativeRestrictedStructuredClone = global.structuredClone || structuredCloneFromMark; - -var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !fails(function () { - // Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` - if (typeof DOMException == 'function') { - var test = structuredCloneFromMark(new DOMException('a', 'DataCloneError')); - if (test.name !== 'DataCloneError' || test.message !== 'a') return true; - } - // current Safari implementation can't clone errors - return structuredCloneFromMark(Error('a')).message !== 'a'; -}); - -var createDataCloneError = function (message) { - try { - return new DOMException(message, 'DataCloneError'); - } catch (error) { - return TypeError(message); - } -}; - -/** - * Tries best to replicate structuredClone behaviour. - * - * @param {Map} map cache map - * @param {any} value object to clone - */ -var structuredCloneInternal = module.exports = function (value, map) { - if (isSymbol(value)) throw createDataCloneError('Symbols are not cloneable'); - if (!isObject(value)) return value; - if (USE_STRUCTURED_CLONE_FROM_MARK) return structuredCloneFromMark(value); - // effectively preserves circular references - if (map) { - if (mapHas(map, value)) return mapGet(map, value); - } else map = new Map(); - - var type = classof(value); - var C, cloned, deep, dataTransfer, i, length, key; - - switch (type) { - case 'BigInt': - // can be a 3rd party polyfill - cloned = Object(value.valueOf()); - break; - case 'Boolean': - cloned = Object(bolleanValueOf(value)); - break; - case 'Number': - cloned = Object(numberValueOf(value)); - break; - case 'String': - cloned = Object(stringValueOf(value)); - break; - case 'Date': - cloned = new Date(getTime(value)); - break; - case 'RegExp': - cloned = new RegExp(value); - break; - case 'ArrayBuffer': - case 'SharedArrayBuffer': - if (isArrayBufferDetached(value)) throw createDataCloneError('ArrayBuffer is deatched'); - cloned = type === 'ArrayBuffer' ? value.slice(0) - // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original - : nativeRestrictedStructuredClone ? nativeRestrictedStructuredClone(value) : value; - break; - case 'DataView': - case 'Int8Array': - case 'Uint8Array': - case 'Uint8ClampedArray': - case 'Int16Array': - case 'Uint16Array': - case 'Int32Array': - case 'Uint32Array': - case 'Float32Array': - case 'Float64Array': - case 'BigInt64Array': - case 'BigUint64Array': - cloned = new global[type]( - // this is safe, since arraybuffer cannot have circular references - structuredCloneInternal(value.buffer, map), - value.byteOffset, - type === 'DataView' ? value.byteLength : value.length - ); - break; - case 'Map': - cloned = new Map(); - deep = true; - break; - case 'Set': - cloned = new Set(); - deep = true; - break; - case 'Error': - switch (value.name) { - case 'Error': - C = Error; - break; - case 'EvalError': - C = EvalError; - break; - case 'RangeError': - C = RangeError; - break; - case 'ReferenceError': - C = ReferenceError; - break; - case 'SyntaxError': - C = SyntaxError; - break; - case 'TypeError': - C = TypeError; - break; - case 'URIError': - C = URIError; - break; - default: - C = Error; - } - cloned = C(value.message); - deep = true; // clone stack after storing in the map - break; - case 'Array': - cloned = []; - deep = true; - break; - case 'Object': - cloned = {}; - deep = true; - break; - case 'Blob': - cloned = value.slice(0, value.size, value.type); - break; - case 'DOMException': - cloned = new (getBuiltin('DOMException'))(value.message, value.name); - deep = true; // clone stack after storing in the map - break; - case 'DOMPoint': - case 'DOMPointReadOnly': - cloned = global[type].fromPoint - ? global[type].fromPoint(value) - : new global[type](value.x, value.y, value.z, value.w); - break; - case 'DOMQuad': - cloned = new DOMQuad( - structuredCloneInternal(value.p1, map), - structuredCloneInternal(value.p2, map), - structuredCloneInternal(value.p3, map), - structuredCloneInternal(value.p4, map) - ); - break; - case 'DOMRect': - case 'DOMRectReadOnly': - cloned = global[type].fromRect - ? global[type].fromRect(value) - : new global[type](value.x, value.y, value.width, value.height); - break; - case 'DOMMatrix': - case 'DOMMatrixReadOnly': - cloned = global[type].fromMatrix - ? global[type].fromMatrix(value) - : new global[type](value); - break; - case 'AudioData': - case 'VideoFrame': - cloned = value.clone(); - break; - case 'File': - cloned = new File([value], value.name, value); - break; - case 'FileList': - dataTransfer = new DataTransfer(); - for (i = 0, length = value.length; i < length; i++) { - dataTransfer.items.add(structuredCloneInternal(value[i], map)); - } - cloned = dataTransfer.files; - break; - case 'ImageData': - cloned = new ImageData( - structuredCloneInternal(value.data, map), - value.width, - value.height, - { colorSpace: value.colorSpace } - ); - break; - default: - if (nativeRestrictedStructuredClone) cloned = nativeRestrictedStructuredClone(value); - else throw createDataCloneError('Uncloneable type: ' + type); - } - - mapSet(map, value, cloned); - - if (deep) switch (type) { - case 'Map': - value.forEach(function (v, k) { - mapSet(cloned, structuredCloneInternal(k, map), structuredCloneInternal(v, map)); - }); - break; - case 'Set': - value.forEach(function (v) { - setAdd(cloned, structuredCloneInternal(v, map)); - }); - break; - case 'Error': - case 'DOMException': - if (ERROR_STACK_INSTALLABLE) createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map)); - break; - case 'Array': - case 'Object': - for (key in value) if (hasOwn(value, key)) { - cloned[key] = structuredCloneInternal(value[key], map); - } - break; - } - - return cloned; -}; diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 80ba53e83684..d7861fec54fd 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -1,17 +1,272 @@ var IS_PURE = require('../internals/is-pure'); var $ = require('../internals/export'); var global = require('../internals/global'); +var getBuiltin = require('../internals/get-built-in'); +var uncurryThis = require('../internals/function-uncurry-this'); var fails = require('../internals/fails'); +var uid = require('../internals/uid'); +var isObject = require('../internals/is-object'); +var isSymbol = require('../internals/is-symbol'); var anObject = require('../internals/an-object'); -var structuredCloneInternal = require('../internals/structured-clone'); +var classof = require('../internals/classof'); +var hasOwn = require('../internals/has-own-property'); +var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); +var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); +var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); -var n$StructuredClone = global.structuredClone; +var nativeStructuredClone = global.structuredClone; +var Object = global.Object; +var Date = global.Date; +var Error = global.Error; +var EvalError = global.EvalError; +var RangeError = global.RangeError; +var ReferenceError = global.ReferenceError; +var SyntaxError = global.SyntaxError; var TypeError = global.TypeError; +var URIError = global.URIError; +var PerformanceMark = global.PerformanceMark; +var Set = getBuiltin('Set'); +var Map = getBuiltin('Map'); +var MapPrototype = Map.prototype; +var mapHas = uncurryThis(MapPrototype.has); +var mapGet = uncurryThis(MapPrototype.get); +var mapSet = uncurryThis(MapPrototype.set); +var setAdd = uncurryThis(Set.prototype.add); +var bolleanValueOf = uncurryThis(true.valueOf); +var numberValueOf = uncurryThis(1.0.valueOf); +var stringValueOf = uncurryThis(''.valueOf); +var getTime = uncurryThis(Date.prototype.getTime); +var PERFORMANCE_MARK = uid('structuredClone'); +var DATA_CLONE_ERROR = 'DataCloneError'; + +// Chrome 78+, Safari 14.1+ +var structuredCloneFromMark = (function (structuredCloneFromMarkImpl) { + return !fails(function () { + var set = new global.Set([42]); + var cloned = structuredCloneFromMarkImpl(set); + return cloned === set || !set.has(42); + }) && structuredCloneFromMarkImpl; +})(function (value) { + return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail; +}); + +// + FF94+ +var nativeRestrictedStructuredClone = nativeStructuredClone || structuredCloneFromMark; + +var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !fails(function () { + // Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` + if (typeof DOMException == 'function') { + var test = structuredCloneFromMark(new DOMException(PERFORMANCE_MARK, DATA_CLONE_ERROR)); + if (test.name !== DATA_CLONE_ERROR || test.message !== PERFORMANCE_MARK) return true; + } + // current Safari implementation can't clone errors + return structuredCloneFromMark(Error(PERFORMANCE_MARK)).message !== PERFORMANCE_MARK; +}); + +// waiting for https://github.com/zloirock/core-js/pull/991 +var DOMException = function (message, name) { + try { + return new global.DOMException(message, name); + } catch (error) { + return TypeError(message); + } +}; + +var structuredCloneInternal = function (value, map) { + if (isSymbol(value)) throw new DOMException('Symbols are not cloneable', DATA_CLONE_ERROR); + if (!isObject(value)) return value; + if (USE_STRUCTURED_CLONE_FROM_MARK) return structuredCloneFromMark(value); + // effectively preserves circular references + if (map) { + if (mapHas(map, value)) return mapGet(map, value); + } else map = new Map(); + + var type = classof(value); + var C, cloned, deep, dataTransfer, i, length, key; + + switch (type) { + case 'BigInt': + // can be a 3rd party polyfill + cloned = Object(value.valueOf()); + break; + case 'Boolean': + cloned = Object(bolleanValueOf(value)); + break; + case 'Number': + cloned = Object(numberValueOf(value)); + break; + case 'String': + cloned = Object(stringValueOf(value)); + break; + case 'Date': + cloned = new Date(getTime(value)); + break; + case 'RegExp': + cloned = new RegExp(value); + break; + case 'ArrayBuffer': + case 'SharedArrayBuffer': + if (isArrayBufferDetached(value)) throw new DOMException('ArrayBuffer is deatched', DATA_CLONE_ERROR); + cloned = type === 'ArrayBuffer' ? value.slice(0) + // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original + : nativeRestrictedStructuredClone ? nativeRestrictedStructuredClone(value) : value; + break; + case 'DataView': + case 'Int8Array': + case 'Uint8Array': + case 'Uint8ClampedArray': + case 'Int16Array': + case 'Uint16Array': + case 'Int32Array': + case 'Uint32Array': + case 'Float32Array': + case 'Float64Array': + case 'BigInt64Array': + case 'BigUint64Array': + cloned = new global[type]( + // this is safe, since arraybuffer cannot have circular references + structuredCloneInternal(value.buffer, map), + value.byteOffset, + type === 'DataView' ? value.byteLength : value.length + ); + break; + case 'Map': + cloned = new Map(); + deep = true; + break; + case 'Set': + cloned = new Set(); + deep = true; + break; + case 'Error': + switch (value.name) { + case 'Error': + C = Error; + break; + case 'EvalError': + C = EvalError; + break; + case 'RangeError': + C = RangeError; + break; + case 'ReferenceError': + C = ReferenceError; + break; + case 'SyntaxError': + C = SyntaxError; + break; + case 'TypeError': + C = TypeError; + break; + case 'URIError': + C = URIError; + break; + default: + C = Error; + } + cloned = C(value.message); + deep = true; // clone stack after storing in the map + break; + case 'Array': + cloned = []; + deep = true; + break; + case 'Object': + cloned = {}; + deep = true; + break; + case 'Blob': + cloned = value.slice(0, value.size, value.type); + break; + case 'DOMException': + cloned = new DOMException(value.message, value.name); + deep = true; // clone stack after storing in the map + break; + case 'DOMPoint': + case 'DOMPointReadOnly': + cloned = global[type].fromPoint + ? global[type].fromPoint(value) + : new global[type](value.x, value.y, value.z, value.w); + break; + case 'DOMQuad': + cloned = new DOMQuad( + structuredCloneInternal(value.p1, map), + structuredCloneInternal(value.p2, map), + structuredCloneInternal(value.p3, map), + structuredCloneInternal(value.p4, map) + ); + break; + case 'DOMRect': + case 'DOMRectReadOnly': + cloned = global[type].fromRect + ? global[type].fromRect(value) + : new global[type](value.x, value.y, value.width, value.height); + break; + case 'DOMMatrix': + case 'DOMMatrixReadOnly': + cloned = global[type].fromMatrix + ? global[type].fromMatrix(value) + : new global[type](value); + break; + case 'AudioData': + case 'VideoFrame': + cloned = value.clone(); + break; + case 'File': + cloned = new File([value], value.name, value); + break; + case 'FileList': + dataTransfer = new DataTransfer(); + for (i = 0, length = value.length; i < length; i++) { + dataTransfer.items.add(structuredCloneInternal(value[i], map)); + } + cloned = dataTransfer.files; + break; + case 'ImageData': + cloned = new ImageData( + structuredCloneInternal(value.data, map), + value.width, + value.height, + { colorSpace: value.colorSpace } + ); + break; + default: + if (nativeRestrictedStructuredClone) cloned = nativeRestrictedStructuredClone(value); + else throw new DOMException('Uncloneable type: ' + type, DATA_CLONE_ERROR); + } + + mapSet(map, value, cloned); + + if (deep) switch (type) { + case 'Map': + value.forEach(function (v, k) { + mapSet(cloned, structuredCloneInternal(k, map), structuredCloneInternal(v, map)); + }); + break; + case 'Set': + value.forEach(function (v) { + setAdd(cloned, structuredCloneInternal(v, map)); + }); + break; + case 'Error': + case 'DOMException': + if (ERROR_STACK_INSTALLABLE) createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map)); + break; + case 'Array': + case 'Object': + for (key in value) if (hasOwn(value, key)) { + cloned[key] = structuredCloneInternal(value[key], map); + } + break; + } + + return cloned; +}; var FORCED = IS_PURE || fails(function () { // current FF implementation can't clone errors // https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 - return n$StructuredClone(Error('a')).message !== 'a'; + return nativeStructuredClone(Error('a')).message !== 'a'; }); $({ global: true, enumerable: true, sham: true, forced: FORCED }, { @@ -20,7 +275,7 @@ $({ global: true, enumerable: true, sham: true, forced: FORCED }, { var transfer = options && options.transfer; if (transfer !== undefined) { - if (!IS_PURE && n$StructuredClone) return n$StructuredClone(value, options); + if (!IS_PURE && nativeStructuredClone) return nativeStructuredClone(value, options); throw TypeError('Transfer option is not supported'); } From 68e1f60e01b63aa8673ca1ebb5b617458c29215c Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 4 Nov 2021 02:34:57 +0700 Subject: [PATCH 42/73] simplify `ArrayBuffer` case, update entries dependencies --- packages/core-js/es/array-buffer/constructor.js | 1 + packages/core-js/es/data-view/index.js | 2 ++ packages/core-js/es/typed-array/float32-array.js | 2 ++ packages/core-js/es/typed-array/float64-array.js | 2 ++ packages/core-js/es/typed-array/int16-array.js | 2 ++ packages/core-js/es/typed-array/int32-array.js | 2 ++ packages/core-js/es/typed-array/int8-array.js | 2 ++ packages/core-js/es/typed-array/uint16-array.js | 2 ++ packages/core-js/es/typed-array/uint32-array.js | 2 ++ packages/core-js/es/typed-array/uint8-array.js | 2 ++ .../core-js/es/typed-array/uint8-clamped-array.js | 2 ++ .../core-js/internals/array-buffer-is-deatched.js | 14 -------------- packages/core-js/modules/web.structured-clone.js | 14 +++++++++----- packages/core-js/stable/structured-clone.js | 2 +- packages/core-js/web/structured-clone.js | 2 +- 15 files changed, 32 insertions(+), 21 deletions(-) delete mode 100644 packages/core-js/internals/array-buffer-is-deatched.js diff --git a/packages/core-js/es/array-buffer/constructor.js b/packages/core-js/es/array-buffer/constructor.js index b7a76ed5ce70..addb8503ff08 100644 --- a/packages/core-js/es/array-buffer/constructor.js +++ b/packages/core-js/es/array-buffer/constructor.js @@ -1,4 +1,5 @@ require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.object.to-string'); var path = require('../../internals/path'); diff --git a/packages/core-js/es/data-view/index.js b/packages/core-js/es/data-view/index.js index da3582d7ffa2..aa7f63ef61f3 100644 --- a/packages/core-js/es/data-view/index.js +++ b/packages/core-js/es/data-view/index.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.data-view'); require('../../modules/es.object.to-string'); var path = require('../../internals/path'); diff --git a/packages/core-js/es/typed-array/float32-array.js b/packages/core-js/es/typed-array/float32-array.js index 70d1f810fe1d..92880ea2de50 100644 --- a/packages/core-js/es/typed-array/float32-array.js +++ b/packages/core-js/es/typed-array/float32-array.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.typed-array.float32-array'); require('./methods'); var global = require('../../internals/global'); diff --git a/packages/core-js/es/typed-array/float64-array.js b/packages/core-js/es/typed-array/float64-array.js index b84eae0a9cdf..17dafd612413 100644 --- a/packages/core-js/es/typed-array/float64-array.js +++ b/packages/core-js/es/typed-array/float64-array.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.typed-array.float64-array'); require('./methods'); var global = require('../../internals/global'); diff --git a/packages/core-js/es/typed-array/int16-array.js b/packages/core-js/es/typed-array/int16-array.js index 81f5a8e5d038..a2938d2669b4 100644 --- a/packages/core-js/es/typed-array/int16-array.js +++ b/packages/core-js/es/typed-array/int16-array.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.typed-array.int16-array'); require('./methods'); var global = require('../../internals/global'); diff --git a/packages/core-js/es/typed-array/int32-array.js b/packages/core-js/es/typed-array/int32-array.js index 48176bf56a36..5f1d985cbe6b 100644 --- a/packages/core-js/es/typed-array/int32-array.js +++ b/packages/core-js/es/typed-array/int32-array.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.typed-array.int32-array'); require('./methods'); var global = require('../../internals/global'); diff --git a/packages/core-js/es/typed-array/int8-array.js b/packages/core-js/es/typed-array/int8-array.js index 7d53845a78b5..5e8eaa6df297 100644 --- a/packages/core-js/es/typed-array/int8-array.js +++ b/packages/core-js/es/typed-array/int8-array.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.typed-array.int8-array'); require('./methods'); var global = require('../../internals/global'); diff --git a/packages/core-js/es/typed-array/uint16-array.js b/packages/core-js/es/typed-array/uint16-array.js index 9fe6065e5087..152d9af1fc79 100644 --- a/packages/core-js/es/typed-array/uint16-array.js +++ b/packages/core-js/es/typed-array/uint16-array.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.typed-array.uint16-array'); require('./methods'); var global = require('../../internals/global'); diff --git a/packages/core-js/es/typed-array/uint32-array.js b/packages/core-js/es/typed-array/uint32-array.js index 26bdb10c22f1..c1977a846a82 100644 --- a/packages/core-js/es/typed-array/uint32-array.js +++ b/packages/core-js/es/typed-array/uint32-array.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.typed-array.uint32-array'); require('./methods'); var global = require('../../internals/global'); diff --git a/packages/core-js/es/typed-array/uint8-array.js b/packages/core-js/es/typed-array/uint8-array.js index 96a5bf12f982..7b547c19af3e 100644 --- a/packages/core-js/es/typed-array/uint8-array.js +++ b/packages/core-js/es/typed-array/uint8-array.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.typed-array.uint8-array'); require('./methods'); var global = require('../../internals/global'); diff --git a/packages/core-js/es/typed-array/uint8-clamped-array.js b/packages/core-js/es/typed-array/uint8-clamped-array.js index 7e2d3ce70374..50aea2dc26bc 100644 --- a/packages/core-js/es/typed-array/uint8-clamped-array.js +++ b/packages/core-js/es/typed-array/uint8-clamped-array.js @@ -1,3 +1,5 @@ +require('../../modules/es.array-buffer.constructor'); +require('../../modules/es.array-buffer.slice'); require('../../modules/es.typed-array.uint8-clamped-array'); require('./methods'); var global = require('../../internals/global'); diff --git a/packages/core-js/internals/array-buffer-is-deatched.js b/packages/core-js/internals/array-buffer-is-deatched.js deleted file mode 100644 index b15344c6dca1..000000000000 --- a/packages/core-js/internals/array-buffer-is-deatched.js +++ /dev/null @@ -1,14 +0,0 @@ -var NATIVE_ARRAY_BUFFER = require('../internals/array-buffer-native'); - -module.exports = function (arrayBuffer) { - // core-js implementation doesn't have deatched state - if (!NATIVE_ARRAY_BUFFER) return false; - if (arrayBuffer.byteLength !== 0) return false; - try { - // https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice - arrayBuffer.slice(0, 0); - return false; - } catch (error) { - return true; - } -}; diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index d7861fec54fd..a59a629a715a 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -11,7 +11,6 @@ var anObject = require('../internals/an-object'); var classof = require('../internals/classof'); var hasOwn = require('../internals/has-own-property'); var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); -var isArrayBufferDetached = require('../internals/array-buffer-is-deatched'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); var nativeStructuredClone = global.structuredClone; @@ -105,11 +104,16 @@ var structuredCloneInternal = function (value, map) { cloned = new RegExp(value); break; case 'ArrayBuffer': + // detached buffers throws on `.slice` + try { + cloned = value.slice(0); + } catch (error) { + throw new DOMException('ArrayBuffer is deatched', DATA_CLONE_ERROR); + } + break; case 'SharedArrayBuffer': - if (isArrayBufferDetached(value)) throw new DOMException('ArrayBuffer is deatched', DATA_CLONE_ERROR); - cloned = type === 'ArrayBuffer' ? value.slice(0) - // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original - : nativeRestrictedStructuredClone ? nativeRestrictedStructuredClone(value) : value; + // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original + cloned = nativeRestrictedStructuredClone ? nativeRestrictedStructuredClone(value) : value; break; case 'DataView': case 'Int8Array': diff --git a/packages/core-js/stable/structured-clone.js b/packages/core-js/stable/structured-clone.js index 07e0c9874677..9f0748204dbe 100644 --- a/packages/core-js/stable/structured-clone.js +++ b/packages/core-js/stable/structured-clone.js @@ -1,6 +1,6 @@ +require('../modules/es.object.to-string'); require('../modules/es.map'); require('../modules/es.set'); -require('../modules/es.array-buffer.slice'); require('../modules/web.structured-clone'); var path = require('../internals/path'); diff --git a/packages/core-js/web/structured-clone.js b/packages/core-js/web/structured-clone.js index 07e0c9874677..9f0748204dbe 100644 --- a/packages/core-js/web/structured-clone.js +++ b/packages/core-js/web/structured-clone.js @@ -1,6 +1,6 @@ +require('../modules/es.object.to-string'); require('../modules/es.map'); require('../modules/es.set'); -require('../modules/es.array-buffer.slice'); require('../modules/web.structured-clone'); var path = require('../internals/path'); From 488c1fe9faf3eb29fe804d8bdd61e22c1d370dba Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 4 Nov 2021 04:14:06 +0700 Subject: [PATCH 43/73] fix the order of variables definition --- .../core-js/modules/web.structured-clone.js | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index a59a629a715a..2249524cf2e8 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -38,12 +38,21 @@ var getTime = uncurryThis(Date.prototype.getTime); var PERFORMANCE_MARK = uid('structuredClone'); var DATA_CLONE_ERROR = 'DataCloneError'; +// waiting for https://github.com/zloirock/core-js/pull/991 +var DOMException = function (message, name) { + try { + return new global.DOMException(message, name); + } catch (error) { + return TypeError(message); + } +}; + // Chrome 78+, Safari 14.1+ var structuredCloneFromMark = (function (structuredCloneFromMarkImpl) { return !fails(function () { var set = new global.Set([42]); var cloned = structuredCloneFromMarkImpl(set); - return cloned === set || !set.has(42); + return cloned === set || !cloned.has(42); }) && structuredCloneFromMarkImpl; })(function (value) { return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail; @@ -54,23 +63,12 @@ var nativeRestrictedStructuredClone = nativeStructuredClone || structuredCloneFr var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !fails(function () { // Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` - if (typeof DOMException == 'function') { - var test = structuredCloneFromMark(new DOMException(PERFORMANCE_MARK, DATA_CLONE_ERROR)); - if (test.name !== DATA_CLONE_ERROR || test.message !== PERFORMANCE_MARK) return true; - } - // current Safari implementation can't clone errors - return structuredCloneFromMark(Error(PERFORMANCE_MARK)).message !== PERFORMANCE_MARK; + var test = structuredCloneFromMark(new DOMException(PERFORMANCE_MARK, DATA_CLONE_ERROR)); + return test.name !== DATA_CLONE_ERROR || test.message !== PERFORMANCE_MARK + // current Safari implementation can't clone errors + || structuredCloneFromMark(Error(PERFORMANCE_MARK)).message !== PERFORMANCE_MARK; }); -// waiting for https://github.com/zloirock/core-js/pull/991 -var DOMException = function (message, name) { - try { - return new global.DOMException(message, name); - } catch (error) { - return TypeError(message); - } -}; - var structuredCloneInternal = function (value, map) { if (isSymbol(value)) throw new DOMException('Symbols are not cloneable', DATA_CLONE_ERROR); if (!isObject(value)) return value; From e46a9ead2b022e99be987808fe2baeaf481a6c6f Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 4 Nov 2021 20:02:33 +0700 Subject: [PATCH 44/73] use `nativeRestrictedStructuredClone` where it's possible, throw `DataCloneError` where it's required --- .../core-js/modules/web.structured-clone.js | 273 ++++++++++-------- 1 file changed, 157 insertions(+), 116 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 2249524cf2e8..3043b21a1f49 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -5,6 +5,7 @@ var getBuiltin = require('../internals/get-built-in'); var uncurryThis = require('../internals/function-uncurry-this'); var fails = require('../internals/fails'); var uid = require('../internals/uid'); +var isConstructor = require('../internals/is-constructor'); var isObject = require('../internals/is-object'); var isSymbol = require('../internals/is-symbol'); var anObject = require('../internals/an-object'); @@ -69,8 +70,12 @@ var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !fails(function () { || structuredCloneFromMark(Error(PERFORMANCE_MARK)).message !== PERFORMANCE_MARK; }); +var throwUncloneableType = function (type) { + throw new DOMException('Uncloneable type: ' + type, DATA_CLONE_ERROR); +}; + var structuredCloneInternal = function (value, map) { - if (isSymbol(value)) throw new DOMException('Symbols are not cloneable', DATA_CLONE_ERROR); + if (isSymbol(value)) throwUncloneableType('Symbol'); if (!isObject(value)) return value; if (USE_STRUCTURED_CLONE_FROM_MARK) return structuredCloneFromMark(value); // effectively preserves circular references @@ -79,58 +84,17 @@ var structuredCloneInternal = function (value, map) { } else map = new Map(); var type = classof(value); - var C, cloned, deep, dataTransfer, i, length, key; + var deep = false; + var C, cloned, dataTransfer, i, length, key; switch (type) { - case 'BigInt': - // can be a 3rd party polyfill - cloned = Object(value.valueOf()); - break; - case 'Boolean': - cloned = Object(bolleanValueOf(value)); - break; - case 'Number': - cloned = Object(numberValueOf(value)); - break; - case 'String': - cloned = Object(stringValueOf(value)); - break; - case 'Date': - cloned = new Date(getTime(value)); - break; - case 'RegExp': - cloned = new RegExp(value); - break; - case 'ArrayBuffer': - // detached buffers throws on `.slice` - try { - cloned = value.slice(0); - } catch (error) { - throw new DOMException('ArrayBuffer is deatched', DATA_CLONE_ERROR); - } - break; - case 'SharedArrayBuffer': - // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original - cloned = nativeRestrictedStructuredClone ? nativeRestrictedStructuredClone(value) : value; + case 'Array': + cloned = []; + deep = true; break; - case 'DataView': - case 'Int8Array': - case 'Uint8Array': - case 'Uint8ClampedArray': - case 'Int16Array': - case 'Uint16Array': - case 'Int32Array': - case 'Uint32Array': - case 'Float32Array': - case 'Float64Array': - case 'BigInt64Array': - case 'BigUint64Array': - cloned = new global[type]( - // this is safe, since arraybuffer cannot have circular references - structuredCloneInternal(value.buffer, map), - value.byteOffset, - type === 'DataView' ? value.byteLength : value.length - ); + case 'Object': + cloned = {}; + deep = true; break; case 'Map': cloned = new Map(); @@ -167,79 +131,161 @@ var structuredCloneInternal = function (value, map) { C = Error; } cloned = C(value.message); - deep = true; // clone stack after storing in the map - break; - case 'Array': - cloned = []; - deep = true; - break; - case 'Object': - cloned = {}; - deep = true; - break; - case 'Blob': - cloned = value.slice(0, value.size, value.type); + deep = ERROR_STACK_INSTALLABLE; break; case 'DOMException': cloned = new DOMException(value.message, value.name); - deep = true; // clone stack after storing in the map - break; - case 'DOMPoint': - case 'DOMPointReadOnly': - cloned = global[type].fromPoint - ? global[type].fromPoint(value) - : new global[type](value.x, value.y, value.z, value.w); + deep = ERROR_STACK_INSTALLABLE; break; - case 'DOMQuad': - cloned = new DOMQuad( - structuredCloneInternal(value.p1, map), - structuredCloneInternal(value.p2, map), - structuredCloneInternal(value.p3, map), - structuredCloneInternal(value.p4, map) + case 'DataView': + case 'Int8Array': + case 'Uint8Array': + case 'Uint8ClampedArray': + case 'Int16Array': + case 'Uint16Array': + case 'Int32Array': + case 'Uint32Array': + case 'Float32Array': + case 'Float64Array': + case 'BigInt64Array': + case 'BigUint64Array': + C = global[type]; + if (!isConstructor(C)) throwUncloneableType(type); + cloned = new C( + // this is safe, since arraybuffer cannot have circular references + structuredCloneInternal(value.buffer, map), + value.byteOffset, + type === 'DataView' ? value.byteLength : value.length ); break; - case 'DOMRect': - case 'DOMRectReadOnly': - cloned = global[type].fromRect - ? global[type].fromRect(value) - : new global[type](value.x, value.y, value.width, value.height); - break; - case 'DOMMatrix': - case 'DOMMatrixReadOnly': - cloned = global[type].fromMatrix - ? global[type].fromMatrix(value) - : new global[type](value); - break; - case 'AudioData': - case 'VideoFrame': - cloned = value.clone(); - break; - case 'File': - cloned = new File([value], value.name, value); + case 'DOMQuad': + C = global.DOMQuad; + if (isConstructor(C)) { + cloned = new C( + structuredCloneInternal(value.p1, map), + structuredCloneInternal(value.p2, map), + structuredCloneInternal(value.p3, map), + structuredCloneInternal(value.p4, map) + ); + } else if (nativeRestrictedStructuredClone) { + cloned = nativeRestrictedStructuredClone(value); + } else throwUncloneableType(type); break; case 'FileList': - dataTransfer = new DataTransfer(); - for (i = 0, length = value.length; i < length; i++) { - dataTransfer.items.add(structuredCloneInternal(value[i], map)); - } - cloned = dataTransfer.files; + C = global.DataTransfer; + if (isConstructor(C)) { + dataTransfer = new C(); + for (i = 0, length = value.length; i < length; i++) { + dataTransfer.items.add(structuredCloneInternal(value[i], map)); + } + cloned = dataTransfer.files; + } else if (nativeRestrictedStructuredClone) { + cloned = nativeRestrictedStructuredClone(value); + } else throwUncloneableType(type); break; case 'ImageData': - cloned = new ImageData( - structuredCloneInternal(value.data, map), - value.width, - value.height, - { colorSpace: value.colorSpace } - ); + C = global.ImageData; + if (isConstructor(C)) { + cloned = new C( + structuredCloneInternal(value.data, map), + value.width, + value.height, + { colorSpace: value.colorSpace } + ); + } else if (nativeRestrictedStructuredClone) { + cloned = nativeRestrictedStructuredClone(value); + } else throwUncloneableType(type); break; default: - if (nativeRestrictedStructuredClone) cloned = nativeRestrictedStructuredClone(value); - else throw new DOMException('Uncloneable type: ' + type, DATA_CLONE_ERROR); + if (nativeRestrictedStructuredClone) { + cloned = nativeRestrictedStructuredClone(value); + } else switch (type) { + case 'BigInt': + // can be a 3rd party polyfill + cloned = Object(value.valueOf()); + break; + case 'Boolean': + cloned = Object(bolleanValueOf(value)); + break; + case 'Number': + cloned = Object(numberValueOf(value)); + break; + case 'String': + cloned = Object(stringValueOf(value)); + break; + case 'Date': + cloned = new Date(getTime(value)); + break; + case 'RegExp': + cloned = new RegExp(value); + break; + case 'ArrayBuffer': + // detached buffers throws on `.slice` + try { + cloned = value.slice(0); + } catch (error) { + throw new DOMException('ArrayBuffer is deatched', DATA_CLONE_ERROR); + } break; + case 'SharedArrayBuffer': + // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original + cloned = value; + break; + case 'Blob': + try { + cloned = value.slice(0, value.size, value.type); + } catch (error) { + throwUncloneableType(type); + } break; + case 'DOMPoint': + case 'DOMPointReadOnly': + C = global[type]; + try { + cloned = C.fromPoint ? C.fromPoint(value) : new C(value.x, value.y, value.z, value.w); + } catch (error) { + throwUncloneableType(type); + } break; + case 'DOMRect': + case 'DOMRectReadOnly': + C = global[type]; + try { + cloned = C.fromRect ? C.fromRect(value) : new C(value.x, value.y, value.width, value.height); + } catch (error) { + throwUncloneableType(type); + } break; + case 'DOMMatrix': + case 'DOMMatrixReadOnly': + C = global[type]; + try { + cloned = C.fromMatrix ? C.fromMatrix(value) : new C(value); + } catch (error) { + throwUncloneableType(type); + } break; + case 'AudioData': + case 'VideoFrame': + try { + cloned = value.clone(); + } catch (error) { + throwUncloneableType(type); + } break; + case 'File': + try { + cloned = new File([value], value.name, value); + } catch (error) { + throwUncloneableType(type); + } break; + default: + throwUncloneableType(type); + } } mapSet(map, value, cloned); if (deep) switch (type) { + case 'Array': + case 'Object': + for (key in value) if (hasOwn(value, key)) { + cloned[key] = structuredCloneInternal(value[key], map); + } break; case 'Map': value.forEach(function (v, k) { mapSet(cloned, structuredCloneInternal(k, map), structuredCloneInternal(v, map)); @@ -252,26 +298,21 @@ var structuredCloneInternal = function (value, map) { break; case 'Error': case 'DOMException': - if (ERROR_STACK_INSTALLABLE) createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map)); - break; - case 'Array': - case 'Object': - for (key in value) if (hasOwn(value, key)) { - cloned[key] = structuredCloneInternal(value[key], map); - } - break; + if (ERROR_STACK_INSTALLABLE) { + createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map)); + } break; } return cloned; }; -var FORCED = IS_PURE || fails(function () { +var FORCED_REPLACEMENT = IS_PURE || fails(function () { // current FF implementation can't clone errors // https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 return nativeStructuredClone(Error('a')).message !== 'a'; }); -$({ global: true, enumerable: true, sham: true, forced: FORCED }, { +$({ global: true, enumerable: true, sham: true, forced: FORCED_REPLACEMENT }, { structuredClone: function structuredClone(value /* , { transfer } */) { var options = arguments.length > 1 ? anObject(arguments[1]) : undefined; var transfer = options && options.transfer; From 166cc3f0bf817b0d22097b376f7d43c3db8c5d42 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 4 Nov 2021 20:34:43 +0700 Subject: [PATCH 45/73] clone`Error#stack` it's not defined in the spec, but seems it should work similarly to `.stack` and it works in Chrome --- .../core-js/modules/web.structured-clone.js | 9 +++-- tests/pure/web.structured-clone.js | 34 ++++++++++--------- tests/tests/web.structured-clone.js | 34 ++++++++++--------- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 3043b21a1f49..cfb1a0d5e513 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -131,11 +131,11 @@ var structuredCloneInternal = function (value, map) { C = Error; } cloned = C(value.message); - deep = ERROR_STACK_INSTALLABLE; + deep = true; break; case 'DOMException': cloned = new DOMException(value.message, value.name); - deep = ERROR_STACK_INSTALLABLE; + deep = true; break; case 'DataView': case 'Int8Array': @@ -297,10 +297,13 @@ var structuredCloneInternal = function (value, map) { }); break; case 'Error': + if (hasOwn(value, 'cause')) { + createNonEnumerableProperty(cloned, 'cause', structuredCloneInternal(value.cause, map)); + } // fallthrough case 'DOMException': if (ERROR_STACK_INSTALLABLE) { createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map)); - } break; + } } return cloned; diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index f7903443f637..e5f3ff1deb76 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -184,39 +184,41 @@ QUnit.module('structuredClone', () => { QUnit.test('Error', assert => { const errors = [ new Error(), - new Error('abc', 'def'), + new Error('abc', 'def', { cause: 42 }), new EvalError(), - new EvalError('ghi', 'jkl'), + new EvalError('ghi', 'jkl', { cause: 42 }), new RangeError(), - new RangeError('ghi', 'jkl'), + new RangeError('ghi', 'jkl', { cause: 42 }), new ReferenceError(), - new ReferenceError('ghi', 'jkl'), + new ReferenceError('ghi', 'jkl', { cause: 42 }), new SyntaxError(), - new SyntaxError('ghi', 'jkl'), + new SyntaxError('ghi', 'jkl', { cause: 42 }), new TypeError(), - new TypeError('ghi', 'jkl'), + new TypeError('ghi', 'jkl', { cause: 42 }), new URIError(), - new URIError('ghi', 'jkl'), + new URIError('ghi', 'jkl', { cause: 42 }), ]; for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - assert.equal(orig.constructor, clone.constructor, `constructor ${ orig.name }`); - assert.equal(orig.name, clone.name, `name ${ orig.name }`); - assert.equal(orig.message, clone.message, `message ${ orig.name }`); - assert.equal(orig.stack, clone.stack, `stack ${ orig.name }`); + assert.equal(orig.constructor, clone.constructor, `${ orig.name }#constructor`); + assert.equal(orig.name, clone.name, `${ orig.name }#name`); + assert.equal(orig.message, clone.message, `${ orig.name }#message`); + assert.equal(orig.stack, clone.stack, `${ orig.name }#stack`); + assert.equal(orig.cause, clone.cause, `${ orig.name }#cause`); }); const aggregates = [ new AggregateError([1, 2]), - new AggregateError([1, 2], 42), + new AggregateError([1, 2], 42, { cause: 42 }), ]; for (const error of aggregates) { const clone = structuredClone(error); - assert.equal(Error, clone.constructor); - assert.equal('Error', clone.name); - assert.equal(error.message, clone.message); - assert.equal(error.stack, clone.stack); + assert.equal(Error, clone.constructor, 'AggregateError#constructor'); + assert.equal('Error', clone.name, 'AggregateError#name'); + assert.equal(error.message, clone.message, 'AggregateError#message'); + assert.equal(error.stack, clone.stack, 'AggregateError#stack'); + assert.equal(error.cause, clone.cause, 'AggregateError#cause'); } }); diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index f45d62589711..e2d0bdbf8071 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -177,39 +177,41 @@ QUnit.module('structuredClone', () => { QUnit.test('Error', assert => { const errors = [ new Error(), - new Error('abc', 'def'), + new Error('abc', 'def', { cause: 42 }), new EvalError(), - new EvalError('ghi', 'jkl'), + new EvalError('ghi', 'jkl', { cause: 42 }), new RangeError(), - new RangeError('ghi', 'jkl'), + new RangeError('ghi', 'jkl', { cause: 42 }), new ReferenceError(), - new ReferenceError('ghi', 'jkl'), + new ReferenceError('ghi', 'jkl', { cause: 42 }), new SyntaxError(), - new SyntaxError('ghi', 'jkl'), + new SyntaxError('ghi', 'jkl', { cause: 42 }), new TypeError(), - new TypeError('ghi', 'jkl'), + new TypeError('ghi', 'jkl', { cause: 42 }), new URIError(), - new URIError('ghi', 'jkl'), + new URIError('ghi', 'jkl', { cause: 42 }), ]; for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - assert.equal(orig.constructor, clone.constructor, `constructor ${ orig.name }`); - assert.equal(orig.name, clone.name, `name ${ orig.name }`); - assert.equal(orig.message, clone.message, `message ${ orig.name }`); - assert.equal(orig.stack, clone.stack, `stack ${ orig.name }`); + assert.equal(orig.constructor, clone.constructor, `${ orig.name }#constructor`); + assert.equal(orig.name, clone.name, `${ orig.name }#name`); + assert.equal(orig.message, clone.message, `${ orig.name }#message`); + assert.equal(orig.stack, clone.stack, `${ orig.name }#stack`); + assert.equal(orig.cause, clone.cause, `${ orig.name }#cause`); }); const aggregates = [ new AggregateError([1, 2]), - new AggregateError([1, 2], 42), + new AggregateError([1, 2], 42, { cause: 42 }), ]; for (const error of aggregates) { const clone = structuredClone(error); - assert.equal(Error, clone.constructor); - assert.equal('Error', clone.name); - assert.equal(error.message, clone.message); - assert.equal(error.stack, clone.stack); + assert.equal(Error, clone.constructor, 'AggregateError#constructor'); + assert.equal('Error', clone.name, 'AggregateError#name'); + assert.equal(error.message, clone.message, 'AggregateError#message'); + assert.equal(error.stack, clone.stack, 'AggregateError#stack'); + assert.equal(error.cause, clone.cause, 'AggregateError#cause'); } }); From 0a84f7750d7b73c973c59f9b1c4747875c5861f7 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 4 Nov 2021 23:04:17 +0700 Subject: [PATCH 46/73] drop extra `case` --- packages/core-js/modules/web.structured-clone.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index cfb1a0d5e513..841b3d16aa24 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -106,9 +106,6 @@ var structuredCloneInternal = function (value, map) { break; case 'Error': switch (value.name) { - case 'Error': - C = Error; - break; case 'EvalError': C = EvalError; break; From f64a56243b5667e69004e8255a5c8754f24abf97 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 5 Nov 2021 02:08:37 +0700 Subject: [PATCH 47/73] implement new errors cloning semantic --- packages/core-js-compat/src/data.mjs | 5 +- .../core-js/modules/web.structured-clone.js | 88 ++++++++++++------- tests/pure/web.structured-clone.js | 36 ++++---- tests/tests/web.structured-clone.js | 36 ++++---- 4 files changed, 95 insertions(+), 70 deletions(-) diff --git a/packages/core-js-compat/src/data.mjs b/packages/core-js-compat/src/data.mjs index f27eec7a4fee..691168964a25 100644 --- a/packages/core-js-compat/src/data.mjs +++ b/packages/core-js-compat/src/data.mjs @@ -1851,10 +1851,11 @@ export const data = { safari: '12.1', }, 'web.structured-clone': { - deno: '1.14', + // https://github.com/whatwg/html/pull/5749 + // deno: '1.14', // current FF implementation can't clone errors // firefox: '94', - node: '17.0', + // node: '17.0', }, 'web.timers': { android: '1.5', diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 841b3d16aa24..21ac57f74364 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -25,6 +25,10 @@ var SyntaxError = global.SyntaxError; var TypeError = global.TypeError; var URIError = global.URIError; var PerformanceMark = global.PerformanceMark; +var WebAssembly = global.WebAssembly; +var CompileError = WebAssembly && WebAssembly.CompileError || Error; +var LinkError = WebAssembly && WebAssembly.LinkError || Error; +var RuntimeError = WebAssembly && WebAssembly.RuntimeError || Error; var Set = getBuiltin('Set'); var Map = getBuiltin('Map'); var MapPrototype = Map.prototype; @@ -48,27 +52,36 @@ var DOMException = function (message, name) { } }; -// Chrome 78+, Safari 14.1+ -var structuredCloneFromMark = (function (structuredCloneFromMarkImpl) { +var checkBasicSemantic = function (structuredCloneImplementation) { + return !fails(function () { + var set = new global.Set([7]); + var cloned = structuredCloneImplementation(set); + return cloned === set || !cloned.has(7); + }); +}; + +// https://github.com/whatwg/html/pull/5749 +var checkNewErrorsSemantic = function (structuredCloneImplementation) { return !fails(function () { - var set = new global.Set([42]); - var cloned = structuredCloneFromMarkImpl(set); - return cloned === set || !cloned.has(42); - }) && structuredCloneFromMarkImpl; + var test = structuredCloneImplementation(new global.AggregateError([1], PERFORMANCE_MARK, { cause: 3 })); + return test.errors[0] !== 1 || test.message !== PERFORMANCE_MARK || test.cause !== 3; + }); +}; + +// Chrome 78+, Safari 14.1+ +var structuredCloneFromMark = (function (structuredCloneFromMarkImplementation) { + return checkBasicSemantic(structuredCloneFromMarkImplementation) && structuredCloneFromMarkImplementation; })(function (value) { return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail; }); -// + FF94+ -var nativeRestrictedStructuredClone = nativeStructuredClone || structuredCloneFromMark; +// + FF94+, Node 17.0+, Deno 1.14+ +var nativeRestrictedStructuredClone = checkBasicSemantic(nativeStructuredClone) ? nativeStructuredClone : structuredCloneFromMark; -var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !fails(function () { - // Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` - var test = structuredCloneFromMark(new DOMException(PERFORMANCE_MARK, DATA_CLONE_ERROR)); - return test.name !== DATA_CLONE_ERROR || test.message !== PERFORMANCE_MARK - // current Safari implementation can't clone errors - || structuredCloneFromMark(Error(PERFORMANCE_MARK)).message !== PERFORMANCE_MARK; -}); +// Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` +// current Safari implementation can't clone errors +// no one of current implementations supports new (html/5749) error cloning semantic +var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && checkNewErrorsSemantic(structuredCloneFromMark); var throwUncloneableType = function (type) { throw new DOMException('Uncloneable type: ' + type, DATA_CLONE_ERROR); @@ -85,7 +98,7 @@ var structuredCloneInternal = function (value, map) { var type = classof(value); var deep = false; - var C, cloned, dataTransfer, i, length, key; + var C, name, cloned, dataTransfer, i, length, key; switch (type) { case 'Array': @@ -105,29 +118,41 @@ var structuredCloneInternal = function (value, map) { deep = true; break; case 'Error': - switch (value.name) { + name = value.name; + switch (name) { + case 'AggregateError': + cloned = getBuiltin('AggregateError')([]); + break; case 'EvalError': - C = EvalError; + cloned = EvalError(); break; case 'RangeError': - C = RangeError; + cloned = RangeError(); break; case 'ReferenceError': - C = ReferenceError; + cloned = ReferenceError(); break; case 'SyntaxError': - C = SyntaxError; + cloned = SyntaxError(); break; case 'TypeError': - C = TypeError; + cloned = TypeError(); break; case 'URIError': - C = URIError; + cloned = URIError(); + break; + case 'CompileError': + cloned = CompileError(); + break; + case 'LinkError': + cloned = LinkError(); + break; + case 'RuntimeError': + cloned = RuntimeError(); break; default: - C = Error; + cloned = Error(); } - cloned = C(value.message); deep = true; break; case 'DOMException': @@ -294,8 +319,12 @@ var structuredCloneInternal = function (value, map) { }); break; case 'Error': + createNonEnumerableProperty(cloned, 'message', structuredCloneInternal(value.message, map)); if (hasOwn(value, 'cause')) { createNonEnumerableProperty(cloned, 'cause', structuredCloneInternal(value.cause, map)); + } + if (name == 'AggregateError') { + cloned.errors = structuredCloneInternal(value.errors, map); } // fallthrough case 'DOMException': if (ERROR_STACK_INSTALLABLE) { @@ -306,11 +335,10 @@ var structuredCloneInternal = function (value, map) { return cloned; }; -var FORCED_REPLACEMENT = IS_PURE || fails(function () { - // current FF implementation can't clone errors - // https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 - return nativeStructuredClone(Error('a')).message !== 'a'; -}); +// current FF implementation can't clone errors +// https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 +// no one of current implementations supports new (html/5749) error cloning semantic +var FORCED_REPLACEMENT = IS_PURE || !checkNewErrorsSemantic(nativeStructuredClone); $({ global: true, enumerable: true, sham: true, forced: FORCED_REPLACEMENT }, { structuredClone: function structuredClone(value /* , { transfer } */) { diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index e5f3ff1deb76..a0d0ee59d58d 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -197,29 +197,27 @@ QUnit.module('structuredClone', () => { new TypeError('ghi', 'jkl', { cause: 42 }), new URIError(), new URIError('ghi', 'jkl', { cause: 42 }), - ]; - - for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - assert.equal(orig.constructor, clone.constructor, `${ orig.name }#constructor`); - assert.equal(orig.name, clone.name, `${ orig.name }#name`); - assert.equal(orig.message, clone.message, `${ orig.name }#message`); - assert.equal(orig.stack, clone.stack, `${ orig.name }#stack`); - assert.equal(orig.cause, clone.cause, `${ orig.name }#cause`); - }); - - const aggregates = [ new AggregateError([1, 2]), new AggregateError([1, 2], 42, { cause: 42 }), ]; - for (const error of aggregates) { - const clone = structuredClone(error); - assert.equal(Error, clone.constructor, 'AggregateError#constructor'); - assert.equal('Error', clone.name, 'AggregateError#name'); - assert.equal(error.message, clone.message, 'AggregateError#message'); - assert.equal(error.stack, clone.stack, 'AggregateError#stack'); - assert.equal(error.cause, clone.cause, 'AggregateError#cause'); - } + const compile = fromSource('WebAssembly.CompileError()'); + const link = fromSource('WebAssembly.LinkError()'); + const runtime = fromSource('WebAssembly.RuntimeError()'); + + if (compile) errors.push(compile); + if (link) errors.push(link); + if (runtime) errors.push(runtime); + + for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { + const { name } = orig; + assert.equal(orig.constructor, clone.constructor, `${ name }#constructor`); + assert.equal(orig.name, clone.name, `${ name }#name`); + assert.equal(orig.message, clone.message, `${ name }#message`); + assert.equal(orig.stack, clone.stack, `${ name }#stack`); + assert.equal(orig.cause, clone.cause, `${ name }#cause`); + assert.deepEqual(orig.errors, clone.errors, `${ name }#errors`); + }); }); // Arrays diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index e2d0bdbf8071..17d66dc6bb88 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -190,29 +190,27 @@ QUnit.module('structuredClone', () => { new TypeError('ghi', 'jkl', { cause: 42 }), new URIError(), new URIError('ghi', 'jkl', { cause: 42 }), - ]; - - for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - assert.equal(orig.constructor, clone.constructor, `${ orig.name }#constructor`); - assert.equal(orig.name, clone.name, `${ orig.name }#name`); - assert.equal(orig.message, clone.message, `${ orig.name }#message`); - assert.equal(orig.stack, clone.stack, `${ orig.name }#stack`); - assert.equal(orig.cause, clone.cause, `${ orig.name }#cause`); - }); - - const aggregates = [ new AggregateError([1, 2]), new AggregateError([1, 2], 42, { cause: 42 }), ]; - for (const error of aggregates) { - const clone = structuredClone(error); - assert.equal(Error, clone.constructor, 'AggregateError#constructor'); - assert.equal('Error', clone.name, 'AggregateError#name'); - assert.equal(error.message, clone.message, 'AggregateError#message'); - assert.equal(error.stack, clone.stack, 'AggregateError#stack'); - assert.equal(error.cause, clone.cause, 'AggregateError#cause'); - } + const compile = fromSource('WebAssembly.CompileError()'); + const link = fromSource('WebAssembly.LinkError()'); + const runtime = fromSource('WebAssembly.RuntimeError()'); + + if (compile) errors.push(compile); + if (link) errors.push(link); + if (runtime) errors.push(runtime); + + for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { + const { name } = orig; + assert.equal(orig.constructor, clone.constructor, `${ name }#constructor`); + assert.equal(orig.name, clone.name, `${ name }#name`); + assert.equal(orig.message, clone.message, `${ name }#message`); + assert.equal(orig.stack, clone.stack, `${ name }#stack`); + assert.equal(orig.cause, clone.cause, `${ name }#cause`); + assert.deepEqual(orig.errors, clone.errors, `${ name }#errors`); + }); }); // Arrays From 904ede61890d39a91517de887c3a0f16bc063ad3 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 5 Nov 2021 03:49:00 +0700 Subject: [PATCH 48/73] update compat test --- tests/compat/tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/compat/tests.js b/tests/compat/tests.js index 479eae24579d..029be6b429e4 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1672,7 +1672,8 @@ GLOBAL.tests = { return Object.getOwnPropertyDescriptor(GLOBAL, 'queueMicrotask').value; }, 'web.structured-clone': function () { - return structuredClone(Error('a')).message === 'a'; + var test = structuredClone(new AggregateError([1], 'a', { cause: 3 })); + return test.errors[0] === 1 && test.message === 'a' && test.cause === 3; }, 'web.timers': function () { return !/MSIE .\./.test(USERAGENT); From 7beae9584c506e41a9d9136ff372a92a704d29c5 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Fri, 5 Nov 2021 23:27:02 +0700 Subject: [PATCH 49/73] improve reusage of native implementations --- .../core-js/modules/web.structured-clone.js | 30 +++++++++++++------ tests/compat/tests.js | 2 +- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 21ac57f74364..d7c4fcc96c4b 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -2,6 +2,7 @@ var IS_PURE = require('../internals/is-pure'); var $ = require('../internals/export'); var global = require('../internals/global'); var getBuiltin = require('../internals/get-built-in'); +var call = require('../internals/function-call'); var uncurryThis = require('../internals/function-uncurry-this'); var fails = require('../internals/fails'); var uid = require('../internals/uid'); @@ -14,7 +15,6 @@ var hasOwn = require('../internals/has-own-property'); var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); -var nativeStructuredClone = global.structuredClone; var Object = global.Object; var Date = global.Date; var Error = global.Error; @@ -25,6 +25,9 @@ var SyntaxError = global.SyntaxError; var TypeError = global.TypeError; var URIError = global.URIError; var PerformanceMark = global.PerformanceMark; +var performance = global.performance; +var mark = performance && performance.mark; +var clearMarks = performance && performance.clearMarks; var WebAssembly = global.WebAssembly; var CompileError = WebAssembly && WebAssembly.CompileError || Error; var LinkError = WebAssembly && WebAssembly.LinkError || Error; @@ -54,9 +57,10 @@ var DOMException = function (message, name) { var checkBasicSemantic = function (structuredCloneImplementation) { return !fails(function () { - var set = new global.Set([7]); - var cloned = structuredCloneImplementation(set); - return cloned === set || !cloned.has(7); + var set1 = new global.Set([7]); + var set2 = structuredCloneImplementation(set1); + var number = structuredCloneImplementation(Object(7)); + return set2 == set1 || !set2.has(7) || typeof number != 'object' || number != 7; }); }; @@ -64,18 +68,26 @@ var checkBasicSemantic = function (structuredCloneImplementation) { var checkNewErrorsSemantic = function (structuredCloneImplementation) { return !fails(function () { var test = structuredCloneImplementation(new global.AggregateError([1], PERFORMANCE_MARK, { cause: 3 })); - return test.errors[0] !== 1 || test.message !== PERFORMANCE_MARK || test.cause !== 3; + return test.name != 'AggregateError' || test.errors[0] != 1 || test.message != PERFORMANCE_MARK || test.cause != 3; }); }; -// Chrome 78+, Safari 14.1+ -var structuredCloneFromMark = (function (structuredCloneFromMarkImplementation) { - return checkBasicSemantic(structuredCloneFromMarkImplementation) && structuredCloneFromMarkImplementation; +// FF94+, NodeJS 17.0+, Deno 1.14+ +var nativeStructuredClone = global.structuredClone; + +// Chrome 78+, Safari 14.1+, NodeJS 16.0+, Deno 1.11+ (old Deno implementations too naive) +var structuredCloneFromMark = (function (structuredCloneFromMarkConstructorImplementation, structuredCloneFromMarkImplementation) { + return checkBasicSemantic(structuredCloneFromMarkConstructorImplementation) ? structuredCloneFromMarkConstructorImplementation + : checkBasicSemantic(structuredCloneFromMarkImplementation) && structuredCloneFromMarkImplementation; })(function (value) { return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail; +}, function (value) { + // NodeJS haven't `PerformanceMark` constructor + var result = call(mark, performance, PERFORMANCE_MARK, { detail: value }); + call(clearMarks, performance, PERFORMANCE_MARK); + return result.detail; }); -// + FF94+, Node 17.0+, Deno 1.14+ var nativeRestrictedStructuredClone = checkBasicSemantic(nativeStructuredClone) ? nativeStructuredClone : structuredCloneFromMark; // Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` diff --git a/tests/compat/tests.js b/tests/compat/tests.js index 029be6b429e4..c021dbba6291 100644 --- a/tests/compat/tests.js +++ b/tests/compat/tests.js @@ -1673,7 +1673,7 @@ GLOBAL.tests = { }, 'web.structured-clone': function () { var test = structuredClone(new AggregateError([1], 'a', { cause: 3 })); - return test.errors[0] === 1 && test.message === 'a' && test.cause === 3; + return test.name == 'AggregateError' && test.errors[0] == 1 && test.message == 'a' && test.cause == 3; }, 'web.timers': function () { return !/MSIE .\./.test(USERAGENT); From 5d170d8012deb8ba5a75836ead27f2709880ee7a Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sat, 6 Nov 2021 14:59:20 +0700 Subject: [PATCH 50/73] improve `transfer` handling --- .../core-js/modules/web.structured-clone.js | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index d7c4fcc96c4b..ff5e3524a18d 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -6,6 +6,7 @@ var call = require('../internals/function-call'); var uncurryThis = require('../internals/function-uncurry-this'); var fails = require('../internals/fails'); var uid = require('../internals/uid'); +var isArray = require('../internals/is-array'); var isConstructor = require('../internals/is-constructor'); var isObject = require('../internals/is-object'); var isSymbol = require('../internals/is-symbol'); @@ -352,16 +353,27 @@ var structuredCloneInternal = function (value, map) { // no one of current implementations supports new (html/5749) error cloning semantic var FORCED_REPLACEMENT = IS_PURE || !checkNewErrorsSemantic(nativeStructuredClone); +var PROPER_TRANSFER = nativeStructuredClone && !fails(function () { + var buffer = new global.ArrayBuffer(8); + var clone = nativeStructuredClone(buffer, { transfer: [buffer] }); + return buffer.byteLength != 0 || clone.byteLength != 8; +}); + $({ global: true, enumerable: true, sham: true, forced: FORCED_REPLACEMENT }, { structuredClone: function structuredClone(value /* , { transfer } */) { var options = arguments.length > 1 ? anObject(arguments[1]) : undefined; - var transfer = options && options.transfer; + var transfer = options ? options.transfer : undefined; + var map, transfered, i; if (transfer !== undefined) { - if (!IS_PURE && nativeStructuredClone) return nativeStructuredClone(value, options); - throw TypeError('Transfer option is not supported'); + if (!PROPER_TRANSFER) throw TypeError('Transfer option is not supported'); + if (!isArray(transfer)) throw TypeError('Transfer option should be an array'); + map = new Map(); + transfered = nativeStructuredClone(transfer, { transfer: transfer }); + i = transfered.length; + while (i--) mapSet(map, transfer[i], transfered[i]); } - return structuredCloneInternal(value); + return structuredCloneInternal(value, map); } }); From a14201d498ef8f14c109546b07af32aa128ab96f Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sat, 6 Nov 2021 15:38:30 +0700 Subject: [PATCH 51/73] handle a case with existent native structuredClone, structuredCloneFromMark and transfer --- packages/core-js/modules/web.structured-clone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index ff5e3524a18d..116b29f697cb 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -94,7 +94,7 @@ var nativeRestrictedStructuredClone = checkBasicSemantic(nativeStructuredClone) // Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` // current Safari implementation can't clone errors // no one of current implementations supports new (html/5749) error cloning semantic -var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && checkNewErrorsSemantic(structuredCloneFromMark); +var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !nativeStructuredClone && checkNewErrorsSemantic(structuredCloneFromMark); var throwUncloneableType = function (type) { throw new DOMException('Uncloneable type: ' + type, DATA_CLONE_ERROR); From b925e5a2832eec7b938ee270d27ccc7ae0908b8d Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sat, 6 Nov 2021 15:52:56 +0700 Subject: [PATCH 52/73] avoid extra feature detection --- packages/core-js/modules/web.structured-clone.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 116b29f697cb..a0e1b472806f 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -77,9 +77,9 @@ var checkNewErrorsSemantic = function (structuredCloneImplementation) { var nativeStructuredClone = global.structuredClone; // Chrome 78+, Safari 14.1+, NodeJS 16.0+, Deno 1.11+ (old Deno implementations too naive) -var structuredCloneFromMark = (function (structuredCloneFromMarkConstructorImplementation, structuredCloneFromMarkImplementation) { - return checkBasicSemantic(structuredCloneFromMarkConstructorImplementation) ? structuredCloneFromMarkConstructorImplementation - : checkBasicSemantic(structuredCloneFromMarkImplementation) && structuredCloneFromMarkImplementation; +var structuredCloneFromMark = !nativeStructuredClone && (function (fromMarkConstructor, fromMark) { + return checkBasicSemantic(fromMarkConstructor) ? fromMarkConstructor + : checkBasicSemantic(fromMark) && fromMark; })(function (value) { return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail; }, function (value) { From 017a3741051a57b9fc49370a77f0d486e1309194 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sat, 6 Nov 2021 16:50:25 +0700 Subject: [PATCH 53/73] fix `.sham` flag --- packages/core-js/modules/web.structured-clone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index a0e1b472806f..948a9f21b246 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -359,7 +359,7 @@ var PROPER_TRANSFER = nativeStructuredClone && !fails(function () { return buffer.byteLength != 0 || clone.byteLength != 8; }); -$({ global: true, enumerable: true, sham: true, forced: FORCED_REPLACEMENT }, { +$({ global: true, enumerable: true, sham: !PROPER_TRANSFER, forced: FORCED_REPLACEMENT }, { structuredClone: function structuredClone(value /* , { transfer } */) { var options = arguments.length > 1 ? anObject(arguments[1]) : undefined; var transfer = options ? options.transfer : undefined; From 047e8ac4362bfe2893ed895a7b284b003d00a5c5 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sat, 6 Nov 2021 18:22:24 +0700 Subject: [PATCH 54/73] polyfill transferring of some platform objects --- .../core-js/modules/web.structured-clone.js | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 948a9f21b246..b2434751aecc 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -14,6 +14,7 @@ var anObject = require('../internals/an-object'); var classof = require('../internals/classof'); var hasOwn = require('../internals/has-own-property'); var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); +var lengthOfArrayLike = require('../internals/length-of-array-like'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); var Object = global.Object; @@ -210,7 +211,7 @@ var structuredCloneInternal = function (value, map) { C = global.DataTransfer; if (isConstructor(C)) { dataTransfer = new C(); - for (i = 0, length = value.length; i < length; i++) { + for (i = 0, length = lengthOfArrayLike(value); i < length; i++) { dataTransfer.items.add(structuredCloneInternal(value[i], map)); } cloned = dataTransfer.files; @@ -348,6 +349,32 @@ var structuredCloneInternal = function (value, map) { return cloned; }; +var tryToTransfer = function (array, length, map) { + var i = 0; + var value, type, transferred, canvas, context; + for (;i < length; i++) { + value = array[i]; + if (mapHas(map, value)) throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR); + type = classof(value); + try { + switch (type) { + case 'ImageBitmap': + canvas = new OffscreenCanvas(value.width, value.height); + context = canvas.getContext('bitmaprenderer'); + context.transferFromImageBitmap(value); + transferred = canvas.transferToImageBitmap(); + break; + case 'AudioData': + case 'VideoFrame': + transferred = value.clone(); + value.close(); + } + } catch (error) { /* empty */ } + if (transferred === undefined) throw new DOMException('This object cannot be transferred: ' + type, DATA_CLONE_ERROR); + mapSet(map, value, transferred); + } +}; + // current FF implementation can't clone errors // https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 // no one of current implementations supports new (html/5749) error cloning semantic @@ -363,15 +390,16 @@ $({ global: true, enumerable: true, sham: !PROPER_TRANSFER, forced: FORCED_REPLA structuredClone: function structuredClone(value /* , { transfer } */) { var options = arguments.length > 1 ? anObject(arguments[1]) : undefined; var transfer = options ? options.transfer : undefined; - var map, transfered, i; + var map, transferred, length; if (transfer !== undefined) { - if (!PROPER_TRANSFER) throw TypeError('Transfer option is not supported'); if (!isArray(transfer)) throw TypeError('Transfer option should be an array'); + length = lengthOfArrayLike(transfer); map = new Map(); - transfered = nativeStructuredClone(transfer, { transfer: transfer }); - i = transfered.length; - while (i--) mapSet(map, transfer[i], transfered[i]); + if (PROPER_TRANSFER) { + transferred = nativeStructuredClone(transfer, { transfer: transfer }); + while (length--) mapSet(map, transfer[length], transferred[length]); + } else tryToTransfer(transfer, length, map); } return structuredCloneInternal(value, map); From 00f0d0c156573cff84a0690ed8acd3f7c8936a3f Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sat, 6 Nov 2021 18:38:52 +0700 Subject: [PATCH 55/73] some stylistic changes --- .../core-js/modules/web.structured-clone.js | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index b2434751aecc..ab9374f4643c 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -349,11 +349,24 @@ var structuredCloneInternal = function (value, map) { return cloned; }; -var tryToTransfer = function (array, length, map) { +var PROPER_TRANSFER = nativeStructuredClone && !fails(function () { + var buffer = new global.ArrayBuffer(8); + var clone = nativeStructuredClone(buffer, { transfer: [buffer] }); + return buffer.byteLength != 0 || clone.byteLength != 8; +}); + +var tryToTransfer = function (transfer, map) { + if (!isArray(transfer)) throw TypeError('Transfer option should be an array'); + var i = 0; - var value, type, transferred, canvas, context; - for (;i < length; i++) { - value = array[i]; + var length = lengthOfArrayLike(transfer); + var value, type, transferredArray, transferred, canvas, context; + + if (PROPER_TRANSFER) { + transferredArray = nativeStructuredClone(transfer, { transfer: transfer }); + while (i < length) mapSet(map, transfer[i], transferredArray[i++]); + } else while (i < length) { + value = transfer[i++]; if (mapHas(map, value)) throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR); type = classof(value); try { @@ -378,28 +391,17 @@ var tryToTransfer = function (array, length, map) { // current FF implementation can't clone errors // https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 // no one of current implementations supports new (html/5749) error cloning semantic -var FORCED_REPLACEMENT = IS_PURE || !checkNewErrorsSemantic(nativeStructuredClone); - -var PROPER_TRANSFER = nativeStructuredClone && !fails(function () { - var buffer = new global.ArrayBuffer(8); - var clone = nativeStructuredClone(buffer, { transfer: [buffer] }); - return buffer.byteLength != 0 || clone.byteLength != 8; -}); +var FORCED_REPLACEMENT = IS_PURE || !PROPER_TRANSFER || !checkNewErrorsSemantic(nativeStructuredClone); $({ global: true, enumerable: true, sham: !PROPER_TRANSFER, forced: FORCED_REPLACEMENT }, { structuredClone: function structuredClone(value /* , { transfer } */) { var options = arguments.length > 1 ? anObject(arguments[1]) : undefined; var transfer = options ? options.transfer : undefined; - var map, transferred, length; + var map; if (transfer !== undefined) { - if (!isArray(transfer)) throw TypeError('Transfer option should be an array'); - length = lengthOfArrayLike(transfer); map = new Map(); - if (PROPER_TRANSFER) { - transferred = nativeStructuredClone(transfer, { transfer: transfer }); - while (length--) mapSet(map, transfer[length], transferred[length]); - } else tryToTransfer(transfer, length, map); + tryToTransfer(transfer, map); } return structuredCloneInternal(value, map); From 4c3269568ed16c2c26df3e67ec622f9af8cc09e8 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 7 Nov 2021 00:26:02 +0700 Subject: [PATCH 56/73] use `CreateDataProperty` --- packages/core-js/modules/web.structured-clone.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index ab9374f4643c..1a49e19d963f 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -13,6 +13,7 @@ var isSymbol = require('../internals/is-symbol'); var anObject = require('../internals/an-object'); var classof = require('../internals/classof'); var hasOwn = require('../internals/has-own-property'); +var createProperty = require('../internals/create-property'); var createNonEnumerableProperty = require('../internals/create-non-enumerable-property'); var lengthOfArrayLike = require('../internals/length-of-array-like'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); @@ -320,7 +321,7 @@ var structuredCloneInternal = function (value, map) { case 'Array': case 'Object': for (key in value) if (hasOwn(value, key)) { - cloned[key] = structuredCloneInternal(value[key], map); + createProperty(cloned, key, structuredCloneInternal(value[key], map)); } break; case 'Map': value.forEach(function (v, k) { From 1fdfd148c5aa39dc47368875b8563a0df4db4c0c Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 7 Nov 2021 00:48:38 +0700 Subject: [PATCH 57/73] throw 'Cloning of %TYPE% cannot be properly polyfilled' where it's useful --- .eslintrc.js | 2 +- .../core-js/modules/web.structured-clone.js | 87 +++++++++++++------ 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 41fca105a55d..a023f56dfc23 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -108,7 +108,7 @@ const base = { // disallow unnecessary labels 'no-extra-label': ERROR, // disallow fallthrough of case statements - 'no-fallthrough': ERROR, + 'no-fallthrough': [ERROR, { commentPattern: 'break omitted' }], // disallow the use of leading or trailing decimal points in numeric literals 'no-floating-decimal': ERROR, // disallow reassignments of native objects diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 1a49e19d963f..c7b415bc6adc 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -7,6 +7,7 @@ var uncurryThis = require('../internals/function-uncurry-this'); var fails = require('../internals/fails'); var uid = require('../internals/uid'); var isArray = require('../internals/is-array'); +var isCallable = require('../internals/is-callable'); var isConstructor = require('../internals/is-constructor'); var isObject = require('../internals/is-object'); var isSymbol = require('../internals/is-symbol'); @@ -48,6 +49,7 @@ var stringValueOf = uncurryThis(''.valueOf); var getTime = uncurryThis(Date.prototype.getTime); var PERFORMANCE_MARK = uid('structuredClone'); var DATA_CLONE_ERROR = 'DataCloneError'; +var TRANSFERRING = 'Transferring'; // waiting for https://github.com/zloirock/core-js/pull/991 var DOMException = function (message, name) { @@ -98,12 +100,16 @@ var nativeRestrictedStructuredClone = checkBasicSemantic(nativeStructuredClone) // no one of current implementations supports new (html/5749) error cloning semantic var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !nativeStructuredClone && checkNewErrorsSemantic(structuredCloneFromMark); -var throwUncloneableType = function (type) { +var throwUncloneable = function (type) { throw new DOMException('Uncloneable type: ' + type, DATA_CLONE_ERROR); }; +var throwUnpolyfillable = function (type, kind) { + throw new DOMException((kind || 'Cloning') + ' of ' + type + ' cannot be properly polyfilled in this engine', DATA_CLONE_ERROR); +}; + var structuredCloneInternal = function (value, map) { - if (isSymbol(value)) throwUncloneableType('Symbol'); + if (isSymbol(value)) throwUncloneable('Symbol'); if (!isObject(value)) return value; if (USE_STRUCTURED_CLONE_FROM_MARK) return structuredCloneFromMark(value); // effectively preserves circular references @@ -187,7 +193,7 @@ var structuredCloneInternal = function (value, map) { case 'BigInt64Array': case 'BigUint64Array': C = global[type]; - if (!isConstructor(C)) throwUncloneableType(type); + if (!isConstructor(C)) throwUnpolyfillable(type); cloned = new C( // this is safe, since arraybuffer cannot have circular references structuredCloneInternal(value.buffer, map), @@ -206,7 +212,7 @@ var structuredCloneInternal = function (value, map) { ); } else if (nativeRestrictedStructuredClone) { cloned = nativeRestrictedStructuredClone(value); - } else throwUncloneableType(type); + } else throwUnpolyfillable(type); break; case 'FileList': C = global.DataTransfer; @@ -218,7 +224,7 @@ var structuredCloneInternal = function (value, map) { cloned = dataTransfer.files; } else if (nativeRestrictedStructuredClone) { cloned = nativeRestrictedStructuredClone(value); - } else throwUncloneableType(type); + } else throwUnpolyfillable(type); break; case 'ImageData': C = global.ImageData; @@ -231,7 +237,7 @@ var structuredCloneInternal = function (value, map) { ); } else if (nativeRestrictedStructuredClone) { cloned = nativeRestrictedStructuredClone(value); - } else throwUncloneableType(type); + } else throwUnpolyfillable(type); break; default: if (nativeRestrictedStructuredClone) { @@ -271,47 +277,62 @@ var structuredCloneInternal = function (value, map) { try { cloned = value.slice(0, value.size, value.type); } catch (error) { - throwUncloneableType(type); + throwUnpolyfillable(type); } break; case 'DOMPoint': case 'DOMPointReadOnly': C = global[type]; try { - cloned = C.fromPoint ? C.fromPoint(value) : new C(value.x, value.y, value.z, value.w); + cloned = C.fromPoint + ? C.fromPoint(value) + : new C(value.x, value.y, value.z, value.w); } catch (error) { - throwUncloneableType(type); + throwUnpolyfillable(type); } break; case 'DOMRect': case 'DOMRectReadOnly': C = global[type]; try { - cloned = C.fromRect ? C.fromRect(value) : new C(value.x, value.y, value.width, value.height); + cloned = C.fromRect + ? C.fromRect(value) + : new C(value.x, value.y, value.width, value.height); } catch (error) { - throwUncloneableType(type); + throwUnpolyfillable(type); } break; case 'DOMMatrix': case 'DOMMatrixReadOnly': C = global[type]; try { - cloned = C.fromMatrix ? C.fromMatrix(value) : new C(value); + cloned = C.fromMatrix + ? C.fromMatrix(value) + : new C(value); } catch (error) { - throwUncloneableType(type); + throwUnpolyfillable(type); } break; case 'AudioData': case 'VideoFrame': + if (!isCallable(value.clone)) throwUnpolyfillable(type); try { cloned = value.clone(); } catch (error) { - throwUncloneableType(type); + throwUncloneable(type); } break; case 'File': try { cloned = new File([value], value.name, value); } catch (error) { - throwUncloneableType(type); + throwUnpolyfillable(type); } break; + case 'CryptoKey': + case 'GPUCompilationMessage': + case 'GPUCompilationInfo': + case 'ImageBitmap': + case 'RTCCertificate': + case 'WebAssembly.Module': + throwUnpolyfillable(type); + // break omitted default: - throwUncloneableType(type); + throwUncloneable(type); } } @@ -340,7 +361,7 @@ var structuredCloneInternal = function (value, map) { } if (name == 'AggregateError') { cloned.errors = structuredCloneInternal(value.errors, map); - } // fallthrough + } // break omitted case 'DOMException': if (ERROR_STACK_INSTALLABLE) { createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map)); @@ -361,7 +382,7 @@ var tryToTransfer = function (transfer, map) { var i = 0; var length = lengthOfArrayLike(transfer); - var value, type, transferredArray, transferred, canvas, context; + var value, type, C, transferredArray, transferred, canvas, context; if (PROPER_TRANSFER) { transferredArray = nativeStructuredClone(transfer, { transfer: transfer }); @@ -369,21 +390,33 @@ var tryToTransfer = function (transfer, map) { } else while (i < length) { value = transfer[i++]; if (mapHas(map, value)) throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR); + type = classof(value); - try { - switch (type) { - case 'ImageBitmap': - canvas = new OffscreenCanvas(value.width, value.height); + + switch (type) { + case 'ArrayBuffer': + throwUnpolyfillable(type, TRANSFERRING); + // break omitted + case 'ImageBitmap': + C = global.OffscreenCanvas; + if (!isConstructor(C)) throwUnpolyfillable(type, TRANSFERRING); + try { + canvas = new C(value.width, value.height); context = canvas.getContext('bitmaprenderer'); context.transferFromImageBitmap(value); transferred = canvas.transferToImageBitmap(); - break; - case 'AudioData': - case 'VideoFrame': + } catch (error) { /* empty */ } + break; + case 'AudioData': + case 'VideoFrame': + if (!isCallable(value.clone) || !isCallable(value.close)) throwUnpolyfillable(type, TRANSFERRING); + try { transferred = value.clone(); value.close(); - } - } catch (error) { /* empty */ } + } catch (error) { /* empty */ } + break; + } + if (transferred === undefined) throw new DOMException('This object cannot be transferred: ' + type, DATA_CLONE_ERROR); mapSet(map, value, transferred); } From 9fbedfbbd93f8e07cb0211a52b2921c613403d5f Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 7 Nov 2021 03:41:26 +0700 Subject: [PATCH 58/73] some comments --- packages/core-js/modules/web.structured-clone.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index c7b415bc6adc..63acf8906bdd 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -77,7 +77,7 @@ var checkNewErrorsSemantic = function (structuredCloneImplementation) { }); }; -// FF94+, NodeJS 17.0+, Deno 1.14+ +// FF94+, Safari TP134+, Chrome Canary 98+, NodeJS 17.0+, Deno 1.14+ var nativeStructuredClone = global.structuredClone; // Chrome 78+, Safari 14.1+, NodeJS 16.0+, Deno 1.11+ (old Deno implementations too naive) @@ -422,7 +422,7 @@ var tryToTransfer = function (transfer, map) { } }; -// current FF implementation can't clone errors +// current FF and Safari implementations can't clone errors // https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 // no one of current implementations supports new (html/5749) error cloning semantic var FORCED_REPLACEMENT = IS_PURE || !PROPER_TRANSFER || !checkNewErrorsSemantic(nativeStructuredClone); From 31d5712f7e78ca4386a2bf156b3c8ec1dfc2571a Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 7 Nov 2021 04:28:34 +0700 Subject: [PATCH 59/73] `.transfer` option should be convertible to a sequence https://webidl.spec.whatwg.org/#es-sequence --- packages/core-js/modules/web.structured-clone.js | 13 ++++++++++--- packages/core-js/stable/structured-clone.js | 1 + packages/core-js/web/structured-clone.js | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 63acf8906bdd..0702463ea6e9 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -6,11 +6,11 @@ var call = require('../internals/function-call'); var uncurryThis = require('../internals/function-uncurry-this'); var fails = require('../internals/fails'); var uid = require('../internals/uid'); -var isArray = require('../internals/is-array'); var isCallable = require('../internals/is-callable'); var isConstructor = require('../internals/is-constructor'); var isObject = require('../internals/is-object'); var isSymbol = require('../internals/is-symbol'); +var iterate = require('../internals/iterate'); var anObject = require('../internals/an-object'); var classof = require('../internals/classof'); var hasOwn = require('../internals/has-own-property'); @@ -43,6 +43,7 @@ var mapHas = uncurryThis(MapPrototype.has); var mapGet = uncurryThis(MapPrototype.get); var mapSet = uncurryThis(MapPrototype.set); var setAdd = uncurryThis(Set.prototype.add); +var push = uncurryThis([].push); var bolleanValueOf = uncurryThis(true.valueOf); var numberValueOf = uncurryThis(1.0.valueOf); var stringValueOf = uncurryThis(''.valueOf); @@ -377,8 +378,14 @@ var PROPER_TRANSFER = nativeStructuredClone && !fails(function () { return buffer.byteLength != 0 || clone.byteLength != 8; }); -var tryToTransfer = function (transfer, map) { - if (!isArray(transfer)) throw TypeError('Transfer option should be an array'); +var tryToTransfer = function (rawTransfer, map) { + if (!isObject(rawTransfer)) throw TypeError('Transfer option cannot be converted to a sequence'); + + var transfer = []; + + iterate(rawTransfer, function (value) { + push(transfer, anObject(value)); + }); var i = 0; var length = lengthOfArrayLike(transfer); diff --git a/packages/core-js/stable/structured-clone.js b/packages/core-js/stable/structured-clone.js index 9f0748204dbe..283f3aed5f08 100644 --- a/packages/core-js/stable/structured-clone.js +++ b/packages/core-js/stable/structured-clone.js @@ -1,3 +1,4 @@ +require('../modules/es.array.iterator'); require('../modules/es.object.to-string'); require('../modules/es.map'); require('../modules/es.set'); diff --git a/packages/core-js/web/structured-clone.js b/packages/core-js/web/structured-clone.js index 9f0748204dbe..283f3aed5f08 100644 --- a/packages/core-js/web/structured-clone.js +++ b/packages/core-js/web/structured-clone.js @@ -1,3 +1,4 @@ +require('../modules/es.array.iterator'); require('../modules/es.object.to-string'); require('../modules/es.map'); require('../modules/es.set'); From 95663bf72be875cc81e36e87a9b504e48cac7734 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 7 Nov 2021 13:57:42 +0700 Subject: [PATCH 60/73] some more related errors on unpolyfillable transferable behavior --- packages/core-js/modules/web.structured-clone.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 0702463ea6e9..0f3260a2ce15 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -401,9 +401,6 @@ var tryToTransfer = function (rawTransfer, map) { type = classof(value); switch (type) { - case 'ArrayBuffer': - throwUnpolyfillable(type, TRANSFERRING); - // break omitted case 'ImageBitmap': C = global.OffscreenCanvas; if (!isConstructor(C)) throwUnpolyfillable(type, TRANSFERRING); @@ -422,6 +419,13 @@ var tryToTransfer = function (rawTransfer, map) { value.close(); } catch (error) { /* empty */ } break; + case 'ArrayBuffer': + case 'MessagePort': + case 'OffscreenCanvas': + case 'ReadableStream': + case 'TransformStream': + case 'WritableStream': + throwUnpolyfillable(type, TRANSFERRING); } if (transferred === undefined) throw new DOMException('This object cannot be transferred: ' + type, DATA_CLONE_ERROR); From be7be95f33b97e0676f0ed92b57a564958d982c9 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 7 Nov 2021 17:49:09 +0700 Subject: [PATCH 61/73] Update packages/core-js/modules/web.structured-clone.js Co-authored-by: Kenta Moriuchi --- packages/core-js/modules/web.structured-clone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 0f3260a2ce15..d6c1633f3910 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -112,7 +112,7 @@ var throwUnpolyfillable = function (type, kind) { var structuredCloneInternal = function (value, map) { if (isSymbol(value)) throwUncloneable('Symbol'); if (!isObject(value)) return value; - if (USE_STRUCTURED_CLONE_FROM_MARK) return structuredCloneFromMark(value); + if (USE_STRUCTURED_CLONE_FROM_MARK && !map) return structuredCloneFromMark(value); // effectively preserves circular references if (map) { if (mapHas(map, value)) return mapGet(map, value); From bba045795180c1b905203252d9f86696f4c1d3fd Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Sun, 7 Nov 2021 23:13:18 +0700 Subject: [PATCH 62/73] drop `USE_STRUCTURED_CLONE_FROM_MARK` fast case native `structuredClone` is already available in unstable versions of all modern engines, but no one of them still not passes new (related to html/5749) errors cloning tests --- .../core-js/modules/web.structured-clone.js | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index d6c1633f3910..3636a0d35d99 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -78,10 +78,18 @@ var checkNewErrorsSemantic = function (structuredCloneImplementation) { }); }; -// FF94+, Safari TP134+, Chrome Canary 98+, NodeJS 17.0+, Deno 1.14+ +// FF94+, Safari TP134+, Chrome Canary 98+, NodeJS 17.0+, Deno 1.13+ +// current FF and Safari implementations can't clone errors +// https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 +// no one of current implementations supports new (html/5749) error cloning semantic var nativeStructuredClone = global.structuredClone; +var FORCED_REPLACEMENT = IS_PURE || !checkNewErrorsSemantic(nativeStructuredClone); + // Chrome 78+, Safari 14.1+, NodeJS 16.0+, Deno 1.11+ (old Deno implementations too naive) +// Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` +// current Safari implementation can't clone errors +// no one of current implementations supports new (html/5749) error cloning semantic var structuredCloneFromMark = !nativeStructuredClone && (function (fromMarkConstructor, fromMark) { return checkBasicSemantic(fromMarkConstructor) ? fromMarkConstructor : checkBasicSemantic(fromMark) && fromMark; @@ -96,11 +104,6 @@ var structuredCloneFromMark = !nativeStructuredClone && (function (fromMarkConst var nativeRestrictedStructuredClone = checkBasicSemantic(nativeStructuredClone) ? nativeStructuredClone : structuredCloneFromMark; -// Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` -// current Safari implementation can't clone errors -// no one of current implementations supports new (html/5749) error cloning semantic -var USE_STRUCTURED_CLONE_FROM_MARK = !IS_PURE && !nativeStructuredClone && checkNewErrorsSemantic(structuredCloneFromMark); - var throwUncloneable = function (type) { throw new DOMException('Uncloneable type: ' + type, DATA_CLONE_ERROR); }; @@ -112,7 +115,6 @@ var throwUnpolyfillable = function (type, kind) { var structuredCloneInternal = function (value, map) { if (isSymbol(value)) throwUncloneable('Symbol'); if (!isObject(value)) return value; - if (USE_STRUCTURED_CLONE_FROM_MARK && !map) return structuredCloneFromMark(value); // effectively preserves circular references if (map) { if (mapHas(map, value)) return mapGet(map, value); @@ -433,11 +435,6 @@ var tryToTransfer = function (rawTransfer, map) { } }; -// current FF and Safari implementations can't clone errors -// https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 -// no one of current implementations supports new (html/5749) error cloning semantic -var FORCED_REPLACEMENT = IS_PURE || !PROPER_TRANSFER || !checkNewErrorsSemantic(nativeStructuredClone); - $({ global: true, enumerable: true, sham: !PROPER_TRANSFER, forced: FORCED_REPLACEMENT }, { structuredClone: function structuredClone(value /* , { transfer } */) { var options = arguments.length > 1 ? anObject(arguments[1]) : undefined; From e9f74db57b47d96b1141d0dcee5033ff666de622 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 11 Nov 2021 02:54:27 +0700 Subject: [PATCH 63/73] update docs --- CHANGELOG.md | 4 ++++ README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1404606d156d..fac53432fe88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Changelog ##### Unreleased +- Added `structuredClone` method [from the HTML spec](https://html.spec.whatwg.org/multipage/structured-data.html#dom-structuredclone), [see MDN](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) + - Includes all cases of cloning and transferring of required ECMAScript and platform types that can be polyfilled, for the details see [the caveats](https://github.com/zloirock/core-js#caveats-when-using-structuredclone-polyfill) + - Uses native structured cloning algorithm implementations where it's possible + - Includes the new semantic of errors cloning from [`html/5749`](https://github.com/whatwg/html/pull/5749) - Added `DOMException` polyfill, [the Web IDL spec](https://webidl.spec.whatwg.org/#idl-DOMException), [see MDN](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) - Includes `DOMException` and its attributes polyfills with fixes of many different engines bugs - Includes `DOMException#stack` property polyfill in engines that should have it diff --git a/README.md b/README.md index 3aea84f826fe..9b2d270afb13 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ Promise.resolve(32).then(x => console.log(x)); // => 32 - [Pre-stage 0 proposals](#pre-stage-0-proposals) - [`Reflect` metadata](#reflect-metadata) - [Web standards](#web-standards) + - [`structuredClone`](#structuredclone) - [`setTimeout` and `setInterval`](#settimeout-and-setinterval) - [`setImmediate`](#setimmediate) - [`queueMicrotask`](#queuemicrotask) @@ -2865,6 +2866,51 @@ Reflect.getOwnMetadata('foo', object); // => 'bar' ```js core-js(-pure)/web ``` +#### `structuredClone`[⬆](#index) +[Spec](https://html.spec.whatwg.org/multipage/structured-data.html#dom-structuredclone), module [`web.structured-clone`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.structured-clone.js) +```js +function structuredClone(value: Serializable, { transfer?: Sequence }): any; +``` +[*CommonJS entry points:*](#commonjs-api) +```js +core-js/web/structured-clone +core-js(-pure)/stable|features/structured-clone +``` +[*Examples*](t.ly/dBEC): +```js +const structured = [{ a: 42 }]; +const sclone = structuredClone(structured); +console.log(sclone); // => [{ a: 42 }] +console.log(structured !== sclone); // => true +console.log(structured[0] !== sclone[0]); // => true + +const circular = {}; +circular.circular = circular; +const cclone = structuredClone(circular); +console.log(cclone.circular === cclone); // => true + +structuredClone(42); // => 42 +structuredClone({ x: 42 }); // => { x: 42 } +structuredClone([1, 2, 3]); // => [1, 2, 3] +structuredClone(new Set([1, 2, 3])); // => Set{ 1, 2, 3 } +structuredClone(new Map([['a', 1], ['b', 2]])); // => Map{ a: 1, b: 2 } +structuredClone(new Int8Array([1, 2, 3])); // => new Int8Array([1, 2, 3]) +structuredClone(new AggregateError([1, 2, 3], 'message')); // => new AggregateError([1, 2, 3], 'message')) +structuredClone(new TypeError('message', { cause: 42 })); // => new TypeError('message', { cause: 42 }) +structuredClone(new DOMException('message', 'DataCloneError')); // => new DOMException('message', 'DataCloneError') +structuredClone(document.getElementById('myfileinput')); // => new FileList +structuredClone(new DOMPoint(1, 2, 3, 4)); // => new DOMPoint(1, 2, 3, 4) +structuredClone(new Blob(['test'])); // => new Blob(['test']) +structuredClone(new ImageData(8, 8)); // => new ImageData(8, 8) +// etc. + +structuredClone(new WeakMap()); // => DataCloneError on non-serializable types +``` +##### Caveats when using `structuredClone` polyfill:[⬆](#index) + +* `ArrayBuffer` instances and many platform types cannot be transferred in most engines since we have no way to polyfill this behavior, however `.transfer` option works for some platform types. I recommend avoiding this option. +* Some specific platform types can't be cloned in old engines. Mainly it's very specific types or very old engines, but here are some exceptions. For example, we have no sync way to clone `ImageBitmap` in Safari 14.0- or Firefox 83-, so it's recommended to look to the [polyfill source](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.structured-clone.js) if you wanna clone something specific. + #### `setTimeout` and `setInterval`[⬆](#index) Module [`web.timers`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.timers.js). Additional arguments fix for IE9-. ```js From f80540d309fa26ada886347dea3340a070777eca Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Wed, 17 Nov 2021 20:10:54 +0700 Subject: [PATCH 64/73] don't test the native source on node --- tests/tests/web.structured-clone.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 17d66dc6bb88..2dbff94c8019 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -1,6 +1,6 @@ // Originally from: https://github.com/web-platform-tests/wpt/blob/4b35e758e2fc4225368304b02bcec9133965fd1a/IndexedDB/structured-clone.any.js // Copyright © web-platform-tests contributors. Available under the 3-Clause BSD License. -import { GLOBAL } from '../helpers/constants'; +import { GLOBAL, NODE } from '../helpers/constants'; import { fromSource } from '../helpers/helpers'; const { from } = Array; @@ -11,7 +11,7 @@ QUnit.module('structuredClone', () => { assert.isFunction(structuredClone, 'structuredClone is a function'); assert.name(structuredClone, 'structuredClone'); assert.arity(structuredClone, 1); - assert.looksNative(structuredClone); + if (!NODE) assert.looksNative(structuredClone); }); function cloneTest(value, verifyFunc) { From 2fbece8cda734bac879a1aa3b9b0f9a9d87a8eed Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Wed, 17 Nov 2021 20:17:01 +0700 Subject: [PATCH 65/73] fix linting --- tests/pure/web.structured-clone.js | 78 ++++++++++++++--------------- tests/tests/web.structured-clone.js | 78 ++++++++++++++--------------- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index a0d0ee59d58d..47f82fb1a12c 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -29,8 +29,8 @@ QUnit.module('structuredClone', () => { function cloneObjectTest(assert, value, verifyFunc) { cloneTest(value, (orig, clone) => { assert.notEqual(orig, clone, 'clone should have different reference'); - assert.equal(typeof clone, 'object', 'clone should be an object'); - assert.equal(getPrototypeOf(orig), getPrototypeOf(clone), 'clone should have same prototype'); + assert.same(typeof clone, 'object', 'clone should be an object'); + assert.same(getPrototypeOf(orig), getPrototypeOf(clone), 'clone should have same prototype'); verifyFunc(orig, clone); }); } @@ -108,9 +108,9 @@ QUnit.module('structuredClone', () => { for (const date of dates) cloneTest(date, (orig, clone) => { assert.notEqual(orig, clone); - assert.equal(typeof clone, 'object'); - assert.equal(getPrototypeOf(orig), getPrototypeOf(clone)); - assert.equal(orig.valueOf(), clone.valueOf()); + assert.same(typeof clone, 'object'); + assert.same(getPrototypeOf(orig), getPrototypeOf(clone)); + assert.same(orig.valueOf(), clone.valueOf()); }); }); @@ -132,7 +132,7 @@ QUnit.module('structuredClone', () => { if (giuy) regexes.push(giuy); for (const regex of regexes) cloneObjectTest(assert, regex, (orig, clone) => { - assert.equal(orig.toString(), clone.toString(), `regex ${ regex }`); + assert.same(orig.toString(), clone.toString(), `regex ${ regex }`); }); }); @@ -211,11 +211,11 @@ QUnit.module('structuredClone', () => { for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { const { name } = orig; - assert.equal(orig.constructor, clone.constructor, `${ name }#constructor`); - assert.equal(orig.name, clone.name, `${ name }#name`); - assert.equal(orig.message, clone.message, `${ name }#message`); - assert.equal(orig.stack, clone.stack, `${ name }#stack`); - assert.equal(orig.cause, clone.cause, `${ name }#cause`); + assert.same(orig.constructor, clone.constructor, `${ name }#constructor`); + assert.same(orig.name, clone.name, `${ name }#name`); + assert.same(orig.message, clone.message, `${ name }#message`); + assert.same(orig.stack, clone.stack, `${ name }#stack`); + assert.same(orig.cause, clone.cause, `${ name }#cause`); assert.deepEqual(orig.errors, clone.errors, `${ name }#errors`); }); }); @@ -237,7 +237,7 @@ QUnit.module('structuredClone', () => { assert.deepEqual(orig, clone, `array content should be same: ${ array }`); assert.deepEqual(keys(orig), keys(clone), `array key should be same: ${ array }`); for (const key of keys(orig)) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -247,7 +247,7 @@ QUnit.module('structuredClone', () => { cloneObjectTest(assert, { foo: true, bar: false }, (orig, clone) => { assert.deepEqual(keys(orig), keys(clone)); for (const key of keys(orig)) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -259,7 +259,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMMatrix', assert => { cloneObjectTest(assert, new DOMMatrix(), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -269,7 +269,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMMatrixReadOnly', assert => { cloneObjectTest(assert, new DOMMatrixReadOnly(), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -279,7 +279,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMPoint', assert => { cloneObjectTest(assert, new DOMPoint(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -289,7 +289,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMPointReadOnly', assert => { cloneObjectTest(assert, new DOMPointReadOnly(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -314,7 +314,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMRect', assert => { cloneObjectTest(assert, new DOMRect(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -324,7 +324,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMRectReadOnly', assert => { cloneObjectTest(assert, new DOMRectReadOnly(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -336,9 +336,9 @@ QUnit.module('structuredClone', () => { imageData.data[i] = i; } cloneObjectTest(assert, imageData, (orig, clone) => { - assert.equal(orig.width, clone.width); - assert.equal(orig.height, clone.height); - assert.equal(orig.colorSpace, clone.colorSpace); + assert.same(orig.width, clone.width); + assert.same(orig.height, clone.height); + assert.same(orig.colorSpace, clone.colorSpace); assert.arrayEqual(orig.data, clone.data); }); }); @@ -348,10 +348,10 @@ QUnit.module('structuredClone', () => { assert, new Blob(['This is a test.'], { type: 'a/b' }), (orig, clone) => { - assert.equal(orig.size, clone.size); - assert.equal(orig.type, clone.type); + assert.same(orig.size, clone.size); + assert.same(orig.type, clone.type); // TODO: async - // assert.equal(await orig.text(), await clone.text()); + // assert.same(await orig.text(), await clone.text()); }); }); @@ -363,10 +363,10 @@ QUnit.module('structuredClone', () => { ]; for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - assert.equal(orig.name, clone.name); - assert.equal(orig.message, clone.message); - assert.equal(orig.code, clone.code); - assert.equal(orig.stack, clone.stack); + assert.same(orig.name, clone.name); + assert.same(orig.message, clone.message); + assert.same(orig.code, clone.code); + assert.same(orig.stack, clone.stack); }); }); @@ -375,12 +375,12 @@ QUnit.module('structuredClone', () => { assert, new File(['This is a test.'], 'foo.txt', { type: 'c/d' }), (orig, clone) => { - assert.equal(orig.size, clone.size); - assert.equal(orig.type, clone.type); - assert.equal(orig.name, clone.name); - assert.equal(orig.lastModified, clone.lastModified); + assert.same(orig.size, clone.size); + assert.same(orig.type, clone.type); + assert.same(orig.name, clone.name); + assert.same(orig.lastModified, clone.lastModified); // TODO: async - // assert.equal(await orig.text(), await clone.text()); + // assert.same(await orig.text(), await clone.text()); }); }); @@ -392,11 +392,11 @@ QUnit.module('structuredClone', () => { assert, transfer.files, (orig, clone) => { - assert.equal(1, clone.length); - assert.equal(orig[0].size, clone[0].size); - assert.equal(orig[0].type, clone[0].type); - assert.equal(orig[0].name, clone[0].name); - assert.equal(orig[0].lastModified, clone[0].lastModified); + assert.same(1, clone.length); + assert.same(orig[0].size, clone[0].size); + assert.same(orig[0].type, clone[0].type); + assert.same(orig[0].name, clone[0].name); + assert.same(orig[0].lastModified, clone[0].lastModified); }, ); }); diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 2dbff94c8019..335e2fe0b010 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -22,8 +22,8 @@ QUnit.module('structuredClone', () => { function cloneObjectTest(assert, value, verifyFunc) { cloneTest(value, (orig, clone) => { assert.notEqual(orig, clone, 'clone should have different reference'); - assert.equal(typeof clone, 'object', 'clone should be an object'); - assert.equal(getPrototypeOf(orig), getPrototypeOf(clone), 'clone should have same prototype'); + assert.same(typeof clone, 'object', 'clone should be an object'); + assert.same(getPrototypeOf(orig), getPrototypeOf(clone), 'clone should have same prototype'); verifyFunc(orig, clone); }); } @@ -101,9 +101,9 @@ QUnit.module('structuredClone', () => { for (const date of dates) cloneTest(date, (orig, clone) => { assert.notEqual(orig, clone); - assert.equal(typeof clone, 'object'); - assert.equal(getPrototypeOf(orig), getPrototypeOf(clone)); - assert.equal(orig.valueOf(), clone.valueOf()); + assert.same(typeof clone, 'object'); + assert.same(getPrototypeOf(orig), getPrototypeOf(clone)); + assert.same(orig.valueOf(), clone.valueOf()); }); }); @@ -125,7 +125,7 @@ QUnit.module('structuredClone', () => { if (giuy) regexes.push(giuy); for (const regex of regexes) cloneObjectTest(assert, regex, (orig, clone) => { - assert.equal(orig.toString(), clone.toString(), `regex ${ regex }`); + assert.same(orig.toString(), clone.toString(), `regex ${ regex }`); }); }); @@ -204,11 +204,11 @@ QUnit.module('structuredClone', () => { for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { const { name } = orig; - assert.equal(orig.constructor, clone.constructor, `${ name }#constructor`); - assert.equal(orig.name, clone.name, `${ name }#name`); - assert.equal(orig.message, clone.message, `${ name }#message`); - assert.equal(orig.stack, clone.stack, `${ name }#stack`); - assert.equal(orig.cause, clone.cause, `${ name }#cause`); + assert.same(orig.constructor, clone.constructor, `${ name }#constructor`); + assert.same(orig.name, clone.name, `${ name }#name`); + assert.same(orig.message, clone.message, `${ name }#message`); + assert.same(orig.stack, clone.stack, `${ name }#stack`); + assert.same(orig.cause, clone.cause, `${ name }#cause`); assert.deepEqual(orig.errors, clone.errors, `${ name }#errors`); }); }); @@ -230,7 +230,7 @@ QUnit.module('structuredClone', () => { assert.deepEqual(orig, clone, `array content should be same: ${ array }`); assert.deepEqual(keys(orig), keys(clone), `array key should be same: ${ array }`); for (const key of keys(orig)) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -240,7 +240,7 @@ QUnit.module('structuredClone', () => { cloneObjectTest(assert, { foo: true, bar: false }, (orig, clone) => { assert.deepEqual(keys(orig), keys(clone)); for (const key of keys(orig)) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -252,7 +252,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMMatrix', assert => { cloneObjectTest(assert, new DOMMatrix(), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -262,7 +262,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMMatrixReadOnly', assert => { cloneObjectTest(assert, new DOMMatrixReadOnly(), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -272,7 +272,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMPoint', assert => { cloneObjectTest(assert, new DOMPoint(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -282,7 +282,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMPointReadOnly', assert => { cloneObjectTest(assert, new DOMPointReadOnly(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -307,7 +307,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMRect', assert => { cloneObjectTest(assert, new DOMRect(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -317,7 +317,7 @@ QUnit.module('structuredClone', () => { QUnit.test('Geometry types, DOMRectReadOnly', assert => { cloneObjectTest(assert, new DOMRectReadOnly(1, 2, 3, 4), (orig, clone) => { for (const key of keys(getPrototypeOf(orig))) { - assert.equal(orig[key], clone[key], `Property ${ key }`); + assert.same(orig[key], clone[key], `Property ${ key }`); } }); }); @@ -329,9 +329,9 @@ QUnit.module('structuredClone', () => { imageData.data[i] = i; } cloneObjectTest(assert, imageData, (orig, clone) => { - assert.equal(orig.width, clone.width); - assert.equal(orig.height, clone.height); - assert.equal(orig.colorSpace, clone.colorSpace); + assert.same(orig.width, clone.width); + assert.same(orig.height, clone.height); + assert.same(orig.colorSpace, clone.colorSpace); assert.arrayEqual(orig.data, clone.data); }); }); @@ -341,10 +341,10 @@ QUnit.module('structuredClone', () => { assert, new Blob(['This is a test.'], { type: 'a/b' }), (orig, clone) => { - assert.equal(orig.size, clone.size); - assert.equal(orig.type, clone.type); + assert.same(orig.size, clone.size); + assert.same(orig.type, clone.type); // TODO: async - // assert.equal(await orig.text(), await clone.text()); + // assert.same(await orig.text(), await clone.text()); }); }); @@ -356,10 +356,10 @@ QUnit.module('structuredClone', () => { ]; for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - assert.equal(orig.name, clone.name); - assert.equal(orig.message, clone.message); - assert.equal(orig.code, clone.code); - assert.equal(orig.stack, clone.stack); + assert.same(orig.name, clone.name); + assert.same(orig.message, clone.message); + assert.same(orig.code, clone.code); + assert.same(orig.stack, clone.stack); }); }); @@ -368,12 +368,12 @@ QUnit.module('structuredClone', () => { assert, new File(['This is a test.'], 'foo.txt', { type: 'c/d' }), (orig, clone) => { - assert.equal(orig.size, clone.size); - assert.equal(orig.type, clone.type); - assert.equal(orig.name, clone.name); - assert.equal(orig.lastModified, clone.lastModified); + assert.same(orig.size, clone.size); + assert.same(orig.type, clone.type); + assert.same(orig.name, clone.name); + assert.same(orig.lastModified, clone.lastModified); // TODO: async - // assert.equal(await orig.text(), await clone.text()); + // assert.same(await orig.text(), await clone.text()); }); }); @@ -385,11 +385,11 @@ QUnit.module('structuredClone', () => { assert, transfer.files, (orig, clone) => { - assert.equal(1, clone.length); - assert.equal(orig[0].size, clone[0].size); - assert.equal(orig[0].type, clone[0].type); - assert.equal(orig[0].name, clone[0].name); - assert.equal(orig[0].lastModified, clone[0].lastModified); + assert.same(1, clone.length); + assert.same(orig[0].size, clone[0].size); + assert.same(orig[0].type, clone[0].type); + assert.same(orig[0].name, clone[0].name); + assert.same(orig[0].lastModified, clone[0].lastModified); }, ); }); From a9298a77691709fde67543194d00c52b71eef813 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Wed, 17 Nov 2021 20:30:02 +0700 Subject: [PATCH 66/73] native NodeJS `structuredClone` throws a `TypeError` on transferable non-serializable instead of `DOMException` --- tests/pure/web.structured-clone.js | 3 ++- tests/tests/web.structured-clone.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index 47f82fb1a12c..dfa069da9ceb 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -416,7 +416,8 @@ QUnit.module('structuredClone', () => { if (channel) nons.push(channel); for (const it of nons) { - assert.throws(() => structuredClone(it), fromSource('new DOMException') ? DOMException : TypeError); + // native NodeJS `structuredClone` throws a `TypeError` on transferable non-serializable instead of `DOMException` + assert.throws(() => structuredClone(it)); } }); }); diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 335e2fe0b010..07e88718fcd2 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -409,8 +409,8 @@ QUnit.module('structuredClone', () => { if (channel) nons.push(channel); for (const it of nons) { - // TODO: remove DOMException constructor check after https://github.com/zloirock/core-js/pull/991 - assert.throws(() => structuredClone(it), fromSource('new DOMException') ? DOMException : TypeError); + // native NodeJS `structuredClone` throws a `TypeError` on transferable non-serializable instead of `DOMException` + assert.throws(() => structuredClone(it)); } }); }); From 58264516ede530ea4e6189cad07443758ae86902 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Wed, 17 Nov 2021 20:33:30 +0700 Subject: [PATCH 67/73] NodeJS events are simple objects --- tests/pure/web.structured-clone.js | 5 +++-- tests/tests/web.structured-clone.js | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index dfa069da9ceb..8184441f0f9d 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -1,7 +1,7 @@ // Originally from: https://github.com/web-platform-tests/wpt/blob/4b35e758e2fc4225368304b02bcec9133965fd1a/IndexedDB/structured-clone.any.js // Copyright © web-platform-tests contributors. Available under the 3-Clause BSD License. /* eslint-disable es/no-typed-arrays -- safe */ -import { GLOBAL } from '../helpers/constants'; +import { GLOBAL, NODE } from '../helpers/constants'; import { fromSource } from '../helpers/helpers'; import structuredClone from 'core-js-pure/stable/structured-clone'; @@ -412,7 +412,8 @@ QUnit.module('structuredClone', () => { const event = fromSource('new Event("")'); const channel = fromSource('new MessageChannel'); - if (event) nons.push(event); + // NodeJS events are simple objects + if (event && !NODE) nons.push(event); if (channel) nons.push(channel); for (const it of nons) { diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 07e88718fcd2..4e9c6e4d81dc 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -405,7 +405,8 @@ QUnit.module('structuredClone', () => { const event = fromSource('new Event("")'); const channel = fromSource('new MessageChannel'); - if (event) nons.push(event); + // NodeJS events are simple objects + if (event && !NODE) nons.push(event); if (channel) nons.push(channel); for (const it of nons) { From 6475af3743e96a5208e69211ce2b7327e4362201 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Wed, 17 Nov 2021 21:11:04 +0700 Subject: [PATCH 68/73] add a workaround for `node-qunit` bug https://github.com/qunitjs/node-qunit/issues/146 --- tests/pure/web.structured-clone.js | 3 ++- tests/tests/web.structured-clone.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index 8184441f0f9d..79671a51d58e 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -30,7 +30,8 @@ QUnit.module('structuredClone', () => { cloneTest(value, (orig, clone) => { assert.notEqual(orig, clone, 'clone should have different reference'); assert.same(typeof clone, 'object', 'clone should be an object'); - assert.same(getPrototypeOf(orig), getPrototypeOf(clone), 'clone should have same prototype'); + // https://github.com/qunitjs/node-qunit/issues/146 + assert.ok(getPrototypeOf(orig) === getPrototypeOf(clone), 'clone should have same prototype'); verifyFunc(orig, clone); }); } diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 4e9c6e4d81dc..139b036c2ef7 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -23,7 +23,8 @@ QUnit.module('structuredClone', () => { cloneTest(value, (orig, clone) => { assert.notEqual(orig, clone, 'clone should have different reference'); assert.same(typeof clone, 'object', 'clone should be an object'); - assert.same(getPrototypeOf(orig), getPrototypeOf(clone), 'clone should have same prototype'); + // https://github.com/qunitjs/node-qunit/issues/146 + assert.ok(getPrototypeOf(orig) === getPrototypeOf(clone), 'clone should have same prototype'); verifyFunc(orig, clone); }); } From 205a8b6745c72b30ab486ca011a0f3d0692582ef Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Wed, 17 Nov 2021 21:34:24 +0700 Subject: [PATCH 69/73] drop reusage of structured cloning from `performance.mark` on NodeJS --- .../core-js/modules/web.structured-clone.js | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 3636a0d35d99..d8a928ca6498 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -2,7 +2,6 @@ var IS_PURE = require('../internals/is-pure'); var $ = require('../internals/export'); var global = require('../internals/global'); var getBuiltin = require('../internals/get-built-in'); -var call = require('../internals/function-call'); var uncurryThis = require('../internals/function-uncurry-this'); var fails = require('../internals/fails'); var uid = require('../internals/uid'); @@ -29,9 +28,6 @@ var SyntaxError = global.SyntaxError; var TypeError = global.TypeError; var URIError = global.URIError; var PerformanceMark = global.PerformanceMark; -var performance = global.performance; -var mark = performance && performance.mark; -var clearMarks = performance && performance.clearMarks; var WebAssembly = global.WebAssembly; var CompileError = WebAssembly && WebAssembly.CompileError || Error; var LinkError = WebAssembly && WebAssembly.LinkError || Error; @@ -67,7 +63,7 @@ var checkBasicSemantic = function (structuredCloneImplementation) { var set2 = structuredCloneImplementation(set1); var number = structuredCloneImplementation(Object(7)); return set2 == set1 || !set2.has(7) || typeof number != 'object' || number != 7; - }); + }) && structuredCloneImplementation; }; // https://github.com/whatwg/html/pull/5749 @@ -75,7 +71,7 @@ var checkNewErrorsSemantic = function (structuredCloneImplementation) { return !fails(function () { var test = structuredCloneImplementation(new global.AggregateError([1], PERFORMANCE_MARK, { cause: 3 })); return test.name != 'AggregateError' || test.errors[0] != 1 || test.message != PERFORMANCE_MARK || test.cause != 3; - }); + }) && structuredCloneImplementation; }; // FF94+, Safari TP134+, Chrome Canary 98+, NodeJS 17.0+, Deno 1.13+ @@ -86,23 +82,18 @@ var nativeStructuredClone = global.structuredClone; var FORCED_REPLACEMENT = IS_PURE || !checkNewErrorsSemantic(nativeStructuredClone); -// Chrome 78+, Safari 14.1+, NodeJS 16.0+, Deno 1.11+ (old Deno implementations too naive) -// Chrome 82- implementation swaps `.name` and `.message` of cloned `DOMException` +// Chrome 82+, Safari 14.1+, Deno 1.11+ +// Chrome 78-81 implementation swaps `.name` and `.message` of cloned `DOMException` +// Deno 1.2-1.10 implementations too naive +// NodeJS 16.0+ haven't `PerformanceMark` constructor and structured cloning implementation +// from `performance.mark` is too naive and can't clone, for example, `RegExp` or some boxed primitives // current Safari implementation can't clone errors // no one of current implementations supports new (html/5749) error cloning semantic -var structuredCloneFromMark = !nativeStructuredClone && (function (fromMarkConstructor, fromMark) { - return checkBasicSemantic(fromMarkConstructor) ? fromMarkConstructor - : checkBasicSemantic(fromMark) && fromMark; -})(function (value) { +var structuredCloneFromMark = !nativeStructuredClone && checkBasicSemantic(function (value) { return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail; -}, function (value) { - // NodeJS haven't `PerformanceMark` constructor - var result = call(mark, performance, PERFORMANCE_MARK, { detail: value }); - call(clearMarks, performance, PERFORMANCE_MARK); - return result.detail; }); -var nativeRestrictedStructuredClone = checkBasicSemantic(nativeStructuredClone) ? nativeStructuredClone : structuredCloneFromMark; +var nativeRestrictedStructuredClone = checkBasicSemantic(nativeStructuredClone) || structuredCloneFromMark; var throwUncloneable = function (type) { throw new DOMException('Uncloneable type: ' + type, DATA_CLONE_ERROR); From 6414360ee84c1206c5e487db0fa68018b42dd0d7 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Wed, 17 Nov 2021 22:46:37 +0700 Subject: [PATCH 70/73] add some comments --- packages/core-js/modules/web.structured-clone.js | 3 ++- tests/pure/web.structured-clone.js | 5 +++-- tests/tests/web.structured-clone.js | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index d8a928ca6498..50d8d839b453 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -85,8 +85,9 @@ var FORCED_REPLACEMENT = IS_PURE || !checkNewErrorsSemantic(nativeStructuredClon // Chrome 82+, Safari 14.1+, Deno 1.11+ // Chrome 78-81 implementation swaps `.name` and `.message` of cloned `DOMException` // Deno 1.2-1.10 implementations too naive -// NodeJS 16.0+ haven't `PerformanceMark` constructor and structured cloning implementation +// NodeJS 16.0+ haven't `PerformanceMark` constructor, structured cloning implementation // from `performance.mark` is too naive and can't clone, for example, `RegExp` or some boxed primitives +// https://github.com/nodejs/node/issues/40840 // current Safari implementation can't clone errors // no one of current implementations supports new (html/5749) error cloning semantic var structuredCloneFromMark = !nativeStructuredClone && checkBasicSemantic(function (value) { diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index 79671a51d58e..e899911fecb0 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -411,14 +411,15 @@ QUnit.module('structuredClone', () => { ]; const event = fromSource('new Event("")'); - const channel = fromSource('new MessageChannel'); + const port = fromSource('new MessageChannel().port1'); // NodeJS events are simple objects if (event && !NODE) nons.push(event); - if (channel) nons.push(channel); + if (port) nons.push(port); for (const it of nons) { // native NodeJS `structuredClone` throws a `TypeError` on transferable non-serializable instead of `DOMException` + // https://github.com/nodejs/node/issues/40841 assert.throws(() => structuredClone(it)); } }); diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 139b036c2ef7..794b2abcfe77 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -404,14 +404,15 @@ QUnit.module('structuredClone', () => { ]; const event = fromSource('new Event("")'); - const channel = fromSource('new MessageChannel'); + const port = fromSource('new MessageChannel().port1'); // NodeJS events are simple objects if (event && !NODE) nons.push(event); - if (channel) nons.push(channel); + if (port) nons.push(port); for (const it of nons) { // native NodeJS `structuredClone` throws a `TypeError` on transferable non-serializable instead of `DOMException` + // https://github.com/nodejs/node/issues/40841 assert.throws(() => structuredClone(it)); } }); From 64be468b2af36e9105c122dba5c259595907bc9d Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Tue, 23 Nov 2021 02:49:30 +0700 Subject: [PATCH 71/73] refactor tests --- tests/pure/web.structured-clone.js | 6 +++--- tests/tests/web.structured-clone.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index e899911fecb0..0f791092a2f7 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -28,10 +28,10 @@ QUnit.module('structuredClone', () => { // Specialization of cloneTest() for objects, with common asserts. function cloneObjectTest(assert, value, verifyFunc) { cloneTest(value, (orig, clone) => { - assert.notEqual(orig, clone, 'clone should have different reference'); + assert.notSame(orig, clone, 'clone should have different reference'); assert.same(typeof clone, 'object', 'clone should be an object'); // https://github.com/qunitjs/node-qunit/issues/146 - assert.ok(getPrototypeOf(orig) === getPrototypeOf(clone), 'clone should have same prototype'); + assert.true(getPrototypeOf(orig) === getPrototypeOf(clone), 'clone should have same prototype'); verifyFunc(orig, clone); }); } @@ -108,7 +108,7 @@ QUnit.module('structuredClone', () => { ]; for (const date of dates) cloneTest(date, (orig, clone) => { - assert.notEqual(orig, clone); + assert.notSame(orig, clone); assert.same(typeof clone, 'object'); assert.same(getPrototypeOf(orig), getPrototypeOf(clone)); assert.same(orig.valueOf(), clone.valueOf()); diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 794b2abcfe77..f55cd67ad021 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -21,10 +21,10 @@ QUnit.module('structuredClone', () => { // Specialization of cloneTest() for objects, with common asserts. function cloneObjectTest(assert, value, verifyFunc) { cloneTest(value, (orig, clone) => { - assert.notEqual(orig, clone, 'clone should have different reference'); + assert.notSame(orig, clone, 'clone should have different reference'); assert.same(typeof clone, 'object', 'clone should be an object'); // https://github.com/qunitjs/node-qunit/issues/146 - assert.ok(getPrototypeOf(orig) === getPrototypeOf(clone), 'clone should have same prototype'); + assert.true(getPrototypeOf(orig) === getPrototypeOf(clone), 'clone should have same prototype'); verifyFunc(orig, clone); }); } @@ -101,7 +101,7 @@ QUnit.module('structuredClone', () => { ]; for (const date of dates) cloneTest(date, (orig, clone) => { - assert.notEqual(orig, clone); + assert.notSame(orig, clone); assert.same(typeof clone, 'object'); assert.same(getPrototypeOf(orig), getPrototypeOf(clone)); assert.same(orig.valueOf(), clone.valueOf()); From 27182a314e5fe387a55ad9cfe8260d5b4fcecea9 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 9 Dec 2021 21:07:02 +0700 Subject: [PATCH 72/73] use polyfilled `DOMException` --- packages/core-js/modules/web.structured-clone.js | 10 +--------- packages/core-js/stable/structured-clone.js | 4 ++++ tests/pure/web.structured-clone.js | 4 ++-- tests/tests/web.structured-clone.js | 3 +-- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 50d8d839b453..97146835f022 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -32,6 +32,7 @@ var WebAssembly = global.WebAssembly; var CompileError = WebAssembly && WebAssembly.CompileError || Error; var LinkError = WebAssembly && WebAssembly.LinkError || Error; var RuntimeError = WebAssembly && WebAssembly.RuntimeError || Error; +var DOMException = getBuiltin('DOMException'); var Set = getBuiltin('Set'); var Map = getBuiltin('Map'); var MapPrototype = Map.prototype; @@ -48,15 +49,6 @@ var PERFORMANCE_MARK = uid('structuredClone'); var DATA_CLONE_ERROR = 'DataCloneError'; var TRANSFERRING = 'Transferring'; -// waiting for https://github.com/zloirock/core-js/pull/991 -var DOMException = function (message, name) { - try { - return new global.DOMException(message, name); - } catch (error) { - return TypeError(message); - } -}; - var checkBasicSemantic = function (structuredCloneImplementation) { return !fails(function () { var set1 = new global.Set([7]); diff --git a/packages/core-js/stable/structured-clone.js b/packages/core-js/stable/structured-clone.js index 283f3aed5f08..36e2b60d2f3a 100644 --- a/packages/core-js/stable/structured-clone.js +++ b/packages/core-js/stable/structured-clone.js @@ -1,7 +1,11 @@ +require('../modules/es.error.to-string'); require('../modules/es.array.iterator'); require('../modules/es.object.to-string'); require('../modules/es.map'); require('../modules/es.set'); +require('../modules/web.dom-exception.constructor'); +require('../modules/web.dom-exception.stack'); +require('../modules/web.dom-exception.to-string-tag'); require('../modules/web.structured-clone'); var path = require('../internals/path'); diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index 0f791092a2f7..178f2462ba47 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -13,6 +13,7 @@ import Symbol from 'core-js-pure/es/symbol'; import Map from 'core-js-pure/es/map'; import Set from 'core-js-pure/es/set'; import AggregateError from 'core-js-pure/es/aggregate-error'; +import DOMException from 'core-js-pure/stable/dom-exception'; QUnit.module('structuredClone', () => { QUnit.test('identity', assert => { @@ -356,8 +357,7 @@ QUnit.module('structuredClone', () => { }); }); - // TODO: remove check after https://github.com/zloirock/core-js/pull/991 - if (fromSource('new DOMException')) QUnit.test('DOMException', assert => { + QUnit.test('DOMException', assert => { const errors = [ new DOMException(), new DOMException('foo', 'DataCloneError'), diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index f55cd67ad021..3be3d19ae54b 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -349,8 +349,7 @@ QUnit.module('structuredClone', () => { }); }); - // TODO: remove DOMException constructor check after https://github.com/zloirock/core-js/pull/991 - if (fromSource('new DOMException')) QUnit.test('DOMException', assert => { + QUnit.test('DOMException', assert => { const errors = [ new DOMException(), new DOMException('foo', 'DataCloneError'), From e0d475e4cef24b19bed3cb965ad9351d2965beb1 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 9 Dec 2021 21:53:33 +0700 Subject: [PATCH 73/73] improve description of some tests --- tests/pure/web.structured-clone.js | 41 ++++++++++++++--------------- tests/tests/web.structured-clone.js | 41 ++++++++++++++--------------- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/tests/pure/web.structured-clone.js b/tests/pure/web.structured-clone.js index 178f2462ba47..63dd0c8c1cd1 100644 --- a/tests/pure/web.structured-clone.js +++ b/tests/pure/web.structured-clone.js @@ -185,34 +185,33 @@ QUnit.module('structuredClone', () => { // Error QUnit.test('Error', assert => { const errors = [ - new Error(), - new Error('abc', 'def', { cause: 42 }), - new EvalError(), - new EvalError('ghi', 'jkl', { cause: 42 }), - new RangeError(), - new RangeError('ghi', 'jkl', { cause: 42 }), - new ReferenceError(), - new ReferenceError('ghi', 'jkl', { cause: 42 }), - new SyntaxError(), - new SyntaxError('ghi', 'jkl', { cause: 42 }), - new TypeError(), - new TypeError('ghi', 'jkl', { cause: 42 }), - new URIError(), - new URIError('ghi', 'jkl', { cause: 42 }), - new AggregateError([1, 2]), - new AggregateError([1, 2], 42, { cause: 42 }), + ['Error', new Error()], + ['Error', new Error('abc', 'def', { cause: 42 })], + ['EvalError', new EvalError()], + ['EvalError', new EvalError('ghi', 'jkl', { cause: 42 })], + ['RangeError', new RangeError()], + ['RangeError', new RangeError('ghi', 'jkl', { cause: 42 })], + ['ReferenceError', new ReferenceError()], + ['ReferenceError', new ReferenceError('ghi', 'jkl', { cause: 42 })], + ['SyntaxError', new SyntaxError()], + ['SyntaxError', new SyntaxError('ghi', 'jkl', { cause: 42 })], + ['TypeError', new TypeError()], + ['TypeError', new TypeError('ghi', 'jkl', { cause: 42 })], + ['URIError', new URIError()], + ['URIError', new URIError('ghi', 'jkl', { cause: 42 })], + ['AggregateError', new AggregateError([1, 2])], + ['AggregateError', new AggregateError([1, 2], 42, { cause: 42 })], ]; const compile = fromSource('WebAssembly.CompileError()'); const link = fromSource('WebAssembly.LinkError()'); const runtime = fromSource('WebAssembly.RuntimeError()'); - if (compile) errors.push(compile); - if (link) errors.push(link); - if (runtime) errors.push(runtime); + if (compile) errors.push(['CompileError', compile]); + if (link) errors.push(['LinkError', link]); + if (runtime) errors.push(['RuntimeError', runtime]); - for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - const { name } = orig; + for (const [name, error] of errors) cloneObjectTest(assert, error, (orig, clone) => { assert.same(orig.constructor, clone.constructor, `${ name }#constructor`); assert.same(orig.name, clone.name, `${ name }#name`); assert.same(orig.message, clone.message, `${ name }#message`); diff --git a/tests/tests/web.structured-clone.js b/tests/tests/web.structured-clone.js index 3be3d19ae54b..c69816949688 100644 --- a/tests/tests/web.structured-clone.js +++ b/tests/tests/web.structured-clone.js @@ -177,34 +177,33 @@ QUnit.module('structuredClone', () => { // Error QUnit.test('Error', assert => { const errors = [ - new Error(), - new Error('abc', 'def', { cause: 42 }), - new EvalError(), - new EvalError('ghi', 'jkl', { cause: 42 }), - new RangeError(), - new RangeError('ghi', 'jkl', { cause: 42 }), - new ReferenceError(), - new ReferenceError('ghi', 'jkl', { cause: 42 }), - new SyntaxError(), - new SyntaxError('ghi', 'jkl', { cause: 42 }), - new TypeError(), - new TypeError('ghi', 'jkl', { cause: 42 }), - new URIError(), - new URIError('ghi', 'jkl', { cause: 42 }), - new AggregateError([1, 2]), - new AggregateError([1, 2], 42, { cause: 42 }), + ['Error', new Error()], + ['Error', new Error('abc', 'def', { cause: 42 })], + ['EvalError', new EvalError()], + ['EvalError', new EvalError('ghi', 'jkl', { cause: 42 })], + ['RangeError', new RangeError()], + ['RangeError', new RangeError('ghi', 'jkl', { cause: 42 })], + ['ReferenceError', new ReferenceError()], + ['ReferenceError', new ReferenceError('ghi', 'jkl', { cause: 42 })], + ['SyntaxError', new SyntaxError()], + ['SyntaxError', new SyntaxError('ghi', 'jkl', { cause: 42 })], + ['TypeError', new TypeError()], + ['TypeError', new TypeError('ghi', 'jkl', { cause: 42 })], + ['URIError', new URIError()], + ['URIError', new URIError('ghi', 'jkl', { cause: 42 })], + ['AggregateError', new AggregateError([1, 2])], + ['AggregateError', new AggregateError([1, 2], 42, { cause: 42 })], ]; const compile = fromSource('WebAssembly.CompileError()'); const link = fromSource('WebAssembly.LinkError()'); const runtime = fromSource('WebAssembly.RuntimeError()'); - if (compile) errors.push(compile); - if (link) errors.push(link); - if (runtime) errors.push(runtime); + if (compile) errors.push(['CompileError', compile]); + if (link) errors.push(['LinkError', link]); + if (runtime) errors.push(['RuntimeError', runtime]); - for (const error of errors) cloneObjectTest(assert, error, (orig, clone) => { - const { name } = orig; + for (const [name, error] of errors) cloneObjectTest(assert, error, (orig, clone) => { assert.same(orig.constructor, clone.constructor, `${ name }#constructor`); assert.same(orig.name, clone.name, `${ name }#name`); assert.same(orig.message, clone.message, `${ name }#message`);