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.
462 lines
16 KiB
462 lines
16 KiB
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 regExpFlags = require('../internals/regexp-flags'); |
|
var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); |
|
|
|
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 getFlags = uncurryThis(regExpFlags); |
|
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; |
|
}; |
|
|
|
// https://github.com/whatwg/html/pull/5749 |
|
var checkNewErrorsSemantic = 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; |
|
}) && structuredCloneImplementation; |
|
}; |
|
|
|
// FF94+, Safari TP134+, Chrome Canary 98+, NodeJS 17.0+, Deno 1.13+ |
|
// current FF and Safari implementations can't clone errors |
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1556604 |
|
// no one of current implementations supports new (html/5749) error cloning semantic |
|
var nativeStructuredClone = global.structuredClone; |
|
|
|
var FORCED_REPLACEMENT = IS_PURE || !checkNewErrorsSemantic(nativeStructuredClone); |
|
|
|
// Chrome 82+, Safari 14.1+, Deno 1.11+ |
|
// Chrome 78-81 implementation swaps `.name` and `.message` of cloned `DOMException` |
|
// Safari 14.1 implementation doesn't clone some `RegExp` flags, so requires a workaround |
|
// current Safari implementation can't clone errors |
|
// Deno 1.2-1.10 implementations too naive |
|
// NodeJS 16.0+ does not have `PerformanceMark` constructor, 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, 'flags' in value ? value.flags : getFlags(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 () { |
|
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 ? 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); |
|
} |
|
});
|
|
|