Skip to content

Commit

Permalink
WinForms/WPF/OffScreen - Add WaitForBrowserInitialLoadAsync
Browse files Browse the repository at this point in the history
Second attempt at WaitForBrowserInitialLoadAsync, includes optional
CancellationToken param.

Issue #3842
  • Loading branch information
amaitland committed Oct 27, 2021
1 parent c832f7b commit 91b7514
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 50 deletions.
140 changes: 140 additions & 0 deletions CefSharp.Core/WebBrowserExtensionsEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using CefSharp.Internals;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace CefSharp
Expand Down Expand Up @@ -50,6 +51,145 @@ public static Task<NavigationEntry> GetVisibleNavigationEntryAsync(this IWebBrow
return tcs.Task;
}

/// <summary>
/// See <see cref="IWebBrowser.WaitForBrowserInitialLoadAsync()"/> for details
/// </summary>
/// <param name="chromiumWebBrowser">ChromiumWebBrowser instance (cannot be null)</param>
/// <param name="cancellationToken">cancellation token</param>
/// <returns>See <see cref="IWebBrowser.WaitForBrowserInitialLoadAsync()"/> for details</returns>
public static Task<LoadUrlAsyncResponse> WaitForBrowserInitialLoadAsync(IWebBrowser chromiumWebBrowser, CancellationToken? cancellationToken = null)
{
var tcs = new TaskCompletionSource<LoadUrlAsyncResponse>();

if (cancellationToken?.IsCancellationRequested ?? false)
{
tcs.TrySetCanceled();

return tcs.Task;
}

if (chromiumWebBrowser.IsDisposed)
{
var ex = new ObjectDisposedException(nameof(chromiumWebBrowser));

tcs.TrySetException(ex);

return tcs.Task;
}

//If the browser has already initialized then we
//should check if the page has already loaded
if (chromiumWebBrowser.IsBrowserInitialized)
{
var browser = chromiumWebBrowser.GetBrowser();

if (!browser.IsLoading)
{
chromiumWebBrowser.GetVisibleNavigationEntryAsync().ContinueWith(x =>
{
if (cancellationToken?.IsCancellationRequested ?? false)
{
tcs.TrySetCanceledAsync();

return;
}

var navEntry = x.Result;

int statusCode = navEntry?.HttpStatusCode ?? -1;

//By default 0 is some sort of error, we map that to -1
//so that it's clearer that something failed.
if (statusCode == 0)
{
statusCode = -1;
}

var response = new LoadUrlAsyncResponse(CefErrorCode.None, statusCode);

tcs.TrySetResultAsync(response);

}, TaskScheduler.Default);

return tcs.Task;
}
}

EventHandler<LoadErrorEventArgs> loadErrorHandler = null;
EventHandler<LoadingStateChangedEventArgs> loadingStateChangeHandler = null;

loadErrorHandler = (sender, args) =>
{
//Ignore Aborted
if (args.ErrorCode == CefErrorCode.Aborted)
{
return;
}

//If LoadError was called then we'll remove both our handlers
//as we won't need to capture LoadingStateChanged, we know there
//was an error
chromiumWebBrowser.LoadError -= loadErrorHandler;
chromiumWebBrowser.LoadingStateChanged -= loadingStateChangeHandler;

if (cancellationToken?.IsCancellationRequested ?? false)
{
tcs.TrySetCanceledAsync();

return;
}

//Ensure our continuation is executed on the ThreadPool
//For the .Net Core implementation we could use
//TaskCreationOptions.RunContinuationsAsynchronously
tcs.TrySetResultAsync(new LoadUrlAsyncResponse(args.ErrorCode, -1));
};

loadingStateChangeHandler = (sender, args) =>
{
//Wait for IsLoading = false
if (!args.IsLoading)
{
//If LoadingStateChanged was called then we'll remove both our handlers
//as LoadError won't be called, our site has loaded with a valid HttpStatusCode
//HttpStatusCodes can still be for example 404, this is considered a successful request,
//the server responded, it just didn't have the page you were after.
chromiumWebBrowser.LoadError -= loadErrorHandler;
chromiumWebBrowser.LoadingStateChanged -= loadingStateChangeHandler;

if (cancellationToken?.IsCancellationRequested ?? false)
{
tcs.TrySetCanceledAsync();

return;
}

var host = args.Browser.GetHost();

var navEntry = host?.GetVisibleNavigationEntry();

int statusCode = navEntry?.HttpStatusCode ?? -1;

//By default 0 is some sort of error, we map that to -1
//so that it's clearer that something failed.
if (statusCode == 0)
{
statusCode = -1;
}

//Ensure our continuation is executed on the ThreadPool
//For the .Net Core implementation we could use
//TaskCreationOptions.RunContinuationsAsynchronously
tcs.TrySetResultAsync(new LoadUrlAsyncResponse(CefErrorCode.None, statusCode));
}
};

chromiumWebBrowser.LoadError += loadErrorHandler;
chromiumWebBrowser.LoadingStateChanged += loadingStateChangeHandler;

return tcs.Task;
}

