Skip to content

Commit 8a10672

Browse files
committed
WPF - Add basic Wpf HwndHost implementation
1 parent 0805033 commit 8a10672

File tree

26 files changed

+1956
-38
lines changed

26 files changed

+1956
-38
lines changed

CefSharp.OutOfProcess.BrowserProcess/BrowserProcessHandler.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,30 @@ Task IOutOfProcessClientRpc.CreateBrowser(IntPtr parentHwnd, string url, int id)
9999
windowInfo.WindowName = "CefSharpBrowserProcess";
100100
windowInfo.SetAsChild(parentHwnd);
101101

102+
//Disable Window activation by default
103+
//https://bitbucket.org/chromiumembedded/cef/issues/1856/branch-2526-cef-activates-browser-window
104+
windowInfo.ExStyle |= OutOfProcessChromiumWebBrowser.WS_EX_NOACTIVATE;
105+
102106
browser.CreateBrowser(windowInfo);
103107

104108
_browsers.Add(browser);
105109

106110
return true;
107111
});
108112
}
113+
114+
void IOutOfProcessClientRpc.NotifyMoveOrResizeStarted(int browserId)
115+
{
116+
var browser = _browsers.FirstOrDefault(x => x.Id == browserId);
117+
118+
browser?.GetBrowserHost().NotifyMoveOrResizeStarted();
119+
}
120+
121+
void IOutOfProcessClientRpc.SetFocus(int browserId, bool focus)
122+
{
123+
var browser = _browsers.FirstOrDefault(x => x.Id == browserId);
124+
125+
browser?.GetBrowserHost().SetFocus(focus);
126+
}
109127
}
110128
}

CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
using CefSharp.Callback;
77
using System.IO;
88
using CefSharp.OutOfProcess.Interface;
9+
using System.Runtime.InteropServices;
10+
using PInvoke;
11+
using System.Diagnostics;
912

