Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 15, 2025

Throw error when custom event name matches browserEventName to prevent double-triggering

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Prevent double event dispatch when custom event shares name with browser event

Description

Registering a custom event with the same name as its browserEventName causes the event to fire twice - once for the native browser event and once for the custom event wrapper. This was a silent failure that led to confusing behavior.

// This now throws instead of causing double-triggering:
Blazor.registerCustomEventType('scrolltop', { 
    browserEventName: 'scrolltop'  // ❌ Error: names must differ
});

// Correct usage:
Blazor.registerCustomEventType('customscroll', { 
    browserEventName: 'scrolltop'  // ✅ Works as expected
});

Changes:

  • Added validation in registerCustomEventType to throw when eventName === options.browserEventName
  • Added E2E test covering the error case
  • Error message guides developers to choose a different custom event name

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • accounts.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9278 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.zp1m5U --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9969198621140636915,366823893651791886,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
    • Triggering command: /opt/google/chrome/chrome /opt/google/chrome/chrome --allow-pre-commit-input --disable-REDACTED-networking --disable-client-side-phishing-detection --disable-default-apps --disable-features=IgnoreDuplicateNavs,Prewarm --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-logging=stderr --headless=new --ignore-certificate-errors --log-level=0 --no-first-run --no-sandbox --no-service-autorun --password-store=basic --remote-debugging-port=0 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9426 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.0oRQDJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,11294284216419269144,6731500018699212375,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
  • android.clients.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9278 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.zp1m5U --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9969198621140636915,366823893651791886,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
    • Triggering command: /opt/google/chrome/chrome /opt/google/chrome/chrome --allow-pre-commit-input --disable-REDACTED-networking --disable-client-side-phishing-detection --disable-default-apps --disable-features=IgnoreDuplicateNavs,Prewarm --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-logging=stderr --headless=new --ignore-certificate-errors --log-level=0 --no-first-run --no-sandbox --no-service-autorun --password-store=basic --remote-debugging-port=0 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9426 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.0oRQDJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,11294284216419269144,6731500018699212375,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
  • clients2.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9278 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.zp1m5U --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9969198621140636915,366823893651791886,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
    • Triggering command: /opt/google/chrome/chrome /opt/google/chrome/chrome --allow-pre-commit-input --disable-REDACTED-networking --disable-client-side-phishing-detection --disable-default-apps --disable-features=IgnoreDuplicateNavs,Prewarm --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-logging=stderr --headless=new --ignore-certificate-errors --log-level=0 --no-first-run --no-sandbox --no-service-autorun --password-store=basic --remote-debugging-port=0 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9426 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.0oRQDJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,11294284216419269144,6731500018699212375,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
  • content-autofill.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9278 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.zp1m5U --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9969198621140636915,366823893651791886,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
    • Triggering command: /opt/google/chrome/chrome /opt/google/chrome/chrome --allow-pre-commit-input --disable-REDACTED-networking --disable-client-side-phishing-detection --disable-default-apps --disable-features=IgnoreDuplicateNavs,Prewarm --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-logging=stderr --headless=new --ignore-certificate-errors --log-level=0 --no-first-run --no-sandbox --no-service-autorun --password-store=basic --remote-debugging-port=0 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9426 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.0oRQDJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,11294284216419269144,6731500018699212375,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
  • googlechromelabs.github.io
    • Triggering command: /usr/local/bin/node node install.mjs (dns block)
  • https://storage.googleapis.com/chrome-for-testing-public/142.0.7444.175/linux64/chrome-headless-shell-linux64.zip
    • Triggering command: /usr/local/bin/node node install.mjs (http block)
  • optimizationguide-pa.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9278 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.zp1m5U --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9969198621140636915,366823893651791886,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
    • Triggering command: /opt/google/chrome/chrome /opt/google/chrome/chrome --allow-pre-commit-input --disable-REDACTED-networking --disable-client-side-phishing-detection --disable-default-apps --disable-features=IgnoreDuplicateNavs,Prewarm --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-logging=stderr --headless=new --ignore-certificate-errors --log-level=0 --no-first-run --no-sandbox --no-service-autorun --password-store=basic --remote-debugging-port=0 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9426 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.0oRQDJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,11294284216419269144,6731500018699212375,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
  • www.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9278 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.zp1m5U --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,9969198621140636915,366823893651791886,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)
    • Triggering command: /opt/google/chrome/chrome /opt/google/chrome/chrome --allow-pre-commit-input --disable-REDACTED-networking --disable-client-side-phishing-detection --disable-default-apps --disable-features=IgnoreDuplicateNavs,Prewarm --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-logging=stderr --headless=new --ignore-certificate-errors --log-level=0 --no-first-run --no-sandbox --no-service-autorun --password-store=basic --remote-debugging-port=0 (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --ignore-certificate-errors --use-angle=swiftshader-webgl --ignore-certificate-errors --crashpad-handler-pid=9426 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/.org.chromium.Chromium.scoped_dir.0oRQDJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,11294284216419269144,6731500018699212375,262144 --disable-features=IgnoreDuplicateNavs,PaintHolding,Prewarm --variations-seed-version --trace-process-track-uuid=3190708989122997041 --enable-logging=stderr (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Blazor WASM Custom javascript dispatching of new Event Gets Triggered Twice</issue_title>
<issue_description>### Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I'm using Blazor .NET 8 WASM

I'm planning to do listening of scroll bottom or specialized scroll bottom with padding. But when i use el.dispatchEvent(new Event('onscrolltop', {bubbles:true}))

with blazor event @onscrolltop="MyCallback" the blazor code is triggered twice.

Expected Behavior

event should only trigger once.

Steps To Reproduce

to replicate

dotnet new blazor -int WebAssembly -ai -o TestBlazor1

add TestBlazor1.Client/wwwroot/TestBlazor1.Client.lib.module.js

export function afterWebStarted(blazor) {
    blazor.registerCustomEventType('scrolltop', {
      browserEventName: 'scrolltop',
      createEventArgs: evt=>({
        scrollTop: evt.target.scrollTop,
        scrollLeft: evt.target.scrollLeft

      })
    //   createEventArgs: eventArgsCreator
    });
  }

add TestBlazor1.Client/Pages/Test.razor

@page "/test"

<h3>Test</h3>
<div
style='height: 200px; overflow-y: scroll; border: 1px solid black;'
@onscrolltop="ConsoleWrite"
@onscrolltop:stopPropagation="true"
thisisnotevent-onscroll="this.dispatchEvent(new Event('scrolltop', {bubbles:true}));"
onclick="this.dispatchEvent(new Event('scrolltop', {bubbles:true}));">

@for(var i = 0; i< 10000; ++i)
{
    <div>Item @i</div>
}

</div>

@code{

    private void ConsoleWrite(ScrollEventArgs evt)
    {
        Console.WriteLine("Scroll Top Triggered " + evt.ScrollLeft + " , " + evt.ScrollTop);

    }
}

add TestBlazor1.Client/EventHandlers.cs

using Microsoft.AspNetCore.Components;

namespace TestBlazor1.Client;


public class ScrollEventArgs : EventArgs
{
    public double ScrollTop { get; set; }
    public double ScrollLeft{get;set;}
}

[EventHandler("onscrolltop", typeof(ScrollEventArgs),
    enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}

When clicking the div with list of entries. the event will trigger twice. I tried @onscrolltop:stopPropagation="true", but it still triggers twice. Removing bubble will not trigger the event. I also tried javascript onscroll, it also triggers twice

Exceptions (if any)

No response

.NET Version

8.0.302

Anything else?

Microsoft.AspNetCore.Components.WebAssembly Version="8.0.6"

dotnet --info output

.NET SDK:
Version: 8.0.302
Commit: ef14e02af8
Workload version: 8.0.300-manifests.5273bb1c
MSBuild version: 17.10.4+10fbfbf2e

Runtime Environment:
OS Name: Windows
OS Version: 10.0.22631
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\8.0.302\

.NET workloads installed:
[aspire]
Installation Source: VS 17.10.35013.160
Manifest Version: 8.0.0/8.0.100
Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.aspire\8.0.0\WorkloadManifest.json
Install Type: FileBased

Host:
Version: 8.0.6
Architecture: x64
Commit: 3b8b000a0e

.NET SDKs installed:
8.0.204 [C:\Program Files\dotnet\sdk]
8.0.302 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 8.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 8.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 8.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download</issue_description>

<agent_instructions>We want to report an error. Ideally we want to cover this with an E2E test case if possible</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@javiercn @stevenpua thanks for contacting us.
@onscrolltop="ConsoleWrite"
@onscrolltop:stopPropagation="true"
onscroll1="this.dispatchEvent(new Event('scrolltop', {bubbles:true}));"
onclick="this.dispatchEvent(new Event('scrolltop', {bubbles:true}));">

You seem to have registered two separate events here, isn't that the reason for the double dispatc...


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits December 15, 2025 14:02
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix Blazor WASM custom event triggering twice Throw error when custom event name matches browserEventName to prevent double-triggering Dec 15, 2025
Copilot AI requested a review from javiercn December 15, 2025 14:24
@javiercn javiercn marked this pull request as ready for review December 17, 2025 14:02
@javiercn javiercn requested a review from a team as a code owner December 17, 2025 14:02
Copilot AI review requested due to automatic review settings December 17, 2025 14:02
Copy link
Member

@pavelsavara pavelsavara left a comment

Choose a reason for hiding this comment

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

Is this a breaking change ?

Copy link
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 adds validation to prevent a double-triggering bug in Blazor's custom event registration. When a custom event name matches its browserEventName, both the native browser event and the custom event wrapper would fire, causing confusing behavior. The fix throws an error with a helpful message when this misconfiguration is detected.

Key Changes:

  • Added validation in registerCustomEventType to throw when eventName === options.browserEventName
  • Added E2E test to verify the error is thrown correctly
  • Includes descriptive error message to guide developers toward correct usage

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/Components/Web.JS/src/Rendering/Events/EventTypes.ts Adds validation to throw error when custom event name matches browserEventName
src/Components/test/E2ETest/Tests/EventCustomArgsTest.cs Adds E2E test to verify error is thrown for matching names
src/Components/test/testassets/BasicTestApp/EventCustomArgsComponent.razor Adds UI button to trigger the validation error in test

The implementation is solid and follows existing patterns in the codebase. The error message is clear and helpful, and the E2E test adequately covers the new validation. No issues were identified during the review.

@javiercn
Copy link
Member

@pavelsavara the pre-existing behavior is problematic (dispatches the event twice) and the consensus on the issue from Steve and others is to not allow this. In general, we don't want custom events to stomp over the names of existing events, so we want to provide a better error experience here.

@javiercn javiercn merged commit b51307a into main Dec 17, 2025
31 of 32 checks passed
@javiercn javiercn deleted the copilot/fix-blazor-event-triggering branch December 17, 2025 17:08
@dotnet-policy-service dotnet-policy-service bot added this to the 11.0-preview1 milestone Dec 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Blazor WASM Custom javascript dispatching of new Event Gets Triggered Twice

3 participants