Skip to content

Commit 79b23f4

Browse files
Stop processing original response streaming content if user has started navigating away (#50814)
* Reproduce #50733 as a failing E2E test * Don't process original request blazor-ssr content if the user has already navigated away * Quarantine (actually comment out) one of the CanRenderComponentWithPersistedState cases See #50810 * Update EventTest.cs * Disable another flaky test
1 parent acdb4fd commit 79b23f4

File tree

7 files changed

+65
-7
lines changed

7 files changed

+65
-7
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ private void SendBatchAsStreamingUpdate(in RenderBatch renderBatch, TextWriter w
118118
}
119119

120120
// Now process the list, skipping any we've already visited in an earlier iteration
121+
var isEnhancedNavigation = IsProgressivelyEnhancedNavigation(_httpContext.Request);
121122
for (var i = 0; i < componentIdsInDepthOrder.Length; i++)
122123
{
123124
var componentId = componentIdsInDepthOrder[i].ComponentId;
@@ -132,7 +133,7 @@ private void SendBatchAsStreamingUpdate(in RenderBatch renderBatch, TextWriter w
132133
// as it is being written out.
133134
writer.Write($"<template blazor-component-id=\"");
134135
writer.Write(componentId);
135-
writer.Write("\">");
136+
writer.Write(isEnhancedNavigation ? "\" enhanced-nav=\"true\">" : "\">");
136137

137138
// We don't need boundary markers at the top-level since the info is on the <template> anyway.
138139
WriteComponentHtml(componentId, writer, allowBoundaryMarkers: false);

src/Components/Web.JS/dist/Release/blazor.web.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/Rendering/StreamingRendering.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
import { SsrStartOptions } from '../Platform/SsrStartOptions';
5-
import { NavigationEnhancementCallbacks, performEnhancedPageLoad, replaceDocumentWithPlainText } from '../Services/NavigationEnhancement';
5+
import { NavigationEnhancementCallbacks, hasNeverStartedAnyEnhancedPageLoad, performEnhancedPageLoad, replaceDocumentWithPlainText } from '../Services/NavigationEnhancement';
66
import { isWithinBaseUriSpace, toAbsoluteUri } from '../Services/NavigationUtils';
77
import { synchronizeDomContent } from './DomMerging/DomSync';
88

@@ -36,7 +36,14 @@ class BlazorStreamingUpdate extends HTMLElement {
3636
if (node instanceof HTMLTemplateElement) {
3737
const componentId = node.getAttribute('blazor-component-id');
3838
if (componentId) {
39-
insertStreamingContentIntoDocument(componentId, node.content);
39+
// For enhanced nav page loads, we automatically cancel the response stream if another enhanced nav supersedes it. But there's
40+
// no way to cancel the original page load. So, to avoid continuing to process <blazor-ssr> blocks from the original page load
41+
// if an enhanced nav supersedes it, we must explicitly check whether this content is from the original page load, and if so,
42+
// ignore it if any enhanced nav has started yet. Fixes https://github.com/dotnet/aspnetcore/issues/50733
43+
const isFromEnhancedNav = node.getAttribute('enhanced-nav') === 'true';
44+
if (isFromEnhancedNav || hasNeverStartedAnyEnhancedPageLoad()) {
45+
insertStreamingContentIntoDocument(componentId, node.content);
46+
}
4047
} else {
4148
switch (node.getAttribute('type')) {
4249
case 'redirection':

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export function isPageLoading() {
4444
return performingEnhancedPageLoad || document.readyState === 'loading';
4545
}
4646

47+
export function hasNeverStartedAnyEnhancedPageLoad() {
48+
return !currentEnhancedNavigationAbortController;
49+
}
50+
4751
export function attachProgressivelyEnhancedNavigationListener(callbacks: NavigationEnhancementCallbacks) {
4852
navigationEnhancementCallbacks = callbacks;
4953
document.addEventListener('click', onDocumentClick);

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,50 @@ static async Task<string> BandwidthThrottledGet(string url, int chunkLength, int
222222
}
223223
}
224224
}
225+
226+
[Theory]
227+
[InlineData(false)]
228+
[InlineData(true)]
229+
public async void StopsProcessingStreamingOutputFromPreviousRequestAfterEnhancedNav(bool duringEnhancedNavigation)
230+
{
231+
IWebElement originalH1Elem;
232+
233+
if (duringEnhancedNavigation)
234+
{
235+
Navigate($"{ServerPathBase}/nav");
236+
originalH1Elem = Browser.Exists(By.TagName("h1"));
237+
Browser.Equal("Hello", () => originalH1Elem.Text);
238+
Browser.Exists(By.TagName("nav")).FindElement(By.LinkText("Streaming")).Click();
239+
}
240+
else
241+
{
242+
Navigate($"{ServerPathBase}/streaming");
243+
originalH1Elem = Browser.Exists(By.TagName("h1"));
244+
}
245+
246+
// Initial "waiting" state
247+
Browser.Equal("Streaming Rendering", () => originalH1Elem.Text);
248+
var getStatusText = () => Browser.Exists(By.Id("status"));
249+
var getDisplayedItems = () => Browser.FindElements(By.TagName("li"));
250+
var addItemsUrl = Browser.FindElement(By.Id("add-item-link")).GetDomProperty("href");
251+
var endResponseUrl = Browser.FindElement(By.Id("end-response-link")).GetDomProperty("href");
252+
Assert.Equal("Waiting for more...", getStatusText().Text);
253+
Assert.Empty(getDisplayedItems());
254+
Assert.StartsWith("http", addItemsUrl);
255+
256+
// Navigate away using enhanced nav, before the response is completed
257+
Browser.Exists(By.TagName("nav")).FindElement(By.LinkText("Streaming with interactivity")).Click();
258+
Browser.Equal("Streaming Rendering with Interactivity", () => originalH1Elem.Text);
259+
var statusElem = Browser.FindElement(By.Id("status"));
260+
Assert.Equal("Not streaming", statusElem.Text);
261+
262+
// Now if the earlier navigation produces more output, we do *not* add it to the page
263+
var addItemsOutput = await new HttpClient().GetStringAsync(addItemsUrl);
264+
Assert.Equal("Added item", addItemsOutput);
265+
await Task.Delay(1000); // Make sure we would really have seen the UI change by now if it was going to
266+
Browser.Equal("Not streaming", () => statusElem.Text); // It didn't get removed from the doc, nor was its text updated
267+
268+
// Tidy up
269+
await new HttpClient().GetAsync(endResponseUrl);
270+
}
225271
}

src/Components/test/E2ETest/Tests/EventTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ public void DragDrop_CanTrigger()
246246
var actions = new Actions(Browser).DragAndDrop(input, target);
247247

248248
actions.Perform();
249-
// drop doesn't seem to trigger in Selenium. But it's sufficient to determine "any" drag event works
250-
Browser.Equal("dragstart,", () => output.Text);
249+
// drop doesn't reliably trigger in Selenium. But it's sufficient to determine "any" drag event works
250+
Browser.True(() => output.Text.StartsWith("dragstart,", StringComparison.Ordinal));
251251
}
252252

253253
[Fact]

src/Components/test/E2ETest/Tests/StatePersistenceTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public override Task InitializeAsync()
5050
[InlineData(false, typeof(InteractiveServerRenderMode), "ServerStreaming")]
5151
[InlineData(false, typeof(InteractiveWebAssemblyRenderMode), (string)null)]
5252
[InlineData(false, typeof(InteractiveWebAssemblyRenderMode), "WebAssemblyStreaming")]
53-
[InlineData(false, typeof(InteractiveAutoRenderMode), (string)null)]
53+
// [InlineData(false, typeof(InteractiveAutoRenderMode), (string)null)] https://github.com/dotnet/aspnetcore/issues/50810
5454
// [InlineData(false, typeof(InteractiveAutoRenderMode), "AutoStreaming")] https://github.com/dotnet/aspnetcore/issues/50810
5555
public void CanRenderComponentWithPersistedState(bool suppressEnhancedNavigation, Type renderMode, string streaming)
5656
{

0 commit comments

Comments
 (0)