Commit ef8947aecf9 for nodejs
commit ef8947aecf91283f349893264999e51e6b865628
Author: Filip Skokan <panva.ip@gmail.com>
Date: Sun Sep 27 19:02:12 2026 +0200
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: https://github.com/nodejs/node/pull/66354
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
diff --git a/lib/internal/webidl.js b/lib/internal/webidl.js
index 88f4205a1c0..819b5af205b 100644
--- a/lib/internal/webidl.js
+++ b/lib/internal/webidl.js
@@ -815,7 +815,7 @@ function createDictionaryConverter(
* @returns {Converter}
*/
function createSequenceConverter(converter) {
- return function(V, options = kEmptyObject) {
+ return function(V, options = kEmptyObject, method = undefined) {
// Web IDL sequence conversion step 1: require an ECMA-262 Object.
if (type(V) !== 'Object') {
throw makeException(
@@ -823,8 +823,11 @@ function createSequenceConverter(converter) {
options);
}
- // Step 2: GetMethod(V, %Symbol.iterator%).
- const method = V[SymbolIterator];
+ // Step 2: GetMethod(V, %Symbol.iterator%). Overload resolution can
+ // supply the method it already retrieved, avoiding a second lookup.
+ if (method === undefined) {
+ method = V[SymbolIterator];
+ }
// Step 3: throw if the iterator method is undefined, null, or not callable.
if (typeof method !== 'function') {
throw makeException(
diff --git a/lib/internal/webworker.js b/lib/internal/webworker.js
index d7e768372de..228fb194d29 100644
--- a/lib/internal/webworker.js
+++ b/lib/internal/webworker.js
@@ -319,9 +319,12 @@ function fetchClassicScriptSourceSync(url, blob) {
// sequence<object> overload, everything else is converted as a
// StructuredSerializeOptions dictionary.
function normalizeTransfer(transferOrOptions, options) {
- if (typeof transferOrOptions === 'object' && transferOrOptions !== null &&
- transferOrOptions[SymbolIterator] !== undefined) {
- return converters['sequence<object>'](transferOrOptions, options);
+ if ((typeof transferOrOptions === 'object' && transferOrOptions !== null) ||
+ typeof transferOrOptions === 'function') {
+ const method = transferOrOptions[SymbolIterator];
+ if (method != null) {
+ return converters['sequence<object>'](transferOrOptions, options, method);
+ }
}
return converters.StructuredSerializeOptions(transferOrOptions, options)
.transfer;
diff --git a/test/parallel/test-webworker-postmessage-overloads.js b/test/parallel/test-webworker-postmessage-overloads.js
new file mode 100644
index 00000000000..17b83191b30
--- /dev/null
+++ b/test/parallel/test-webworker-postmessage-overloads.js
@@ -0,0 +1,53 @@
+// Flags: --experimental-web-worker
+'use strict';
+
+const common = require('../common');
+const assert = require('node:assert');
+const { pathToFileURL } = require('node:url');
+
+function checkPostMessage(post) {
+ // A null iterator selects the dictionary overload.
+ const dictionaryBuffer = new ArrayBuffer(8);
+ post(null, { [Symbol.iterator]: null, transfer: [dictionaryBuffer] });
+ assert.strictEqual(dictionaryBuffer.byteLength, 0);
+
+ // Callable objects can also be iterable transfer lists.
+ const functionBuffer = new ArrayBuffer(8);
+ function transfer() {}
+ transfer[Symbol.iterator] = function*() { yield functionBuffer; };
+ post(null, transfer);
+ assert.strictEqual(functionBuffer.byteLength, 0);
+
+ // Overload resolution must reuse the iterator method it retrieved.
+ const getterBuffer = new ArrayBuffer(8);
+ const iterable = {};
+ Object.defineProperty(iterable, Symbol.iterator, {
+ get: common.mustCall(() => common.mustCall(function*() {
+ assert.strictEqual(this, iterable);
+ yield getterBuffer;
+ })),
+ });
+ post(null, iterable);
+ assert.strictEqual(getterBuffer.byteLength, 0);
+
+ // A present, non-callable iterator must fail before reading the dictionary.
+ for (const value of [{}, function() {}]) {
+ value[Symbol.iterator] = 1;
+ Object.defineProperty(value, 'transfer', { get: common.mustNotCall() });
+ assert.throws(() => post(null, value), TypeError);
+ }
+}
+
+// The test runner can also execute this file in a regular Node worker.
+if (typeof globalThis.DedicatedWorkerGlobalScope === 'undefined') {
+ const worker = new Worker(pathToFileURL(__filename));
+ worker.onerror = common.mustNotCall('worker failed');
+ const done = common.mustCall(() => worker.terminate());
+ worker.onmessage = ({ data }) => {
+ if (data === 'done') done();
+ };
+ checkPostMessage(worker.postMessage.bind(worker));
+} else {
+ checkPostMessage(globalThis.postMessage);
+ globalThis.postMessage('done');
+}