Skip to content

Commit c106d9f

Browse files
committed
Start to refactor to allow for multiple browsers per process
1 parent 7296c46 commit c106d9f

7 files changed

Lines changed: 234 additions & 141 deletions

File tree

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,93 @@
1-
using System;
1+
using CefSharp.Internals;
2+
using PInvoke;
3+
using StreamJsonRpc;
4+
using System;
5+
using System.Linq;
6+
using System.Collections.Generic;
7+
using System.Diagnostics;
28

39
namespace CefSharp.OutOfProcess.BrowserProcess
410
{
511
public class BrowserProcessHandler : CefSharp.Handler.BrowserProcessHandler
612
{
713
private readonly int _parentProcessId;
8-
private readonly IntPtr _hostHwnd;
14+
private IList<OutOfProcessChromiumWebBrowser> _browsers = new List<OutOfProcessChromiumWebBrowser>();
15+
/// <summary>
16+
/// JSON RPC used for IPC with host
17+
/// </summary>
18+
private JsonRpc _jsonRpc;
919

10-
public BrowserProcessHandler(int parentProcessId, IntPtr hostHwnd)
20+
public BrowserProcessHandler(int parentProcessId)
1121
{
1222
_parentProcessId = parentProcessId;
13-
_hostHwnd = hostHwnd;
1423
}
1524

1625
protected override void OnContextInitialized()
1726
{
1827
base.OnContextInitialized();
1928

20-
var browser = new OutOfProcessChromiumWebBrowser("https://github.com");
29+
_jsonRpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput());
30+
_jsonRpc.AllowModificationWhileListening = true;
2131

22-
var windowInfo = new WindowInfo();
23-
windowInfo.WindowName = "CefSharpBrowserProcess";
24-
windowInfo.SetAsChild(_hostHwnd);
32+
_jsonRpc.AddLocalRpcMethod("CloseBrowser", (Action<int>)delegate (int browserId)
33+
{
34+
_ = CefThread.ExecuteOnUiThread(() =>
35+
{
36+
var p = _browsers.FirstOrDefault(x => x.Id == browserId);
2537

26-
browser.CreateBrowser(windowInfo);
38+
_browsers.Remove(p);
39+
40+
p.Dispose();
41+
42+
return true;
43+
});
44+
});
45+
46+
_jsonRpc.AddLocalRpcMethod("CloseHost", (Action)delegate ()
47+
{
48+
_ = CefThread.ExecuteOnUiThread(() =>
49+
{
50+
Cef.QuitMessageLoop();
51+
52+
return true;
53+
});
54+
});
55+
56+
_jsonRpc.AddLocalRpcMethod("CreateBrowser", (Action<int, string, int>)delegate (int parentHwnd, string url, int id)
57+
{
58+
Debugger.Break();
59+
60+
_ = CefThread.ExecuteOnUiThread(() =>
61+
{
62+
63+
var browser = new OutOfProcessChromiumWebBrowser(_jsonRpc, id, url);
64+
65+
var windowInfo = new WindowInfo();
66+
windowInfo.WindowName = "CefSharpBrowserProcess";
67+
windowInfo.SetAsChild(new IntPtr(parentHwnd));
68+
69+
browser.CreateBrowser(windowInfo);
70+
71+
_browsers.Add(browser);
72+
73+
return true;
74+
});
75+
});
76+
77+
var threadId = Kernel32.GetCurrentThreadId();
78+
79+
_ = _jsonRpc.NotifyAsync("OnContextInitialized", threadId);
80+
}
81+
82+
protected override void Dispose(bool disposing)
83+
{
84+
base.Dispose(disposing);
85+
86+
if(disposing)
87+
{
88+
_jsonRpc?.Dispose();
89+
_jsonRpc = null;
90+
}
2791
}
2892
}
2993
}

CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs

Lines changed: 25 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Threading;
55
using System.ComponentModel;
66
using StreamJsonRpc;
7-
using PInvoke;
87

