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.
463 lines
16 KiB
463 lines
16 KiB
3 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 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);
|
||
|
}
|
||
|
});
|