1013
namespace CefSharp.OutOfProcess.BrowserProcess
1114
{
@@ -19,9 +22,17 @@ public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal
1922
"The undelying CefBrowser instance is not yet initialized. Use the IsBrowserInitializedChanged event and check " +
2023
"the IsBrowserInitialized property to determine when the browser has been initialized.";
2124

25+
public const uint WS_EX_NOACTIVATE = 0x08000000;
26+
2227
private IDevToolsMessageObserver _devtoolsMessageObserver;
2328
private IRegistration _devtoolsRegistration;
2429

30+
/// <summary>
31+
/// If true the the WS_EX_NOACTIVATE style will be removed so that future mouse clicks
32+
/// inside the browser correctly activate and focus the window.
33+
/// </summary>
34+
private bool _removeExNoActivateStyle;
35+
2536
/// <summary>
2637
/// Internal ID used for tracking browsers between Processes;
2738
/// </summary>
@@ -382,6 +393,18 @@ void IWebBrowserInternal.OnAfterBrowserCreated(IBrowser browser)
382393
/// <param name="args">The <see cref="LoadingStateChangedEventArgs"/> instance containing the event data.</param>
383394
void IWebBrowserInternal.SetLoadingStateChange(LoadingStateChangedEventArgs args)
384395
{
396+
if (_removeExNoActivateStyle && InternalIsBrowserInitialized())
397+
{
398+
_removeExNoActivateStyle = false;
399+
400+
var hwnd = BrowserCore.GetHost().GetWindowHandle();
401+
402+
// Remove the WS_EX_NOACTIVATE style so that future mouse clicks inside the
403+
// browser correctly activate and focus the browser.
404+
//https://github.com/chromiumembedded/cef/blob/9df4a54308a88fd80c5774d91c62da35afb5fd1b/tests/cefclient/browser/root_window_win.cc#L1088
405+
RemoveExNoActivateStyle(hwnd);
406+
}
407+
385408
CanGoBack = args.CanGoBack;
386409
CanGoForward = args.CanGoForward;
387410
IsLoading = args.IsLoading;
@@ -391,6 +414,42 @@ void IWebBrowserInternal.SetLoadingStateChange(LoadingStateChangedEventArgs args
391414
LoadingStateChanged?.Invoke(this, args);
392415
}
393416

417+
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
418+
private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int index);
419+
420+
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
421+
private static extern int SetWindowLong32(HandleRef hWnd, int index, int dwNewLong);
422+
423+
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
424+
private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int index, IntPtr dwNewLong);
425+
426+
private const int GWL_EXSTYLE = -20;
427+
428+
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowlongptra
429+
//SetWindowLongPtr for x64, SetWindowLong for x86
430+
private void RemoveExNoActivateStyle(IntPtr hwnd)
431+
{
432+
var exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
433+
434+
if (IntPtr.Size == 8)
435+
{
436+
if ((exStyle.ToInt64() & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE)
437+
{
438+
exStyle = new IntPtr(exStyle.ToInt64() & ~WS_EX_NOACTIVATE);
439+
//Remove WS_EX_NOACTIVATE
440+
SetWindowLongPtr64(new HandleRef(this, hwnd), GWL_EXSTYLE, exStyle);
441+
}
442+
}
443+
else
444+
{
445+
if ((exStyle.ToInt32() & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE)
446+
{
447+
//Remove WS_EX_NOACTIVATE
448+
SetWindowLong32(new HandleRef(this, hwnd), GWL_EXSTYLE, (int)(exStyle.ToInt32() & ~WS_EX_NOACTIVATE));
449+
}
450+
}
451+
}
452+
394453
/// <inheritdoc/>
395454
public void LoadUrl(string url)
396455
{
@@ -699,6 +758,8 @@ protected virtual void Dispose(bool disposing)
699758
/// <exception cref="System.Exception">An instance of the underlying browser has already been created, this method can only be called once.</exception>
700759
public void CreateBrowser(IWindowInfo windowInfo = null, IBrowserSettings browserSettings = null)
701760
{
761+
//Debugger.Break();
762+
702763
if (_browserCreated)
703764
{
704765
throw new Exception("An instance of the underlying browser has already been created, this method can only be called once.");
@@ -711,11 +772,9 @@ public void CreateBrowser(IWindowInfo windowInfo = null, IBrowserSettings browse
711772
browserSettings = Core.ObjectFactory.CreateBrowserSettings(autoDispose: true);
712773
}
713774

714-
if (windowInfo == null)
715-
{
716-
windowInfo = Core.ObjectFactory.CreateWindowInfo();
717-
windowInfo.SetAsWindowless(IntPtr.Zero);
718-
}
775+
//We actually check if WS_EX_NOACTIVATE was set for instances
776+
//the user has override CreateBrowserWindowInfo and not called base.CreateBrowserWindowInfo
777+
_removeExNoActivateStyle = (windowInfo.ExStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE;
719778

720779
//TODO: We need some sort of timeout and
721780
//if we use the same approach for WPF/WinForms then

CefSharp.OutOfProcess.BrowserProcess/Program.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Diagnostics;
3-
using System.IO;
43
using System.Threading.Tasks;
54
using CefSharp.Internals;
65

@@ -18,21 +17,27 @@ public static int Main(string[] args)
1817
//Debugger.Launch();
1918

2019
var parentProcessId = int.Parse(CommandLineArgsParser.GetArgumentValue(args, "--parentProcessId"));
20+
var cachePath = CommandLineArgsParser.GetArgumentValue(args, "--cachePath");
2121

2222
var parentProcess = Process.GetProcessById(parentProcessId);
2323

2424
var settings = new CefSettings()
2525
{
2626
//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data
27-
CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\OutOfProcessCache"),
27+
CachePath = cachePath,
2828
MultiThreadedMessageLoop = false
2929
};
3030

3131
var browserProcessHandler = new BrowserProcessHandler(parentProcessId);
3232

3333
Cef.EnableWaitForBrowsersToClose();
3434

35-
Cef.Initialize(settings, performDependencyCheck:true, browserProcessHandler: browserProcessHandler);
35+
var success = Cef.Initialize(settings, performDependencyCheck:true, browserProcessHandler: browserProcessHandler);
36+
37+
if(!success)
38+
{
39+
return 1;
40+
}
3641

3742
_ = Task.Run(() =>
3843
{

CefSharp.OutOfProcess.Core/IChromiumWebBrowser.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ public interface IChromiumWebBrowser : IDisposable
1313
/// Occurs when the browser address changed.
1414
/// </summary>
1515
event EventHandler<AddressChangedEventArgs> AddressChanged;
16-
/// <summary>
17-
/// Occurs when document title changes.
18-
/// </summary>
19-
event EventHandler<TitleChangedEventArgs> TitleChanged;
2016

2117
/// <summary>
2218
/// Event handler for changes to the status message.

CefSharp.OutOfProcess.Core/Internal/IChromiumWebBrowserInternal.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public interface IChromiumWebBrowserInternal : IChromiumWebBrowser
1313
/// Set the browser Hwnd
1414
/// </summary>
1515
/// <param name="hwnd">Hwnd</param>
16-
void SetBrowserHwnd(IntPtr hwnd);
16+
void OnAfterBrowserCreated(IntPtr hwnd);
1717

1818
/// <summary>
1919
/// Called when a DevTools message arrives from the browser process

CefSharp.OutOfProcess.Core/OutOfProcessHost.cs

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,22 @@ public class OutOfProcessHost : IOutOfProcessHostRpc, IDisposable
1919

2020
private Process _browserProcess;
2121
private JsonRpc _jsonRpc;
22-
private IOutOfProcessClientRpc _browserProcessServer;
22+
private IOutOfProcessClientRpc _outOfProcessClient;
2323
private string _cefSharpVersion;
2424
private string _cefVersion;
2525
private string _chromiumVersion;
2626
private int _uiThreadId;
2727
private int _remoteuiThreadId;
2828
private int _browserIdentifier = 1;
29-
private string _outofProcessFilePath;
30-
29+
private string _outofProcessHostExePath;
30+
private string _cachePath;
3131
private ConcurrentDictionary<int, IChromiumWebBrowserInternal> _browsers = new ConcurrentDictionary<int, IChromiumWebBrowserInternal>();
3232
private TaskCompletionSource<OutOfProcessHost> _processInitialized = new TaskCompletionSource<OutOfProcessHost>(TaskCreationOptions.RunContinuationsAsynchronously);
3333

34-
private OutOfProcessHost(string path)
34+
private OutOfProcessHost(string outOfProcessHostExePath, string cachePath = null)
3535
{
36-
_outofProcessFilePath = path;
36+
_outofProcessHostExePath = outOfProcessHostExePath;
37+
_cachePath = cachePath;
3738
}
3839

3940
/// <summary>
@@ -88,14 +89,14 @@ public string ChromiumVersion
8889
public bool CreateBrowser(IChromiumWebBrowserInternal browser, IntPtr handle, string url, out int id)
8990
{
9091
id = _browserIdentifier++;
91-
_ = _browserProcessServer.CreateBrowser(handle, url, id);
92+
_ = _outOfProcessClient.CreateBrowser(handle, url, id);
9293

9394
return _browsers.TryAdd(id, browser);
9495
}
9596

9697
internal Task SendDevToolsMessageAsync(int browserId, string message)
9798
{
98-
return _browserProcessServer.SendDevToolsMessage(browserId, message);
99+
return _outOfProcessClient.SendDevToolsMessage(browserId, message);
99100
}
100101

101102
private Task<OutOfProcessHost> InitializedTask
@@ -107,24 +108,31 @@ private void Init()
107108
{
108109
var currentProcess = Process.GetCurrentProcess();
109110

110-
var args = $"--parentProcessId={currentProcess.Id}";
111+
var args = $"--parentProcessId={currentProcess.Id} --cachePath={_cachePath}";
111112

112-
_browserProcess = Process.Start(new ProcessStartInfo(_outofProcessFilePath, args)
113+
_browserProcess = Process.Start(new ProcessStartInfo(_outofProcessHostExePath, args)
113114
{
114115
RedirectStandardInput = true,
115116
RedirectStandardOutput = true,
116117
});
117118

119+
_browserProcess.Exited += OnBrowserProcessExited;
120+
118121
_jsonRpc = JsonRpc.Attach(_browserProcess.StandardInput.BaseStream, _browserProcess.StandardOutput.BaseStream);
119122

120-
_browserProcessServer = _jsonRpc.Attach<IOutOfProcessClientRpc>();
123+
_outOfProcessClient = _jsonRpc.Attach<IOutOfProcessClientRpc>();
121124
_jsonRpc.AllowModificationWhileListening = true;
122125
_jsonRpc.AddLocalRpcTarget<IOutOfProcessHostRpc>(this, null);
123126
_jsonRpc.AllowModificationWhileListening = false;
124127

125128
_uiThreadId = Kernel32.GetCurrentThreadId();
126129
}
127130

131+
private void OnBrowserProcessExited(object sender, EventArgs e)
132+
{
133+
var exitCode = _browserProcess.ExitCode;
134+
}
135+
128136
void IOutOfProcessHostRpc.NotifyAddressChanged(int browserId, string address)
129137
{
130138
if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser))
@@ -137,7 +145,7 @@ void IOutOfProcessHostRpc.NotifyBrowserCreated(int browserId, IntPtr browserHwnd
137145
{
138146
if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser))
139147
{
140-
chromiumWebBrowser.SetBrowserHwnd(browserHwnd);
148+
chromiumWebBrowser.OnAfterBrowserCreated(browserHwnd);
141149
}
142150
}
143151

@@ -199,19 +207,34 @@ void IOutOfProcessHostRpc.NotifyTitleChanged(int browserId, string title)
199207
}
200208
}
201209

210+
public void NotifyMoveOrResizeStarted(int id)
211+
{
212+
_outOfProcessClient.NotifyMoveOrResizeStarted(id);
213+
}
214+
215+
/// <summary>
216+
/// Set whether the browser is focused. (Used for Normal Rendering e.g. WinForms)
217+
/// </summary>
218+
/// <param name="id">browser id</param>
219+
/// <param name="focus">set focus</param>
220+
public void SetFocus(int id, bool focus)
221+
{
222+
_outOfProcessClient.SetFocus(id, focus);
223+
}
224+
202225
public void CloseBrowser(int id)
203226
{
204-
_browserProcessServer.CloseBrowser(id);
227+
_ = _outOfProcessClient.CloseBrowser(id);
205228
}
206229

207230
public void Dispose()
208231
{
209-
_browserProcessServer.CloseHost();
232+
_ = _outOfProcessClient.CloseHost();
210233
_jsonRpc?.Dispose();
211234
_jsonRpc = null;
212235
}
213236

214-
public static Task<OutOfProcessHost> CreateAsync(string path = HostExeName)
237+
public static Task<OutOfProcessHost> CreateAsync(string path = HostExeName, string cachePath = null)
215238
{
216239
if(string.IsNullOrEmpty(path))
217240
{
@@ -225,7 +248,7 @@ public static Task<OutOfProcessHost> CreateAsync(string path = HostExeName)
225248
throw new FileNotFoundException("Unable to find Host executable.", path);
226249
}
227250

228-
var host = new OutOfProcessHost(fullPath);
251+
var host = new OutOfProcessHost(fullPath, cachePath);
229252

230253
host.Init();
231254

CefSharp.OutOfProcess.Interface/IOutOfProcessClientRpc.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public interface IOutOfProcessClientRpc
2020
/// </summary>
2121
/// <param name="browserId">browser Id</param>
2222
/// <param name="message"devtools message (json)></param>
23-
/// <returns></returns>
23+
/// <returns>Task</returns>
2424
Task SendDevToolsMessage(int browserId, string message);
2525

2626
/// <summary>
@@ -34,8 +34,22 @@ public interface IOutOfProcessClientRpc
3434
/// </summary>
3535
/// <param name="parentHwnd">parent Hwnd</param>
3636
/// <param name="url">start url</param>
37-
/// <param name="id">browser id</param>
37+
/// <param name="browserId">browser id</param>
3838
/// <returns>Task</returns>
39-
Task CreateBrowser(IntPtr parentHwnd, string url, int id);
39+
Task CreateBrowser(IntPtr parentHwnd, string url, int browserId);
40+
41+
/// <summary>
42+
/// Notify the browser that the window hosting it is about to be moved or resized.
43+
/// This will dismiss any existing popups (dropdowns).
44+
/// </summary>
45+
/// <param name="browserId">browser Id</param>
46+
void NotifyMoveOrResizeStarted(int browserId);
47+
48+
/// <summary>
49+
/// Set whether the browser is focused.
50+
/// </summary>
51+
/// <param name="id">browser id</param>
52+
/// <param name="focus">set focus</param>
53+
void SetFocus(int browserId, bool focus);
4054
}
4155
}

CefSharp.OutOfProcess.WinForms.Example/BrowserForm.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ private async void BrowserFormLoad(object sender, EventArgs e)
6161
{
6262
var outOfProcessHostPath = Path.GetFullPath($"..\\..\\..\\..\\..\\CefSharp.OutOfProcess.BrowserProcess\\bin\\{_buildType}\\{_targetFramework}");
6363
outOfProcessHostPath = Path.Combine(outOfProcessHostPath, OutOfProcessHost.HostExeName);
64-
_outOfProcessHost = await OutOfProcessHost.CreateAsync(outOfProcessHostPath);
64+
var cachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\OutOfProcessCache");
65+
_outOfProcessHost = await OutOfProcessHost.CreateAsync(outOfProcessHostPath, cachePath);
6566

6667
AddTab(DefaultUrlForAddedTabs);
6768
}

CefSharp.OutOfProcess.WinForms.Example/BrowserTabUserControl.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ private async void OnBrowserNetworkRequestFailed(object sender, Puppeteer.Reques
4343
{
4444
var request = args.Request;
4545

46-
var errorHtml = string.Format("<html><body><h2>Failed to load URL {0} with error {1} ({2}).</h2></body></html>",
46+
var errorHtml = string.Format("<html><body><h2>Failed to load URL {0} with error {1}.</h2></body></html>",
4747
request.Url, request.Failure);
4848

4949
await Browser.MainFrame.SetContentAsync(errorHtml);

0 commit comments

Comments
 (0)