Skip to content

globals: expose MessageChannel, MessagePort, and BroadcastChannel #2597

Closed

Description

@andrewtdiz

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:

  • BroadcastChannel is a global class added in v18.0.0.
  • MessageChannel is a global class added in v15.0.0.
  • MessagePort is 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_CONSTRUCTORS prepopulates many global constructor names (TextEncoderStream, URL, AbortController, Blob, Request, Response, etc.) but does not include MessageChannel, MessagePort, or BroadcastChannel.
  • 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, and BroadcastChannel finds only the partial worker_threads import bookkeeping and parity probes; there is no native global constructor routing for these names.
  • docs/runtime-parity.md lists MessageChannel, MessagePort, and BroadcastChannel under Web / Global APIs as covered, so the docs currently overstate the native global surface.
  • test-files/test_parity_worker_threads.ts probes 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 global
  • globalThis.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

  1. andrewtdiz commented on May 31, 2026

    @andrewtdiz
    ContributorAuthor

    Fresh current-main update:

    4e510532d hotfix: actually remove native_module.rs punycode/timers conflict marker (#3883)
    

    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_threads
    global types: function function function
    namespace identity: true true true
    named identity: true true true
    constructor names: MessageChannel MessagePort BroadcastChannel
    constructor lengths: 0 0 1
    prototype constructors: true true true
    MessageChannel call: threw
    MessagePort new: threw
    channel shape: true true object object
    port shape: true true true true
    port methods: true true true true true true
    port events: null null
    

    Report:

    /private/tmp/perry-node-compat-main/test-parity/reports/parity_report_20260531_142128.json
    

    So the original global exposure/identity gap appears fixed for the covered surface. The fixture still fails later at BroadcastChannel.close() with TypeError: 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.

  2. andrewtdiz commented on May 31, 2026

    @andrewtdiz
    ContributorAuthor

    Current-main rerun at 22824ba90 makes the original global exposure/identity gap close-ready for the covered surface:

    $ ./run_parity_tests.sh --suite node-suite --module worker_threads
    Report: /private/tmp/perry-node-compat-main/test-parity/reports/parity_report_20260531_153707.json

    The message-channel/globals fixture reaches and matches all global/module constructor exposure checks before failing later in BroadcastChannel method dispatch:

    global types: function function function
    namespace identity: true true true
    named identity: true true true
    constructor names: MessageChannel MessagePort BroadcastChannel
    constructor lengths: 0 0 1
    prototype constructors: true true true
    MessageChannel call: threw
    MessagePort new: threw
    channel shape: true true object object
    port shape: true true true true
    port methods: true true true true true true
    port events: null null
    

    The remaining failure is BroadcastChannel.close() / method dispatch, tracked by #3158 and coordinated in #3710, not missing global constructors. No Test262 coverage was involved.

  3. proggeramlug commented on Jun 2, 2026

    @proggeramlug
    Contributor

    Implemented by #3634 / #3808 (merged, on main): MessageChannel, MessagePort, and BroadcastChannel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions