Skip to content

fix(runtime): don't overwrite native Event/EventTarget on web#122

Merged
V3RON merged 2 commits into
callstackincubator:mainfrom
nachooya:upstream-pr/runtime-preserve-native-dom-event
May 18, 2026
Merged

fix(runtime): don't overwrite native Event/EventTarget on web#122
V3RON merged 2 commits into
callstackincubator:mainfrom
nachooya:upstream-pr/runtime-preserve-native-dom-event

Conversation

@nachooya
Copy link
Copy Markdown
Contributor

@nachooya nachooya commented May 14, 2026

Summary

@react-native-harness/runtime's initialize.ts unconditionally assigns event-target-shim's classes to globalThis.Event and globalThis.EventTarget. That polyfill is required on RN's JavaScriptCore runtime (which has no DOM), but on web (RN Web running in a real browser) it clobbers the browser's native ctors.

Safari's native EventTarget.dispatchEvent() does an internal brand check on its argument and throws

TypeError: Argument 1 ('event') to EventTarget.dispatchEvent must be an instance of Event

when a polyfill instance is dispatched onto a native EventTarget — even though the polyfill behaves mostly like Event. That's enough to break any DOM-event-driven flow in the page.

Fix

Only install the polyfill when a native ctor is not already present:

if (typeof globalThis.Event !== 'function') {
  globalThis.Event = Shim.Event;
}
if (typeof globalThis.EventTarget !== 'function') {
  globalThis.EventTarget = Shim.EventTarget;
}
  • On RN/JSC: typeof globalThis.Event !== 'function', polyfill installs — unchanged behaviour.
  • On RN Web / any browser: Event is a native ctor, polyfill is skipped, native dispatch keeps working.

Test plan

  • pnpm nx run-many -t build typecheck lint -p @react-native-harness/runtime — green.

The runtime's initialize hook unconditionally replaces globalThis.Event
and globalThis.EventTarget with event-target-shim's classes. That's
necessary on RN's JSC runtime (no DOM), but on web (RN Web running in a
real browser) it stomps on the browser's native ctors.

Safari's native EventTarget.dispatchEvent() does an internal brand
check on its argument and throws

  TypeError: Argument 1 ('event') to EventTarget.dispatchEvent must be
  an instance of Event

when a polyfill instance is dispatched on a native EventTarget — even
though the polyfill mostly behaves like Event. That's enough to break
any DOM-event-driven flow in the page; the visible casualty was
FairPlay playback through @castlabs/react-native-prestoplay, where
AppleEmeManager re-dispatches a synthetic `encrypted` event onto the
<video> in response to `webkitneedkey` and Safari rejects it.

Gate both assignments on whether a native ctor is already present so
the polyfill only takes effect on platforms that genuinely lack one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

@nachooya is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

@V3RON V3RON self-requested a review May 18, 2026 05:58
@V3RON V3RON merged commit caab9c3 into callstackincubator:main May 18, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants