Skip to content

Keep local MessageChannel ports alive while a message listener is attached#32564

Open
robobun wants to merge 10 commits into
mainfrom
claude/farm/100596c0/fix-messagechannel-keepalive
Open

Keep local MessageChannel ports alive while a message listener is attached#32564
robobun wants to merge 10 commits into
mainfrom
claude/farm/100596c0/fix-messagechannel-keepalive

Conversation

@robobun

@robobun robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes #32562
Fixes #32563

Repro

import { MessageChannel } from 'node:worker_threads';
const { port1, port2 } = new MessageChannel();
port1.on('message', (message) => console.log('received', message));
port2.postMessage({ foo: 'bar' });

Node prints received { foo: 'bar' } and keeps the process alive. Bun delivered the message and then exited immediately. .onmessage = fn worked; only .on('message') / addEventListener('message') exited early, which made the asymmetry the tell.

Cause

Two halves of Node's MessagePort event-loop behavior were missing for local MessageChannel ports:

  1. Listener never ref'd the loop. A MessagePort that is one end of a local MessageChannel is built directly by generateMessagePorts() and never goes through MessagePort::entangle(), where onDidChangeListener (the hook that refs/unrefs the event loop on message-listener add/remove) was assigned. So addEventListener('message') set the listener but nothing pinned the loop. .onmessage worked only because its setter took a separate jsRef().

  2. Peer close never released the loop. Node closes both ends of a channel when either side closes, firing 'close' on the peer so it stops holding the loop. Bun never notified the peer. Once the listener ref above existed, a port whose peer was explicitly closed would pin the loop forever (hanging test-worker-message-port-receive-message and -transfer-duplicate), and issue Bun exits even if MessageChannel from node:worker_threads is still working and has unfinished tasks #32563's repro printed the message but never 'close'.

Fix

  • Wire onDidChangeListener for every port in the MessagePort constructor, not just transferred ports. Fold the listener-count ref and explicit ref()/unref() into one m_hasRef flag plus a self-ref, mirroring Node's handle ref: the first message listener refs, the last one unrefs, ref()/unref() override, and removing the last listener releases regardless of ref(). The .onmessage/.onmessageerror setters no longer ref directly (a messageerror listener alone no longer holds the loop open, matching Node).
  • MessagePort::close() now notifies the peer via MessagePortPipe::notifyPeerClosed(), which sets a PeerClosed bit and wakes the peer's context through the existing drain pipeline. drainAndDispatch() consumes the bit once the inbox is drained (so 'close' lands after queued messages) and calls MessagePort::dispatchPeerClosed(), which fires the one-shot 'close' event and closes that end. The notification runs only on explicit close() / context teardown, never on the GC-driven ~MessagePort path, so a still-referenced peer is not disturbed when an unreferenced sibling is collected.

Verification

Matches Node v26.3.0 across the matrix:

scenario Node Bun (before) Bun (after)
.on('message') alive exits alive
addEventListener('message') alive exits alive
.onmessage = fn alive alive alive
.on('message') + .unref() exits exits exits
.on('message') then .off(...) exits exits exits
two listeners, remove one alive exits alive
.once('message') exits after delivery exits exits after delivery
.ref() with no listener alive alive alive
.unref() then .on('message') alive exits alive
.onmessageerror alone exits alive exits
#32563: listener on port2, port1.close() close fires, exits exits (no close) close fires, exits

Tests added to test/js/web/workers/message-channel.test.ts cover the listener-attachment paths, ref()/unref() and listener-removal parity, and the peer-close 'close' delivery. They fail on the current build and pass with this change. Existing MessagePort lifetime/leak/transfer suites (message-port-pipe, message-port-closed-leak, message-port-context-destroy-leak) and the node test-worker-message-port-* parallel tests pass, confirming the added self-ref and peer-close notification don't leak or disturb transfers.

Known limitation

The close event is delivered to the peer of a closed port but not to the port that called close() itself. Node fires it (asynchronously) on both ends. This has no effect on process lifetime (the closing port releases its event-loop ref via close() regardless) and #32563 is the peer-listens case, which is covered. Dispatching the closing port's own close event needs a deferred dispatch before teardown and is left for a follow-up.

Also pre-existing (not a regression, and niche): calling port.ref() on a port that never had a message listener and then closing the peer keeps the loop alive, because a bare ref() takes the event-loop ref without starting the port, so the peer-close notification is never consumed. Matching Node here (its ref() is a handle op that does not start message dispatch) without consuming queued messages is a separate change, tracked as a follow-up.

