From c6ea9f77961b51fd6ccc2eb94aa2588a54bbac44 Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Wed, 25 Oct 2023 01:17:26 +0700 Subject: [PATCH] simplify `structuredClone` polyfill, avoid second tree pass in cases of transferring --- CHANGELOG.md | 1 + .../core-js/modules/web.structured-clone.js | 168 ++++-------------- 2 files changed, 39 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72c3b2bb4b27..9ce71c1b1d36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Changelog ##### Unreleased +- Simplified `structuredClone` polyfill, avoided second tree pass in cases of transferring - Removed unspecified unnecessary `ArrayBuffer` and `DataView` dependencies of `structuredClone` lack of which could cause errors in some entries in IE10- - Compat data improvements: - Updated Opera Android 78 compat data mapping diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index bde3f5c29b81..5f56877073d7 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -22,6 +22,7 @@ var validateArgumentsLength = require('../internals/validate-arguments-length'); var getRegExpFlags = require('../internals/regexp-get-flags'); var MapHelpers = require('../internals/map-helpers'); var SetHelpers = require('../internals/set-helpers'); +var setIterate = require('../internals/set-iterate'); var detachTransferable = require('../internals/detach-transferable'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); var PROPER_STRUCTURED_CLONE_TRANSFER = require('../internals/structured-clone-proper-transfer'); @@ -48,6 +49,7 @@ var mapGet = MapHelpers.get; var mapSet = MapHelpers.set; var Set = SetHelpers.Set; var setAdd = SetHelpers.add; +var setHas = SetHelpers.has; var objectKeys = getBuiltin('Object', 'keys'); var push = uncurryThis([].push); var thisBooleanValue = uncurryThis(true.valueOf); @@ -145,7 +147,7 @@ var createDataTransfer = function () { }; var cloneBuffer = function (value, map, $type) { - if (map && mapHas(map, value)) return mapGet(map, value); + if (mapHas(map, value)) return mapGet(map, value); var type = $type || classof(value); var clone, length, options, source, target, i; @@ -179,7 +181,7 @@ var cloneBuffer = function (value, map, $type) { } } - if (map) mapSet(map, value, clone); + mapSet(map, value, clone); return clone; }; @@ -192,13 +194,7 @@ var cloneView = function (value, type, offset, length, map) { return new C(cloneBuffer(value.buffer, map), offset, length); }; -var Placeholder = function (object, type, metadata) { - this.object = object; - this.type = type; - this.metadata = metadata; -}; - -var structuredCloneInternal = function (value, map, transferredBuffers) { +var structuredCloneInternal = function (value, map) { if (isSymbol(value)) throwUncloneable('Symbol'); if (!isObject(value)) return value; // effectively preserves circular references @@ -269,9 +265,7 @@ var structuredCloneInternal = function (value, map, transferredBuffers) { break; case 'ArrayBuffer': case 'SharedArrayBuffer': - cloned = transferredBuffers - ? new Placeholder(value, type) - : cloneBuffer(value, map, type); + cloned = cloneBuffer(value, map, type); break; case 'DataView': case 'Int8Array': @@ -287,17 +281,15 @@ var structuredCloneInternal = function (value, map, transferredBuffers) { case 'BigInt64Array': case 'BigUint64Array': length = type === 'DataView' ? value.byteLength : value.length; - cloned = transferredBuffers - ? new Placeholder(value, type, { offset: value.byteOffset, length: length }) - : cloneView(value, type, value.byteOffset, length, map); + cloned = cloneView(value, type, value.byteOffset, length, map); break; case 'DOMQuad': try { cloned = new DOMQuad( - structuredCloneInternal(value.p1, map, transferredBuffers), - structuredCloneInternal(value.p2, map, transferredBuffers), - structuredCloneInternal(value.p3, map, transferredBuffers), - structuredCloneInternal(value.p4, map, transferredBuffers) + structuredCloneInternal(value.p1, map), + structuredCloneInternal(value.p2, map), + structuredCloneInternal(value.p3, map), + structuredCloneInternal(value.p4, map) ); } catch (error) { cloned = tryNativeRestrictedStructuredClone(value, type); @@ -318,7 +310,7 @@ var structuredCloneInternal = function (value, map, transferredBuffers) { dataTransfer = createDataTransfer(); if (dataTransfer) { for (i = 0, length = lengthOfArrayLike(value); i < length; i++) { - dataTransfer.items.add(structuredCloneInternal(value[i], map, transferredBuffers)); + dataTransfer.items.add(structuredCloneInternal(value[i], map)); } cloned = dataTransfer.files; } else cloned = tryNativeRestrictedStructuredClone(value, type); @@ -327,7 +319,7 @@ var structuredCloneInternal = function (value, map, transferredBuffers) { // Safari 9 ImageData is a constructor, but typeof ImageData is 'object' try { cloned = new ImageData( - structuredCloneInternal(value.data, map, transferredBuffers), + structuredCloneInternal(value.data, map), value.width, value.height, { colorSpace: value.colorSpace } @@ -424,105 +416,35 @@ var structuredCloneInternal = function (value, map, transferredBuffers) { keys = objectKeys(value); for (i = 0, length = lengthOfArrayLike(keys); i < length; i++) { key = keys[i]; - createProperty(cloned, key, structuredCloneInternal(value[key], map, transferredBuffers)); + createProperty(cloned, key, structuredCloneInternal(value[key], map)); } break; case 'Map': value.forEach(function (v, k) { - mapSet(cloned, structuredCloneInternal(k, map, transferredBuffers), structuredCloneInternal(v, map, transferredBuffers)); + mapSet(cloned, structuredCloneInternal(k, map), structuredCloneInternal(v, map)); }); break; case 'Set': value.forEach(function (v) { - setAdd(cloned, structuredCloneInternal(v, map, transferredBuffers)); + setAdd(cloned, structuredCloneInternal(v, map)); }); break; case 'Error': - createNonEnumerableProperty(cloned, 'message', structuredCloneInternal(value.message, map, transferredBuffers)); + createNonEnumerableProperty(cloned, 'message', structuredCloneInternal(value.message, map)); if (hasOwn(value, 'cause')) { - createNonEnumerableProperty(cloned, 'cause', structuredCloneInternal(value.cause, map, transferredBuffers)); + createNonEnumerableProperty(cloned, 'cause', structuredCloneInternal(value.cause, map)); } if (name === 'AggregateError') { - cloned.errors = structuredCloneInternal(value.errors, map, transferredBuffers); + cloned.errors = structuredCloneInternal(value.errors, map); } // break omitted case 'DOMException': if (ERROR_STACK_INSTALLABLE) { - createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map, transferredBuffers)); + createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map)); } } return cloned; }; -var replacePlaceholders = function (value, map) { - if (!isObject(value)) return value; - if (mapHas(map, value)) return mapGet(map, value); - - var type, object, metadata, i, length, keys, key, replacement; - - if (value instanceof Placeholder) { - type = value.type; - object = value.object; - - switch (type) { - case 'ArrayBuffer': - case 'SharedArrayBuffer': - replacement = cloneBuffer(object, map, type); - break; - case 'DataView': - case 'Int8Array': - case 'Uint8Array': - case 'Uint8ClampedArray': - case 'Int16Array': - case 'Uint16Array': - case 'Int32Array': - case 'Uint32Array': - case 'Float16Array': - case 'Float32Array': - case 'Float64Array': - case 'BigInt64Array': - case 'BigUint64Array': - metadata = value.metadata; - replacement = cloneView(object, type, metadata.offset, metadata.length, map); - } - } else switch (classof(value)) { - case 'Array': - case 'Object': - keys = objectKeys(value); - for (i = 0, length = lengthOfArrayLike(keys); i < length; i++) { - key = keys[i]; - value[key] = replacePlaceholders(value[key], map); - } break; - case 'Map': - replacement = new Map(); - value.forEach(function (v, k) { - mapSet(replacement, replacePlaceholders(k, map), replacePlaceholders(v, map)); - }); - break; - case 'Set': - replacement = new Set(); - value.forEach(function (v) { - setAdd(replacement, replacePlaceholders(v, map)); - }); - break; - case 'Error': - value.message = replacePlaceholders(value.message, map); - if (hasOwn(value, 'cause')) { - value.cause = replacePlaceholders(value.cause, map); - } - if (value.name === 'AggregateError') { - value.errors = replacePlaceholders(value.errors, map); - } // break omitted - case 'DOMException': - if (ERROR_STACK_INSTALLABLE) { - value.stack = replacePlaceholders(value.stack, map); - } - } - - mapSet(map, value, replacement || value); - - return replacement || value; -}; - var tryToTransfer = function (rawTransfer, map) { if (!isObject(rawTransfer)) throw new TypeError('Transfer option cannot be converted to a sequence'); @@ -534,7 +456,7 @@ var tryToTransfer = function (rawTransfer, map) { var i = 0; var length = lengthOfArrayLike(transfer); - var buffers = []; + var buffers = new Set(); var value, type, C, transferred, canvas, context; while (i < length) { @@ -542,13 +464,15 @@ var tryToTransfer = function (rawTransfer, map) { type = classof(value); + if (type === 'ArrayBuffer' ? setHas(buffers, value) : mapHas(map, value)) { + throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR); + } + if (type === 'ArrayBuffer') { - push(buffers, value); + setAdd(buffers, value); continue; } - if (mapHas(map, value)) throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR); - if (PROPER_STRUCTURED_CLONE_TRANSFER) { transferred = nativeStructuredClone(value, { transfer: [value] }); } else switch (type) { @@ -587,28 +511,18 @@ var tryToTransfer = function (rawTransfer, map) { return buffers; }; -var tryToTransferBuffers = function (transfer, map) { - var i = 0; - var length = lengthOfArrayLike(transfer); - var value, transferred; - - while (i < length) { - value = transfer[i++]; - - if (mapHas(map, value)) throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR); - +var detachBuffers = function (buffers) { + setIterate(buffers, function (buffer) { if (PROPER_STRUCTURED_CLONE_TRANSFER) { - transferred = nativeRestrictedStructuredClone(value, { transfer: [value] }); - } else if (isCallable(value.transfer)) { - transferred = value.transfer(); + nativeRestrictedStructuredClone(buffer, { transfer: [buffer] }); + } else if (isCallable(buffer.transfer)) { + buffer.transfer(); + } else if (detachTransferable) { + detachTransferable(buffer); } else { - if (!detachTransferable) throwUnpolyfillable('ArrayBuffer', TRANSFERRING); - transferred = cloneBuffer(value); - detachTransferable(value); + throwUnpolyfillable('ArrayBuffer', TRANSFERRING); } - - mapSet(map, value, transferred); - } + }); }; // `structuredClone` method @@ -617,24 +531,18 @@ $({ global: true, enumerable: true, sham: !PROPER_STRUCTURED_CLONE_TRANSFER, for structuredClone: function structuredClone(value /* , { transfer } */) { var options = validateArgumentsLength(arguments.length, 1) > 1 && !isNullOrUndefined(arguments[1]) ? anObject(arguments[1]) : undefined; var transfer = options ? options.transfer : undefined; - var transferredBuffers = false; var map, buffers; if (transfer !== undefined) { map = new Map(); buffers = tryToTransfer(transfer, map); - transferredBuffers = !!lengthOfArrayLike(buffers); } - var clone = structuredCloneInternal(value, map, transferredBuffers); + var clone = structuredCloneInternal(value, map); - // since of an issue with cloning views of transferred buffers, we a forced to transfer / clone them in 2 steps + // since of an issue with cloning views of transferred buffers, we a forced to detach them later // https://github.com/zloirock/core-js/issues/1265 - if (transferredBuffers) { - map = new Map(); - tryToTransferBuffers(transfer, map); - clone = replacePlaceholders(clone, map); - } + if (buffers) detachBuffers(buffers); return clone; }