Make registerCustomEventType idempotent for duplicate registrations.#66324
Make registerCustomEventType idempotent for duplicate registrations.#66324ilonatommy wants to merge 3 commits intomainfrom
registerCustomEventType idempotent for duplicate registrations.#66324Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Blazor Web.JS custom event registry to tolerate duplicate calls to registerCustomEventType by warning and ignoring duplicates (first registration wins), which helps scenarios where JS initializers re-run (e.g., enhanced navigation, reconnection, hot reload).
Changes:
- Change
registerCustomEventTypeto warn and early-return on duplicate event name registrations instead of throwing. - Add Jest coverage validating duplicate-registration behavior (no throw, first registration preserved, no duplicate alias entries).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/Components/Web.JS/src/Rendering/Events/EventTypes.ts | Makes duplicate custom event registration idempotent by warning and returning early. |
| src/Components/Web.JS/test/EventTypes.test.ts | Adds tests to verify duplicates are ignored, warning behavior occurs, and aliases aren’t duplicated. |
| console.warn(`The event '${eventName}' is being registered more than once. ` + | ||
| 'Duplicate registrations will be ignored. ' + | ||
| 'This may indicate that a JS initializer is running multiple times.'); |
There was a problem hiding this comment.
Do we have an estimate on how often would you see this in the legitimate cases? Just so we don't spam the console so much that people would view it as the app being buggy.
There was a problem hiding this comment.
I've been looking more at this. And we shouldn't change this to a warning.
There was already an issue we fixed with the event being dispatched twice. Now we are converting something deterministic and invalid into something "valid" that produces nondeterministic behavior (first registration wins). And the only way to find it is if you happen to look at the console errors, which is not something we can generally rely on people doing.
When does it fire a second time? This feels like a fluent UI bug that we shouldn't be working around |
I was hitting it with E2E tests that have multi-host setup in one process. |
Right, So I got the wrong line, we throw on this check: It is a Closing. |
Summary
Change
Blazor.registerCustomEventType()from throwing on duplicate event name registration to warning and ignoring the duplicate. The first registration wins.This is a prerequisite change for adding proper
FluentUItests to E2E.Problem
Blazor.registerCustomEventType()throws an unconditional error when called with an event name that is already registered:This causes failures for libraries like FluentUI Blazor (
Microsoft.FluentUI.AspNetCore.Components) under specific conditions. FluentUI registers ~18 custom events in its JS initializerafterStartedhook. On first load, everything works fine. However, when the initializer fires a second time in the same page context all re-registrations throw and the app breaks.What happens if duplicates are allowed through without guarding?
If the code does NOT early-return and lets a duplicate registration run to completion, the
browserEventNamesToAliasesmap gets the same event name pushed into its alias array a second time (line 43:aliasGroup.push(eventName)). This causes double event dispatching - each native DOM event would trigger the Blazor handler twice. TheeventNameAliasRegisteredCallbacks(line 52) would also fire redundantly, causing additional duplicate listener setup.The early-return before these lines prevents both problems.
History
The throw was introduced in #29993.
At the time:
lib.module.jswithbeforeStart/afterStarted) did NOT exist - they were introduced in .NET 7The PR review comment on the throw said: "I'd err on the side of strictness. We can always loosen such rules later, but can't tighten them." - explicitly acknowledging the rule could be relaxed in the future.
How other frameworks handle this
customElements.define()(Web standard)DOMException. Community convention is to guard withcustomElements.get()before calling.Vue.component)@NgModule)customElements.get()beforedefine().Vue and React are permissive. Angular and the Web Components spec are strict. However, the strict frameworks don't have an equivalent of Blazor's JS initializer hooks that can re-fire - their registration points run exactly once.
Change
createEventArgsfunction is preservedeventName === options.browserEventNamecheck still throws etc.