|
2 | 2 | using System.Collections.Generic; |
3 | 3 | using System.Diagnostics; |
4 | 4 | using System.IO; |
| 5 | +using System.Linq; |
5 | 6 | using System.Text.Json; |
6 | 7 | using System.Threading.Tasks; |
| 8 | +using System.Timers; |
7 | 9 | using PuppeteerSharp.Cdp; |
8 | 10 | using PuppeteerSharp.Cdp.Messaging; |
9 | 11 | using PuppeteerSharp.Helpers; |
@@ -41,10 +43,14 @@ public abstract class Page : IPage |
41 | 43 |
|
42 | 44 | private readonly TaskQueue _screenshotTaskQueue; |
43 | 45 | private readonly ConcurrentSet<Func<IRequest, Task>> _requestInterceptionTask = []; |
| 46 | + private readonly ConcurrentSet<IRequest> _requests = new(); |
| 47 | + private readonly TaskCompletionSource<bool> _closeTaskCompletionSource = |
| 48 | + new(TaskCreationOptions.RunContinuationsAsynchronously); |
44 | 49 |
|
45 | 50 | internal Page(TaskQueue screenshotTaskQueue) |
46 | 51 | { |
47 | 52 | _screenshotTaskQueue = screenshotTaskQueue; |
| 53 | + Request += (_, e) => _requests.Add(e.Request); |
48 | 54 | } |
49 | 55 |
|
50 | 56 | /// <inheritdoc/> |
@@ -228,6 +234,9 @@ public int DefaultTimeout |
228 | 234 | /// </summary> |
229 | 235 | protected ScreenshotOptions ScreenshotBurstModeOptions { get; set; } |
230 | 236 |
|
| 237 | + private int NumRequestsInProgress |
| 238 | + => _requests.Count(r => r.Response == null); |
| 239 | + |
231 | 240 | /// <inheritdoc/> |
232 | 241 | public abstract Task SetGeolocationAsync(GeolocationOption options); |
233 | 242 |
|
@@ -683,7 +692,58 @@ public Task<IResponse> WaitForNavigationAsync(NavigationOptions options = null) |
683 | 692 | => MainFrame.WaitForNavigationAsync(options); |
684 | 693 |
|
685 | 694 | /// <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 | + } |
687 | 747 |
|
688 | 748 | /// <inheritdoc/> |
689 | 749 | public Task<IRequest> WaitForRequestAsync(string url, WaitForOptions options = null) |
@@ -885,7 +945,11 @@ protected void OnRequest(IRequest request) |
885 | 945 | /// <summary> |
886 | 946 | /// Raises the <see cref="Close"/> event. |
887 | 947 | /// </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 | + } |
889 | 953 |
|
890 | 954 | /// <summary> |
891 | 955 | /// Raises the <see cref="Console"/> event. |
|
0 commit comments