Skip to content

Commit a9b8033

Browse files
authored
Move navigation logic from FrameManager to Frame (#2407)
1 parent b205399 commit a9b8033

File tree

4 files changed

+100
-103
lines changed

4 files changed

+100
-103
lines changed

lib/PuppeteerSharp/Frame.cs

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
using Newtonsoft.Json.Linq;
55
using PuppeteerSharp.Helpers;
66
using PuppeteerSharp.Input;
7+
using PuppeteerSharp.Messaging;
78

89
namespace PuppeteerSharp
910
{
1011
/// <inheritdoc cref="PuppeteerSharp.IFrame" />
1112
public class Frame : IFrame, IEnvironment
1213
{
14+
private const string RefererHeaderName = "referer";
15+
1316
private Task<ElementHandle> _documentTask;
1417

1518
internal Frame(FrameManager frameManager, string frameId, string parentFrameId, CDPSession client)
@@ -56,8 +59,6 @@ internal Frame(FrameManager frameManager, string frameId, string parentFrameId,
5659

5760
internal string ParentId { get; }
5861

59-
internal Frame ParentFrame => FrameManager.FrameTree.GetParentFrame(Id);
60-
6162
internal FrameManager FrameManager { get; }
6263

6364
internal string LoaderId { get; private set; }
@@ -74,23 +75,86 @@ internal Frame(FrameManager frameManager, string frameId, string parentFrameId,
7475

7576
internal bool HasStartedLoading { get; private set; }
7677

78+
private Frame ParentFrame => FrameManager.FrameTree.GetParentFrame(Id);
79+
7780
/// <inheritdoc/>
78-
public Task<IResponse> GoToAsync(string url, NavigationOptions options)
81+
public async Task<IResponse> GoToAsync(string url, NavigationOptions options)
7982
{
83+
var ensureNewDocumentNavigation = false;
84+
8085
if (options == null)
8186
{
8287
throw new ArgumentNullException(nameof(options));
8388
}
8489

85-
return FrameManager.NavigateFrameAsync(this, url, options);
90+
var referrer = string.IsNullOrEmpty(options.Referer)
91+
? FrameManager.NetworkManager.ExtraHTTPHeaders?.GetValueOrDefault(RefererHeaderName)
92+
: options.Referer;
93+
var referrerPolicy = string.IsNullOrEmpty(options.ReferrerPolicy)
94+
? FrameManager.NetworkManager.ExtraHTTPHeaders?.GetValueOrDefault("referer-policy")
95+
: options.ReferrerPolicy;
96+
var timeout = options.Timeout ?? FrameManager.TimeoutSettings.NavigationTimeout;
97+
98+
using var watcher = new LifecycleWatcher(FrameManager.NetworkManager, this, options.WaitUntil, timeout);
99+
try
100+
{
101+
var navigateTask = NavigateAsync();
102+
var task = await Task.WhenAny(
103+
watcher.TerminationTask,
104+
navigateTask).ConfigureAwait(false);
105+
106+
await task.ConfigureAwait(false);
107+
108+
task = await Task.WhenAny(
109+
watcher.TerminationTask,
110+
ensureNewDocumentNavigation ? watcher.NewDocumentNavigationTask : watcher.SameDocumentNavigationTask).ConfigureAwait(false);
111+
112+
await task.ConfigureAwait(false);
113+
}
114+
catch (Exception ex)
115+
{
116+
throw new NavigationException(ex.Message, ex);
117+
}
118+
119+
return watcher.NavigationResponse;
120+
121+
async Task NavigateAsync()
122+
{
123+
var response = await Client.SendAsync<PageNavigateResponse>("Page.navigate", new PageNavigateRequest
124+
{
125+
Url = url,
126+
Referrer = referrer ?? string.Empty,
127+
ReferrerPolicy = referrerPolicy ?? string.Empty,
128+
FrameId = Id,
129+
}).ConfigureAwait(false);
130+
131+
ensureNewDocumentNavigation = !string.IsNullOrEmpty(response.LoaderId);
132+
133+
if (!string.IsNullOrEmpty(response.ErrorText) && response.ErrorText != "net::ERR_HTTP_RESPONSE_CODE_FAILURE")
134+
{
135+
throw new NavigationException(response.ErrorText, url);
136+
}
137+
}
86138
}
87139

88140
/// <inheritdoc/>
89141
public Task<IResponse> GoToAsync(string url, int? timeout = null, WaitUntilNavigation[] waitUntil = null)
90142
=> GoToAsync(url, new NavigationOptions { Timeout = timeout, WaitUntil = waitUntil });
91143

92144
/// <inheritdoc/>
93-
public Task<IResponse> WaitForNavigationAsync(NavigationOptions options = null) => FrameManager.WaitForFrameNavigationAsync(this, options);
145+
public async Task<IResponse> WaitForNavigationAsync(NavigationOptions options = null)
146+
{
147+
var timeout = options?.Timeout ?? FrameManager.TimeoutSettings.NavigationTimeout;
148+
using var watcher = new LifecycleWatcher(FrameManager.NetworkManager, this, options?.WaitUntil, timeout);
149+
var raceTask = await Task.WhenAny(
150+
watcher.NewDocumentNavigationTask,
151+
watcher.SameDocumentNavigationTask,
152+
watcher.TerminationTask).ConfigureAwait(false);
153+
154+
await raceTask.ConfigureAwait(false);
155+
156+
return watcher.NavigationResponse;
157+
}
94158

95159
/// <inheritdoc/>
96160
public Task<JToken> EvaluateExpressionAsync(string script) => MainRealm.EvaluateExpressionAsync(script);
@@ -373,9 +437,9 @@ await IsolatedRealm.EvaluateFunctionAsync(
373437
}",
374438
html).ConfigureAwait(false);
375439

376-
using var watcher = new LifecycleWatcher(FrameManager, this, waitUntil, timeout);
440+
using var watcher = new LifecycleWatcher(FrameManager.NetworkManager, this, waitUntil, timeout);
377441
var watcherTask = await Task.WhenAny(
378-
watcher.TimeoutOrTerminationTask,
442+
watcher.TerminationTask,
379443
watcher.LifecycleTask).ConfigureAwait(false);
380444

381445
await watcherTask.ConfigureAwait(false);
@@ -489,7 +553,7 @@ internal void UpdateClient(CDPSession client)
489553
false);
490554
}
491555

492-
internal DeviceRequestPromptManager GetDeviceRequestPromptManager()
556+
private DeviceRequestPromptManager GetDeviceRequestPromptManager()
493557
{
494558
if (IsOopFrame)
495559
{

lib/PuppeteerSharp/FrameManager.cs

Lines changed: 2 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ namespace PuppeteerSharp
1313
{
1414
internal class FrameManager : IDisposable, IAsyncDisposable
1515
{
16-
private const string RefererHeaderName = "referer";
1716
private const string UtilityWorldName = "__puppeteer_utility_world__";
1817

1918
private readonly ConcurrentDictionary<string, ExecutionContext> _contextIdToContext = new();
@@ -22,7 +21,6 @@ internal class FrameManager : IDisposable, IAsyncDisposable
2221
private readonly List<string> _frameNavigatedReceived = new();
2322
private readonly TaskQueue _eventsQueue = new();
2423
private readonly ConcurrentDictionary<CDPSession, DeviceRequestPromptManager> _deviceRequestPromptManagerMap = new();
25-
private bool _ensureNewDocumentNavigation;
2624

2725
internal FrameManager(CDPSession client, Page page, bool ignoreHTTPSErrors, TimeoutSettings timeoutSettings)
2826
{
@@ -59,54 +57,6 @@ internal FrameManager(CDPSession client, Page page, bool ignoreHTTPSErrors, Time
5957

6058
internal Frame MainFrame => FrameTree.MainFrame;
6159

62-
public async Task<IResponse> NavigateFrameAsync(Frame frame, string url, NavigationOptions options)
63-
{
64-
var referrer = string.IsNullOrEmpty(options.Referer)
65-
? NetworkManager.ExtraHTTPHeaders?.GetValueOrDefault(RefererHeaderName)
66-
: options.Referer;
67-
var referrerPolicy = string.IsNullOrEmpty(options.ReferrerPolicy)
68-
? NetworkManager.ExtraHTTPHeaders?.GetValueOrDefault("referer-policy")
69-
: options.ReferrerPolicy;
70-
var timeout = options.Timeout ?? TimeoutSettings.NavigationTimeout;
71-
72-
using var watcher = new LifecycleWatcher(this, frame, options.WaitUntil, timeout);
73-
try
74-
{
75-
var navigateTask = NavigateAsync(Client, url, referrer, referrerPolicy, frame.Id);
76-
var task = await Task.WhenAny(
77-
watcher.TimeoutOrTerminationTask,
78-
navigateTask).ConfigureAwait(false);
79-
80-
await task.ConfigureAwait(false);
81-
82-
task = await Task.WhenAny(
83-
watcher.TimeoutOrTerminationTask,
84-
_ensureNewDocumentNavigation ? watcher.NewDocumentNavigationTask : watcher.SameDocumentNavigationTask).ConfigureAwait(false);
85-
86-
await task.ConfigureAwait(false);
87-
}
88-
catch (Exception ex)
89-
{
90-
throw new NavigationException(ex.Message, ex);
91-
}
92-
93-
return watcher.NavigationResponse;
94-
}
95-
96-
public async Task<IResponse> WaitForFrameNavigationAsync(Frame frame, NavigationOptions options = null)
97-
{
98-
var timeout = options?.Timeout ?? TimeoutSettings.NavigationTimeout;
99-
using var watcher = new LifecycleWatcher(this, frame, options?.WaitUntil, timeout);
100-
var raceTask = await Task.WhenAny(
101-
watcher.NewDocumentNavigationTask,
102-
watcher.SameDocumentNavigationTask,
103-
watcher.TimeoutOrTerminationTask).ConfigureAwait(false);
104-
105-
await raceTask.ConfigureAwait(false);
106-
107-
return watcher.NavigationResponse;
108-
}
109-
11060
public void Dispose()
11161
{
11262
_eventsQueue?.Dispose();
@@ -186,7 +136,7 @@ await Task.WhenAll(
186136
client.SendAsync("Runtime.enable"),
187137
client == Client ? NetworkManager.InitializeAsync() : Task.CompletedTask).ConfigureAwait(false);
188138

189-
await EnsureIsolatedWorldAsync(client, UtilityWorldName).ConfigureAwait(false);
139+
await CreateIsolatedWorldAsync(client, UtilityWorldName).ConfigureAwait(false);
190140
}
191141
catch (Exception ex)
192142
{
@@ -204,24 +154,6 @@ await Task.WhenAll(
204154

205155
private Frame GetFrame(string frameId) => FrameTree.GetById(frameId);
206156

207-
private async Task NavigateAsync(CDPSession client, string url, string referrer, string referrerPolicy, string frameId)
208-
{
209-
var response = await client.SendAsync<PageNavigateResponse>("Page.navigate", new PageNavigateRequest
210-
{
211-
Url = url,
212-
Referrer = referrer ?? string.Empty,
213-
ReferrerPolicy = referrerPolicy ?? string.Empty,
214-
FrameId = frameId,
215-
}).ConfigureAwait(false);
216-
217-
_ensureNewDocumentNavigation = !string.IsNullOrEmpty(response.LoaderId);
218-
219-
if (!string.IsNullOrEmpty(response.ErrorText) && response.ErrorText != "net::ERR_HTTP_RESPONSE_CODE_FAILURE")
220-
{
221-
throw new NavigationException(response.ErrorText, url);
222-
}
223-
}
224-
225157
private void Client_MessageReceived(object sender, MessageEventArgs e)
226158
{
227159
_ = _eventsQueue.Enqueue(async () =>
@@ -502,7 +434,7 @@ private async Task HandleFrameTreeAsync(CDPSession session, PageGetFrameTree fra
502434
}
503435
}
504436

505-
private async Task EnsureIsolatedWorldAsync(CDPSession session, string name)
437+
private async Task CreateIsolatedWorldAsync(CDPSession session, string name)
506438
{
507439
var key = $"{session.Id}:{name}";
508440
if (_isolatedWorlds.Contains(key))

lib/PuppeteerSharp/LifecycleWatcher.cs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Diagnostics.CodeAnalysis;
43
using System.Diagnostics.Contracts;
54
using System.Linq;
65
using System.Threading;
@@ -20,9 +19,9 @@ internal sealed class LifecycleWatcher : IDisposable
2019
[WaitUntilNavigation.Networkidle2] = "networkAlmostIdle",
2120
};
2221

23-
private static readonly WaitUntilNavigation[] _defaultWaitUntil = { WaitUntilNavigation.Load };
22+
private static readonly WaitUntilNavigation[] _defaultWaitUntil = [WaitUntilNavigation.Load];
2423

25-
private readonly FrameManager _frameManager;
24+
private readonly NetworkManager _networkManager;
2625
private readonly Frame _frame;
2726
private readonly IEnumerable<string> _expectedLifecycle;
2827
private readonly int _timeout;
@@ -37,7 +36,7 @@ internal sealed class LifecycleWatcher : IDisposable
3736
private bool _swapped;
3837

3938
public LifecycleWatcher(
40-
FrameManager frameManager,
39+
NetworkManager networkManager,
4140
Frame frame,
4241
WaitUntilNavigation[] waitUntil,
4342
int timeout)
@@ -49,19 +48,19 @@ public LifecycleWatcher(
4948
return protocolEvent;
5049
});
5150

52-
_frameManager = frameManager;
51+
_networkManager = networkManager;
5352
_frame = frame;
5453
_initialLoaderId = frame.LoaderId;
5554
_timeout = timeout;
5655
_hasSameDocumentNavigation = false;
5756

58-
frameManager.LifecycleEvent += FrameManager_LifecycleEvent;
59-
frameManager.FrameNavigatedWithinDocument += NavigatedWithinDocument;
60-
frameManager.FrameNavigated += Navigated;
61-
frameManager.FrameDetached += OnFrameDetached;
62-
frameManager.NetworkManager.Request += OnRequest;
63-
frameManager.Client.Disconnected += OnClientDisconnected;
64-
frameManager.FrameSwapped += FrameManager_FrameSwapped;
57+
frame.FrameManager.LifecycleEvent += FrameManager_LifecycleEvent;
58+
frame.FrameManager.FrameNavigatedWithinDocument += NavigatedWithinDocument;
59+
frame.FrameManager.FrameNavigated += Navigated;
60+
frame.FrameManager.FrameDetached += OnFrameDetached;
61+
_networkManager.Request += OnRequest;
62+
frame.FrameManager.Client.Disconnected += OnClientDisconnected;
63+
frame.FrameManager.FrameSwapped += FrameManager_FrameSwapped;
6564
CheckLifecycleComplete();
6665
}
6766

@@ -71,25 +70,25 @@ public LifecycleWatcher(
7170

7271
public Response NavigationResponse => (Response)_navigationRequest?.Response;
7372

74-
public Task TimeoutOrTerminationTask => _terminationTaskWrapper.Task.WithTimeout(_timeout, cancellationToken: _terminationCancellationToken.Token);
73+
public Task TerminationTask => _terminationTaskWrapper.Task.WithTimeout(_timeout, cancellationToken: _terminationCancellationToken.Token);
7574

7675
public Task LifecycleTask => _lifecycleTaskWrapper.Task;
7776

7877
public void Dispose()
7978
{
80-
_frameManager.LifecycleEvent -= FrameManager_LifecycleEvent;
81-
_frameManager.FrameNavigatedWithinDocument -= NavigatedWithinDocument;
82-
_frameManager.FrameNavigated -= Navigated;
83-
_frameManager.FrameDetached -= OnFrameDetached;
84-
_frameManager.NetworkManager.Request -= OnRequest;
85-
_frameManager.Client.Disconnected -= OnClientDisconnected;
86-
_frameManager.FrameSwapped -= FrameManager_FrameSwapped;
79+
_frame.FrameManager.LifecycleEvent -= FrameManager_LifecycleEvent;
80+
_frame.FrameManager.FrameNavigatedWithinDocument -= NavigatedWithinDocument;
81+
_frame.FrameManager.FrameNavigated -= Navigated;
82+
_frame.FrameManager.FrameDetached -= OnFrameDetached;
83+
_frame.FrameManager.NetworkManager.Request -= OnRequest;
84+
_frame.FrameManager.Client.Disconnected -= OnClientDisconnected;
85+
_frame.FrameManager.FrameSwapped -= FrameManager_FrameSwapped;
8786
_terminationCancellationToken.Cancel();
8887
_terminationCancellationToken.Dispose();
8988
}
9089

9190
private void OnClientDisconnected(object sender, EventArgs e)
92-
=> Terminate(new TargetClosedException("Navigation failed because browser has disconnected!", _frameManager.Client.CloseReason));
91+
=> Terminate(new TargetClosedException("Navigation failed because browser has disconnected!", _frame.FrameManager.Client.CloseReason));
9392

9493
private void Navigated(object sender, FrameEventArgs e)
9594
{
@@ -172,17 +171,19 @@ private void NavigatedWithinDocument(object sender, FrameEventArgs e)
172171

173172
private bool CheckLifecycle(Frame frame, IEnumerable<string> expectedLifecycle)
174173
{
175-
foreach (var item in expectedLifecycle)
174+
var enumerable = expectedLifecycle as string[] ?? expectedLifecycle.ToArray();
175+
foreach (var item in enumerable)
176176
{
177177
if (!frame.LifecycleEvents.Contains(item))
178178
{
179179
return false;
180180
}
181181
}
182182

183-
foreach (Frame child in frame.ChildFrames)
183+
foreach (var childFrame in frame.ChildFrames)
184184
{
185-
if (child.HasStartedLoading && !CheckLifecycle(child, expectedLifecycle))
185+
var child = (Frame)childFrame;
186+
if (child.HasStartedLoading && !CheckLifecycle(child, enumerable))
186187
{
187188
return false;
188189
}

lib/PuppeteerSharp/Page.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,7 @@ public Task<IElementHandle> WaitForXPathAsync(string xpath, WaitForSelectorOptio
832832

833833
/// <inheritdoc/>
834834
public Task<IResponse> WaitForNavigationAsync(NavigationOptions options = null)
835-
=> FrameManager.WaitForFrameNavigationAsync(FrameManager.MainFrame, options);
835+
=> MainFrame.WaitForNavigationAsync(options);
836836

837837
/// <inheritdoc/>
838838
public async Task WaitForNetworkIdleAsync(WaitForNetworkIdleOptions options = null)

0 commit comments

Comments
 (0)