Repository navigation
Deliver a main-thread postMessage() to the global's own message listeners - #42567
Conversation
…ners The main thread has no parent, so the global postMessage() returned without doing anything and a global 'message' listener never got the message. It now follows the window post message steps: the message is serialized with its transfer list and a task dispatches a MessageEvent on the global. "*" and "/" deliver; any other valid targetOrigin is a different origin, so the message is dropped after serialization. Code that uses globalThis.postMessage as setImmediate, like the VS Code scheduler that @dbml/core bundles, now runs its callbacks. The worker path shares the new serializeMessage helper. Refs oven-sh#42512 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n9mLkdcWtSLUNt9XeJUs4
|
A note about the base of this PR. #41731 is closed in favor of #32482. #32482 is the original PR for #24256. Since its rebase on 2026-09-07 it makes the same source change as #41731: the Nothing changes for this PR today. The base branch When #32482 lands:
I tried the rebase on main plus #32482. It stops on one conflict, in The PR body refers to #41731 in several places. Those references become #32482. |
Stacked on #41731. #41731 fixes the hang of #42512. This PR fixes its other half: the message is delivered. Together they fix #42512.
Problem
postMessage(x)does nothing and returnsundefined.jsFunctionPostMessage(src/jsc/bindings/webcore/Worker.cpp) finds no messaging proxy and returns. A globalmessagelistener never gets the message.globalThis.postMessageuses it assetImmediate.@dbml/corebundles VS Code's scheduler, which does this. On bun its callbacks never run. With Do not keep the main thread alive for a global 'message' listener #41731 alone the process exits, but the callbacks are still dropped.window.postMessage(x, "*")queues amessageevent on the window. Node has no globalpostMessage, so the same code takes itssetTimeoutpath there.Fix
jsFunctionPostMessageruns the window post message steps against the global itself. It serializes the message with its transfer list, then posts a task that dispatches aMessageEventonglobalEventScope.postMessage(message, targetOrigin, transfer)andpostMessage(message, { targetOrigin, transfer }). The worker form throws aTypeErroron the shim'spostMessage(x, "*")(checked in a worker on bun 1.4.2)."*"and"/"(the default: the caller's own origin) deliver. Any other valid URL names a different origin: the message is serialized, its buffers are transferred, and it is dropped. A string that does not parse throws aSyntaxError(step 5).el.taskscounts inis_event_loop_alive_excluding_immediates). The listener itself does not, after Do not keep the main thread alive for a global 'message' listener #41731. Microtasks drain between two messages, as in a browser (message 1, microtask 1, message 2, microtask 2on a debug build).serializeMessage, which the worker path now calls too. That drops twoRETURN_IF_EXCEPTIONchecks from the worker path that could not fire: they followed branches that already returned on an exception, anddisentanglePortsreports throughExceptionOr, not the VM.event.originis"null", the serialization of an opaque origin.event.sourceisnull: bun'sMessageEvent.sourceholds only aMessagePort, where a browser sets theWindowProxy.globals.d.tsgets the two Window overloads and no longer says the call does nothing on the main thread. Docs: one sentence indocs/runtime/workers.mdx.test/js/web/web-globals.test.js. On a debug build of this branch the file passes (30 tests). With onlyWorker.cppreverted, so Do not keep the main thread alive for a global 'message' listener #41731 alone, the 5 new tests fail on their assertions: no event,[]instead of the 6 delivered messages,detached 8 8 8and no errors, and empty output wheremessageerrorand the shim's callbacks should print. On bun 1.4.2 they time out, because the listener still holds the process open. Also rantest/js/web/workers/worker-postmessage-transfer.test.tsandtest/js/web/workers/worker.test.ts(77 tests across the 3 files pass). UnderBUN_JSC_validateExceptionChecks=1, a script that runs every new path on the main thread and one that runs the worker path throughserializeMessagereport no findings.Background
jsFunctionPostMessageis the globalpostMessageof everyZig::GlobalObject. In a worker,WebWorker__getMessagingProxyreturns the proxy to the parent. On the main thread it returns null.GlobalEventScopeis theEventTargetbehindglobalThis.addEventListener. Worker messages reach it throughWorkerMessagingProxy::drainInbox. The new task mirrors that path, includingmessageerrorwhen deserialization throws.Notes
Why stacked on #41731. Without #41731, a global
messagelistener holds the main thread open forever, so every new test hangs instead of failing on its assertion. After #41731 merges I rebase this branch on main.The design choice. #41731 left it open: "Whether it should throw or self-dispatch (browser
window.postMessagesemantics) is a separate decision." Throwing, or removingglobalThis.postMessageon the main thread as in Node, breaks code that calls it today without an error. This PR takes the browser behavior. If another option is preferred, the tests show every behavior that changes.Differences from a browser.
event.sourceisnull(see above). An invalidtargetOriginthrows a JSSyntaxError, where the spec has aDOMExceptionnamed"SyntaxError":ExceptionCode::SyntaxErrormaps to a JSSyntaxErroreverywhere in bun, on purpose (src/jsc/bindings/JSDOMExceptionHandling.cpp, the comment abovecase ExceptionCode::SyntaxError), andnew WebSocket("not a url")throws the same. A message to another origin is dropped before the task is posted, where the spec drops it inside the task. The two are not observably different.The
messageerrortest uses the payload from #39408's tests: a resizableArrayBufferand aDataViewwhose offset is only in bounds after a getter resized the buffer. It serializes, then fails to deserialize.Overlap with #41818. #41818 also rewrites the global
postMessagedeclaration inglobals.d.tsand adds lines totest/integration/bun-types/fixture/worker.ts. Whichever lands second rebases. The combined declaration set is #41818's(message, transfer)and(message, options?)for a worker, plus this PR's(message, targetOrigin, transfer?)andtargetOrigininoptions.Local runs.
test/integration/bun-types/bun-types.test.tshas 10 failures in my environment, the same set with and without this diff. None of them mentionpostMessage. They come from the@types/nodethe fixtures resolve here (fs/promisesexists,tlsBunConnectionOptions). The new fixture lines intest/integration/bun-types/fixture/worker.tstype-check.🤖 Generated with Claude Code
https://claude.ai/code/session_019n9mLkdcWtSLUNt9XeJUs4