From fe287012f274e8a19a7d9849ec881aa04a8131ab Mon Sep 17 00:00:00 2001 From: Denis Pushkarev Date: Thu, 21 Sep 2023 07:26:50 +0700 Subject: [PATCH] add `ArrayBuffer.prototype.{ transfer, transferToFixedLength }` and support transferring of `ArrayBuffer`s via `structuredClone` to engines with `MessageChannel` --- CHANGELOG.md | 1 + README.md | 4 +-- .../internals/array-buffer-transfer.js | 34 ++++++++++++------- .../internals/detach-via-message-channel.js | 31 +++++++++++++++++ .../core-js/modules/web.structured-clone.js | 11 +++--- ...t.array-buffer.transfer-to-fixed-length.js | 3 +- .../esnext.array-buffer.transfer.js | 3 +- 7 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 packages/core-js/internals/detach-via-message-channel.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ad5e9c5a22a3..a9e91ecd1d4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Changelog ##### Unreleased +- Added `ArrayBuffer.prototype.{ transfer, transferToFixedLength }` and support transferring of `ArrayBuffer`s via `structuredClone` to engines with `MessageChannel` - Fully forced polyfilling of [the TC39 `Observable` proposal](https://github.com/tc39/proposal-observable) because of incompatibility with [the new WHATWG `Observable` proposal](https://github.com/WICG/observable) - Added an extra workaround of errors with exotic environment objects in `Symbol` polyfill, [#1289](https://github.com/zloirock/core-js/issues/1289) - Compat data improvements: diff --git a/README.md b/README.md index 3235e1d590a5..098679015777 100644 --- a/README.md +++ b/README.md @@ -2391,7 +2391,7 @@ console.log(view.getFloat16(0)); // => 1.3369140625 ``` ##### [`ArrayBuffer.prototype.transfer` and friends](#https://github.com/tc39/proposal-arraybuffer-transfer)[⬆](#index) -Note: **`ArrayBuffer.prototype.{ transfer, transferToFixedLength }` polyfilled only in runtime with native `structuredClone` with `ArrayBuffer` transfer support.** +Note: **`ArrayBuffer.prototype.{ transfer, transferToFixedLength }` polyfilled only in runtime with native `structuredClone` with `ArrayBuffer` transfer or `MessageChannel` support.** Modules [`esnext.array-buffer.detached`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array-buffer.detached.js), [`esnext.array-buffer.transfer`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array-buffer.transfer.js), [`esnext.array-buffer.transfer-to-fixed-length`](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/esnext.array-buffer.transfer-to-fixed-length.js). ```js class ArrayBuffer { @@ -3222,7 +3222,7 @@ structuredClone(new WeakMap()); // => DataCloneError on non-serializable types ``` ##### Caveats when using `structuredClone` polyfill:[⬆](#index) -* `ArrayBuffer` instances and many platform types cannot be transferred in most engines since we have no way to polyfill this behavior, however `.transfer` option works for some platform types. I recommend avoiding this option. +* Many platform types cannot be transferred in most engines since we have no way to polyfill this behavior, however `.transfer` option works for some platform types. I recommend avoiding this option. * Some specific platform types can't be cloned in old engines. Mainly it's very specific types or very old engines, but here are some exceptions. For example, we have no sync way to clone `ImageBitmap` in Safari 14.0- or Firefox 83-, so it's recommended to look to the [polyfill source](https://github.com/zloirock/core-js/blob/master/packages/core-js/modules/web.structured-clone.js) if you wanna clone something specific. #### Base64 utility methods[⬆](#index) diff --git a/packages/core-js/internals/array-buffer-transfer.js b/packages/core-js/internals/array-buffer-transfer.js index 44bc4b3b45b4..dd9edb368e16 100644 --- a/packages/core-js/internals/array-buffer-transfer.js +++ b/packages/core-js/internals/array-buffer-transfer.js @@ -5,12 +5,13 @@ var uncurryThisAccessor = require('../internals/function-uncurry-this-accessor') var toIndex = require('../internals/to-index'); var isDetached = require('../internals/array-buffer-is-detached'); var arrayBufferByteLength = require('../internals/array-buffer-byte-length'); -var PROPER_TRANSFER = require('../internals/structured-clone-proper-transfer'); +var detachViaMessageChannel = require('../internals/detach-via-message-channel'); +var PROPER_STRUCTURED_CLONE_TRANSFER = require('../internals/structured-clone-proper-transfer'); -var TypeError = global.TypeError; var structuredClone = global.structuredClone; var ArrayBuffer = global.ArrayBuffer; var DataView = global.DataView; +var TypeError = global.TypeError; var min = Math.min; var ArrayBufferPrototype = ArrayBuffer.prototype; var DataViewPrototype = DataView.prototype; @@ -20,19 +21,26 @@ var maxByteLength = uncurryThisAccessor(ArrayBufferPrototype, 'maxByteLength', ' var getInt8 = uncurryThis(DataViewPrototype.getInt8); var setInt8 = uncurryThis(DataViewPrototype.setInt8); -module.exports = PROPER_TRANSFER && function (arrayBuffer, newLength, preserveResizability) { +module.exports = (PROPER_STRUCTURED_CLONE_TRANSFER || detachViaMessageChannel) && function (arrayBuffer, newLength, preserveResizability) { var byteLength = arrayBufferByteLength(arrayBuffer); var newByteLength = newLength === undefined ? byteLength : toIndex(newLength); var fixedLength = !isResizable || !isResizable(arrayBuffer); + var newBuffer; if (isDetached(arrayBuffer)) throw new TypeError('ArrayBuffer is detached'); - var newBuffer = structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); - if (byteLength === newByteLength && (preserveResizability || fixedLength)) return newBuffer; - if (byteLength >= newByteLength && (!preserveResizability || fixedLength)) return slice(newBuffer, 0, newByteLength); - var options = (preserveResizability && !fixedLength) && maxByteLength ? { maxByteLength: maxByteLength(newBuffer) } : undefined; - var newNewBuffer = new ArrayBuffer(newByteLength, options); - var a = new DataView(newBuffer); - var b = new DataView(newNewBuffer); - var copyLength = min(newByteLength, byteLength); - for (var i = 0; i < copyLength; i++) setInt8(b, i, getInt8(a, i)); - return newNewBuffer; + if (PROPER_STRUCTURED_CLONE_TRANSFER) { + arrayBuffer = structuredClone(arrayBuffer, { transfer: [arrayBuffer] }); + if (byteLength === newByteLength && (preserveResizability || fixedLength)) return arrayBuffer; + } + if (byteLength >= newByteLength && (!preserveResizability || fixedLength)) { + newBuffer = slice(arrayBuffer, 0, newByteLength); + } else { + var options = preserveResizability && !fixedLength && maxByteLength ? { maxByteLength: maxByteLength(arrayBuffer) } : undefined; + newBuffer = new ArrayBuffer(newByteLength, options); + var a = new DataView(arrayBuffer); + var b = new DataView(newBuffer); + var copyLength = min(newByteLength, byteLength); + for (var i = 0; i < copyLength; i++) setInt8(b, i, getInt8(a, i)); + } + if (!PROPER_STRUCTURED_CLONE_TRANSFER) detachViaMessageChannel(arrayBuffer); + return newBuffer; }; diff --git a/packages/core-js/internals/detach-via-message-channel.js b/packages/core-js/internals/detach-via-message-channel.js new file mode 100644 index 000000000000..6f76f38d89e6 --- /dev/null +++ b/packages/core-js/internals/detach-via-message-channel.js @@ -0,0 +1,31 @@ +'use strict'; +var global = require('../internals/global'); +var tryNodeRequire = require('../internals/try-node-require'); + +var $ArrayBuffer = global.ArrayBuffer; +var $MessageChannel = global.MessageChannel; +var detach = false; +var WorkerThreads, channel, buffer, $detach; + +if ($ArrayBuffer) try { + if (!$MessageChannel) { + WorkerThreads = tryNodeRequire('worker_threads'); + if (WorkerThreads) $MessageChannel = WorkerThreads.MessageChannel; + } + + if ($MessageChannel) { + channel = new $MessageChannel(); + buffer = new $ArrayBuffer(2); + + $detach = function (transferable) { + channel.port1.postMessage(null, [transferable]); + }; + + if (buffer.byteLength === 2) { + $detach(buffer); + if (buffer.byteLength === 0) detach = $detach; + } + } +} catch (error) { /* empty */ } + +module.exports = detach; diff --git a/packages/core-js/modules/web.structured-clone.js b/packages/core-js/modules/web.structured-clone.js index 062f640b7643..7f8ad8992d35 100644 --- a/packages/core-js/modules/web.structured-clone.js +++ b/packages/core-js/modules/web.structured-clone.js @@ -22,8 +22,9 @@ var validateArgumentsLength = require('../internals/validate-arguments-length'); var getRegExpFlags = require('../internals/regexp-get-flags'); var MapHelpers = require('../internals/map-helpers'); var SetHelpers = require('../internals/set-helpers'); +var arrayBufferTransfer = require('../internals/array-buffer-transfer'); var ERROR_STACK_INSTALLABLE = require('../internals/error-stack-installable'); -var PROPER_TRANSFER = require('../internals/structured-clone-proper-transfer'); +var PROPER_STRUCTURED_CLONE_TRANSFER = require('../internals/structured-clone-proper-transfer'); var Object = global.Object; var Array = global.Array; @@ -548,7 +549,7 @@ var tryToTransfer = function (rawTransfer, map) { if (mapHas(map, value)) throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR); - if (PROPER_TRANSFER) { + if (PROPER_STRUCTURED_CLONE_TRANSFER) { transferred = nativeStructuredClone(value, { transfer: [value] }); } else switch (type) { case 'ImageBitmap': @@ -596,8 +597,8 @@ var tryToTransferBuffers = function (transfer, map) { if (mapHas(map, value)) throw new DOMException('Duplicate transferable', DATA_CLONE_ERROR); - if (PROPER_TRANSFER) { - transferred = nativeStructuredClone(value, { transfer: [value] }); + if (arrayBufferTransfer) { + transferred = arrayBufferTransfer(value, undefined, true); } else { if (!isCallable(value.transfer)) throwUnpolyfillable('ArrayBuffer', TRANSFERRING); transferred = value.transfer(); @@ -609,7 +610,7 @@ var tryToTransferBuffers = function (transfer, map) { // `structuredClone` method // https://html.spec.whatwg.org/multipage/structured-data.html#dom-structuredclone -$({ global: true, enumerable: true, sham: !PROPER_TRANSFER, forced: FORCED_REPLACEMENT }, { +$({ global: true, enumerable: true, sham: !PROPER_STRUCTURED_CLONE_TRANSFER, forced: FORCED_REPLACEMENT }, { structuredClone: function structuredClone(value /* , { transfer } */) { var options = validateArgumentsLength(arguments.length, 1) > 1 && !isNullOrUndefined(arguments[1]) ? anObject(arguments[1]) : undefined; var transfer = options ? options.transfer : undefined; diff --git a/tests/unit-global/esnext.array-buffer.transfer-to-fixed-length.js b/tests/unit-global/esnext.array-buffer.transfer-to-fixed-length.js index 556986358951..50643858ee69 100644 --- a/tests/unit-global/esnext.array-buffer.transfer-to-fixed-length.js +++ b/tests/unit-global/esnext.array-buffer.transfer-to-fixed-length.js @@ -11,7 +11,8 @@ if (transferToFixedLength) QUnit.test('ArrayBuffer#transferToFixedLength', asser assert.looksNative(transferToFixedLength); assert.nonEnumerable(ArrayBuffer.prototype, 'transferToFixedLength'); - const DETACHED = 'detached' in ArrayBuffer.prototype; + // works incorrectly in ancient webkit + const DETACHED = false; // 'detached' in ArrayBuffer.prototype; const array = [0, 1, 2, 3, 4, 5, 6, 7]; diff --git a/tests/unit-global/esnext.array-buffer.transfer.js b/tests/unit-global/esnext.array-buffer.transfer.js index 5b582a9ef951..bc70726c85e2 100644 --- a/tests/unit-global/esnext.array-buffer.transfer.js +++ b/tests/unit-global/esnext.array-buffer.transfer.js @@ -11,7 +11,8 @@ if (transfer) QUnit.test('ArrayBuffer#transfer', assert => { assert.looksNative(transfer); assert.nonEnumerable(ArrayBuffer.prototype, 'transfer'); - const DETACHED = 'detached' in ArrayBuffer.prototype; + // works incorrectly in ancient webkit + const DETACHED = false; // 'detached' in ArrayBuffer.prototype; const array = [0, 1, 2, 3, 4, 5, 6, 7];