Also niche and deferred: transferring a port to an already-closed destination drops the transferred port directly (via ~TransferredMessagePort, outside the close() worklist), and its listening sibling is not woken, so that sibling keeps the loop alive. Waking it fixes the hang but actively closes the dropped endpoint in a way that shifts GC finalization timing and breaks an existing leak test (heapStats confirms no real leak), so it is left as a follow-up. The common transferred-drop path (a port dropped from a live port inbox on close) is handled.

…ached

A MessagePort created as one end of a local MessageChannel never goes
through entangle(), so it never wired onDidChangeListener and never ref'd
the event loop when a message listener was added. port.on('message') and
addEventListener('message') therefore delivered the queued message and let
the process exit, while Node keeps it alive. The .onmessage setter worked
only because it took a separate ref.

Wire onDidChangeListener for every port in the constructor and fold the
listener-count ref and the explicit ref()/unref() into one m_hasRef flag
plus a self-ref, mirroring Node's handle ref: the first message listener
refs, the last one unrefs, ref()/unref() override, and removing the last
listener releases regardless of ref(). The onmessage/onmessageerror setters
no longer ref directly; attaching the listener does it, so a messageerror
listener alone no longer holds the loop open (matching Node).
@robobun

robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 2:08 PM PT - Jun 21st, 2026

@robobun, your commit ab6dfe5 has 2 failures in Build #63829 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 32564

That installs a local version of the PR into your bun-32564 executable, so you can run:

bun-32564 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Bun exits even if MessageChannel from node:worker_threads is still working and has unfinished tasks #32563 - Bun exits prematurely with active local MessageChannel ports using port.on('message', ...), same root cause as Bun exits instead of waiting when MessageChannel from node:worker_threads is used #32562 (onDidChangeListener not wired for local ports)

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #32563

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

MessagePort gains two private helpers, holdEventLoopRef() and releaseEventLoopRef(), that centralize the self-ref and event-loop ref/unref bookkeeping. A new dispatchPeerClosed() callback handles the one-shot close event and is coordinated by MessagePortPipe through a new PeerClosed state flag and notifyPeerClosed() method. All listener lifecycle paths (constructor, onDidChangeListenerImpl, close, disentangle, entangle) and the JS property setters (onmessage, onmessageerror) are updated to use these helpers. The ref()/unref() public methods now call the refactored helpers without passing lexicalGlobalObject. New concurrent tests verify process-exit behavior across all listener attachment styles and after peer closure.

Changes

MessagePort event-loop liveness and peer-close refactor

Layer / File(s) Summary
holdEventLoopRef / releaseEventLoopRef helpers and declarations
src/jsc/bindings/webcore/MessagePort.h, src/jsc/bindings/webcore/MessagePort.cpp
Declares two private helpers in the header with lifecycle documentation, updates jsRef/jsUnref signatures to remove the JSGlobalObject* parameter, adds dispatchPeerClosed() callback, and introduces m_peerClosedHandled state member. In the implementation, removes the extern "C" Bun__eventLoop__incrementRefConcurrently declaration and adds holdEventLoopRef/releaseEventLoopRef bodies that manage m_hasRef, ref()/deref(), and context->refEventLoop()/unrefEventLoop(). jsRef/jsUnref are rewired to delegate to these helpers.
Listener lifecycle wiring and peer-close dispatch
src/jsc/bindings/webcore/MessagePort.cpp
Constructor centrally assigns onDidChangeListener = MessagePort::onDidChangeListenerImpl. onDidChangeListenerImpl now calls holdEventLoopRef/releaseEventLoopRef based on m_messageEventCount transitions instead of inlining ScriptExecutionContext ref/unref. close() notifies the peer via m_pipe->notifyPeerClosed(...), removes listeners, and calls releaseEventLoopRef(). disentangle() removes message listeners and calls releaseEventLoopRef(). entangle() returns the port directly. dispatchPeerClosed() fires the close event under re-entrancy guards, then calls close(). contextDestroyed() comment updated.
JS onmessage / onmessageerror setter and ref() / unref() cleanup
src/jsc/bindings/webcore/JSMessagePort.cpp
setJSMessagePort_onmessageSetter and setJSMessagePort_onmessageerrorSetter remove explicit jsRef calls, delegating the hold to onDidChangeListenerImpl; a standalone messageerror listener must not keep the event loop alive. jsMessagePortPrototypeFunction_refBody and jsMessagePortPrototypeFunction_unrefBody now call jsRef() / jsUnref() without lexicalGlobalObject. jsMessagePortPrototypeFunction_closeBody no longer calls jsUnref, relying on close() to release any event-loop ref.
MessagePortPipe peer-close state and notification
src/jsc/bindings/webcore/MessagePortPipe.h, src/jsc/bindings/webcore/MessagePortPipe.cpp
MessagePortPipe::State gains a PeerClosed flag to track that the peer closed. close() signature now accepts a notifyPeers boolean, distinguishing explicit close paths from GC teardown. notifyPeerClosed(uint8_t peerSide) marks the peer side as closed and schedules draining. drainAndDispatch() handles PeerClosed: clears both DrainScheduled and PeerClosed when the port is null or context unavailable, records the peerClosed condition when the inbox drains, and dispatches port->dispatchPeerClosed() after microtask draining if observed. attach() now schedules draining when PeerClosed is set. TransferredMessagePort teardown uses close(notifyPeers=false).
Event-loop liveness tests with peer-close scenarios
test/js/web/workers/message-channel.test.ts
Adds harness imports and a new describe suite with helper functions (streamHasMarker, expectStaysAlive, expectExitsOnItsOwn) that spawn Bun subprocesses and use Promise.race with ASAN/debug-adjusted time windows to assert whether a process stays alive or exits promptly. Tests cover .on('message'), addEventListener, .onmessage (stay alive), unref(), listener removal, onmessageerror-only (should exit), closing the peer port to deliver the close event and allow process exit, and scenarios where message listeners attached after peer closure do not keep the process alive.

