2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
+ using System . Collections . Generic ;
5
6
using System . Threading . Tasks ;
6
7
using Microsoft . AspNetCore . Components . Rendering ;
7
8
using Microsoft . JSInterop ;
@@ -13,16 +14,73 @@ namespace Microsoft.AspNetCore.Components.Browser
13
14
/// </summary>
14
15
public static class RendererRegistryEventDispatcher
15
16
{
17
+ private static bool isDispatchingEvent ;
18
+ private static Queue < IncomingEventInfo > deferredIncomingEvents
19
+ = new Queue < IncomingEventInfo > ( ) ;
20
+
16
21
/// <summary>
17
22
/// For framework use only.
18
23
/// </summary>
19
24
[ JSInvokable ( nameof ( DispatchEvent ) ) ]
20
25
public static Task DispatchEvent (
21
26
BrowserEventDescriptor eventDescriptor , string eventArgsJson )
22
27
{
23
- var eventArgs = ParseEventArgsJson ( eventDescriptor . EventArgsType , eventArgsJson ) ;
24
- var renderer = RendererRegistry . Current . Find ( eventDescriptor . BrowserRendererId ) ;
25
- return renderer . DispatchEventAsync ( eventDescriptor . EventHandlerId , eventArgs ) ;
28
+ // Be sure we only run one event handler at once. Although they couldn't run
29
+ // simultaneously anyway (there's only one thread), they could run nested on
30
+ // the stack if somehow one event handler triggers another event synchronously.
31
+ // We need event handlers not to overlap because (a) that's consistent with
32
+ // server-side Blazor which uses a sync context, and (b) the rendering logic
33
+ // relies completely on the idea that within a given scope it's only building
34
+ // or processing one batch at a time.
35
+ //
36
+ // The only currently known case where this makes a difference is in the E2E
37
+ // tests in ReorderingFocusComponent, where we hit what seems like a Chrome bug
38
+ // where mutating the DOM cause an element's "change" to fire while its "input"
39
+ // handler is still running (i.e., nested on the stack) -- this doesn't happen
40
+ // in Firefox. Possibly a future version of Chrome may fix this, but even then,
41
+ // it's conceivable that DOM mutation events could trigger this too.
42
+
43
+ if ( isDispatchingEvent )
44
+ {
45
+ var info = new IncomingEventInfo ( eventDescriptor , eventArgsJson ) ;
46
+ deferredIncomingEvents . Enqueue ( info ) ;
47
+ return info . TaskCompletionSource . Task ;
48
+ }
49
+ else
50
+ {
51
+ isDispatchingEvent = true ;
52
+ try
53
+ {
54
+ var eventArgs = ParseEventArgsJson ( eventDescriptor . EventArgsType , eventArgsJson ) ;
55
+ var renderer = RendererRegistry . Current . Find ( eventDescriptor . BrowserRendererId ) ;
56
+ return renderer . DispatchEventAsync ( eventDescriptor . EventHandlerId , eventArgs ) ;
57
+ }
58
+ finally
59
+ {
60
+ isDispatchingEvent = false ;
61
+ if ( deferredIncomingEvents . Count > 0 )
62
+ {
63
+ ProcessNextDeferredEvent ( ) ;
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ private static void ProcessNextDeferredEvent ( )
70
+ {
71
+ var info = deferredIncomingEvents . Dequeue ( ) ;
72
+ var task = DispatchEvent ( info . EventDescriptor , info . EventArgsJson ) ;
73
+ task . ContinueWith ( _ =>
74
+ {
75
+ if ( task . Exception != null )
76
+ {
77
+ info . TaskCompletionSource . SetException ( task . Exception ) ;
78
+ }
79
+ else
80
+ {
81
+ info . TaskCompletionSource . SetResult ( null ) ;
82
+ }
83
+ } ) ;
26
84
}
27
85
28
86
private static UIEventArgs ParseEventArgsJson ( string eventArgsType , string eventArgsJson )
@@ -78,5 +136,19 @@ public class BrowserEventDescriptor
78
136
/// </summary>
79
137
public string EventArgsType { get ; set ; }
80
138
}
139
+
140
+ readonly struct IncomingEventInfo
141
+ {
142
+ public readonly BrowserEventDescriptor EventDescriptor ;
143
+ public readonly string EventArgsJson ;
144
+ public readonly TaskCompletionSource < object > TaskCompletionSource ;
145
+
146
+ public IncomingEventInfo ( BrowserEventDescriptor eventDescriptor , string eventArgsJson )
147
+ {
148
+ EventDescriptor = eventDescriptor ;
149
+ EventArgsJson = eventArgsJson ;
150
+ TaskCompletionSource = new TaskCompletionSource < object > ( ) ;
151
+ }
152
+ }
81
153
}
82
154
}
0 commit comments