Skip to content

Commit f2c0258

Browse files
jasnelltargos
authored andcommitted
lib: add support for JSTransferable as a mixin
Adds a new `makeTransferable()` utility that can construct a `JSTransferable` object that does not directly extend the `JSTransferable` JavaScript class. Because JavaScript does not support multiple inheritance, it is not possible (without help) to implement a class that extends both `JSTransferable` and, for instance, `EventTarget` without incurring a significant additional complexity and performance cost by making all `EventTarget` instances extend `JSTransferable`... That is, we *don't* want: ```js class EventTarget extends JSTransferable { ... } ``` The `makeTransferable()` allows us to create objects that are backed internally by `JSTransferable` without having to actually extend it by leveraging the magic of `Reflect.construct()`. ```js const { JSTransferable, kClone, kDeserialize, kConstructor, makeTransferable, } = require('internal/worker/js_transferable'); class E { constructor(b) { this.b = b; } } class F extends E { [kClone]() { /** ... **/ } [kDeserialize]() { /** ... **/ } static [kConstructor]() { return makeTransferable(F); } } const f = makeTransferable(F, 1); f instanceof F; // true f instanceof E; // true f instanceof JSTransferable; // false const mc = new MessageChannel(); mc.port1.onmessage = ({ data }) => { data instanceof F; // true data instanceof E; // true data instanceof JSTransferable; // false }; mc.port2.postMessage(f); // works! ``` The additional `internal/test/transfer.js` file is required for the test because successfully deserializing transferable classes requires that they be located in `lib/internal` for now. Signed-off-by: James M Snell <[email protected]> PR-URL: #38383 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Khaidi Chu <[email protected]>
1 parent 1bc47a4 commit f2c0258

File tree

4 files changed

+86
-2
lines changed

4 files changed

+86
-2
lines changed

lib/internal/test/transfer.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
const {
4+
makeTransferable,
5+
kClone,
6+
kDeserialize,
7+
} = require('internal/worker/js_transferable');
8+
9+
process.emitWarning(
10+
'These APIs are for internal testing only. Do not use them.',
11+
'internal/test/transfer');
12+
13+
// Used as part of parallel/test-messaging-maketransferable.
14+
// This has to exist within the lib/internal/ path in order
15+
// for deserialization to work.
16+
17+
class E {
18+
constructor(b) {
19+
this.b = b;
20+
}
21+
}
22+
23+
class F extends E {
24+
constructor(b) {
25+
super(b);
26+
/* eslint-disable-next-line no-constructor-return */
27+
return makeTransferable(this);
28+
}
29+
30+
[kClone]() {
31+
return {
32+
data: { b: this.b },
33+
deserializeInfo: 'internal/test/transfer:F'
34+
};
35+
}
36+
37+
[kDeserialize]({ b }) {
38+
this.b = b;
39+
}
40+
}
41+
42+
module.exports = { E, F };

lib/internal/worker/js_transferable.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
'use strict';
22
const {
33
Error,
4+
ObjectDefineProperties,
5+
ObjectGetOwnPropertyDescriptors,
6+
ObjectGetPrototypeOf,
7+
ObjectSetPrototypeOf,
8+
ReflectConstruct,
49
StringPrototypeSplit,
510
} = primordials;
611
const {
@@ -22,21 +27,30 @@ function setup() {
2227
const { 0: module, 1: ctor } = StringPrototypeSplit(deserializeInfo, ':');
2328
const Ctor = require(module)[ctor];
2429
if (typeof Ctor !== 'function' ||
25-
!(Ctor.prototype instanceof JSTransferable)) {
30+
typeof Ctor.prototype[messaging_deserialize_symbol] !== 'function') {
2631
// Not one of the official errors because one should not be able to get
2732
// here without messing with Node.js internals.
2833
// eslint-disable-next-line no-restricted-syntax
2934
throw new Error(`Unknown deserialize spec ${deserializeInfo}`);
3035
}
36+
3137
return new Ctor();
3238
});
3339
}
3440

41+
function makeTransferable(obj) {
42+
const inst = ReflectConstruct(JSTransferable, [], obj.constructor);
43+
ObjectDefineProperties(inst, ObjectGetOwnPropertyDescriptors(obj));
44+
ObjectSetPrototypeOf(inst, ObjectGetPrototypeOf(obj));
45+
return inst;
46+
}
47+
3548
module.exports = {
49+
makeTransferable,
3650
setup,
3751
JSTransferable,
3852
kClone: messaging_clone_symbol,
3953
kDeserialize: messaging_deserialize_symbol,
4054
kTransfer: messaging_transfer_symbol,
41-
kTransferList: messaging_transfer_list_symbol
55+
kTransferList: messaging_transfer_list_symbol,
4256
};

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@
229229
'lib/internal/source_map/source_map.js',
230230
'lib/internal/source_map/source_map_cache.js',
231231
'lib/internal/test/binding.js',
232+
'lib/internal/test/transfer.js',
232233
'lib/internal/timers.js',
233234
'lib/internal/tls.js',
234235
'lib/internal/trace_events_async_hooks.js',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
4+
const common = require('../common');
5+
6+
const assert = require('assert');
7+
const {
8+
JSTransferable,
9+
} = require('internal/worker/js_transferable');
10+
const { E, F } = require('internal/test/transfer');
11+
12+
// Tests that F is transferable even tho it does not directly,
13+
// observably extend the JSTransferable class.
14+
15+
const mc = new MessageChannel();
16+
17+
mc.port1.onmessageerror = common.mustNotCall();
18+
19+
mc.port1.onmessage = common.mustCall(({ data }) => {
20+
assert(!(data instanceof JSTransferable));
21+
assert(data instanceof F);
22+
assert(data instanceof E);
23+
assert.strictEqual(data.b, 1);
24+
mc.port1.close();
25+
});
26+
27+
mc.port2.postMessage(new F(1));

0 commit comments

Comments
 (0)