web.structured-clone.js 16 KB

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