Skip to content

Make registerCustomEventType idempotent for duplicate registrations.#66324

Closed
ilonatommy wants to merge 3 commits intomainfrom
fix-fluentUI-double-registration
Closed

Make registerCustomEventType idempotent for duplicate registrations.#66324
ilonatommy wants to merge 3 commits intomainfrom
fix-fluentUI-double-registration

Conversation

@ilonatommy
Copy link
Copy Markdown
Member

@ilonatommy ilonatommy commented Apr 15, 2026

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 FluentUI tests to E2E.

Problem

Blazor.registerCustomEventType() throws an unconditional error when called with an event name that is already registered:

The event '${eventName}' 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 initializer afterStarted hook. 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 browserEventNamesToAliases map 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. The eventNameAliasRegisteredCallbacks (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:

  • JS initializer hooks (lib.module.js with beforeStart/afterStarted) did NOT exist - they were introduced in .NET 7
  • There was no scenario where the function would be called twice.

The 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

Framework / API Behavior on duplicate registration
customElements.define() (Web standard) Throws DOMException. Community convention is to guard with customElements.get() before calling.
Vue (Vue.component) Silently overwrites — last registration wins, no error.
React No registry — components are JS values, last assignment wins.
Angular (@NgModule) Throws at compile/runtime.
Lit / FAST (web component libs) Libraries guard with customElements.get() before define().

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

  • First registration wins - the original createEventArgs function is preserved
  • No double dispatching - the alias array is not modified on duplicate
  • Console warning - developers can see when duplicates occur (visible in browser DevTools)
  • Other validations preserved - the eventName === options.browserEventName check still throws etc.

@ilonatommy ilonatommy self-assigned this Apr 15, 2026
@ilonatommy ilonatommy requested a review from a team as a code owner April 15, 2026 08:45
@ilonatommy ilonatommy added the area-blazor Includes: Blazor, Razor Components label Apr 15, 2026
Copilot AI review requested due to automatic review settings April 15, 2026 08:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 registerCustomEventType to 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.

Comment thread src/Components/Web.JS/src/Rendering/Events/EventTypes.ts Outdated
Comment thread src/Components/Web.JS/test/EventTypes.test.ts Outdated
Comment thread src/Components/Web.JS/test/EventTypes.test.ts Outdated
Comment thread src/Components/Web.JS/test/EventTypes.test.ts
Comment on lines +23 to +25
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.');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@javiercn
Copy link
Copy Markdown
Member

This causes failures for libraries like FluentUI Blazor (Microsoft.FluentUI.AspNetCore.Components) under specific conditions. FluentUI registers ~18 custom events in its JS initializer afterStarted hook. 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.

When does it fire a second time? This feels like a fluent UI bug that we shouldn't be working around

@ilonatommy
Copy link
Copy Markdown
Member Author

This causes failures for libraries like FluentUI Blazor (Microsoft.FluentUI.AspNetCore.Components) under specific conditions. FluentUI registers ~18 custom events in its JS initializer afterStarted hook. 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.

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. FluentUI's JS initializer registers custom events (dateselected etc.) and throws when called twice across hosts. Let me check it in more details.

@ilonatommy
Copy link
Copy Markdown
Member Author

When does it fire a second time? This feels like a fluent UI bug that we shouldn't be working around

Right, FluentUI registers the same name for both the custom event and the browser events ('dateselected', 'scrollstart', 'scrollend', 'splitterresized', 'splittercollapsed'):
https://github.com/microsoft/fluentui-blazor/blob/c1e68fc9de5df23ebdda140c1d197d2abd855784/src/Core.Assets/src/index.ts#L267

So I got the wrong line, we throw on this check:

throw new Error(`The custom event '${eventName}' cannot have the same name as its browserEventName '${options.browserEventName}'. Choose a different name for the custom event.`);

It is a FluentUI bug that got fixed by adding fluent prefix in microsoft/fluentui-blazor#4609. Choosing a preview version of FluentUI (5.0.0-rc.1-26048.1) solves this.

Closing.

@ilonatommy ilonatommy closed this Apr 17, 2026
@dotnet-policy-service dotnet-policy-service Bot added this to the 11.0-preview4 milestone Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants