Skip to content

Commit

Permalink
Rewrite ChromeWidgetHandleFinder to use a GCHandle - much cleaner met…
Browse files Browse the repository at this point in the history
…hod of interfacing with dllimport
  • Loading branch information
amaitland committed Apr 10, 2016
1 parent 4e79f10 commit 38254a5
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 122 deletions.
13 changes: 0 additions & 13 deletions CefSharp.WinForms.Example/BrowserTabUserControl.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

124 changes: 88 additions & 36 deletions CefSharp.WinForms.Example/BrowserTabUserControl.cs
Expand Up @@ -10,13 +10,16 @@
using CefSharp.WinForms.Internals;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CefSharp.WinForms.Example
{
public partial class BrowserTabUserControl : UserControl
{
public IWinFormsWebBrowser Browser { get; private set; }
private IntPtr browserHandle;
private ChromeWidgetMessageInterceptor messageInterceptor;

public BrowserTabUserControl(Action<string, int?> openNewTab, string url)
{
Expand Down Expand Up @@ -66,6 +69,29 @@ public BrowserTabUserControl(Action<string, int?> openNewTab, string url)
DisplayOutput(version);
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
components = null;
}

if (messageInterceptor != null)
{
messageInterceptor.ReleaseHandle();
messageInterceptor = null;
}
}
base.Dispose(disposing);
}

