Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add wpf offscreen browser #9

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 74 additions & 22 deletions CefSharp.OutOfProcess.BrowserProcess/BrowserProcessHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CefSharp.Internals;
using CefSharp.Internals;
using PInvoke;
using StreamJsonRpc;
using System;
Expand All @@ -13,27 +13,28 @@ namespace CefSharp.OutOfProcess.BrowserProcess
public class BrowserProcessHandler : CefSharp.Handler.BrowserProcessHandler, IOutOfProcessClientRpc
{
private readonly int _parentProcessId;
private IList<OutOfProcessChromiumWebBrowser> _browsers = new List<OutOfProcessChromiumWebBrowser>();
private readonly bool _offscreenRendering;
private readonly IList<OutOfProcessChromiumWebBrowser> _browsers = new List<OutOfProcessChromiumWebBrowser>();
/// <summary>
/// JSON RPC used for IPC with host
/// </summary>
private JsonRpc _jsonRpc;
private IOutOfProcessHostRpc _outOfProcessServer;

public BrowserProcessHandler(int parentProcessId)
public BrowserProcessHandler(int parentProcessId, bool offscreenRendering)
{
_parentProcessId = parentProcessId;
_offscreenRendering = offscreenRendering;
}

protected override void OnContextInitialized()
{
base.OnContextInitialized();

_jsonRpc = JsonRpc.Attach(Console.OpenStandardOutput(), Console.OpenStandardInput());
_outOfProcessServer = _jsonRpc.Attach<IOutOfProcessHostRpc>();
_jsonRpc.AllowModificationWhileListening = true;
_outOfProcessServer = _jsonRpc.Attach<IOutOfProcessHostRpc>();
_jsonRpc.AddLocalRpcTarget<IOutOfProcessClientRpc>(this, null);
_jsonRpc.AllowModificationWhileListening = false;

var threadId = Kernel32.GetCurrentThreadId();

Expand Down Expand Up @@ -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;
});
}
Expand All @@ -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);

Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using CefSharp.Internals;
using System;
using CefSharp.OutOfProcess.Interface;
using CefSharp.Structs;
using CefSharp.Enums;

namespace CefSharp.OutOfProcess.BrowserProcess
{
/// <summary>
/// An ChromiumWebBrowser instance specifically for hosting CEF out of process
/// </summary>
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_");
}

/// <summary>
/// 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.
/// </summary>
public float DpiScaleFactor { get; set; } = 1;

internal CefSharp.Structs.Rect ViewRect { get; set; }

/// <summary>
/// Gets the ScreenInfo - currently used to get the DPI scale factor.
/// </summary>
/// <returns>ScreenInfo containing the current DPI scale factor</returns>
ScreenInfo? IRenderWebBrowser.GetScreenInfo() => GetScreenInfo();

/// <summary>
/// Gets the ScreenInfo - currently used to get the DPI scale factor.
/// </summary>
/// <returns>ScreenInfo containing the current DPI scale factor</returns>
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
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using CefSharp.Internals;
using CefSharp.Internals;
using System;
using System.Threading.Tasks;
using System.Threading;
Expand All @@ -7,15 +7,13 @@
using System.IO;
using CefSharp.OutOfProcess.Interface;
using System.Runtime.InteropServices;
using PInvoke;
using System.Diagnostics;

namespace CefSharp.OutOfProcess.BrowserProcess
{
/// <summary>
/// An ChromiumWebBrowser instance specifically for hosting CEF out of process
/// </summary>
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. " +
Expand All @@ -36,7 +34,7 @@ public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal
/// <summary>
/// Internal ID used for tracking browsers between Processes;
/// </summary>
private int _id;
private readonly int _id;

/// <summary>
/// The managed cef browser adapter
Expand All @@ -46,7 +44,7 @@ public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal
/// <summary>
/// JSON RPC used for IPC with host
/// </summary>
private IOutOfProcessHostRpc _outofProcessHostRpc;
private readonly IOutOfProcessHostRpc _outofProcessHostRpc;

/// <summary>
/// Flag to guard the creation of the underlying browser - only one instance can be created
Expand Down Expand Up @@ -178,6 +176,11 @@ public partial class OutOfProcessChromiumWebBrowser : IWebBrowserInternal
/// <value>The resource handler factory.</value>
public IResourceRequestHandlerFactory ResourceRequestHandlerFactory { get; set; }

/// <summary>
/// Gets the out of process host.
/// </summary>
private protected IOutOfProcessHostRpc OutofProcessHostRpc => _outofProcessHostRpc;

/// <summary>
/// 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
Expand Down Expand Up @@ -453,7 +456,10 @@ private void RemoveExNoActivateStyle(IntPtr hwnd)
/// <inheritdoc/>
public void LoadUrl(string url)
{
throw new NotImplementedException();
using (var frame = _browser.MainFrame)
{
frame.LoadUrl(url);
}
}

/// <inheritdoc/>
Expand Down Expand Up @@ -665,7 +671,7 @@ public bool IsBrowserInitialized
/// </param>
/// <exception cref="System.InvalidOperationException">Cef::Initialize() failed</exception>
public OutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcessServer, int id, string address = "",
IRequestContext requestContext = null)
IRequestContext requestContext = null, bool offscreenRendering = false)
{
_id = id;
RequestContext = requestContext;
Expand All @@ -674,7 +680,7 @@ public OutOfProcessChromiumWebBrowser(IOutOfProcessHostRpc outOfProcessServer, i
Cef.AddDisposable(this);
Address = address;

_managedCefBrowserAdapter = ManagedCefBrowserAdapter.Create(this, false);
_managedCefBrowserAdapter = ManagedCefBrowserAdapter.Create(this, offscreenRendering);
}

/// <summary>
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions CefSharp.OutOfProcess.BrowserProcess/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using CefSharp.Internals;
Expand All @@ -18,17 +18,19 @@ 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);

var settings = new CefSettings()
{
//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();

Expand Down
Loading