Skip to content

Commit 7296c46

Browse files
committed
Close OutOfProcess browser on Dispose
1 parent 17dcfc4 commit 7296c46

7 files changed

Lines changed: 118 additions & 34 deletions

File tree

CefSharp.OutOfProcess.BrowserProcess/BrowserProcessHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protected override void OnContextInitialized()
1717
{
1818
base.OnContextInitialized();
1919

20-
var browser = new ChromiumWebBrowser("https://github.com");
20+
var browser = new OutOfProcessChromiumWebBrowser("https://github.com");
2121

2222
var windowInfo = new WindowInfo();
2323
windowInfo.WindowName = "CefSharpBrowserProcess";

CefSharp.OutOfProcess.BrowserProcess/CefSharp.OutOfProcess.BrowserProcess.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<OutputType>WinExe</OutputType>
@@ -12,6 +12,8 @@
1212

1313
<ItemGroup>
1414
<PackageReference Include="CefSharp.Common.NETCore" Version="102.0.90-RCI4501" />
15+
<PackageReference Include="PInvoke.Kernel32" Version="0.7.104" />
16+
<PackageReference Include="StreamJsonRpc" Version="2.11.35" />
1517
</ItemGroup>
1618

1719
</Project>

CefSharp.OutOfProcess.BrowserProcess/ChromiumWebBrowser.cs renamed to CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
using System.Threading.Tasks;
44
using System.Threading;
55
using System.ComponentModel;
6+
using StreamJsonRpc;
7+
using PInvoke;
68

79
namespace CefSharp.OutOfProcess.BrowserProcess
810
{
911
/// <summary>
1012
/// An ChromiumWebBrowser instance specifically for hosting CEF out of process
1113
/// </summary>
12-
public partial class ChromiumWebBrowser : IWebBrowserInternal
14+
public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal
1315
{
1416
public const string BrowserNotInitializedExceptionErrorMessage =
1517
"The ChromiumWebBrowser instance creates the underlying Chromium Embedded Framework (CEF) browser instance in an async fashion. " +
@@ -21,9 +23,13 @@ public partial class ChromiumWebBrowser : IWebBrowserInternal
2123
/// </summary>
2224
private IBrowserAdapter managedCefBrowserAdapter;
2325

26+
/// <summary>
27+
/// JSON RPC used for IPC with host
28+
/// </summary>
29+
private JsonRpc _jsonRpc;
2430

2531
/// <summary>
26-
/// Flag to guard the creation of the underlying offscreen browser - only one instance can be created
32+
/// Flag to guard the creation of the underlying browser - only one instance can be created
2733
/// </summary>
2834
private bool browserCreated;
2935

@@ -335,6 +341,23 @@ void IWebBrowserInternal.OnAfterBrowserCreated(IBrowser browser)
335341
Interlocked.Exchange(ref browserInitialized, 1);
336342

337343
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);
338361
}
339362

340363
/// <summary>
@@ -611,7 +634,7 @@ public bool IsBrowserInitialized
611634
public event EventHandler<TitleChangedEventArgs> TitleChanged;
612635

613636
/// <summary>
614-
/// Create a new OffScreen Chromium Browser. If you use <see cref="CefSharp.JavascriptBinding.JavascriptBindingSettings.LegacyBindingEnabled"/> = true then you must
637+
/// Create a new ChromiumWebBrowser. If you use <see cref="CefSharp.JavascriptBinding.JavascriptBindingSettings.LegacyBindingEnabled"/> = true then you must
615638
/// set <paramref name="automaticallyCreateBrowser"/> to false and call <see cref="CreateBrowser"/> after the objects are registered.
616639
/// The underlying Chromium Embedded Framework(CEF) Browser is created asynchronouly, to subscribe to the <see cref="BrowserInitialized"/> event it is recommended
617640
/// that you set <paramref name="automaticallyCreateBrowser"/> to false, subscribe to the event and then call <see cref="CreateBrowser(IWindowInfo, IBrowserSettings)"/>
@@ -627,7 +650,7 @@ public bool IsBrowserInitialized
627650
/// you have a chance to subscribe to the event as the CEF Browser is created async. (Issue https://github.com/cefsharp/CefSharp/issues/3552).
628651
/// </param>
629652
/// <exception cref="System.InvalidOperationException">Cef::Initialize() failed</exception>
630-
public ChromiumWebBrowser(string address = "",
653+
public OutOfProcessChromiumWebBrowser(string address = "",
631654
IRequestContext requestContext = null,
632655
Action<IBrowser> onAfterBrowserCreated = null)
633656
{
@@ -641,15 +664,15 @@ public ChromiumWebBrowser(string address = "",
641664
}
642665

643666
/// <summary>
644-
/// Finalizes an instance of the <see cref="ChromiumWebBrowser"/> class.
667+
/// Finalizes an instance of the <see cref="OutOfProcessChromiumWebBrowser"/> class.
645668
/// </summary>
646-
~ChromiumWebBrowser()
669+
~OutOfProcessChromiumWebBrowser()
647670
{
648671
Dispose(false);
649672
}
650673

651674
/// <summary>
652-
/// Releases all resources used by the <see cref="ChromiumWebBrowser"/> object
675+
/// Releases all resources used by the <see cref="OutOfProcessChromiumWebBrowser"/> object
653676
/// </summary>
654677
public void Dispose()
655678
{
@@ -658,7 +681,7 @@ public void Dispose()
658681
}
659682

660683
/// <summary>
661-
/// Releases unmanaged and - optionally - managed resources for the <see cref="ChromiumWebBrowser"/>
684+
/// Releases unmanaged and - optionally - managed resources for the <see cref="OutOfProcessChromiumWebBrowser"/>
662685
/// </summary>
663686
/// <param name="disposing"><see langword="true" /> to release both managed and unmanaged resources; <see langword="false" /> to release only unmanaged resources.</param>
664687
protected virtual void Dispose(bool disposing)
@@ -672,6 +695,9 @@ protected virtual void Dispose(bool disposing)
672695

673696
if (disposing)
674697
{
698+
_jsonRpc?.Dispose();
699+
_jsonRpc = null;
700+
675701
CanExecuteJavascriptInMainFrame = false;
676702
Interlocked.Exchange(ref browserInitialized, 0);
677703

@@ -698,11 +724,8 @@ protected virtual void Dispose(bool disposing)
698724
browser = null;
699725
BrowserCore = null;
700726

701-
if (managedCefBrowserAdapter != null)
702-
{
703-
managedCefBrowserAdapter.Dispose();
704-
managedCefBrowserAdapter = null;
705-
}
727+
managedCefBrowserAdapter?.Dispose();
728+
managedCefBrowserAdapter = null;
706729

707730
// LifeSpanHandler is set to null after managedCefBrowserAdapter.Dispose so ILifeSpanHandler.DoClose
708731
// is called.
@@ -717,12 +740,12 @@ protected virtual void Dispose(bool disposing)
717740
/// </summary>
718741
/// <param name="windowInfo">Window information used when creating the browser</param>
719742
/// <param name="browserSettings">Browser initialization settings</param>
720-
/// <exception cref="System.Exception">An instance of the underlying offscreen browser has already been created, this method can only be called once.</exception>
743+
/// <exception cref="System.Exception">An instance of the underlying browser has already been created, this method can only be called once.</exception>
721744
public void CreateBrowser(IWindowInfo windowInfo = null, IBrowserSettings browserSettings = null)
722745
{
723746
if (browserCreated)
724747
{
725-
throw new Exception("An instance of the underlying offscreen browser has already been created, this method can only be called once.");
748+
throw new Exception("An instance of the underlying browser has already been created, this method can only be called once.");
726749
}
727750

728751
browserCreated = true;
@@ -768,7 +791,7 @@ public void CreateBrowser(IWindowInfo windowInfo = null, IBrowserSettings browse
768791
/// </summary>
769792
/// <param name="windowInfo">Window information used when creating the browser</param>
770793
/// <param name="browserSettings">Browser initialization settings</param>
771-
/// <exception cref="System.Exception">An instance of the underlying offscreen browser has already been created, this method can only be called once.</exception>
794+
/// <exception cref="System.Exception">An instance of the underlying browser has already been created, this method can only be called once.</exception>
772795
/// <returns>
773796
/// A <see cref="Task{IBrowser}"/> that represents the creation of the underlying CEF browser (<see cref="IBrowser"/> instance.
774797
/// When the task completes then the CEF Browser will have been created and you can start performing basic tasks.
@@ -833,7 +856,6 @@ public IJavascriptObjectRepository JavascriptObjectRepository
833856
/// <returns>returns false</returns>
834857
bool IChromiumWebBrowserBase.Focus()
835858
{
836-
// no control to focus for offscreen browser
837859
return false;
838860
}
839861

@@ -899,4 +921,4 @@ void IWebBrowserInternal.SetTooltipText(string tooltipText)
899921
TooltipText = tooltipText;
900922
}
901923
}
902-
}
924+
}

CefSharp.OutOfProcess.BrowserProcess/Program.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ namespace CefSharp.OutOfProcess.BrowserProcess
88
{
99
public class Program
1010
{
11+
private static bool _disposed;
12+
1113
public static int Main(string[] args)
1214
{
1315
Cef.EnableHighDPISupport();
1416

15-
Debugger.Launch();
17+
//Debugger.Launch();
1618

1719
var parentProcessId = int.Parse(CommandLineArgsParser.GetArgumentValue(args, "--parentProcessId"));
1820
var hostHwnd = int.Parse(CommandLineArgsParser.GetArgumentValue(args, "--hostHwnd"));
@@ -36,6 +38,11 @@ public static int Main(string[] args)
3638
{
3739
parentProcess.WaitForExit();
3840

41+
if(_disposed)
42+
{
43+
return;
44+
}
45+
3946
CefThread.ExecuteOnUiThread(() =>
4047
{
4148
Cef.QuitMessageLoop();
@@ -44,7 +51,9 @@ public static int Main(string[] args)
4451
});
4552
});
4653

47-
Cef.RunMessageLoop();
54+
Cef.RunMessageLoop();
55+
56+
_disposed = true;
4857

4958
Cef.WaitForBrowsersToClose();
5059

CefSharp.OutOfProcess.Example/ChromiumWebBrowser.cs

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,24 @@
33
using System.Drawing;
44
using System.Windows.Forms;
55
using PInvoke;
6+
using StreamJsonRpc;
67

78
namespace CefSharp.OutOfProcess.Example
89
{
910
public class ChromiumWebBrowser : Control
1011
{
1112
private IntPtr _browserHwnd = IntPtr.Zero;
12-
private Process _browserProces;
13+
private int _remoteThreadId = -1;
14+
private Process _browserProcess;
15+
private bool _disposed;
16+
private JsonRpc _jsonRpc;
1317

1418
/// <summary>
1519
/// The <see cref="Process"/> in which the CEF Browser is actually running.
1620
/// </summary>
1721
public Process BrowserProcess
1822
{
19-
get { return _browserProces; }
23+
get { return _browserProcess; }
2024
}
2125

2226
/// <summary>
@@ -38,7 +42,44 @@ protected override void OnHandleCreated(EventArgs e)
3842

3943
var args = $"--parentProcessId={currentProcess.Id} --hostHwnd={Handle.ToInt32()}";
4044

41-
_browserProces = Process.Start("CefSharp.OutOfProcess.BrowserProcess.exe", args);
45+
_browserProcess = Process.Start(new ProcessStartInfo("CefSharp.OutOfProcess.BrowserProcess.exe", args)
46+
{
47+
RedirectStandardInput = true,
48+
RedirectStandardOutput = true,
49+
});
50+
51+
Action<int, int> OnAfterBrowserCreatedDelegate = delegate (int ptr, int threadId)
52+
{
53+
_browserHwnd = new IntPtr(ptr);
54+
_remoteThreadId = threadId;
55+
};
56+
57+
_jsonRpc = JsonRpc.Attach(_browserProcess.StandardInput.BaseStream, _browserProcess.StandardOutput.BaseStream);
58+
_jsonRpc.AllowModificationWhileListening = true;
59+
60+
_jsonRpc.AddLocalRpcMethod("OnAfterBrowserCreated", OnAfterBrowserCreatedDelegate);
61+
62+
_jsonRpc.AllowModificationWhileListening = false;
63+
}
64+
65+
protected override void Dispose(bool disposing)
66+
{
67+
base.Dispose(disposing);
68+
69+
if(disposing)
70+
{
71+
_remoteThreadId = -1;
72+
73+
#pragma warning disable VSTHRD110 // Observe result of async calls
74+
_ = _jsonRpc?.NotifyAsync("CLOSE");
75+
#pragma warning restore VSTHRD110 // Observe result of async calls
76+
_jsonRpc?.Dispose();
77+
_jsonRpc = null;
78+
_browserHwnd = IntPtr.Zero;
79+
_disposed = true;
80+
81+
_browserProcess.WaitForExit();
82+
}
4283
}
4384

4485
/// <inheritdoc />
@@ -72,15 +113,6 @@ protected override void OnSizeChanged(EventArgs e)
72113
/// <param name="height">height</param>
73114
protected virtual void ResizeBrowser(int width, int height)
74115
{
75-
if (_browserHwnd == IntPtr.Zero)
76-
{
77-
var result = ChromiumRenderWidgetHandleFinder.TryFindHandle(Handle, "CefBrowserWindow", out var handle);
78-
if (result)
79-
{
80-
_browserHwnd = handle;
81-
}
82-
}
83-
84116
if (_browserHwnd != IntPtr.Zero)
85117
{
86118
if (width == 0 && height == 0)

CefSharp.OutOfProcess.Example/HostForm.Designer.cs

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CefSharp.OutOfProcess.Example/HostForm.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,14 @@ private void ExitToolStripMenuItemClick(object sender, EventArgs e)
3232
{
3333
Close();
3434
}
35+
36+
private void CloseBrowserToolStripMenuItemClick(object sender, EventArgs e)
37+
{
38+
var ctrl = splitContainer.Panel2.Controls[0];
39+
40+
splitContainer.Panel2.Controls.Remove(ctrl);
41+
42+
ctrl.Dispose();
43+
}
3544
}
3645
}

0 commit comments

Comments
 (0)