You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

486 lines
17 KiB

2 years ago
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 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');
var createProperty = require('../internals/create-property');
var createNonEnumerableProperty = require('../internals/create-non-enumerable-property');
var lengthOfArrayLike = require('../internals/length-of-array-like');
var validateArgumentsLength = require('../internals/validate-arguments-length');
var getRegExpFlags = require('../internals/regexp-get-flags');
var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable');
var V8 = require('../internals/engine-v8-version');
var IS_BROWSER = require('../internals/engine-is-browser');
var IS_DENO = require('../internals/engine-is-deno');
var IS_NODE = require('../internals/engine-is-node');
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 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;
var mapHas = uncurryThis(MapPrototype.has);
var mapGet = uncurryThis(MapPrototype.get);
var mapSet = uncurryThis(MapPrototype.set);
var setAdd = uncurryThis(Set.prototype.add);
var objectKeys = getBuiltin('Object', 'keys');
var push = uncurryThis([].push);
var booleanValueOf = 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';
var TRANSFERRING = 'Transferring';
var checkBasicSemantic = function (structuredCloneImplementation) {
return !fails(function () {
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;
}) && structuredCloneImplementation;
};
var checkErrorsCloning = function (structuredCloneImplementation, $Error) {
return !fails(function () {
var error = new $Error();
var test = structuredCloneImplementation({ a: error, b: error });
return !(test && test.a === test.b && test.a instanceof $Error);
});
};
// https://github.com/whatwg/html/pull/5749
var checkNewErrorsCloningSemantic = 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;
});
};
// FF94+, Safari 15.4+, Chrome 98+, NodeJS 17.0+, Deno 1.13+
// FF and Safari implementations can't clone errors
// https://bugzilla.mozilla.org/show_bug.cgi?id=1556604
// Chrome <102 returns `null` if cloned object contains multiple references to one error
// https://bugs.chromium.org/p/v8/issues/detail?id=12542
// NodeJS implementation can't clone DOMExceptions
// https://github.com/nodejs/node/issues/41038
// no one of current implementations supports new (html/5749) error cloning semantic
var nativeStructuredClone = global.structuredClone;
var FORCED_REPLACEMENT = IS_PURE
|| !checkErrorsCloning(nativeStructuredClone, Error)
|| !checkErrorsCloning(nativeStructuredClone, DOMException)
|| !checkNewErrorsCloningSemantic(nativeStructuredClone);
// Chrome 82+, Safari 14.1+, Deno 1.11+
// Chrome 78-81 implementation swaps `.name` and `.message` of cloned `DOMException`
// Chrome returns `null` if cloned object contains multiple references to one error
// Safari 14.1 implementation doesn't clone some `RegExp` flags, so requires a workaround
// Safari implementation can't clone errors
// Deno 1.2-1.10 implementations too naive
// NodeJS 16.0+ does not have `PerformanceMark` constructor
// NodeJS <17.2 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
// no one of current implementations supports new (html/5749) error cloning semantic
var structuredCloneFromMark = !nativeStructuredClone && checkBasicSemantic(function (value) {
return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail;
});
var nativeRestrictedStructuredClone = checkBasicSemantic(nativeStructuredClone) || structuredCloneFromMark;
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)) throwUncloneable('Symbol');
if (!isObject(value)) return value;
// effectively preserves circular references
if (map) {
if (mapHas(map, value)) return mapGet(map, value);
} else map = new Map();
var type = classof(value);
var deep = false;
var C, name, cloned, dataTransfer, i, length, keys, key, source, target;
switch (type) {
case 'Array':
cloned = [];
deep = true;
break;
case 'Object':
cloned = {};
deep = true;
break;
case 'Map':
cloned = new Map();
deep = true;
break;
case 'Set':
cloned = new Set();
deep = true;
break;
case 'RegExp':
// in this block because of a Safari 14.1 bug
// old FF does not clone regexes passed to the constructor, so get the source and flags directly
cloned = new RegExp(value.source, getRegExpFlags(value));
break;
case 'Error':
name = value.name;
switch (name) {
case 'AggregateError':
cloned = getBuiltin('AggregateError')([]);
break;
case 'EvalError':
cloned = EvalError();
break;
case 'RangeError':
cloned = RangeError();
break;
case 'ReferenceError':
cloned = ReferenceError();
break;
case 'SyntaxError':
cloned = SyntaxError();
break;
case 'TypeError':
cloned = TypeError();
break;
case 'URIError':
cloned = URIError();
break;
case 'CompileError':
cloned = CompileError();
break;
case 'LinkError':
cloned = LinkError();
break;
case 'RuntimeError':
cloned = RuntimeError();
break;
default:
cloned = Error();
}
deep = true;
break;
case 'DOMException':
cloned = new DOMException(value.message, value.name);
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':
C = global[type];
// in some old engines like Safari 9, typeof C is 'object'
// on Uint8ClampedArray or some other constructors
if (!isObject(C)) throwUnpolyfillable(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 'DOMQuad':
try {
cloned = new DOMQuad(
structuredCloneInternal(value.p1, map),
structuredCloneInternal(value.p2, map),
structuredCloneInternal(value.p3, map),
structuredCloneInternal(value.p4, map)
);
} catch (error) {
if (nativeRestrictedStructuredClone) {
cloned = nativeRestrictedStructuredClone(value);
} else throwUnpolyfillable(type);
}
break;
case 'FileList':
C = global.DataTransfer;
if (isConstructor(C)) {
dataTransfer = new C();
for (i = 0, length = lengthOfArrayLike(value); i < length; i++) {
dataTransfer.items.add(structuredCloneInternal(value[i], map));
}
cloned = dataTransfer.files;
} else if (nativeRestrictedStructuredClone) {
cloned = nativeRestrictedStructuredClone(value);
} else throwUnpolyfillable(type);
break;
case 'ImageData':
// Safari 9 ImageData is a constructor, but typeof ImageData is 'object'
try {
cloned = new ImageData(
structuredCloneInternal(value.data, map),
value.width,
value.height,
{ colorSpace: value.colorSpace }
);
} catch (error) {
if (nativeRestrictedStructuredClone) {
cloned = nativeRestrictedStructuredClone(value);
} else throwUnpolyfillable(type);
} break;
default:
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(booleanValueOf(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 'ArrayBuffer':
C = global.DataView;
// `ArrayBuffer#slice` is not available in IE10
// `ArrayBuffer#slice` and `DataView` are not available in old FF
if (!C && typeof value.slice != 'function') throwUnpolyfillable(type);
// detached buffers throws in `DataView` and `.slice`
try {
if (typeof value.slice == 'function') {
cloned = value.slice(0);
} else {
length = value.byteLength;
cloned = new ArrayBuffer(length);
source = new C(value);
target = new C(cloned);
for (i = 0; i < length; i++) {
target.setUint8(i, source.getUint8(i));
}
}
} catch (error) {
throw new DOMException('ArrayBuffer is detached', 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) {
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);
} catch (error) {
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);
} catch (error) {
throwUnpolyfillable(type);
} break;
case 'DOMMatrix':
case 'DOMMatrixReadOnly':
C = global[type];
try {
cloned = C.fromMatrix
? C.fromMatrix(value)
: new C(value);
} catch (error) {
throwUnpolyfillable(type);
} break;
case 'AudioData':
case 'VideoFrame':
if (!isCallable(value.clone)) throwUnpolyfillable(type);
try {
cloned = value.clone();
} catch (error) {
throwUncloneable(type);
} break;
case 'File':
try {
cloned = new File([value], value.name, value);
} catch (error) {
throwUnpolyfillable(type);
} break;
case 'CryptoKey':
case 'GPUCompilationMessage':
case 'GPUCompilationInfo':
case 'ImageBitmap':
case 'RTCCertificate':
case 'WebAssembly.Module':
throwUnpolyfillable(type);
// break omitted
default:
throwUncloneable(type);
}
}
mapSet(map, value, cloned);
if (deep) switch (type) {
case 'Array':
case 'Object':
keys = objectKeys(value);
for (i = 0, length = lengthOfArrayLike(keys); i < length; i++) {
key = keys[i];
createProperty(cloned, key, structuredCloneInternal(value[key], map));
} break;
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':
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);
} // break omitted
case 'DOMException':
if (ERROR_STACK_INSTALLABLE) {
createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map));
}
}
return cloned;
};
var PROPER_TRANSFER = nativeStructuredClone && !fails(function () {
// prevent V8 ArrayBufferDetaching protector cell invalidation and performance degradation
// https://github.com/zloirock/core-js/issues/679
if ((IS_DENO && V8 > 92) || (IS_NODE && V8 > 94) || (IS_BROWSER && V8 > 97)) return false;
var buffer = new ArrayBuffer(8);
var clone = nativeStructuredClone(buffer, { transfer: [buffer] });
return buffer.byteLength != 0 || clone.byteLength != 8;
});
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);
var value, type, C, 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);
switch (type) {
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();
} 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 */ }
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);
mapSet(map, value, transferred);
}
};
$({ global: true, enumerable: true, sham: !PROPER_TRANSFER, forced: FORCED_REPLACEMENT }, {
structuredClone: function structuredClone(value /* , { transfer } */) {
var options = validateArgumentsLength(arguments.length, 1) > 1 && arguments[1] != null ? anObject(arguments[1]) : undefined;
var transfer = options ? options.transfer : undefined;
var map;
if (transfer !== undefined) {
map = new Map();
tryToTransfer(transfer, map);
}
return structuredCloneInternal(value, map);
}
});