Skip to content

Commit a7ebaef

Browse files
Fix for the recursive batch rendering bug. Needs more tests though.
1 parent acb01f8 commit a7ebaef

File tree

7 files changed

+42
-6
lines changed

7 files changed

+42
-6
lines changed

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/dist/Release/blazor.webassembly.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Boot.Server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ async function initializeConnection(options: CircuitStartOptions, logger: Logger
8989
const connection = connectionBuilder.build();
9090

9191
setEventDispatcher((descriptor, args) => {
92-
return connection.send('DispatchBrowserEvent', JSON.stringify(descriptor), JSON.stringify(args));
92+
connection.send('DispatchBrowserEvent', JSON.stringify(descriptor), JSON.stringify(args));
9393
});
9494

9595
// Configure navigation via SignalR

src/Components/Web.JS/src/Boot.WebAssembly.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
2222
}
2323
started = true;
2424

25-
setEventDispatcher((eventDescriptor, eventArgs) => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Components.WebAssembly', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs)));
25+
setEventDispatcher((eventDescriptor, eventArgs) => {
26+
// It's extremely unusual, but an event can be raised while we're in the middle of synchronously applying a
27+
// renderbatch. For example, a renderbatch might mutate the DOM in such a way as to cause an <input> to lose
28+
// focus, in turn triggering a 'change' event. It may also be possible to listen to other DOM mutation events
29+
// that are themselves triggered by the application of a renderbatch.
30+
monoPlatform.invokeWhenHeapUnlocked(() => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Components.WebAssembly', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs)));
31+
});
2632

2733
// Configure environment for execution under Mono WebAssembly with shared-memory rendering
2834
const platform = Environment.setPlatform(monoPlatform);

src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ export const monoPlatform: Platform = {
147147
assertHeapIsNotLocked();
148148
currentHeapLock = new MonoHeapLock();
149149
return currentHeapLock;
150+
},
151+
152+
invokeWhenHeapUnlocked: function(callback) {
153+
// This is somewhat like a sync context. If we're not locked, just pass through the call directly.
154+
if (!currentHeapLock) {
155+
callback();
156+
} else {
157+
currentHeapLock.enqueuePostReleaseAction(callback);
158+
}
150159
}
151160
};
152161

@@ -490,11 +499,31 @@ class MonoHeapLock implements HeapLock {
490499
// Within a given heap lock, it's safe to cache decoded strings since the memory can't change
491500
stringCache = new Map<number, string | null>();
492501

502+
private postReleaseActions?: Function[];
503+
504+
enqueuePostReleaseAction(callback: Function) {
505+
if (!this.postReleaseActions) {
506+
this.postReleaseActions = [];
507+
}
508+
509+
this.postReleaseActions.push(callback);
510+
}
511+
493512
release() {
494513
if (currentHeapLock !== this) {
495514
throw new Error('Trying to release a lock which isn\'t current');
496515
}
497516

498517
currentHeapLock = null;
518+
519+
while (this.postReleaseActions?.length) {
520+
const nextQueuedAction = this.postReleaseActions.shift()!;
521+
522+
// It's possible that the action we invoke here might itself take a succession of heap locks,
523+
// but since heap locks must be released synchronously, by the time we get back to this stack
524+
// frame, we know the heap should no longer be locked.
525+
nextQueuedAction();
526+
assertHeapIsNotLocked();
527+
}
499528
}
500529
}

src/Components/Web.JS/src/Platform/Platform.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface Platform {
2020
readStructField<T extends Pointer>(baseAddress: Pointer, fieldOffset?: number): T;
2121

2222
beginHeapLock(): HeapLock;
23+
invokeWhenHeapUnlocked(callback: Function): void;
2324
}
2425

2526
export interface HeapLock {

src/Components/Web.JS/src/Rendering/RendererEventDispatcher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ export function dispatchEvent(eventDescriptor: EventDescriptor, eventArgs: UIEve
1010
throw new Error('eventDispatcher not initialized. Call \'setEventDispatcher\' to configure it.');
1111
}
1212

13-
return eventDispatcherInstance(eventDescriptor, eventArgs);
13+
eventDispatcherInstance(eventDescriptor, eventArgs);
1414
}
1515

16-
export function setEventDispatcher(newDispatcher: (eventDescriptor: EventDescriptor, eventArgs: UIEventArgs) => Promise<void>): void {
16+
export function setEventDispatcher(newDispatcher: (eventDescriptor: EventDescriptor, eventArgs: UIEventArgs) => void): void {
1717
eventDispatcherInstance = newDispatcher;
1818
}

0 commit comments

Comments
 (0)