Skip to content

Commit 8fa28e8

Browse files
committed
fix auto-pause issue
1 parent ab1a3aa commit 8fa28e8

8 files changed

Lines changed: 124 additions & 47 deletions

File tree

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Streaming.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,6 @@ internal void EmitInitializersIfNecessary(HttpContext httpContext, TextWriter wr
104104
writer.Write("<!--Blazor-Web-Initializers:");
105105
writer.Write(initializersBase64);
106106
writer.Write("-->");
107-
108-
EmitBrowserConfigurationOnce(writer);
109107
}
110108
}
111109

src/Components/Server.AutoPause/src/js/autopause.lib.module.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,30 @@ interface WebStartOptionsLike {
1111
circuit?: Record<string, unknown>;
1212
}
1313

14+
type ServerStartOptionsLike = Record<string, unknown>;
15+
1416
let config: AutoPauseConfig | undefined;
1517
let manager: AutoPauseManager | undefined;
1618

17-
function beforeWebStart(options: WebStartOptionsLike): void {
18-
const enabled = options.circuit?.['autoPauseEnabled'] as boolean | undefined;
19+
function configure(options: Record<string, unknown> | undefined): void {
20+
const enabled = options?.['autoPauseEnabled'] as boolean | undefined;
1921
if (enabled === undefined) {
2022
return;
2123
}
2224
config = {
2325
enabled,
24-
hiddenDelayMilliseconds: options.circuit?.['autoPauseHiddenDelayMilliseconds'] as number | undefined ?? 120000,
26+
hiddenDelayMilliseconds: options?.['autoPauseHiddenDelayMilliseconds'] as number | undefined ?? 120000,
2527
};
2628
}
2729

30+
function beforeWebStart(options: WebStartOptionsLike): void {
31+
configure(options.circuit);
32+
}
33+
34+
function beforeServerStart(options: ServerStartOptionsLike): void {
35+
configure(options);
36+
}
37+
2838
// Called by the framework once Blazor has started; activates auto-pause when AddAutoPause
2939
// enabled it. A second call disposes the previous manager so listeners never accumulate.
3040
function afterWebStarted(blazor: BlazorLike): void {
@@ -41,4 +51,4 @@ function afterWebStarted(blazor: BlazorLike): void {
4151
mgr.start();
4252
}
4353

44-
export { beforeWebStart, beforeWebStart as beforeServerStart, afterWebStarted, afterWebStarted as afterServerStarted };
54+
export { beforeWebStart, beforeServerStart, afterWebStarted, afterWebStarted as afterServerStarted };

src/Components/Server.AutoPause/src/js/test/AutoPauseInitializer.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,16 @@ describe('autopause initializer', () => {
100100
const attached = blazor.added.filter(h => !blazor.removed.includes(h));
101101
expect(attached).toHaveLength(1);
102102
});
103+
104+
it('server start reads auto-pause configuration discovered during enhanced navigation', () => {
105+
const blazor = createBlazor();
106+
107+
beforeWebStart({ circuit: { autoPauseEnabled: false } });
108+
afterWebStarted(blazor);
109+
expect(blazor.added).toHaveLength(0);
110+
111+
beforeServerStart({ autoPauseEnabled: true, autoPauseHiddenDelayMilliseconds: 100 });
112+
afterServerStarted(blazor);
113+
expect(blazor.added).toHaveLength(1);
114+
});
103115
});

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,20 @@ import { RootComponentManager } from './Services/RootComponentManager';
1414
import { WebRendererId } from './Rendering/WebRendererId';
1515
import { addDispatchEventMiddleware } from './Rendering/WebRendererInteropMethods';
1616

17-
let initializersPromise: Promise<void> | undefined;
17+
let circuitOptionsPromise: Promise<Partial<CircuitStartOptions>> | undefined;
1818
let appState: string;
1919
let circuit: CircuitManager;
2020
let options: CircuitStartOptions;
2121
let logger: ConsoleLogger;
2222
let serverStartPromise: Promise<void>;
2323
let circuitStarting: Promise<boolean> | undefined;
2424

