Don't render route component if OnNavigateAsync task in-progress (#24225)

This commit is contained in:
Safia Abdalla 2020-07-29 11:37:52 -07:00 committed by GitHub
parent 5e49fc336e
commit f88034902a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 12 deletions

View File

@ -152,6 +152,19 @@ namespace Microsoft.AspNetCore.Components.Routing
internal virtual void Refresh(bool isNavigationIntercepted)
{
// If an `OnNavigateAsync` task is currently in progress, then wait
// for it to complete before rendering. Note: because _previousOnNavigateTask
// is initialized to a CompletedTask on initialization, this will still
// allow first-render to complete successfully.
if (_previousOnNavigateTask.Status != TaskStatus.RanToCompletion)
{
if (Navigating != null)
{
_renderHandle.Render(Navigating);
}
return;
}
RefreshRouteTable();
var locationPath = NavigationManager.ToBaseRelativePath(_locationAbsolute);
@ -248,19 +261,15 @@ namespace Microsoft.AspNetCore.Components.Routing
var previousTask = _previousOnNavigateTask;
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
_previousOnNavigateTask = tcs.Task;
try
// And pass an indicator for the previous task to the currently running one.
var shouldRefresh = await RunOnNavigateAsync(path, previousTask);
tcs.SetResult();
if (shouldRefresh)
{
// And pass an indicator for the previous task to the currently running one.
var shouldRefresh = await RunOnNavigateAsync(path, previousTask);
if (shouldRefresh)
{
Refresh(isNavigationIntercepted);
}
}
finally
{
tcs.SetResult();
Refresh(isNavigationIntercepted);
}
}
private void OnLocationChanged(object sender, LocationChangedEventArgs args)

View File

@ -578,6 +578,29 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
Assert.NotNull(errorUiElem);
}
[Fact]
public void OnNavigate_DoesNotRenderWhileOnNavigateExecuting()
{
var app = Browser.MountTestComponent<TestRouterWithOnNavigate>();
// Navigate to a route
SetUrlViaPushState("/WithParameters/name/Abc");
// Click the button to trigger a re-render
var button = app.FindElement(By.Id("trigger-rerender"));
button.Click();
// Assert that the parameter route didn't render
Browser.DoesNotExist(By.Id("test-info"));
// Navigate to another page to cancel the previous `OnNavigateAsync`
// task and trigger a re-render on its completion
SetUrlViaPushState("/LongPage1");
// Confirm that the route was rendered
Browser.Equal("This is a long page you can scroll.", () => app.FindElement(By.Id("test-info")).Text);
}
private long BrowserScrollY
{
get => (long)((IJavaScriptExecutor)Browser).ExecuteScript("return window.scrollY");

View File

@ -1,5 +1,9 @@
@using Microsoft.AspNetCore.Components.Routing
@using System.Threading
<button @onclick="TriggerRerender" id="trigger-rerender">Trigger Rerender</button>
<Router AppAssembly="@typeof(BasicTestApp.Program).Assembly" OnNavigateAsync="@OnNavigateAsync">
<Navigating>
<div style="padding: 20px;background-color:blue;color:white;" id="loading-banner">
@ -21,7 +25,8 @@
{
{ "LongPage1", new Func<NavigationContext, Task>(TestLoadingPageShows) },
{ "LongPage2", new Func<NavigationContext, Task>(TestOnNavCancel) },
{ "Other", new Func<NavigationContext, Task>(TestOnNavException) }
{ "Other", new Func<NavigationContext, Task>(TestOnNavException) },
{"WithParameters/name/Abc", new Func<NavigationContext, Task>(TestRefreshHandling)}
};
private async Task OnNavigateAsync(NavigationContext args)
@ -50,4 +55,14 @@
await Task.CompletedTask;
throw new Exception("This is an uncaught exception.");
}
public static async Task TestRefreshHandling(NavigationContext args)
{
await Task.Delay(Timeout.Infinite, args.CancellationToken);
}
private void TriggerRerender()
{
Console.WriteLine("Nothing to see here, just an even to trigger a re-render...");
}
}