From f26fd1b10bfd7b06adeb7e1a140970d92e6348b3 Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Mon, 23 Jan 2023 22:29:05 +0100 Subject: [PATCH 01/12] Merge branch 'feat/wpf-offscreen-browser' --- CefSharp.Dom | 2 +- .../BrowserProcessHandler.cs | 101 +- ...efSharp.OutOfProcess.BrowserProcess.csproj | 2 +- .../CefSharp.WPF.Internals/MonitorInfoEx.cs | 54 + .../CefSharp.WPF.Internals/RectStruct.cs | 48 + ...OffscreenOutOfProcessChromiumWebBrowser.cs | 127 + .../OutOfProcessChromiumWebBrowser.cs | 21 +- .../Program.cs | 14 +- .../RenderHandler.cs | 68 + CefSharp.OutOfProcess.Core/CefEventFlags.cs | 40 + .../CefSharp.OutOfProcess.Core.csproj | 5 +- .../IChromiumWebBrowser.cs | 2 +- .../Internal/IChromiumWebBrowserInternal.cs | 2 +- .../Internal/IRenderHandlerInternal.cs | 13 + CefSharp.OutOfProcess.Core/MouseButtonType.cs | 25 + .../OutOfProcessConnectionTransport.cs | 3 +- .../OutOfProcessHost.cs | 539 ++-- ...arp - Backup.OutOfProcess.Interface.csproj | 7 + .../CefSharp.OutOfProcess.Interface.csproj | 2 +- .../IOutOfProcessClientRpc.cs | 24 +- .../IOutOfProcessHostRpc.cs | 10 +- CefSharp.OutOfProcess.Interface/Rect.cs | 51 + ...Sharp.OutOfProcess.WinForms.Example.csproj | 2 +- .../Minimal/HostForm.cs | 2 +- .../CefSharp.OutOfProcess.WinForms.csproj | 2 +- .../ChromiumWebBrowser.cs | 2 +- ...p.OutOfProcess.Wpf.HwndHost.Example.csproj | 1 + .../CefSharp.OutOfProcess.Wpf.HwndHost.csproj | 3 +- .../ChromiumWebBrowser.cs | 2542 ++++++++--------- .../App.config | 6 + .../App.xaml | 11 + .../App.xaml.cs | 16 + .../AssemblyInfo.cs | 14 + .../Behaviours/HoverLinkBehaviour.cs | 34 + .../TextBoxBindingUpdateOnEnterBehaviour.cs | 28 + ...OfProcess.Wpf.OffscreenHost.Example.csproj | 19 + .../Converter/EnvironmentConverter.cs | 19 + .../Converter/TitleConverter.cs | 19 + .../MainWindow.xaml | 85 + .../MainWindow.xaml.cs | 52 + .../app.manifest | 77 + ...harp.OutOfProcess.Wpf.OffscreenHost.csproj | 27 + .../CefSharpWPF/AbstractRenderHandler.cs | 129 + .../DirectWritableBitmapRenderHandler.cs | 132 + .../CefSharpWPF/NativeMethodWrapper.cs | 32 + .../CefSharpWPF/RectStruct.cs | 47 + .../CefSharpWPF/WPFExtensions.cs | 244 ++ .../WritableBitmapRenderHandler.cs | 150 + .../Internals/DelegateCommand.cs | 68 + .../OffscreenChromiumWebBrowser.cs | 1363 +++++++++ CefSharp.OutOfProcess.sln | 146 +- 51 files changed, 4781 insertions(+), 1651 deletions(-) create mode 100644 CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/MonitorInfoEx.cs create mode 100644 CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/RectStruct.cs create mode 100644 CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs create mode 100644 CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs create mode 100644 CefSharp.OutOfProcess.Core/CefEventFlags.cs create mode 100644 CefSharp.OutOfProcess.Core/Internal/IRenderHandlerInternal.cs create mode 100644 CefSharp.OutOfProcess.Core/MouseButtonType.cs create mode 100644 CefSharp.OutOfProcess.Interface/CefSharp - Backup.OutOfProcess.Interface.csproj create mode 100644 CefSharp.OutOfProcess.Interface/Rect.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.config create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.xaml create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.xaml.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/AssemblyInfo.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Behaviours/HoverLinkBehaviour.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Behaviours/TextBoxBindingUpdateOnEnterBehaviour.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Converter/EnvironmentConverter.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Converter/TitleConverter.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/MainWindow.xaml create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/MainWindow.xaml.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/app.manifest create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharp.OutOfProcess.Wpf.OffscreenHost.csproj create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/AbstractRenderHandler.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/DirectWritableBitmapRenderHandler.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/NativeMethodWrapper.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/RectStruct.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/WPFExtensions.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/WritableBitmapRenderHandler.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost/Internals/DelegateCommand.cs create mode 100644 CefSharp.OutOfProcess.Wpf.OffscreenHost/OffscreenChromiumWebBrowser.cs diff --git a/CefSharp.Dom b/CefSharp.Dom index 97f3959..4a1d718 160000 --- a/CefSharp.Dom +++ b/CefSharp.Dom @@ -1 +1 @@ -Subproject commit 97f39597a889c61b3177e70dd1aef832f2c8065b +Subproject commit 4a1d7189ad83161c4d63265077f15cc8dcd5f72b diff --git a/CefSharp.OutOfProcess.BrowserProcess/BrowserProcessHandler.cs b/CefSharp.OutOfProcess.BrowserProcess/BrowserProcessHandler.cs index eb35359..5bee6f0 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/BrowserProcessHandler.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/BrowserProcessHandler.cs @@ -1,4 +1,4 @@ -using CefSharp.Internals; +using CefSharp.Internals; using PInvoke; using StreamJsonRpc; using System; @@ -13,6 +13,7 @@ namespace CefSharp.OutOfProcess.BrowserProcess public class BrowserProcessHandler : CefSharp.Handler.BrowserProcessHandler, IOutOfProcessClientRpc { private readonly int _parentProcessId; + private readonly bool _offscreenRendering; private IList _browsers = new List(); /// /// JSON RPC used for IPC with host @@ -20,9 +21,10 @@ public class BrowserProcessHandler : CefSharp.Handler.BrowserProcessHandler, IOu private JsonRpc _jsonRpc; private IOutOfProcessHostRpc _outOfProcessServer; - public BrowserProcessHandler(int parentProcessId) + public BrowserProcessHandler(int parentProcessId, bool offscreenRendering) { _parentProcessId = parentProcessId; + _offscreenRendering = offscreenRendering; } protected override void OnContextInitialized() @@ -30,10 +32,9 @@ protected override void OnContextInitialized() base.OnContextInitialized(); _jsonRpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput()); - _outOfProcessServer = _jsonRpc.Attach(); _jsonRpc.AllowModificationWhileListening = true; + _outOfProcessServer = _jsonRpc.Attach(); _jsonRpc.AddLocalRpcTarget(this, null); - _jsonRpc.AllowModificationWhileListening = false; var threadId = Kernel32.GetCurrentThreadId(); @@ -69,10 +70,25 @@ Task IOutOfProcessClientRpc.SendDevToolsMessage(int browserId, string message) { return CefThread.ExecuteOnUiThread(() => { - var browser = _browsers.FirstOrDefault(x => x.Id == browserId); + GetBrowser(browserId)?.GetBrowserHost().SendDevToolsMessage(message); + return true; + }); + } - browser?.GetBrowserHost().SendDevToolsMessage(message); + Task IOutOfProcessClientRpc.ShowDevTools(int browserId) + { + return CefThread.ExecuteOnUiThread(() => + { + GetBrowser(browserId).ShowDevTools(); + return true; + }); + } + Task IOutOfProcessClientRpc.LoadUrl(int browserId, string url) + { + return CefThread.ExecuteOnUiThread(() => + { + GetBrowser(browserId).LoadUrl(url); return true; }); } @@ -89,40 +105,77 @@ Task IOutOfProcessClientRpc.CloseHost() Task IOutOfProcessClientRpc.CreateBrowser(IntPtr parentHwnd, string url, int id) { - //Debugger.Break(); - return CefThread.ExecuteOnUiThread(() => { - var browser = new OutOfProcessChromiumWebBrowser(_outOfProcessServer, id, url); - - var windowInfo = new WindowInfo(); - windowInfo.WindowName = "CefSharpBrowserProcess"; - windowInfo.SetAsChild(parentHwnd); - - //Disable Window activation by default - //https://bitbucket.org/chromiumembedded/cef/issues/1856/branch-2526-cef-activates-browser-window - windowInfo.ExStyle |= OutOfProcessChromiumWebBrowser.WS_EX_NOACTIVATE; + OutOfProcessChromiumWebBrowser browser; + + IWindowInfo windowInfo; + + if (_offscreenRendering) + { + browser = new OffscreenOutOfProcessChromiumWebBrowser(_outOfProcessServer, id, url); + windowInfo = Core.ObjectFactory.CreateWindowInfo(); + windowInfo.SetAsWindowless(parentHwnd); + windowInfo.Width = 0; + windowInfo.Height = 0; + } + else + { + browser = new OutOfProcessChromiumWebBrowser(_outOfProcessServer, id, url); + windowInfo = new WindowInfo(); + windowInfo.WindowName = "CefSharpBrowserProcess"; + windowInfo.SetAsChild(parentHwnd); + + //Disable Window activation by default + //https://bitbucket.org/chromiumembedded/cef/issues/1856/branch-2526-cef-activates-browser-window + windowInfo.ExStyle |= OutOfProcessChromiumWebBrowser.WS_EX_NOACTIVATE; + + } browser.CreateBrowser(windowInfo); - + _browsers.Add(browser); return true; }); } - void IOutOfProcessClientRpc.NotifyMoveOrResizeStarted(int browserId) + void IOutOfProcessClientRpc.NotifyMoveOrResizeStarted(int browserId, Rect rect) { - var browser = _browsers.FirstOrDefault(x => x.Id == browserId); + var browser = GetBrowser(browserId); + if(browser == null) + { + return; + } + + var host = browser.GetBrowserHost(); + host.NotifyMoveOrResizeStarted(); + + if (_offscreenRendering && browser is OffscreenOutOfProcessChromiumWebBrowser offscreenBrowser) + { + offscreenBrowser.browserLocation = new System.Drawing.Point(rect.X, rect.Y); + host.NotifyMoveOrResizeStarted(); + + offscreenBrowser.viewRect = new Structs.Rect(0, 0, rect.Width, rect.Height); - browser?.GetBrowserHost().NotifyMoveOrResizeStarted(); + host.WasResized(); + } } void IOutOfProcessClientRpc.SetFocus(int browserId, bool focus) { - var browser = _browsers.FirstOrDefault(x => x.Id == browserId); + GetBrowser(browserId)?.GetBrowserHost().SetFocus(focus); + } - browser?.GetBrowserHost().SetFocus(focus); + void IOutOfProcessClientRpc.SendMouseClickEvent(int browserId, int x, int y, string mouseButtonType, bool mouseUp, int clickCount, uint eventFlags) + { + CefThread.ExecuteOnUiThread(() => + { + GetBrowser(browserId)?.GetBrowserHost().SendMouseClickEvent(x, y, (MouseButtonType)Enum.Parse(typeof(MouseButtonType), mouseButtonType), mouseUp, clickCount, (CefEventFlags)eventFlags); + return true; + }); } + + private OutOfProcessChromiumWebBrowser GetBrowser(int id) => _browsers.FirstOrDefault(x => x.Id == id); } -} +} \ No newline at end of file diff --git a/CefSharp.OutOfProcess.BrowserProcess/CefSharp.OutOfProcess.BrowserProcess.csproj b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.OutOfProcess.BrowserProcess.csproj index a2f52c9..74645f5 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/CefSharp.OutOfProcess.BrowserProcess.csproj +++ b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.OutOfProcess.BrowserProcess.csproj @@ -1,4 +1,4 @@ - + WinExe diff --git a/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/MonitorInfoEx.cs b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/MonitorInfoEx.cs new file mode 100644 index 0000000..69701a8 --- /dev/null +++ b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/MonitorInfoEx.cs @@ -0,0 +1,54 @@ +// Copyright © 2018 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.Runtime.InteropServices; + +namespace CefSharp.Wpf.Internals +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct MonitorInfoEx + { + private const int CCHDEVICENAME = 32; + /// + /// The size, in bytes, of the structure. Set this member to sizeof(MONITORINFOEX) (72) before calling the GetMonitorInfo function. + /// Doing so lets the function determine the type of structure you are passing to it. + /// + public int Size; + + /// + /// A RECT structure that specifies the display monitor rectangle, expressed in virtual-screen coordinates. + /// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. + /// + public RectStruct Monitor; + + /// + /// A RECT structure that specifies the work area rectangle of the display monitor that can be used by applications, + /// expressed in virtual-screen coordinates. Windows uses this rectangle to maximize an application on the monitor. + /// The rest of the area in rcMonitor contains system windows such as the task bar and side bars. + /// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. + /// + public RectStruct WorkArea; + + /// + /// The attributes of the display monitor. + /// + /// This member can be the following value: + /// 1 : MONITORINFOF_PRIMARY + /// + public uint Flags; + + /// + /// A string that specifies the device name of the monitor being used. Most applications have no use for a display monitor name, + /// and so can save some bytes by using a MONITORINFO structure. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] + public string DeviceName; + + public void Init() + { + Size = 40 + 2 * CCHDEVICENAME; + DeviceName = string.Empty; + } + } +} diff --git a/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/RectStruct.cs b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/RectStruct.cs new file mode 100644 index 0000000..9df00b9 --- /dev/null +++ b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/RectStruct.cs @@ -0,0 +1,48 @@ +// Copyright © 2018 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.Runtime.InteropServices; +using CefSharp.Structs; + +namespace CefSharp.Wpf.Internals +{ + /// + /// The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle. + /// + /// + /// + /// By convention, the right and bottom edges of the rectangle are normally considered exclusive. + /// In other words, the pixel whose coordinates are ( right, bottom ) lies immediately outside of the the rectangle. + /// For example, when RECT is passed to the FillRect function, the rectangle is filled up to, but not including, + /// the right column and bottom row of pixels. This structure is identical to the RECTL structure. + /// + [StructLayout(LayoutKind.Sequential)] + public struct RectStruct + { + /// + /// The x-coordinate of the upper-left corner of the rectangle. + /// + public int Left; + + /// + /// The y-coordinate of the upper-left corner of the rectangle. + /// + public int Top; + + /// + /// The x-coordinate of the lower-right corner of the rectangle. + /// + public int Right; + + /// + /// The y-coordinate of the lower-right corner of the rectangle. + /// + public int Bottom; + + public static implicit operator Rect(RectStruct rect) + { + return new Rect(0, 0, rect.Right - rect.Left, rect.Bottom - rect.Top); + } + } +} diff --git a/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs b/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs new file mode 100644 index 0000000..5a52106 --- /dev/null +++ b/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs @@ -0,0 +1,127 @@ +using CefSharp.Internals; +using System; +using CefSharp.OutOfProcess.Interface; +using CefSharp.Wpf.Internals; +using CefSharp.Structs; +using CefSharp.Enums; + +namespace CefSharp.OutOfProcess.BrowserProcess +{ + /// + /// An ChromiumWebBrowser instance specifically for hosting CEF out of process + /// + public class OffscreenOutOfProcessChromiumWebBrowser : OutOfProcessChromiumWebBrowser, IRenderWebBrowser + { + private readonly RenderHandler renderHandler; + private readonly RenderHandler popupRenderHandler; + + /// + /// The MonitorInfo based on the current hwnd + /// + private MonitorInfoEx monitorInfo; + + public OffscreenOutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcessServer, int id, string address = "", IRequestContext requestContext = null) + : base(outOfProcessServer, id, address, requestContext, true) + { + renderHandler = new RenderHandler($"0render_{_id}_"); + popupRenderHandler = new RenderHandler($"0render_{_id}_popup_"); + } + + /// + /// The dpi scale factor, if the browser has already been initialized + /// you must manually call IBrowserHost.NotifyScreenInfoChanged for the + /// browser to be notified of the change. + /// + public float DpiScaleFactor { get; set; } = 1; + + public System.Drawing.Point browserLocation { get; internal set; } + + public CefSharp.Structs.Rect viewRect { get; internal set; } + + /// + /// Gets the ScreenInfo - currently used to get the DPI scale factor. + /// + /// ScreenInfo containing the current DPI scale factor + ScreenInfo? IRenderWebBrowser.GetScreenInfo() => GetScreenInfo(); + + /// + /// Gets the ScreenInfo - currently used to get the DPI scale factor. + /// + /// ScreenInfo containing the current DPI scale factor + protected virtual ScreenInfo? GetScreenInfo() + { + CefSharp.Structs.Rect rect = monitorInfo.Monitor; + CefSharp.Structs.Rect availableRect = monitorInfo.WorkArea; + + if (DpiScaleFactor > 1.0) + { + rect = rect.ScaleByDpi(DpiScaleFactor); + availableRect = availableRect.ScaleByDpi(DpiScaleFactor); + } + + var screenInfo = new ScreenInfo + { + DeviceScaleFactor = DpiScaleFactor, + Rect = rect, + AvailableRect = availableRect, + }; + + return screenInfo; + } + + Structs.Rect IRenderWebBrowser.GetViewRect() => viewRect; + + bool IRenderWebBrowser.GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY) + { + screenX = browserLocation.X; + screenY = browserLocation.Y; + + return true; + } + + void IRenderWebBrowser.OnAcceleratedPaint(PaintElementType type, Structs.Rect dirtyRect, IntPtr sharedHandle) + { + throw new NotImplementedException(); + } + + void IRenderWebBrowser.OnPaint(PaintElementType type, Structs.Rect dirtyRect, IntPtr buffer, int width, int height) + { + var dirtyRectCopy = new Interface.Rect(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height); + string file = type == PaintElementType.Popup + ? popupRenderHandler.OnPaint(buffer, width, height) + : renderHandler.OnPaint(buffer, width, height); + + _outofProcessHostRpc.NotifyPaint(Id, type == PaintElementType.Popup, dirtyRectCopy, width, height, file); + } + + void IRenderWebBrowser.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo) + { + // TODO: (CEF) + } + + bool IRenderWebBrowser.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y) + { + // TODO: (CEF) + return false; + } + + void IRenderWebBrowser.UpdateDragCursor(DragOperationsMask operation) + { + // TODO: (CEF) + } + + void IRenderWebBrowser.OnPopupShow(bool show) => _outofProcessHostRpc.OnPopupShow(Id, show); + + void IRenderWebBrowser.OnPopupSize(CefSharp.Structs.Rect rect) => _outofProcessHostRpc.OnPopupSize(Id, new Interface.Rect(rect.X, rect.Y, rect.Width, rect.Height)); + + void IRenderWebBrowser.OnImeCompositionRangeChanged(Structs.Range selectedRange, CefSharp.Structs.Rect[] characterBounds) + { + throw new NotImplementedException(); + } + + void IRenderWebBrowser.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode) + { + //throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs b/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs index 63966f8..330fb56 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs @@ -1,4 +1,4 @@ -using CefSharp.Internals; +using CefSharp.Internals; using System; using System.Threading.Tasks; using System.Threading; @@ -7,15 +7,13 @@ using System.IO; using CefSharp.OutOfProcess.Interface; using System.Runtime.InteropServices; -using PInvoke; -using System.Diagnostics; namespace CefSharp.OutOfProcess.BrowserProcess { /// /// An ChromiumWebBrowser instance specifically for hosting CEF out of process /// - public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal + public class OutOfProcessChromiumWebBrowser : IWebBrowserInternal { public const string BrowserNotInitializedExceptionErrorMessage = "The ChromiumWebBrowser instance creates the underlying Chromium Embedded Framework (CEF) browser instance in an async fashion. " + @@ -36,7 +34,7 @@ public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal /// /// Internal ID used for tracking browsers between Processes; /// - private int _id; + private protected readonly int _id; /// /// The managed cef browser adapter @@ -46,7 +44,7 @@ public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal /// /// JSON RPC used for IPC with host /// - private IOutOfProcessHostRpc _outofProcessHostRpc; + private protected readonly IOutOfProcessHostRpc _outofProcessHostRpc; /// /// Flag to guard the creation of the underlying browser - only one instance can be created @@ -453,7 +451,10 @@ private void RemoveExNoActivateStyle(IntPtr hwnd) /// public void LoadUrl(string url) { - throw new NotImplementedException(); + using (var frame = _browser.MainFrame) + { + frame.LoadUrl(url); + } } /// @@ -665,7 +666,7 @@ public bool IsBrowserInitialized /// /// Cef::Initialize() failed public OutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcessServer, int id, string address = "", - IRequestContext requestContext = null) + IRequestContext requestContext = null, bool offscreenRendering = false) { _id = id; RequestContext = requestContext; @@ -674,7 +675,7 @@ public OutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcessServer, i Cef.AddDisposable(this); Address = address; - _managedCefBrowserAdapter = ManagedCefBrowserAdapter.Create(this, false); + _managedCefBrowserAdapter = ManagedCefBrowserAdapter.Create(this, offscreenRendering); } /// @@ -774,7 +775,7 @@ public void CreateBrowser(IWindowInfo windowInfo = null, IBrowserSettings browse //We actually check if WS_EX_NOACTIVATE was set for instances //the user has override CreateBrowserWindowInfo and not called base.CreateBrowserWindowInfo - _removeExNoActivateStyle = (windowInfo.ExStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE; + _removeExNoActivateStyle = !windowInfo.WindowlessRenderingEnabled && (windowInfo.ExStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE; //TODO: We need some sort of timeout and //if we use the same approach for WPF/WinForms then diff --git a/CefSharp.OutOfProcess.BrowserProcess/Program.cs b/CefSharp.OutOfProcess.BrowserProcess/Program.cs index 39f6b0b..418d0f5 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/Program.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; using CefSharp.Internals; @@ -18,6 +18,7 @@ public static int Main(string[] args) var parentProcessId = int.Parse(CommandLineArgsParser.GetArgumentValue(args, "--parentProcessId")); var cachePath = CommandLineArgsParser.GetArgumentValue(args, "--cachePath"); + var offscreenRendering = bool.Parse(CommandLineArgsParser.GetArgumentValue(args, "--offscreenRendering")); var parentProcess = Process.GetProcessById(parentProcessId); @@ -25,14 +26,21 @@ public static int Main(string[] args) { //By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data CachePath = cachePath, + WindowlessRenderingEnabled = offscreenRendering, MultiThreadedMessageLoop = false }; - var browserProcessHandler = new BrowserProcessHandler(parentProcessId); + //// // too slow when webgl is used + ////if (offscreenRendering) + ////{ + //// settings.SetOffScreenRenderingBestPerformanceArgs(); + ////} + + var browserProcessHandler = new BrowserProcessHandler(parentProcessId, offscreenRendering); Cef.EnableWaitForBrowsersToClose(); - var success = Cef.Initialize(settings, performDependencyCheck:true, browserProcessHandler: browserProcessHandler); + var success = Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: browserProcessHandler); if(!success) { diff --git a/CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs b/CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs new file mode 100644 index 0000000..2e584ae --- /dev/null +++ b/CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.InteropServices; +using System.IO.MemoryMappedFiles; + +namespace CefSharp.OutOfProcess.BrowserProcess +{ + internal sealed class RenderHandler : IDisposable + { + private readonly string renderFileNameTemplate; + private string renderFileName; + private MemoryMappedViewAccessor viewAccessor; + private MemoryMappedFile mappedFile; + private int currentAvailableBytes = 0; + + public RenderHandler(string fileName) + { + renderFileNameTemplate = fileName; + } + + public string OnPaint(IntPtr buffer, int width, int height) + { + const int bytesPerPixel = 32 / 8; + const int reserverdSizeBits = 2 * sizeof(int); + int maximumPixels = width * height; + int requiredfBytes = (maximumPixels * bytesPerPixel) + reserverdSizeBits; + + bool createNewBitmap = mappedFile == null || currentAvailableBytes < requiredfBytes; + + if (createNewBitmap) + { + currentAvailableBytes = requiredfBytes; + + if (mappedFile != null) + { + mappedFile.SafeMemoryMappedFileHandle.Close(); + mappedFile.Dispose(); + + viewAccessor.SafeMemoryMappedViewHandle.Close(); + viewAccessor.Dispose(); + } + + renderFileName = renderFileNameTemplate + Guid.NewGuid(); + + mappedFile = MemoryMappedFile.CreateNew(renderFileName, requiredfBytes, MemoryMappedFileAccess.ReadWrite); + viewAccessor = mappedFile.CreateViewAccessor(0, requiredfBytes, MemoryMappedFileAccess.Write); + } + + var ptr = viewAccessor.SafeMemoryMappedViewHandle.DangerousGetHandle(); + + Marshal.WriteInt32(ptr, width); + Marshal.WriteInt32(ptr + sizeof(int), height); + CopyMemory(ptr + reserverdSizeBits, buffer, (uint)requiredfBytes - reserverdSizeBits); + + viewAccessor.Flush(); + + return renderFileName; + } + + [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", ExactSpelling = true)] + public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); + + public void Dispose() + { + viewAccessor?.Dispose(); + mappedFile?.Dispose(); + } + } +} \ No newline at end of file diff --git a/CefSharp.OutOfProcess.Core/CefEventFlags.cs b/CefSharp.OutOfProcess.Core/CefEventFlags.cs new file mode 100644 index 0000000..de2576b --- /dev/null +++ b/CefSharp.OutOfProcess.Core/CefEventFlags.cs @@ -0,0 +1,40 @@ +// Copyright © 2015 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.OutOfProcess +{ + /// + /// Supported event bit flags. + /// + [Flags] + public enum CefEventFlags : uint + { + None = 0, + + CapsLockOn = 1 << 0, + + ShiftDown = 1 << 1, + ControlDown = 1 << 2, + AltDown = 1 << 3, + + LeftMouseButton = 1 << 4, + MiddleMouseButton = 1 << 5, + RightMouseButton = 1 << 6, + + /// + /// Mac OS-X command key. + /// + CommandDown = 1 << 7, + + NumLockOn = 1 << 8, + IsKeyPad = 1 << 9, + IsLeft = 1 << 10, + IsRight = 1 << 11, + AltGrDown = 1 << 12, + IsRepeat = 1 << 13, + } +} \ No newline at end of file diff --git a/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj b/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj index 59e3902..bdf1eb7 100644 --- a/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj +++ b/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj @@ -1,7 +1,8 @@ - + - netstandard2.0 + netstandard2.0 + AnyCPU CefSharp.OutOfProcess diff --git a/CefSharp.OutOfProcess.Core/IChromiumWebBrowser.cs b/CefSharp.OutOfProcess.Core/IChromiumWebBrowser.cs index 9704d7b..1b8cfc7 100644 --- a/CefSharp.OutOfProcess.Core/IChromiumWebBrowser.cs +++ b/CefSharp.OutOfProcess.Core/IChromiumWebBrowser.cs @@ -1,4 +1,4 @@ -using CefSharp.Dom; +using CefSharp.Dom; using System; using System.Threading.Tasks; diff --git a/CefSharp.OutOfProcess.Core/Internal/IChromiumWebBrowserInternal.cs b/CefSharp.OutOfProcess.Core/Internal/IChromiumWebBrowserInternal.cs index 5870609..c7cf95f 100644 --- a/CefSharp.OutOfProcess.Core/Internal/IChromiumWebBrowserInternal.cs +++ b/CefSharp.OutOfProcess.Core/Internal/IChromiumWebBrowserInternal.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace CefSharp.OutOfProcess.Internal { diff --git a/CefSharp.OutOfProcess.Core/Internal/IRenderHandlerInternal.cs b/CefSharp.OutOfProcess.Core/Internal/IRenderHandlerInternal.cs new file mode 100644 index 0000000..c55234f --- /dev/null +++ b/CefSharp.OutOfProcess.Core/Internal/IRenderHandlerInternal.cs @@ -0,0 +1,13 @@ +using CefSharp.OutOfProcess.Interface; + +namespace CefSharp.OutOfProcess.Internal +{ + public interface IRenderHandlerInternal + { + void OnPaint(bool isPopup, Rect dirtyRect, int width, int height, string file); + + void OnPopupShow(bool show); + + void OnPopupSize(Rect rect); + } +} \ No newline at end of file diff --git a/CefSharp.OutOfProcess.Core/MouseButtonType.cs b/CefSharp.OutOfProcess.Core/MouseButtonType.cs new file mode 100644 index 0000000..17b2a9b --- /dev/null +++ b/CefSharp.OutOfProcess.Core/MouseButtonType.cs @@ -0,0 +1,25 @@ +// Copyright © 2015 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.OutOfProcess +{ + /// + /// Values that represent mouse button types. + /// + public enum MouseButtonType + { + /// + /// Left Mouse Button + /// + Left = 0, + /// + /// Middle Mouse Button + /// + Middle, + /// + /// Right Mouse Button + /// + Right + } +} \ No newline at end of file diff --git a/CefSharp.OutOfProcess.Core/OutOfProcessConnectionTransport.cs b/CefSharp.OutOfProcess.Core/OutOfProcessConnectionTransport.cs index a08309f..43d6b82 100644 --- a/CefSharp.OutOfProcess.Core/OutOfProcessConnectionTransport.cs +++ b/CefSharp.OutOfProcess.Core/OutOfProcessConnectionTransport.cs @@ -1,4 +1,4 @@ -using CefSharp.Dom.Transport; +using CefSharp.Dom.Transport; using System; using System.Threading.Tasks; @@ -12,6 +12,7 @@ public class OutOfProcessConnectionTransport : IConnectionTransport public event EventHandler MessageReceived; public event EventHandler MessageError; + public event EventHandler Disconnected; public OutOfProcessConnectionTransport(int browserId, OutOfProcessHost outOfProcessHost) { diff --git a/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs b/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs index 1fec5c4..6c934f2 100644 --- a/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs +++ b/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs @@ -1,258 +1,281 @@ -using CefSharp.OutOfProcess.Interface; -using CefSharp.OutOfProcess.Internal; -using PInvoke; -using StreamJsonRpc; -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; - -namespace CefSharp.OutOfProcess -{ - public class OutOfProcessHost : IOutOfProcessHostRpc, IDisposable - { - /// - /// The CefSharp.OutOfProcess.BrowserProcess.exe name - /// - public const string HostExeName = "CefSharp.OutOfProcess.BrowserProcess.exe"; - - private Process _browserProcess; - private JsonRpc _jsonRpc; - private IOutOfProcessClientRpc _outOfProcessClient; - private string _cefSharpVersion; - private string _cefVersion; - private string _chromiumVersion; - private int _uiThreadId; - private int _remoteuiThreadId; - private int _browserIdentifier = 1; - private string _outofProcessHostExePath; - private string _cachePath; - private ConcurrentDictionary _browsers = new ConcurrentDictionary(); - private TaskCompletionSource _processInitialized = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - private OutOfProcessHost(string outOfProcessHostExePath, string cachePath = null) - { - _outofProcessHostExePath = outOfProcessHostExePath; - _cachePath = cachePath; - } - - /// - /// UI Thread assocuated with this - /// - public int UiThreadId - { - get { return _uiThreadId; } - } - - /// - /// Thread Id of the UI Thread running in the Browser Process - /// - public int RemoteUiThreadId - { - get { return _remoteuiThreadId; } - } - - /// - /// CefSharp Version - /// - public string CefSharpVersion - { - get { return _cefSharpVersion; } - } - - /// - /// Cef Version - /// - public string CefVersion - { - get { return _cefVersion; } - } - - /// - /// Chromium Version - /// - public string ChromiumVersion - { - get { return _chromiumVersion; } - } - - /// - /// Sends an IPC message to the Browser Process instructing it - /// to create a new Out of process browser - /// - /// The that will host the browser - /// handle used to host the control - /// - /// - /// - public bool CreateBrowser(IChromiumWebBrowserInternal browser, IntPtr handle, string url, out int id) - { - id = _browserIdentifier++; - _ = _outOfProcessClient.CreateBrowser(handle, url, id); - - return _browsers.TryAdd(id, browser); - } - - internal Task SendDevToolsMessageAsync(int browserId, string message) - { - return _outOfProcessClient.SendDevToolsMessage(browserId, message); - } - - private Task InitializedTask - { - get { return _processInitialized.Task; } - } - - private void Init() - { - var currentProcess = Process.GetCurrentProcess(); - - var args = $"--parentProcessId={currentProcess.Id} --cachePath={_cachePath}"; - - _browserProcess = Process.Start(new ProcessStartInfo(_outofProcessHostExePath, args) - { - RedirectStandardInput = true, - RedirectStandardOutput = true, - }); - - _browserProcess.Exited += OnBrowserProcessExited; - - _jsonRpc = JsonRpc.Attach(_browserProcess.StandardInput.BaseStream, _browserProcess.StandardOutput.BaseStream); - - _outOfProcessClient = _jsonRpc.Attach(); - _jsonRpc.AllowModificationWhileListening = true; - _jsonRpc.AddLocalRpcTarget(this, null); - _jsonRpc.AllowModificationWhileListening = false; - - _uiThreadId = Kernel32.GetCurrentThreadId(); - } - - private void OnBrowserProcessExited(object sender, EventArgs e) - { - var exitCode = _browserProcess.ExitCode; - } - - void IOutOfProcessHostRpc.NotifyAddressChanged(int browserId, string address) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.SetAddress(address); - } - } - - void IOutOfProcessHostRpc.NotifyBrowserCreated(int browserId, IntPtr browserHwnd) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.OnAfterBrowserCreated(browserHwnd); - } - } - - void IOutOfProcessHostRpc.NotifyContextInitialized(int threadId, string cefSharpVersion, string cefVersion, string chromiumVersion) - { - _remoteuiThreadId = threadId; - _cefSharpVersion = cefSharpVersion; - _cefVersion = cefVersion; - _chromiumVersion = chromiumVersion; - - _processInitialized.TrySetResult(this); - } - - void IOutOfProcessHostRpc.NotifyDevToolsAgentDetached(int browserId) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - - } - } - - void IOutOfProcessHostRpc.NotifyDevToolsMessage(int browserId, string devToolsMessage) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.OnDevToolsMessage(devToolsMessage); - } - } - - void IOutOfProcessHostRpc.NotifyDevToolsReady(int browserId) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.OnDevToolsReady(); - } - } - - void IOutOfProcessHostRpc.NotifyLoadingStateChange(int browserId, bool canGoBack, bool canGoForward, bool isLoading) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.SetLoadingStateChange(canGoBack, canGoForward, isLoading); - } - } - - void IOutOfProcessHostRpc.NotifyStatusMessage(int browserId, string statusMessage) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.SetStatusMessage(statusMessage); - } - } - - void IOutOfProcessHostRpc.NotifyTitleChanged(int browserId, string title) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.SetTitle(title); - } - } - - public void NotifyMoveOrResizeStarted(int id) - { - _outOfProcessClient.NotifyMoveOrResizeStarted(id); - } - - /// - /// Set whether the browser is focused. (Used for Normal Rendering e.g. WinForms) - /// - /// browser id - /// set focus - public void SetFocus(int id, bool focus) - { - _outOfProcessClient.SetFocus(id, focus); - } - - public void CloseBrowser(int id) - { - _ = _outOfProcessClient.CloseBrowser(id); - } - - public void Dispose() - { - _ = _outOfProcessClient.CloseHost(); - _jsonRpc?.Dispose(); - _jsonRpc = null; - } - - public static Task CreateAsync(string path = HostExeName, string cachePath = null) - { - if(string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - var fullPath = Path.GetFullPath(path); - - if (!File.Exists(fullPath)) - { - throw new FileNotFoundException("Unable to find Host executable.", path); - } - - var host = new OutOfProcessHost(fullPath, cachePath); - - host.Init(); - - return host.InitializedTask; - } - } -} +using CefSharp.OutOfProcess.Interface; +using CefSharp.OutOfProcess.Internal; +using PInvoke; +using StreamJsonRpc; +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +namespace CefSharp.OutOfProcess +{ + public class OutOfProcessHost : IOutOfProcessHostRpc, IDisposable + { + /// + /// The CefSharp.OutOfProcess.BrowserProcess.exe name + /// + public const string HostExeName = "CefSharp.OutOfProcess.BrowserProcess.exe"; + + private readonly bool _offscreenRendering; + private Process _browserProcess; + private JsonRpc _jsonRpc; + private IOutOfProcessClientRpc _outOfProcessClient; + private string _cefSharpVersion; + private string _cefVersion; + private string _chromiumVersion; + private int _uiThreadId; + private int _remoteuiThreadId; + private int _browserIdentifier = 1; + private string _outofProcessHostExePath; + private string _cachePath; + private ConcurrentDictionary _browsers = new ConcurrentDictionary(); + private TaskCompletionSource _processInitialized = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + private OutOfProcessHost(string outOfProcessHostExePath, string cachePath = null, bool offscreenRendering = false) + { + _outofProcessHostExePath = outOfProcessHostExePath; + _cachePath = cachePath; + _offscreenRendering = offscreenRendering; + } + + /// + /// UI Thread assocuated with this + /// + public int UiThreadId + { + get { return _uiThreadId; } + } + + /// + /// Thread Id of the UI Thread running in the Browser Process + /// + public int RemoteUiThreadId + { + get { return _remoteuiThreadId; } + } + + /// + /// CefSharp Version + /// + public string CefSharpVersion + { + get { return _cefSharpVersion; } + } + + /// + /// Cef Version + /// + public string CefVersion + { + get { return _cefVersion; } + } + + /// + /// Chromium Version + /// + public string ChromiumVersion + { + get { return _chromiumVersion; } + } + + /// + /// Sends an IPC message to the Browser Process instructing it + /// to create a new Out of process browser + /// + /// The that will host the browser + /// handle used to host the control + /// + /// + /// + public bool CreateBrowser(IChromiumWebBrowserInternal browser, IntPtr handle, string url, out int id) + { + id = _browserIdentifier++; + _ = _outOfProcessClient.CreateBrowser(handle, url, id); + + return _browsers.TryAdd(id, browser); + } + + public Task ShowDevTools(int browserId) + { + return _outOfProcessClient.ShowDevTools(browserId); + } + + public void SendMouseClickEvent(int browserId, int x, int y, MouseButtonType mouseButtonType, bool mouseUp, int clickCount, CefEventFlags eventFlags) + { + _outOfProcessClient.SendMouseClickEvent(browserId, x, y, mouseButtonType.ToString(), mouseUp, clickCount, (uint)eventFlags); + } + + public Task LoadUrl(int browserId, string url) + { + return _outOfProcessClient.LoadUrl(browserId, url); + } + + internal Task SendDevToolsMessageAsync(int browserId, string message) + { + return _outOfProcessClient.SendDevToolsMessage(browserId, message); + } + + private Task InitializedTask + { + get { return _processInitialized.Task; } + } + + private void Init() + { + var currentProcess = Process.GetCurrentProcess(); + + var args = $"--parentProcessId={currentProcess.Id} --cachePath={_cachePath} --offscreenRendering={_offscreenRendering}"; + + _browserProcess = Process.Start(new ProcessStartInfo(_outofProcessHostExePath, args) + { + RedirectStandardInput = true, + RedirectStandardOutput = true, + UseShellExecute = false, + }); + + _browserProcess.Exited += OnBrowserProcessExited; + + _jsonRpc = JsonRpc.Attach(_browserProcess.StandardInput.BaseStream, _browserProcess.StandardOutput.BaseStream); + + _outOfProcessClient = _jsonRpc.Attach(); + _jsonRpc.AllowModificationWhileListening = true; + _jsonRpc.AddLocalRpcTarget(this, null); + _jsonRpc.AllowModificationWhileListening = false; + + _uiThreadId = Kernel32.GetCurrentThreadId(); + } + + private void OnBrowserProcessExited(object sender, EventArgs e) + { + var exitCode = _browserProcess.ExitCode; + } + + void IOutOfProcessHostRpc.NotifyAddressChanged(int browserId, string address) + { + GetBrowser(browserId)?.SetAddress(address); + } + + void IOutOfProcessHostRpc.NotifyBrowserCreated(int browserId, IntPtr browserHwnd) + { + GetBrowser(browserId)?.OnAfterBrowserCreated(browserHwnd); + } + + void IOutOfProcessHostRpc.NotifyContextInitialized(int threadId, string cefSharpVersion, string cefVersion, string chromiumVersion) + { + _remoteuiThreadId = threadId; + _cefSharpVersion = cefSharpVersion; + _cefVersion = cefVersion; + _chromiumVersion = chromiumVersion; + + _processInitialized.TrySetResult(this); + } + + void IOutOfProcessHostRpc.NotifyDevToolsAgentDetached(int browserId) + { + if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) + { + + } + } + + void IOutOfProcessHostRpc.NotifyDevToolsMessage(int browserId, string devToolsMessage) + { + GetBrowser(browserId)?.OnDevToolsMessage(devToolsMessage); + } + + void IOutOfProcessHostRpc.NotifyDevToolsReady(int browserId) + { + GetBrowser(browserId)?.OnDevToolsReady(); + } + + void IOutOfProcessHostRpc.NotifyLoadingStateChange(int browserId, bool canGoBack, bool canGoForward, bool isLoading) + { + GetBrowser(browserId)?.SetLoadingStateChange(canGoBack, canGoForward, isLoading); + } + + void IOutOfProcessHostRpc.NotifyStatusMessage(int browserId, string statusMessage) + { + GetBrowser(browserId)?.SetStatusMessage(statusMessage); + } + + void IOutOfProcessHostRpc.NotifyTitleChanged(int browserId, string title) + { + GetBrowser(browserId)?.SetTitle(title); + } + + void IOutOfProcessHostRpc.NotifyPaint(int browserId, bool isPopup, Rect dirtyRect, int width, int height, string file) + { + ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPaint(isPopup, dirtyRect, width, height, file); + } + + void IOutOfProcessHostRpc.NotifyPopupShow(int browserId, bool show) + { + ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPopupShow(show); + } + + void IOutOfProcessHostRpc.NotifyPopupSize(int browserId, Rect rect) + { + ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPopupSize(rect); + } + + public void NotifyMoveOrResizeStarted(int id, Rect rect = default) + { + _outOfProcessClient.NotifyMoveOrResizeStarted(id, rect); + } + + /// + /// Set whether the browser is focused. (Used for Normal Rendering e.g. WinForms) + /// + /// browser id + /// set focus + public void SetFocus(int id, bool focus) + { + _outOfProcessClient.SetFocus(id, focus); + } + + public void CloseBrowser(int id) + { + _ = _outOfProcessClient.CloseBrowser(id); + _browsers.TryRemove(id, out _); + } + + public void Dispose() + { + _ = _outOfProcessClient.CloseHost(); + _jsonRpc?.Dispose(); + _jsonRpc = null; + } + + public static Task CreateAsync(string path = HostExeName, string cachePath = null, bool offScreenRendering = false) + { + if(string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException(nameof(path)); + } + + var fullPath = Path.GetFullPath(path); + + if (!File.Exists(fullPath)) + { + throw new FileNotFoundException("Unable to find Host executable.", path); + } + + var host = new OutOfProcessHost(fullPath, cachePath, offScreenRendering); + + host.Init(); + + return host.InitializedTask; + } + + private IChromiumWebBrowserInternal GetBrowser(int browserId) + { + if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) + { + return chromiumWebBrowser; + } + + return null; + } + } +} diff --git a/CefSharp.OutOfProcess.Interface/CefSharp - Backup.OutOfProcess.Interface.csproj b/CefSharp.OutOfProcess.Interface/CefSharp - Backup.OutOfProcess.Interface.csproj new file mode 100644 index 0000000..969882a --- /dev/null +++ b/CefSharp.OutOfProcess.Interface/CefSharp - Backup.OutOfProcess.Interface.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/CefSharp.OutOfProcess.Interface/CefSharp.OutOfProcess.Interface.csproj b/CefSharp.OutOfProcess.Interface/CefSharp.OutOfProcess.Interface.csproj index 9f5c4f4..969882a 100644 --- a/CefSharp.OutOfProcess.Interface/CefSharp.OutOfProcess.Interface.csproj +++ b/CefSharp.OutOfProcess.Interface/CefSharp.OutOfProcess.Interface.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0 diff --git a/CefSharp.OutOfProcess.Interface/IOutOfProcessClientRpc.cs b/CefSharp.OutOfProcess.Interface/IOutOfProcessClientRpc.cs index ffa40e8..35b007f 100644 --- a/CefSharp.OutOfProcess.Interface/IOutOfProcessClientRpc.cs +++ b/CefSharp.OutOfProcess.Interface/IOutOfProcessClientRpc.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace CefSharp.OutOfProcess.Interface @@ -19,10 +19,14 @@ public interface IOutOfProcessClientRpc /// Send DevTools message /// /// browser Id - /// + /// devtools message (json) /// Task Task SendDevToolsMessage(int browserId, string message); + Task ShowDevTools(int browserId); + + Task LoadUrl(int browserId, string url); + /// /// Close the Browser Process (host) /// @@ -43,7 +47,8 @@ public interface IOutOfProcessClientRpc /// This will dismiss any existing popups (dropdowns). /// /// browser Id - void NotifyMoveOrResizeStarted(int browserId); + /// Position and size of the window. Only required in offscreen mode. + void NotifyMoveOrResizeStarted(int browserId, Rect rect = default); /// /// Set whether the browser is focused. @@ -51,5 +56,18 @@ public interface IOutOfProcessClientRpc /// browser id /// set focus void SetFocus(int browserId, bool focus); + + /// + /// Sends a mouse click to the client. + /// Custom implementation necessary because IDevToolsContext can't handle clicks on popups + /// + /// + /// + /// + /// + /// + /// + /// + void SendMouseClickEvent(int browserId, int x, int y, string mouseButtonType, bool mouseUp, int clickCount, uint eventFlags); } } diff --git a/CefSharp.OutOfProcess.Interface/IOutOfProcessHostRpc.cs b/CefSharp.OutOfProcess.Interface/IOutOfProcessHostRpc.cs index 2ff909e..f6f521c 100644 --- a/CefSharp.OutOfProcess.Interface/IOutOfProcessHostRpc.cs +++ b/CefSharp.OutOfProcess.Interface/IOutOfProcessHostRpc.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace CefSharp.OutOfProcess.Interface { @@ -78,5 +78,11 @@ public interface IOutOfProcessHostRpc /// Cef Version /// Chromium Version void NotifyContextInitialized(int threadId, string cefSharpVersion, string cefVersion, string chromiumVersion); + + void NotifyPaint(int browserId, bool isPopup, Rect dirtyRect, int width, int height, string file); + + void NotifyPopupShow(int browserId, bool show); + + void NotifyPopupSize(int browserId, Rect rect); } -} +} \ No newline at end of file diff --git a/CefSharp.OutOfProcess.Interface/Rect.cs b/CefSharp.OutOfProcess.Interface/Rect.cs new file mode 100644 index 0000000..cd48c0a --- /dev/null +++ b/CefSharp.OutOfProcess.Interface/Rect.cs @@ -0,0 +1,51 @@ +// Copyright © 2015 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.Diagnostics; + +namespace CefSharp.OutOfProcess.Interface +{ + /// + /// Represents a rectangle + /// + [DebuggerDisplay("X = {X}, Y = {Y}, Width = {Width}, Height = {Height}")] + public struct Rect + { + /// + /// X coordinate + /// + public int X { get; private set; } + + /// + /// Y coordinate + /// + public int Y { get; private set; } + + /// + /// Width + /// + public int Width { get; private set; } + + /// + /// Height + /// + public int Height { get; private set; } + + /// + /// Rect + /// + /// x coordinate + /// y coordinate + /// width + /// height + public Rect(int x, int y, int width, int height) + : this() + { + X = x; + Y = y; + Width = width; + Height = height; + } + } +} \ No newline at end of file diff --git a/CefSharp.OutOfProcess.WinForms.Example/CefSharp.OutOfProcess.WinForms.Example.csproj b/CefSharp.OutOfProcess.WinForms.Example/CefSharp.OutOfProcess.WinForms.Example.csproj index 98ae21f..c1c6697 100644 --- a/CefSharp.OutOfProcess.WinForms.Example/CefSharp.OutOfProcess.WinForms.Example.csproj +++ b/CefSharp.OutOfProcess.WinForms.Example/CefSharp.OutOfProcess.WinForms.Example.csproj @@ -1,4 +1,4 @@ - + WinExe diff --git a/CefSharp.OutOfProcess.WinForms.Example/Minimal/HostForm.cs b/CefSharp.OutOfProcess.WinForms.Example/Minimal/HostForm.cs index 23eb215..d99c774 100644 --- a/CefSharp.OutOfProcess.WinForms.Example/Minimal/HostForm.cs +++ b/CefSharp.OutOfProcess.WinForms.Example/Minimal/HostForm.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Windows.Forms; diff --git a/CefSharp.OutOfProcess.WinForms/CefSharp.OutOfProcess.WinForms.csproj b/CefSharp.OutOfProcess.WinForms/CefSharp.OutOfProcess.WinForms.csproj index fd55fc3..6fa0be9 100644 --- a/CefSharp.OutOfProcess.WinForms/CefSharp.OutOfProcess.WinForms.csproj +++ b/CefSharp.OutOfProcess.WinForms/CefSharp.OutOfProcess.WinForms.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1;net462 diff --git a/CefSharp.OutOfProcess.WinForms/ChromiumWebBrowser.cs b/CefSharp.OutOfProcess.WinForms/ChromiumWebBrowser.cs index a43100a..c9d5392 100644 --- a/CefSharp.OutOfProcess.WinForms/ChromiumWebBrowser.cs +++ b/CefSharp.OutOfProcess.WinForms/ChromiumWebBrowser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Drawing; using System.Threading.Tasks; using System.Windows.Forms; diff --git a/CefSharp.OutOfProcess.Wpf.HwndHost.Example/CefSharp.OutOfProcess.Wpf.HwndHost.Example.csproj b/CefSharp.OutOfProcess.Wpf.HwndHost.Example/CefSharp.OutOfProcess.Wpf.HwndHost.Example.csproj index 5b36601..9f4ad4c 100644 --- a/CefSharp.OutOfProcess.Wpf.HwndHost.Example/CefSharp.OutOfProcess.Wpf.HwndHost.Example.csproj +++ b/CefSharp.OutOfProcess.Wpf.HwndHost.Example/CefSharp.OutOfProcess.Wpf.HwndHost.Example.csproj @@ -5,6 +5,7 @@ netcoreapp3.1;net462 CefSharp.OutOfProcess.Wpf.HwndHost.Example CefSharp.OutOfProcess.Wpf.HwndHost.Example.App + true AnyCPU app.manifest diff --git a/CefSharp.OutOfProcess.Wpf.HwndHost/CefSharp.OutOfProcess.Wpf.HwndHost.csproj b/CefSharp.OutOfProcess.Wpf.HwndHost/CefSharp.OutOfProcess.Wpf.HwndHost.csproj index f686ad4..178a6c0 100644 --- a/CefSharp.OutOfProcess.Wpf.HwndHost/CefSharp.OutOfProcess.Wpf.HwndHost.csproj +++ b/CefSharp.OutOfProcess.Wpf.HwndHost/CefSharp.OutOfProcess.Wpf.HwndHost.csproj @@ -1,8 +1,9 @@ - + netcoreapp3.1;net462 true + AnyCPU diff --git a/CefSharp.OutOfProcess.Wpf.HwndHost/ChromiumWebBrowser.cs b/CefSharp.OutOfProcess.Wpf.HwndHost/ChromiumWebBrowser.cs index 93df485..8db5ac8 100644 --- a/CefSharp.OutOfProcess.Wpf.HwndHost/ChromiumWebBrowser.cs +++ b/CefSharp.OutOfProcess.Wpf.HwndHost/ChromiumWebBrowser.cs @@ -1,1271 +1,1271 @@ -// Copyright © 2022 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 CefSharp.OutOfProcess.Internal; -using CefSharp.OutOfProcess.WinForms; -using CefSharp.OutOfProcess.Wpf.HwndHost.Internals; -using CefSharp.Dom; -using PInvoke; -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Threading; -using Window = System.Windows.Window; - -namespace CefSharp.OutOfProcess.Wpf.HwndHost -{ - /// - /// ChromiumWebBrowser is the WPF web browser control - /// - /// - /// - /// based on https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/walkthrough-hosting-a-win32-control-in-wpf - /// and https://stackoverflow.com/questions/6500336/custom-dwm-drawn-window-frame-flickers-on-resizing-if-the-window-contains-a-hwnd/17471534#17471534 - public class ChromiumWebBrowser : System.Windows.Interop.HwndHost, IChromiumWebBrowserInternal - { - private const string BrowserNotInitializedExceptionErrorMessage = - "The ChromiumWebBrowser instance creates the underlying Chromium Embedded Framework (CEF) browser instance in an async fashion. " + - "The undelying CefBrowser instance is not yet initialized. Use the IsBrowserInitializedChanged event and check " + - "the IsBrowserInitialized property to determine when the browser has been initialized."; - - private const int WS_CHILD = 0x40000000, - WS_VISIBLE = 0x10000000, - LBS_NOTIFY = 0x00000001, - HOST_ID = 0x00000002, - LISTBOX_ID = 0x00000001, - WS_VSCROLL = 0x00200000, - WS_BORDER = 0x00800000, - WS_CLIPCHILDREN = 0x02000000; - - [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)] - private static extern IntPtr CreateWindowEx(int dwExStyle, - string lpszClassName, - string lpszWindowName, - int style, - int x, int y, - int width, int height, - IntPtr hwndParent, - IntPtr hMenu, - IntPtr hInst, - [MarshalAs(UnmanagedType.AsAny)] object pvParam); - - [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)] - private static extern bool DestroyWindow(IntPtr hwnd); - - [DllImport("user32.dll", EntryPoint = "GetWindowLong")] - private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int index); - - [DllImport("user32.dll", EntryPoint = "SetWindowLong")] - private static extern int SetWindowLong32(HandleRef hWnd, int index, int dwNewLong); - - [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] - private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int index, IntPtr dwNewLong); - - private OutOfProcessHost _host; - private IntPtr _browserHwnd = IntPtr.Zero; - private OutOfProcessConnectionTransport _devToolsContextConnectionTransport; - private IDevToolsContext _devToolsContext; - private int _id; - private bool _devToolsReady; - - /// - /// Handle we'll use to host the browser - /// - private IntPtr _hwndHost; - /// - /// The ignore URI change - /// - private bool _ignoreUriChange; - /// - /// Initial address - /// - private readonly string _initialAddress; - /// - /// Has the underlying Cef Browser been created (slightly different to initliazed in that - /// the browser is initialized in an async fashion) - /// - private bool _browserCreated; - /// - /// The browser initialized - boolean represented as 0 (false) and 1(true) as we use Interlocker to increment/reset - /// - private int _browserInitialized; - /// - /// A flag that indicates whether or not the designer is active - /// NOTE: Needs to be static for OnApplicationExit - /// - private static bool DesignMode; - - /// - /// The value for disposal, if it's 1 (one) then this instance is either disposed - /// or in the process of getting disposed - /// - private int _disposeSignaled; - - /// - /// Current DPI Scale - /// - private double _dpiScale; - - /// - /// The HwndSource RootVisual (Window) - We store a reference - /// to unsubscribe event handlers - /// - private Window _sourceWindow; - - /// - /// Store the previous window state, used to determine if the - /// Windows was previous - /// and resume rendering - /// - private WindowState _previousWindowState; - - /// - /// This flag is set when the browser gets focus before the underlying CEF browser - /// has been initialized. - /// - private bool _initialFocus; - - /// - /// Activates browser upon creation, the default value is false. Prior to version 73 - /// the default behaviour was to activate browser on creation (Equivilent of setting this property to true). - /// To restore this behaviour set this value to true immediately after you create the instance. - /// https://bitbucket.org/chromiumembedded/cef/issues/1856/branch-2526-cef-activates-browser-window - /// - public bool ActivateBrowserOnCreation { get; set; } - - /// - /// Gets a value indicating whether this instance is disposed. - /// - /// if this instance is disposed; otherwise, . - public bool IsDisposed - { - get - { - return Interlocked.CompareExchange(ref _disposeSignaled, 1, 1) == 1; - } - } - - /// - public event EventHandler DOMContentLoaded; - /// - public event EventHandler BrowserProcessCrashed; - /// - public event EventHandler FrameAttached; - /// - public event EventHandler FrameDetached; - /// - public event EventHandler FrameNavigated; - /// - public event EventHandler JavaScriptLoad; - /// - public event EventHandler RuntimeExceptionThrown; - /// - public event EventHandler Popup; - /// - public event EventHandler NetworkRequest; - /// - public event EventHandler NetworkRequestFailed; - /// - public event EventHandler NetworkRequestFinished; - /// - public event EventHandler NetworkRequestServedFromCache; - /// - public event EventHandler NetworkResponse; - /// - public event EventHandler AddressChanged; - /// - public event EventHandler LoadingStateChanged; - /// - public event EventHandler StatusMessage; - /// - public event EventHandler ConsoleMessage; - /// - public event EventHandler LifecycleEvent; - /// - public event EventHandler DevToolsContextAvailable; - - /// - /// Navigates to the previous page in the browser history. Will automatically be enabled/disabled depending on the - /// browser state. - /// - /// The back command. - public ICommand BackCommand { get; private set; } - /// - /// Navigates to the next page in the browser history. Will automatically be enabled/disabled depending on the - /// browser state. - /// - /// The forward command. - public ICommand ForwardCommand { get; private set; } - /// - /// Reloads the content of the current page. Will automatically be enabled/disabled depending on the browser state. - /// - /// The reload command. - public ICommand ReloadCommand { get; private set; } - /// - /// Prints the current browser contents. - /// - /// The print command. - public ICommand PrintCommand { get; private set; } - /// - /// Increases the zoom level. - /// - /// The zoom in command. - public ICommand ZoomInCommand { get; private set; } - /// - /// Decreases the zoom level. - /// - /// The zoom out command. - public ICommand ZoomOutCommand { get; private set; } - /// - /// Resets the zoom level to the default. (100%) - /// - /// The zoom reset command. - public ICommand ZoomResetCommand { get; private set; } - /// - /// Opens up a new program window (using the default text editor) where the source code of the currently displayed web - /// page is shown. - /// - /// The view source command. - public ICommand ViewSourceCommand { get; private set; } - /// - /// Command which cleans up the Resources used by the ChromiumWebBrowser - /// - /// The cleanup command. - public ICommand CleanupCommand { get; private set; } - /// - /// Stops loading the current page. - /// - /// The stop command. - public ICommand StopCommand { get; private set; } - /// - /// Cut selected text to the clipboard. - /// - /// The cut command. - public ICommand CutCommand { get; private set; } - /// - /// Copy selected text to the clipboard. - /// - /// The copy command. - public ICommand CopyCommand { get; private set; } - /// - /// Paste text from the clipboard. - /// - /// The paste command. - public ICommand PasteCommand { get; private set; } - /// - /// Select all text. - /// - /// The select all command. - public ICommand SelectAllCommand { get; private set; } - /// - /// Undo last action. - /// - /// The undo command. - public ICommand UndoCommand { get; private set; } - /// - /// Redo last action. - /// - /// The redo command. - public ICommand RedoCommand { get; private set; } - - /// - /// Initializes a new instance of the instance. - /// - /// Out of process host - /// address to load initially - public ChromiumWebBrowser(OutOfProcessHost host, string initialAddress = null) - { - if(host == null) - { - throw new ArgumentNullException(nameof(host)); - } - - _host = host; - _initialAddress = initialAddress; - - Focusable = true; - FocusVisualStyle = null; - - WebBrowser = this; - - SizeChanged += OnSizeChanged; - IsVisibleChanged += OnIsVisibleChanged; - - BackCommand = new DelegateCommand(() => _devToolsContext.GoBackAsync(), () => CanGoBack); - ForwardCommand = new DelegateCommand(() => _devToolsContext.GoForwardAsync(), () => CanGoForward); - ReloadCommand = new DelegateCommand(() => _devToolsContext.ReloadAsync(), () => !IsLoading); - //PrintCommand = new DelegateCommand(this.Print); - //ZoomInCommand = new DelegateCommand(ZoomIn); - //ZoomOutCommand = new DelegateCommand(ZoomOut); - //ZoomResetCommand = new DelegateCommand(ZoomReset); - //ViewSourceCommand = new DelegateCommand(this.ViewSource); - CleanupCommand = new DelegateCommand(Dispose); - //StopCommand = new DelegateCommand(this.Stop); - //CutCommand = new DelegateCommand(this.Cut); - //CopyCommand = new DelegateCommand(this.Copy); - //PasteCommand = new DelegateCommand(this.Paste); - //SelectAllCommand = new DelegateCommand(this.SelectAll); - //UndoCommand = new DelegateCommand(this.Undo); - //RedoCommand = new DelegateCommand(this.Redo); - - PresentationSource.AddSourceChangedHandler(this, PresentationSourceChangedHandler); - - UseLayoutRounding = true; - } - - /// - int IChromiumWebBrowserInternal.Id - { - get { return _id; } - } - - /// - /// DevToolsContext - provides communication with the underlying browser - /// - public IDevToolsContext DevToolsContext - { - get - { - if (_devToolsReady) - { - return _devToolsContext; - } - - return default; - } - } - - /// - public bool IsBrowserInitialized => _browserHwnd != IntPtr.Zero; - - - /// - public Frame[] Frames => _devToolsContext == null ? null : _devToolsContext.Frames; - - /// - public Frame MainFrame => _devToolsContext == null ? null : _devToolsContext.MainFrame; - - /// - void IChromiumWebBrowserInternal.OnDevToolsMessage(string jsonMsg) - { - _devToolsContextConnectionTransport?.InvokeMessageReceived(jsonMsg); - } - - /// - void IChromiumWebBrowserInternal.OnDevToolsReady() - { - var ctx = (DevToolsContext)_devToolsContext; - - ctx.DOMContentLoaded += DOMContentLoaded; - ctx.Error += BrowserProcessCrashed; - ctx.FrameAttached += FrameAttached; - ctx.FrameDetached += FrameDetached; - ctx.FrameNavigated += FrameNavigated; - ctx.Load += JavaScriptLoad; - ctx.PageError += RuntimeExceptionThrown; - ctx.Popup += Popup; - ctx.Request += NetworkRequest; - ctx.RequestFailed += NetworkRequestFailed; - ctx.RequestFinished += NetworkRequestFinished; - ctx.RequestServedFromCache += NetworkRequestServedFromCache; - ctx.Response += NetworkResponse; - ctx.Console += ConsoleMessage; - ctx.LifecycleEvent += LifecycleEvent; - - _ = ctx.InvokeGetFrameTreeAsync().ContinueWith(t => - { - _devToolsReady = true; - - DevToolsContextAvailable?.Invoke(this, EventArgs.Empty); - - //NOW the user can start using the devtools context - }, TaskScheduler.Current); - - // Only call Load if initialAddress is null and Address is not empty - if (string.IsNullOrEmpty(_initialAddress) && !string.IsNullOrEmpty(Address)) - { - LoadUrl(Address); - } - } - - /// - public void LoadUrl(string url) - { - _ = _devToolsContext.GoToAsync(url); - } - - /// - public Task LoadUrlAsync(string url, int? timeout = null, WaitUntilNavigation[] waitUntil = null) - { - return _devToolsContext.GoToAsync(url, timeout, waitUntil); - } - - /// - public Task GoBackAsync(NavigationOptions options = null) - { - return _devToolsContext.GoBackAsync(options); - } - - /// - public Task GoForwardAsync(NavigationOptions options = null) - { - return _devToolsContext.GoForwardAsync(options); - } - - private void PresentationSourceChangedHandler(object sender, SourceChangedEventArgs args) - { - if (args.NewSource != null) - { - var source = (HwndSource)args.NewSource; - - var matrix = source.CompositionTarget.TransformToDevice; - - _dpiScale = matrix.M11; - - var window = source.RootVisual as Window; - if (window != null) - { - window.StateChanged += OnWindowStateChanged; - window.LocationChanged += OnWindowLocationChanged; - _sourceWindow = window; - - if (CleanupElement == null) - { - CleanupElement = window; - } - else if (CleanupElement is Window parent) - { - //If the CleanupElement is a window then move it to the new Window - if (parent != window) - { - CleanupElement = window; - } - } - } - } - else if (args.OldSource != null) - { - var window = args.OldSource.RootVisual as Window; - if (window != null) - { - window.StateChanged -= OnWindowStateChanged; - window.LocationChanged -= OnWindowLocationChanged; - _sourceWindow = null; - } - } - } - - /// - protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi) - { - _dpiScale = newDpi.DpiScaleX; - - //If the DPI changed then we need to resize. - ResizeBrowser((int)ActualWidth, (int)ActualHeight); - - base.OnDpiChanged(oldDpi, newDpi); - } - - private void OnSizeChanged(object sender, SizeChangedEventArgs e) - { - ResizeBrowser((int)e.NewSize.Width, (int)e.NewSize.Height); - } - - /// - protected override HandleRef BuildWindowCore(HandleRef hwndParent) - { - if (_hwndHost == IntPtr.Zero) - { - _hwndHost = CreateWindowEx(0, "static", "", - WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, - 0, 0, - (int)ActualWidth, (int)ActualHeight, - hwndParent.Handle, - (IntPtr)HOST_ID, - IntPtr.Zero, - 0); - } - - _host.CreateBrowser(this, _hwndHost, url: _initialAddress, out _id); - - _devToolsContextConnectionTransport = new OutOfProcessConnectionTransport(_id, _host); - - var connection = DevToolsConnection.Attach(_devToolsContextConnectionTransport); - _devToolsContext = Dom.DevToolsContext.CreateForOutOfProcess(connection); - - return new HandleRef(null, _hwndHost); - } - - /// - protected override void DestroyWindowCore(HandleRef hwnd) - { - DestroyWindow(hwnd.Handle); - } - - /// - protected override bool TabIntoCore(TraversalRequest request) - { - if(InternalIsBrowserInitialized()) - { - _host.SetFocus(_id, true); - - return true; - } - - return base.TabIntoCore(request); - } - - /// - protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) - { - if(!e.Handled) - { - if (InternalIsBrowserInitialized()) - { - _host.SetFocus(_id, true); - } - else - { - _initialFocus = true; - } - } - - base.OnGotKeyboardFocus(e); - } - - /// - protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) - { - if (!e.Handled) - { - if (InternalIsBrowserInitialized()) - { - _host.SetFocus(_id, false); - } - else - { - _initialFocus = false; - } - } - - base.OnLostKeyboardFocus(e); - } - - - /// - protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - const int WM_SETFOCUS = 0x0007; - const int WM_MOUSEACTIVATE = 0x0021; - switch (msg) - { - case WM_SETFOCUS: - case WM_MOUSEACTIVATE: - { - if(InternalIsBrowserInitialized()) - { - - _host.SetFocus(_id, true); - - handled = true; - - return IntPtr.Zero; - } - break; - } - } - return base.WndProc(hwnd, msg, wParam, lParam, ref handled); - } - - /// - /// If not in design mode; Releases unmanaged and - optionally - managed resources for the - /// - /// to release both managed and unmanaged resources; to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - // Attempt to move the disposeSignaled state from 0 to 1. If successful, we can be assured that - // this thread is the first thread to do so, and can safely dispose of the object. - if (Interlocked.CompareExchange(ref _disposeSignaled, 1, 0) != 0) - { - return; - } - - if (!DesignMode) - { - InternalDispose(disposing); - } - - base.Dispose(disposing); - } - - /// - /// Releases unmanaged and - optionally - managed resources for the - /// - /// to release both managed and unmanaged resources; to release only unmanaged resources. - /// - /// This method cannot be inlined as the designer will attempt to load libcef.dll and will subsiquently throw an exception. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private void InternalDispose(bool disposing) - { - Interlocked.Exchange(ref _browserInitialized, 0); - - if (disposing) - { - SizeChanged -= OnSizeChanged; - IsVisibleChanged -= OnIsVisibleChanged; - - PresentationSource.RemoveSourceChangedHandler(this, PresentationSourceChangedHandler); - // Release window event listeners if PresentationSourceChangedHandler event wasn't - // fired before Dispose - if (_sourceWindow != null) - { - _sourceWindow.StateChanged -= OnWindowStateChanged; - _sourceWindow.LocationChanged -= OnWindowLocationChanged; - _sourceWindow = null; - } - - - UiThreadRunAsync(() => - { - OnIsBrowserInitializedChanged(true, false); - - //To Minic the WPF behaviour this happens after OnIsBrowserInitializedChanged - IsBrowserInitializedChanged?.Invoke(this, EventArgs.Empty); - - WebBrowser = null; - }); - - // Don't maintain a reference to event listeners anylonger: - //ConsoleMessage = null; - //FrameLoadEnd = null; - //FrameLoadStart = null; - IsBrowserInitializedChanged = null; - //LoadError = null; - LoadingStateChanged = null; - StatusMessage = null; - TitleChanged = null; - - if (CleanupElement != null) - { - CleanupElement.Unloaded -= OnCleanupElementUnloaded; - } - } - } - - /// - void IChromiumWebBrowserInternal.SetAddress(string address) - { - UiThreadRunAsync(() => - { - _ignoreUriChange = true; - SetCurrentValue(AddressProperty, address); - _ignoreUriChange = false; - - // The tooltip should obviously also be reset (and hidden) when the address changes. - SetCurrentValue(TooltipTextProperty, null); - }); - } - - /// - void IChromiumWebBrowserInternal.SetLoadingStateChange(bool canGoBack, bool canGoForward, bool isLoading) - { - UiThreadRunAsync(() => - { - SetCurrentValue(CanGoBackProperty, canGoBack); - SetCurrentValue(CanGoForwardProperty, canGoForward); - SetCurrentValue(IsLoadingProperty, isLoading); - - ((DelegateCommand)BackCommand).RaiseCanExecuteChanged(); - ((DelegateCommand)ForwardCommand).RaiseCanExecuteChanged(); - ((DelegateCommand)ReloadCommand).RaiseCanExecuteChanged(); - }); - - LoadingStateChanged?.Invoke(this, new LoadingStateChangedEventArgs(canGoBack, canGoForward, isLoading)); - } - - /// - void IChromiumWebBrowserInternal.SetTitle(string title) - { - UiThreadRunAsync(() => SetCurrentValue(TitleProperty, title)); - } - - /// - /// Sets the tooltip text. - /// - /// The tooltip text. - //void IWebBrowserInternal.SetTooltipText(string tooltipText) - //{ - // UiThreadRunAsync(() => SetCurrentValue(TooltipTextProperty, tooltipText)); - //} - - /// - /// Handles the event. - /// - /// The instance containing the event data. - //void IWebBrowserInternal.OnConsoleMessage(ConsoleMessageEventArgs args) - //{ - // ConsoleMessage?.Invoke(this, args); - //} - - /// - void IChromiumWebBrowserInternal.SetStatusMessage(string msg) - { - StatusMessage?.Invoke(this, new StatusMessageEventArgs(msg)); - } - - /// - void IChromiumWebBrowserInternal.OnAfterBrowserCreated(IntPtr hwnd) - { - if (IsDisposed) - { - return; - } - - _browserHwnd = hwnd; - - Interlocked.Exchange(ref _browserInitialized, 1); - - UiThreadRunAsync(() => - { - if (!IsDisposed) - { - OnIsBrowserInitializedChanged(false, true); - //To Minic the WPF behaviour this happens after OnIsBrowserInitializedChanged - IsBrowserInitializedChanged?.Invoke(this, EventArgs.Empty); - } - }); - - ResizeBrowser((int)ActualWidth, (int)ActualHeight); - - if (_initialFocus) - { - _host.SetFocus(_id, true); - } - } - - /// - /// A flag that indicates whether the state of the control current supports the GoBack action (true) or not (false). - /// - /// true if this instance can go back; otherwise, false. - /// In the WPF control, this property is implemented as a Dependency Property and fully supports data - /// binding. - public bool CanGoBack - { - get { return (bool)GetValue(CanGoBackProperty); } - } - - /// - /// The can go back property - /// - public static DependencyProperty CanGoBackProperty = DependencyProperty.Register(nameof(CanGoBack), typeof(bool), typeof(ChromiumWebBrowser)); - - /// - /// A flag that indicates whether the state of the control currently supports the GoForward action (true) or not (false). - /// - /// true if this instance can go forward; otherwise, false. - /// In the WPF control, this property is implemented as a Dependency Property and fully supports data - /// binding. - public bool CanGoForward - { - get { return (bool)GetValue(CanGoForwardProperty); } - } - - /// - /// The can go forward property - /// - public static DependencyProperty CanGoForwardProperty = DependencyProperty.Register(nameof(CanGoForward), typeof(bool), typeof(ChromiumWebBrowser)); - - /// - /// The address (URL) which the browser control is currently displaying. - /// Will automatically be updated as the user navigates to another page (e.g. by clicking on a link). - /// - /// The address. - /// In the WPF control, this property is implemented as a Dependency Property and fully supports data - /// binding. - public string Address - { - get { return (string)GetValue(AddressProperty); } - set { SetValue(AddressProperty, value); } - } - - /// - /// The address property - /// - public static readonly DependencyProperty AddressProperty = - DependencyProperty.Register(nameof(Address), typeof(string), typeof(ChromiumWebBrowser), - new UIPropertyMetadata(null, OnAddressChanged)); - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private static void OnAddressChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) - { - var owner = (ChromiumWebBrowser)sender; - var oldValue = (string)args.OldValue; - var newValue = (string)args.NewValue; - - owner.OnAddressChanged(oldValue, newValue); - } - - /// - /// Called when [address changed]. - /// - /// The old value. - /// The new value. - protected virtual void OnAddressChanged(string oldValue, string newValue) - { - if (_ignoreUriChange || newValue == null || !InternalIsBrowserInitialized()) - { - return; - } - - LoadUrl(newValue); - } - - /// - /// A flag that indicates whether the control is currently loading one or more web pages (true) or not (false). - /// - /// true if this instance is loading; otherwise, false. - /// In the WPF control, this property is implemented as a Dependency Property and fully supports data - /// binding. - public bool IsLoading - { - get { return (bool)GetValue(IsLoadingProperty); } - } - - /// - /// The is loading property - /// - public static readonly DependencyProperty IsLoadingProperty = - DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(ChromiumWebBrowser), new PropertyMetadata(false)); - - /// - /// Event called after the underlying CEF browser instance has been created and - /// when the instance has been Disposed. - /// will be true when the underlying CEF Browser - /// has been created and false when the browser is being Disposed. - /// - public event EventHandler IsBrowserInitializedChanged; - - /// - /// Called when [is browser initialized changed]. - /// - /// if set to true [old value]. - /// if set to true [new value]. - protected virtual void OnIsBrowserInitializedChanged(bool oldValue, bool newValue) - { - if (newValue && !IsDisposed) - { - //var task = this.GetZoomLevelAsync(); - //task.ContinueWith(previous => - //{ - // if (previous.Status == TaskStatus.RanToCompletion) - // { - // UiThreadRunAsync(() => - // { - // if (!IsDisposed) - // { - // SetCurrentValue(ZoomLevelProperty, previous.Result); - // } - // }); - // } - // else - // { - // throw new InvalidOperationException("Unexpected failure of calling CEF->GetZoomLevelAsync", previous.Exception); - // } - //}, TaskContinuationOptions.ExecuteSynchronously); - } - } - - /// - /// The title of the web page being currently displayed. - /// - /// The title. - /// This property is implemented as a Dependency Property and fully supports data binding. - public string Title - { - get { return (string)GetValue(TitleProperty); } - set { SetValue(TitleProperty, value); } - } - - /// - /// The title property - /// - public static readonly DependencyProperty TitleProperty = - DependencyProperty.Register(nameof(Title), typeof(string), typeof(ChromiumWebBrowser), new PropertyMetadata(null, OnTitleChanged)); - - /// - /// Event handler that will get called when the browser title changes - /// - public event DependencyPropertyChangedEventHandler TitleChanged; - - /// - /// Handles the event. - /// - /// The d. - /// The instance containing the event data. - private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var owner = (ChromiumWebBrowser)d; - - owner.TitleChanged?.Invoke(owner, e); - } - - /// - /// The zoom level at which the browser control is currently displaying. - /// Can be set to 0 to clear the zoom level (resets to default zoom level). - /// - /// The zoom level. - public double ZoomLevel - { - get { return (double)GetValue(ZoomLevelProperty); } - set { SetValue(ZoomLevelProperty, value); } - } - - /// - /// The zoom level property - /// - public static readonly DependencyProperty ZoomLevelProperty = - DependencyProperty.Register(nameof(ZoomLevel), typeof(double), typeof(ChromiumWebBrowser), - new UIPropertyMetadata(0d, OnZoomLevelChanged)); - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private static void OnZoomLevelChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) - { - var owner = (ChromiumWebBrowser)sender; - var oldValue = (double)args.OldValue; - var newValue = (double)args.NewValue; - - owner.OnZoomLevelChanged(oldValue, newValue); - } - - /// - /// Called when [zoom level changed]. - /// - /// The old value. - /// The new value. - protected virtual void OnZoomLevelChanged(double oldValue, double newValue) - { - throw new NotImplementedException(); - } - - /// - /// Specifies the amount used to increase/decrease to ZoomLevel by - /// By Default this value is 0.10 - /// - /// The zoom level increment. - public double ZoomLevelIncrement - { - get { return (double)GetValue(ZoomLevelIncrementProperty); } - set { SetValue(ZoomLevelIncrementProperty, value); } - } - - /// - /// The zoom level increment property - /// - public static readonly DependencyProperty ZoomLevelIncrementProperty = - DependencyProperty.Register(nameof(ZoomLevelIncrement), typeof(double), typeof(ChromiumWebBrowser), new PropertyMetadata(0.10)); - - /// - /// The CleanupElement controls when the Browser will be Disposed. - /// The will be Disposed when is called. - /// Be aware that this Control is not usable anymore after it has been disposed. - /// - /// The cleanup element. - public FrameworkElement CleanupElement - { - get { return (FrameworkElement)GetValue(CleanupElementProperty); } - set { SetValue(CleanupElementProperty, value); } - } - - /// - /// The cleanup element property - /// - public static readonly DependencyProperty CleanupElementProperty = - DependencyProperty.Register(nameof(CleanupElement), typeof(FrameworkElement), typeof(ChromiumWebBrowser), new PropertyMetadata(null, OnCleanupElementChanged)); - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private static void OnCleanupElementChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) - { - var owner = (ChromiumWebBrowser)sender; - var oldValue = (FrameworkElement)args.OldValue; - var newValue = (FrameworkElement)args.NewValue; - - owner.OnCleanupElementChanged(oldValue, newValue); - } - - /// - /// Called when [cleanup element changed]. - /// - /// The old value. - /// The new value. - protected virtual void OnCleanupElementChanged(FrameworkElement oldValue, FrameworkElement newValue) - { - if (oldValue != null) - { - oldValue.Unloaded -= OnCleanupElementUnloaded; - } - - if (newValue != null) - { - newValue.Unloaded += OnCleanupElementUnloaded; - } - } - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private void OnCleanupElementUnloaded(object sender, RoutedEventArgs e) - { - Dispose(); - } - - /// - /// The text that will be displayed as a ToolTip - /// - /// The tooltip text. - public string TooltipText - { - get { return (string)GetValue(TooltipTextProperty); } - } - - /// - /// The tooltip text property - /// - public static readonly DependencyProperty TooltipTextProperty = - DependencyProperty.Register(nameof(TooltipText), typeof(string), typeof(ChromiumWebBrowser)); - - /// - /// Gets or sets the WebBrowser. - /// - /// The WebBrowser. - public IChromiumWebBrowser WebBrowser - { - get { return (IChromiumWebBrowser)GetValue(WebBrowserProperty); } - set { SetValue(WebBrowserProperty, value); } - } - - /// - /// The WebBrowser property - /// - public static readonly DependencyProperty WebBrowserProperty = - DependencyProperty.Register(nameof(WebBrowser), typeof(IChromiumWebBrowser), typeof(ChromiumWebBrowser), new UIPropertyMetadata(defaultValue: null)); - - /// - /// Runs the specific Action on the Dispatcher in an async fashion - /// - /// The action. - /// The priority. - private void UiThreadRunAsync(Action action, DispatcherPriority priority = DispatcherPriority.DataBind) - { - if (Dispatcher.CheckAccess()) - { - action(); - } - else if (!Dispatcher.HasShutdownStarted) - { - _ = Dispatcher.InvokeAsync(action, priority); - } - } - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs args) - { - var isVisible = (bool)args.NewValue; - - if (InternalIsBrowserInitialized()) - { - if (isVisible) - { - ResizeBrowser((int)ActualWidth, (int)ActualHeight); - } - else - { - //Hide browser - ResizeBrowser(0, 0); - } - } - } - - /// - /// Zooms the browser in. - /// - private void ZoomIn() - { - UiThreadRunAsync(() => - { - ZoomLevel = ZoomLevel + ZoomLevelIncrement; - }); - } - - /// - /// Zooms the browser out. - /// - private void ZoomOut() - { - UiThreadRunAsync(() => - { - ZoomLevel = ZoomLevel - ZoomLevelIncrement; - }); - } - - /// - /// Reset the browser's zoom level to default. - /// - private void ZoomReset() - { - UiThreadRunAsync(() => - { - ZoomLevel = 0; - }); - } - - /// - /// Check is browserisinitialized - /// - /// true if browser is initialized - private bool InternalIsBrowserInitialized() - { - // Use CompareExchange to read the current value - if disposeCount is 1, we set it to 1, effectively a no-op - // Volatile.Read would likely use a memory barrier which I believe is unnecessary in this scenario - return Interlocked.CompareExchange(ref _browserInitialized, 0, 0) == 1; - } - - /// - /// Resizes the browser to the specified and . - /// If and are both 0 then the browser - /// will be hidden and resource usage will be minimised. - /// - /// width - /// height - protected virtual void ResizeBrowser(int width, int height) - { - if (_browserHwnd != IntPtr.Zero) - { - if (_dpiScale > 1) - { - width = (int)(width * _dpiScale); - height = (int)(height * _dpiScale); - } - - if (width == 0 && height == 0) - { - // For windowed browsers when the frame window is minimized set the - // browser window size to 0x0 to reduce resource usage. - HideInternal(); - } - else - { - ShowInternal(width, height); - } - } - } - - /// - /// When minimized set the browser window size to 0x0 to reduce resource usage. - /// https://github.com/chromiumembedded/cef/blob/c7701b8a6168f105f2c2d6b239ce3958da3e3f13/tests/cefclient/browser/browser_window_std_win.cc#L87 - /// - internal virtual void HideInternal() - { - if (_browserHwnd != IntPtr.Zero) - { - User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, 0, 0, User32.SetWindowPosFlags.SWP_NOZORDER | User32.SetWindowPosFlags.SWP_NOMOVE | User32.SetWindowPosFlags.SWP_NOACTIVATE); - } - } - - /// - /// Show the browser (called after previous minimised) - /// - internal virtual void ShowInternal(int width, int height) - { - if (_browserHwnd != IntPtr.Zero) - { - User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, width, height, User32.SetWindowPosFlags.SWP_NOZORDER); - } - } - - private void OnWindowStateChanged(object sender, EventArgs e) - { - var window = (Window)sender; - - switch (window.WindowState) - { - case WindowState.Normal: - case WindowState.Maximized: - { - if (_previousWindowState == WindowState.Minimized && InternalIsBrowserInitialized()) - { - ResizeBrowser((int)ActualWidth, (int)ActualHeight); - } - break; - } - case WindowState.Minimized: - { - if (InternalIsBrowserInitialized()) - { - //Set the browser size to 0,0 to reduce CPU usage - ResizeBrowser(0, 0); - } - break; - } - } - - _previousWindowState = window.WindowState; - } - - private void OnWindowLocationChanged(object sender, EventArgs e) - { - if (InternalIsBrowserInitialized()) - { - _host.NotifyMoveOrResizeStarted(_id); - } - } - - /// - /// Throw exception if browser not initialized. - /// - /// Thrown when an exception error condition occurs. - private void ThrowExceptionIfBrowserNotInitialized() - { - if (!InternalIsBrowserInitialized()) - { - throw new Exception(BrowserNotInitializedExceptionErrorMessage); - } - } - - /// - /// Throw exception if disposed. - /// - /// Thrown when a supplied object has been disposed. - private void ThrowExceptionIfDisposed() - { - if (IsDisposed) - { - throw new ObjectDisposedException("browser", "Browser has been disposed"); - } - } - } -} +// Copyright © 2022 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 CefSharp.OutOfProcess.Internal; +using CefSharp.OutOfProcess.WinForms; +using CefSharp.OutOfProcess.Wpf.HwndHost.Internals; +using CefSharp.Dom; +using PInvoke; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Threading; +using Window = System.Windows.Window; + +namespace CefSharp.OutOfProcess.Wpf.HwndHost +{ + /// + /// ChromiumWebBrowser is the WPF web browser control + /// + /// + /// + /// based on https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/walkthrough-hosting-a-win32-control-in-wpf + /// and https://stackoverflow.com/questions/6500336/custom-dwm-drawn-window-frame-flickers-on-resizing-if-the-window-contains-a-hwnd/17471534#17471534 + public class ChromiumWebBrowser : System.Windows.Interop.HwndHost, IChromiumWebBrowserInternal + { + private const string BrowserNotInitializedExceptionErrorMessage = + "The ChromiumWebBrowser instance creates the underlying Chromium Embedded Framework (CEF) browser instance in an async fashion. " + + "The undelying CefBrowser instance is not yet initialized. Use the IsBrowserInitializedChanged event and check " + + "the IsBrowserInitialized property to determine when the browser has been initialized."; + + private const int WS_CHILD = 0x40000000, + WS_VISIBLE = 0x10000000, + LBS_NOTIFY = 0x00000001, + HOST_ID = 0x00000002, + LISTBOX_ID = 0x00000001, + WS_VSCROLL = 0x00200000, + WS_BORDER = 0x00800000, + WS_CLIPCHILDREN = 0x02000000; + + [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)] + private static extern IntPtr CreateWindowEx(int dwExStyle, + string lpszClassName, + string lpszWindowName, + int style, + int x, int y, + int width, int height, + IntPtr hwndParent, + IntPtr hMenu, + IntPtr hInst, + [MarshalAs(UnmanagedType.AsAny)] object pvParam); + + [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)] + private static extern bool DestroyWindow(IntPtr hwnd); + + [DllImport("user32.dll", EntryPoint = "GetWindowLong")] + private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int index); + + [DllImport("user32.dll", EntryPoint = "SetWindowLong")] + private static extern int SetWindowLong32(HandleRef hWnd, int index, int dwNewLong); + + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] + private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int index, IntPtr dwNewLong); + + private OutOfProcessHost _host; + private IntPtr _browserHwnd = IntPtr.Zero; + private OutOfProcessConnectionTransport _devToolsContextConnectionTransport; + private IDevToolsContext _devToolsContext; + private int _id; + private bool _devToolsReady; + + /// + /// Handle we'll use to host the browser + /// + private IntPtr _hwndHost; + /// + /// The ignore URI change + /// + private bool _ignoreUriChange; + /// + /// Initial address + /// + private readonly string _initialAddress; + /// + /// Has the underlying Cef Browser been created (slightly different to initliazed in that + /// the browser is initialized in an async fashion) + /// + private bool _browserCreated; + /// + /// The browser initialized - boolean represented as 0 (false) and 1(true) as we use Interlocker to increment/reset + /// + private int _browserInitialized; + /// + /// A flag that indicates whether or not the designer is active + /// NOTE: Needs to be static for OnApplicationExit + /// + private static bool DesignMode; + + /// + /// The value for disposal, if it's 1 (one) then this instance is either disposed + /// or in the process of getting disposed + /// + private int _disposeSignaled; + + /// + /// Current DPI Scale + /// + private double _dpiScale; + + /// + /// The HwndSource RootVisual (Window) - We store a reference + /// to unsubscribe event handlers + /// + private Window _sourceWindow; + + /// + /// Store the previous window state, used to determine if the + /// Windows was previous + /// and resume rendering + /// + private WindowState _previousWindowState; + + /// + /// This flag is set when the browser gets focus before the underlying CEF browser + /// has been initialized. + /// + private bool _initialFocus; + + /// + /// Activates browser upon creation, the default value is false. Prior to version 73 + /// the default behaviour was to activate browser on creation (Equivilent of setting this property to true). + /// To restore this behaviour set this value to true immediately after you create the instance. + /// https://bitbucket.org/chromiumembedded/cef/issues/1856/branch-2526-cef-activates-browser-window + /// + public bool ActivateBrowserOnCreation { get; set; } + + /// + /// Gets a value indicating whether this instance is disposed. + /// + /// if this instance is disposed; otherwise, . + public bool IsDisposed + { + get + { + return Interlocked.CompareExchange(ref _disposeSignaled, 1, 1) == 1; + } + } + + /// + public event EventHandler DOMContentLoaded; + /// + public event EventHandler BrowserProcessCrashed; + /// + public event EventHandler FrameAttached; + /// + public event EventHandler FrameDetached; + /// + public event EventHandler FrameNavigated; + /// + public event EventHandler JavaScriptLoad; + /// + public event EventHandler RuntimeExceptionThrown; + /// + public event EventHandler Popup; + /// + public event EventHandler NetworkRequest; + /// + public event EventHandler NetworkRequestFailed; + /// + public event EventHandler NetworkRequestFinished; + /// + public event EventHandler NetworkRequestServedFromCache; + /// + public event EventHandler NetworkResponse; + /// + public event EventHandler AddressChanged; + /// + public event EventHandler LoadingStateChanged; + /// + public event EventHandler StatusMessage; + /// + public event EventHandler ConsoleMessage; + /// + public event EventHandler LifecycleEvent; + /// + public event EventHandler DevToolsContextAvailable; + + /// + /// Navigates to the previous page in the browser history. Will automatically be enabled/disabled depending on the + /// browser state. + /// + /// The back command. + public ICommand BackCommand { get; private set; } + /// + /// Navigates to the next page in the browser history. Will automatically be enabled/disabled depending on the + /// browser state. + /// + /// The forward command. + public ICommand ForwardCommand { get; private set; } + /// + /// Reloads the content of the current page. Will automatically be enabled/disabled depending on the browser state. + /// + /// The reload command. + public ICommand ReloadCommand { get; private set; } + /// + /// Prints the current browser contents. + /// + /// The print command. + public ICommand PrintCommand { get; private set; } + /// + /// Increases the zoom level. + /// + /// The zoom in command. + public ICommand ZoomInCommand { get; private set; } + /// + /// Decreases the zoom level. + /// + /// The zoom out command. + public ICommand ZoomOutCommand { get; private set; } + /// + /// Resets the zoom level to the default. (100%) + /// + /// The zoom reset command. + public ICommand ZoomResetCommand { get; private set; } + /// + /// Opens up a new program window (using the default text editor) where the source code of the currently displayed web + /// page is shown. + /// + /// The view source command. + public ICommand ViewSourceCommand { get; private set; } + /// + /// Command which cleans up the Resources used by the ChromiumWebBrowser + /// + /// The cleanup command. + public ICommand CleanupCommand { get; private set; } + /// + /// Stops loading the current page. + /// + /// The stop command. + public ICommand StopCommand { get; private set; } + /// + /// Cut selected text to the clipboard. + /// + /// The cut command. + public ICommand CutCommand { get; private set; } + /// + /// Copy selected text to the clipboard. + /// + /// The copy command. + public ICommand CopyCommand { get; private set; } + /// + /// Paste text from the clipboard. + /// + /// The paste command. + public ICommand PasteCommand { get; private set; } + /// + /// Select all text. + /// + /// The select all command. + public ICommand SelectAllCommand { get; private set; } + /// + /// Undo last action. + /// + /// The undo command. + public ICommand UndoCommand { get; private set; } + /// + /// Redo last action. + /// + /// The redo command. + public ICommand RedoCommand { get; private set; } + + /// + /// Initializes a new instance of the instance. + /// + /// Out of process host + /// address to load initially + public ChromiumWebBrowser(OutOfProcessHost host, string initialAddress = null) + { + if(host == null) + { + throw new ArgumentNullException(nameof(host)); + } + + _host = host; + _initialAddress = initialAddress; + + Focusable = true; + FocusVisualStyle = null; + + WebBrowser = this; + + SizeChanged += OnSizeChanged; + IsVisibleChanged += OnIsVisibleChanged; + + BackCommand = new DelegateCommand(() => _devToolsContext.GoBackAsync(), () => CanGoBack); + ForwardCommand = new DelegateCommand(() => _devToolsContext.GoForwardAsync(), () => CanGoForward); + ReloadCommand = new DelegateCommand(() => _devToolsContext.ReloadAsync(), () => !IsLoading); + //PrintCommand = new DelegateCommand(this.Print); + //ZoomInCommand = new DelegateCommand(ZoomIn); + //ZoomOutCommand = new DelegateCommand(ZoomOut); + //ZoomResetCommand = new DelegateCommand(ZoomReset); + //ViewSourceCommand = new DelegateCommand(this.ViewSource); + CleanupCommand = new DelegateCommand(Dispose); + //StopCommand = new DelegateCommand(this.Stop); + //CutCommand = new DelegateCommand(this.Cut); + //CopyCommand = new DelegateCommand(this.Copy); + //PasteCommand = new DelegateCommand(this.Paste); + //SelectAllCommand = new DelegateCommand(this.SelectAll); + //UndoCommand = new DelegateCommand(this.Undo); + //RedoCommand = new DelegateCommand(this.Redo); + + PresentationSource.AddSourceChangedHandler(this, PresentationSourceChangedHandler); + + UseLayoutRounding = true; + } + + /// + int IChromiumWebBrowserInternal.Id + { + get { return _id; } + } + + /// + /// DevToolsContext - provides communication with the underlying browser + /// + public IDevToolsContext DevToolsContext + { + get + { + if (_devToolsReady) + { + return _devToolsContext; + } + + return default; + } + } + + /// + public bool IsBrowserInitialized => _browserHwnd != IntPtr.Zero; + + + /// + public Frame[] Frames => _devToolsContext == null ? null : _devToolsContext.Frames; + + /// + public Frame MainFrame => _devToolsContext == null ? null : _devToolsContext.MainFrame; + + /// + void IChromiumWebBrowserInternal.OnDevToolsMessage(string jsonMsg) + { + _devToolsContextConnectionTransport?.InvokeMessageReceived(jsonMsg); + } + + /// + void IChromiumWebBrowserInternal.OnDevToolsReady() + { + var ctx = (DevToolsContext)_devToolsContext; + + ctx.DOMContentLoaded += DOMContentLoaded; + ctx.Error += BrowserProcessCrashed; + ctx.FrameAttached += FrameAttached; + ctx.FrameDetached += FrameDetached; + ctx.FrameNavigated += FrameNavigated; + ctx.Load += JavaScriptLoad; + ctx.PageError += RuntimeExceptionThrown; + ctx.Popup += Popup; + ctx.Request += NetworkRequest; + ctx.RequestFailed += NetworkRequestFailed; + ctx.RequestFinished += NetworkRequestFinished; + ctx.RequestServedFromCache += NetworkRequestServedFromCache; + ctx.Response += NetworkResponse; + ctx.Console += ConsoleMessage; + ctx.LifecycleEvent += LifecycleEvent; + + _ = ctx.InvokeGetFrameTreeAsync().ContinueWith(t => + { + _devToolsReady = true; + + DevToolsContextAvailable?.Invoke(this, EventArgs.Empty); + + //NOW the user can start using the devtools context + }, TaskScheduler.Current); + + // Only call Load if initialAddress is null and Address is not empty + if (string.IsNullOrEmpty(_initialAddress) && !string.IsNullOrEmpty(Address)) + { + LoadUrl(Address); + } + } + + /// + public void LoadUrl(string url) + { + _ = _devToolsContext.GoToAsync(url); + } + + /// + public Task LoadUrlAsync(string url, int? timeout = null, WaitUntilNavigation[] waitUntil = null) + { + return _devToolsContext.GoToAsync(url, timeout, waitUntil); + } + + /// + public Task GoBackAsync(NavigationOptions options = null) + { + return _devToolsContext.GoBackAsync(options); + } + + /// + public Task GoForwardAsync(NavigationOptions options = null) + { + return _devToolsContext.GoForwardAsync(options); + } + + private void PresentationSourceChangedHandler(object sender, SourceChangedEventArgs args) + { + if (args.NewSource != null) + { + var source = (HwndSource)args.NewSource; + + var matrix = source.CompositionTarget.TransformToDevice; + + _dpiScale = matrix.M11; + + var window = source.RootVisual as Window; + if (window != null) + { + window.StateChanged += OnWindowStateChanged; + window.LocationChanged += OnWindowLocationChanged; + _sourceWindow = window; + + if (CleanupElement == null) + { + CleanupElement = window; + } + else if (CleanupElement is Window parent) + { + //If the CleanupElement is a window then move it to the new Window + if (parent != window) + { + CleanupElement = window; + } + } + } + } + else if (args.OldSource != null) + { + var window = args.OldSource.RootVisual as Window; + if (window != null) + { + window.StateChanged -= OnWindowStateChanged; + window.LocationChanged -= OnWindowLocationChanged; + _sourceWindow = null; + } + } + } + + /// + protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi) + { + _dpiScale = newDpi.DpiScaleX; + + //If the DPI changed then we need to resize. + ResizeBrowser((int)ActualWidth, (int)ActualHeight); + + base.OnDpiChanged(oldDpi, newDpi); + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + ResizeBrowser((int)e.NewSize.Width, (int)e.NewSize.Height); + } + + /// + protected override HandleRef BuildWindowCore(HandleRef hwndParent) + { + if (_hwndHost == IntPtr.Zero) + { + _hwndHost = CreateWindowEx(0, "static", "", + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, + 0, 0, + (int)ActualWidth, (int)ActualHeight, + hwndParent.Handle, + (IntPtr)HOST_ID, + IntPtr.Zero, + 0); + } + + _host.CreateBrowser(this, _hwndHost, url: _initialAddress, out _id); + + _devToolsContextConnectionTransport = new OutOfProcessConnectionTransport(_id, _host); + + var connection = DevToolsConnection.Attach(_devToolsContextConnectionTransport); + _devToolsContext = Dom.DevToolsContext.CreateForOutOfProcess(connection); + + return new HandleRef(null, _hwndHost); + } + + /// + protected override void DestroyWindowCore(HandleRef hwnd) + { + DestroyWindow(hwnd.Handle); + } + + /// + protected override bool TabIntoCore(TraversalRequest request) + { + if(InternalIsBrowserInitialized()) + { + _host.SetFocus(_id, true); + + return true; + } + + return base.TabIntoCore(request); + } + + /// + protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + if(!e.Handled) + { + if (InternalIsBrowserInitialized()) + { + _host.SetFocus(_id, true); + } + else + { + _initialFocus = true; + } + } + + base.OnGotKeyboardFocus(e); + } + + /// + protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + if (!e.Handled) + { + if (InternalIsBrowserInitialized()) + { + _host.SetFocus(_id, false); + } + else + { + _initialFocus = false; + } + } + + base.OnLostKeyboardFocus(e); + } + + + /// + protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + const int WM_SETFOCUS = 0x0007; + const int WM_MOUSEACTIVATE = 0x0021; + switch (msg) + { + case WM_SETFOCUS: + case WM_MOUSEACTIVATE: + { + if(InternalIsBrowserInitialized()) + { + + _host.SetFocus(_id, true); + + handled = true; + + return IntPtr.Zero; + } + break; + } + } + return base.WndProc(hwnd, msg, wParam, lParam, ref handled); + } + + /// + /// If not in design mode; Releases unmanaged and - optionally - managed resources for the + /// + /// to release both managed and unmanaged resources; to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + // Attempt to move the disposeSignaled state from 0 to 1. If successful, we can be assured that + // this thread is the first thread to do so, and can safely dispose of the object. + if (Interlocked.CompareExchange(ref _disposeSignaled, 1, 0) != 0) + { + return; + } + + if (!DesignMode) + { + InternalDispose(disposing); + } + + base.Dispose(disposing); + } + + /// + /// Releases unmanaged and - optionally - managed resources for the + /// + /// to release both managed and unmanaged resources; to release only unmanaged resources. + /// + /// This method cannot be inlined as the designer will attempt to load libcef.dll and will subsiquently throw an exception. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void InternalDispose(bool disposing) + { + Interlocked.Exchange(ref _browserInitialized, 0); + + if (disposing) + { + SizeChanged -= OnSizeChanged; + IsVisibleChanged -= OnIsVisibleChanged; + + PresentationSource.RemoveSourceChangedHandler(this, PresentationSourceChangedHandler); + // Release window event listeners if PresentationSourceChangedHandler event wasn't + // fired before Dispose + if (_sourceWindow != null) + { + _sourceWindow.StateChanged -= OnWindowStateChanged; + _sourceWindow.LocationChanged -= OnWindowLocationChanged; + _sourceWindow = null; + } + + + UiThreadRunAsync(() => + { + OnIsBrowserInitializedChanged(true, false); + + //To Minic the WPF behaviour this happens after OnIsBrowserInitializedChanged + IsBrowserInitializedChanged?.Invoke(this, EventArgs.Empty); + + WebBrowser = null; + }); + + // Don't maintain a reference to event listeners anylonger: + //ConsoleMessage = null; + //FrameLoadEnd = null; + //FrameLoadStart = null; + IsBrowserInitializedChanged = null; + //LoadError = null; + LoadingStateChanged = null; + StatusMessage = null; + TitleChanged = null; + + if (CleanupElement != null) + { + CleanupElement.Unloaded -= OnCleanupElementUnloaded; + } + } + } + + /// + void IChromiumWebBrowserInternal.SetAddress(string address) + { + UiThreadRunAsync(() => + { + _ignoreUriChange = true; + SetCurrentValue(AddressProperty, address); + _ignoreUriChange = false; + + // The tooltip should obviously also be reset (and hidden) when the address changes. + SetCurrentValue(TooltipTextProperty, null); + }); + } + + /// + void IChromiumWebBrowserInternal.SetLoadingStateChange(bool canGoBack, bool canGoForward, bool isLoading) + { + UiThreadRunAsync(() => + { + SetCurrentValue(CanGoBackProperty, canGoBack); + SetCurrentValue(CanGoForwardProperty, canGoForward); + SetCurrentValue(IsLoadingProperty, isLoading); + + ((DelegateCommand)BackCommand).RaiseCanExecuteChanged(); + ((DelegateCommand)ForwardCommand).RaiseCanExecuteChanged(); + ((DelegateCommand)ReloadCommand).RaiseCanExecuteChanged(); + }); + + LoadingStateChanged?.Invoke(this, new LoadingStateChangedEventArgs(canGoBack, canGoForward, isLoading)); + } + + /// + void IChromiumWebBrowserInternal.SetTitle(string title) + { + UiThreadRunAsync(() => SetCurrentValue(TitleProperty, title)); + } + + /// + /// Sets the tooltip text. + /// + /// The tooltip text. + //void IWebBrowserInternal.SetTooltipText(string tooltipText) + //{ + // UiThreadRunAsync(() => SetCurrentValue(TooltipTextProperty, tooltipText)); + //} + + /// + /// Handles the event. + /// + /// The instance containing the event data. + //void IWebBrowserInternal.OnConsoleMessage(ConsoleMessageEventArgs args) + //{ + // ConsoleMessage?.Invoke(this, args); + //} + + /// + void IChromiumWebBrowserInternal.SetStatusMessage(string msg) + { + StatusMessage?.Invoke(this, new StatusMessageEventArgs(msg)); + } + + /// + void IChromiumWebBrowserInternal.OnAfterBrowserCreated(IntPtr hwnd) + { + if (IsDisposed) + { + return; + } + + _browserHwnd = hwnd; + + Interlocked.Exchange(ref _browserInitialized, 1); + + UiThreadRunAsync(() => + { + if (!IsDisposed) + { + OnIsBrowserInitializedChanged(false, true); + //To Minic the WPF behaviour this happens after OnIsBrowserInitializedChanged + IsBrowserInitializedChanged?.Invoke(this, EventArgs.Empty); + } + }); + + ResizeBrowser((int)ActualWidth, (int)ActualHeight); + + if (_initialFocus) + { + _host.SetFocus(_id, true); + } + } + + /// + /// A flag that indicates whether the state of the control current supports the GoBack action (true) or not (false). + /// + /// true if this instance can go back; otherwise, false. + /// In the WPF control, this property is implemented as a Dependency Property and fully supports data + /// binding. + public bool CanGoBack + { + get { return (bool)GetValue(CanGoBackProperty); } + } + + /// + /// The can go back property + /// + public static DependencyProperty CanGoBackProperty = DependencyProperty.Register(nameof(CanGoBack), typeof(bool), typeof(ChromiumWebBrowser)); + + /// + /// A flag that indicates whether the state of the control currently supports the GoForward action (true) or not (false). + /// + /// true if this instance can go forward; otherwise, false. + /// In the WPF control, this property is implemented as a Dependency Property and fully supports data + /// binding. + public bool CanGoForward + { + get { return (bool)GetValue(CanGoForwardProperty); } + } + + /// + /// The can go forward property + /// + public static DependencyProperty CanGoForwardProperty = DependencyProperty.Register(nameof(CanGoForward), typeof(bool), typeof(ChromiumWebBrowser)); + + /// + /// The address (URL) which the browser control is currently displaying. + /// Will automatically be updated as the user navigates to another page (e.g. by clicking on a link). + /// + /// The address. + /// In the WPF control, this property is implemented as a Dependency Property and fully supports data + /// binding. + public string Address + { + get { return (string)GetValue(AddressProperty); } + set { SetValue(AddressProperty, value); } + } + + /// + /// The address property + /// + public static readonly DependencyProperty AddressProperty = + DependencyProperty.Register(nameof(Address), typeof(string), typeof(ChromiumWebBrowser), + new UIPropertyMetadata(null, OnAddressChanged)); + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private static void OnAddressChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + var owner = (ChromiumWebBrowser)sender; + var oldValue = (string)args.OldValue; + var newValue = (string)args.NewValue; + + owner.OnAddressChanged(oldValue, newValue); + } + + /// + /// Called when [address changed]. + /// + /// The old value. + /// The new value. + protected virtual void OnAddressChanged(string oldValue, string newValue) + { + if (_ignoreUriChange || newValue == null || !InternalIsBrowserInitialized()) + { + return; + } + + LoadUrl(newValue); + } + + /// + /// A flag that indicates whether the control is currently loading one or more web pages (true) or not (false). + /// + /// true if this instance is loading; otherwise, false. + /// In the WPF control, this property is implemented as a Dependency Property and fully supports data + /// binding. + public bool IsLoading + { + get { return (bool)GetValue(IsLoadingProperty); } + } + + /// + /// The is loading property + /// + public static readonly DependencyProperty IsLoadingProperty = + DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(ChromiumWebBrowser), new PropertyMetadata(false)); + + /// + /// Event called after the underlying CEF browser instance has been created and + /// when the instance has been Disposed. + /// will be true when the underlying CEF Browser + /// has been created and false when the browser is being Disposed. + /// + public event EventHandler IsBrowserInitializedChanged; + + /// + /// Called when [is browser initialized changed]. + /// + /// if set to true [old value]. + /// if set to true [new value]. + protected virtual void OnIsBrowserInitializedChanged(bool oldValue, bool newValue) + { + if (newValue && !IsDisposed) + { + //var task = this.GetZoomLevelAsync(); + //task.ContinueWith(previous => + //{ + // if (previous.Status == TaskStatus.RanToCompletion) + // { + // UiThreadRunAsync(() => + // { + // if (!IsDisposed) + // { + // SetCurrentValue(ZoomLevelProperty, previous.Result); + // } + // }); + // } + // else + // { + // throw new InvalidOperationException("Unexpected failure of calling CEF->GetZoomLevelAsync", previous.Exception); + // } + //}, TaskContinuationOptions.ExecuteSynchronously); + } + } + + /// + /// The title of the web page being currently displayed. + /// + /// The title. + /// This property is implemented as a Dependency Property and fully supports data binding. + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// The title property + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register(nameof(Title), typeof(string), typeof(ChromiumWebBrowser), new PropertyMetadata(null, OnTitleChanged)); + + /// + /// Event handler that will get called when the browser title changes + /// + public event DependencyPropertyChangedEventHandler TitleChanged; + + /// + /// Handles the event. + /// + /// The d. + /// The instance containing the event data. + private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var owner = (ChromiumWebBrowser)d; + + owner.TitleChanged?.Invoke(owner, e); + } + + /// + /// The zoom level at which the browser control is currently displaying. + /// Can be set to 0 to clear the zoom level (resets to default zoom level). + /// + /// The zoom level. + public double ZoomLevel + { + get { return (double)GetValue(ZoomLevelProperty); } + set { SetValue(ZoomLevelProperty, value); } + } + + /// + /// The zoom level property + /// + public static readonly DependencyProperty ZoomLevelProperty = + DependencyProperty.Register(nameof(ZoomLevel), typeof(double), typeof(ChromiumWebBrowser), + new UIPropertyMetadata(0d, OnZoomLevelChanged)); + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private static void OnZoomLevelChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + var owner = (ChromiumWebBrowser)sender; + var oldValue = (double)args.OldValue; + var newValue = (double)args.NewValue; + + owner.OnZoomLevelChanged(oldValue, newValue); + } + + /// + /// Called when [zoom level changed]. + /// + /// The old value. + /// The new value. + protected virtual void OnZoomLevelChanged(double oldValue, double newValue) + { + throw new NotImplementedException(); + } + + /// + /// Specifies the amount used to increase/decrease to ZoomLevel by + /// By Default this value is 0.10 + /// + /// The zoom level increment. + public double ZoomLevelIncrement + { + get { return (double)GetValue(ZoomLevelIncrementProperty); } + set { SetValue(ZoomLevelIncrementProperty, value); } + } + + /// + /// The zoom level increment property + /// + public static readonly DependencyProperty ZoomLevelIncrementProperty = + DependencyProperty.Register(nameof(ZoomLevelIncrement), typeof(double), typeof(ChromiumWebBrowser), new PropertyMetadata(0.10)); + + /// + /// The CleanupElement controls when the Browser will be Disposed. + /// The will be Disposed when is called. + /// Be aware that this Control is not usable anymore after it has been disposed. + /// + /// The cleanup element. + public FrameworkElement CleanupElement + { + get { return (FrameworkElement)GetValue(CleanupElementProperty); } + set { SetValue(CleanupElementProperty, value); } + } + + /// + /// The cleanup element property + /// + public static readonly DependencyProperty CleanupElementProperty = + DependencyProperty.Register(nameof(CleanupElement), typeof(FrameworkElement), typeof(ChromiumWebBrowser), new PropertyMetadata(null, OnCleanupElementChanged)); + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private static void OnCleanupElementChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + var owner = (ChromiumWebBrowser)sender; + var oldValue = (FrameworkElement)args.OldValue; + var newValue = (FrameworkElement)args.NewValue; + + owner.OnCleanupElementChanged(oldValue, newValue); + } + + /// + /// Called when [cleanup element changed]. + /// + /// The old value. + /// The new value. + protected virtual void OnCleanupElementChanged(FrameworkElement oldValue, FrameworkElement newValue) + { + if (oldValue != null) + { + oldValue.Unloaded -= OnCleanupElementUnloaded; + } + + if (newValue != null) + { + newValue.Unloaded += OnCleanupElementUnloaded; + } + } + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private void OnCleanupElementUnloaded(object sender, RoutedEventArgs e) + { + Dispose(); + } + + /// + /// The text that will be displayed as a ToolTip + /// + /// The tooltip text. + public string TooltipText + { + get { return (string)GetValue(TooltipTextProperty); } + } + + /// + /// The tooltip text property + /// + public static readonly DependencyProperty TooltipTextProperty = + DependencyProperty.Register(nameof(TooltipText), typeof(string), typeof(ChromiumWebBrowser)); + + /// + /// Gets or sets the WebBrowser. + /// + /// The WebBrowser. + public IChromiumWebBrowser WebBrowser + { + get { return (IChromiumWebBrowser)GetValue(WebBrowserProperty); } + set { SetValue(WebBrowserProperty, value); } + } + + /// + /// The WebBrowser property + /// + public static readonly DependencyProperty WebBrowserProperty = + DependencyProperty.Register(nameof(WebBrowser), typeof(IChromiumWebBrowser), typeof(ChromiumWebBrowser), new UIPropertyMetadata(defaultValue: null)); + + /// + /// Runs the specific Action on the Dispatcher in an async fashion + /// + /// The action. + /// The priority. + private void UiThreadRunAsync(Action action, DispatcherPriority priority = DispatcherPriority.DataBind) + { + if (Dispatcher.CheckAccess()) + { + action(); + } + else if (!Dispatcher.HasShutdownStarted) + { + _ = Dispatcher.InvokeAsync(action, priority); + } + } + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs args) + { + var isVisible = (bool)args.NewValue; + + if (InternalIsBrowserInitialized()) + { + if (isVisible) + { + ResizeBrowser((int)ActualWidth, (int)ActualHeight); + } + else + { + //Hide browser + ResizeBrowser(0, 0); + } + } + } + + /// + /// Zooms the browser in. + /// + private void ZoomIn() + { + UiThreadRunAsync(() => + { + ZoomLevel = ZoomLevel + ZoomLevelIncrement; + }); + } + + /// + /// Zooms the browser out. + /// + private void ZoomOut() + { + UiThreadRunAsync(() => + { + ZoomLevel = ZoomLevel - ZoomLevelIncrement; + }); + } + + /// + /// Reset the browser's zoom level to default. + /// + private void ZoomReset() + { + UiThreadRunAsync(() => + { + ZoomLevel = 0; + }); + } + + /// + /// Check is browserisinitialized + /// + /// true if browser is initialized + private bool InternalIsBrowserInitialized() + { + // Use CompareExchange to read the current value - if disposeCount is 1, we set it to 1, effectively a no-op + // Volatile.Read would likely use a memory barrier which I believe is unnecessary in this scenario + return Interlocked.CompareExchange(ref _browserInitialized, 0, 0) == 1; + } + + /// + /// Resizes the browser to the specified and . + /// If and are both 0 then the browser + /// will be hidden and resource usage will be minimised. + /// + /// width + /// height + protected virtual void ResizeBrowser(int width, int height) + { + if (_browserHwnd != IntPtr.Zero) + { + if (_dpiScale > 1) + { + width = (int)(width * _dpiScale); + height = (int)(height * _dpiScale); + } + + if (width == 0 && height == 0) + { + // For windowed browsers when the frame window is minimized set the + // browser window size to 0x0 to reduce resource usage. + HideInternal(); + } + else + { + ShowInternal(width, height); + } + } + } + + /// + /// When minimized set the browser window size to 0x0 to reduce resource usage. + /// https://github.com/chromiumembedded/cef/blob/c7701b8a6168f105f2c2d6b239ce3958da3e3f13/tests/cefclient/browser/browser_window_std_win.cc#L87 + /// + internal virtual void HideInternal() + { + if (_browserHwnd != IntPtr.Zero) + { + User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, 0, 0, User32.SetWindowPosFlags.SWP_NOZORDER | User32.SetWindowPosFlags.SWP_NOMOVE | User32.SetWindowPosFlags.SWP_NOACTIVATE); + } + } + + /// + /// Show the browser (called after previous minimised) + /// + internal virtual void ShowInternal(int width, int height) + { + if (_browserHwnd != IntPtr.Zero) + { + User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, width, height, User32.SetWindowPosFlags.SWP_NOZORDER); + } + } + + private void OnWindowStateChanged(object sender, EventArgs e) + { + var window = (Window)sender; + + switch (window.WindowState) + { + case WindowState.Normal: + case WindowState.Maximized: + { + if (_previousWindowState == WindowState.Minimized && InternalIsBrowserInitialized()) + { + ResizeBrowser((int)ActualWidth, (int)ActualHeight); + } + break; + } + case WindowState.Minimized: + { + if (InternalIsBrowserInitialized()) + { + //Set the browser size to 0,0 to reduce CPU usage + ResizeBrowser(0, 0); + } + break; + } + } + + _previousWindowState = window.WindowState; + } + + private void OnWindowLocationChanged(object sender, EventArgs e) + { + if (InternalIsBrowserInitialized()) + { + _host.NotifyMoveOrResizeStarted(_id); + } + } + + /// + /// Throw exception if browser not initialized. + /// + /// Thrown when an exception error condition occurs. + private void ThrowExceptionIfBrowserNotInitialized() + { + if (!InternalIsBrowserInitialized()) + { + throw new Exception(BrowserNotInitializedExceptionErrorMessage); + } + } + + /// + /// Throw exception if disposed. + /// + /// Thrown when a supplied object has been disposed. + private void ThrowExceptionIfDisposed() + { + if (IsDisposed) + { + throw new ObjectDisposedException("browser", "Browser has been disposed"); + } + } + } +} diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.config b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.config new file mode 100644 index 0000000..81ca9c6 --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.xaml b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.xaml new file mode 100644 index 0000000..cd28f96 --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.xaml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.xaml.cs b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.xaml.cs new file mode 100644 index 0000000..589e16d --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/App.xaml.cs @@ -0,0 +1,16 @@ +// Copyright © 2022 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; + +namespace CefSharp.OutOfProcess.Wpf.OffscreenHost.Example +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + + } +} diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/AssemblyInfo.cs b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/AssemblyInfo.cs new file mode 100644 index 0000000..ed33db9 --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/AssemblyInfo.cs @@ -0,0 +1,14 @@ +// Copyright © 2019 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; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Behaviours/HoverLinkBehaviour.cs b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Behaviours/HoverLinkBehaviour.cs new file mode 100644 index 0000000..b67fdd2 --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Behaviours/HoverLinkBehaviour.cs @@ -0,0 +1,34 @@ +using System.Windows; +using System; +using Microsoft.Xaml.Behaviors; + +namespace CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.Behaviours +{ + public class HoverLinkBehaviour : Behavior + { + // Using a DependencyProperty as the backing store for HoverLink. This enables animation, styling, binding, etc... + public static readonly DependencyProperty HoverLinkProperty = DependencyProperty.Register("HoverLink", typeof(string), typeof(HoverLinkBehaviour), new PropertyMetadata(string.Empty)); + + public string HoverLink + { + get { return (string)GetValue(HoverLinkProperty); } + set { SetValue(HoverLinkProperty, value); } + } + + protected override void OnAttached() + { + AssociatedObject.StatusMessage += OnStatusMessageChanged; + } + + protected override void OnDetaching() + { + AssociatedObject.StatusMessage -= OnStatusMessageChanged; + } + + private void OnStatusMessageChanged(object sender, StatusMessageEventArgs e) + { + var chromiumWebBrowser = sender as OffscreenChromiumWebBrowser; + chromiumWebBrowser.Dispatcher.BeginInvoke((Action)(() => HoverLink = e.Value)); + } + } +} diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Behaviours/TextBoxBindingUpdateOnEnterBehaviour.cs b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Behaviours/TextBoxBindingUpdateOnEnterBehaviour.cs new file mode 100644 index 0000000..0d9b1f8 --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Behaviours/TextBoxBindingUpdateOnEnterBehaviour.cs @@ -0,0 +1,28 @@ +namespace CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.Behaviours +{ + using System.Windows.Controls; + using System.Windows.Input; + using Microsoft.Xaml.Behaviors; + + public class TextBoxBindingUpdateOnEnterBehaviour : Behavior + { + protected override void OnAttached() + { + AssociatedObject.KeyDown += OnTextBoxKeyDown; + } + + protected override void OnDetaching() + { + AssociatedObject.KeyDown -= OnTextBoxKeyDown; + } + + private void OnTextBoxKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter) + { + var txtBox = sender as TextBox; + txtBox.GetBindingExpression(TextBox.TextProperty).UpdateSource(); + } + } + } +} diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj new file mode 100644 index 0000000..912bcba --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj @@ -0,0 +1,19 @@ + + + WinExe + true + net462 + CefSharp.OutOfProcess.Wpf.OffscreenHost.Example + CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.App + true + AnyCPU + app.manifest + + + + + + + + + \ No newline at end of file diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Converter/EnvironmentConverter.cs b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Converter/EnvironmentConverter.cs new file mode 100644 index 0000000..0c18b5e --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Converter/EnvironmentConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.Converter +{ + public class EnvironmentConverter : IValueConverter + { + object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return Environment.Is64BitProcess ? "x64" : "x86"; + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } +} diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Converter/TitleConverter.cs b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Converter/TitleConverter.cs new file mode 100644 index 0000000..dfa888c --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/Converter/TitleConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.Converter +{ + public class TitleConverter : IValueConverter + { + object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return "CefSharp.OutOfProcess.Wpf.OffscreenHost.Example - " + (value ?? "No Title Specified"); + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return Binding.DoNothing; + } + } +} diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/MainWindow.xaml b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/MainWindow.xaml new file mode 100644 index 0000000..b342b39 --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/MainWindow.xaml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + private int _disposeSignaled; - /// - /// Current DPI Scale - /// - private double _dpiScale; - /// /// The HwndSource RootVisual (Window) - We store a reference /// to unsubscribe event handlers @@ -112,17 +99,14 @@ public class OffscreenChromiumWebBrowser : Control, IChromiumWebBrowserInternal, private DirectWritableBitmapRenderHandler _popupRenderHandler; private WindowState _previousWindowState; + private Rect _popupRect; + private bool _showPopup; + /// /// Gets a value indicating whether this instance is disposed. /// /// if this instance is disposed; otherwise, . - public bool IsDisposed - { - get - { - return Interlocked.CompareExchange(ref _disposeSignaled, 1, 1) == 1; - } - } + public bool IsDisposed => Interlocked.CompareExchange(ref _disposeSignaled, 1, 1) == 1; /// public event EventHandler DOMContentLoaded; @@ -191,99 +175,26 @@ public bool IsDisposed /// browser state. /// /// The back command. - public ICommand BackCommand { get; private set; } + public ICommand BackCommand { get; } /// /// Navigates to the next page in the browser history. Will automatically be enabled/disabled depending on the /// browser state. /// /// The forward command. - public ICommand ForwardCommand { get; private set; } + public ICommand ForwardCommand { get; } /// /// Reloads the content of the current page. Will automatically be enabled/disabled depending on the browser state. /// /// The reload command. - public ICommand ReloadCommand { get; private set; } - - /// - /// Prints the current browser contents. - /// - /// The print command. - public ICommand PrintCommand { get; private set; } - - /// - /// Increases the zoom level. - /// - /// The zoom in command. - public ICommand ZoomInCommand { get; private set; } - - /// - /// Decreases the zoom level. - /// - /// The zoom out command. - public ICommand ZoomOutCommand { get; private set; } - - /// - /// Resets the zoom level to the default. (100%) - /// - /// The zoom reset command. - public ICommand ZoomResetCommand { get; private set; } - - /// - /// Opens up a new program window (using the default text editor) where the source code of the currently displayed web - /// page is shown. - /// - /// The view source command. - public ICommand ViewSourceCommand { get; private set; } + public ICommand ReloadCommand { get; } /// /// Command which cleans up the Resources used by the ChromiumWebBrowser /// /// The cleanup command. - public ICommand CleanupCommand { get; private set; } - - /// - /// Stops loading the current page. - /// - /// The stop command. - public ICommand StopCommand { get; private set; } - - /// - /// Cut selected text to the clipboard. - /// - /// The cut command. - public ICommand CutCommand { get; private set; } - - /// - /// Copy selected text to the clipboard. - /// - /// The copy command. - public ICommand CopyCommand { get; private set; } - - /// - /// Paste text from the clipboard. - /// - /// The paste command. - public ICommand PasteCommand { get; private set; } - - /// - /// Select all text. - /// - /// The select all command. - public ICommand SelectAllCommand { get; private set; } - - /// - /// Undo last action. - /// - /// The undo command. - public ICommand UndoCommand { get; private set; } - - /// - /// Redo last action. - /// - /// The redo command. - public ICommand RedoCommand { get; private set; } + public ICommand CleanupCommand { get; } private static readonly DependencyPropertyKey sTitlePropertyKey; public static readonly DependencyProperty TitleProperty; @@ -301,52 +212,34 @@ static OffscreenChromiumWebBrowser() public OffscreenChromiumWebBrowser(OutOfProcessHost host, string initialAddress = null) { - if (host == null) - { - throw new ArgumentNullException(nameof(host)); - } - - _host = host; + _host = host ?? throw new ArgumentNullException(nameof(host)); _initialAddress = initialAddress; Focusable = true; FocusVisualStyle = null; IsTabStop = true; - // WebBrowser = this; - SizeChanged += OnSizeChanged; IsVisibleChanged += OnIsVisibleChanged; BackCommand = new DelegateCommand(() => _devToolsContext.GoBackAsync(), () => CanGoBack); ForwardCommand = new DelegateCommand(() => _devToolsContext.GoForwardAsync(), () => CanGoForward); ReloadCommand = new DelegateCommand(() => _devToolsContext.ReloadAsync(), () => !IsLoading); - //PrintCommand = new DelegateCommand(this.Print); - //ZoomInCommand = new DelegateCommand(ZoomIn); - //ZoomOutCommand = new DelegateCommand(ZoomOut); - //ZoomResetCommand = new DelegateCommand(ZoomReset); - //ViewSourceCommand = new DelegateCommand(this.ViewSource); CleanupCommand = new DelegateCommand(Dispose); - //StopCommand = new DelegateCommand(this.Stop); - //CutCommand = new DelegateCommand(this.Cut); - //CopyCommand = new DelegateCommand(this.Copy); - //PasteCommand = new DelegateCommand(this.Paste); - //SelectAllCommand = new DelegateCommand(this.SelectAll); - //UndoCommand = new DelegateCommand(this.Undo); - //RedoCommand = new DelegateCommand(this.Redo); PresentationSource.AddSourceChangedHandler(this, PresentationSourceChangedHandler); UseLayoutRounding = true; - _hwndHost = new WindowInteropHelper(Application.Current.MainWindow).Handle; + var hwndHost = new WindowInteropHelper(Application.Current.MainWindow).Handle; - _host.CreateBrowser(this, _hwndHost, url: _initialAddress, out _id); + _host.CreateBrowser(this, hwndHost, url: _initialAddress, out _id); _devToolsContextConnectionTransport = new OutOfProcessConnectionTransport(_id, _host); var connection = DevToolsConnection.Attach(_devToolsContextConnectionTransport); _devToolsContext = Dom.DevToolsContext.CreateForOutOfProcess(connection); } + protected void ShowDevTools() => _host.ShowDevTools(_id); /// @@ -355,18 +248,7 @@ public OffscreenChromiumWebBrowser(OutOfProcessHost host, string initialAddress /// /// DevToolsContext - provides communication with the underlying browser /// - public IDevToolsContext DevToolsContext - { - get - { - if (_devToolsReady) - { - return _devToolsContext; - } - - return default; - } - } + public IDevToolsContext DevToolsContext => _devToolsReady ? _devToolsContext : default; /// public bool IsBrowserInitialized => _browserHwnd != IntPtr.Zero; @@ -414,41 +296,23 @@ async void IChromiumWebBrowserInternal.OnDevToolsReady() /// public async void LoadUrl(string url) { - // TODO: (CEF) sporadic crash - try - { - // TODO: (CEF) _ = await _devToolsContext.GoToAsync(url); - // _ = await _devToolsContext.GoToAsync(url); - await _host.LoadUrl(_id, url); - } - catch (Exception ex) - { - ; - // TODO: (CEF) frame sporadically not available. Probably race condition - } + await _host.LoadUrl(_id, url); } /// - public Task LoadUrlAsync(string url, int? timeout = null, WaitUntilNavigation[] waitUntil = null) - { - return _devToolsContext.GoToAsync(url, timeout, waitUntil); - } + public Task LoadUrlAsync(string url, int? timeout = null, WaitUntilNavigation[] waitUntil = null) + => _devToolsContext.GoToAsync(url, timeout, waitUntil); /// - public Task GoBackAsync(NavigationOptions options = null) - { - return _devToolsContext.GoBackAsync(options); - } + public Task GoBackAsync(NavigationOptions options = null) + => _devToolsContext.GoBackAsync(options); /// - public Task GoForwardAsync(NavigationOptions options = null) - { - return _devToolsContext.GoForwardAsync(options); - } + public Task GoForwardAsync(NavigationOptions options = null) + => _devToolsContext.GoForwardAsync(options); protected virtual void OnInitializeDevToolsContext(IDevToolsContext context) { - } private void PresentationSourceChangedHandler(object sender, SourceChangedEventArgs args) @@ -459,10 +323,7 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA var matrix = source.CompositionTarget.TransformToDevice; - _dpiScale = matrix.M11; - - var window = source.RootVisual as Window; - if (window != null) + if (source.RootVisual is Window window) { window.StateChanged += OnWindowStateChanged; window.LocationChanged += OnWindowLocationChanged; @@ -482,10 +343,9 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA } } } - else if (args.OldSource != null) + else { - var window = args.OldSource.RootVisual as Window; - if (window != null) + if (args.OldSource?.RootVisual is Window window) { window.StateChanged -= OnWindowStateChanged; window.LocationChanged -= OnWindowLocationChanged; @@ -497,8 +357,6 @@ private void PresentationSourceChangedHandler(object sender, SourceChangedEventA /// protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi) { - _dpiScale = newDpi.DpiScaleX; - //If the DPI changed then we need to resize. ResizeBrowser((int)ActualWidth, (int)ActualHeight); @@ -666,10 +524,7 @@ void IChromiumWebBrowserInternal.OnAfterBrowserCreated(IntPtr hwnd) /// true if this instance can go back; otherwise, false. /// In the WPF control, this property is implemented as a Dependency Property and fully supports data /// binding. - public bool CanGoBack - { - get { return (bool)GetValue(CanGoBackProperty); } - } + public bool CanGoBack => (bool)GetValue(CanGoBackProperty); /// /// The can go back property @@ -682,10 +537,7 @@ public bool CanGoBack /// true if this instance can go forward; otherwise, false. /// In the WPF control, this property is implemented as a Dependency Property and fully supports data /// binding. - public bool CanGoForward - { - get { return (bool)GetValue(CanGoForwardProperty); } - } + public bool CanGoForward => (bool)GetValue(CanGoForwardProperty); /// /// The can go forward property @@ -701,8 +553,8 @@ public bool CanGoForward /// binding. public string Address { - get { return (string)GetValue(AddressProperty); } - set { SetValue(AddressProperty, value); } + get => (string)GetValue(AddressProperty); + set => SetValue(AddressProperty, value); } /// @@ -733,17 +585,11 @@ private static void OnAddressChanged(DependencyObject sender, DependencyProperty /// The new value. protected virtual void OnAddressChanged(string oldValue, string newValue) { - ////TODO (CEF): Check if (_ignoreUriChange || newValue == null || !InternalIsBrowserInitialized()) { return; } - if (_devToolsContext.MainFrame == null) - { - // return; - } - LoadUrl(newValue); } @@ -753,10 +599,7 @@ protected virtual void OnAddressChanged(string oldValue, string newValue) /// true if this instance is loading; otherwise, false. /// In the WPF control, this property is implemented as a Dependency Property and fully supports data /// binding. - public bool IsLoading - { - get { return (bool)GetValue(IsLoadingProperty); } - } + public bool IsLoading => (bool)GetValue(IsLoadingProperty); /// /// The is loading property @@ -792,8 +635,8 @@ protected virtual void OnIsBrowserInitializedChanged(bool oldValue, bool newValu /// The cleanup element. public FrameworkElement CleanupElement { - get { return (FrameworkElement)GetValue(CleanupElementProperty); } - set { SetValue(CleanupElementProperty, value); } + get => (FrameworkElement)GetValue(CleanupElementProperty); + set => SetValue(CleanupElementProperty, value); } Dom.Frame[] IChromiumWebBrowser.Frames => throw new NotImplementedException(); @@ -888,7 +731,7 @@ private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArg } /// - /// Check is browserisinitialized + /// Check is /// /// true if browser is initialized private bool InternalIsBrowserInitialized() @@ -1004,17 +847,9 @@ private void ThrowExceptionIfDisposed() } } - private void Dispose() - { - // TODO: (CEF) - Dispose(true); - } + private void Dispose() => Dispose(true); - void IDisposable.Dispose() - { - Dispose(true); - // TODO: (CEF) - } + void IDisposable.Dispose() => Dispose(true); protected virtual void Dispose(bool isDisposing) { @@ -1023,8 +858,8 @@ protected virtual void Dispose(bool isDisposing) void IRenderHandlerInternal.OnPaint(bool isPopup, Interface.Rect directRect, int width, int height, string file) { - const int DefaultDpi = 96; - var scale = DefaultDpi * 1.0; + const int defaultDpi = 96; + const double scale = defaultDpi * 1.0; if (_renderHandler == null && !IsDisposed) { _renderHandler = new DirectWritableBitmapRenderHandler(scale, scale); @@ -1035,42 +870,38 @@ void IRenderHandlerInternal.OnPaint(bool isPopup, Interface.Rect directRect, int { if (isPopup) { - _popupRenderHandler?.OnPaint(directRect, width, height, popupImage, file); + _popupRenderHandler?.OnPaint(directRect, width, height, _popupImage, file); } else { - _renderHandler?.OnPaint(directRect, width, height, image, file); + _renderHandler?.OnPaint(directRect, width, height, _image, file); } InvalidateVisual(); }, DispatcherPriority.Render); } - private bool showPopup; - void IRenderHandlerInternal.OnPopupShow(bool show) { - showPopup = show; + _showPopup = show; } void IRenderHandlerInternal.OnPopupSize(Interface.Rect rect) { - popupRect = new Rect(rect.X, rect.Y, rect.Width, rect.Height); + _popupRect = new Rect(rect.X, rect.Y, rect.Width, rect.Height); } - private Rect popupRect; - protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); - if (image != null && image.Source != null) + if (_image?.Source != null) { - drawingContext.DrawImage(image.Source, new Rect(0, 0, (int)image.Source.Width, (int)image.Source.Height)); + drawingContext.DrawImage(_image.Source, new Rect(0, 0, (int)_image.Source.Width, (int)_image.Source.Height)); } - if (showPopup && popupImage != null && image.Source != null) + if (_showPopup && _popupImage != null && _image?.Source != null) { - drawingContext.DrawImage(popupImage.Source, popupRect); + drawingContext.DrawImage(_popupImage.Source, _popupRect); } } @@ -1110,7 +941,7 @@ await DevToolsContext.Mouse /// /// The that contains the event data. /// This event data reports details about the mouse button that was pressed and the handled state. - protected async override void OnMouseDown(MouseButtonEventArgs e) + protected override void OnMouseDown(MouseButtonEventArgs e) { if (DevToolsContext == null) { @@ -1147,7 +978,7 @@ protected async override void OnMouseDown(MouseButtonEventArgs e) /// Invoked when an unhandled routed event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. /// /// The that contains the event data. The event data reports that the mouse button was released. - protected override async void OnMouseUp(MouseButtonEventArgs e) + protected override void OnMouseUp(MouseButtonEventArgs e) { if (DevToolsContext == null) { @@ -1220,21 +1051,6 @@ void IChromiumWebBrowserInternal.SetStatusMessage(string msg) /// The instance containing the event data. private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((OffscreenChromiumWebBrowser)d).TitleChanged?.Invoke(d, e); - /////// - /////// Called when the IME composition range has changed. - /////// - /////// is the range of characters that have been selected - /////// is the bounds of each character in view coordinates. - ////protected virtual void OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds) - ////{ - //// //// TODO: (CEF) - //// ////var imeKeyboardHandler = WpfKeyboardHandler as WpfImeKeyboardHandler; - //// ////if (imeKeyboardHandler != null) - //// ////{ - //// //// imeKeyboardHandler.ChangeCompositionRange(selectedRange, characterBounds); - //// ////} - ////} - /// /// 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. @@ -1298,16 +1114,18 @@ private void HandleKeyPress(KeyEventArgs e) e.Handled = true; } - if (KeyMapping.ContainsKey(e.Key.ToString())) + if (!_keyMapping.ContainsKey(e.Key.ToString())) { - if (e.IsDown) - { - DevToolsContext.Keyboard.DownAsync(KeyMapping[e.Key.ToString()]); - } - else - { - DevToolsContext.Keyboard.UpAsync(KeyMapping[e.Key.ToString()]); - } + return; + } + + if (e.IsDown) + { + DevToolsContext.Keyboard.DownAsync(_keyMapping[e.Key.ToString()]); + } + else + { + DevToolsContext.Keyboard.UpAsync(_keyMapping[e.Key.ToString()]); } } @@ -1337,25 +1155,26 @@ private async void HandleTextInput(TextCompositionEventArgs e) /// The instance containing the event data. private void OnMouseButton(MouseButtonEventArgs e) { - if (!e.Handled && _devToolsReady) + if (e.Handled || !_devToolsReady) { - var mouseUp = e.ButtonState == MouseButtonState.Released; - var point = e.GetPosition(this); + return; + } - //// Chromium only supports values of 1, 2 or 3. - //// https://github.com/cefsharp/CefSharp/issues/3940 - //// Anything greater than 3 then we send click count of 1 - var clickCount = e.ClickCount; + var mouseUp = e.ButtonState == MouseButtonState.Released; + var point = e.GetPosition(this); - if (clickCount > 3) - { - clickCount = 1; - } + //// Chromium only supports values of 1, 2 or 3. + //// https://github.com/cefsharp/CefSharp/issues/3940 + //// Anything greater than 3 then we send click count of 1 + var clickCount = e.ClickCount; + if (clickCount > 3) + { + clickCount = 1; + } - _host.SendMouseClickEvent(_id, (int)point.X, (int)point.Y, (MouseButtonType)e.ChangedButton, mouseUp, clickCount, e.GetModifiers()); + _host.SendMouseClickEvent(_id, (int)point.X, (int)point.Y, (MouseButtonType)e.ChangedButton, mouseUp, clickCount, e.GetModifiers()); - e.Handled = true; - } + e.Handled = true; } #endregion From 34e0c6ae7a87c7a9a9de9943697b4efaa36defda Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Thu, 26 Jan 2023 18:56:40 +0100 Subject: [PATCH 05/12] revert --- .../CefSharp.OutOfProcess.Core.csproj | 2 +- .../CefSharp - Backup.OutOfProcess.Interface.csproj | 7 ------- .../CefSharp.OutOfProcess.Interface.csproj | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 CefSharp.OutOfProcess.Interface/CefSharp - Backup.OutOfProcess.Interface.csproj diff --git a/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj b/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj index bdf1eb7..b340afb 100644 --- a/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj +++ b/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0 AnyCPU CefSharp.OutOfProcess diff --git a/CefSharp.OutOfProcess.Interface/CefSharp - Backup.OutOfProcess.Interface.csproj b/CefSharp.OutOfProcess.Interface/CefSharp - Backup.OutOfProcess.Interface.csproj deleted file mode 100644 index 969882a..0000000 --- a/CefSharp.OutOfProcess.Interface/CefSharp - Backup.OutOfProcess.Interface.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - netstandard2.0 - - - diff --git a/CefSharp.OutOfProcess.Interface/CefSharp.OutOfProcess.Interface.csproj b/CefSharp.OutOfProcess.Interface/CefSharp.OutOfProcess.Interface.csproj index 969882a..9f5c4f4 100644 --- a/CefSharp.OutOfProcess.Interface/CefSharp.OutOfProcess.Interface.csproj +++ b/CefSharp.OutOfProcess.Interface/CefSharp.OutOfProcess.Interface.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0 From 0ecc37cac041dbe0a438254cd0a998a48441ffd1 Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Thu, 26 Jan 2023 19:01:05 +0100 Subject: [PATCH 06/12] remove plattformtarget attributes --- CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj | 5 ++--- .../CefSharp.OutOfProcess.Wpf.HwndHost.csproj | 1 - .../CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj | 2 +- .../CefSharp.OutOfProcess.Wpf.OffscreenHost.csproj | 1 - 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj b/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj index b340afb..749af1e 100644 --- a/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj +++ b/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj @@ -1,9 +1,8 @@ - netstandard2.0 - AnyCPU - CefSharp.OutOfProcess + netstandard2.0 + CefSharp.OutOfProcess diff --git a/CefSharp.OutOfProcess.Wpf.HwndHost/CefSharp.OutOfProcess.Wpf.HwndHost.csproj b/CefSharp.OutOfProcess.Wpf.HwndHost/CefSharp.OutOfProcess.Wpf.HwndHost.csproj index 178a6c0..41402f4 100644 --- a/CefSharp.OutOfProcess.Wpf.HwndHost/CefSharp.OutOfProcess.Wpf.HwndHost.csproj +++ b/CefSharp.OutOfProcess.Wpf.HwndHost/CefSharp.OutOfProcess.Wpf.HwndHost.csproj @@ -3,7 +3,6 @@ netcoreapp3.1;net462 true - AnyCPU diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj index 912bcba..88ca3d0 100644 --- a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj @@ -2,7 +2,7 @@ WinExe true - net462 + netcoreapp3.1;net462 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.App true diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharp.OutOfProcess.Wpf.OffscreenHost.csproj b/CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharp.OutOfProcess.Wpf.OffscreenHost.csproj index d995f0a..fa0b1d8 100644 --- a/CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharp.OutOfProcess.Wpf.OffscreenHost.csproj +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharp.OutOfProcess.Wpf.OffscreenHost.csproj @@ -3,7 +3,6 @@ net462;netcoreapp3.1 true true - AnyCPU From a29ee5ed1ee79cada0218d459c18513f5dfb574d Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Thu, 26 Jan 2023 19:06:25 +0100 Subject: [PATCH 07/12] remove unused code, whitespace --- .../CefSharp.WPF.Internals/MonitorInfoEx.cs | 54 ------------------- ...OffscreenOutOfProcessChromiumWebBrowser.cs | 9 +--- .../Program.cs | 8 +-- 3 files changed, 3 insertions(+), 68 deletions(-) delete mode 100644 CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/MonitorInfoEx.cs diff --git a/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/MonitorInfoEx.cs b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/MonitorInfoEx.cs deleted file mode 100644 index 69701a8..0000000 --- a/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/MonitorInfoEx.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2018 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.Runtime.InteropServices; - -namespace CefSharp.Wpf.Internals -{ - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - internal struct MonitorInfoEx - { - private const int CCHDEVICENAME = 32; - /// - /// The size, in bytes, of the structure. Set this member to sizeof(MONITORINFOEX) (72) before calling the GetMonitorInfo function. - /// Doing so lets the function determine the type of structure you are passing to it. - /// - public int Size; - - /// - /// A RECT structure that specifies the display monitor rectangle, expressed in virtual-screen coordinates. - /// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. - /// - public RectStruct Monitor; - - /// - /// A RECT structure that specifies the work area rectangle of the display monitor that can be used by applications, - /// expressed in virtual-screen coordinates. Windows uses this rectangle to maximize an application on the monitor. - /// The rest of the area in rcMonitor contains system windows such as the task bar and side bars. - /// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. - /// - public RectStruct WorkArea; - - /// - /// The attributes of the display monitor. - /// - /// This member can be the following value: - /// 1 : MONITORINFOF_PRIMARY - /// - public uint Flags; - - /// - /// A string that specifies the device name of the monitor being used. Most applications have no use for a display monitor name, - /// and so can save some bytes by using a MONITORINFO structure. - /// - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] - public string DeviceName; - - public void Init() - { - Size = 40 + 2 * CCHDEVICENAME; - DeviceName = string.Empty; - } - } -} diff --git a/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs b/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs index 885ae9a..161cfb4 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs @@ -15,11 +15,6 @@ public class OffscreenOutOfProcessChromiumWebBrowser : OutOfProcessChromiumWebBr private readonly RenderHandler renderHandler; private readonly RenderHandler popupRenderHandler; - /// - /// The MonitorInfo based on the current hwnd - /// - private MonitorInfoEx monitorInfo; - public OffscreenOutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcessServer, int id, string address = "", IRequestContext requestContext = null) : base(outOfProcessServer, id, address, requestContext, true) { @@ -48,8 +43,8 @@ public OffscreenOutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcess /// ScreenInfo containing the current DPI scale factor protected virtual ScreenInfo? GetScreenInfo() { - CefSharp.Structs.Rect rect = monitorInfo.Monitor; - CefSharp.Structs.Rect availableRect = monitorInfo.WorkArea; + CefSharp.Structs.Rect rect; + CefSharp.Structs.Rect availableRect; if (DpiScaleFactor > 1.0) { diff --git a/CefSharp.OutOfProcess.BrowserProcess/Program.cs b/CefSharp.OutOfProcess.BrowserProcess/Program.cs index 418d0f5..c585f18 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/Program.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/Program.cs @@ -30,17 +30,11 @@ public static int Main(string[] args) MultiThreadedMessageLoop = false }; - //// // too slow when webgl is used - ////if (offscreenRendering) - ////{ - //// settings.SetOffScreenRenderingBestPerformanceArgs(); - ////} - var browserProcessHandler = new BrowserProcessHandler(parentProcessId, offscreenRendering); Cef.EnableWaitForBrowsersToClose(); - var success = Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: browserProcessHandler); + var success = Cef.Initialize(settings, performDependencyCheck:true, browserProcessHandler: browserProcessHandler); if(!success) { From b6f704f6efbd099d551fb2d47436f1295c25ff09 Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Thu, 26 Jan 2023 19:47:59 +0100 Subject: [PATCH 08/12] minor --- .../OffscreenOutOfProcessChromiumWebBrowser.cs | 11 +++++------ .../OutOfProcessChromiumWebBrowser.cs | 9 +++++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs b/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs index 161cfb4..9a91758 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs @@ -1,7 +1,6 @@ using CefSharp.Internals; using System; using CefSharp.OutOfProcess.Interface; -using CefSharp.Wpf.Internals; using CefSharp.Structs; using CefSharp.Enums; @@ -18,8 +17,8 @@ public class OffscreenOutOfProcessChromiumWebBrowser : OutOfProcessChromiumWebBr public OffscreenOutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcessServer, int id, string address = "", IRequestContext requestContext = null) : base(outOfProcessServer, id, address, requestContext, true) { - renderHandler = new RenderHandler($"0render_{_id}_"); - popupRenderHandler = new RenderHandler($"0render_{_id}_popup_"); + renderHandler = new RenderHandler($"0render_{Id}_"); + popupRenderHandler = new RenderHandler($"0render_{Id}_popup_"); } /// @@ -84,7 +83,7 @@ void IRenderWebBrowser.OnPaint(PaintElementType type, Structs.Rect dirtyRect, In ? popupRenderHandler.OnPaint(buffer, width, height) : renderHandler.OnPaint(buffer, width, height); - _outofProcessHostRpc.NotifyPaint(Id, type == PaintElementType.Popup, dirtyRectCopy, width, height, file); + OutofProcessHostRpc.NotifyPaint(Id, type == PaintElementType.Popup, dirtyRectCopy, width, height, file); } void IRenderWebBrowser.OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo) @@ -103,9 +102,9 @@ void IRenderWebBrowser.UpdateDragCursor(DragOperationsMask operation) // not implemented } - void IRenderWebBrowser.OnPopupShow(bool show) => _outofProcessHostRpc.NotifyPopupShow(Id, show); + void IRenderWebBrowser.OnPopupShow(bool show) => OutofProcessHostRpc.NotifyPopupShow(Id, show); - void IRenderWebBrowser.OnPopupSize(CefSharp.Structs.Rect rect) => _outofProcessHostRpc.NotifyPopupSize(Id, new Interface.Rect(rect.X, rect.Y, rect.Width, rect.Height)); + void IRenderWebBrowser.OnPopupSize(CefSharp.Structs.Rect rect) => OutofProcessHostRpc.NotifyPopupSize(Id, new Interface.Rect(rect.X, rect.Y, rect.Width, rect.Height)); void IRenderWebBrowser.OnImeCompositionRangeChanged(Structs.Range selectedRange, CefSharp.Structs.Rect[] characterBounds) { diff --git a/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs b/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs index 330fb56..49181b2 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs @@ -34,7 +34,7 @@ public class OutOfProcessChromiumWebBrowser : IWebBrowserInternal /// /// Internal ID used for tracking browsers between Processes; /// - private protected readonly int _id; + private readonly int _id; /// /// The managed cef browser adapter @@ -44,7 +44,7 @@ public class OutOfProcessChromiumWebBrowser : IWebBrowserInternal /// /// JSON RPC used for IPC with host /// - private protected readonly IOutOfProcessHostRpc _outofProcessHostRpc; + private readonly IOutOfProcessHostRpc _outofProcessHostRpc; /// /// Flag to guard the creation of the underlying browser - only one instance can be created @@ -176,6 +176,11 @@ public class OutOfProcessChromiumWebBrowser : IWebBrowserInternal /// The resource handler factory. public IResourceRequestHandlerFactory ResourceRequestHandlerFactory { get; set; } + /// + /// Gets the out of process host. + /// + private protected IOutOfProcessHostRpc OutofProcessHostRpc => _outofProcessHostRpc; + /// /// Event handler that will get called when the resource load for a navigation fails or is canceled. /// It's important to note this event is fired on a CEF UI thread, which by default is not the same as your application UI From 64782343a0ff14292feac8b335efd2ab4709c873 Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Thu, 26 Jan 2023 20:21:12 +0100 Subject: [PATCH 09/12] renormalize --- .../CefSharp.WPF.Internals/RectStruct.cs | 96 +- .../RenderHandler.cs | 134 +- CefSharp.OutOfProcess.Core/CefEventFlags.cs | 4 +- CefSharp.OutOfProcess.Core/MouseButtonType.cs | 40 +- .../OutOfProcessHost.cs | 562 ++-- .../ChromiumWebBrowser.cs | 2542 ++++++++--------- .../CefSharpWPF/WPFExtensions.cs | 4 +- 7 files changed, 1691 insertions(+), 1691 deletions(-) diff --git a/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/RectStruct.cs b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/RectStruct.cs index 9df00b9..7999a6a 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/RectStruct.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/RectStruct.cs @@ -1,48 +1,48 @@ -// Copyright © 2018 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.Runtime.InteropServices; -using CefSharp.Structs; - -namespace CefSharp.Wpf.Internals -{ - /// - /// The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle. - /// - /// - /// - /// By convention, the right and bottom edges of the rectangle are normally considered exclusive. - /// In other words, the pixel whose coordinates are ( right, bottom ) lies immediately outside of the the rectangle. - /// For example, when RECT is passed to the FillRect function, the rectangle is filled up to, but not including, - /// the right column and bottom row of pixels. This structure is identical to the RECTL structure. - /// - [StructLayout(LayoutKind.Sequential)] - public struct RectStruct - { - /// - /// The x-coordinate of the upper-left corner of the rectangle. - /// - public int Left; - - /// - /// The y-coordinate of the upper-left corner of the rectangle. - /// - public int Top; - - /// - /// The x-coordinate of the lower-right corner of the rectangle. - /// - public int Right; - - /// - /// The y-coordinate of the lower-right corner of the rectangle. - /// - public int Bottom; - - public static implicit operator Rect(RectStruct rect) - { - return new Rect(0, 0, rect.Right - rect.Left, rect.Bottom - rect.Top); - } - } -} +// Copyright © 2018 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.Runtime.InteropServices; +using CefSharp.Structs; + +namespace CefSharp.Wpf.Internals +{ + /// + /// The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle. + /// + /// + /// + /// By convention, the right and bottom edges of the rectangle are normally considered exclusive. + /// In other words, the pixel whose coordinates are ( right, bottom ) lies immediately outside of the the rectangle. + /// For example, when RECT is passed to the FillRect function, the rectangle is filled up to, but not including, + /// the right column and bottom row of pixels. This structure is identical to the RECTL structure. + /// + [StructLayout(LayoutKind.Sequential)] + public struct RectStruct + { + /// + /// The x-coordinate of the upper-left corner of the rectangle. + /// + public int Left; + + /// + /// The y-coordinate of the upper-left corner of the rectangle. + /// + public int Top; + + /// + /// The x-coordinate of the lower-right corner of the rectangle. + /// + public int Right; + + /// + /// The y-coordinate of the lower-right corner of the rectangle. + /// + public int Bottom; + + public static implicit operator Rect(RectStruct rect) + { + return new Rect(0, 0, rect.Right - rect.Left, rect.Bottom - rect.Top); + } + } +} diff --git a/CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs b/CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs index 2e584ae..036bb84 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs @@ -1,68 +1,68 @@ -using System; -using System.Runtime.InteropServices; -using System.IO.MemoryMappedFiles; - -namespace CefSharp.OutOfProcess.BrowserProcess -{ - internal sealed class RenderHandler : IDisposable - { - private readonly string renderFileNameTemplate; - private string renderFileName; - private MemoryMappedViewAccessor viewAccessor; - private MemoryMappedFile mappedFile; - private int currentAvailableBytes = 0; - - public RenderHandler(string fileName) - { - renderFileNameTemplate = fileName; - } - - public string OnPaint(IntPtr buffer, int width, int height) - { - const int bytesPerPixel = 32 / 8; - const int reserverdSizeBits = 2 * sizeof(int); - int maximumPixels = width * height; - int requiredfBytes = (maximumPixels * bytesPerPixel) + reserverdSizeBits; - - bool createNewBitmap = mappedFile == null || currentAvailableBytes < requiredfBytes; - - if (createNewBitmap) - { - currentAvailableBytes = requiredfBytes; - - if (mappedFile != null) - { - mappedFile.SafeMemoryMappedFileHandle.Close(); - mappedFile.Dispose(); - - viewAccessor.SafeMemoryMappedViewHandle.Close(); - viewAccessor.Dispose(); - } - - renderFileName = renderFileNameTemplate + Guid.NewGuid(); - - mappedFile = MemoryMappedFile.CreateNew(renderFileName, requiredfBytes, MemoryMappedFileAccess.ReadWrite); - viewAccessor = mappedFile.CreateViewAccessor(0, requiredfBytes, MemoryMappedFileAccess.Write); - } - - var ptr = viewAccessor.SafeMemoryMappedViewHandle.DangerousGetHandle(); - - Marshal.WriteInt32(ptr, width); - Marshal.WriteInt32(ptr + sizeof(int), height); - CopyMemory(ptr + reserverdSizeBits, buffer, (uint)requiredfBytes - reserverdSizeBits); - - viewAccessor.Flush(); - - return renderFileName; - } - - [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", ExactSpelling = true)] - public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); - - public void Dispose() - { - viewAccessor?.Dispose(); - mappedFile?.Dispose(); - } - } +using System; +using System.Runtime.InteropServices; +using System.IO.MemoryMappedFiles; + +namespace CefSharp.OutOfProcess.BrowserProcess +{ + internal sealed class RenderHandler : IDisposable + { + private readonly string renderFileNameTemplate; + private string renderFileName; + private MemoryMappedViewAccessor viewAccessor; + private MemoryMappedFile mappedFile; + private int currentAvailableBytes = 0; + + public RenderHandler(string fileName) + { + renderFileNameTemplate = fileName; + } + + public string OnPaint(IntPtr buffer, int width, int height) + { + const int bytesPerPixel = 32 / 8; + const int reserverdSizeBits = 2 * sizeof(int); + int maximumPixels = width * height; + int requiredfBytes = (maximumPixels * bytesPerPixel) + reserverdSizeBits; + + bool createNewBitmap = mappedFile == null || currentAvailableBytes < requiredfBytes; + + if (createNewBitmap) + { + currentAvailableBytes = requiredfBytes; + + if (mappedFile != null) + { + mappedFile.SafeMemoryMappedFileHandle.Close(); + mappedFile.Dispose(); + + viewAccessor.SafeMemoryMappedViewHandle.Close(); + viewAccessor.Dispose(); + } + + renderFileName = renderFileNameTemplate + Guid.NewGuid(); + + mappedFile = MemoryMappedFile.CreateNew(renderFileName, requiredfBytes, MemoryMappedFileAccess.ReadWrite); + viewAccessor = mappedFile.CreateViewAccessor(0, requiredfBytes, MemoryMappedFileAccess.Write); + } + + var ptr = viewAccessor.SafeMemoryMappedViewHandle.DangerousGetHandle(); + + Marshal.WriteInt32(ptr, width); + Marshal.WriteInt32(ptr + sizeof(int), height); + CopyMemory(ptr + reserverdSizeBits, buffer, (uint)requiredfBytes - reserverdSizeBits); + + viewAccessor.Flush(); + + return renderFileName; + } + + [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", ExactSpelling = true)] + public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); + + public void Dispose() + { + viewAccessor?.Dispose(); + mappedFile?.Dispose(); + } + } } \ No newline at end of file diff --git a/CefSharp.OutOfProcess.Core/CefEventFlags.cs b/CefSharp.OutOfProcess.Core/CefEventFlags.cs index de2576b..ef118a5 100644 --- a/CefSharp.OutOfProcess.Core/CefEventFlags.cs +++ b/CefSharp.OutOfProcess.Core/CefEventFlags.cs @@ -2,11 +2,11 @@ // // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -using System; +using System; namespace CefSharp.OutOfProcess -{ +{ /// /// Supported event bit flags. /// diff --git a/CefSharp.OutOfProcess.Core/MouseButtonType.cs b/CefSharp.OutOfProcess.Core/MouseButtonType.cs index 17b2a9b..ec921bc 100644 --- a/CefSharp.OutOfProcess.Core/MouseButtonType.cs +++ b/CefSharp.OutOfProcess.Core/MouseButtonType.cs @@ -2,24 +2,24 @@ // // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -namespace CefSharp.OutOfProcess -{ - /// - /// Values that represent mouse button types. - /// - public enum MouseButtonType - { - /// - /// Left Mouse Button - /// - Left = 0, - /// - /// Middle Mouse Button - /// - Middle, - /// - /// Right Mouse Button - /// - Right - } +namespace CefSharp.OutOfProcess +{ + /// + /// Values that represent mouse button types. + /// + public enum MouseButtonType + { + /// + /// Left Mouse Button + /// + Left = 0, + /// + /// Middle Mouse Button + /// + Middle, + /// + /// Right Mouse Button + /// + Right + } } \ No newline at end of file diff --git a/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs b/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs index 6c934f2..a499eff 100644 --- a/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs +++ b/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs @@ -1,281 +1,281 @@ -using CefSharp.OutOfProcess.Interface; -using CefSharp.OutOfProcess.Internal; -using PInvoke; -using StreamJsonRpc; -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; - -namespace CefSharp.OutOfProcess -{ - public class OutOfProcessHost : IOutOfProcessHostRpc, IDisposable - { - /// - /// The CefSharp.OutOfProcess.BrowserProcess.exe name - /// - public const string HostExeName = "CefSharp.OutOfProcess.BrowserProcess.exe"; - - private readonly bool _offscreenRendering; - private Process _browserProcess; - private JsonRpc _jsonRpc; - private IOutOfProcessClientRpc _outOfProcessClient; - private string _cefSharpVersion; - private string _cefVersion; - private string _chromiumVersion; - private int _uiThreadId; - private int _remoteuiThreadId; - private int _browserIdentifier = 1; - private string _outofProcessHostExePath; - private string _cachePath; - private ConcurrentDictionary _browsers = new ConcurrentDictionary(); - private TaskCompletionSource _processInitialized = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - private OutOfProcessHost(string outOfProcessHostExePath, string cachePath = null, bool offscreenRendering = false) - { - _outofProcessHostExePath = outOfProcessHostExePath; - _cachePath = cachePath; - _offscreenRendering = offscreenRendering; - } - - /// - /// UI Thread assocuated with this - /// - public int UiThreadId - { - get { return _uiThreadId; } - } - - /// - /// Thread Id of the UI Thread running in the Browser Process - /// - public int RemoteUiThreadId - { - get { return _remoteuiThreadId; } - } - - /// - /// CefSharp Version - /// - public string CefSharpVersion - { - get { return _cefSharpVersion; } - } - - /// - /// Cef Version - /// - public string CefVersion - { - get { return _cefVersion; } - } - - /// - /// Chromium Version - /// - public string ChromiumVersion - { - get { return _chromiumVersion; } - } - - /// - /// Sends an IPC message to the Browser Process instructing it - /// to create a new Out of process browser - /// - /// The that will host the browser - /// handle used to host the control - /// - /// - /// - public bool CreateBrowser(IChromiumWebBrowserInternal browser, IntPtr handle, string url, out int id) - { - id = _browserIdentifier++; - _ = _outOfProcessClient.CreateBrowser(handle, url, id); - - return _browsers.TryAdd(id, browser); - } - - public Task ShowDevTools(int browserId) - { - return _outOfProcessClient.ShowDevTools(browserId); - } - - public void SendMouseClickEvent(int browserId, int x, int y, MouseButtonType mouseButtonType, bool mouseUp, int clickCount, CefEventFlags eventFlags) - { - _outOfProcessClient.SendMouseClickEvent(browserId, x, y, mouseButtonType.ToString(), mouseUp, clickCount, (uint)eventFlags); - } - - public Task LoadUrl(int browserId, string url) - { - return _outOfProcessClient.LoadUrl(browserId, url); - } - - internal Task SendDevToolsMessageAsync(int browserId, string message) - { - return _outOfProcessClient.SendDevToolsMessage(browserId, message); - } - - private Task InitializedTask - { - get { return _processInitialized.Task; } - } - - private void Init() - { - var currentProcess = Process.GetCurrentProcess(); - - var args = $"--parentProcessId={currentProcess.Id} --cachePath={_cachePath} --offscreenRendering={_offscreenRendering}"; - - _browserProcess = Process.Start(new ProcessStartInfo(_outofProcessHostExePath, args) - { - RedirectStandardInput = true, - RedirectStandardOutput = true, - UseShellExecute = false, - }); - - _browserProcess.Exited += OnBrowserProcessExited; - - _jsonRpc = JsonRpc.Attach(_browserProcess.StandardInput.BaseStream, _browserProcess.StandardOutput.BaseStream); - - _outOfProcessClient = _jsonRpc.Attach(); - _jsonRpc.AllowModificationWhileListening = true; - _jsonRpc.AddLocalRpcTarget(this, null); - _jsonRpc.AllowModificationWhileListening = false; - - _uiThreadId = Kernel32.GetCurrentThreadId(); - } - - private void OnBrowserProcessExited(object sender, EventArgs e) - { - var exitCode = _browserProcess.ExitCode; - } - - void IOutOfProcessHostRpc.NotifyAddressChanged(int browserId, string address) - { - GetBrowser(browserId)?.SetAddress(address); - } - - void IOutOfProcessHostRpc.NotifyBrowserCreated(int browserId, IntPtr browserHwnd) - { - GetBrowser(browserId)?.OnAfterBrowserCreated(browserHwnd); - } - - void IOutOfProcessHostRpc.NotifyContextInitialized(int threadId, string cefSharpVersion, string cefVersion, string chromiumVersion) - { - _remoteuiThreadId = threadId; - _cefSharpVersion = cefSharpVersion; - _cefVersion = cefVersion; - _chromiumVersion = chromiumVersion; - - _processInitialized.TrySetResult(this); - } - - void IOutOfProcessHostRpc.NotifyDevToolsAgentDetached(int browserId) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - - } - } - - void IOutOfProcessHostRpc.NotifyDevToolsMessage(int browserId, string devToolsMessage) - { - GetBrowser(browserId)?.OnDevToolsMessage(devToolsMessage); - } - - void IOutOfProcessHostRpc.NotifyDevToolsReady(int browserId) - { - GetBrowser(browserId)?.OnDevToolsReady(); - } - - void IOutOfProcessHostRpc.NotifyLoadingStateChange(int browserId, bool canGoBack, bool canGoForward, bool isLoading) - { - GetBrowser(browserId)?.SetLoadingStateChange(canGoBack, canGoForward, isLoading); - } - - void IOutOfProcessHostRpc.NotifyStatusMessage(int browserId, string statusMessage) - { - GetBrowser(browserId)?.SetStatusMessage(statusMessage); - } - - void IOutOfProcessHostRpc.NotifyTitleChanged(int browserId, string title) - { - GetBrowser(browserId)?.SetTitle(title); - } - - void IOutOfProcessHostRpc.NotifyPaint(int browserId, bool isPopup, Rect dirtyRect, int width, int height, string file) - { - ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPaint(isPopup, dirtyRect, width, height, file); - } - - void IOutOfProcessHostRpc.NotifyPopupShow(int browserId, bool show) - { - ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPopupShow(show); - } - - void IOutOfProcessHostRpc.NotifyPopupSize(int browserId, Rect rect) - { - ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPopupSize(rect); - } - - public void NotifyMoveOrResizeStarted(int id, Rect rect = default) - { - _outOfProcessClient.NotifyMoveOrResizeStarted(id, rect); - } - - /// - /// Set whether the browser is focused. (Used for Normal Rendering e.g. WinForms) - /// - /// browser id - /// set focus - public void SetFocus(int id, bool focus) - { - _outOfProcessClient.SetFocus(id, focus); - } - - public void CloseBrowser(int id) - { - _ = _outOfProcessClient.CloseBrowser(id); - _browsers.TryRemove(id, out _); - } - - public void Dispose() - { - _ = _outOfProcessClient.CloseHost(); - _jsonRpc?.Dispose(); - _jsonRpc = null; - } - - public static Task CreateAsync(string path = HostExeName, string cachePath = null, bool offScreenRendering = false) - { - if(string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - var fullPath = Path.GetFullPath(path); - - if (!File.Exists(fullPath)) - { - throw new FileNotFoundException("Unable to find Host executable.", path); - } - - var host = new OutOfProcessHost(fullPath, cachePath, offScreenRendering); - - host.Init(); - - return host.InitializedTask; - } - - private IChromiumWebBrowserInternal GetBrowser(int browserId) - { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - return chromiumWebBrowser; - } - - return null; - } - } -} +using CefSharp.OutOfProcess.Interface; +using CefSharp.OutOfProcess.Internal; +using PInvoke; +using StreamJsonRpc; +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +namespace CefSharp.OutOfProcess +{ + public class OutOfProcessHost : IOutOfProcessHostRpc, IDisposable + { + /// + /// The CefSharp.OutOfProcess.BrowserProcess.exe name + /// + public const string HostExeName = "CefSharp.OutOfProcess.BrowserProcess.exe"; + + private readonly bool _offscreenRendering; + private Process _browserProcess; + private JsonRpc _jsonRpc; + private IOutOfProcessClientRpc _outOfProcessClient; + private string _cefSharpVersion; + private string _cefVersion; + private string _chromiumVersion; + private int _uiThreadId; + private int _remoteuiThreadId; + private int _browserIdentifier = 1; + private string _outofProcessHostExePath; + private string _cachePath; + private ConcurrentDictionary _browsers = new ConcurrentDictionary(); + private TaskCompletionSource _processInitialized = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + private OutOfProcessHost(string outOfProcessHostExePath, string cachePath = null, bool offscreenRendering = false) + { + _outofProcessHostExePath = outOfProcessHostExePath; + _cachePath = cachePath; + _offscreenRendering = offscreenRendering; + } + + /// + /// UI Thread assocuated with this + /// + public int UiThreadId + { + get { return _uiThreadId; } + } + + /// + /// Thread Id of the UI Thread running in the Browser Process + /// + public int RemoteUiThreadId + { + get { return _remoteuiThreadId; } + } + + /// + /// CefSharp Version + /// + public string CefSharpVersion + { + get { return _cefSharpVersion; } + } + + /// + /// Cef Version + /// + public string CefVersion + { + get { return _cefVersion; } + } + + /// + /// Chromium Version + /// + public string ChromiumVersion + { + get { return _chromiumVersion; } + } + + /// + /// Sends an IPC message to the Browser Process instructing it + /// to create a new Out of process browser + /// + /// The that will host the browser + /// handle used to host the control + /// + /// + /// + public bool CreateBrowser(IChromiumWebBrowserInternal browser, IntPtr handle, string url, out int id) + { + id = _browserIdentifier++; + _ = _outOfProcessClient.CreateBrowser(handle, url, id); + + return _browsers.TryAdd(id, browser); + } + + public Task ShowDevTools(int browserId) + { + return _outOfProcessClient.ShowDevTools(browserId); + } + + public void SendMouseClickEvent(int browserId, int x, int y, MouseButtonType mouseButtonType, bool mouseUp, int clickCount, CefEventFlags eventFlags) + { + _outOfProcessClient.SendMouseClickEvent(browserId, x, y, mouseButtonType.ToString(), mouseUp, clickCount, (uint)eventFlags); + } + + public Task LoadUrl(int browserId, string url) + { + return _outOfProcessClient.LoadUrl(browserId, url); + } + + internal Task SendDevToolsMessageAsync(int browserId, string message) + { + return _outOfProcessClient.SendDevToolsMessage(browserId, message); + } + + private Task InitializedTask + { + get { return _processInitialized.Task; } + } + + private void Init() + { + var currentProcess = Process.GetCurrentProcess(); + + var args = $"--parentProcessId={currentProcess.Id} --cachePath={_cachePath} --offscreenRendering={_offscreenRendering}"; + + _browserProcess = Process.Start(new ProcessStartInfo(_outofProcessHostExePath, args) + { + RedirectStandardInput = true, + RedirectStandardOutput = true, + UseShellExecute = false, + }); + + _browserProcess.Exited += OnBrowserProcessExited; + + _jsonRpc = JsonRpc.Attach(_browserProcess.StandardInput.BaseStream, _browserProcess.StandardOutput.BaseStream); + + _outOfProcessClient = _jsonRpc.Attach(); + _jsonRpc.AllowModificationWhileListening = true; + _jsonRpc.AddLocalRpcTarget(this, null); + _jsonRpc.AllowModificationWhileListening = false; + + _uiThreadId = Kernel32.GetCurrentThreadId(); + } + + private void OnBrowserProcessExited(object sender, EventArgs e) + { + var exitCode = _browserProcess.ExitCode; + } + + void IOutOfProcessHostRpc.NotifyAddressChanged(int browserId, string address) + { + GetBrowser(browserId)?.SetAddress(address); + } + + void IOutOfProcessHostRpc.NotifyBrowserCreated(int browserId, IntPtr browserHwnd) + { + GetBrowser(browserId)?.OnAfterBrowserCreated(browserHwnd); + } + + void IOutOfProcessHostRpc.NotifyContextInitialized(int threadId, string cefSharpVersion, string cefVersion, string chromiumVersion) + { + _remoteuiThreadId = threadId; + _cefSharpVersion = cefSharpVersion; + _cefVersion = cefVersion; + _chromiumVersion = chromiumVersion; + + _processInitialized.TrySetResult(this); + } + + void IOutOfProcessHostRpc.NotifyDevToolsAgentDetached(int browserId) + { + if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) + { + + } + } + + void IOutOfProcessHostRpc.NotifyDevToolsMessage(int browserId, string devToolsMessage) + { + GetBrowser(browserId)?.OnDevToolsMessage(devToolsMessage); + } + + void IOutOfProcessHostRpc.NotifyDevToolsReady(int browserId) + { + GetBrowser(browserId)?.OnDevToolsReady(); + } + + void IOutOfProcessHostRpc.NotifyLoadingStateChange(int browserId, bool canGoBack, bool canGoForward, bool isLoading) + { + GetBrowser(browserId)?.SetLoadingStateChange(canGoBack, canGoForward, isLoading); + } + + void IOutOfProcessHostRpc.NotifyStatusMessage(int browserId, string statusMessage) + { + GetBrowser(browserId)?.SetStatusMessage(statusMessage); + } + + void IOutOfProcessHostRpc.NotifyTitleChanged(int browserId, string title) + { + GetBrowser(browserId)?.SetTitle(title); + } + + void IOutOfProcessHostRpc.NotifyPaint(int browserId, bool isPopup, Rect dirtyRect, int width, int height, string file) + { + ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPaint(isPopup, dirtyRect, width, height, file); + } + + void IOutOfProcessHostRpc.NotifyPopupShow(int browserId, bool show) + { + ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPopupShow(show); + } + + void IOutOfProcessHostRpc.NotifyPopupSize(int browserId, Rect rect) + { + ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPopupSize(rect); + } + + public void NotifyMoveOrResizeStarted(int id, Rect rect = default) + { + _outOfProcessClient.NotifyMoveOrResizeStarted(id, rect); + } + + /// + /// Set whether the browser is focused. (Used for Normal Rendering e.g. WinForms) + /// + /// browser id + /// set focus + public void SetFocus(int id, bool focus) + { + _outOfProcessClient.SetFocus(id, focus); + } + + public void CloseBrowser(int id) + { + _ = _outOfProcessClient.CloseBrowser(id); + _browsers.TryRemove(id, out _); + } + + public void Dispose() + { + _ = _outOfProcessClient.CloseHost(); + _jsonRpc?.Dispose(); + _jsonRpc = null; + } + + public static Task CreateAsync(string path = HostExeName, string cachePath = null, bool offScreenRendering = false) + { + if(string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException(nameof(path)); + } + + var fullPath = Path.GetFullPath(path); + + if (!File.Exists(fullPath)) + { + throw new FileNotFoundException("Unable to find Host executable.", path); + } + + var host = new OutOfProcessHost(fullPath, cachePath, offScreenRendering); + + host.Init(); + + return host.InitializedTask; + } + + private IChromiumWebBrowserInternal GetBrowser(int browserId) + { + if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) + { + return chromiumWebBrowser; + } + + return null; + } + } +} diff --git a/CefSharp.OutOfProcess.Wpf.HwndHost/ChromiumWebBrowser.cs b/CefSharp.OutOfProcess.Wpf.HwndHost/ChromiumWebBrowser.cs index 8db5ac8..93df485 100644 --- a/CefSharp.OutOfProcess.Wpf.HwndHost/ChromiumWebBrowser.cs +++ b/CefSharp.OutOfProcess.Wpf.HwndHost/ChromiumWebBrowser.cs @@ -1,1271 +1,1271 @@ -// Copyright © 2022 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 CefSharp.OutOfProcess.Internal; -using CefSharp.OutOfProcess.WinForms; -using CefSharp.OutOfProcess.Wpf.HwndHost.Internals; -using CefSharp.Dom; -using PInvoke; -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Threading; -using Window = System.Windows.Window; - -namespace CefSharp.OutOfProcess.Wpf.HwndHost -{ - /// - /// ChromiumWebBrowser is the WPF web browser control - /// - /// - /// - /// based on https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/walkthrough-hosting-a-win32-control-in-wpf - /// and https://stackoverflow.com/questions/6500336/custom-dwm-drawn-window-frame-flickers-on-resizing-if-the-window-contains-a-hwnd/17471534#17471534 - public class ChromiumWebBrowser : System.Windows.Interop.HwndHost, IChromiumWebBrowserInternal - { - private const string BrowserNotInitializedExceptionErrorMessage = - "The ChromiumWebBrowser instance creates the underlying Chromium Embedded Framework (CEF) browser instance in an async fashion. " + - "The undelying CefBrowser instance is not yet initialized. Use the IsBrowserInitializedChanged event and check " + - "the IsBrowserInitialized property to determine when the browser has been initialized."; - - private const int WS_CHILD = 0x40000000, - WS_VISIBLE = 0x10000000, - LBS_NOTIFY = 0x00000001, - HOST_ID = 0x00000002, - LISTBOX_ID = 0x00000001, - WS_VSCROLL = 0x00200000, - WS_BORDER = 0x00800000, - WS_CLIPCHILDREN = 0x02000000; - - [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)] - private static extern IntPtr CreateWindowEx(int dwExStyle, - string lpszClassName, - string lpszWindowName, - int style, - int x, int y, - int width, int height, - IntPtr hwndParent, - IntPtr hMenu, - IntPtr hInst, - [MarshalAs(UnmanagedType.AsAny)] object pvParam); - - [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)] - private static extern bool DestroyWindow(IntPtr hwnd); - - [DllImport("user32.dll", EntryPoint = "GetWindowLong")] - private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int index); - - [DllImport("user32.dll", EntryPoint = "SetWindowLong")] - private static extern int SetWindowLong32(HandleRef hWnd, int index, int dwNewLong); - - [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] - private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int index, IntPtr dwNewLong); - - private OutOfProcessHost _host; - private IntPtr _browserHwnd = IntPtr.Zero; - private OutOfProcessConnectionTransport _devToolsContextConnectionTransport; - private IDevToolsContext _devToolsContext; - private int _id; - private bool _devToolsReady; - - /// - /// Handle we'll use to host the browser - /// - private IntPtr _hwndHost; - /// - /// The ignore URI change - /// - private bool _ignoreUriChange; - /// - /// Initial address - /// - private readonly string _initialAddress; - /// - /// Has the underlying Cef Browser been created (slightly different to initliazed in that - /// the browser is initialized in an async fashion) - /// - private bool _browserCreated; - /// - /// The browser initialized - boolean represented as 0 (false) and 1(true) as we use Interlocker to increment/reset - /// - private int _browserInitialized; - /// - /// A flag that indicates whether or not the designer is active - /// NOTE: Needs to be static for OnApplicationExit - /// - private static bool DesignMode; - - /// - /// The value for disposal, if it's 1 (one) then this instance is either disposed - /// or in the process of getting disposed - /// - private int _disposeSignaled; - - /// - /// Current DPI Scale - /// - private double _dpiScale; - - /// - /// The HwndSource RootVisual (Window) - We store a reference - /// to unsubscribe event handlers - /// - private Window _sourceWindow; - - /// - /// Store the previous window state, used to determine if the - /// Windows was previous - /// and resume rendering - /// - private WindowState _previousWindowState; - - /// - /// This flag is set when the browser gets focus before the underlying CEF browser - /// has been initialized. - /// - private bool _initialFocus; - - /// - /// Activates browser upon creation, the default value is false. Prior to version 73 - /// the default behaviour was to activate browser on creation (Equivilent of setting this property to true). - /// To restore this behaviour set this value to true immediately after you create the instance. - /// https://bitbucket.org/chromiumembedded/cef/issues/1856/branch-2526-cef-activates-browser-window - /// - public bool ActivateBrowserOnCreation { get; set; } - - /// - /// Gets a value indicating whether this instance is disposed. - /// - /// if this instance is disposed; otherwise, . - public bool IsDisposed - { - get - { - return Interlocked.CompareExchange(ref _disposeSignaled, 1, 1) == 1; - } - } - - /// - public event EventHandler DOMContentLoaded; - /// - public event EventHandler BrowserProcessCrashed; - /// - public event EventHandler FrameAttached; - /// - public event EventHandler FrameDetached; - /// - public event EventHandler FrameNavigated; - /// - public event EventHandler JavaScriptLoad; - /// - public event EventHandler RuntimeExceptionThrown; - /// - public event EventHandler Popup; - /// - public event EventHandler NetworkRequest; - /// - public event EventHandler NetworkRequestFailed; - /// - public event EventHandler NetworkRequestFinished; - /// - public event EventHandler NetworkRequestServedFromCache; - /// - public event EventHandler NetworkResponse; - /// - public event EventHandler AddressChanged; - /// - public event EventHandler LoadingStateChanged; - /// - public event EventHandler StatusMessage; - /// - public event EventHandler ConsoleMessage; - /// - public event EventHandler LifecycleEvent; - /// - public event EventHandler DevToolsContextAvailable; - - /// - /// Navigates to the previous page in the browser history. Will automatically be enabled/disabled depending on the - /// browser state. - /// - /// The back command. - public ICommand BackCommand { get; private set; } - /// - /// Navigates to the next page in the browser history. Will automatically be enabled/disabled depending on the - /// browser state. - /// - /// The forward command. - public ICommand ForwardCommand { get; private set; } - /// - /// Reloads the content of the current page. Will automatically be enabled/disabled depending on the browser state. - /// - /// The reload command. - public ICommand ReloadCommand { get; private set; } - /// - /// Prints the current browser contents. - /// - /// The print command. - public ICommand PrintCommand { get; private set; } - /// - /// Increases the zoom level. - /// - /// The zoom in command. - public ICommand ZoomInCommand { get; private set; } - /// - /// Decreases the zoom level. - /// - /// The zoom out command. - public ICommand ZoomOutCommand { get; private set; } - /// - /// Resets the zoom level to the default. (100%) - /// - /// The zoom reset command. - public ICommand ZoomResetCommand { get; private set; } - /// - /// Opens up a new program window (using the default text editor) where the source code of the currently displayed web - /// page is shown. - /// - /// The view source command. - public ICommand ViewSourceCommand { get; private set; } - /// - /// Command which cleans up the Resources used by the ChromiumWebBrowser - /// - /// The cleanup command. - public ICommand CleanupCommand { get; private set; } - /// - /// Stops loading the current page. - /// - /// The stop command. - public ICommand StopCommand { get; private set; } - /// - /// Cut selected text to the clipboard. - /// - /// The cut command. - public ICommand CutCommand { get; private set; } - /// - /// Copy selected text to the clipboard. - /// - /// The copy command. - public ICommand CopyCommand { get; private set; } - /// - /// Paste text from the clipboard. - /// - /// The paste command. - public ICommand PasteCommand { get; private set; } - /// - /// Select all text. - /// - /// The select all command. - public ICommand SelectAllCommand { get; private set; } - /// - /// Undo last action. - /// - /// The undo command. - public ICommand UndoCommand { get; private set; } - /// - /// Redo last action. - /// - /// The redo command. - public ICommand RedoCommand { get; private set; } - - /// - /// Initializes a new instance of the instance. - /// - /// Out of process host - /// address to load initially - public ChromiumWebBrowser(OutOfProcessHost host, string initialAddress = null) - { - if(host == null) - { - throw new ArgumentNullException(nameof(host)); - } - - _host = host; - _initialAddress = initialAddress; - - Focusable = true; - FocusVisualStyle = null; - - WebBrowser = this; - - SizeChanged += OnSizeChanged; - IsVisibleChanged += OnIsVisibleChanged; - - BackCommand = new DelegateCommand(() => _devToolsContext.GoBackAsync(), () => CanGoBack); - ForwardCommand = new DelegateCommand(() => _devToolsContext.GoForwardAsync(), () => CanGoForward); - ReloadCommand = new DelegateCommand(() => _devToolsContext.ReloadAsync(), () => !IsLoading); - //PrintCommand = new DelegateCommand(this.Print); - //ZoomInCommand = new DelegateCommand(ZoomIn); - //ZoomOutCommand = new DelegateCommand(ZoomOut); - //ZoomResetCommand = new DelegateCommand(ZoomReset); - //ViewSourceCommand = new DelegateCommand(this.ViewSource); - CleanupCommand = new DelegateCommand(Dispose); - //StopCommand = new DelegateCommand(this.Stop); - //CutCommand = new DelegateCommand(this.Cut); - //CopyCommand = new DelegateCommand(this.Copy); - //PasteCommand = new DelegateCommand(this.Paste); - //SelectAllCommand = new DelegateCommand(this.SelectAll); - //UndoCommand = new DelegateCommand(this.Undo); - //RedoCommand = new DelegateCommand(this.Redo); - - PresentationSource.AddSourceChangedHandler(this, PresentationSourceChangedHandler); - - UseLayoutRounding = true; - } - - /// - int IChromiumWebBrowserInternal.Id - { - get { return _id; } - } - - /// - /// DevToolsContext - provides communication with the underlying browser - /// - public IDevToolsContext DevToolsContext - { - get - { - if (_devToolsReady) - { - return _devToolsContext; - } - - return default; - } - } - - /// - public bool IsBrowserInitialized => _browserHwnd != IntPtr.Zero; - - - /// - public Frame[] Frames => _devToolsContext == null ? null : _devToolsContext.Frames; - - /// - public Frame MainFrame => _devToolsContext == null ? null : _devToolsContext.MainFrame; - - /// - void IChromiumWebBrowserInternal.OnDevToolsMessage(string jsonMsg) - { - _devToolsContextConnectionTransport?.InvokeMessageReceived(jsonMsg); - } - - /// - void IChromiumWebBrowserInternal.OnDevToolsReady() - { - var ctx = (DevToolsContext)_devToolsContext; - - ctx.DOMContentLoaded += DOMContentLoaded; - ctx.Error += BrowserProcessCrashed; - ctx.FrameAttached += FrameAttached; - ctx.FrameDetached += FrameDetached; - ctx.FrameNavigated += FrameNavigated; - ctx.Load += JavaScriptLoad; - ctx.PageError += RuntimeExceptionThrown; - ctx.Popup += Popup; - ctx.Request += NetworkRequest; - ctx.RequestFailed += NetworkRequestFailed; - ctx.RequestFinished += NetworkRequestFinished; - ctx.RequestServedFromCache += NetworkRequestServedFromCache; - ctx.Response += NetworkResponse; - ctx.Console += ConsoleMessage; - ctx.LifecycleEvent += LifecycleEvent; - - _ = ctx.InvokeGetFrameTreeAsync().ContinueWith(t => - { - _devToolsReady = true; - - DevToolsContextAvailable?.Invoke(this, EventArgs.Empty); - - //NOW the user can start using the devtools context - }, TaskScheduler.Current); - - // Only call Load if initialAddress is null and Address is not empty - if (string.IsNullOrEmpty(_initialAddress) && !string.IsNullOrEmpty(Address)) - { - LoadUrl(Address); - } - } - - /// - public void LoadUrl(string url) - { - _ = _devToolsContext.GoToAsync(url); - } - - /// - public Task LoadUrlAsync(string url, int? timeout = null, WaitUntilNavigation[] waitUntil = null) - { - return _devToolsContext.GoToAsync(url, timeout, waitUntil); - } - - /// - public Task GoBackAsync(NavigationOptions options = null) - { - return _devToolsContext.GoBackAsync(options); - } - - /// - public Task GoForwardAsync(NavigationOptions options = null) - { - return _devToolsContext.GoForwardAsync(options); - } - - private void PresentationSourceChangedHandler(object sender, SourceChangedEventArgs args) - { - if (args.NewSource != null) - { - var source = (HwndSource)args.NewSource; - - var matrix = source.CompositionTarget.TransformToDevice; - - _dpiScale = matrix.M11; - - var window = source.RootVisual as Window; - if (window != null) - { - window.StateChanged += OnWindowStateChanged; - window.LocationChanged += OnWindowLocationChanged; - _sourceWindow = window; - - if (CleanupElement == null) - { - CleanupElement = window; - } - else if (CleanupElement is Window parent) - { - //If the CleanupElement is a window then move it to the new Window - if (parent != window) - { - CleanupElement = window; - } - } - } - } - else if (args.OldSource != null) - { - var window = args.OldSource.RootVisual as Window; - if (window != null) - { - window.StateChanged -= OnWindowStateChanged; - window.LocationChanged -= OnWindowLocationChanged; - _sourceWindow = null; - } - } - } - - /// - protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi) - { - _dpiScale = newDpi.DpiScaleX; - - //If the DPI changed then we need to resize. - ResizeBrowser((int)ActualWidth, (int)ActualHeight); - - base.OnDpiChanged(oldDpi, newDpi); - } - - private void OnSizeChanged(object sender, SizeChangedEventArgs e) - { - ResizeBrowser((int)e.NewSize.Width, (int)e.NewSize.Height); - } - - /// - protected override HandleRef BuildWindowCore(HandleRef hwndParent) - { - if (_hwndHost == IntPtr.Zero) - { - _hwndHost = CreateWindowEx(0, "static", "", - WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, - 0, 0, - (int)ActualWidth, (int)ActualHeight, - hwndParent.Handle, - (IntPtr)HOST_ID, - IntPtr.Zero, - 0); - } - - _host.CreateBrowser(this, _hwndHost, url: _initialAddress, out _id); - - _devToolsContextConnectionTransport = new OutOfProcessConnectionTransport(_id, _host); - - var connection = DevToolsConnection.Attach(_devToolsContextConnectionTransport); - _devToolsContext = Dom.DevToolsContext.CreateForOutOfProcess(connection); - - return new HandleRef(null, _hwndHost); - } - - /// - protected override void DestroyWindowCore(HandleRef hwnd) - { - DestroyWindow(hwnd.Handle); - } - - /// - protected override bool TabIntoCore(TraversalRequest request) - { - if(InternalIsBrowserInitialized()) - { - _host.SetFocus(_id, true); - - return true; - } - - return base.TabIntoCore(request); - } - - /// - protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) - { - if(!e.Handled) - { - if (InternalIsBrowserInitialized()) - { - _host.SetFocus(_id, true); - } - else - { - _initialFocus = true; - } - } - - base.OnGotKeyboardFocus(e); - } - - /// - protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) - { - if (!e.Handled) - { - if (InternalIsBrowserInitialized()) - { - _host.SetFocus(_id, false); - } - else - { - _initialFocus = false; - } - } - - base.OnLostKeyboardFocus(e); - } - - - /// - protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) - { - const int WM_SETFOCUS = 0x0007; - const int WM_MOUSEACTIVATE = 0x0021; - switch (msg) - { - case WM_SETFOCUS: - case WM_MOUSEACTIVATE: - { - if(InternalIsBrowserInitialized()) - { - - _host.SetFocus(_id, true); - - handled = true; - - return IntPtr.Zero; - } - break; - } - } - return base.WndProc(hwnd, msg, wParam, lParam, ref handled); - } - - /// - /// If not in design mode; Releases unmanaged and - optionally - managed resources for the - /// - /// to release both managed and unmanaged resources; to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - // Attempt to move the disposeSignaled state from 0 to 1. If successful, we can be assured that - // this thread is the first thread to do so, and can safely dispose of the object. - if (Interlocked.CompareExchange(ref _disposeSignaled, 1, 0) != 0) - { - return; - } - - if (!DesignMode) - { - InternalDispose(disposing); - } - - base.Dispose(disposing); - } - - /// - /// Releases unmanaged and - optionally - managed resources for the - /// - /// to release both managed and unmanaged resources; to release only unmanaged resources. - /// - /// This method cannot be inlined as the designer will attempt to load libcef.dll and will subsiquently throw an exception. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private void InternalDispose(bool disposing) - { - Interlocked.Exchange(ref _browserInitialized, 0); - - if (disposing) - { - SizeChanged -= OnSizeChanged; - IsVisibleChanged -= OnIsVisibleChanged; - - PresentationSource.RemoveSourceChangedHandler(this, PresentationSourceChangedHandler); - // Release window event listeners if PresentationSourceChangedHandler event wasn't - // fired before Dispose - if (_sourceWindow != null) - { - _sourceWindow.StateChanged -= OnWindowStateChanged; - _sourceWindow.LocationChanged -= OnWindowLocationChanged; - _sourceWindow = null; - } - - - UiThreadRunAsync(() => - { - OnIsBrowserInitializedChanged(true, false); - - //To Minic the WPF behaviour this happens after OnIsBrowserInitializedChanged - IsBrowserInitializedChanged?.Invoke(this, EventArgs.Empty); - - WebBrowser = null; - }); - - // Don't maintain a reference to event listeners anylonger: - //ConsoleMessage = null; - //FrameLoadEnd = null; - //FrameLoadStart = null; - IsBrowserInitializedChanged = null; - //LoadError = null; - LoadingStateChanged = null; - StatusMessage = null; - TitleChanged = null; - - if (CleanupElement != null) - { - CleanupElement.Unloaded -= OnCleanupElementUnloaded; - } - } - } - - /// - void IChromiumWebBrowserInternal.SetAddress(string address) - { - UiThreadRunAsync(() => - { - _ignoreUriChange = true; - SetCurrentValue(AddressProperty, address); - _ignoreUriChange = false; - - // The tooltip should obviously also be reset (and hidden) when the address changes. - SetCurrentValue(TooltipTextProperty, null); - }); - } - - /// - void IChromiumWebBrowserInternal.SetLoadingStateChange(bool canGoBack, bool canGoForward, bool isLoading) - { - UiThreadRunAsync(() => - { - SetCurrentValue(CanGoBackProperty, canGoBack); - SetCurrentValue(CanGoForwardProperty, canGoForward); - SetCurrentValue(IsLoadingProperty, isLoading); - - ((DelegateCommand)BackCommand).RaiseCanExecuteChanged(); - ((DelegateCommand)ForwardCommand).RaiseCanExecuteChanged(); - ((DelegateCommand)ReloadCommand).RaiseCanExecuteChanged(); - }); - - LoadingStateChanged?.Invoke(this, new LoadingStateChangedEventArgs(canGoBack, canGoForward, isLoading)); - } - - /// - void IChromiumWebBrowserInternal.SetTitle(string title) - { - UiThreadRunAsync(() => SetCurrentValue(TitleProperty, title)); - } - - /// - /// Sets the tooltip text. - /// - /// The tooltip text. - //void IWebBrowserInternal.SetTooltipText(string tooltipText) - //{ - // UiThreadRunAsync(() => SetCurrentValue(TooltipTextProperty, tooltipText)); - //} - - /// - /// Handles the event. - /// - /// The instance containing the event data. - //void IWebBrowserInternal.OnConsoleMessage(ConsoleMessageEventArgs args) - //{ - // ConsoleMessage?.Invoke(this, args); - //} - - /// - void IChromiumWebBrowserInternal.SetStatusMessage(string msg) - { - StatusMessage?.Invoke(this, new StatusMessageEventArgs(msg)); - } - - /// - void IChromiumWebBrowserInternal.OnAfterBrowserCreated(IntPtr hwnd) - { - if (IsDisposed) - { - return; - } - - _browserHwnd = hwnd; - - Interlocked.Exchange(ref _browserInitialized, 1); - - UiThreadRunAsync(() => - { - if (!IsDisposed) - { - OnIsBrowserInitializedChanged(false, true); - //To Minic the WPF behaviour this happens after OnIsBrowserInitializedChanged - IsBrowserInitializedChanged?.Invoke(this, EventArgs.Empty); - } - }); - - ResizeBrowser((int)ActualWidth, (int)ActualHeight); - - if (_initialFocus) - { - _host.SetFocus(_id, true); - } - } - - /// - /// A flag that indicates whether the state of the control current supports the GoBack action (true) or not (false). - /// - /// true if this instance can go back; otherwise, false. - /// In the WPF control, this property is implemented as a Dependency Property and fully supports data - /// binding. - public bool CanGoBack - { - get { return (bool)GetValue(CanGoBackProperty); } - } - - /// - /// The can go back property - /// - public static DependencyProperty CanGoBackProperty = DependencyProperty.Register(nameof(CanGoBack), typeof(bool), typeof(ChromiumWebBrowser)); - - /// - /// A flag that indicates whether the state of the control currently supports the GoForward action (true) or not (false). - /// - /// true if this instance can go forward; otherwise, false. - /// In the WPF control, this property is implemented as a Dependency Property and fully supports data - /// binding. - public bool CanGoForward - { - get { return (bool)GetValue(CanGoForwardProperty); } - } - - /// - /// The can go forward property - /// - public static DependencyProperty CanGoForwardProperty = DependencyProperty.Register(nameof(CanGoForward), typeof(bool), typeof(ChromiumWebBrowser)); - - /// - /// The address (URL) which the browser control is currently displaying. - /// Will automatically be updated as the user navigates to another page (e.g. by clicking on a link). - /// - /// The address. - /// In the WPF control, this property is implemented as a Dependency Property and fully supports data - /// binding. - public string Address - { - get { return (string)GetValue(AddressProperty); } - set { SetValue(AddressProperty, value); } - } - - /// - /// The address property - /// - public static readonly DependencyProperty AddressProperty = - DependencyProperty.Register(nameof(Address), typeof(string), typeof(ChromiumWebBrowser), - new UIPropertyMetadata(null, OnAddressChanged)); - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private static void OnAddressChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) - { - var owner = (ChromiumWebBrowser)sender; - var oldValue = (string)args.OldValue; - var newValue = (string)args.NewValue; - - owner.OnAddressChanged(oldValue, newValue); - } - - /// - /// Called when [address changed]. - /// - /// The old value. - /// The new value. - protected virtual void OnAddressChanged(string oldValue, string newValue) - { - if (_ignoreUriChange || newValue == null || !InternalIsBrowserInitialized()) - { - return; - } - - LoadUrl(newValue); - } - - /// - /// A flag that indicates whether the control is currently loading one or more web pages (true) or not (false). - /// - /// true if this instance is loading; otherwise, false. - /// In the WPF control, this property is implemented as a Dependency Property and fully supports data - /// binding. - public bool IsLoading - { - get { return (bool)GetValue(IsLoadingProperty); } - } - - /// - /// The is loading property - /// - public static readonly DependencyProperty IsLoadingProperty = - DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(ChromiumWebBrowser), new PropertyMetadata(false)); - - /// - /// Event called after the underlying CEF browser instance has been created and - /// when the instance has been Disposed. - /// will be true when the underlying CEF Browser - /// has been created and false when the browser is being Disposed. - /// - public event EventHandler IsBrowserInitializedChanged; - - /// - /// Called when [is browser initialized changed]. - /// - /// if set to true [old value]. - /// if set to true [new value]. - protected virtual void OnIsBrowserInitializedChanged(bool oldValue, bool newValue) - { - if (newValue && !IsDisposed) - { - //var task = this.GetZoomLevelAsync(); - //task.ContinueWith(previous => - //{ - // if (previous.Status == TaskStatus.RanToCompletion) - // { - // UiThreadRunAsync(() => - // { - // if (!IsDisposed) - // { - // SetCurrentValue(ZoomLevelProperty, previous.Result); - // } - // }); - // } - // else - // { - // throw new InvalidOperationException("Unexpected failure of calling CEF->GetZoomLevelAsync", previous.Exception); - // } - //}, TaskContinuationOptions.ExecuteSynchronously); - } - } - - /// - /// The title of the web page being currently displayed. - /// - /// The title. - /// This property is implemented as a Dependency Property and fully supports data binding. - public string Title - { - get { return (string)GetValue(TitleProperty); } - set { SetValue(TitleProperty, value); } - } - - /// - /// The title property - /// - public static readonly DependencyProperty TitleProperty = - DependencyProperty.Register(nameof(Title), typeof(string), typeof(ChromiumWebBrowser), new PropertyMetadata(null, OnTitleChanged)); - - /// - /// Event handler that will get called when the browser title changes - /// - public event DependencyPropertyChangedEventHandler TitleChanged; - - /// - /// Handles the event. - /// - /// The d. - /// The instance containing the event data. - private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var owner = (ChromiumWebBrowser)d; - - owner.TitleChanged?.Invoke(owner, e); - } - - /// - /// The zoom level at which the browser control is currently displaying. - /// Can be set to 0 to clear the zoom level (resets to default zoom level). - /// - /// The zoom level. - public double ZoomLevel - { - get { return (double)GetValue(ZoomLevelProperty); } - set { SetValue(ZoomLevelProperty, value); } - } - - /// - /// The zoom level property - /// - public static readonly DependencyProperty ZoomLevelProperty = - DependencyProperty.Register(nameof(ZoomLevel), typeof(double), typeof(ChromiumWebBrowser), - new UIPropertyMetadata(0d, OnZoomLevelChanged)); - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private static void OnZoomLevelChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) - { - var owner = (ChromiumWebBrowser)sender; - var oldValue = (double)args.OldValue; - var newValue = (double)args.NewValue; - - owner.OnZoomLevelChanged(oldValue, newValue); - } - - /// - /// Called when [zoom level changed]. - /// - /// The old value. - /// The new value. - protected virtual void OnZoomLevelChanged(double oldValue, double newValue) - { - throw new NotImplementedException(); - } - - /// - /// Specifies the amount used to increase/decrease to ZoomLevel by - /// By Default this value is 0.10 - /// - /// The zoom level increment. - public double ZoomLevelIncrement - { - get { return (double)GetValue(ZoomLevelIncrementProperty); } - set { SetValue(ZoomLevelIncrementProperty, value); } - } - - /// - /// The zoom level increment property - /// - public static readonly DependencyProperty ZoomLevelIncrementProperty = - DependencyProperty.Register(nameof(ZoomLevelIncrement), typeof(double), typeof(ChromiumWebBrowser), new PropertyMetadata(0.10)); - - /// - /// The CleanupElement controls when the Browser will be Disposed. - /// The will be Disposed when is called. - /// Be aware that this Control is not usable anymore after it has been disposed. - /// - /// The cleanup element. - public FrameworkElement CleanupElement - { - get { return (FrameworkElement)GetValue(CleanupElementProperty); } - set { SetValue(CleanupElementProperty, value); } - } - - /// - /// The cleanup element property - /// - public static readonly DependencyProperty CleanupElementProperty = - DependencyProperty.Register(nameof(CleanupElement), typeof(FrameworkElement), typeof(ChromiumWebBrowser), new PropertyMetadata(null, OnCleanupElementChanged)); - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private static void OnCleanupElementChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) - { - var owner = (ChromiumWebBrowser)sender; - var oldValue = (FrameworkElement)args.OldValue; - var newValue = (FrameworkElement)args.NewValue; - - owner.OnCleanupElementChanged(oldValue, newValue); - } - - /// - /// Called when [cleanup element changed]. - /// - /// The old value. - /// The new value. - protected virtual void OnCleanupElementChanged(FrameworkElement oldValue, FrameworkElement newValue) - { - if (oldValue != null) - { - oldValue.Unloaded -= OnCleanupElementUnloaded; - } - - if (newValue != null) - { - newValue.Unloaded += OnCleanupElementUnloaded; - } - } - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private void OnCleanupElementUnloaded(object sender, RoutedEventArgs e) - { - Dispose(); - } - - /// - /// The text that will be displayed as a ToolTip - /// - /// The tooltip text. - public string TooltipText - { - get { return (string)GetValue(TooltipTextProperty); } - } - - /// - /// The tooltip text property - /// - public static readonly DependencyProperty TooltipTextProperty = - DependencyProperty.Register(nameof(TooltipText), typeof(string), typeof(ChromiumWebBrowser)); - - /// - /// Gets or sets the WebBrowser. - /// - /// The WebBrowser. - public IChromiumWebBrowser WebBrowser - { - get { return (IChromiumWebBrowser)GetValue(WebBrowserProperty); } - set { SetValue(WebBrowserProperty, value); } - } - - /// - /// The WebBrowser property - /// - public static readonly DependencyProperty WebBrowserProperty = - DependencyProperty.Register(nameof(WebBrowser), typeof(IChromiumWebBrowser), typeof(ChromiumWebBrowser), new UIPropertyMetadata(defaultValue: null)); - - /// - /// Runs the specific Action on the Dispatcher in an async fashion - /// - /// The action. - /// The priority. - private void UiThreadRunAsync(Action action, DispatcherPriority priority = DispatcherPriority.DataBind) - { - if (Dispatcher.CheckAccess()) - { - action(); - } - else if (!Dispatcher.HasShutdownStarted) - { - _ = Dispatcher.InvokeAsync(action, priority); - } - } - - /// - /// Handles the event. - /// - /// The sender. - /// The instance containing the event data. - private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs args) - { - var isVisible = (bool)args.NewValue; - - if (InternalIsBrowserInitialized()) - { - if (isVisible) - { - ResizeBrowser((int)ActualWidth, (int)ActualHeight); - } - else - { - //Hide browser - ResizeBrowser(0, 0); - } - } - } - - /// - /// Zooms the browser in. - /// - private void ZoomIn() - { - UiThreadRunAsync(() => - { - ZoomLevel = ZoomLevel + ZoomLevelIncrement; - }); - } - - /// - /// Zooms the browser out. - /// - private void ZoomOut() - { - UiThreadRunAsync(() => - { - ZoomLevel = ZoomLevel - ZoomLevelIncrement; - }); - } - - /// - /// Reset the browser's zoom level to default. - /// - private void ZoomReset() - { - UiThreadRunAsync(() => - { - ZoomLevel = 0; - }); - } - - /// - /// Check is browserisinitialized - /// - /// true if browser is initialized - private bool InternalIsBrowserInitialized() - { - // Use CompareExchange to read the current value - if disposeCount is 1, we set it to 1, effectively a no-op - // Volatile.Read would likely use a memory barrier which I believe is unnecessary in this scenario - return Interlocked.CompareExchange(ref _browserInitialized, 0, 0) == 1; - } - - /// - /// Resizes the browser to the specified and . - /// If and are both 0 then the browser - /// will be hidden and resource usage will be minimised. - /// - /// width - /// height - protected virtual void ResizeBrowser(int width, int height) - { - if (_browserHwnd != IntPtr.Zero) - { - if (_dpiScale > 1) - { - width = (int)(width * _dpiScale); - height = (int)(height * _dpiScale); - } - - if (width == 0 && height == 0) - { - // For windowed browsers when the frame window is minimized set the - // browser window size to 0x0 to reduce resource usage. - HideInternal(); - } - else - { - ShowInternal(width, height); - } - } - } - - /// - /// When minimized set the browser window size to 0x0 to reduce resource usage. - /// https://github.com/chromiumembedded/cef/blob/c7701b8a6168f105f2c2d6b239ce3958da3e3f13/tests/cefclient/browser/browser_window_std_win.cc#L87 - /// - internal virtual void HideInternal() - { - if (_browserHwnd != IntPtr.Zero) - { - User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, 0, 0, User32.SetWindowPosFlags.SWP_NOZORDER | User32.SetWindowPosFlags.SWP_NOMOVE | User32.SetWindowPosFlags.SWP_NOACTIVATE); - } - } - - /// - /// Show the browser (called after previous minimised) - /// - internal virtual void ShowInternal(int width, int height) - { - if (_browserHwnd != IntPtr.Zero) - { - User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, width, height, User32.SetWindowPosFlags.SWP_NOZORDER); - } - } - - private void OnWindowStateChanged(object sender, EventArgs e) - { - var window = (Window)sender; - - switch (window.WindowState) - { - case WindowState.Normal: - case WindowState.Maximized: - { - if (_previousWindowState == WindowState.Minimized && InternalIsBrowserInitialized()) - { - ResizeBrowser((int)ActualWidth, (int)ActualHeight); - } - break; - } - case WindowState.Minimized: - { - if (InternalIsBrowserInitialized()) - { - //Set the browser size to 0,0 to reduce CPU usage - ResizeBrowser(0, 0); - } - break; - } - } - - _previousWindowState = window.WindowState; - } - - private void OnWindowLocationChanged(object sender, EventArgs e) - { - if (InternalIsBrowserInitialized()) - { - _host.NotifyMoveOrResizeStarted(_id); - } - } - - /// - /// Throw exception if browser not initialized. - /// - /// Thrown when an exception error condition occurs. - private void ThrowExceptionIfBrowserNotInitialized() - { - if (!InternalIsBrowserInitialized()) - { - throw new Exception(BrowserNotInitializedExceptionErrorMessage); - } - } - - /// - /// Throw exception if disposed. - /// - /// Thrown when a supplied object has been disposed. - private void ThrowExceptionIfDisposed() - { - if (IsDisposed) - { - throw new ObjectDisposedException("browser", "Browser has been disposed"); - } - } - } -} +// Copyright © 2022 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 CefSharp.OutOfProcess.Internal; +using CefSharp.OutOfProcess.WinForms; +using CefSharp.OutOfProcess.Wpf.HwndHost.Internals; +using CefSharp.Dom; +using PInvoke; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Threading; +using Window = System.Windows.Window; + +namespace CefSharp.OutOfProcess.Wpf.HwndHost +{ + /// + /// ChromiumWebBrowser is the WPF web browser control + /// + /// + /// + /// based on https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/walkthrough-hosting-a-win32-control-in-wpf + /// and https://stackoverflow.com/questions/6500336/custom-dwm-drawn-window-frame-flickers-on-resizing-if-the-window-contains-a-hwnd/17471534#17471534 + public class ChromiumWebBrowser : System.Windows.Interop.HwndHost, IChromiumWebBrowserInternal + { + private const string BrowserNotInitializedExceptionErrorMessage = + "The ChromiumWebBrowser instance creates the underlying Chromium Embedded Framework (CEF) browser instance in an async fashion. " + + "The undelying CefBrowser instance is not yet initialized. Use the IsBrowserInitializedChanged event and check " + + "the IsBrowserInitialized property to determine when the browser has been initialized."; + + private const int WS_CHILD = 0x40000000, + WS_VISIBLE = 0x10000000, + LBS_NOTIFY = 0x00000001, + HOST_ID = 0x00000002, + LISTBOX_ID = 0x00000001, + WS_VSCROLL = 0x00200000, + WS_BORDER = 0x00800000, + WS_CLIPCHILDREN = 0x02000000; + + [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)] + private static extern IntPtr CreateWindowEx(int dwExStyle, + string lpszClassName, + string lpszWindowName, + int style, + int x, int y, + int width, int height, + IntPtr hwndParent, + IntPtr hMenu, + IntPtr hInst, + [MarshalAs(UnmanagedType.AsAny)] object pvParam); + + [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)] + private static extern bool DestroyWindow(IntPtr hwnd); + + [DllImport("user32.dll", EntryPoint = "GetWindowLong")] + private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int index); + + [DllImport("user32.dll", EntryPoint = "SetWindowLong")] + private static extern int SetWindowLong32(HandleRef hWnd, int index, int dwNewLong); + + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] + private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int index, IntPtr dwNewLong); + + private OutOfProcessHost _host; + private IntPtr _browserHwnd = IntPtr.Zero; + private OutOfProcessConnectionTransport _devToolsContextConnectionTransport; + private IDevToolsContext _devToolsContext; + private int _id; + private bool _devToolsReady; + + /// + /// Handle we'll use to host the browser + /// + private IntPtr _hwndHost; + /// + /// The ignore URI change + /// + private bool _ignoreUriChange; + /// + /// Initial address + /// + private readonly string _initialAddress; + /// + /// Has the underlying Cef Browser been created (slightly different to initliazed in that + /// the browser is initialized in an async fashion) + /// + private bool _browserCreated; + /// + /// The browser initialized - boolean represented as 0 (false) and 1(true) as we use Interlocker to increment/reset + /// + private int _browserInitialized; + /// + /// A flag that indicates whether or not the designer is active + /// NOTE: Needs to be static for OnApplicationExit + /// + private static bool DesignMode; + + /// + /// The value for disposal, if it's 1 (one) then this instance is either disposed + /// or in the process of getting disposed + /// + private int _disposeSignaled; + + /// + /// Current DPI Scale + /// + private double _dpiScale; + + /// + /// The HwndSource RootVisual (Window) - We store a reference + /// to unsubscribe event handlers + /// + private Window _sourceWindow; + + /// + /// Store the previous window state, used to determine if the + /// Windows was previous + /// and resume rendering + /// + private WindowState _previousWindowState; + + /// + /// This flag is set when the browser gets focus before the underlying CEF browser + /// has been initialized. + /// + private bool _initialFocus; + + /// + /// Activates browser upon creation, the default value is false. Prior to version 73 + /// the default behaviour was to activate browser on creation (Equivilent of setting this property to true). + /// To restore this behaviour set this value to true immediately after you create the instance. + /// https://bitbucket.org/chromiumembedded/cef/issues/1856/branch-2526-cef-activates-browser-window + /// + public bool ActivateBrowserOnCreation { get; set; } + + /// + /// Gets a value indicating whether this instance is disposed. + /// + /// if this instance is disposed; otherwise, . + public bool IsDisposed + { + get + { + return Interlocked.CompareExchange(ref _disposeSignaled, 1, 1) == 1; + } + } + + /// + public event EventHandler DOMContentLoaded; + /// + public event EventHandler BrowserProcessCrashed; + /// + public event EventHandler FrameAttached; + /// + public event EventHandler FrameDetached; + /// + public event EventHandler FrameNavigated; + /// + public event EventHandler JavaScriptLoad; + /// + public event EventHandler RuntimeExceptionThrown; + /// + public event EventHandler Popup; + /// + public event EventHandler NetworkRequest; + /// + public event EventHandler NetworkRequestFailed; + /// + public event EventHandler NetworkRequestFinished; + /// + public event EventHandler NetworkRequestServedFromCache; + /// + public event EventHandler NetworkResponse; + /// + public event EventHandler AddressChanged; + /// + public event EventHandler LoadingStateChanged; + /// + public event EventHandler StatusMessage; + /// + public event EventHandler ConsoleMessage; + /// + public event EventHandler LifecycleEvent; + /// + public event EventHandler DevToolsContextAvailable; + + /// + /// Navigates to the previous page in the browser history. Will automatically be enabled/disabled depending on the + /// browser state. + /// + /// The back command. + public ICommand BackCommand { get; private set; } + /// + /// Navigates to the next page in the browser history. Will automatically be enabled/disabled depending on the + /// browser state. + /// + /// The forward command. + public ICommand ForwardCommand { get; private set; } + /// + /// Reloads the content of the current page. Will automatically be enabled/disabled depending on the browser state. + /// + /// The reload command. + public ICommand ReloadCommand { get; private set; } + /// + /// Prints the current browser contents. + /// + /// The print command. + public ICommand PrintCommand { get; private set; } + /// + /// Increases the zoom level. + /// + /// The zoom in command. + public ICommand ZoomInCommand { get; private set; } + /// + /// Decreases the zoom level. + /// + /// The zoom out command. + public ICommand ZoomOutCommand { get; private set; } + /// + /// Resets the zoom level to the default. (100%) + /// + /// The zoom reset command. + public ICommand ZoomResetCommand { get; private set; } + /// + /// Opens up a new program window (using the default text editor) where the source code of the currently displayed web + /// page is shown. + /// + /// The view source command. + public ICommand ViewSourceCommand { get; private set; } + /// + /// Command which cleans up the Resources used by the ChromiumWebBrowser + /// + /// The cleanup command. + public ICommand CleanupCommand { get; private set; } + /// + /// Stops loading the current page. + /// + /// The stop command. + public ICommand StopCommand { get; private set; } + /// + /// Cut selected text to the clipboard. + /// + /// The cut command. + public ICommand CutCommand { get; private set; } + /// + /// Copy selected text to the clipboard. + /// + /// The copy command. + public ICommand CopyCommand { get; private set; } + /// + /// Paste text from the clipboard. + /// + /// The paste command. + public ICommand PasteCommand { get; private set; } + /// + /// Select all text. + /// + /// The select all command. + public ICommand SelectAllCommand { get; private set; } + /// + /// Undo last action. + /// + /// The undo command. + public ICommand UndoCommand { get; private set; } + /// + /// Redo last action. + /// + /// The redo command. + public ICommand RedoCommand { get; private set; } + + /// + /// Initializes a new instance of the instance. + /// + /// Out of process host + /// address to load initially + public ChromiumWebBrowser(OutOfProcessHost host, string initialAddress = null) + { + if(host == null) + { + throw new ArgumentNullException(nameof(host)); + } + + _host = host; + _initialAddress = initialAddress; + + Focusable = true; + FocusVisualStyle = null; + + WebBrowser = this; + + SizeChanged += OnSizeChanged; + IsVisibleChanged += OnIsVisibleChanged; + + BackCommand = new DelegateCommand(() => _devToolsContext.GoBackAsync(), () => CanGoBack); + ForwardCommand = new DelegateCommand(() => _devToolsContext.GoForwardAsync(), () => CanGoForward); + ReloadCommand = new DelegateCommand(() => _devToolsContext.ReloadAsync(), () => !IsLoading); + //PrintCommand = new DelegateCommand(this.Print); + //ZoomInCommand = new DelegateCommand(ZoomIn); + //ZoomOutCommand = new DelegateCommand(ZoomOut); + //ZoomResetCommand = new DelegateCommand(ZoomReset); + //ViewSourceCommand = new DelegateCommand(this.ViewSource); + CleanupCommand = new DelegateCommand(Dispose); + //StopCommand = new DelegateCommand(this.Stop); + //CutCommand = new DelegateCommand(this.Cut); + //CopyCommand = new DelegateCommand(this.Copy); + //PasteCommand = new DelegateCommand(this.Paste); + //SelectAllCommand = new DelegateCommand(this.SelectAll); + //UndoCommand = new DelegateCommand(this.Undo); + //RedoCommand = new DelegateCommand(this.Redo); + + PresentationSource.AddSourceChangedHandler(this, PresentationSourceChangedHandler); + + UseLayoutRounding = true; + } + + /// + int IChromiumWebBrowserInternal.Id + { + get { return _id; } + } + + /// + /// DevToolsContext - provides communication with the underlying browser + /// + public IDevToolsContext DevToolsContext + { + get + { + if (_devToolsReady) + { + return _devToolsContext; + } + + return default; + } + } + + /// + public bool IsBrowserInitialized => _browserHwnd != IntPtr.Zero; + + + /// + public Frame[] Frames => _devToolsContext == null ? null : _devToolsContext.Frames; + + /// + public Frame MainFrame => _devToolsContext == null ? null : _devToolsContext.MainFrame; + + /// + void IChromiumWebBrowserInternal.OnDevToolsMessage(string jsonMsg) + { + _devToolsContextConnectionTransport?.InvokeMessageReceived(jsonMsg); + } + + /// + void IChromiumWebBrowserInternal.OnDevToolsReady() + { + var ctx = (DevToolsContext)_devToolsContext; + + ctx.DOMContentLoaded += DOMContentLoaded; + ctx.Error += BrowserProcessCrashed; + ctx.FrameAttached += FrameAttached; + ctx.FrameDetached += FrameDetached; + ctx.FrameNavigated += FrameNavigated; + ctx.Load += JavaScriptLoad; + ctx.PageError += RuntimeExceptionThrown; + ctx.Popup += Popup; + ctx.Request += NetworkRequest; + ctx.RequestFailed += NetworkRequestFailed; + ctx.RequestFinished += NetworkRequestFinished; + ctx.RequestServedFromCache += NetworkRequestServedFromCache; + ctx.Response += NetworkResponse; + ctx.Console += ConsoleMessage; + ctx.LifecycleEvent += LifecycleEvent; + + _ = ctx.InvokeGetFrameTreeAsync().ContinueWith(t => + { + _devToolsReady = true; + + DevToolsContextAvailable?.Invoke(this, EventArgs.Empty); + + //NOW the user can start using the devtools context + }, TaskScheduler.Current); + + // Only call Load if initialAddress is null and Address is not empty + if (string.IsNullOrEmpty(_initialAddress) && !string.IsNullOrEmpty(Address)) + { + LoadUrl(Address); + } + } + + /// + public void LoadUrl(string url) + { + _ = _devToolsContext.GoToAsync(url); + } + + /// + public Task LoadUrlAsync(string url, int? timeout = null, WaitUntilNavigation[] waitUntil = null) + { + return _devToolsContext.GoToAsync(url, timeout, waitUntil); + } + + /// + public Task GoBackAsync(NavigationOptions options = null) + { + return _devToolsContext.GoBackAsync(options); + } + + /// + public Task GoForwardAsync(NavigationOptions options = null) + { + return _devToolsContext.GoForwardAsync(options); + } + + private void PresentationSourceChangedHandler(object sender, SourceChangedEventArgs args) + { + if (args.NewSource != null) + { + var source = (HwndSource)args.NewSource; + + var matrix = source.CompositionTarget.TransformToDevice; + + _dpiScale = matrix.M11; + + var window = source.RootVisual as Window; + if (window != null) + { + window.StateChanged += OnWindowStateChanged; + window.LocationChanged += OnWindowLocationChanged; + _sourceWindow = window; + + if (CleanupElement == null) + { + CleanupElement = window; + } + else if (CleanupElement is Window parent) + { + //If the CleanupElement is a window then move it to the new Window + if (parent != window) + { + CleanupElement = window; + } + } + } + } + else if (args.OldSource != null) + { + var window = args.OldSource.RootVisual as Window; + if (window != null) + { + window.StateChanged -= OnWindowStateChanged; + window.LocationChanged -= OnWindowLocationChanged; + _sourceWindow = null; + } + } + } + + /// + protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi) + { + _dpiScale = newDpi.DpiScaleX; + + //If the DPI changed then we need to resize. + ResizeBrowser((int)ActualWidth, (int)ActualHeight); + + base.OnDpiChanged(oldDpi, newDpi); + } + + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + ResizeBrowser((int)e.NewSize.Width, (int)e.NewSize.Height); + } + + /// + protected override HandleRef BuildWindowCore(HandleRef hwndParent) + { + if (_hwndHost == IntPtr.Zero) + { + _hwndHost = CreateWindowEx(0, "static", "", + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, + 0, 0, + (int)ActualWidth, (int)ActualHeight, + hwndParent.Handle, + (IntPtr)HOST_ID, + IntPtr.Zero, + 0); + } + + _host.CreateBrowser(this, _hwndHost, url: _initialAddress, out _id); + + _devToolsContextConnectionTransport = new OutOfProcessConnectionTransport(_id, _host); + + var connection = DevToolsConnection.Attach(_devToolsContextConnectionTransport); + _devToolsContext = Dom.DevToolsContext.CreateForOutOfProcess(connection); + + return new HandleRef(null, _hwndHost); + } + + /// + protected override void DestroyWindowCore(HandleRef hwnd) + { + DestroyWindow(hwnd.Handle); + } + + /// + protected override bool TabIntoCore(TraversalRequest request) + { + if(InternalIsBrowserInitialized()) + { + _host.SetFocus(_id, true); + + return true; + } + + return base.TabIntoCore(request); + } + + /// + protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + if(!e.Handled) + { + if (InternalIsBrowserInitialized()) + { + _host.SetFocus(_id, true); + } + else + { + _initialFocus = true; + } + } + + base.OnGotKeyboardFocus(e); + } + + /// + protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) + { + if (!e.Handled) + { + if (InternalIsBrowserInitialized()) + { + _host.SetFocus(_id, false); + } + else + { + _initialFocus = false; + } + } + + base.OnLostKeyboardFocus(e); + } + + + /// + protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + const int WM_SETFOCUS = 0x0007; + const int WM_MOUSEACTIVATE = 0x0021; + switch (msg) + { + case WM_SETFOCUS: + case WM_MOUSEACTIVATE: + { + if(InternalIsBrowserInitialized()) + { + + _host.SetFocus(_id, true); + + handled = true; + + return IntPtr.Zero; + } + break; + } + } + return base.WndProc(hwnd, msg, wParam, lParam, ref handled); + } + + /// + /// If not in design mode; Releases unmanaged and - optionally - managed resources for the + /// + /// to release both managed and unmanaged resources; to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + // Attempt to move the disposeSignaled state from 0 to 1. If successful, we can be assured that + // this thread is the first thread to do so, and can safely dispose of the object. + if (Interlocked.CompareExchange(ref _disposeSignaled, 1, 0) != 0) + { + return; + } + + if (!DesignMode) + { + InternalDispose(disposing); + } + + base.Dispose(disposing); + } + + /// + /// Releases unmanaged and - optionally - managed resources for the + /// + /// to release both managed and unmanaged resources; to release only unmanaged resources. + /// + /// This method cannot be inlined as the designer will attempt to load libcef.dll and will subsiquently throw an exception. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private void InternalDispose(bool disposing) + { + Interlocked.Exchange(ref _browserInitialized, 0); + + if (disposing) + { + SizeChanged -= OnSizeChanged; + IsVisibleChanged -= OnIsVisibleChanged; + + PresentationSource.RemoveSourceChangedHandler(this, PresentationSourceChangedHandler); + // Release window event listeners if PresentationSourceChangedHandler event wasn't + // fired before Dispose + if (_sourceWindow != null) + { + _sourceWindow.StateChanged -= OnWindowStateChanged; + _sourceWindow.LocationChanged -= OnWindowLocationChanged; + _sourceWindow = null; + } + + + UiThreadRunAsync(() => + { + OnIsBrowserInitializedChanged(true, false); + + //To Minic the WPF behaviour this happens after OnIsBrowserInitializedChanged + IsBrowserInitializedChanged?.Invoke(this, EventArgs.Empty); + + WebBrowser = null; + }); + + // Don't maintain a reference to event listeners anylonger: + //ConsoleMessage = null; + //FrameLoadEnd = null; + //FrameLoadStart = null; + IsBrowserInitializedChanged = null; + //LoadError = null; + LoadingStateChanged = null; + StatusMessage = null; + TitleChanged = null; + + if (CleanupElement != null) + { + CleanupElement.Unloaded -= OnCleanupElementUnloaded; + } + } + } + + /// + void IChromiumWebBrowserInternal.SetAddress(string address) + { + UiThreadRunAsync(() => + { + _ignoreUriChange = true; + SetCurrentValue(AddressProperty, address); + _ignoreUriChange = false; + + // The tooltip should obviously also be reset (and hidden) when the address changes. + SetCurrentValue(TooltipTextProperty, null); + }); + } + + /// + void IChromiumWebBrowserInternal.SetLoadingStateChange(bool canGoBack, bool canGoForward, bool isLoading) + { + UiThreadRunAsync(() => + { + SetCurrentValue(CanGoBackProperty, canGoBack); + SetCurrentValue(CanGoForwardProperty, canGoForward); + SetCurrentValue(IsLoadingProperty, isLoading); + + ((DelegateCommand)BackCommand).RaiseCanExecuteChanged(); + ((DelegateCommand)ForwardCommand).RaiseCanExecuteChanged(); + ((DelegateCommand)ReloadCommand).RaiseCanExecuteChanged(); + }); + + LoadingStateChanged?.Invoke(this, new LoadingStateChangedEventArgs(canGoBack, canGoForward, isLoading)); + } + + /// + void IChromiumWebBrowserInternal.SetTitle(string title) + { + UiThreadRunAsync(() => SetCurrentValue(TitleProperty, title)); + } + + /// + /// Sets the tooltip text. + /// + /// The tooltip text. + //void IWebBrowserInternal.SetTooltipText(string tooltipText) + //{ + // UiThreadRunAsync(() => SetCurrentValue(TooltipTextProperty, tooltipText)); + //} + + /// + /// Handles the event. + /// + /// The instance containing the event data. + //void IWebBrowserInternal.OnConsoleMessage(ConsoleMessageEventArgs args) + //{ + // ConsoleMessage?.Invoke(this, args); + //} + + /// + void IChromiumWebBrowserInternal.SetStatusMessage(string msg) + { + StatusMessage?.Invoke(this, new StatusMessageEventArgs(msg)); + } + + /// + void IChromiumWebBrowserInternal.OnAfterBrowserCreated(IntPtr hwnd) + { + if (IsDisposed) + { + return; + } + + _browserHwnd = hwnd; + + Interlocked.Exchange(ref _browserInitialized, 1); + + UiThreadRunAsync(() => + { + if (!IsDisposed) + { + OnIsBrowserInitializedChanged(false, true); + //To Minic the WPF behaviour this happens after OnIsBrowserInitializedChanged + IsBrowserInitializedChanged?.Invoke(this, EventArgs.Empty); + } + }); + + ResizeBrowser((int)ActualWidth, (int)ActualHeight); + + if (_initialFocus) + { + _host.SetFocus(_id, true); + } + } + + /// + /// A flag that indicates whether the state of the control current supports the GoBack action (true) or not (false). + /// + /// true if this instance can go back; otherwise, false. + /// In the WPF control, this property is implemented as a Dependency Property and fully supports data + /// binding. + public bool CanGoBack + { + get { return (bool)GetValue(CanGoBackProperty); } + } + + /// + /// The can go back property + /// + public static DependencyProperty CanGoBackProperty = DependencyProperty.Register(nameof(CanGoBack), typeof(bool), typeof(ChromiumWebBrowser)); + + /// + /// A flag that indicates whether the state of the control currently supports the GoForward action (true) or not (false). + /// + /// true if this instance can go forward; otherwise, false. + /// In the WPF control, this property is implemented as a Dependency Property and fully supports data + /// binding. + public bool CanGoForward + { + get { return (bool)GetValue(CanGoForwardProperty); } + } + + /// + /// The can go forward property + /// + public static DependencyProperty CanGoForwardProperty = DependencyProperty.Register(nameof(CanGoForward), typeof(bool), typeof(ChromiumWebBrowser)); + + /// + /// The address (URL) which the browser control is currently displaying. + /// Will automatically be updated as the user navigates to another page (e.g. by clicking on a link). + /// + /// The address. + /// In the WPF control, this property is implemented as a Dependency Property and fully supports data + /// binding. + public string Address + { + get { return (string)GetValue(AddressProperty); } + set { SetValue(AddressProperty, value); } + } + + /// + /// The address property + /// + public static readonly DependencyProperty AddressProperty = + DependencyProperty.Register(nameof(Address), typeof(string), typeof(ChromiumWebBrowser), + new UIPropertyMetadata(null, OnAddressChanged)); + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private static void OnAddressChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + var owner = (ChromiumWebBrowser)sender; + var oldValue = (string)args.OldValue; + var newValue = (string)args.NewValue; + + owner.OnAddressChanged(oldValue, newValue); + } + + /// + /// Called when [address changed]. + /// + /// The old value. + /// The new value. + protected virtual void OnAddressChanged(string oldValue, string newValue) + { + if (_ignoreUriChange || newValue == null || !InternalIsBrowserInitialized()) + { + return; + } + + LoadUrl(newValue); + } + + /// + /// A flag that indicates whether the control is currently loading one or more web pages (true) or not (false). + /// + /// true if this instance is loading; otherwise, false. + /// In the WPF control, this property is implemented as a Dependency Property and fully supports data + /// binding. + public bool IsLoading + { + get { return (bool)GetValue(IsLoadingProperty); } + } + + /// + /// The is loading property + /// + public static readonly DependencyProperty IsLoadingProperty = + DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(ChromiumWebBrowser), new PropertyMetadata(false)); + + /// + /// Event called after the underlying CEF browser instance has been created and + /// when the instance has been Disposed. + /// will be true when the underlying CEF Browser + /// has been created and false when the browser is being Disposed. + /// + public event EventHandler IsBrowserInitializedChanged; + + /// + /// Called when [is browser initialized changed]. + /// + /// if set to true [old value]. + /// if set to true [new value]. + protected virtual void OnIsBrowserInitializedChanged(bool oldValue, bool newValue) + { + if (newValue && !IsDisposed) + { + //var task = this.GetZoomLevelAsync(); + //task.ContinueWith(previous => + //{ + // if (previous.Status == TaskStatus.RanToCompletion) + // { + // UiThreadRunAsync(() => + // { + // if (!IsDisposed) + // { + // SetCurrentValue(ZoomLevelProperty, previous.Result); + // } + // }); + // } + // else + // { + // throw new InvalidOperationException("Unexpected failure of calling CEF->GetZoomLevelAsync", previous.Exception); + // } + //}, TaskContinuationOptions.ExecuteSynchronously); + } + } + + /// + /// The title of the web page being currently displayed. + /// + /// The title. + /// This property is implemented as a Dependency Property and fully supports data binding. + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// The title property + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register(nameof(Title), typeof(string), typeof(ChromiumWebBrowser), new PropertyMetadata(null, OnTitleChanged)); + + /// + /// Event handler that will get called when the browser title changes + /// + public event DependencyPropertyChangedEventHandler TitleChanged; + + /// + /// Handles the event. + /// + /// The d. + /// The instance containing the event data. + private static void OnTitleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var owner = (ChromiumWebBrowser)d; + + owner.TitleChanged?.Invoke(owner, e); + } + + /// + /// The zoom level at which the browser control is currently displaying. + /// Can be set to 0 to clear the zoom level (resets to default zoom level). + /// + /// The zoom level. + public double ZoomLevel + { + get { return (double)GetValue(ZoomLevelProperty); } + set { SetValue(ZoomLevelProperty, value); } + } + + /// + /// The zoom level property + /// + public static readonly DependencyProperty ZoomLevelProperty = + DependencyProperty.Register(nameof(ZoomLevel), typeof(double), typeof(ChromiumWebBrowser), + new UIPropertyMetadata(0d, OnZoomLevelChanged)); + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private static void OnZoomLevelChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + var owner = (ChromiumWebBrowser)sender; + var oldValue = (double)args.OldValue; + var newValue = (double)args.NewValue; + + owner.OnZoomLevelChanged(oldValue, newValue); + } + + /// + /// Called when [zoom level changed]. + /// + /// The old value. + /// The new value. + protected virtual void OnZoomLevelChanged(double oldValue, double newValue) + { + throw new NotImplementedException(); + } + + /// + /// Specifies the amount used to increase/decrease to ZoomLevel by + /// By Default this value is 0.10 + /// + /// The zoom level increment. + public double ZoomLevelIncrement + { + get { return (double)GetValue(ZoomLevelIncrementProperty); } + set { SetValue(ZoomLevelIncrementProperty, value); } + } + + /// + /// The zoom level increment property + /// + public static readonly DependencyProperty ZoomLevelIncrementProperty = + DependencyProperty.Register(nameof(ZoomLevelIncrement), typeof(double), typeof(ChromiumWebBrowser), new PropertyMetadata(0.10)); + + /// + /// The CleanupElement controls when the Browser will be Disposed. + /// The will be Disposed when is called. + /// Be aware that this Control is not usable anymore after it has been disposed. + /// + /// The cleanup element. + public FrameworkElement CleanupElement + { + get { return (FrameworkElement)GetValue(CleanupElementProperty); } + set { SetValue(CleanupElementProperty, value); } + } + + /// + /// The cleanup element property + /// + public static readonly DependencyProperty CleanupElementProperty = + DependencyProperty.Register(nameof(CleanupElement), typeof(FrameworkElement), typeof(ChromiumWebBrowser), new PropertyMetadata(null, OnCleanupElementChanged)); + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private static void OnCleanupElementChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + var owner = (ChromiumWebBrowser)sender; + var oldValue = (FrameworkElement)args.OldValue; + var newValue = (FrameworkElement)args.NewValue; + + owner.OnCleanupElementChanged(oldValue, newValue); + } + + /// + /// Called when [cleanup element changed]. + /// + /// The old value. + /// The new value. + protected virtual void OnCleanupElementChanged(FrameworkElement oldValue, FrameworkElement newValue) + { + if (oldValue != null) + { + oldValue.Unloaded -= OnCleanupElementUnloaded; + } + + if (newValue != null) + { + newValue.Unloaded += OnCleanupElementUnloaded; + } + } + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private void OnCleanupElementUnloaded(object sender, RoutedEventArgs e) + { + Dispose(); + } + + /// + /// The text that will be displayed as a ToolTip + /// + /// The tooltip text. + public string TooltipText + { + get { return (string)GetValue(TooltipTextProperty); } + } + + /// + /// The tooltip text property + /// + public static readonly DependencyProperty TooltipTextProperty = + DependencyProperty.Register(nameof(TooltipText), typeof(string), typeof(ChromiumWebBrowser)); + + /// + /// Gets or sets the WebBrowser. + /// + /// The WebBrowser. + public IChromiumWebBrowser WebBrowser + { + get { return (IChromiumWebBrowser)GetValue(WebBrowserProperty); } + set { SetValue(WebBrowserProperty, value); } + } + + /// + /// The WebBrowser property + /// + public static readonly DependencyProperty WebBrowserProperty = + DependencyProperty.Register(nameof(WebBrowser), typeof(IChromiumWebBrowser), typeof(ChromiumWebBrowser), new UIPropertyMetadata(defaultValue: null)); + + /// + /// Runs the specific Action on the Dispatcher in an async fashion + /// + /// The action. + /// The priority. + private void UiThreadRunAsync(Action action, DispatcherPriority priority = DispatcherPriority.DataBind) + { + if (Dispatcher.CheckAccess()) + { + action(); + } + else if (!Dispatcher.HasShutdownStarted) + { + _ = Dispatcher.InvokeAsync(action, priority); + } + } + + /// + /// Handles the event. + /// + /// The sender. + /// The instance containing the event data. + private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs args) + { + var isVisible = (bool)args.NewValue; + + if (InternalIsBrowserInitialized()) + { + if (isVisible) + { + ResizeBrowser((int)ActualWidth, (int)ActualHeight); + } + else + { + //Hide browser + ResizeBrowser(0, 0); + } + } + } + + /// + /// Zooms the browser in. + /// + private void ZoomIn() + { + UiThreadRunAsync(() => + { + ZoomLevel = ZoomLevel + ZoomLevelIncrement; + }); + } + + /// + /// Zooms the browser out. + /// + private void ZoomOut() + { + UiThreadRunAsync(() => + { + ZoomLevel = ZoomLevel - ZoomLevelIncrement; + }); + } + + /// + /// Reset the browser's zoom level to default. + /// + private void ZoomReset() + { + UiThreadRunAsync(() => + { + ZoomLevel = 0; + }); + } + + /// + /// Check is browserisinitialized + /// + /// true if browser is initialized + private bool InternalIsBrowserInitialized() + { + // Use CompareExchange to read the current value - if disposeCount is 1, we set it to 1, effectively a no-op + // Volatile.Read would likely use a memory barrier which I believe is unnecessary in this scenario + return Interlocked.CompareExchange(ref _browserInitialized, 0, 0) == 1; + } + + /// + /// Resizes the browser to the specified and . + /// If and are both 0 then the browser + /// will be hidden and resource usage will be minimised. + /// + /// width + /// height + protected virtual void ResizeBrowser(int width, int height) + { + if (_browserHwnd != IntPtr.Zero) + { + if (_dpiScale > 1) + { + width = (int)(width * _dpiScale); + height = (int)(height * _dpiScale); + } + + if (width == 0 && height == 0) + { + // For windowed browsers when the frame window is minimized set the + // browser window size to 0x0 to reduce resource usage. + HideInternal(); + } + else + { + ShowInternal(width, height); + } + } + } + + /// + /// When minimized set the browser window size to 0x0 to reduce resource usage. + /// https://github.com/chromiumembedded/cef/blob/c7701b8a6168f105f2c2d6b239ce3958da3e3f13/tests/cefclient/browser/browser_window_std_win.cc#L87 + /// + internal virtual void HideInternal() + { + if (_browserHwnd != IntPtr.Zero) + { + User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, 0, 0, User32.SetWindowPosFlags.SWP_NOZORDER | User32.SetWindowPosFlags.SWP_NOMOVE | User32.SetWindowPosFlags.SWP_NOACTIVATE); + } + } + + /// + /// Show the browser (called after previous minimised) + /// + internal virtual void ShowInternal(int width, int height) + { + if (_browserHwnd != IntPtr.Zero) + { + User32.SetWindowPos(_browserHwnd, IntPtr.Zero, 0, 0, width, height, User32.SetWindowPosFlags.SWP_NOZORDER); + } + } + + private void OnWindowStateChanged(object sender, EventArgs e) + { + var window = (Window)sender; + + switch (window.WindowState) + { + case WindowState.Normal: + case WindowState.Maximized: + { + if (_previousWindowState == WindowState.Minimized && InternalIsBrowserInitialized()) + { + ResizeBrowser((int)ActualWidth, (int)ActualHeight); + } + break; + } + case WindowState.Minimized: + { + if (InternalIsBrowserInitialized()) + { + //Set the browser size to 0,0 to reduce CPU usage + ResizeBrowser(0, 0); + } + break; + } + } + + _previousWindowState = window.WindowState; + } + + private void OnWindowLocationChanged(object sender, EventArgs e) + { + if (InternalIsBrowserInitialized()) + { + _host.NotifyMoveOrResizeStarted(_id); + } + } + + /// + /// Throw exception if browser not initialized. + /// + /// Thrown when an exception error condition occurs. + private void ThrowExceptionIfBrowserNotInitialized() + { + if (!InternalIsBrowserInitialized()) + { + throw new Exception(BrowserNotInitializedExceptionErrorMessage); + } + } + + /// + /// Throw exception if disposed. + /// + /// Thrown when a supplied object has been disposed. + private void ThrowExceptionIfDisposed() + { + if (IsDisposed) + { + throw new ObjectDisposedException("browser", "Browser has been disposed"); + } + } + } +} diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/WPFExtensions.cs b/CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/WPFExtensions.cs index 2aaeddd..f4e941f 100644 --- a/CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/WPFExtensions.cs +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost/CefSharpWPF/WPFExtensions.cs @@ -6,8 +6,8 @@ using System.Text; using System.Windows; using System.Windows.Input; -using CefSharp.OutOfProcess; - +using CefSharp.OutOfProcess; + namespace CefSharp.Wpf.Internals { /// From dd71e815d9f628d4fd90aca87a600d0352bef329 Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Thu, 26 Jan 2023 20:27:27 +0100 Subject: [PATCH 10/12] minor --- .../CefSharp.OutOfProcess.Wpf.HwndHost.Example.csproj | 3 +-- .../CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CefSharp.OutOfProcess.Wpf.HwndHost.Example/CefSharp.OutOfProcess.Wpf.HwndHost.Example.csproj b/CefSharp.OutOfProcess.Wpf.HwndHost.Example/CefSharp.OutOfProcess.Wpf.HwndHost.Example.csproj index 9f4ad4c..b5bd1ee 100644 --- a/CefSharp.OutOfProcess.Wpf.HwndHost.Example/CefSharp.OutOfProcess.Wpf.HwndHost.Example.csproj +++ b/CefSharp.OutOfProcess.Wpf.HwndHost.Example/CefSharp.OutOfProcess.Wpf.HwndHost.Example.csproj @@ -5,8 +5,7 @@ netcoreapp3.1;net462 CefSharp.OutOfProcess.Wpf.HwndHost.Example CefSharp.OutOfProcess.Wpf.HwndHost.Example.App - true - AnyCPU + AnyCPU app.manifest diff --git a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj index 88ca3d0..10c912a 100644 --- a/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj @@ -5,8 +5,7 @@ netcoreapp3.1;net462 CefSharp.OutOfProcess.Wpf.OffscreenHost.Example CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.App - true - AnyCPU + AnyCPU app.manifest From ebd394efb43962dffab78ceb1400407ff341f0fb Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Fri, 27 Jan 2023 17:31:03 +0100 Subject: [PATCH 11/12] revert whitespace change --- CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj b/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj index 749af1e..ef4794e 100644 --- a/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj +++ b/CefSharp.OutOfProcess.Core/CefSharp.OutOfProcess.Core.csproj @@ -1,8 +1,8 @@ - netstandard2.0 - CefSharp.OutOfProcess + netstandard2.0 + CefSharp.OutOfProcess From ea46d2ca93a8c511be8ec27ed00f43767b444025 Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Mon, 30 Jan 2023 15:29:25 +0100 Subject: [PATCH 12/12] review --- .../OffscreenOutOfProcessChromiumWebBrowser.cs | 6 +++--- CefSharp.OutOfProcess.Core/OutOfProcessHost.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs b/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs index 9a91758..1b3274d 100644 --- a/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs +++ b/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs @@ -3,7 +3,7 @@ using CefSharp.OutOfProcess.Interface; using CefSharp.Structs; using CefSharp.Enums; - + namespace CefSharp.OutOfProcess.BrowserProcess { /// @@ -42,8 +42,8 @@ public OffscreenOutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcess /// ScreenInfo containing the current DPI scale factor protected virtual ScreenInfo? GetScreenInfo() { - CefSharp.Structs.Rect rect; - CefSharp.Structs.Rect availableRect; + CefSharp.Structs.Rect rect = new CefSharp.Structs.Rect(); + CefSharp.Structs.Rect availableRect = new CefSharp.Structs.Rect(); if (DpiScaleFactor > 1.0) { diff --git a/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs b/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs index a499eff..69899d5 100644 --- a/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs +++ b/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs @@ -263,7 +263,7 @@ public static Task CreateAsync(string path = HostExeName, stri var host = new OutOfProcessHost(fullPath, cachePath, offScreenRendering); - host.Init(); + host.Init(); return host.InitializedTask; }