web.structured-clone.js 18 KB

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