Skip to content

Commit

Permalink
simplify structuredClone polyfill, avoid second tree pass in cases …
Browse files Browse the repository at this point in the history
…of transferring
  • Loading branch information
zloirock committed Oct 24, 2023
1 parent 82978a7 commit c6ea9f7
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 130 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
168 changes: 38 additions & 130 deletions packages/core-js/modules/web.structured-clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -179,7 +181,7 @@ var cloneBuffer = function (value, map, $type) {
}
}

if (map) mapSet(map, value, clone);
mapSet(map, value, clone);

return clone;
};
Expand All @@ -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
Expand Down Expand Up @@ -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':
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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 }
Expand Down Expand Up @@ -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');

Expand All @@ -534,21 +456,23 @@ 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) {
value = transfer[i++];

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) {
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
Expand Down

0 comments on commit c6ea9f7

Please sign in to comment.