Suggested reviewers

  • Jarred-Sumner
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: ensuring local MessageChannel ports remain alive while message listeners are attached, which is the core issue this PR addresses.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description thoroughly covers both issues (#32562 and #32563), the root causes, the fix implementation, verification against Node.js, and known limitations with test evidence.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/web/workers/message-channel.test.ts`:
- Around line 351-365: The expectStaysAlive function leaves stderr unconsumed,
which can cause the OS pipe buffer to fill and deadlock the child process if
debug or ASAN builds emit warnings. Modify the function to drain stderr
concurrently alongside the existing timeout and process exit logic using
Promise.all, similar to how expectExitsOnItsOwn does it. Specifically, add
proc.stderr.text() to the Promise.all array that already contains
proc.exited.then() and Bun.sleep() so that stderr is continuously drained while
waiting for the process outcome.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4f9eb02b-c97a-4559-907b-56affb5eff98

📥 Commits

Reviewing files that changed from the base of the PR and between 602d956 and 1e27b23.

📒 Files selected for processing (4)
  • src/jsc/bindings/webcore/JSMessagePort.cpp
  • src/jsc/bindings/webcore/MessagePort.cpp
  • src/jsc/bindings/webcore/MessagePort.h
  • test/js/web/workers/message-channel.test.ts

Comment thread test/js/web/workers/message-channel.test.ts
Comment thread src/jsc/bindings/webcore/MessagePort.cpp
jsRef()/jsUnref() reach the event loop through scriptExecutionContext()
now, so their JSGlobalObject* parameter is dead. Drop it and update the
ref/unref prototype bindings. The jsUnref() call that preceded close() in
the close binding is also redundant since close() releases the event-loop
ref itself; remove it.

In the keep-alive test, drain the child's stderr in the background so a
debug/ASAN warning can't fill the OS pipe buffer and deadlock a process
the test expects to stay alive.
@robobun

robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, but I'm keeping this PR scoped to #32562 and not tagging Fixes #32563.

They share the keep-alive root cause, but #32563 needs something this PR does not implement: the MessagePort 'close' event. In Node, port.close() emits 'close' on both ends of the channel (the closing port and its peer), ordered after any already-queued messages, and a 'close'-only listener does not by itself keep the loop alive. Bun currently never dispatches that event, and there's no peer-close notification in MessagePortPipe.

With only this PR, #32563's repro actually gets worse: the message listener now correctly keeps the process alive, but since 'close' never fires, it hangs instead of exiting. Verified locally (Node vs this build):

port2.on('message', m => console.log(m));
port2.on('close', () => console.log('closed!'));
port1.postMessage('foobar');
port1.close();

Node prints foobar then closed! and exits; this build prints foobar and hangs.

The 'close' event (self + peer, cross-thread, ordered after the inbox drains) is a separate, sizable piece of work that deserves its own PR and review, so I'll address #32563 separately rather than bundle a cross-thread feature into this lifetime fix.

Comment thread src/jsc/bindings/webcore/MessagePort.cpp
…e loop

Keeping a port alive while it has a message listener (the previous commit)
is only half of Node's behavior: when one end of a channel is closed, Node
closes both ends, firing 'close' on the peer and letting it stop holding
the event loop. Bun never notified the peer, so a port with a message
listener whose peer was explicitly closed would pin the loop forever. This
hung test-worker-message-port-receive-message and
test-worker-message-port-transfer-duplicate, and left the issue #32563
repro (port2 listening, port1.close()) printing only the message.

MessagePort::close() now calls MessagePortPipe::notifyPeerClosed(), which
sets a PeerClosed bit on the peer side and wakes its context through the
existing drain pipeline. drainAndDispatch() consumes that bit once its
inbox is fully drained (so 'close' is ordered after queued messages) and
calls MessagePort::dispatchPeerClosed(), which fires the one-shot 'close'
event and closes that end, releasing the listener's event-loop ref.

The notification is deliberately driven from MessagePort::close() (explicit
close and context teardown) and not from the GC-driven ~MessagePort path,
which reaches MessagePortPipe::close() directly: a still-referenced peer
must not be disturbed when an unreferenced sibling is collected, otherwise
a listening port would exit as soon as its idle peer was swept.

Fixes #32563.
@robobun

robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

Update on #32563: I ended up fixing it here after all.

CI showed the two issues are inseparable. Keeping a port alive on a message listener (the #32562 fix) made two existing node-compat tests hang (test-worker-message-port-receive-message, test-worker-message-port-transfer-duplicate): they attach a listener, then close the other end, and expected the process to exit. Without peer-close handling the listener pinned the loop forever. So the peer-close path that #32563 needs is also required to not regress #32562.

This PR now implements it: MessagePort::close() notifies the peer, which fires the one-shot 'close' event (ordered after any queued messages) and releases the listener's event-loop ref. The notification is driven only from explicit close() / context teardown, not the GC-driven ~MessagePort path, so collecting an idle sibling doesn't make a listening peer exit early. PR body and tests updated; both node regressors pass again.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/web/workers/message-channel.test.ts`:
- Around line 442-447: The test "onmessageerror alone does not keep the process
alive" is using the global MessageChannel instead of importing from the
node:worker_threads module, which weakens coverage for the node-worker_threads
path. Fix this by modifying the code string passed to expectExitsOnItsOwn to
import MessageChannel from 'node:worker_threads' at the beginning of the string,
then use that imported MessageChannel instead of relying on the global
MessageChannel when instantiating port1 and port2.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 786d6d82-87f9-4c81-b4f9-297d4f26fef5

📥 Commits

Reviewing files that changed from the base of the PR and between 703f45a and 574a40b.

📒 Files selected for processing (5)
  • src/jsc/bindings/webcore/MessagePort.cpp
  • src/jsc/bindings/webcore/MessagePort.h
  • src/jsc/bindings/webcore/MessagePortPipe.cpp
  • src/jsc/bindings/webcore/MessagePortPipe.h
  • test/js/web/workers/message-channel.test.ts

Comment thread test/js/web/workers/message-channel.test.ts
robobun added 2 commits June 21, 2026 16:28
Matches the sibling unref()/off() exit cases so the whole node-worker_threads
exit group goes through the same import path.
Windows x64-baseline failed only on bun-install.test.ts network flake
(GitHub API 504s, git clone timeouts); unrelated to this MessagePort change.
Comment thread src/jsc/bindings/webcore/MessagePortPipe.cpp
Comment thread src/jsc/bindings/webcore/MessagePort.cpp Outdated
The peer-close notification added earlier only covered an explicit close of
the direct peer. Two paths to a dead peer still pinned a listening port's
event-loop ref forever (a hang that exited pre-PR and in Node):

  - close-then-listen: port1.close(); port2.on('message', cb). At close time
    port2 had not started, so notifyPeerClosed bailed and never recorded the
    close; the later listener then ref'd the loop with no release.
  - transferred-then-dropped: a port transferred inside a message that is
    dropped (its holder closed) never told its listening sibling.

Rework the notification to be complete:

  - MessagePortPipe::close() takes a notifyPeers flag and, when set, wakes the
    peer of every side it tears down (the closed side plus in-transit ports
    harvested from the dropped inbox). MessagePort::close() passes true; the
    GC-driven ~MessagePort and dropped-TransferredMessagePort paths pass false
    so collecting an idle port never disturbs a still-referenced peer.
  - notifyPeerClosed() records the PeerClosed bit even when the peer has not
    started listening yet; attach() honors it and schedules the drain, so a
    listener added after the peer closed still observes 'close' and releases.

Known gap: the 'close' event still fires only on the peer, not on the port
that called close() (Node fires it, asynchronously, on both ends). That has
no effect on process lifetime and is left for a follow-up.
@robobun

robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

CI status: the diff is green; the red is CI-infrastructure flake in test-runner meta-tests, unrelated to this change.

This PR touches only src/jsc/bindings/webcore/MessagePort*.{cpp,h} + JSMessagePort.cpp and one web test file. It does not touch the test runner, expect, or assertion code. Its own coverage passes on every lane (message-channel.test.ts, message-port-*, node test-worker-message-port-*).

The red on build 63829 (HEAD ab6dfe5) is test/js/bun/test/expect-assertions.test.ts and test/js/bun/test/done-async.test.ts failing on both the macOS aarch64 and ubuntu x64 lanes. These are test-runner meta-tests whose cases intentionally fail to verify failure reporting; both pass locally with this build (1 pass, 0 fail each via bun bd test), so CI is running them in a mode where the intentional failures count (the macOS lane also shows a bytes@3.1.2 failed to resolve registry error that appears to disrupt the runner's handling). None of this can be caused by a MessagePort-only diff.

I've used my single CI re-roll already, so I'm not pushing another retrigger. The fix is verified locally: #32562 stays alive, #32563 fires close and exits, the peer-close edge cases exit, and the leak/lifetime/transfer suites are green. Ready for a maintainer to re-run the flaky lanes or merge.

Comment thread src/jsc/bindings/webcore/MessagePort.cpp
The notifyPeers flag is threaded through the whole close() worklist, so the
GC-driven ~MessagePort path (notifyPeers=false) also suppressed the peer
notification for in-transit transferred ports harvested from the dropped
inbox. A port transferred into a message whose holder is then collected (not
explicitly closed) never told its listening sibling, so the sibling pinned the
event loop forever.

notifyPeers must gate only the top-level side's direct peer (collecting an idle
port must not disturb a still-referenced peer). A harvested in-transit port is
undeliverable once its carrier is dropped, regardless of how the carrier died,
so its peer is always notified. Track the notify decision per worklist entry:
the seed uses notifyPeers, harvested entries always use true.
Comment thread src/jsc/bindings/webcore/MessagePortPipe.h
Comment thread src/jsc/bindings/webcore/MessagePort.cpp
hasPendingActivity() gates the JS wrapper's GC reachability, but it didn't
know about the new PeerClosed bit. In the window between the peer closing and
the queued drain firing the one-shot 'close' event, hasPendingActivity()
returned false (inbox empty, peer closed), so a GC could sweep the wrapper and
the 'close' listener's weak refs, silently dropping the event (and tripping a
debug ASSERT in ensureJSFunction).

Include PeerClosed in hasPendingActivity() alongside the queued-message check.
The bit is cleared in the same store that dispatches 'close', so the wrapper
becomes collectable again immediately after, mirroring how a queued message
keeps the wrapper alive only until it is delivered.
Comment thread src/jsc/bindings/webcore/MessagePortPipe.cpp Outdated
A port transferred to an already-closed destination is dropped directly by
~TransferredMessagePort (outside the close() worklist), and its listening
sibling is not woken. Actively waking it (notifyPeers=true here) fixes the
resulting event-loop-ref hang, but schedules a 'close' drain that shifts GC
finalization timing for the dropped endpoint enough to break the existing
"port dropped in transit does not pin its peer" leak test (heapStats confirms
the wrappers are still collected, so it is a timing artifact, not a real
leak). Keep notifyPeers=false and record the niche case as a known limitation
rather than reshape the finalization path here.
Comment thread src/jsc/bindings/webcore/MessagePortPipe.h Outdated
Comment thread src/jsc/bindings/webcore/MessagePortPipe.cpp
- ~TransferredMessagePort: inline the rationale for notifyPeers=false instead
  of pointing at "the PR" (comments must stand on their own, not reference
  transcript/PR context).
- close() header: the notifyPeers doc drifted after the worklist change. It
  governs only the direct peer of `side`; harvested in-transit ports always
  notify their peers regardless of the flag. Match the .cpp comment.
Comment thread src/jsc/bindings/webcore/MessagePort.cpp
Comment thread src/jsc/bindings/webcore/MessagePort.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

1 participant