denoland/deno · error · TypeError

MessageEvent constructor: Expected eventInitDict.source (${f

Error message

MessageEvent constructor: Expected eventInitDict.source (${formatForBranded(source)}) to be an instance of MessagePort.

What it means

eventInitDict.source is typed in the spec as MessagePort | ServiceWorker | WindowProxy, but Deno has no JS-level brand for Window or ServiceWorker. The constructor therefore accepts anything except primitives and values whose prototype is exactly Object.prototype (ext/web/02_event.js:1405-1420): numbers, strings, booleans, and plain {} literals throw TypeError 'Expected eventInitDict.source ... to be an instance of MessagePort'. Real MessagePorts, the global object, and user-defined class instances pass through unchanged — a deliberate compromise to satisfy both WPT and Node's worker tests.

Source

Thrown at ext/web/02_event.js:1415

      // ServiceWorker brand to gate against, so accept anything except
      // primitives (which the node_compat tests expect to throw on,
      // e.g. `source: 1`) and plain `Object` instances like `{}` (also
      // checked by Node). MessagePort, Window-like globals, and
      // user-defined classes all pass through unchanged -- matching both
      // the WPT `messageevent-constructor.https.html` test (which
      // assigns `window` to `source`) and node_compat
      // `test-worker-message-event`.
      const t = typeof source;
      const isPrimitive = t !== "object" && t !== "function";
      // Treat values whose prototype is exactly Object.prototype (i.e.
      // plain object literals like `{}`) as invalid sources. Using the
      // prototype chain rather than `.constructor` avoids the
      // prefer-primordials lint and is more reliable since user code
      // can override `constructor`.
      const isPlainObject = !isPrimitive &&
        ObjectGetPrototypeOf(source) === ObjectPrototype;
      if (isPrimitive || isPlainObject) {
        throw new TypeError(
          `MessageEvent constructor: Expected eventInitDict.source (${
            formatForBranded(source)
          }) to be an instance of MessagePort.`,
        );
      }
      this.#source = source;
    } else {
      this.#source = null;
    }
  }

  [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
    return inspect(
      getCreateFilteredInspectProxy()({
        object: this,
        evaluate: ObjectPrototypeIsPrototypeOf(MessageEventPrototype, this),
        keys: [
          ...new SafeArrayIterator(EVENT_PROPS),

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Set source to a real MessagePort (e.g. channel.port2) or omit it / pass null.
  2. Primitives and {} are rejected by design — delete the source field from fixtures instead of stubbing it.
  3. For window-like targets pass the global object itself (WPT assigns window), never a literal stand-in.

Example fix

// before
new MessageEvent('message', { data, source: {} });
// TypeError: Expected eventInitDict.source ([object Object]) to be an instance of MessagePort.

// after
new MessageEvent('message', { data, source: channel.port2 });
Defensive patterns

Strategy: type-guard

Validate before calling

const s = eventInit.source;
const ok = s == null ||
  ((typeof s === 'object' || typeof s === 'function') &&
    Object.getPrototypeOf(s) !== Object.prototype);
if (!ok) delete eventInit.source;
new MessageEvent('message', eventInit);

Type guard

function isMessageEventSource(v) {
  if (v == null) return true;
  if (typeof v !== 'object' && typeof v !== 'function') return false;
  return Object.getPrototypeOf(v) !== Object.prototype; // plain {} rejected
}

Prevention

When it happens

Trigger: new MessageEvent('message', { data, source: 1 }), { source: 'worker' }, or { source: {} } — primitives and plain object literals. source: null/undefined omitted, source: channel.port2, or source: globalThis are fine.

Common situations: Worker test fixtures setting source to a literal or a number (copied from Node test suites that assert the throw); forwarding a received event's source field that is actually plain data; TypeScript code assuming any object is accepted because the message mentions only MessagePort.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/5ef11bc51a656555. Report an issue: GitHub.