Skip to content

Commit

Permalink
add ArrayBuffer.prototype.{ transfer, transferToFixedLength } and s…
Browse files Browse the repository at this point in the history
…upport transferring of `ArrayBuffer`s via `structuredClone` to engines with `MessageChannel`
  • Loading branch information
zloirock committed Sep 21, 2023
1 parent 8f87b9e commit fe28701
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down
34 changes: 21 additions & 13 deletions packages/core-js/internals/array-buffer-transfer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
};
31 changes: 31 additions & 0 deletions packages/core-js/internals/detach-via-message-channel.js
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 6 additions & 5 deletions packages/core-js/modules/web.structured-clone.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down
3 changes: 2 additions & 1 deletion tests/unit-global/esnext.array-buffer.transfer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down

0 comments on commit fe28701

Please sign in to comment.