Skip to content

Commit 460d905

Browse files
kblokMeir017
authored andcommitted
Page.goto should properly handle historyAPI in beforeunload (#647)
* Page.goto should properly handle historyAPI in beforeunload * Feature complete * Code Factor
1 parent 339c7dc commit 460d905

File tree

6 files changed

+68
-38
lines changed

6 files changed

+68
-38
lines changed

lib/PuppeteerSharp.Tests/PageTests/GotoTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ public async Task ShouldNavigateToEmptyPageWithDOMContentLoaded()
3434
Assert.Null(response.SecurityDetails);
3535
}
3636

37+
[Fact]
38+
public async Task ShouldWorkWhenPageCallsHistoryAPIInBeforeunload()
39+
{
40+
await Page.GoToAsync(TestConstants.EmptyPage);
41+
await Page.EvaluateFunctionAsync(@"() =>
42+
{
43+
window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false);
44+
}");
45+
var response = await Page.GoToAsync(TestConstants.ServerUrl + "/grid.html");
46+
Assert.Equal(HttpStatusCode.OK, response.Status);
47+
}
48+
3749
[Fact]
3850
public async Task ShouldFailWhenServerReturns204()
3951
{

lib/PuppeteerSharp/Frame.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ internal void Detach()
613613
{
614614
while (WaitTasks.Count > 0)
615615
{
616-
WaitTasks[0].Termiante(new Exception("waitForFunction failed: frame got detached."));
616+
WaitTasks[0].Terminate(new Exception("waitForFunction failed: frame got detached."));
617617
}
618618
Detached = true;
619619
if (ParentFrame != null)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
4+
namespace PuppeteerSharp.Messaging
5+
{
6+
internal class PageNavigateResponse
7+
{
8+
[JsonProperty("errorText")]
9+
internal string ErrorText { get; set; }
10+
11+
[JsonProperty("loaderId")]
12+
internal string LoaderId { get; set; }
13+
}
14+
}

lib/PuppeteerSharp/NavigatorWatcher.cs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,29 +52,21 @@ public NavigatorWatcher(FrameManager frameManager, Frame mainFrame, int timeout,
5252
frameManager.LifecycleEvent += CheckLifecycleComplete;
5353
frameManager.FrameNavigatedWithinDocument += NavigatedWithinDocument;
5454
frameManager.FrameDetached += CheckLifecycleComplete;
55-
LifeCycleCompleteTaskWrapper = new TaskCompletionSource<bool>();
56-
57-
NavigationTask = Task.WhenAny(new[]
58-
{
59-
TaskHelper.CreateTimeoutTask(_timeout),
60-
LifeCycleCompleteTask
61-
}).ContinueWith((task) =>
62-
{
63-
CleanUp();
64-
return task.GetAwaiter().GetResult();
65-
});
55+
SameDocumentNavigationTaskWrapper = new TaskCompletionSource<bool>();
56+
NewDocumentNavigationTaskWrapper = new TaskCompletionSource<bool>();
57+
TimeoutTask = TaskHelper.CreateTimeoutTask(timeout);
6658
}
6759

6860
#region Properties
6961
public Task<Task> NavigationTask { get; internal set; }
70-
public Task<bool> LifeCycleCompleteTask => LifeCycleCompleteTaskWrapper.Task;
71-
public TaskCompletionSource<bool> LifeCycleCompleteTaskWrapper { get; }
62+
public Task<bool> SameDocumentNavigationTask => SameDocumentNavigationTaskWrapper.Task;
63+
public TaskCompletionSource<bool> SameDocumentNavigationTaskWrapper { get; }
64+
public Task<bool> NewDocumentNavigationTask => NewDocumentNavigationTaskWrapper.Task;
65+
public TaskCompletionSource<bool> NewDocumentNavigationTaskWrapper { get; }
66+
public Task TimeoutTask { get; }
7267

7368
#endregion
7469

75-
#region Public methods
76-
public void Cancel() => CleanUp();
77-
#endregion
7870
#region Private methods
7971

8072
private void CheckLifecycleComplete(object sender, FrameEventArgs e)
@@ -88,8 +80,15 @@ private void CheckLifecycleComplete(object sender, FrameEventArgs e)
8880
{
8981
return;
9082
}
91-
92-
LifeCycleCompleteTaskWrapper.TrySetResult(true);
83+
84+
if (_hasSameDocumentNavigation)
85+
{
86+
SameDocumentNavigationTaskWrapper.TrySetResult(true);
87+
}
88+
if (_frame.LoaderId != _initialLoaderId)
89+
{
90+
NewDocumentNavigationTaskWrapper.TrySetResult(true);
91+
}
9392
}
9493

9594
private void NavigatedWithinDocument(object sender, FrameEventArgs e)

lib/PuppeteerSharp/Page.cs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class Page : IDisposable
4242
private readonly Dictionary<string, Delegate> _pageBindings;
4343
private readonly Dictionary<string, Worker> _workers;
4444
private readonly ILogger _logger;
45+
private bool _ensureNewDocumentNavigation;
4546

4647
private static readonly Dictionary<string, decimal> _unitToPixels = new Dictionary<string, decimal> {
4748
{"px", 1},
@@ -716,7 +717,7 @@ void createRequestEventListener(object sender, RequestEventArgs e)
716717
var navigateTask = Navigate(Client, url, referrer);
717718

718719
await Task.WhenAny(
719-
watcher.NavigationTask,
720+
watcher.TimeoutTask,
720721
navigateTask).ConfigureAwait(false);
721722

722723
AggregateException exception = null;
@@ -725,21 +726,19 @@ await Task.WhenAny(
725726
{
726727
exception = navigateTask.Exception;
727728
}
728-
else if (watcher.NavigationTask.IsCompleted &&
729-
watcher.NavigationTask.Result.IsFaulted)
729+
else
730730
{
731-
exception = watcher.NavigationTask.Result?.Exception;
732-
}
731+
await Task.WhenAny(
732+
watcher.TimeoutTask,
733+
_ensureNewDocumentNavigation ? watcher.NewDocumentNavigationTask : watcher.SameDocumentNavigationTask
734+
).ConfigureAwait(false);
733735

734-
if (exception == null)
735-
{
736-
await Task.WhenAll(
737-
watcher.NavigationTask,
738-
navigateTask).ConfigureAwait(false);
739-
exception = navigateTask.Exception ?? watcher.NavigationTask.Result.Exception;
736+
if (watcher.TimeoutTask.IsFaulted)
737+
{
738+
exception = watcher.TimeoutTask.Exception;
739+
}
740740
}
741741

742-
watcher.Cancel();
743742
_networkManager.Request -= createRequestEventListener;
744743

745744
if (exception != null)
@@ -1408,11 +1407,15 @@ public async Task<Response> WaitForNavigationAsync(NavigationOptions options = n
14081407

14091408
_networkManager.Response += createResponseEventListener;
14101409

1411-
await watcher.NavigationTask.ConfigureAwait(false);
1410+
var raceTask = await Task.WhenAny(
1411+
watcher.NewDocumentNavigationTask,
1412+
watcher.SameDocumentNavigationTask,
1413+
watcher.TimeoutTask
1414+
).ConfigureAwait(false);
14121415

14131416
_networkManager.Response -= createResponseEventListener;
14141417

1415-
var exception = watcher.NavigationTask.Exception;
1418+
var exception = raceTask.Exception;
14161419
if (exception != null)
14171420
{
14181421
throw new NavigationException(exception.Message, exception);
@@ -2025,15 +2028,17 @@ await Task.WhenAll(Frames.Select(frame => frame.EvaluateExpressionAsync(expressi
20252028

20262029
private async Task Navigate(CDPSession client, string url, string referrer)
20272030
{
2028-
dynamic response = await client.SendAsync("Page.navigate", new
2031+
var response = await client.SendAsync<PageNavigateResponse>("Page.navigate", new
20292032
{
20302033
url,
20312034
referrer = referrer ?? string.Empty
20322035
}).ConfigureAwait(false);
20332036

2034-
if (response.errorText != null)
2037+
_ensureNewDocumentNavigation = !string.IsNullOrEmpty(response.LoaderId);
2038+
2039+
if (!string.IsNullOrEmpty(response.ErrorText))
20352040
{
2036-
throw new NavigationException(response.errorText.ToString(), url);
2041+
throw new NavigationException(response.ErrorText, url);
20372042
}
20382043
}
20392044

lib/PuppeteerSharp/WaitTask.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ internal WaitTask(
144144
if (timeout > 0)
145145
{
146146
_timeoutTimer = System.Threading.Tasks.Task.Delay(timeout, _cts.Token).ContinueWith(_
147-
=> Termiante(new WaitTaskTimeoutException(timeout, title)));
147+
=> Terminate(new WaitTaskTimeoutException(timeout, title)));
148148
}
149149

150150
_taskCompletion = new TaskCompletionSource<JSHandle>();
@@ -213,7 +213,7 @@ await _frame.EvaluateFunctionAsync<bool>("s => !s", success)
213213
Cleanup();
214214
}
215215

216-
internal void Termiante(Exception exception)
216+
internal void Terminate(Exception exception)
217217
{
218218
_terminated = true;
219219
_taskCompletion.TrySetException(exception);

0 commit comments

Comments
 (0)