From f88034902a8761153aeb9c12658d7915b9d66853 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Wed, 29 Jul 2020 11:37:52 -0700 Subject: [PATCH] Don't render route component if OnNavigateAsync task in-progress (#24225) --- .../Components/src/Routing/Router.cs | 31 ++++++++++++------- .../test/E2ETest/Tests/RoutingTest.cs | 23 ++++++++++++++ .../RouterTest/TestRouterWithOnNavigate.razor | 17 +++++++++- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs index ab95ee2c63..8470101904 100644 --- a/src/Components/Components/src/Routing/Router.cs +++ b/src/Components/Components/src/Routing/Router.cs @@ -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) diff --git a/src/Components/test/E2ETest/Tests/RoutingTest.cs b/src/Components/test/E2ETest/Tests/RoutingTest.cs index 074e57d372..28fe0a1727 100644 --- a/src/Components/test/E2ETest/Tests/RoutingTest.cs +++ b/src/Components/test/E2ETest/Tests/RoutingTest.cs @@ -578,6 +578,29 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Assert.NotNull(errorUiElem); } + [Fact] + public void OnNavigate_DoesNotRenderWhileOnNavigateExecuting() + { + var app = Browser.MountTestComponent(); + + // 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"); diff --git a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor index 5baa82fc00..7b0c289b56 100644 --- a/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor +++ b/src/Components/test/testassets/BasicTestApp/RouterTest/TestRouterWithOnNavigate.razor @@ -1,5 +1,9 @@ @using Microsoft.AspNetCore.Components.Routing +@using System.Threading + + +
@@ -21,7 +25,8 @@ { { "LongPage1", new Func(TestLoadingPageShows) }, { "LongPage2", new Func(TestOnNavCancel) }, - { "Other", new Func(TestOnNavException) } + { "Other", new Func(TestOnNavException) }, + {"WithParameters/name/Abc", new Func(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..."); + } }