'use strict'; 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 isNullOrUndefined = require('../internals/is-null-or-undefined'); 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 MapHelpers = require('../internals/map-helpers'); var SetHelpers = require('../internals/set-helpers'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); var PROPER_TRANSFER = require('../internals/structured-clone-proper-transfer'); var Object = global.Object; var Array = global.Array; 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 Map = MapHelpers.Map; var mapHas = MapHelpers.has; var mapGet = MapHelpers.get; var mapSet = MapHelpers.set; var Set = SetHelpers.Set; var setAdd = SetHelpers.add; var objectKeys = getBuiltin('Object', 'keys'); var push = uncurryThis([].push); var thisBooleanValue = uncurryThis(true.valueOf); var thisNumberValue = uncurryThis(1.0.valueOf); var thisStringValue = uncurryThis(''.valueOf); var thisTimeValue = 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 && test.a.stack === error.stack); }); }; // 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<103 and Safari implementations can't clone errors // https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 // FF103 can clone errors, but `.stack` of clone is an empty string // https://bugzilla.mozilla.org/show_bug.cgi?id=1778762 // FF104+ fixed it on usual errors, but not on DOMExceptions // https://bugzilla.mozilla.org/show_bug.cgi?id=1777321 // 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 // only FF103+ 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 those 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, action) { throw new DOMException((action || 'Cloning') + ' of ' + type + ' cannot be properly polyfilled in this engine', DATA_CLONE_ERROR); }; var tryNativeRestrictedStructuredClone = function (value, type) { if (!nativeRestrictedStructuredClone) throwUnpolyfillable(type); return nativeRestrictedStructuredClone(value); }; var createDataTransfer = function () { var dataTransfer; try { dataTransfer = new global.DataTransfer(); } catch (error) { try { dataTransfer = new global.ClipboardEvent('').clipboardData; } catch (error2) { /* empty */ } } return dataTransfer && dataTransfer.items && dataTransfer.files ? dataTransfer : null; }; var cloneBuffer = function (value, map, $type) { if (mapHas(map, value)) return mapGet(map, value); var type = $type || classof(value); var clone, length, options, source, target, i; if (type === 'SharedArrayBuffer') { if (nativeRestrictedStructuredClone) clone = nativeRestrictedStructuredClone(value); // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original else clone = value; } else { var DataView = global.DataView; // `ArrayBuffer#slice` is not available in IE10 // `ArrayBuffer#slice` and `DataView` are not available in old FF if (!DataView && typeof value.slice != 'function') throwUnpolyfillable('ArrayBuffer'); // detached buffers throws in `DataView` and `.slice` try { if (typeof value.slice == 'function' && !value.resizable) { clone = value.slice(0); } else { length = value.byteLength; options = 'maxByteLength' in value ? { maxByteLength: value.maxByteLength } : undefined; clone = new ArrayBuffer(length, options); source = new DataView(value); target = new DataView(clone); for (i = 0; i < length; i++) { target.setUint8(i, source.getUint8(i)); } } } catch (error) { throw new DOMException('ArrayBuffer is detached', DATA_CLONE_ERROR); } } mapSet(map, value, clone); return clone; }; var cloneView = function (value, type, offset, length, map) { var 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); 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) { 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 C, name, cloned, dataTransfer, i, length, keys, key; switch (type) { case 'Array': cloned = Array(lengthOfArrayLike(value)); break; case 'Object': cloned = {}; break; case 'Map': cloned = new Map(); break; case 'Set': cloned = new Set(); 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(); } break; case 'DOMException': cloned = new DOMException(value.message, value.name); break; case 'ArrayBuffer': case 'SharedArrayBuffer': cloned = transferredBuffers ? new Placeholder(value, type) : cloneBuffer(value, 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': 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); 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) ); } catch (error) { cloned = tryNativeRestrictedStructuredClone(value, type); } break; case 'File': if (nativeRestrictedStructuredClone) try { cloned = nativeRestrictedStructuredClone(value); // NodeJS 20.0.0 bug, https://github.com/nodejs/node/issues/47612 if (classof(cloned) !== type) cloned = undefined; } catch (error) { /* empty */ } if (!cloned) try { cloned = new File([value], value.name, value); } catch (error) { /* empty */ } if (!cloned) throwUnpolyfillable(type); break; case 'FileList': dataTransfer = createDataTransfer(); if (dataTransfer) { for (i = 0, length = lengthOfArrayLike(value); i < length; i++) { dataTransfer.items.add(structuredCloneInternal(value[i], map, transferredBuffers)); } cloned = dataTransfer.files; } else cloned = tryNativeRestrictedStructuredClone(value, type); break; case 'ImageData': // Safari 9 ImageData is a constructor, but typeof ImageData is 'object' try { cloned = new ImageData( structuredCloneInternal(value.data, map, transferredBuffers), value.width, value.height, { colorSpace: value.colorSpace } ); } catch (error) { cloned = tryNativeRestrictedStructuredClone(value, 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(thisBooleanValue(value)); break; case 'Number': cloned = Object(thisNumberValue(value)); break; case 'String': cloned = Object(thisStringValue(value)); break; case 'Date': cloned = new Date(thisTimeValue(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 'CropTarget': case 'CryptoKey': case 'FileSystemDirectoryHandle': case 'FileSystemFileHandle': case 'FileSystemHandle': case 'GPUCompilationInfo': case 'GPUCompilationMessage': case 'ImageBitmap': case 'RTCCertificate': case 'WebAssembly.Module': throwUnpolyfillable(type); // break omitted default: throwUncloneable(type); } } mapSet(map, value, cloned); 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, transferredBuffers)); } break; case 'Map': value.forEach(function (v, k) { mapSet(cloned, structuredCloneInternal(k, map, transferredBuffers), structuredCloneInternal(v, map, transferredBuffers)); }); break; case 'Set': value.forEach(function (v) { setAdd(cloned, structuredCloneInternal(v, map, transferredBuffers)); }); break; case 'Error': createNonEnumerableProperty(cloned, 'message', structuredCloneInternal(value.message, map, transferredBuffers)); if (hasOwn(value, 'cause')) { createNonEnumerableProperty(cloned, 'cause', structuredCloneInternal(value.cause, map, transferredBuffers)); } if (name === 'AggregateError') { cloned.errors = structuredCloneInternal(value.errors, map, transferredBuffers); } // break omitted case 'DOMException': if (ERROR_STACK_INSTALLABLE) { createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map, transferredBuffers)); } } 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 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 buffers = []; var value, type, C, transferred, canvas, context; while (i < length) { value = transfer[i++]; type = classof(value); if (type === 'ArrayBuffer') { push(buffers, value); continue; } if (mapHas(map, value)) throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR); if (PROPER_TRANSFER) { transferred = nativeStructuredClone(value, { transfer: [value] }); } else 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 'MediaSourceHandle': 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); } 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); if (PROPER_TRANSFER) { transferred = nativeStructuredClone(value, { transfer: [value] }); } else { if (!isCallable(value.transfer)) throwUnpolyfillable('ArrayBuffer', TRANSFERRING); transferred = value.transfer(); } mapSet(map, value, transferred); } }; // `structuredClone` method // https://html.spec.whatwg.org/multipage/structured-data.html#dom-structuredclone $({ global: true, enumerable: true, sham: !PROPER_TRANSFER, forced: FORCED_REPLACEMENT }, { 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); // since of an issue with cloning views of transferred buffers, we a forced to transfer / clone them in 2 steps // https://github.com/zloirock/core-js/issues/1265 if (transferredBuffers) { map = new Map(); tryToTransferBuffers(transfer, map); clone = replacePlaceholders(clone, map); } return clone; } });