UNPKG

19.1 kBJavaScriptView Raw
1'use strict';
2var IS_PURE = require('../internals/is-pure');
3var $ = require('../internals/export');
4var global = require('../internals/global');
5var getBuiltIn = require('../internals/get-built-in');
6var uncurryThis = require('../internals/function-uncurry-this');
7var fails = require('../internals/fails');
8var uid = require('../internals/uid');
9var isCallable = require('../internals/is-callable');
10var isConstructor = require('../internals/is-constructor');
11var isNullOrUndefined = require('../internals/is-null-or-undefined');
12var isObject = require('../internals/is-object');
13var isSymbol = require('../internals/is-symbol');
14var iterate = require('../internals/iterate');
15var anObject = require('../internals/an-object');
16var classof = require('../internals/classof');
17var hasOwn = require('../internals/has-own-property');
18var createProperty = require('../internals/create-property');
19var createNonEnumerableProperty = require('../internals/create-non-enumerable-property');
20var lengthOfArrayLike = require('../internals/length-of-array-like');
21var validateArgumentsLength = require('../internals/validate-arguments-length');
22var getRegExpFlags = require('../internals/regexp-get-flags');
23var MapHelpers = require('../internals/map-helpers');
24var SetHelpers = require('../internals/set-helpers');
25var setIterate = require('../internals/set-iterate');
26var detachTransferable = require('../internals/detach-transferable');
27var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable');
28var PROPER_STRUCTURED_CLONE_TRANSFER = require('../internals/structured-clone-proper-transfer');
29
30var Object = global.Object;
31var Array = global.Array;
32var Date = global.Date;
33var Error = global.Error;
34var TypeError = global.TypeError;
35var PerformanceMark = global.PerformanceMark;
36var DOMException = getBuiltIn('DOMException');
37var Map = MapHelpers.Map;
38var mapHas = MapHelpers.has;
39var mapGet = MapHelpers.get;
40var mapSet = MapHelpers.set;
41var Set = SetHelpers.Set;
42var setAdd = SetHelpers.add;
43var setHas = SetHelpers.has;
44var objectKeys = getBuiltIn('Object', 'keys');
45var push = uncurryThis([].push);
46var thisBooleanValue = uncurryThis(true.valueOf);
47var thisNumberValue = uncurryThis(1.0.valueOf);
48var thisStringValue = uncurryThis(''.valueOf);
49var thisTimeValue = uncurryThis(Date.prototype.getTime);
50var PERFORMANCE_MARK = uid('structuredClone');
51var DATA_CLONE_ERROR = 'DataCloneError';
52var TRANSFERRING = 'Transferring';
53
54var checkBasicSemantic = function (structuredCloneImplementation) {
55 return !fails(function () {
56 var set1 = new global.Set([7]);
57 var set2 = structuredCloneImplementation(set1);
58 var number = structuredCloneImplementation(Object(7));
59 return set2 === set1 || !set2.has(7) || !isObject(number) || +number !== 7;
60 }) && structuredCloneImplementation;
61};
62
63var checkErrorsCloning = function (structuredCloneImplementation, $Error) {
64 return !fails(function () {
65 var error = new $Error();
66 var test = structuredCloneImplementation({ a: error, b: error });
67 return !(test && test.a === test.b && test.a instanceof $Error && test.a.stack === error.stack);
68 });
69};
70
71// https://github.com/whatwg/html/pull/5749
72var checkNewErrorsCloningSemantic = function (structuredCloneImplementation) {
73 return !fails(function () {
74 var test = structuredCloneImplementation(new global.AggregateError([1], PERFORMANCE_MARK, { cause: 3 }));
75 return test.name !== 'AggregateError' || test.errors[0] !== 1 || test.message !== PERFORMANCE_MARK || test.cause !== 3;
76 });
77};
78
79// FF94+, Safari 15.4+, Chrome 98+, NodeJS 17.0+, Deno 1.13+
80// FF<103 and Safari implementations can't clone errors
81// https://bugzilla.mozilla.org/show_bug.cgi?id=1556604
82// FF103 can clone errors, but `.stack` of clone is an empty string
83// https://bugzilla.mozilla.org/show_bug.cgi?id=1778762
84// FF104+ fixed it on usual errors, but not on DOMExceptions
85// https://bugzilla.mozilla.org/show_bug.cgi?id=1777321
86// Chrome <102 returns `null` if cloned object contains multiple references to one error
87// https://bugs.chromium.org/p/v8/issues/detail?id=12542
88// NodeJS implementation can't clone DOMExceptions
89// https://github.com/nodejs/node/issues/41038
90// only FF103+ supports new (html/5749) error cloning semantic
91var nativeStructuredClone = global.structuredClone;
92
93var FORCED_REPLACEMENT = IS_PURE
94 || !checkErrorsCloning(nativeStructuredClone, Error)
95 || !checkErrorsCloning(nativeStructuredClone, DOMException)
96 || !checkNewErrorsCloningSemantic(nativeStructuredClone);
97
98// Chrome 82+, Safari 14.1+, Deno 1.11+
99// Chrome 78-81 implementation swaps `.name` and `.message` of cloned `DOMException`
100// Chrome returns `null` if cloned object contains multiple references to one error
101// Safari 14.1 implementation doesn't clone some `RegExp` flags, so requires a workaround
102// Safari implementation can't clone errors
103// Deno 1.2-1.10 implementations too naive
104// NodeJS 16.0+ does not have `PerformanceMark` constructor
105// NodeJS <17.2 structured cloning implementation from `performance.mark` is too naive
106// and can't clone, for example, `RegExp` or some boxed primitives
107// https://github.com/nodejs/node/issues/40840
108// no one of those implementations supports new (html/5749) error cloning semantic
109var structuredCloneFromMark = !nativeStructuredClone && checkBasicSemantic(function (value) {
110 return new PerformanceMark(PERFORMANCE_MARK, { detail: value }).detail;
111});
112
113var nativeRestrictedStructuredClone = checkBasicSemantic(nativeStructuredClone) || structuredCloneFromMark;
114
115var throwUncloneable = function (type) {
116 throw new DOMException('Uncloneable type: ' + type, DATA_CLONE_ERROR);
117};
118
119var throwUnpolyfillable = function (type, action) {
120 throw new DOMException((action || 'Cloning') + ' of ' + type + ' cannot be properly polyfilled in this engine', DATA_CLONE_ERROR);
121};
122
123var tryNativeRestrictedStructuredClone = function (value, type) {
124 if (!nativeRestrictedStructuredClone) throwUnpolyfillable(type);
125 return nativeRestrictedStructuredClone(value);
126};
127
128var createDataTransfer = function () {
129 var dataTransfer;
130 try {
131 dataTransfer = new global.DataTransfer();
132 } catch (error) {
133 try {
134 dataTransfer = new global.ClipboardEvent('').clipboardData;
135 } catch (error2) { /* empty */ }
136 }
137 return dataTransfer && dataTransfer.items && dataTransfer.files ? dataTransfer : null;
138};
139
140var cloneBuffer = function (value, map, $type) {
141 if (mapHas(map, value)) return mapGet(map, value);
142
143 var type = $type || classof(value);
144 var clone, length, options, source, target, i;
145
146 if (type === 'SharedArrayBuffer') {
147 if (nativeRestrictedStructuredClone) clone = nativeRestrictedStructuredClone(value);
148 // SharedArrayBuffer should use shared memory, we can't polyfill it, so return the original
149 else clone = value;
150 } else {
151 var DataView = global.DataView;
152
153 // `ArrayBuffer#slice` is not available in IE10
154 // `ArrayBuffer#slice` and `DataView` are not available in old FF
155 if (!DataView && !isCallable(value.slice)) throwUnpolyfillable('ArrayBuffer');
156 // detached buffers throws in `DataView` and `.slice`
157 try {
158 if (isCallable(value.slice) && !value.resizable) {
159 clone = value.slice(0);
160 } else {
161 length = value.byteLength;
162 options = 'maxByteLength' in value ? { maxByteLength: value.maxByteLength } : undefined;
163 // eslint-disable-next-line es/no-resizable-and-growable-arraybuffers -- safe
164 clone = new ArrayBuffer(length, options);
165 source = new DataView(value);
166 target = new DataView(clone);
167 for (i = 0; i < length; i++) {
168 target.setUint8(i, source.getUint8(i));
169 }
170 }
171 } catch (error) {
172 throw new DOMException('ArrayBuffer is detached', DATA_CLONE_ERROR);
173 }
174 }
175
176 mapSet(map, value, clone);
177
178 return clone;
179};
180
181var cloneView = function (value, type, offset, length, map) {
182 var C = global[type];
183 // in some old engines like Safari 9, typeof C is 'object'
184 // on Uint8ClampedArray or some other constructors
185 if (!isObject(C)) throwUnpolyfillable(type);
186 return new C(cloneBuffer(value.buffer, map), offset, length);
187};
188
189var structuredCloneInternal = function (value, map) {
190 if (isSymbol(value)) throwUncloneable('Symbol');
191 if (!isObject(value)) return value;
192 // effectively preserves circular references
193 if (map) {
194 if (mapHas(map, value)) return mapGet(map, value);
195 } else map = new Map();
196
197 var type = classof(value);
198 var C, name, cloned, dataTransfer, i, length, keys, key;
199
200 switch (type) {
201 case 'Array':
202 cloned = Array(lengthOfArrayLike(value));
203 break;
204 case 'Object':
205 cloned = {};
206 break;
207 case 'Map':
208 cloned = new Map();
209 break;
210 case 'Set':
211 cloned = new Set();
212 break;
213 case 'RegExp':
214 // in this block because of a Safari 14.1 bug
215 // old FF does not clone regexes passed to the constructor, so get the source and flags directly
216 cloned = new RegExp(value.source, getRegExpFlags(value));
217 break;
218 case 'Error':
219 name = value.name;
220 switch (name) {
221 case 'AggregateError':
222 cloned = new (getBuiltIn(name))([]);
223 break;
224 case 'EvalError':
225 case 'RangeError':
226 case 'ReferenceError':
227 case 'SuppressedError':
228 case 'SyntaxError':
229 case 'TypeError':
230 case 'URIError':
231 cloned = new (getBuiltIn(name))();
232 break;
233 case 'CompileError':
234 case 'LinkError':
235 case 'RuntimeError':
236 cloned = new (getBuiltIn('WebAssembly', name))();
237 break;
238 default:
239 cloned = new Error();
240 }
241 break;
242 case 'DOMException':
243 cloned = new DOMException(value.message, value.name);
244 break;
245 case 'ArrayBuffer':
246 case 'SharedArrayBuffer':
247 cloned = cloneBuffer(value, map, type);
248 break;
249 case 'DataView':
250 case 'Int8Array':
251 case 'Uint8Array':
252 case 'Uint8ClampedArray':
253 case 'Int16Array':
254 case 'Uint16Array':
255 case 'Int32Array':
256 case 'Uint32Array':
257 case 'Float16Array':
258 case 'Float32Array':
259 case 'Float64Array':
260 case 'BigInt64Array':
261 case 'BigUint64Array':
262 length = type === 'DataView' ? value.byteLength : value.length;
263 cloned = cloneView(value, type, value.byteOffset, length, map);
264 break;
265 case 'DOMQuad':
266 try {
267 cloned = new DOMQuad(
268 structuredCloneInternal(value.p1, map),
269 structuredCloneInternal(value.p2, map),
270 structuredCloneInternal(value.p3, map),
271 structuredCloneInternal(value.p4, map)
272 );
273 } catch (error) {
274 cloned = tryNativeRestrictedStructuredClone(value, type);
275 }
276 break;
277 case 'File':
278 if (nativeRestrictedStructuredClone) try {
279 cloned = nativeRestrictedStructuredClone(value);
280 // NodeJS 20.0.0 bug, https://github.com/nodejs/node/issues/47612
281 if (classof(cloned) !== type) cloned = undefined;
282 } catch (error) { /* empty */ }
283 if (!cloned) try {
284 cloned = new File([value], value.name, value);
285 } catch (error) { /* empty */ }
286 if (!cloned) throwUnpolyfillable(type);
287 break;
288 case 'FileList':
289 dataTransfer = createDataTransfer();
290 if (dataTransfer) {
291 for (i = 0, length = lengthOfArrayLike(value); i < length; i++) {
292 dataTransfer.items.add(structuredCloneInternal(value[i], map));
293 }
294 cloned = dataTransfer.files;
295 } else cloned = tryNativeRestrictedStructuredClone(value, type);
296 break;
297 case 'ImageData':
298 // Safari 9 ImageData is a constructor, but typeof ImageData is 'object'
299 try {
300 cloned = new ImageData(
301 structuredCloneInternal(value.data, map),
302 value.width,
303 value.height,
304 { colorSpace: value.colorSpace }
305 );
306 } catch (error) {
307 cloned = tryNativeRestrictedStructuredClone(value, type);
308 } break;
309 default:
310 if (nativeRestrictedStructuredClone) {
311 cloned = nativeRestrictedStructuredClone(value);
312 } else switch (type) {
313 case 'BigInt':
314 // can be a 3rd party polyfill
315 cloned = Object(value.valueOf());
316 break;
317 case 'Boolean':
318 cloned = Object(thisBooleanValue(value));
319 break;
320 case 'Number':
321 cloned = Object(thisNumberValue(value));
322 break;
323 case 'String':
324 cloned = Object(thisStringValue(value));
325 break;
326 case 'Date':
327 cloned = new Date(thisTimeValue(value));
328 break;
329 case 'Blob':
330 try {
331 cloned = value.slice(0, value.size, value.type);
332 } catch (error) {
333 throwUnpolyfillable(type);
334 } break;
335 case 'DOMPoint':
336 case 'DOMPointReadOnly':
337 C = global[type];
338 try {
339 cloned = C.fromPoint
340 ? C.fromPoint(value)
341 : new C(value.x, value.y, value.z, value.w);
342 } catch (error) {
343 throwUnpolyfillable(type);
344 } break;
345 case 'DOMRect':
346 case 'DOMRectReadOnly':
347 C = global[type];
348 try {
349 cloned = C.fromRect
350 ? C.fromRect(value)
351 : new C(value.x, value.y, value.width, value.height);
352 } catch (error) {
353 throwUnpolyfillable(type);
354 } break;
355 case 'DOMMatrix':
356 case 'DOMMatrixReadOnly':
357 C = global[type];
358 try {
359 cloned = C.fromMatrix
360 ? C.fromMatrix(value)
361 : new C(value);
362 } catch (error) {
363 throwUnpolyfillable(type);
364 } break;
365 case 'AudioData':
366 case 'VideoFrame':
367 if (!isCallable(value.clone)) throwUnpolyfillable(type);
368 try {
369 cloned = value.clone();
370 } catch (error) {
371 throwUncloneable(type);
372 } break;
373 case 'CropTarget':
374 case 'CryptoKey':
375 case 'FileSystemDirectoryHandle':
376 case 'FileSystemFileHandle':
377 case 'FileSystemHandle':
378 case 'GPUCompilationInfo':
379 case 'GPUCompilationMessage':
380 case 'ImageBitmap':
381 case 'RTCCertificate':
382 case 'WebAssembly.Module':
383 throwUnpolyfillable(type);
384 // break omitted
385 default:
386 throwUncloneable(type);
387 }
388 }
389
390 mapSet(map, value, cloned);
391
392 switch (type) {
393 case 'Array':
394 case 'Object':
395 keys = objectKeys(value);
396 for (i = 0, length = lengthOfArrayLike(keys); i < length; i++) {
397 key = keys[i];
398 createProperty(cloned, key, structuredCloneInternal(value[key], map));
399 } break;
400 case 'Map':
401 value.forEach(function (v, k) {
402 mapSet(cloned, structuredCloneInternal(k, map), structuredCloneInternal(v, map));
403 });
404 break;
405 case 'Set':
406 value.forEach(function (v) {
407 setAdd(cloned, structuredCloneInternal(v, map));
408 });
409 break;
410 case 'Error':
411 createNonEnumerableProperty(cloned, 'message', structuredCloneInternal(value.message, map));
412 if (hasOwn(value, 'cause')) {
413 createNonEnumerableProperty(cloned, 'cause', structuredCloneInternal(value.cause, map));
414 }
415 if (name === 'AggregateError') {
416 cloned.errors = structuredCloneInternal(value.errors, map);
417 } else if (name === 'SuppressedError') {
418 cloned.error = structuredCloneInternal(value.error, map);
419 cloned.suppressed = structuredCloneInternal(value.suppressed, map);
420 } // break omitted
421 case 'DOMException':
422 if (ERROR_STACK_INSTALLABLE) {
423 createNonEnumerableProperty(cloned, 'stack', structuredCloneInternal(value.stack, map));
424 }
425 }
426
427 return cloned;
428};
429
430var tryToTransfer = function (rawTransfer, map) {
431 if (!isObject(rawTransfer)) throw new TypeError('Transfer option cannot be converted to a sequence');
432
433 var transfer = [];
434
435 iterate(rawTransfer, function (value) {
436 push(transfer, anObject(value));
437 });
438
439 var i = 0;
440 var length = lengthOfArrayLike(transfer);
441 var buffers = new Set();
442 var value, type, C, transferred, canvas, context;
443
444 while (i < length) {
445 value = transfer[i++];
446
447 type = classof(value);
448
449 if (type === 'ArrayBuffer' ? setHas(buffers, value) : mapHas(map, value)) {
450 throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR);
451 }
452
453 if (type === 'ArrayBuffer') {
454 setAdd(buffers, value);
455 continue;
456 }
457
458 if (PROPER_STRUCTURED_CLONE_TRANSFER) {
459 transferred = nativeStructuredClone(value, { transfer: [value] });
460 } else switch (type) {
461 case 'ImageBitmap':
462 C = global.OffscreenCanvas;
463 if (!isConstructor(C)) throwUnpolyfillable(type, TRANSFERRING);
464 try {
465 canvas = new C(value.width, value.height);
466 context = canvas.getContext('bitmaprenderer');
467 context.transferFromImageBitmap(value);
468 transferred = canvas.transferToImageBitmap();
469 } catch (error) { /* empty */ }
470 break;
471 case 'AudioData':
472 case 'VideoFrame':
473 if (!isCallable(value.clone) || !isCallable(value.close)) throwUnpolyfillable(type, TRANSFERRING);
474 try {
475 transferred = value.clone();
476 value.close();
477 } catch (error) { /* empty */ }
478 break;
479 case 'MediaSourceHandle':
480 case 'MessagePort':
481 case 'OffscreenCanvas':
482 case 'ReadableStream':
483 case 'TransformStream':
484 case 'WritableStream':
485 throwUnpolyfillable(type, TRANSFERRING);
486 }
487
488 if (transferred === undefined) throw new DOMException('This object cannot be transferred: ' + type, DATA_CLONE_ERROR);
489
490 mapSet(map, value, transferred);
491 }
492
493 return buffers;
494};
495
496var detachBuffers = function (buffers) {
497 setIterate(buffers, function (buffer) {
498 if (PROPER_STRUCTURED_CLONE_TRANSFER) {
499 nativeRestrictedStructuredClone(buffer, { transfer: [buffer] });
500 } else if (isCallable(buffer.transfer)) {
501 buffer.transfer();
502 } else if (detachTransferable) {
503 detachTransferable(buffer);
504 } else {
505 throwUnpolyfillable('ArrayBuffer', TRANSFERRING);
506 }
507 });
508};
509
510// `structuredClone` method
511// https://html.spec.whatwg.org/multipage/structured-data.html#dom-structuredclone
512$({ global: true, enumerable: true, sham: !PROPER_STRUCTURED_CLONE_TRANSFER, forced: FORCED_REPLACEMENT }, {
513 structuredClone: function structuredClone(value /* , { transfer } */) {
514 var options = validateArgumentsLength(arguments.length, 1) > 1 && !isNullOrUndefined(arguments[1]) ? anObject(arguments[1]) : undefined;
515 var transfer = options ? options.transfer : undefined;
516 var map, buffers;
517
518 if (transfer !== undefined) {
519 map = new Map();
520 buffers = tryToTransfer(transfer, map);
521 }
522
523 var clone = structuredCloneInternal(value, map);
524
525 // since of an issue with cloning views of transferred buffers, we a forced to detach them later
526 // https://github.com/zloirock/core-js/issues/1265
527 if (buffers) detachBuffers(buffers);
528
529 return clone;
530 }
531});