Skip to content

Commit 230cd0c

Browse files
panvaaduh95
authored andcommitted
worker: fix postMessage overload resolution
Web IDL overload resolution treats functions as objects, accepts a null iterator as missing, and retrieves the iterator method only once. Reuse that method during sequence conversion in both postMessage entry points. Signed-off-by: Filip Skokan <panva.ip@gmail.com> Assisted-by: Codex PR-URL: #66354 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Aviv Keller <me@aviv.sh>
1 parent 33caead commit 230cd0c

3 files changed

Lines changed: 65 additions & 6 deletions

File tree

‎lib/internal/webidl.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -815,16 +815,19 @@ function createDictionaryConverter(
815815
* @returns {Converter}
816816
*/
817817
function createSequenceConverter(converter) {
818-
return function(V, options = kEmptyObject) {
818+
return function(V, options = kEmptyObject, method = undefined) {
819819
// Web IDL sequence conversion step 1: require an ECMA-262 Object.
820820
if (type(V) !== 'Object') {
821821
throw makeException(
822822
'cannot be converted to sequence.',
823823
options);
824824
}
825825

826-
// Step 2: GetMethod(V, %Symbol.iterator%).
827-
const method = V[SymbolIterator];
826+
// Step 2: GetMethod(V, %Symbol.iterator%). Overload resolution can
827+
// supply the method it already retrieved, avoiding a second lookup.
828+
if (method === undefined) {
829+
method = V[SymbolIterator];
830+
}
828831
// Step 3: throw if the iterator method is undefined, null, or not callable.
829832
if (typeof method !== 'function') {
830833
throw makeException(

‎lib/internal/webworker.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,12 @@ function fetchClassicScriptSourceSync(url, blob) {
319319
// sequence<object> overload, everything else is converted as a
320320
// StructuredSerializeOptions dictionary.
321321
function normalizeTransfer(transferOrOptions, options) {
322-
if (typeof transferOrOptions === 'object' && transferOrOptions !== null &&
323-
transferOrOptions[SymbolIterator] !== undefined) {
324-
return converters['sequence<object>'](transferOrOptions, options);
322+
if ((typeof transferOrOptions === 'object' && transferOrOptions !== null) ||
323+
typeof transferOrOptions === 'function') {
324+
const method = transferOrOptions[SymbolIterator];
325+
if (method != null) {
326+
return converters['sequence<object>'](transferOrOptions, options, method);
327+
}
325328
}
326329
return converters.StructuredSerializeOptions(transferOrOptions, options)
327330
.transfer;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Flags: --experimental-web-worker
2+
'use strict';
3+
4+
const common = require('../common');
5+
const assert = require('node:assert');
6+
const { pathToFileURL } = require('node:url');
7+
8+
function checkPostMessage(post) {
9+
// A null iterator selects the dictionary overload.
10+
const dictionaryBuffer = new ArrayBuffer(8);
11+
post(null, { [Symbol.iterator]: null, transfer: [dictionaryBuffer] });
12+
assert.strictEqual(dictionaryBuffer.byteLength, 0);
13+
14+
// Callable objects can also be iterable transfer lists.
15+
const functionBuffer = new ArrayBuffer(8);
16+
function transfer() {}
17+
transfer[Symbol.iterator] = function*() { yield functionBuffer; };
18+
post(null, transfer);
19+
assert.strictEqual(functionBuffer.byteLength, 0);
20+
21+
// Overload resolution must reuse the iterator method it retrieved.
22+
const getterBuffer = new ArrayBuffer(8);
23+
const iterable = {};
24+
Object.defineProperty(iterable, Symbol.iterator, {
25+
get: common.mustCall(() => common.mustCall(function*() {
26+
assert.strictEqual(this, iterable);
27+
yield getterBuffer;
28+
})),
29+
});
30+
post(null, iterable);
31+
assert.strictEqual(getterBuffer.byteLength, 0);
32+
33+
// A present, non-callable iterator must fail before reading the dictionary.
34+
for (const value of [{}, function() {}]) {
35+
value[Symbol.iterator] = 1;
36+
Object.defineProperty(value, 'transfer', { get: common.mustNotCall() });
37+
assert.throws(() => post(null, value), TypeError);
38+
}
39+
}
40+
41+
// The test runner can also execute this file in a regular Node worker.
42+
if (typeof globalThis.DedicatedWorkerGlobalScope === 'undefined') {
43+
const worker = new Worker(pathToFileURL(__filename));
44+
worker.onerror = common.mustNotCall('worker failed');
45+
const done = common.mustCall(() => worker.terminate());
46+
worker.onmessage = ({ data }) => {
47+
if (data === 'done') done();
48+
};
49+
checkPostMessage(worker.postMessage.bind(worker));
50+
} else {
51+
checkPostMessage(globalThis.postMessage);
52+
globalThis.postMessage('done');
53+
}

0 commit comments

Comments
 (0)