web.structured-clone.js 19 KB

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