Skip to content

fix(core): prevent double invocation of load listener on img/iframe during event replay#68178

Draft
arturovt wants to merge 1 commit intoangular:mainfrom
arturovt:test/issue_59260
Draft

fix(core): prevent double invocation of load listener on img/iframe during event replay#68178
arturovt wants to merge 1 commit intoangular:mainfrom
arturovt:test/issue_59260

Conversation

@arturovt
Copy link
Copy Markdown
Contributor

@arturovt arturovt commented Apr 13, 2026

When SSR with withEventReplay() is used in zoneless mode, Angular registers a native DOM listener on elements like <img> via renderer.listen, and also stashes the same listener in __jsaction_fns for replay. After hydration, invokeListeners calls the handler once (the replay), but the browser independently fires a native load event on the element as it finishes loading — triggering the listener a second time.

The fix introduces a counter map on the element (keyed by event type) that invokeListeners increments before calling handlers. The native DOM listener then checks this map via shouldSkipNativeListener and skips itself if a replay already handled the event.

The check is limited to elements that load external resources on their own (img, iframe, script, link, object, embed, input) — bubbling events like click on other elements are unaffected.

shouldSkipNativeListener follows the enableImpl pattern: it defaults to () => false so terser/esbuild eliminate the branch entirely in apps that don't use event replay. enableSkipNativeListenerImpl() is called from the withEventReplay() environment initializer to swap in the real implementation.

Fixes #59260

@angular-robot angular-robot bot added the area: core Issues related to the framework runtime label Apr 13, 2026
@ngbot ngbot bot added this to the Backlog milestone Apr 13, 2026
@thePunderWoman
Copy link
Copy Markdown
Contributor

Hey @arturovt, per discussion offline, we couldn't land this as it is, as it's just a failing test, which would break CI. The only way we would accept this is if it also included the fix.

@arturovt
Copy link
Copy Markdown
Contributor Author

Hey @arturovt, per discussion offline, we couldn't land this as it is, as it's just a failing test, which would break CI. The only way we would accept this is if it also included the fix.

makes sense, I've been working on a fix locally, I just need to sort things out, I would push the fix to the same branch. Will keep it in draft for now.

…uring event replay

When SSR with `withEventReplay()` is used in zoneless mode, Angular registers a native DOM listener on elements like `<img>` via `renderer.listen`, and also stashes the same listener in `__jsaction_fns` for replay. After hydration, `invokeListeners` calls the handler once (the replay), but the browser independently fires a native `load` event on the element as it finishes loading — triggering the listener a second time.

The fix introduces a counter map on the element (keyed by event type) that `invokeListeners` increments before calling handlers. The native DOM listener then checks this map via `shouldSkipNativeListener` and skips itself if a replay already handled the event.

The check is limited to elements that load external resources on their own (`img`, `iframe`, `script`, `link`, `object`, `embed`, `input`) — bubbling events like `click` on other elements are unaffected.

`shouldSkipNativeListener` follows the `enableImpl` pattern: it defaults to `() => false` so terser/esbuild eliminate the branch entirely in apps that don't use event replay. `enableSkipNativeListenerImpl()` is called from the `withEventReplay()` environment initializer to swap in the real implementation.

Fixes angular#59260
@arturovt arturovt changed the title test(core): add failing test for (load) event being replayed twice with withEventReplay() fix(core): prevent double invocation of load listener on img/iframe during event replay Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Issues related to the framework runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The load event on image elements is triggered twice with event replay

2 participants