Repository navigation
globals: expose MessageChannel, MessagePort, and BroadcastChannel #2597
Description
Summary
Current Node exposes the worker messaging constructors MessageChannel, MessagePort, and BroadcastChannel as globals. Perry tracks the node:worker_threads module gap in #2507, but the global aliases are a separate observable surface: browser-compatible packages often feature-detect or instantiate these constructors through globalThis without importing node:worker_threads.
Today Perry's native globalThis constructor population omits all three names, so globalThis.MessageChannel, globalThis.MessagePort, and globalThis.BroadcastChannel are not Node-compatible even if the module implementation later lands.
Expected Node behavior
Node documents these as global classes in the Globals API:
BroadcastChannelis a global class added in v18.0.0.MessageChannelis a global class added in v15.0.0.MessagePortis a global class added in v15.0.0.
A local Node v25.9.0 probe shows the globals are also identical to the node:worker_threads exports:
BroadcastChannel function function true
MessageChannel function function true
MessagePort function function true
channel ports true true function function
broadcast proto close,constructor,name,onmessage,onmessageerror,postMessage,ref,unref
Probe:
const wt = require("node:worker_threads");
for (const k of ["BroadcastChannel", "MessageChannel", "MessagePort"]) {
console.log(k, typeof globalThis[k], typeof wt[k], globalThis[k] === wt[k]);
}
const ch = new MessageChannel();
console.log("channel ports", ch.port1 instanceof MessagePort, ch.port2 instanceof MessagePort, typeof ch.port1.postMessage, typeof ch.port1.close);
const bc = new BroadcastChannel("probe");
console.log("broadcast proto", Object.getOwnPropertyNames(BroadcastChannel.prototype).sort().join(","));
bc.close(); ch.port1.close(); ch.port2.close();Current Perry evidence
Source inspection against origin/main:
crates/perry-runtime/src/object/global_this.rs::GLOBAL_THIS_BUILTIN_CONSTRUCTORSprepopulates many global constructor names (TextEncoderStream,URL,AbortController,Blob,Request,Response, etc.) but does not includeMessageChannel,MessagePort, orBroadcastChannel.crates/perry-codegen/src/expr/helpers.rs::is_global_this_builtin_name()mirrors the global constructor routing list and also omits all three names.- A source grep for
MessageChannel,MessagePort, andBroadcastChannelfinds only the partialworker_threadsimport bookkeeping and parity probes; there is no native global constructor routing for these names. docs/runtime-parity.mdlistsMessageChannel,MessagePort, andBroadcastChannelunder Web / Global APIs as covered, so the docs currently overstate the native global surface.test-files/test_parity_worker_threads.tsprobes the module exports, but there is no focused parity test for the global aliases or module/global identity.
I did not run a Perry binary because this checkout does not have target/release/perry; this issue is based on committed origin/main source, current Node docs, and a local Node v25.9.0 probe.
Suggested test surface
Add parity tests that cover both direct global access and module identity:
const wt = require("node:worker_threads");
console.log(typeof globalThis.MessageChannel === "function");
console.log(typeof globalThis.MessagePort === "function");
console.log(typeof globalThis.BroadcastChannel === "function");
console.log(globalThis.MessageChannel === wt.MessageChannel);
console.log(globalThis.MessagePort === wt.MessagePort);
console.log(globalThis.BroadcastChannel === wt.BroadcastChannel);
const ch = new globalThis.MessageChannel();
console.log(ch.port1 instanceof globalThis.MessagePort);
console.log(ch.port2 instanceof globalThis.MessagePort);
ch.port1.close();
ch.port2.close();For BroadcastChannel, also check constructor/prototype shape and close, postMessage, ref, unref, onmessage, and onmessageerror once #2507 provides the underlying implementation.
Scope / non-goals
This issue is scoped to global exposure, constructor identity, and minimal constructor/prototype shape. It should coordinate with #2507 for real message delivery, worker lifecycle, transfer semantics, and cross-thread behavior. A first fix can make the globals resolve to the same constructor objects as node:worker_threads once those constructors exist.
Duplicate search
Checked existing issues and PRs with:
EventSource BroadcastChannel MessageChannel MessagePort globalglobalThis.MessageChannel BroadcastChannel MessagePort
No issue or PR covered the global alias/identity gap. #2507 covers the node:worker_threads object/channel implementation and should remain the underlying behavior tracker, but it does not explicitly track these global aliases.
Activity
andrewtdiz commentedon May 31, 2026
Fresh current-main update:
The curated worker_threads global-identity fixture now gets through the global constructor exposure checks:
perl -e 'alarm shift; exec @ARGV' 120 ./run_parity_tests.sh --suite node-suite --module worker_threadsReport:
So the original global exposure/identity gap appears fixed for the covered surface. The fixture still fails later at
BroadcastChannel.close()withTypeError: close is not a function, but that is behavior/method dispatch rather than global alias exposure; I linked that to the worker messaging tracker in #3710 and the BroadcastChannel behavior issue #3158.andrewtdiz commentedon May 31, 2026
Current-main rerun at
22824ba90makes the original global exposure/identity gap close-ready for the covered surface:The
message-channel/globalsfixture reaches and matches all global/module constructor exposure checks before failing later in BroadcastChannel method dispatch:The remaining failure is
BroadcastChannel.close()/ method dispatch, tracked by #3158 and coordinated in #3710, not missing global constructors. No Test262 coverage was involved.proggeramlug commentedon Jun 2, 2026
Implemented by #3634 / #3808 (merged, on
main): MessageChannel, MessagePort, and BroadcastChannel.