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); } });