From def0b0098b9f30c3f2f383c92e2f90ab1475c259 Mon Sep 17 00:00:00 2001 From: Sebastian Mayer Date: Mon, 30 Jan 2023 16:30:26 +0100 Subject: [PATCH 1/2] Squashed commit of the following: commit b2018c1424e00b8597e50d416f8ea3e04caa896d Author: Sebastian Mayer Date: Mon Jan 30 16:20:14 2023 +0100 Merge branch 'feat/add-wpf-offscreen-browser' --- CefSharp.Dom | 2 +- .../BrowserProcessHandler.cs | 96 +- .../CefSharp.WPF.Internals/RectStruct.cs | 48 + ...OffscreenOutOfProcessChromiumWebBrowser.cs | 119 ++ .../OutOfProcessChromiumWebBrowser.cs | 26 +- .../Program.cs | 6 +- .../RenderHandler.cs | 68 + CefSharp.OutOfProcess.Core/CefEventFlags.cs | 40 + .../IChromiumWebBrowser.cs | 2 +- .../Internal/IRenderHandlerInternal.cs | 13 + CefSharp.OutOfProcess.Core/MouseButtonType.cs | 25 + .../OutOfProcessConnectionTransport.cs | 3 +- .../OutOfProcessHost.cs | 95 +- .../IOutOfProcessClientRpc.cs | 24 +- .../IOutOfProcessHostRpc.cs | 10 +- CefSharp.OutOfProcess.Interface/Rect.cs | 51 + .../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 | 18 + .../Converter/EnvironmentConverter.cs | 19 + .../Converter/TitleConverter.cs | 19 + .../MainWindow.xaml | 85 ++ .../MainWindow.xaml.cs | 52 + .../app.manifest | 77 ++ ...harp.OutOfProcess.Wpf.OffscreenHost.csproj | 26 + .../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 | 1182 +++++++++++++++++ CefSharp.OutOfProcess.sln | 146 +- 38 files changed, 3018 insertions(+), 145 deletions(-) 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/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..ae12dbb 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,16 +13,18 @@ namespace CefSharp.OutOfProcess.BrowserProcess public class BrowserProcessHandler : CefSharp.Handler.BrowserProcessHandler, IOutOfProcessClientRpc { private readonly int _parentProcessId; - private IList _browsers = new List(); + private readonly bool _offscreenRendering; + private readonly IList _browsers = new List(); /// /// JSON RPC used for IPC with host /// 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; }); } @@ -93,15 +109,30 @@ Task IOutOfProcessClientRpc.CreateBrowser(IntPtr parentHwnd, string url, int id) 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); @@ -111,18 +142,39 @@ Task IOutOfProcessClientRpc.CreateBrowser(IntPtr parentHwnd, string url, int id) }); } - 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; + } - browser?.GetBrowserHost().NotifyMoveOrResizeStarted(); + var host = browser.GetBrowserHost(); + host.NotifyMoveOrResizeStarted(); + + if (_offscreenRendering && browser is OffscreenOutOfProcessChromiumWebBrowser offscreenBrowser) + { + host.NotifyMoveOrResizeStarted(); + offscreenBrowser.ViewRect = new Structs.Rect(rect.X, rect.Y, rect.Width, rect.Height); + 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.WPF.Internals/RectStruct.cs b/CefSharp.OutOfProcess.BrowserProcess/CefSharp.WPF.Internals/RectStruct.cs new file mode 100644 index 0000000..7999a6a --- /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..1b3274d --- /dev/null +++ b/CefSharp.OutOfProcess.BrowserProcess/OffscreenOutOfProcessChromiumWebBrowser.cs @@ -0,0 +1,119 @@ +using CefSharp.Internals; +using System; +using CefSharp.OutOfProcess.Interface; +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; + + 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; + + internal CefSharp.Structs.Rect ViewRect { get; 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 = new CefSharp.Structs.Rect(); + CefSharp.Structs.Rect availableRect = new CefSharp.Structs.Rect(); + + 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 = ViewRect.X + viewX; + screenY = ViewRect.Y + viewY; + + 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) + { + // not implemented + } + + bool IRenderWebBrowser.StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y) + { + // not implemented + return false; + } + + void IRenderWebBrowser.UpdateDragCursor(DragOperationsMask operation) + { + // not implemented + } + + 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.OnImeCompositionRangeChanged(Structs.Range selectedRange, CefSharp.Structs.Rect[] characterBounds) + { + throw new NotImplementedException(); + } + + void IRenderWebBrowser.OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode) + { + // not implemented + } + } +} \ No newline at end of file diff --git a/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs b/CefSharp.OutOfProcess.BrowserProcess/OutOfProcessChromiumWebBrowser.cs index 63966f8..49181b2 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 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 readonly IOutOfProcessHostRpc _outofProcessHostRpc; /// /// Flag to guard the creation of the underlying browser - only one instance can be created @@ -178,6 +176,11 @@ public partial 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 @@ -453,7 +456,10 @@ private void RemoveExNoActivateStyle(IntPtr hwnd) /// public void LoadUrl(string url) { - throw new NotImplementedException(); + using (var frame = _browser.MainFrame) + { + frame.LoadUrl(url); + } } /// @@ -665,7 +671,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 +680,7 @@ public OutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcessServer, i Cef.AddDisposable(this); Address = address; - _managedCefBrowserAdapter = ManagedCefBrowserAdapter.Create(this, false); + _managedCefBrowserAdapter = ManagedCefBrowserAdapter.Create(this, offscreenRendering); } /// @@ -774,7 +780,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..c585f18 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,10 +26,11 @@ 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); + var browserProcessHandler = new BrowserProcessHandler(parentProcessId, offscreenRendering); Cef.EnableWaitForBrowsersToClose(); diff --git a/CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs b/CefSharp.OutOfProcess.BrowserProcess/RenderHandler.cs new file mode 100644 index 0000000..036bb84 --- /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..ef118a5 --- /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/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/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..ec921bc --- /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..69899d5 100644 --- a/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs +++ b/CefSharp.OutOfProcess.Core/OutOfProcessHost.cs @@ -1,4 +1,4 @@ -using CefSharp.OutOfProcess.Interface; +using CefSharp.OutOfProcess.Interface; using CefSharp.OutOfProcess.Internal; using PInvoke; using StreamJsonRpc; @@ -17,6 +17,7 @@ public class OutOfProcessHost : IOutOfProcessHostRpc, IDisposable /// public const string HostExeName = "CefSharp.OutOfProcess.BrowserProcess.exe"; + private readonly bool _offscreenRendering; private Process _browserProcess; private JsonRpc _jsonRpc; private IOutOfProcessClientRpc _outOfProcessClient; @@ -31,10 +32,11 @@ public class OutOfProcessHost : IOutOfProcessHostRpc, IDisposable private ConcurrentDictionary _browsers = new ConcurrentDictionary(); private TaskCompletionSource _processInitialized = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - private OutOfProcessHost(string outOfProcessHostExePath, string cachePath = null) + private OutOfProcessHost(string outOfProcessHostExePath, string cachePath = null, bool offscreenRendering = false) { _outofProcessHostExePath = outOfProcessHostExePath; _cachePath = cachePath; + _offscreenRendering = offscreenRendering; } /// @@ -94,6 +96,21 @@ public bool CreateBrowser(IChromiumWebBrowserInternal browser, IntPtr handle, st 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); @@ -108,12 +125,13 @@ private void Init() { var currentProcess = Process.GetCurrentProcess(); - var args = $"--parentProcessId={currentProcess.Id} --cachePath={_cachePath}"; + 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; @@ -135,18 +153,12 @@ private void OnBrowserProcessExited(object sender, EventArgs e) void IOutOfProcessHostRpc.NotifyAddressChanged(int browserId, string address) { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.SetAddress(address); - } + GetBrowser(browserId)?.SetAddress(address); } void IOutOfProcessHostRpc.NotifyBrowserCreated(int browserId, IntPtr browserHwnd) { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.OnAfterBrowserCreated(browserHwnd); - } + GetBrowser(browserId)?.OnAfterBrowserCreated(browserHwnd); } void IOutOfProcessHostRpc.NotifyContextInitialized(int threadId, string cefSharpVersion, string cefVersion, string chromiumVersion) @@ -169,47 +181,47 @@ void IOutOfProcessHostRpc.NotifyDevToolsAgentDetached(int browserId) void IOutOfProcessHostRpc.NotifyDevToolsMessage(int browserId, string devToolsMessage) { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.OnDevToolsMessage(devToolsMessage); - } + GetBrowser(browserId)?.OnDevToolsMessage(devToolsMessage); } void IOutOfProcessHostRpc.NotifyDevToolsReady(int browserId) { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.OnDevToolsReady(); - } + GetBrowser(browserId)?.OnDevToolsReady(); } void IOutOfProcessHostRpc.NotifyLoadingStateChange(int browserId, bool canGoBack, bool canGoForward, bool isLoading) { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.SetLoadingStateChange(canGoBack, canGoForward, isLoading); - } + GetBrowser(browserId)?.SetLoadingStateChange(canGoBack, canGoForward, isLoading); } void IOutOfProcessHostRpc.NotifyStatusMessage(int browserId, string statusMessage) { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.SetStatusMessage(statusMessage); - } + GetBrowser(browserId)?.SetStatusMessage(statusMessage); } void IOutOfProcessHostRpc.NotifyTitleChanged(int browserId, string title) { - if (_browsers.TryGetValue(browserId, out var chromiumWebBrowser)) - { - chromiumWebBrowser.SetTitle(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); } - public void NotifyMoveOrResizeStarted(int id) + void IOutOfProcessHostRpc.NotifyPopupSize(int browserId, Rect rect) { - _outOfProcessClient.NotifyMoveOrResizeStarted(id); + ((IRenderHandlerInternal)GetBrowser(browserId))?.OnPopupSize(rect); + } + + public void NotifyMoveOrResizeStarted(int id, Rect rect = default) + { + _outOfProcessClient.NotifyMoveOrResizeStarted(id, rect); } /// @@ -225,6 +237,7 @@ public void SetFocus(int id, bool focus) public void CloseBrowser(int id) { _ = _outOfProcessClient.CloseBrowser(id); + _browsers.TryRemove(id, out _); } public void Dispose() @@ -234,7 +247,7 @@ public void Dispose() _jsonRpc = null; } - public static Task CreateAsync(string path = HostExeName, string cachePath = null) + public static Task CreateAsync(string path = HostExeName, string cachePath = null, bool offScreenRendering = false) { if(string.IsNullOrEmpty(path)) { @@ -248,11 +261,21 @@ public static Task CreateAsync(string path = HostExeName, stri throw new FileNotFoundException("Unable to find Host executable.", path); } - var host = new OutOfProcessHost(fullPath, cachePath); + 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/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.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..10c912a --- /dev/null +++ b/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example/CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.csproj @@ -0,0 +1,18 @@ + + + WinExe + true + netcoreapp3.1;net462 + CefSharp.OutOfProcess.Wpf.OffscreenHost.Example + CefSharp.OutOfProcess.Wpf.OffscreenHost.Example.App + 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 @@ + + + + + + + + + + + + + + + + + + + + +