private void OnBrowserHandleCreated(object sender, EventArgs e)
{
browserHandle = ((ChromiumWebBrowser)Browser).Handle;
Expand Down Expand Up @@ -170,44 +196,70 @@ private void OnIsBrowserInitializedChanged(object sender, IsBrowserInitializedCh
}
var preferences = requestContext.GetAllPreferences(true);
var doNotTrack = (bool)preferences["enable_do_not_track"];

ChromeWidgetMessageInterceptor.SetupLoop((ChromiumWebBrowser)Browser, (message) =>


Task.Run(() =>
{
const int WM_MOUSEACTIVATE = 0x0021;
const int WM_NCLBUTTONDOWN = 0x00A1;
const int WM_LBUTTONDOWN = 0x0201;
if (message.Msg == WM_MOUSEACTIVATE) {
// The default processing of WM_MOUSEACTIVATE results in MA_NOACTIVATE,
// and the subsequent mouse click is eaten by Chrome.
// This means any .NET ToolStrip or ContextMenuStrip does not get closed.
// By posting a WM_NCLBUTTONDOWN message to a harmless co-ordinate of the
// top-level window, we rely on the ToolStripManager's message handling
// to close any open dropdowns:
// http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/ToolStripManager.cs,1249
var topLevelWindowHandle = message.WParam;
PostMessage(topLevelWindowHandle, WM_NCLBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
try
{
while (true)
{
IntPtr chromeWidgetHostHandle;
if (ChromeWidgetHandleFinder.TryFindHandle(browserHandle, out chromeWidgetHostHandle))
{
messageInterceptor = new ChromeWidgetMessageInterceptor((Control)Browser, chromeWidgetHostHandle, message =>
{
const int WM_MOUSEACTIVATE = 0x0021;
const int WM_NCLBUTTONDOWN = 0x00A1;
const int WM_LBUTTONDOWN = 0x0201;
if (message.Msg == WM_MOUSEACTIVATE)
{
// The default processing of WM_MOUSEACTIVATE results in MA_NOACTIVATE,
// and the subsequent mouse click is eaten by Chrome.
// This means any .NET ToolStrip or ContextMenuStrip does not get closed.
// By posting a WM_NCLBUTTONDOWN message to a harmless co-ordinate of the
// top-level window, we rely on the ToolStripManager's message handling
// to close any open dropdowns:
// http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/ToolStripManager.cs,1249
var topLevelWindowHandle = message.WParam;
PostMessage(topLevelWindowHandle, WM_NCLBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
}
//Forward mouse button down message to browser control
//else if(message.Msg == WM_LBUTTONDOWN)
//{
// PostMessage(browserHandle, WM_LBUTTONDOWN, message.WParam, message.LParam);
//}
// The ChromiumWebBrowserControl does not fire MouseEnter/Move/Leave events, because Chromium handles these.
// However we can hook into Chromium's messaging window to receive the events.
//
//const int WM_MOUSEMOVE = 0x0200;
//const int WM_MOUSELEAVE = 0x02A3;
//
//switch (message.Msg) {
// case WM_MOUSEMOVE:
// Console.WriteLine("WM_MOUSEMOVE");
// break;
// case WM_MOUSELEAVE:
// Console.WriteLine("WM_MOUSELEAVE");
// break;
//}
});
break;
}
else
{
// Chrome hasn't yet set up its message-loop window.
Thread.Sleep(10);
}
}
}
catch
{
// Errors are likely to occur if browser is disposed, and no good way to check from another thread
}
//Forward mouse button down message to browser control
//else if(message.Msg == WM_LBUTTONDOWN)
//{
// PostMessage(browserHandle, WM_LBUTTONDOWN, message.WParam, message.LParam);
//}
// The ChromiumWebBrowserControl does not fire MouseEnter/Move/Leave events, because Chromium handles these.
// However we can hook into Chromium's messaging window to receive the events.
//
//const int WM_MOUSEMOVE = 0x0200;
//const int WM_MOUSELEAVE = 0x02A3;
//
//switch (message.Msg) {
// case WM_MOUSEMOVE:
// Console.WriteLine("WM_MOUSEMOVE");
// break;
// case WM_MOUSELEAVE:
// Console.WriteLine("WM_MOUSELEAVE");
// break;
//}
});
}
}
Expand Down
103 changes: 30 additions & 73 deletions CefSharp.WinForms.Example/ChromeWidgetMessageInterceptor.cs
Expand Up @@ -5,8 +5,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CefSharp.WinForms.Example
Expand All @@ -19,63 +17,25 @@ namespace CefSharp.WinForms.Example
///
/// The supplied Action delegate is fired upon each message.
/// </summary>
class ChromeWidgetMessageInterceptor : NativeWindow
internal class ChromeWidgetMessageInterceptor : NativeWindow
{
private readonly ChromiumWebBrowser browser;
private Action<Message> forwardAction;

private ChromeWidgetMessageInterceptor(ChromiumWebBrowser browser, IntPtr chromeWidgetHostHandle, Action<Message> forwardAction)
internal ChromeWidgetMessageInterceptor(Control browser, IntPtr chromeWidgetHostHandle, Action<Message> forwardAction)
{
AssignHandle(chromeWidgetHostHandle);

this.browser = browser;
browser.HandleDestroyed += BrowserHandleDestroyed;

this.forwardAction = forwardAction;
}

/// <summary>
/// Asynchronously wait for the Chromium widget window to be created for the given ChromiumWebBrowser,
/// and when created hook into its Windows message loop.
/// </summary>
/// <param name="browser">The browser to intercept Windows messages for.</param>
/// <param name="forwardAction">This action will be called whenever a Windows message is received.</param>
internal static void SetupLoop(ChromiumWebBrowser browser, Action<Message> forwardAction)
{
Task.Factory.StartNew(() =>
{
try
{
bool foundWidget = false;
while (!foundWidget)
{
browser.Invoke((Action)(() =>
{
IntPtr chromeWidgetHostHandle;
if (ChromeWidgetHandleFinder.TryFindHandle(browser, out chromeWidgetHostHandle))
{
foundWidget = true;
new ChromeWidgetMessageInterceptor(browser, chromeWidgetHostHandle, forwardAction);
}
else
{
// Chrome hasn't yet set up its message-loop window.
Thread.Sleep(10);
}
}));
}
}
catch
{
// Errors are likely to occur if browser is disposed, and no good way to check from another thread
}
});
}

private void BrowserHandleDestroyed(object sender, EventArgs e)
{
ReleaseHandle();

var browser = (Control)sender;

browser.HandleDestroyed -= BrowserHandleDestroyed;
forwardAction = null;
}
Expand All @@ -91,45 +51,36 @@ protected override void WndProc(ref Message m)
}
}

class ChromeWidgetHandleFinder
internal static class ChromeWidgetHandleFinder
{
private delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam);

[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr lParam);

private readonly IntPtr mainHandle;
private string seekClassName;
private IntPtr descendantFound;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

private ChromeWidgetHandleFinder(IntPtr handle)
private class ClassDetails
{
this.mainHandle = handle;
public IntPtr DescendantFound { get; set; }
}

private IntPtr FindDescendantByClassName(string className)
private static bool EnumWindow(IntPtr hWnd, IntPtr lParam)
{
descendantFound = IntPtr.Zero;
seekClassName = className;

EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(this.mainHandle, childProc, IntPtr.Zero);

return descendantFound;
}

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
const string chromeWidgetHostClassName = "Chrome_RenderWidgetHostHWND";

private bool EnumWindow(IntPtr hWnd, IntPtr lParam)
{
StringBuilder buffer = new StringBuilder(128);
var buffer = new StringBuilder(128);
GetClassName(hWnd, buffer, buffer.Capacity);

if (buffer.ToString() == seekClassName)
if (buffer.ToString() == chromeWidgetHostClassName)
{
descendantFound = hWnd;
var gcHandle = GCHandle.FromIntPtr(lParam);

var classDetails = (ClassDetails)gcHandle.Target;

classDetails.DescendantFound = hWnd;
return false;
}

Expand All @@ -140,13 +91,19 @@ private bool EnumWindow(IntPtr hWnd, IntPtr lParam)
/// Chrome's message-loop Window isn't created synchronously, so this may not find it.
/// If so, you need to wait and try again later.
/// </summary>
public static bool TryFindHandle(ChromiumWebBrowser browser, out IntPtr chromeWidgetHostHandle)
public static bool TryFindHandle(IntPtr browserHandle, out IntPtr chromeWidgetHostHandle)
{
var browserHandle = browser.Handle;
var windowHandleInfo = new ChromeWidgetHandleFinder(browserHandle);
const string chromeWidgetHostClassName = "Chrome_RenderWidgetHostHWND";
chromeWidgetHostHandle = windowHandleInfo.FindDescendantByClassName(chromeWidgetHostClassName);
return chromeWidgetHostHandle != IntPtr.Zero;
var classDetails = new ClassDetails();
var gcHandle = GCHandle.Alloc(classDetails);

var childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(browserHandle, childProc, GCHandle.ToIntPtr(gcHandle));

chromeWidgetHostHandle = classDetails.DescendantFound;

gcHandle.Free();

return classDetails.DescendantFound != IntPtr.Zero;
}
}
}

0 comments on commit 38254a5

Please sign in to comment.