98
namespace CefSharp.OutOfProcess.BrowserProcess
109
{
@@ -18,6 +17,11 @@ public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal
1817
"The undelying CefBrowser instance is not yet initialized. Use the IsBrowserInitializedChanged event and check " +
1918
"the IsBrowserInitialized property to determine when the browser has been initialized.";
2019

20+
/// <summary>
21+
/// Internal ID used for tracking browsers between Processes;
22+
/// </summary>
23+
private int _id;
24+
2125
/// <summary>
2226
/// The managed cef browser adapter
2327
/// </summary>
@@ -33,12 +37,6 @@ public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal
3337
/// </summary>
3438
private bool browserCreated;
3539

36-
/// <summary>
37-
/// Action which is called immediately before the <see cref="BrowserInitialized"/> event after the
38-
/// uderlying Chromium Embedded Framework (CEF) browser has been created.
39-
/// </summary>
40-
private Action<IBrowser> onAfterBrowserCreatedDelegate;
41-
4240
/// <summary>
4341
/// Used as workaround for issue https://github.com/cefsharp/CefSharp/issues/3021
4442
/// </summary>
@@ -70,6 +68,11 @@ public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal
7068
/// </summary>
7169
private Action<bool?, CefErrorCode?> initialLoadAction;
7270

71+
/// <summary>
72+
/// Id
73+
/// </summary>
74+
public int Id => _id;
75+
7376
/// <summary>
7477
/// Get access to the core <see cref="IBrowser"/> instance.
7578
/// Maybe null if the underlying CEF Browser has not yet been
@@ -258,7 +261,7 @@ void IWebBrowserInternal.SetCanExecuteJavascriptOnMainFrame(long frameId, bool c
258261
void IWebBrowserInternal.SetJavascriptMessageReceived(JavascriptMessageReceivedEventArgs args)
259262
{
260263
//Run the event on the ThreadPool (rather than the CEF Thread we are currently on).
261-
Task.Run(() => JavascriptMessageReceived?.Invoke(this, args));
264+
_ = Task.Run(() => JavascriptMessageReceived?.Invoke(this, args));
262265
}
263266

264267
/// <summary>
@@ -340,24 +343,7 @@ void IWebBrowserInternal.OnAfterBrowserCreated(IBrowser browser)
340343
initialLoadAction = InitialLoad;
341344
Interlocked.Exchange(ref browserInitialized, 1);
342345

343-
OnAfterBrowserCreated(browser);
344-
345-
_jsonRpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput());
346-
_jsonRpc.AllowModificationWhileListening = true;
347-
348-
_jsonRpc.AddLocalRpcMethod("CLOSE", (Action) delegate ()
349-
{
350-
_ = CefThread.ExecuteOnUiThread(() =>
351-
{
352-
Cef.QuitMessageLoop();
353-
354-
return true;
355-
});
356-
});
357-
358-
var threadId = Kernel32.GetCurrentThreadId();
359-
360-
_ = _jsonRpc.NotifyAsync("OnAfterBrowserCreated", browser.GetHost().GetWindowHandle().ToInt32(), threadId);
346+
_ = _jsonRpc.NotifyAsync("OnAfterBrowserCreated", _id, browser.GetHost().GetWindowHandle().ToInt32());
361347
}
362348

363349
/// <summary>
@@ -481,8 +467,6 @@ private void InitialLoad(bool? isLoading, CefErrorCode? errorCode)
481467
}
482468
}
483469

484-
partial void OnAfterBrowserCreated(IBrowser browser);
485-
486470
partial void SetLoadingStateChange(LoadingStateChangedEventArgs args);
487471

488472
/// <summary>
@@ -609,14 +593,6 @@ public bool IsBrowserInitialized
609593
/// <value>The accessibility handler.</value>
610594
public IAccessibilityHandler AccessibilityHandler { get; set; }
611595
/// <summary>
612-
/// Event called after the underlying CEF browser instance has been created.
613-
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
614-
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
615-
/// To access UI elements you'll need to Invoke/Dispatch onto the UI Thread.
616-
/// (The exception to this is when you're running with settings.MultiThreadedMessageLoop = false, then they'll be the same thread).
617-
/// </summary>
618-
public event EventHandler BrowserInitialized;
619-
/// <summary>
620596
/// Occurs when the browser address changed.
621597
/// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI
622598
/// thread. It is unwise to block on this thread for any length of time as your browser will become unresponsive and/or hang..
@@ -640,6 +616,7 @@ public bool IsBrowserInitialized
640616
/// that you set <paramref name="automaticallyCreateBrowser"/> to false, subscribe to the event and then call <see cref="CreateBrowser(IWindowInfo, IBrowserSettings)"/>
641617
/// to ensure you are subscribe to the event before it's fired (Issue https://github.com/cefsharp/CefSharp/issues/3552).
642618
/// </summary>
619+
/// <param name="id">id</param>
643620
/// <param name="address">Initial address (url) to load</param>
644621
/// <param name="browserSettings">The browser settings to use. If null, the default settings are used.</param>
645622
/// <param name="requestContext">See <see cref="RequestContext" /> for more details. Defaults to null</param>
@@ -650,15 +627,15 @@ public bool IsBrowserInitialized
650627
/// you have a chance to subscribe to the event as the CEF Browser is created async. (Issue https://github.com/cefsharp/CefSharp/issues/3552).
651628
/// </param>
652629
/// <exception cref="System.InvalidOperationException">Cef::Initialize() failed</exception>
653-
public OutOfProcessChromiumWebBrowser(string address = "",
654-
IRequestContext requestContext = null,
655-
Action<IBrowser> onAfterBrowserCreated = null)
630+
public OutOfProcessChromiumWebBrowser(JsonRpc jsonRpc, int id, string address = "",
631+
IRequestContext requestContext = null)
656632
{
633+
_id = id;
657634
RequestContext = requestContext;
635+
_jsonRpc = jsonRpc;
658636

659637
Cef.AddDisposable(this);
660638
Address = address;
661-
onAfterBrowserCreatedDelegate = onAfterBrowserCreated;
662639

663640
managedCefBrowserAdapter = ManagedCefBrowserAdapter.Create(this, false);
664641
}
@@ -695,15 +672,11 @@ protected virtual void Dispose(bool disposing)
695672

