From 749a60f35dbd9a34ef669ef31413ae34911a2a40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Mon, 10 Apr 2017 14:56:24 +0100 Subject: [PATCH 01/10] Support serialization of Classes (previously only structs were supported) --- .../Serialization/V8Serialization.cpp | 2 +- .../ModelBinding/ModelBindingExtensions.cs | 38 +------------------ 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/CefSharp.Core/Internals/Serialization/V8Serialization.cpp b/CefSharp.Core/Internals/Serialization/V8Serialization.cpp index d7e7ee5488..6ef4890490 100644 --- a/CefSharp.Core/Internals/Serialization/V8Serialization.cpp +++ b/CefSharp.Core/Internals/Serialization/V8Serialization.cpp @@ -111,7 +111,7 @@ namespace CefSharp } list->SetList(index, subList); } - else if (type->IsValueType && !type->IsPrimitive && !type->IsEnum) + else if (!type->IsPrimitive && !type->IsEnum) { auto fields = type->GetFields(); auto subDict = CefDictionaryValue::Create(); diff --git a/CefSharp/ModelBinding/ModelBindingExtensions.cs b/CefSharp/ModelBinding/ModelBindingExtensions.cs index a0b957147a..9e299297ae 100644 --- a/CefSharp/ModelBinding/ModelBindingExtensions.cs +++ b/CefSharp/ModelBinding/ModelBindingExtensions.cs @@ -36,7 +36,7 @@ public static T CreateInstance(this Type type, bool nonPublic = false) /// if a non-public constructor can be used, otherwise . public static object CreateInstance(this Type type, bool nonPublic = false) { - return CreateInstanceInternal(type, nonPublic); + return Activator.CreateInstance(type, nonPublic); } /// @@ -217,41 +217,5 @@ public static TypeCode GetTypeCode(this Type type) else return TypeCode.Object; } - - private static object CreateInstanceInternal(Type type, bool nonPublic = false) - { - var constructor = type.GetDefaultConstructor(nonPublic); - - if (constructor == null) - { - throw new MissingMethodException("No parameterless constructor defined for this object."); - } - - return constructor.Invoke(new object[0]); - } - - private static ConstructorInfo GetDefaultConstructor(this Type type, bool nonPublic = false) - { - var typeInfo = type.GetTypeInfo(); - - var constructors = typeInfo.DeclaredConstructors; - - foreach (var constructor in constructors) - { - var parameters = constructor.GetParameters(); - - if (parameters.Length > 0) - { - continue; - } - - if (!constructor.IsPrivate || nonPublic) - { - return constructor; - } - } - - return null; - } } } From 278a6c4adae660d7fb5d65d57cf326e7f7564a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Mon, 10 Apr 2017 15:41:17 +0100 Subject: [PATCH 02/10] Added managed methods incterception mechanism --- .../Views/BrowserTabView.xaml.cs | 19 +++++++++++++++- CefSharp/BindingOptions.cs | 5 +++++ CefSharp/CefSharp.csproj | 1 + CefSharp/Internals/JavascriptObject.cs | 2 ++ .../Internals/JavascriptObjectRepository.cs | 10 ++++++++- CefSharp/ModelBinding/IInterceptor.cs | 22 +++++++++++++++++++ 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 CefSharp/ModelBinding/IInterceptor.cs diff --git a/CefSharp.Wpf.Example/Views/BrowserTabView.xaml.cs b/CefSharp.Wpf.Example/Views/BrowserTabView.xaml.cs index e776d368d8..5a032d381e 100644 --- a/CefSharp.Wpf.Example/Views/BrowserTabView.xaml.cs +++ b/CefSharp.Wpf.Example/Views/BrowserTabView.xaml.cs @@ -13,11 +13,23 @@ using CefSharp.ModelBinding; using CefSharp.Wpf.Example.ViewModels; using System.IO; +using System; namespace CefSharp.Wpf.Example.Views { public partial class BrowserTabView : UserControl { + + private class CustomInterceptor : IInterceptor + { + public object Intercept(Func originalMethod, string methodName) + { + object result = originalMethod(); + System.Diagnostics.Debug.WriteLine("Called " + methodName); + return result; + } + } + //Store draggable region if we have one - used for hit testing private Region region; @@ -27,7 +39,12 @@ public BrowserTabView() browser.RequestHandler = new RequestHandler(); browser.RegisterJsObject("bound", new BoundObject(), BindingOptions.DefaultBinder); - browser.RegisterAsyncJsObject("boundAsync", new AsyncBoundObject()); + var bindingOptions = new BindingOptions() + { + Binder = BindingOptions.DefaultBinder.Binder, + Interceptor = new CustomInterceptor() + }; + browser.RegisterAsyncJsObject("boundAsync", new AsyncBoundObject(), bindingOptions); // Enable touch scrolling - once properly tested this will likely become the default //browser.IsManipulationEnabled = true; diff --git a/CefSharp/BindingOptions.cs b/CefSharp/BindingOptions.cs index b9de3a6cbe..dcddb35e02 100644 --- a/CefSharp/BindingOptions.cs +++ b/CefSharp/BindingOptions.cs @@ -30,6 +30,11 @@ public static BindingOptions DefaultBinder /// public IBinder Binder { get; set; } + /// + /// interceptor used for intercepting calls to methods + /// + public IInterceptor Interceptor { get; set; } + public BindingOptions() { CamelCaseJavascriptNames = true; diff --git a/CefSharp/CefSharp.csproj b/CefSharp/CefSharp.csproj index 63853abe46..5c6fa8cbc7 100644 --- a/CefSharp/CefSharp.csproj +++ b/CefSharp/CefSharp.csproj @@ -164,6 +164,7 @@ Code + Code diff --git a/CefSharp/Internals/JavascriptObject.cs b/CefSharp/Internals/JavascriptObject.cs index 26d38dc5ab..09348e2e67 100644 --- a/CefSharp/Internals/JavascriptObject.cs +++ b/CefSharp/Internals/JavascriptObject.cs @@ -53,6 +53,8 @@ public class JavascriptObject //: DynamicObject maybe later public IBinder Binder { get; set; } + public IInterceptor Interceptor { get; set; } + public JavascriptObject() { Methods = new List(); diff --git a/CefSharp/Internals/JavascriptObjectRepository.cs b/CefSharp/Internals/JavascriptObjectRepository.cs index a5b12f8f69..9167de1342 100644 --- a/CefSharp/Internals/JavascriptObjectRepository.cs +++ b/CefSharp/Internals/JavascriptObjectRepository.cs @@ -89,6 +89,7 @@ private JavascriptObject CreateInternal(string name, object value, bool analyseP jsObject.Name = name; jsObject.JavascriptName = name; jsObject.Binder = options == null ? null : options.Binder; + jsObject.Interceptor = options == null ? null : options.Interceptor; AnalyseObjectForBinding(jsObject, analyseMethods: true, analyseProperties: analyseProperties, readPropertyValue: false, camelCaseJavascriptNames: camelCaseJavascriptNames); @@ -187,7 +188,14 @@ public bool TryCallMethod(long objectId, string name, object[] parameters, out o } } - result = method.Function(obj.Value, parameters); + if (obj.Interceptor != null) + { + result = obj.Interceptor.Intercept(() => method.Function(obj.Value, parameters), method.ManagedName); + } + else + { + result = method.Function(obj.Value, parameters); + } } catch (Exception e) { diff --git a/CefSharp/ModelBinding/IInterceptor.cs b/CefSharp/ModelBinding/IInterceptor.cs new file mode 100644 index 0000000000..d691be768d --- /dev/null +++ b/CefSharp/ModelBinding/IInterceptor.cs @@ -0,0 +1,22 @@ +// Copyright © 2010-2017 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using System; + +namespace CefSharp.ModelBinding +{ + /// + /// Intercepts methods execution + /// + public interface IInterceptor + { + /// + /// Intercept the given method name + /// + /// method to be called + /// Name of the method to be called + /// The method result + object Intercept(Func originalMethod, string methodName); + } +} \ No newline at end of file From c87bc70968c3521b435e382a32ff0ec015e3605c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Mon, 29 May 2017 19:22:25 +0100 Subject: [PATCH 03/10] Prevent accelerators keys to work when typing text --- CefSharp.Wpf/ChromiumWebBrowser.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CefSharp.Wpf/ChromiumWebBrowser.cs b/CefSharp.Wpf/ChromiumWebBrowser.cs index 55e5fe3b6a..ce88585285 100644 --- a/CefSharp.Wpf/ChromiumWebBrowser.cs +++ b/CefSharp.Wpf/ChromiumWebBrowser.cs @@ -1904,6 +1904,23 @@ private void OnPreviewKey(KeyEventArgs e) } } + /// + /// Handles the event. + /// + /// The instance containing the event data. + protected override void OnPreviewTextInput(TextCompositionEventArgs e) + { + if (browser != null) { + var browserHost = browser.GetHost(); + for (int i = 0; i < e.Text.Length; i++) + { + browserHost.SendKeyEvent((int)WM.IME_CHAR, e.Text[i], 0); + } + e.Handled = true; + } + base.OnTextInput(e); + } + /// /// Invoked when an unhandled  attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. /// From ced0844c5881aff55299693cf01ef71a8c7ad3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Wed, 31 May 2017 13:01:17 +0100 Subject: [PATCH 04/10] Improved keyboard handling --- CefSharp.Wpf/ChromiumWebBrowser.cs | 107 +++++------------------------ 1 file changed, 17 insertions(+), 90 deletions(-) diff --git a/CefSharp.Wpf/ChromiumWebBrowser.cs b/CefSharp.Wpf/ChromiumWebBrowser.cs index ce88585285..b79d0b5a3c 100644 --- a/CefSharp.Wpf/ChromiumWebBrowser.cs +++ b/CefSharp.Wpf/ChromiumWebBrowser.cs @@ -2,11 +2,8 @@ // // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -using CefSharp.Internals; -using CefSharp.Wpf.Internals; -using CefSharp.Wpf.Rendering; -using Microsoft.Win32.SafeHandles; using System; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -17,11 +14,12 @@ using System.Windows.Interop; using System.Windows.Media; using System.Windows.Threading; -using CefSharp.ModelBinding; -using System.Runtime.CompilerServices; +using CefSharp.Internals; +using CefSharp.Wpf.Internals; +using CefSharp.Wpf.Rendering; +using Microsoft.Win32.SafeHandles; -namespace CefSharp.Wpf -{ +namespace CefSharp.Wpf { /// /// ChromiumWebBrowser is the WPF web browser control /// @@ -35,10 +33,6 @@ public class ChromiumWebBrowser : ContentControl, IRenderWebBrowser, IWpfWebBrow /// private HwndSource source; /// - /// The source hook - /// - private HwndSourceHook sourceHook; - /// /// The tooltip timer /// private DispatcherTimer tooltipTimer; @@ -549,7 +543,7 @@ protected virtual void Dispose(bool isDisposing) Cef.RemoveDisposable(this); - RemoveSourceHook(); + source = null; } } @@ -1467,8 +1461,6 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA var notifyDpiChanged = !matrix.Equals(source.CompositionTarget.TransformToDevice); matrix = source.CompositionTarget.TransformToDevice; - sourceHook = SourceHook; - source.AddHook(sourceHook); if (notifyDpiChanged) { @@ -1481,18 +1473,6 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA } else if (args.OldSource != null) { - RemoveSourceHook(); - } - } - - /// - /// Removes the source hook. - /// - private void RemoveSourceHook() - { - if (source != null && sourceHook != null) - { - source.RemoveHook(sourceHook); source = null; } } @@ -1693,59 +1673,6 @@ private Popup CreatePopup() return newPopup; } - /// - /// WindowProc callback interceptor. Handles Windows messages intended for the source hWnd, and passes them to the - /// contained browser as needed. - /// - /// The source handle. - /// The message. - /// Additional message info. - /// Even more message info. - /// if set to true, the event has already been handled by someone else. - /// IntPtr. - protected virtual IntPtr SourceHook(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled) - { - if (handled) - { - return IntPtr.Zero; - } - - switch ((WM)message) - { - case WM.SYSCHAR: - case WM.SYSKEYDOWN: - case WM.SYSKEYUP: - case WM.KEYDOWN: - case WM.KEYUP: - case WM.CHAR: - case WM.IME_CHAR: - { - if (!IsKeyboardFocused) - { - break; - } - - if (message == (int)WM.SYSKEYDOWN && - wParam.ToInt32() == KeyInterop.VirtualKeyFromKey(Key.F4)) - { - // We don't want CEF to receive this event (and mark it as handled), since that makes it impossible to - // shut down a CefSharp-based app by pressing Alt-F4, which is kind of bad. - return IntPtr.Zero; - } - - if (browser != null) - { - browser.GetHost().SendKeyEvent(message, wParam.CastToInt32(), lParam.CastToInt32()); - handled = true; - } - - break; - } - } - - return IntPtr.Zero; - } - /// /// Converts a .NET Drag event to a CefSharp MouseEvent /// @@ -1888,20 +1815,20 @@ private void OnPreviewKey(KeyEventArgs e) // Hooking the Tab key like this makes the tab focusing in essence work like // KeyboardNavigation.TabNavigation="Cycle"; you will never be able to Tab out of the web browser control. // We also add the condition to allow ctrl+a to work when the web browser control is put inside listbox. - if (e.Key == Key.Tab || e.Key == Key.Home || e.Key == Key.End || e.Key == Key.Up - || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right - || (e.Key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) + //if (e.Key == Key.Tab || e.Key == Key.Home || e.Key == Key.End || e.Key == Key.Up + // || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right + // || (e.Key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) + //{ + if (browser != null) { - var modifiers = e.GetModifiers(); + var modifiers = 0;// e.GetModifiers(); var message = (int)(e.IsDown ? WM.KEYDOWN : WM.KEYUP); var virtualKey = KeyInterop.VirtualKeyFromKey(e.Key); - if(browser != null) - { - browser.GetHost().SendKeyEvent(message, virtualKey, (int)modifiers); - e.Handled = true; - } + browser.GetHost().SendKeyEvent(message, virtualKey, (int)modifiers); + //e.Handled = true; } + //} } /// @@ -1914,7 +1841,7 @@ protected override void OnPreviewTextInput(TextCompositionEventArgs e) var browserHost = browser.GetHost(); for (int i = 0; i < e.Text.Length; i++) { - browserHost.SendKeyEvent((int)WM.IME_CHAR, e.Text[i], 0); + browserHost.SendKeyEvent((int)WM.CHAR, e.Text[i], 0); } e.Handled = true; } From e27a8a5f25d5b658b81e7d95bc05745f9c0a89dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Wed, 31 May 2017 14:07:55 +0100 Subject: [PATCH 05/10] Disable keyboard navigation using arrows, tabs and home/end keys --- CefSharp.Wpf/ChromiumWebBrowser.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CefSharp.Wpf/ChromiumWebBrowser.cs b/CefSharp.Wpf/ChromiumWebBrowser.cs index b79d0b5a3c..568353da20 100644 --- a/CefSharp.Wpf/ChromiumWebBrowser.cs +++ b/CefSharp.Wpf/ChromiumWebBrowser.cs @@ -1810,25 +1810,25 @@ protected override void OnPreviewKeyUp(KeyEventArgs e) /// The instance containing the event data. private void OnPreviewKey(KeyEventArgs e) { - // As KeyDown and KeyUp bubble, it appears they're being handled before they get a chance to - // trigger the appropriate WM_ messages handled by our SourceHook, so we have to handle these extra keys here. - // Hooking the Tab key like this makes the tab focusing in essence work like - // KeyboardNavigation.TabNavigation="Cycle"; you will never be able to Tab out of the web browser control. - // We also add the condition to allow ctrl+a to work when the web browser control is put inside listbox. - //if (e.Key == Key.Tab || e.Key == Key.Home || e.Key == Key.End || e.Key == Key.Up - // || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right - // || (e.Key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) - //{ if (browser != null) { - var modifiers = 0;// e.GetModifiers(); + var modifiers = e.GetModifiers(); var message = (int)(e.IsDown ? WM.KEYDOWN : WM.KEYUP); var virtualKey = KeyInterop.VirtualKeyFromKey(e.Key); browser.GetHost().SendKeyEvent(message, virtualKey, (int)modifiers); - //e.Handled = true; } - //} + + // Hooking the Tab key like this makes the tab focusing in essence work like + // KeyboardNavigation.TabNavigation="Cycle"; you will never be able to Tab out of the web browser control. + // We also add the condition to allow ctrl+a to work when the web browser control is put inside listbox. + // Prevent keyboard navigation using arrows and home and end keys + if (e.Key == Key.Tab || e.Key == Key.Home || e.Key == Key.End || e.Key == Key.Up + || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right + || (e.Key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) + { + e.Handled = true; + } } /// From 3a9d33427a4d75a8afcdea234593a2167490e60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Wed, 31 May 2017 14:29:51 +0100 Subject: [PATCH 06/10] Fixed identation --- CefSharp.Wpf/ChromiumWebBrowser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CefSharp.Wpf/ChromiumWebBrowser.cs b/CefSharp.Wpf/ChromiumWebBrowser.cs index f43db2eacd..924c8950cc 100644 --- a/CefSharp.Wpf/ChromiumWebBrowser.cs +++ b/CefSharp.Wpf/ChromiumWebBrowser.cs @@ -19,7 +19,8 @@ using CefSharp.Wpf.Rendering; using Microsoft.Win32.SafeHandles; -namespace CefSharp.Wpf { +namespace CefSharp.Wpf +{ /// /// ChromiumWebBrowser is the WPF web browser control /// From fa033b65a2d5edc9de2e3d9ba9cca4ceec3aebf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Wed, 31 May 2017 14:39:54 +0100 Subject: [PATCH 07/10] Fixed identation --- CefSharp.Wpf/ChromiumWebBrowser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CefSharp.Wpf/ChromiumWebBrowser.cs b/CefSharp.Wpf/ChromiumWebBrowser.cs index 924c8950cc..df2bb5bb73 100644 --- a/CefSharp.Wpf/ChromiumWebBrowser.cs +++ b/CefSharp.Wpf/ChromiumWebBrowser.cs @@ -1925,7 +1925,8 @@ private void OnPreviewKey(KeyEventArgs e) /// The instance containing the event data. protected override void OnPreviewTextInput(TextCompositionEventArgs e) { - if (browser != null) { + if (browser != null) + { var browserHost = browser.GetHost(); for (int i = 0; i < e.Text.Length; i++) { From 1728e9a9d384fb4cb296da163f7365447f7a8ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Mon, 12 Jun 2017 10:12:44 +0100 Subject: [PATCH 08/10] Call correct base method and handle text input in case event wasn't handled --- CefSharp.Wpf/ChromiumWebBrowser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CefSharp.Wpf/ChromiumWebBrowser.cs b/CefSharp.Wpf/ChromiumWebBrowser.cs index df2bb5bb73..d26ecd7295 100644 --- a/CefSharp.Wpf/ChromiumWebBrowser.cs +++ b/CefSharp.Wpf/ChromiumWebBrowser.cs @@ -1925,7 +1925,7 @@ private void OnPreviewKey(KeyEventArgs e) /// The instance containing the event data. protected override void OnPreviewTextInput(TextCompositionEventArgs e) { - if (browser != null) + if (!e.Handled && browser != null) { var browserHost = browser.GetHost(); for (int i = 0; i < e.Text.Length; i++) @@ -1934,7 +1934,7 @@ protected override void OnPreviewTextInput(TextCompositionEventArgs e) } e.Handled = true; } - base.OnTextInput(e); + base.OnPreviewTextInput(e); } /// From 4d5cf63dd2f8c13b9de08d26f768305e832c1bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Tue, 26 Dec 2017 11:33:30 +0000 Subject: [PATCH 09/10] Removed SourceHook method --- CefSharp.Wpf/ChromiumWebBrowser.cs | 57 ------------------------------ 1 file changed, 57 deletions(-) diff --git a/CefSharp.Wpf/ChromiumWebBrowser.cs b/CefSharp.Wpf/ChromiumWebBrowser.cs index 9c20bc4371..abd1bfd957 100644 --- a/CefSharp.Wpf/ChromiumWebBrowser.cs +++ b/CefSharp.Wpf/ChromiumWebBrowser.cs @@ -1794,63 +1794,6 @@ private Popup CreatePopup() return newPopup; } - /// -<<<<<<< HEAD - /// WindowProc callback interceptor. Handles Windows messages intended for the source hWnd, and passes them to the - /// contained browser as needed. - /// - /// The source handle. - /// The message. - /// Additional message info. - /// Even more message info. - /// if set to true, the event has already been handled by someone else. - /// IntPtr. - protected virtual IntPtr SourceHook(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled) - { - if (handled) - { - return IntPtr.Zero; - } - - switch ((WM)message) - { - case WM.SYSCHAR: - case WM.SYSKEYDOWN: - case WM.SYSKEYUP: - case WM.KEYDOWN: - case WM.KEYUP: - case WM.CHAR: - case WM.IME_CHAR: - { - if (!IsKeyboardFocused) - { - break; - } - - if (message == (int)WM.SYSKEYDOWN && - wParam.ToInt32() == KeyInterop.VirtualKeyFromKey(Key.F4)) - { - // We don't want CEF to receive this event (and mark it as handled), since that makes it impossible to - // shut down a CefSharp-based app by pressing Alt-F4, which is kind of bad. - return IntPtr.Zero; - } - - if (browser != null) - { - browser.GetHost().SendKeyEvent(message, wParam.CastToInt32(), lParam.CastToInt32()); - handled = true; - } - - break; - } - } - - return IntPtr.Zero; - } - - /// -======= ->>>>>>> 1728e9a9d384fb4cb296da163f7365447f7a8ff2 /// Converts a .NET Drag event to a CefSharp MouseEvent /// /// The instance containing the event data. From b600d093b020332796b11f1eeb8d6c4564545110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Neves?= Date: Mon, 8 Jan 2018 18:41:25 +0000 Subject: [PATCH 10/10] Added previous keyboard handling logic and ability to use it through a property --- CefSharp.Wpf/CefSharp.Wpf.csproj | 4 + CefSharp.Wpf/ChromiumWebBrowser.cs | 84 ++-- CefSharp.Wpf/Internals/IWpfKeyboardHandler.cs | 20 + CefSharp.Wpf/Internals/VirtualKeys.cs | 405 ++++++++++++++++++ CefSharp.Wpf/Internals/WpfKeyboardHandler.cs | 100 +++++ .../Internals/WpfLegacyKeyboardHandler.cs | 136 ++++++ 6 files changed, 713 insertions(+), 36 deletions(-) create mode 100644 CefSharp.Wpf/Internals/IWpfKeyboardHandler.cs create mode 100644 CefSharp.Wpf/Internals/VirtualKeys.cs create mode 100644 CefSharp.Wpf/Internals/WpfKeyboardHandler.cs create mode 100644 CefSharp.Wpf/Internals/WpfLegacyKeyboardHandler.cs diff --git a/CefSharp.Wpf/CefSharp.Wpf.csproj b/CefSharp.Wpf/CefSharp.Wpf.csproj index abe045d237..a069ae59a0 100644 --- a/CefSharp.Wpf/CefSharp.Wpf.csproj +++ b/CefSharp.Wpf/CefSharp.Wpf.csproj @@ -78,6 +78,10 @@ + + + + diff --git a/CefSharp.Wpf/ChromiumWebBrowser.cs b/CefSharp.Wpf/ChromiumWebBrowser.cs index d2ec3f1f2e..84cc3dfb85 100644 --- a/CefSharp.Wpf/ChromiumWebBrowser.cs +++ b/CefSharp.Wpf/ChromiumWebBrowser.cs @@ -90,6 +90,10 @@ public class ChromiumWebBrowser : ContentControl, IRenderWebBrowser, IWpfWebBrow /// user attempts to set after browser created) /// private IRequestContext requestContext; + /// + /// Handles keypress and text typing + /// + private IWpfKeyboardHandler wpfKeyboardHandler; /// /// A flag that indicates whether or not the designer is active @@ -203,6 +207,26 @@ public IRequestContext RequestContext /// /// The find handler. public IFindHandler FindHandler { get; set; } + /// + /// Legacy keyboard handler uses WindowProc callback interceptor to handle keypress events. + /// Only use this method if you find problems with the default keyboard handling mechanism. + /// This might be removed in the future. + /// + public bool UseLegacyKeyboardHandler + { + get + { + return wpfKeyboardHandler is WpfLegacyKeyboardHandler; + } + set + { + if (value != UseLegacyKeyboardHandler) { + wpfKeyboardHandler.Dispose(); + wpfKeyboardHandler = CreateWpfKeyboardHandler(this, value); + wpfKeyboardHandler.Setup(source); + } + } + } /// /// Event handler for receiving Javascript console messages being sent from web pages. @@ -464,6 +488,8 @@ private void NoInliningConstructor() ResourceHandlerFactory = new DefaultResourceHandlerFactory(); BrowserSettings = new BrowserSettings(); + wpfKeyboardHandler = CreateWpfKeyboardHandler(this); + PresentationSource.AddSourceChangedHandler(this, PresentationSourceChangedHandler); RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality); @@ -577,6 +603,7 @@ protected virtual void Dispose(bool isDisposing) Cef.RemoveDisposable(this); + wpfKeyboardHandler.Dispose(); source = null; } } @@ -1555,6 +1582,8 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA DpiScaleFactor = source.CompositionTarget.TransformToDevice.M11; + wpfKeyboardHandler.Setup(source); + if (notifyDpiChanged && browser != null) { browser.GetHost().NotifyScreenInfoChanged(); @@ -1572,6 +1601,8 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA } else if (args.OldSource != null) { + wpfKeyboardHandler.Dispose(); + var window = args.OldSource.RootVisual as Window; if (window != null) { @@ -1934,7 +1965,7 @@ protected override void OnPreviewKeyDown(KeyEventArgs e) { if (!e.Handled) { - OnPreviewKey(e); + wpfKeyboardHandler.HandleKeyPress(e); } base.OnPreviewKeyDown(e); @@ -1949,54 +1980,23 @@ protected override void OnPreviewKeyUp(KeyEventArgs e) { if (!e.Handled) { - OnPreviewKey(e); + wpfKeyboardHandler.HandleKeyPress(e); } base.OnPreviewKeyUp(e); } - /// - /// Handles the event. - /// - /// The instance containing the event data. - private void OnPreviewKey(KeyEventArgs e) - { - if (browser != null) - { - var modifiers = e.GetModifiers(); - var message = (int)(e.IsDown ? WM.KEYDOWN : WM.KEYUP); - var virtualKey = KeyInterop.VirtualKeyFromKey(e.Key); - - browser.GetHost().SendKeyEvent(message, virtualKey, (int)modifiers); - } - - // Hooking the Tab key like this makes the tab focusing in essence work like - // KeyboardNavigation.TabNavigation="Cycle"; you will never be able to Tab out of the web browser control. - // We also add the condition to allow ctrl+a to work when the web browser control is put inside listbox. - // Prevent keyboard navigation using arrows and home and end keys - if (e.Key == Key.Tab || e.Key == Key.Home || e.Key == Key.End || e.Key == Key.Up - || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right - || (e.Key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) - { - e.Handled = true; - } - } - /// /// Handles the event. /// /// The instance containing the event data. protected override void OnPreviewTextInput(TextCompositionEventArgs e) { - if (!e.Handled && browser != null) + if (!e.Handled) { - var browserHost = browser.GetHost(); - for (int i = 0; i < e.Text.Length; i++) - { - browserHost.SendKeyEvent((int)WM.CHAR, e.Text[i], 0); - } - e.Handled = true; + wpfKeyboardHandler.HandleTextInput(e); } + base.OnPreviewTextInput(e); } @@ -2284,6 +2284,18 @@ private bool InternalIsBrowserInitialized() return Interlocked.CompareExchange(ref browserInitialized, 0, 0) == 1; } + private static IWpfKeyboardHandler CreateWpfKeyboardHandler(ChromiumWebBrowser owner, bool useLegacyHandler = false) + { + if (useLegacyHandler) + { + return new WpfLegacyKeyboardHandler(owner); + } + else + { + return new WpfKeyboardHandler(owner); + } + } + //protected override void OnManipulationDelta(ManipulationDeltaEventArgs e) //{ // base.OnManipulationDelta(e); diff --git a/CefSharp.Wpf/Internals/IWpfKeyboardHandler.cs b/CefSharp.Wpf/Internals/IWpfKeyboardHandler.cs new file mode 100644 index 0000000000..3038f83b33 --- /dev/null +++ b/CefSharp.Wpf/Internals/IWpfKeyboardHandler.cs @@ -0,0 +1,20 @@ +// Copyright © 2010-2017 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using System; +using System.Windows.Input; +using System.Windows.Interop; + +namespace CefSharp.Wpf.Internals +{ + /// + /// Implement this interface to handle events related to keyboard input. + /// + internal interface IWpfKeyboardHandler : IDisposable + { + void Setup(HwndSource source); + void HandleKeyPress(KeyEventArgs e); + void HandleTextInput(TextCompositionEventArgs e); + } +} \ No newline at end of file diff --git a/CefSharp.Wpf/Internals/VirtualKeys.cs b/CefSharp.Wpf/Internals/VirtualKeys.cs new file mode 100644 index 0000000000..34f3b35e7a --- /dev/null +++ b/CefSharp.Wpf/Internals/VirtualKeys.cs @@ -0,0 +1,405 @@ +// Copyright © 2010-2017 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +namespace CefSharp.Wpf.Internals +{ + /// + /// Enumeration for virtual keys taken from http://www.pinvoke.net/default.aspx/Enums/VirtualKeys.html + /// + public enum VirtualKeys : ushort + { + /// + LeftButton = 0x01, + /// + RightButton = 0x02, + /// + Cancel = 0x03, + /// + MiddleButton = 0x04, + /// + ExtraButton1 = 0x05, + /// + ExtraButton2 = 0x06, + /// + Back = 0x08, + /// + Tab = 0x09, + /// + Clear = 0x0C, + /// + Return = 0x0D, + /// + Shift = 0x10, + /// + Control = 0x11, + /// + Menu = 0x12, + /// + Pause = 0x13, + /// + CapsLock = 0x14, + /// + Kana = 0x15, + /// + Hangeul = 0x15, + /// + Hangul = 0x15, + /// + Junja = 0x17, + /// + Final = 0x18, + /// + Hanja = 0x19, + /// + Kanji = 0x19, + /// + Escape = 0x1B, + /// + Convert = 0x1C, + /// + NonConvert = 0x1D, + /// + Accept = 0x1E, + /// + ModeChange = 0x1F, + /// + Space = 0x20, + /// + Prior = 0x21, + /// + Next = 0x22, + /// + End = 0x23, + /// + Home = 0x24, + /// + Left = 0x25, + /// + Up = 0x26, + /// + Right = 0x27, + /// + Down = 0x28, + /// + Select = 0x29, + /// + Print = 0x2A, + /// + Execute = 0x2B, + /// + Snapshot = 0x2C, + /// + Insert = 0x2D, + /// + Delete = 0x2E, + /// + Help = 0x2F, + /// + N0 = 0x30, + /// + N1 = 0x31, + /// + N2 = 0x32, + /// + N3 = 0x33, + /// + N4 = 0x34, + /// + N5 = 0x35, + /// + N6 = 0x36, + /// + N7 = 0x37, + /// + N8 = 0x38, + /// + N9 = 0x39, + /// + A = 0x41, + /// + B = 0x42, + /// + C = 0x43, + /// + D = 0x44, + /// + E = 0x45, + /// + F = 0x46, + /// + G = 0x47, + /// + H = 0x48, + /// + I = 0x49, + /// + J = 0x4A, + /// + K = 0x4B, + /// + L = 0x4C, + /// + M = 0x4D, + /// + N = 0x4E, + /// + O = 0x4F, + /// + P = 0x50, + /// + Q = 0x51, + /// + R = 0x52, + /// + S = 0x53, + /// + T = 0x54, + /// + U = 0x55, + /// + V = 0x56, + /// + W = 0x57, + /// + X = 0x58, + /// + Y = 0x59, + /// + Z = 0x5A, + /// + LeftWindows = 0x5B, + /// + RightWindows = 0x5C, + /// + Application = 0x5D, + /// + Sleep = 0x5F, + /// + Numpad0 = 0x60, + /// + Numpad1 = 0x61, + /// + Numpad2 = 0x62, + /// + Numpad3 = 0x63, + /// + Numpad4 = 0x64, + /// + Numpad5 = 0x65, + /// + Numpad6 = 0x66, + /// + Numpad7 = 0x67, + /// + Numpad8 = 0x68, + /// + Numpad9 = 0x69, + /// + Multiply = 0x6A, + /// + Add = 0x6B, + /// + Separator = 0x6C, + /// + Subtract = 0x6D, + /// + Decimal = 0x6E, + /// + Divide = 0x6F, + /// + F1 = 0x70, + /// + F2 = 0x71, + /// + F3 = 0x72, + /// + F4 = 0x73, + /// + F5 = 0x74, + /// + F6 = 0x75, + /// + F7 = 0x76, + /// + F8 = 0x77, + /// + F9 = 0x78, + /// + F10 = 0x79, + /// + F11 = 0x7A, + /// + F12 = 0x7B, + /// + F13 = 0x7C, + /// + F14 = 0x7D, + /// + F15 = 0x7E, + /// + F16 = 0x7F, + /// + F17 = 0x80, + /// + F18 = 0x81, + /// + F19 = 0x82, + /// + F20 = 0x83, + /// + F21 = 0x84, + /// + F22 = 0x85, + /// + F23 = 0x86, + /// + F24 = 0x87, + /// + NumLock = 0x90, + /// + ScrollLock = 0x91, + /// + NEC_Equal = 0x92, + /// + Fujitsu_Jisho = 0x92, + /// + Fujitsu_Masshou = 0x93, + /// + Fujitsu_Touroku = 0x94, + /// + Fujitsu_Loya = 0x95, + /// + Fujitsu_Roya = 0x96, + /// + LeftShift = 0xA0, + /// + RightShift = 0xA1, + /// + LeftControl = 0xA2, + /// + RightControl = 0xA3, + /// + LeftMenu = 0xA4, + /// + RightMenu = 0xA5, + /// + BrowserBack = 0xA6, + /// + BrowserForward = 0xA7, + /// + BrowserRefresh = 0xA8, + /// + BrowserStop = 0xA9, + /// + BrowserSearch = 0xAA, + /// + BrowserFavorites = 0xAB, + /// + BrowserHome = 0xAC, + /// + VolumeMute = 0xAD, + /// + VolumeDown = 0xAE, + /// + VolumeUp = 0xAF, + /// + MediaNextTrack = 0xB0, + /// + MediaPrevTrack = 0xB1, + /// + MediaStop = 0xB2, + /// + MediaPlayPause = 0xB3, + /// + LaunchMail = 0xB4, + /// + LaunchMediaSelect = 0xB5, + /// + LaunchApplication1 = 0xB6, + /// + LaunchApplication2 = 0xB7, + /// + OEM1 = 0xBA, + /// + OEMPlus = 0xBB, + /// + OEMComma = 0xBC, + /// + OEMMinus = 0xBD, + /// + OEMPeriod = 0xBE, + /// + OEM2 = 0xBF, + /// + OEM3 = 0xC0, + /// + OEM4 = 0xDB, + /// + OEM5 = 0xDC, + /// + OEM6 = 0xDD, + /// + OEM7 = 0xDE, + /// + OEM8 = 0xDF, + /// + OEMAX = 0xE1, + /// + OEM102 = 0xE2, + /// + ICOHelp = 0xE3, + /// + ICO00 = 0xE4, + /// + ProcessKey = 0xE5, + /// + ICOClear = 0xE6, + /// + Packet = 0xE7, + /// + OEMReset = 0xE9, + /// + OEMJump = 0xEA, + /// + OEMPA1 = 0xEB, + /// + OEMPA2 = 0xEC, + /// + OEMPA3 = 0xED, + /// + OEMWSCtrl = 0xEE, + /// + OEMCUSel = 0xEF, + /// + OEMATTN = 0xF0, + /// + OEMFinish = 0xF1, + /// + OEMCopy = 0xF2, + /// + OEMAuto = 0xF3, + /// + OEMENLW = 0xF4, + /// + OEMBackTab = 0xF5, + /// + ATTN = 0xF6, + /// + CRSel = 0xF7, + /// + EXSel = 0xF8, + /// + EREOF = 0xF9, + /// + Play = 0xFA, + /// + Zoom = 0xFB, + /// + Noname = 0xFC, + /// + PA1 = 0xFD, + /// + OEMClear = 0xFE + } +} \ No newline at end of file diff --git a/CefSharp.Wpf/Internals/WpfKeyboardHandler.cs b/CefSharp.Wpf/Internals/WpfKeyboardHandler.cs new file mode 100644 index 0000000000..9986d5f155 --- /dev/null +++ b/CefSharp.Wpf/Internals/WpfKeyboardHandler.cs @@ -0,0 +1,100 @@ +// Copyright © 2010-2017 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using System.Windows.Input; +using System.Windows.Interop; + +namespace CefSharp.Wpf.Internals +{ + internal class WpfKeyboardHandler : IWpfKeyboardHandler + { + /// + /// The owner browser instance + /// + private readonly ChromiumWebBrowser owner; + + public WpfKeyboardHandler(ChromiumWebBrowser owner) + { + this.owner = owner; + } + + public void Setup(HwndSource source) + { + // nothing to do here + } + + public void Dispose() + { + // nothing to do here + } + + public void HandleKeyPress(KeyEventArgs e) + { + var browser = owner.GetBrowser(); + var key = e.SystemKey != Key.None ? e.SystemKey : e.Key; + if (browser != null) + { + int message; + int virtualKey = 0; + var modifiers = e.GetModifiers(); + + switch (key) + { + case Key.LeftAlt: + case Key.RightAlt: + virtualKey = (int) VirtualKeys.Menu; + break; + + case Key.LeftCtrl: + case Key.RightCtrl: + virtualKey = (int) VirtualKeys.Control; + break; + + case Key.LeftShift: + case Key.RightShift: + virtualKey = (int) VirtualKeys.Shift; + break; + + default: + virtualKey = KeyInterop.VirtualKeyFromKey(key); + break; + } + + if (e.IsDown) + { + message = (int)(e.SystemKey != Key.None ? WM.SYSKEYDOWN : WM.KEYDOWN); + } else { + message = (int)(e.SystemKey != Key.None ? WM.SYSKEYUP : WM.KEYUP); + } + + browser.GetHost().SendKeyEvent(message, virtualKey, (int)modifiers); + } + + // Hooking the Tab key like this makes the tab focusing in essence work like + // KeyboardNavigation.TabNavigation="Cycle"; you will never be able to Tab out of the web browser control. + // We also add the condition to allow ctrl+a to work when the web browser control is put inside listbox. + // Prevent keyboard navigation using arrows and home and end keys + if (key == Key.Tab || key == Key.Home || key == Key.End || key == Key.Up + || key == Key.Down || key == Key.Left || key == Key.Right + || (key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) + { + e.Handled = true; + } + } + + public void HandleTextInput(TextCompositionEventArgs e) + { + var browser = owner.GetBrowser(); + if (browser != null) + { + var browserHost = browser.GetHost(); + for (int i = 0; i < e.Text.Length; i++) + { + browserHost.SendKeyEvent((int)WM.CHAR, e.Text[i], 0); + } + e.Handled = true; + } + } + } +} \ No newline at end of file diff --git a/CefSharp.Wpf/Internals/WpfLegacyKeyboardHandler.cs b/CefSharp.Wpf/Internals/WpfLegacyKeyboardHandler.cs new file mode 100644 index 0000000000..7e9ae2002c --- /dev/null +++ b/CefSharp.Wpf/Internals/WpfLegacyKeyboardHandler.cs @@ -0,0 +1,136 @@ +// Copyright © 2010-2017 The CefSharp Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using System; +using System.Windows.Input; +using System.Windows.Interop; +using CefSharp.Internals; + +namespace CefSharp.Wpf.Internals +{ + internal class WpfLegacyKeyboardHandler : IWpfKeyboardHandler + { + /// + /// The source hook + /// + private HwndSourceHook sourceHook; + + /// + /// The source + /// + private HwndSource source; + + /// + /// The owner browser instance + /// + private readonly ChromiumWebBrowser owner; + + public WpfLegacyKeyboardHandler(ChromiumWebBrowser owner) + { + this.owner = owner; + } + + public void Setup(HwndSource source) + { + this.source = source; + if (source != null) + { + sourceHook = SourceHook; + source.AddHook(SourceHook); + } + } + + public void Dispose() + { + if (source != null && sourceHook != null) + { + source.RemoveHook(sourceHook); + } + source = null; + } + + /// + /// WindowProc callback interceptor. Handles Windows messages intended for the source hWnd, and passes them to the + /// contained browser as needed. + /// + /// The source handle. + /// The message. + /// Additional message info. + /// Even more message info. + /// if set to true, the event has already been handled by someone else. + /// IntPtr. + protected IntPtr SourceHook(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled) + { + if (handled) + { + return IntPtr.Zero; + } + + switch ((WM)message) + { + case WM.SYSCHAR: + case WM.SYSKEYDOWN: + case WM.SYSKEYUP: + case WM.KEYDOWN: + case WM.KEYUP: + case WM.CHAR: + case WM.IME_CHAR: + { + if (!owner.IsKeyboardFocused) + { + break; + } + + if (message == (int)WM.SYSKEYDOWN && + wParam.ToInt32() == KeyInterop.VirtualKeyFromKey(Key.F4)) + { + // We don't want CEF to receive this event (and mark it as handled), since that makes it impossible to + // shut down a CefSharp-based app by pressing Alt-F4, which is kind of bad. + return IntPtr.Zero; + } + + var browser = owner.GetBrowser(); + if (browser != null) + { + browser.GetHost().SendKeyEvent(message, wParam.CastToInt32(), lParam.CastToInt32()); + handled = true; + } + + break; + } + } + + return IntPtr.Zero; + } + + public void HandleKeyPress(KeyEventArgs e) + { + // As KeyDown and KeyUp bubble, it appears they're being handled before they get a chance to + // trigger the appropriate WM_ messages handled by our SourceHook, so we have to handle these extra keys here. + // Hooking the Tab key like this makes the tab focusing in essence work like + // KeyboardNavigation.TabNavigation="Cycle"; you will never be able to Tab out of the web browser control. + // We also add the condition to allow ctrl+a to work when the web browser control is put inside listbox. + if (e.Key == Key.Tab || e.Key == Key.Home || e.Key == Key.End || e.Key == Key.Up + || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right + || (e.Key == Key.A && Keyboard.Modifiers == ModifierKeys.Control)) + { + var modifiers = e.GetModifiers(); + var message = (int)(e.IsDown ? WM.KEYDOWN : WM.KEYUP); + var virtualKey = KeyInterop.VirtualKeyFromKey(e.Key); + var browser = owner.GetBrowser(); + + if (browser != null) + { + browser.GetHost().SendKeyEvent(message, virtualKey, (int)modifiers); + e.Handled = true; + } + } + } + + public void HandleTextInput(TextCompositionEventArgs e) + { + // nothing to do here + } + } +}