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');
+}