/// <summary>
/// Downloads the specified <paramref name="url"/> and calls <paramref name="completeHandler"/>
/// when the download is complete. Makes a GET Request.
Expand Down
16 changes: 8 additions & 8 deletions CefSharp.Test/DevTools/DevToolsClientFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public async Task CanCaptureScreenshot()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
Expand Down Expand Up @@ -83,7 +83,7 @@ public async Task CanGetDevToolsProtocolVersion()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
Expand All @@ -104,7 +104,7 @@ public async Task CanEmulationCanEmulate()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
Expand All @@ -120,7 +120,7 @@ public async Task CanGetPageNavigationHistory()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
Expand All @@ -143,7 +143,7 @@ public async Task CanSetCookieForDomain(string name, string value, string domain
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
Expand All @@ -159,7 +159,7 @@ public async Task CanUseMultipleDevToolsClientInstancesPerBrowser()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
Expand Down Expand Up @@ -192,7 +192,7 @@ public async Task CanSetUserAgentOverride()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
Expand Down Expand Up @@ -309,7 +309,7 @@ public async Task ExecuteDevToolsMethodThrowsExceptionWithInvalidMethod()
{
using (var browser = new ChromiumWebBrowser("www.google.com"))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

using (var devToolsClient = browser.GetDevToolsClient())
{
Expand Down
12 changes: 6 additions & 6 deletions CefSharp.Test/JavascriptBinding/IntegrationTestFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public async Task IsObjectCachedWithInvalidObjectNameReturnsFalse()
{
using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

//We'll execute twice using the different cased (camelcase naming and standard)
var response = await browser.EvaluateScriptAsync("CefSharp.IsObjectCached('doesntexist')");
Expand All @@ -186,7 +186,7 @@ public async Task JsBindingGlobalObjectNameCustomValueExecuteIsObjectCachedSucce
//To modify the settings we need to defer browser creation slightly
browser.CreateBrowser();

await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

var result = await browser.EvaluateScriptAsync("bindingApiObject.isObjectCached('doesntexist') === false");

Expand All @@ -205,7 +205,7 @@ public async Task JsBindingGlobalApiDisabled()
//To modify the settings we need to defer browser creation slightly
browser.CreateBrowser();

var loadResponse = await browser.LoadUrlAsync();
var loadResponse = await browser.WaitForBrowserInitialLoadAsync();

Assert.True(loadResponse.Success);

Expand All @@ -231,7 +231,7 @@ public async Task JsBindingGlobalApiEnabled()
//To modify the settings we need to defer browser creation slightly
browser.CreateBrowser();

await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

var response1 = await browser.EvaluateScriptAsync("typeof window.cefSharp === 'undefined'");
var response2 = await browser.EvaluateScriptAsync("typeof window.CefSharp === 'undefined'");
Expand All @@ -251,7 +251,7 @@ public async Task JsBindingRenderProcessId(string script)
{
using (var browser = new ChromiumWebBrowser(CefExample.BindingApiCustomObjectNameTestUrl))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

var result = await browser.EvaluateScriptAsync(script);

Expand All @@ -271,7 +271,7 @@ public async Task CanCallCefSharpBindObjectAsyncWithoutParams()
{
using (var browser = new ChromiumWebBrowser(CefExample.HelloWorldUrl))
{
await browser.LoadUrlAsync();
await browser.WaitForBrowserInitialLoadAsync();

//TODO: See if we can avoid GetAwaiter().GetResult()
var evt = Assert.Raises<JavascriptBindingEventArgs>(
Expand Down
Loading

0 comments on commit 91b7514

Please sign in to comment.