Skip to content

Commit 399890f

Browse files
authored
Refactor WaitForNetworkIdle (#2824)
1 parent c908c5c commit 399890f

File tree

5 files changed

+71
-63
lines changed

5 files changed

+71
-63
lines changed

lib/PuppeteerSharp/Cdp/CdpPage.cs

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -419,62 +419,6 @@ await Task.WhenAll(
419419
return navigationTask.Result;
420420
}
421421

422-
/// <inheritdoc/>
423-
public override async Task WaitForNetworkIdleAsync(WaitForNetworkIdleOptions options = null)
424-
{
425-
var timeout = options?.Timeout ?? DefaultTimeout;
426-
var idleTime = options?.IdleTime ?? 500;
427-
428-
var networkIdleTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
429-
430-
var idleTimer = new Timer { Interval = idleTime, };
431-
432-
idleTimer.Elapsed += (_, _) => { networkIdleTcs.TrySetResult(true); };
433-
434-
var networkManager = FrameManager.NetworkManager;
435-
436-
void Evaluate()
437-
{
438-
idleTimer.Stop();
439-
440-
if (networkManager.NumRequestsInProgress == 0)
441-
{
442-
idleTimer.Start();
443-
}
444-
}
445-
446-
void RequestEventListener(object sender, RequestEventArgs e) => Evaluate();
447-
void ResponseEventListener(object sender, ResponseCreatedEventArgs e) => Evaluate();
448-
449-
void Cleanup()
450-
{
451-
idleTimer.Stop();
452-
idleTimer.Dispose();
453-
454-
networkManager.Request -= RequestEventListener;
455-
networkManager.Response -= ResponseEventListener;
456-
}
457-
458-
networkManager.Request += RequestEventListener;
459-
networkManager.Response += ResponseEventListener;
460-
461-
Evaluate();
462-
463-
await Task.WhenAny(networkIdleTcs.Task, SessionClosedTask).WithTimeout(timeout, t =>
464-
{
465-
Cleanup();
466-
467-
return new TimeoutException($"Timeout of {t.TotalMilliseconds} ms exceeded");
468-
}).ConfigureAwait(false);
469-
470-
Cleanup();
471-
472-
if (SessionClosedTask.IsFaulted)
473-
{
474-
await SessionClosedTask.ConfigureAwait(false);
475-
}
476-
}
477-
478422
/// <inheritdoc/>
479423
public override async Task<IRequest> WaitForRequestAsync(Func<IRequest, bool> predicate, WaitForOptions options = null)
480424
{

lib/PuppeteerSharp/Cdp/NetworkEventManager.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ internal class NetworkEventManager
1414
private readonly ConcurrentDictionary<string, List<RedirectInfo>> _queuedRedirectInfoMap = new();
1515
private readonly ConcurrentDictionary<string, List<ResponseReceivedExtraInfoResponse>> _responseReceivedExtraInfoMap = new();
1616

17-
public int NumRequestsInProgress
18-
=> _httpRequestsMap.Values.Count(r => r.Response == null);
19-
2017
internal void Forget(string requestId)
2118
{
2219
_requestWillBeSentMap.TryRemove(requestId, out _);

lib/PuppeteerSharp/Cdp/NetworkManager.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ internal NetworkManager(bool acceptInsecureCerts, IFrameProvider frameManager, I
5656

5757
internal Dictionary<string, string> ExtraHTTPHeaders => _extraHTTPHeaders?.Clone();
5858

59-
internal int NumRequestsInProgress => _networkEventManager.NumRequestsInProgress;
60-
6159
internal Task AddClientAsync(ICDPSession client)
6260
{
6361
if (_clients.ContainsKey(client))

lib/PuppeteerSharp/Page.cs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.IO;
5+
using System.Linq;
56
using System.Text.Json;
67
using System.Threading.Tasks;
8+
using System.Timers;
79
using PuppeteerSharp.Cdp;
810
using PuppeteerSharp.Cdp.Messaging;
911
using PuppeteerSharp.Helpers;
@@ -41,10 +43,14 @@ public abstract class Page : IPage
4143

4244
private readonly TaskQueue _screenshotTaskQueue;
4345
private readonly ConcurrentSet<Func<IRequest, Task>> _requestInterceptionTask = [];
46+
private readonly ConcurrentSet<IRequest> _requests = new();
47+
private readonly TaskCompletionSource<bool> _closeTaskCompletionSource =
48+
new(TaskCreationOptions.RunContinuationsAsynchronously);
4449

4550
internal Page(TaskQueue screenshotTaskQueue)
4651
{
4752
_screenshotTaskQueue = screenshotTaskQueue;
53+
Request += (_, e) => _requests.Add(e.Request);
4854
}
4955

5056
/// <inheritdoc/>
@@ -228,6 +234,9 @@ public int DefaultTimeout
228234
/// </summary>
229235
protected ScreenshotOptions ScreenshotBurstModeOptions { get; set; }
230236

237+
private int NumRequestsInProgress
238+
=> _requests.Count(r => r.Response == null);
239+
231240
/// <inheritdoc/>
232241
public abstract Task SetGeolocationAsync(GeolocationOption options);
233242

@@ -683,7 +692,58 @@ public Task<IResponse> WaitForNavigationAsync(NavigationOptions options = null)
683692
=> MainFrame.WaitForNavigationAsync(options);
684693

685694
/// <inheritdoc/>
686-
public abstract Task WaitForNetworkIdleAsync(WaitForNetworkIdleOptions options = null);
695+
public async Task WaitForNetworkIdleAsync(WaitForNetworkIdleOptions options = null)
696+
{
697+
var timeout = options?.Timeout ?? DefaultTimeout;
698+
var idleTime = options?.IdleTime ?? 500;
699+
700+
var networkIdleTcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
701+
702+
var idleTimer = new Timer { Interval = idleTime, };
703+
704+
idleTimer.Elapsed += (_, _) => { networkIdleTcs.TrySetResult(true); };
705+
706+
void Evaluate()
707+
{
708+
idleTimer.Stop();
709+
710+
if (NumRequestsInProgress <= (options?.Concurrency ?? 0))
711+
{
712+
idleTimer.Start();
713+
}
714+
}
715+
716+
void RequestEventListener(object sender, RequestEventArgs e) => Evaluate();
717+
void ResponseEventListener(object sender, ResponseCreatedEventArgs e) => Evaluate();
718+
719+
void Cleanup()
720+
{
721+
idleTimer.Stop();
722+
idleTimer.Dispose();
723+
724+
Request -= RequestEventListener;
725+
Response -= ResponseEventListener;
726+
}
727+
728+
Request += RequestEventListener;
729+
Response += ResponseEventListener;
730+
731+
Evaluate();
732+
733+
await Task.WhenAny(networkIdleTcs.Task, _closeTaskCompletionSource.Task).WithTimeout(timeout, t =>
734+
{
735+
Cleanup();
736+
737+
return new TimeoutException($"Timeout of {t.TotalMilliseconds} ms exceeded");
738+
}).ConfigureAwait(false);
739+
740+
Cleanup();
741+
742+
if (_closeTaskCompletionSource.Task.IsFaulted)
743+
{
744+
await _closeTaskCompletionSource.Task.ConfigureAwait(false);
745+
}
746+
}
687747

688748
/// <inheritdoc/>
689749
public Task<IRequest> WaitForRequestAsync(string url, WaitForOptions options = null)
@@ -885,7 +945,11 @@ protected void OnRequest(IRequest request)
885945
/// <summary>
886946
/// Raises the <see cref="Close"/> event.
887947
/// </summary>
888-
protected void OnClose() => Close?.Invoke(this, EventArgs.Empty);
948+
protected void OnClose()
949+
{
950+
_closeTaskCompletionSource?.TrySetException(new TargetClosedException("Target closed", "Session closed"));
951+
Close?.Invoke(this, EventArgs.Empty);
952+
}
889953

890954
/// <summary>
891955
/// Raises the <see cref="Console"/> event.

lib/PuppeteerSharp/WaitForNetworkIdleOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,10 @@ public class WaitForNetworkIdleOptions : WaitForOptions
1010
/// How long to wait for no network requests in milliseconds, defaults to 500 milliseconds.
1111
/// </summary>
1212
public int? IdleTime { get; set; }
13+
14+
/// <summary>
15+
/// Maximum number concurrent of network connections to be considered inactive.
16+
/// </summary>
17+
public int Concurrency { get; set; }
1318
}
1419
}

0 commit comments

Comments
 (0)