25-
export function setCircuitOptions(initializersReady: Promise<Partial<CircuitStartOptions>>) {
26-
if (options) {
25+
export function setCircuitOptions(optionsReady: Promise<Partial<CircuitStartOptions>>) {
26+
if (circuitOptionsPromise) {
2727
throw new Error('Circuit options have already been configured.');
2828
}
2929

30-
initializersPromise = setOptions(initializersReady);
31-
32-
async function setOptions(initializers: Promise<Partial<CircuitStartOptions>>): Promise<void> {
33-
const configuredOptions = await initializers;
34-
options = resolveOptions(configuredOptions);
35-
}
30+
circuitOptionsPromise = optionsReady;
3631
}
3732

3833
export function startServer(components: RootComponentManager<ServerComponentDescriptor>, jsEventRegistry: JSEventRegistry): Promise<void> {
@@ -46,7 +41,7 @@ export function startServer(components: RootComponentManager<ServerComponentDesc
4641
}
4742

4843
async function startServerCore(components: RootComponentManager<ServerComponentDescriptor>, jsEventRegistry: JSEventRegistry, resolve: () => void, _: any) {
49-
await initializersPromise;
44+
options = resolveOptions(await circuitOptionsPromise);
5045
const jsInitializer = await fetchAndInvokeInitializers(options);
5146

5247
appState = discoverServerPersistedState(document) || '';

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

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ function boot(options?: Partial<WebStartOptions>) : Promise<void> {
5959
enhancedNavigationStarted: () => {
6060
jsEventRegistry.dispatchEvent('enhancednavigationstart', {});
6161
},
62+
documentReceived: (source) => {
63+
updateOptionsFromBrowserConfiguration(options, source);
64+
},
6265
documentUpdated: () => {
6366
rootComponentManager.onDocumentUpdated();
6467
resetScrollIfNeeded(ScrollResetSchedule.AfterDocumentUpdate);
@@ -99,8 +102,31 @@ function boot(options?: Partial<WebStartOptions>) : Promise<void> {
99102
}
100103

101104
function onInitialDomContentLoaded(options: Partial<WebStartOptions>) {
102-
// Discover server-emitted browser configuration and merge into options
103-
const browserConfig = discoverBrowserConfiguration(document);
105+
updateOptionsFromBrowserConfiguration(options);
106+
107+
// Retrieve and start invoking the initializers.
108+
// Blazor server options get defaults that are configured before we invoke the initializers
109+
// so we do the same here.
110+
const initialCircuitOptions = resolveOptions(options?.circuit || {});
111+
options.circuit = initialCircuitOptions;
112+
options.webAssembly = options.webAssembly || ({} as WebAssemblyStartOptions);
113+
const logger = new ConsoleLogger(initialCircuitOptions.logLevel);
114+
const initializersPromise = fetchAndInvokeInitializers(options, logger);
115+
setCircuitOptions(resolveConfiguredOptions(initializersPromise, initialCircuitOptions));
116+
setWebAssemblyOptions(resolveConfiguredOptions(initializersPromise, options.webAssembly));
117+
118+
registerAllComponentDescriptors(document);
119+
120+
rootComponentManager.onDocumentUpdated();
121+
122+
// Initialize client-side validation if the page has validatable fields.
123+
initFormValidationIfNeeded();
124+
125+
callAfterStartedCallbacks(initializersPromise);
126+
}
127+
128+
function updateOptionsFromBrowserConfiguration(options: Partial<WebStartOptions>, source: Document = document): void {
129+
const browserConfig = discoverBrowserConfiguration(source);
104130
if (browserConfig) {
105131
if (browserConfig.logLevel !== undefined) {
106132
options.logLevel = browserConfig.logLevel;
@@ -141,37 +167,16 @@ function onInitialDomContentLoaded(options: Partial<WebStartOptions>) {
141167
}
142168
}
143169
}
144-
}
145-
146-
// Retrieve and start invoking the initializers.
147-
// Blazor server options get defaults that are configured before we invoke the initializers
148-
// so we do the same here.
149-
const initialCircuitOptions = resolveOptions(options?.circuit || {});
150-
options.circuit = initialCircuitOptions;
151-
options.webAssembly = options.webAssembly || ({} as WebAssemblyStartOptions);
152-
const logger = new ConsoleLogger(initialCircuitOptions.logLevel);
153-
const initializersPromise = fetchAndInvokeInitializers(options, logger);
154-
setCircuitOptions(resolveConfiguredOptions(initializersPromise, initialCircuitOptions));
155-
setWebAssemblyOptions(resolveConfiguredOptions(initializersPromise, options.webAssembly));
156170

157-
// If BrowserConfiguration had WebAssembly server options, apply them
158-
// before registering component descriptors, since registration triggers
159-
// WebAssembly platform loading which captures these options.
160-
if (browserConfig?.webAssembly) {
161-
rootComponentManager.setWebAssemblyOptions({
162-
environmentName: browserConfig.webAssembly.environmentName ?? '',
163-
environmentVariables: browserConfig.webAssembly.environmentVariables ?? {},
164-
});
171+
// Apply WebAssembly server options before processing component descriptors, since
172+
// registration can trigger platform loading that captures these options.
173+
if (browserConfig.webAssembly) {
174+
rootComponentManager.setWebAssemblyOptions({
175+
environmentName: browserConfig.webAssembly.environmentName ?? '',
176+
environmentVariables: browserConfig.webAssembly.environmentVariables ?? {},
177+
});
178+
}
165179
}
166-
167-
registerAllComponentDescriptors(document);
168-
169-
rootComponentManager.onDocumentUpdated();
170-
171-
// Initialize client-side validation if the page has validatable fields.
172-
initFormValidationIfNeeded();
173-
174-
callAfterStartedCallbacks(initializersPromise);
175180
}
176181

177182
function initFormValidationIfNeeded(): void {

src/Components/Web.JS/src/Services/NavigationEnhancement.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ let currentContentUrl = location.href;
4444

4545
export interface NavigationEnhancementCallbacks {
4646
enhancedNavigationStarted: () => void;
47+
documentReceived: (source: Document) => void;
4748
documentUpdated: () => void;
4849
enhancedNavigationCompleted: () => void;
4950
}
@@ -309,6 +310,7 @@ export async function performEnhancedPageLoad(internalDestinationHref: string, i
309310
if (responseContentType?.startsWith('text/html') && initialContent) {
310311
// For HTML responses, regardless of the status code, display it
311312
const parsedHtml = new DOMParser().parseFromString(initialContent, 'text/html');
313+
navigationEnhancementCallbacks.documentReceived(parsedHtml);
312314
synchronizeDomContent(document, parsedHtml);
313315
navigationEnhancementCallbacks.documentUpdated();
314316
} else if (responseContentType?.startsWith('text/') && initialContent) {

src/Components/test/E2ETest/ServerRenderingTests/BlazorWebJsInitializersTest.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,38 @@ public void InitializersRunsClassicInitializersWhenEnabled(bool streaming, bool
8181
}
8282
}
8383

84+
[Fact]
85+
public void ServerInitializerActivatedAfterEnhancedNavigationFromStaticPage()
86+
{
87+
Navigate($"{ServerPathBase}/initializers?streaming=false&wasm=false&server=false&auto-pause=true&auto-pause-delay-ms=10");
88+
89+
Browser.True(() => (bool)((IJavaScriptExecutor)Browser).ExecuteScript(
90+
"return typeof Blazor.pauseCircuit === 'undefined'"));
91+
92+
((IJavaScriptExecutor)Browser).ExecuteScript(
93+
"Blazor.navigateTo('persistent-state/server-pause?auto-pause=true&auto-pause-delay-ms=10')");
94+
95+
Browser.Exists(By.Id("render-mode-interactive"));
96+
Browser.True(() => (bool)((IJavaScriptExecutor)Browser).ExecuteScript(
97+
"return typeof Blazor.pauseCircuit === 'function'"));
98+
99+
((IJavaScriptExecutor)Browser).ExecuteScript(
100+
"""
101+
window.autoPauseCallCount = 0;
102+
const pauseCircuit = Blazor.pauseCircuit;
103+
Blazor.pauseCircuit = (...args) => {
104+
window.autoPauseCallCount++;
105+
return pauseCircuit(...args);
106+
};
107+
Object.defineProperty(document, 'visibilityState', { configurable: true, get: () => 'hidden' });
108+
Object.defineProperty(document, 'hidden', { configurable: true, get: () => true });
109+
document.dispatchEvent(new Event('visibilitychange'));
110+
""");
111+
112+
Browser.Equal(1L, () => (long)((IJavaScriptExecutor)Browser).ExecuteScript(
113+
"return window.autoPauseCallCount"));
114+
}
115+
84116
private void EnableClassicInitializers(IWebDriver browser)
85117
{
86118
browser.Navigate().GoToUrl($"{new Uri(_serverFixture.RootUri, ServerPathBase)}/");

src/Components/test/testassets/Components.TestServer/RazorComponents/App.razor

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,24 @@
2222
[SupplyParameterFromQuery(Name = "appSetsEventArgsPath")]
2323
public bool AppSetsEventArgsPath { get; set; }
2424

25+
[Parameter]
26+
[SupplyParameterFromQuery(Name = "auto-pause")]
27+
public bool AutoPause { get; set; }
28+
29+
[Parameter]
30+
[SupplyParameterFromQuery(Name = "auto-pause-delay-ms")]
31+
public int AutoPauseDelayMs { get; set; }
32+
33+
private BrowserOptions AutoPauseConfig
34+
{
35+
get
36+
{
37+
var options = new BrowserOptions();
38+
options.AddAutoPause(pause => pause.HiddenDelay = TimeSpan.FromMilliseconds(AutoPauseDelayMs > 0 ? AutoPauseDelayMs : 200));
39+
return options;
40+
}
41+
}
42+
2543
[Parameter]
2644
[SupplyParameterFromQuery(Name = "useOnNavigateAsync")]
2745
public bool ShouldDelayOnNavigateAsync { get; set; }
@@ -104,6 +122,11 @@
104122
<HeadOutlet />
105123
</head>
106124
<body>
125+
@if (AutoPause)
126+
{
127+
<ConfigureBrowser Options="@AutoPauseConfig" />
128+
}
129+
107130
@if(string.Equals(UseCustomRouter, "true", StringComparison.OrdinalIgnoreCase))
108131
{
109132
<CustomRouter AppAssembly="@typeof(App).Assembly">

0 commit comments

Comments
 (0)