Skip to content

Commit

Permalink
OffScreen - Create Browser when Global CefRequestContext initialized.
Browse files Browse the repository at this point in the history
- GlobalContextInitialized wraps a TaskCompletionSource which is resolved
  when the Global CefRequestContext has been created
- Reduce code duplication

If successful we can apply the same changes to WinForms/WPF though they're less likely to have
issues than the OffScreen implementation as the CreateBrowser doesn't happen until much later

Issue #3850 #3634
  • Loading branch information
amaitland committed Oct 26, 2021
1 parent 239372a commit 9993aa7
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 49 deletions.
5 changes: 5 additions & 0 deletions CefSharp.Core.Runtime/Cef.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ namespace CefSharp

auto success = CefInitialize(main_args, *(cefSettings->_cefSettings), app.get(), nullptr);

if (!success)
{
CefSharp::Internals::GlobalContextInitialized::SetResult(false);
}

_initialized = success;
_multiThreadedMessageLoop = cefSettings->MultiThreadedMessageLoop;

Expand Down
12 changes: 7 additions & 5 deletions CefSharp.Core.Runtime/Internals/CefSharpApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,6 @@ namespace CefSharp

virtual void OnContextInitialized() override
{
if (!Object::ReferenceEquals(_app, nullptr) && !Object::ReferenceEquals(_app->BrowserProcessHandler, nullptr))
{
_app->BrowserProcessHandler->OnContextInitialized();
}

auto customSchemes = (IEnumerable<CefCustomScheme^>^)_customSchemes;

//CefRegisterSchemeHandlerFactory requires access to the Global CefRequestContext
Expand All @@ -111,6 +106,13 @@ namespace CefSharp
CefRegisterSchemeHandlerFactory(StringUtils::ToNative(cefCustomScheme->SchemeName), StringUtils::ToNative(domainName), wrapper);
}
}

CefSharp::Internals::GlobalContextInitialized::SetResult(true);

if (!Object::ReferenceEquals(_app, nullptr) && !Object::ReferenceEquals(_app->BrowserProcessHandler, nullptr))
{
_app->BrowserProcessHandler->OnContextInitialized();
}
}

virtual void OnScheduleMessagePumpWork(int64 delay_ms) override
Expand Down
16 changes: 4 additions & 12 deletions CefSharp.Core/Cef.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,28 +181,20 @@ public static bool Initialize(CefSettingsBase settings, bool performDependencyCh
/// </remarks>
public static Task<bool> InitializeAsync(CefSettingsBase settings, bool performDependencyCheck = true)
{
var tcs = new TaskCompletionSource<bool>();
var handler = new InitializeAsyncBrowserProcessHandler(tcs);

using (settings.settings)
{
try
{
var success = Core.Cef.Initialize(settings.settings, performDependencyCheck, handler);

//Failed, need to check the log file
if (!success)
{
tcs.TrySetResult(false);
}
//Ignore the result, the Task will be set in Core.Cef.Initialze
Core.Cef.Initialize(settings.settings, performDependencyCheck);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
GlobalContextInitialized.SetException(ex);
}
}

return tcs.Task;
return GlobalContextInitialized.Task;
}

/// <summary>
Expand Down
59 changes: 27 additions & 32 deletions CefSharp.OffScreen/ChromiumWebBrowser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,27 @@ public void CreateBrowser(IWindowInfo windowInfo = null, IBrowserSettings browse
windowInfo.SetAsWindowless(IntPtr.Zero);
}

managedCefBrowserAdapter.CreateBrowser(windowInfo, browserSettings, RequestContext, Address);

//Dispose of BrowserSettings if we created it, if user created then they're responsible
if (browserSettings.AutoDispose)
//TODO: We need some sort of timeout and
//if we use the same approach for WPF/WinForms then
//we need to move the common code into the partial class
GlobalContextInitialized.ExecuteOrEnqueue((success) =>
{
browserSettings.Dispose();
}
browserSettings = null;
if(!success)
{
return;
}

managedCefBrowserAdapter.CreateBrowser(windowInfo, browserSettings, RequestContext, Address);

//Dispose of BrowserSettings if we created it, if user created then they're responsible
if (browserSettings.AutoDispose)
{
browserSettings.Dispose();
}
browserSettings = null;

});

}

/// <summary>
Expand All @@ -348,39 +361,21 @@ public void CreateBrowser(IWindowInfo windowInfo = null, IBrowserSettings browse
/// </returns>
public Task<IBrowser> CreateBrowserAsync(IWindowInfo windowInfo = null, IBrowserSettings browserSettings = null)
{
if (browserCreated)
{
throw new Exception("An instance of the underlying offscreen browser has already been created, this method can only be called once.");
}

browserCreated = true;

if (browserSettings == null)
{
browserSettings = Core.ObjectFactory.CreateBrowserSettings(autoDispose: true);
}

if (windowInfo == null)
{
windowInfo = Core.ObjectFactory.CreateWindowInfo();
windowInfo.SetAsWindowless(IntPtr.Zero);
}

var tcs = new TaskCompletionSource<IBrowser>();

onAfterBrowserCreatedDelegate = new Action<IBrowser>(b =>
onAfterBrowserCreatedDelegate += new Action<IBrowser>(b =>
{
tcs.TrySetResultAsync(b);
});

managedCefBrowserAdapter.CreateBrowser(windowInfo, browserSettings, RequestContext, Address);

//Dispose of BrowserSettings if we created it, if user created then they're responsible
if (browserSettings.AutoDispose)
try
{
CreateBrowser(windowInfo, browserSettings);
}
catch(Exception ex)
{
browserSettings.Dispose();
tcs.TrySetExceptionAsync(ex);
}
browserSettings = null;

return tcs.Task;
}
Expand Down
46 changes: 46 additions & 0 deletions CefSharp/Internals/GlobalContextInitialized.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright © 2021 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System;
using System.Threading.Tasks;

namespace CefSharp.Internals
{
public static class GlobalContextInitialized
{
private static TaskCompletionSource<bool> TaskCompletionSource = new TaskCompletionSource<bool>();

public static Task<bool> Task
{
get { return TaskCompletionSource.Task; }
}

public static void SetResult(bool success)
{
TaskCompletionSource.TrySetResult(success);
}

public static void SetException(Exception ex)
{
TaskCompletionSource.TrySetException(ex);
}

/// <summary>
/// We need to be sure the CEF Global Context has been initialized before
/// we create the browser. If the CefRequestContext has already been initialzed
/// then we'll execute syncroniously. If the CefRequestContext hasn't been
/// initialized then we will continue on the CEF UI Thread.
/// https://github.com/cefsharp/CefSharp/issues/3850
/// </summary>
/// <param name="action">action to invoke</param>
public static void ExecuteOrEnqueue(Action<bool> action)
{
TaskCompletionSource.Task.ContinueWith((t) =>
{
action(t.Result);

}, TaskContinuationOptions.ExecuteSynchronously);
}
}
}

0 comments on commit 9993aa7

Please sign in to comment.