696673
if (disposing)
697674
{
698-
_jsonRpc?.Dispose();
699-
_jsonRpc = null;
700-
701675
CanExecuteJavascriptInMainFrame = false;
702676
Interlocked.Exchange(ref browserInitialized, 0);
703677

704678
// Don't reference event listeners any longer:
705679
AddressChanged = null;
706-
BrowserInitialized = null;
707680
ConsoleMessage = null;
708681
FrameLoadEnd = null;
709682
FrameLoadStart = null;
@@ -784,40 +757,6 @@ public void CreateBrowser(IWindowInfo windowInfo = null, IBrowserSettings browse
784757

785758
}
786759

787-
/// <summary>
788-
/// Create the underlying CEF browser. The address and request context passed into the constructor
789-
/// will be used. If a <see cref="Action{IBrowser}"/> delegate was passed
790-
/// into the constructor it will not be called as this method overrides that value internally.
791-
/// </summary>
792-
/// <param name="windowInfo">Window information used when creating the browser</param>
793-
/// <param name="browserSettings">Browser initialization settings</param>
794-
/// <exception cref="System.Exception">An instance of the underlying browser has already been created, this method can only be called once.</exception>
795-
/// <returns>
796-
/// A <see cref="Task{IBrowser}"/> that represents the creation of the underlying CEF browser (<see cref="IBrowser"/> instance.
797-
/// When the task completes then the CEF Browser will have been created and you can start performing basic tasks.
798-
/// Note that the control's <see cref="BrowserInitialized"/> event will be invoked after this task completes.
799-
/// </returns>
800-
public Task<IBrowser> CreateBrowserAsync(IWindowInfo windowInfo = null, IBrowserSettings browserSettings = null)
801-
{
802-
var tcs = new TaskCompletionSource<IBrowser>();
803-
804-
onAfterBrowserCreatedDelegate += new Action<IBrowser>(b =>
805-
{
806-
tcs.TrySetResultAsync(b);
807-
});
808-
809-
try
810-
{
811-
CreateBrowser(windowInfo, browserSettings);
812-
}
813-
catch (Exception ex)
814-
{
815-
tcs.TrySetExceptionAsync(ex);
816-
}
817-
818-
return tcs.Task;
819-
}
820-
821760
/// <inheritdoc/>
822761
public void Load(string url)
823762
{
@@ -851,12 +790,18 @@ public IJavascriptObjectRepository JavascriptObjectRepository
851790
}
852791

853792
/// <summary>
854-
/// Has Focus - Always False
793+
/// TODO: Improve focus
794+
/// Has Focus
855795
/// </summary>
856796
/// <returns>returns false</returns>
857797
bool IChromiumWebBrowserBase.Focus()
858798
{
859-
return false;
799+
ThrowExceptionIfDisposed();
800+
ThrowExceptionIfBrowserNotInitialized();
801+
802+
BrowserCore.GetHost().SetFocus(true);
803+
804+
return true;
860805
}
861806

862807
/// <summary>
@@ -871,16 +816,6 @@ public IBrowser GetBrowser()
871816
return browser;
872817
}
873818

874-
/// <summary>
875-
/// Called when [after browser created].
876-
/// </summary>
877-
/// <param name="browser">The browser.</param>
878-
partial void OnAfterBrowserCreated(IBrowser browser)
879-
{
880-
onAfterBrowserCreatedDelegate?.Invoke(browser);
881-
BrowserInitialized?.Invoke(this, EventArgs.Empty);
882-
}
883-
884819
/// <summary>
885820
/// Sets the address.
886821
/// </summary>

CefSharp.OutOfProcess.BrowserProcess/Program.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ public static int Main(string[] args)
1414
{
1515
Cef.EnableHighDPISupport();
1616

17-
//Debugger.Launch();
17+
Debugger.Launch();
1818

1919
var parentProcessId = int.Parse(CommandLineArgsParser.GetArgumentValue(args, "--parentProcessId"));
20-
var hostHwnd = int.Parse(CommandLineArgsParser.GetArgumentValue(args, "--hostHwnd"));
2120

2221
var parentProcess = Process.GetProcessById(parentProcessId);
2322

@@ -28,7 +27,7 @@ public static int Main(string[] args)
2827
MultiThreadedMessageLoop = false
2928
};
3029

31-
var browserProcessHandler = new BrowserProcessHandler(parentProcessId, new IntPtr(hostHwnd));
30+
var browserProcessHandler = new BrowserProcessHandler(parentProcessId);
3231

3332
Cef.EnableWaitForBrowsersToClose();
3433

0 commit comments

Comments
 (0)