From 38254a59d505073c99069e0e41bff5714108d329 Mon Sep 17 00:00:00 2001 From: amaitland Date: Thu, 7 Apr 2016 23:13:25 +1000 Subject: [PATCH] Rewrite ChromeWidgetHandleFinder to use a GCHandle - much cleaner method of interfacing with dllimport --- .../BrowserTabUserControl.Designer.cs | 13 -- .../BrowserTabUserControl.cs | 124 +++++++++++++----- .../ChromeWidgetMessageInterceptor.cs | 103 +++++---------- 3 files changed, 118 insertions(+), 122 deletions(-) diff --git a/CefSharp.WinForms.Example/BrowserTabUserControl.Designer.cs b/CefSharp.WinForms.Example/BrowserTabUserControl.Designer.cs index f14ce4bb84..b97bf79f70 100644 --- a/CefSharp.WinForms.Example/BrowserTabUserControl.Designer.cs +++ b/CefSharp.WinForms.Example/BrowserTabUserControl.Designer.cs @@ -7,19 +7,6 @@ partial class BrowserTabUserControl /// private System.ComponentModel.IContainer components = null; - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - #region Windows Form Designer generated code /// diff --git a/CefSharp.WinForms.Example/BrowserTabUserControl.cs b/CefSharp.WinForms.Example/BrowserTabUserControl.cs index 1ea4a27779..5ea3d381ad 100644 --- a/CefSharp.WinForms.Example/BrowserTabUserControl.cs +++ b/CefSharp.WinForms.Example/BrowserTabUserControl.cs @@ -10,6 +10,8 @@ using CefSharp.WinForms.Internals; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace CefSharp.WinForms.Example { @@ -17,6 +19,7 @@ public partial class BrowserTabUserControl : UserControl { public IWinFormsWebBrowser Browser { get; private set; } private IntPtr browserHandle; + private ChromeWidgetMessageInterceptor messageInterceptor; public BrowserTabUserControl(Action openNewTab, string url) { @@ -66,6 +69,29 @@ public BrowserTabUserControl(Action openNewTab, string url) DisplayOutput(version); } + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + 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; @@ -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; - //} }); } } diff --git a/CefSharp.WinForms.Example/ChromeWidgetMessageInterceptor.cs b/CefSharp.WinForms.Example/ChromeWidgetMessageInterceptor.cs index ca56a3337d..b11c1ac1d4 100644 --- a/CefSharp.WinForms.Example/ChromeWidgetMessageInterceptor.cs +++ b/CefSharp.WinForms.Example/ChromeWidgetMessageInterceptor.cs @@ -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 @@ -19,63 +17,25 @@ namespace CefSharp.WinForms.Example /// /// The supplied Action delegate is fired upon each message. /// - class ChromeWidgetMessageInterceptor : NativeWindow + internal class ChromeWidgetMessageInterceptor : NativeWindow { - private readonly ChromiumWebBrowser browser; private Action forwardAction; - private ChromeWidgetMessageInterceptor(ChromiumWebBrowser browser, IntPtr chromeWidgetHostHandle, Action forwardAction) + internal ChromeWidgetMessageInterceptor(Control browser, IntPtr chromeWidgetHostHandle, Action forwardAction) { AssignHandle(chromeWidgetHostHandle); - this.browser = browser; browser.HandleDestroyed += BrowserHandleDestroyed; this.forwardAction = forwardAction; } - /// - /// Asynchronously wait for the Chromium widget window to be created for the given ChromiumWebBrowser, - /// and when created hook into its Windows message loop. - /// - /// The browser to intercept Windows messages for. - /// This action will be called whenever a Windows message is received. - internal static void SetupLoop(ChromiumWebBrowser browser, Action 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; } @@ -91,7 +51,7 @@ protected override void WndProc(ref Message m) } } - class ChromeWidgetHandleFinder + internal static class ChromeWidgetHandleFinder { private delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam); @@ -99,37 +59,28 @@ class ChromeWidgetHandleFinder [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; } @@ -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. /// - 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; } } }