From 07c3c0c3e3612dfb5ac38235152ac19dcd5d1504 Mon Sep 17 00:00:00 2001 From: Per Lundberg Date: Wed, 6 Nov 2013 22:52:18 +0100 Subject: [PATCH] Lots of work towards #159. --- CefSharp.BrowserSubprocess/JavascriptProxy.cs | 8 +- .../JavascriptServiceHost.cs | 10 +- CefSharp.BrowserSubprocess/Program.cs | 39 ++++-- .../SubprocessCefApp.cs | 13 +- CefSharp/CefSharp.vcxproj | 3 +- CefSharp/CefSharp.vcxproj.filters | 22 ++-- .../JavascriptProxySupport.h | 30 +++++ CefSharp/ManagedCefBrowserAdapter.h | 6 +- CefSharp/ScriptCore.cpp | 30 ----- CefSharp/Wrappers/CefAppWrapper.h | 6 +- CefSharp/Wrappers/CefBrowserWrapper.h | 75 ----------- CefSharp/Wrappers/CefSubprocessWrapper.h | 124 ++++++++++++++++++ 12 files changed, 222 insertions(+), 144 deletions(-) create mode 100644 CefSharp/Internals/JavascriptBinding/JavascriptProxySupport.h delete mode 100644 CefSharp/Wrappers/CefBrowserWrapper.h create mode 100644 CefSharp/Wrappers/CefSubprocessWrapper.h diff --git a/CefSharp.BrowserSubprocess/JavascriptProxy.cs b/CefSharp.BrowserSubprocess/JavascriptProxy.cs index 27f3ab8900..3941ef2417 100644 --- a/CefSharp.BrowserSubprocess/JavascriptProxy.cs +++ b/CefSharp.BrowserSubprocess/JavascriptProxy.cs @@ -5,14 +5,10 @@ namespace CefSharp.BrowserSubprocess { internal class JavascriptProxy : IJavascriptProxy { - public const String BaseAddress = "net.pipe://localhost"; - public const String ServiceName = "JavaScriptProxy"; - public const String Address = BaseAddress + "/" + ServiceName; - public object EvaluateScript(int frameId, string script, double timeout) { - var cefFrame = SubprocessCefApp.Instance.CefBrowserWrapper.GetFrame(frameId); - return null; + var result = SubprocessCefApp.Instance.CefSubprocessWrapper.EvaluateScript(frameId, script, timeout); + return result; } } } \ No newline at end of file diff --git a/CefSharp.BrowserSubprocess/JavascriptServiceHost.cs b/CefSharp.BrowserSubprocess/JavascriptServiceHost.cs index 8930eca2a6..0b5a02125a 100644 --- a/CefSharp.BrowserSubprocess/JavascriptServiceHost.cs +++ b/CefSharp.BrowserSubprocess/JavascriptServiceHost.cs @@ -7,20 +7,18 @@ namespace CefSharp.BrowserSubprocess { internal class JavascriptServiceHost { - public static void Create(int browserId) + public static void Create(int parentProcessId, int browserId) { var uris = new[] { - new Uri(JavascriptProxy.BaseAddress) + new Uri(JavascriptProxySupport.BaseAddress) }; var host = new ServiceHost(typeof(JavascriptProxy), uris); AddDebugBehavior(host); - // TODO: Include the name of the "parent process" here also, so you can run more than one CefSharp-based application - // TODO: simultaneously. :) - var serviceName = JavascriptProxy.ServiceName + "_" + browserId; - + var serviceName = JavascriptProxySupport.GetServiceName(parentProcessId, browserId); + Kernel32.OutputDebugString("Setting up IJavascriptProxy using service name: " + serviceName); host.AddServiceEndpoint( typeof(IJavascriptProxy), new NetNamedPipeBinding(), diff --git a/CefSharp.BrowserSubprocess/Program.cs b/CefSharp.BrowserSubprocess/Program.cs index 7de2e74d25..dfd6274d32 100644 --- a/CefSharp.BrowserSubprocess/Program.cs +++ b/CefSharp.BrowserSubprocess/Program.cs @@ -1,8 +1,8 @@ -using System; +using CefSharp.Wrappers; +using System; +using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Windows.Forms; -using CefSharp.Wrappers; +using System.Linq; namespace CefSharp.BrowserSubprocess { @@ -10,20 +10,41 @@ public class Program { static int Main(string[] args) { - var hInstance = Process.GetCurrentProcess().Handle; LogCommandLine(args); - MessageBox.Show("Please attach debugger now", null, MessageBoxButtons.OK, MessageBoxIcon.Information); - return ExecuteCefRenderProcess(hInstance); + var subprocessCefApp = SubprocessCefApp.Instance; + subprocessCefApp.ParentProcessId = FindParentProcessId(args); + + //MessageBox.Show("Please attach debugger now", null, MessageBoxButtons.OK, MessageBoxIcon.Information); + var result = ExecuteCefRenderProcess(); + Kernel32.OutputDebugString("BrowserSubprocess shutting down."); + return result; } private static void LogCommandLine(string[] args) { - Kernel32.OutputDebugString("BrowserSubprocess starting up with command line: " + String.Join("\n", args)); + Kernel32.OutputDebugString("BrowserSubprocess starting up with command line: " + String.Join("\n", args)); + } + + private static int? FindParentProcessId(IEnumerable args) + { + // Format being parsed: + // --channel=3828.2.1260352072\1102986608 + // We only really care about the PID (3828) part. + var channelPrefix = "--channel="; + var channelArgument = args.SingleOrDefault(arg => arg.StartsWith(channelPrefix)); + if (channelArgument == null) return null; + + var parentProcessId = channelArgument + .Substring(channelPrefix.Length) + .Split('.') + .First(); + return int.Parse(parentProcessId); } - private static int ExecuteCefRenderProcess(IntPtr hInstance) + private static int ExecuteCefRenderProcess() { + var hInstance = Process.GetCurrentProcess().Handle; var subprocessCefApp = SubprocessCefApp.Instance; return GlobalMethods.CefExecuteProcess(hInstance, subprocessCefApp); } diff --git a/CefSharp.BrowserSubprocess/SubprocessCefApp.cs b/CefSharp.BrowserSubprocess/SubprocessCefApp.cs index 9e38238d7b..25b8252937 100644 --- a/CefSharp.BrowserSubprocess/SubprocessCefApp.cs +++ b/CefSharp.BrowserSubprocess/SubprocessCefApp.cs @@ -11,12 +11,17 @@ public static SubprocessCefApp Instance get { return instance ?? (instance = new SubprocessCefApp()); } } - public CefBrowserWrapper CefBrowserWrapper { get; private set; } + public CefSubprocessWrapper CefSubprocessWrapper { get; private set; } + public int? ParentProcessId { get; set; } - public override void OnBrowserCreated(CefBrowserWrapper cefBrowserWrapper) + public override void OnBrowserCreated(CefSubprocessWrapper cefBrowserWrapper) { - CefBrowserWrapper = cefBrowserWrapper; - JavascriptServiceHost.Create(cefBrowserWrapper.BrowserId); + CefSubprocessWrapper = cefBrowserWrapper; + + if (ParentProcessId != null) + { + JavascriptServiceHost.Create(ParentProcessId.Value, CefSubprocessWrapper.BrowserId); + } } } } \ No newline at end of file diff --git a/CefSharp/CefSharp.vcxproj b/CefSharp/CefSharp.vcxproj index 5eb23949bd..3f4cf3ab33 100644 --- a/CefSharp/CefSharp.vcxproj +++ b/CefSharp/CefSharp.vcxproj @@ -239,6 +239,7 @@ + @@ -279,7 +280,7 @@ - + diff --git a/CefSharp/CefSharp.vcxproj.filters b/CefSharp/CefSharp.vcxproj.filters index af6f45dbbf..ee4115d5a3 100644 --- a/CefSharp/CefSharp.vcxproj.filters +++ b/CefSharp/CefSharp.vcxproj.filters @@ -24,6 +24,9 @@ {b5511138-8f2e-4ace-9e68-76929de8c004} + + {598626f0-6a78-477c-955b-8cdf26d2cb64} + @@ -184,9 +187,6 @@ Header Files - - Header Files - Header Files @@ -202,14 +202,20 @@ Header Files - - Source Files\Wrappers - Header Files - - Source Files\Wrappers + + Header Files\Wrappers + + + Header Files\Wrappers + + + Header Files\Internals\JavascriptBinding + + + Header Files\Internals\JavascriptBinding \ No newline at end of file diff --git a/CefSharp/Internals/JavascriptBinding/JavascriptProxySupport.h b/CefSharp/Internals/JavascriptBinding/JavascriptProxySupport.h new file mode 100644 index 0000000000..846b8f3c70 --- /dev/null +++ b/CefSharp/Internals/JavascriptBinding/JavascriptProxySupport.h @@ -0,0 +1,30 @@ +// Copyright © 2013 The CefSharp Project. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +using namespace System; + +namespace CefSharp +{ + namespace Internals + { + namespace JavascriptBinding + { + public ref class JavascriptProxySupport + { + public: + literal String^ BaseAddress = "net.pipe://localhost"; + + static String^ GetServiceName(int parentProcessId, int browserId) + { + auto elements = gcnew array(3); + elements[0] = "JavaScriptProxy"; + elements[1] = parentProcessId.ToString(); + elements[2] = browserId.ToString(); + + return String::Join("_", elements); + } + }; + } + } +} diff --git a/CefSharp/ManagedCefBrowserAdapter.h b/CefSharp/ManagedCefBrowserAdapter.h index aef32842ab..3a4e1d46e0 100644 --- a/CefSharp/ManagedCefBrowserAdapter.h +++ b/CefSharp/ManagedCefBrowserAdapter.h @@ -9,11 +9,13 @@ #include "MouseButtonType.h" #include "ScriptCore.h" #include "Internals/JavascriptBinding/IJavascriptProxy.h" +#include "Internals/JavascriptBinding/JavascriptProxySupport.h" #include "Internals/IRenderWebBrowser.h" #include "Internals/RenderClientAdapter.h" using namespace CefSharp::Internals; using namespace CefSharp::Internals::JavascriptBinding; +using namespace System::Diagnostics; using namespace System::ServiceModel; namespace CefSharp @@ -260,11 +262,11 @@ namespace CefSharp if (browser != nullptr && frame != nullptr) { - // TODO: Make the address be shared between the Subprocess and the CefSharp projects. // TODO: Don't instantiate this on every request. The problem is that the CefBrowser is not set in our constructor. + auto serviceName = JavascriptProxySupport::GetServiceName(Process::GetCurrentProcess()->Id, _renderClientAdapter->GetCefBrowser()->GetIdentifier()); auto channelFactory = gcnew ChannelFactory( gcnew NetNamedPipeBinding(), - gcnew EndpointAddress("net.pipe://localhost/JavaScriptProxy_" + _renderClientAdapter->GetCefBrowser()->GetIdentifier()) + gcnew EndpointAddress(JavascriptProxySupport::BaseAddress + "/" + serviceName) ); _javaScriptProxy = channelFactory->CreateChannel(); diff --git a/CefSharp/ScriptCore.cpp b/CefSharp/ScriptCore.cpp index 5297fc6899..5b0830f8a4 100644 --- a/CefSharp/ScriptCore.cpp +++ b/CefSharp/ScriptCore.cpp @@ -37,36 +37,6 @@ namespace CefSharp if (TryGetMainFrame(browser, mainFrame)) { // TODO: fails, most likely because we are not in the render process. - CefRefPtr context = mainFrame->GetV8Context(); - - if (context.get() && context->Enter()) - { - CefRefPtr result; - CefRefPtr exception; - - bool success = context->Eval(script, result, exception); - if (success) - { - try - { - _result = TypeUtils::ConvertFromCef(result); - } - catch (Exception^ ex) - { - _exceptionMessage = ex->Message; - } - } - else if (exception.get()) - { - _exceptionMessage = StringUtils::ToClr(exception->GetMessage()); - } - else - { - _exceptionMessage = "Failed to evaluate script"; - } - - context->Exit(); - } } else { diff --git a/CefSharp/Wrappers/CefAppWrapper.h b/CefSharp/Wrappers/CefAppWrapper.h index e65c4a01b7..e60a768c8b 100644 --- a/CefSharp/Wrappers/CefAppWrapper.h +++ b/CefSharp/Wrappers/CefAppWrapper.h @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. #include "Stdafx.h" -#include "CefBrowserWrapper.h" +#include "CefSubprocessWrapper.h" #include "include/cef_app.h" namespace CefSharp @@ -25,7 +25,7 @@ namespace CefSharp CefAppWrapper(); ~CefAppWrapper(); void* GetUnmanaged(); - virtual void OnBrowserCreated(CefBrowserWrapper^ cefBrowserWrapper) {}; + virtual void OnBrowserCreated(CefSubprocessWrapper^ cefBrowserWrapper) {}; private: !CefAppWrapper(); @@ -51,7 +51,7 @@ namespace CefSharp virtual DECL void CefAppUnmanagedWrapper::OnBrowserCreated(CefRefPtr browser) OVERRIDE { // TODO: Could destroy this CefBrowserWrapper in OnBrowserDestroyed(), but it doesn't seem to be reliably called... - _cefAppWrapper->OnBrowserCreated(gcnew CefBrowserWrapper(browser)); + _cefAppWrapper->OnBrowserCreated(gcnew CefSubprocessWrapper(browser)); } IMPLEMENT_REFCOUNTING(CefAppUnmanagedWrapper); diff --git a/CefSharp/Wrappers/CefBrowserWrapper.h b/CefSharp/Wrappers/CefBrowserWrapper.h deleted file mode 100644 index 7f8333f7c0..0000000000 --- a/CefSharp/Wrappers/CefBrowserWrapper.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright © 2010-2013 The CefSharp Project. All rights reserved. -// -// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. - -#include "include/cef_browser.h" -#include "include/cef_runnable.h" - -using namespace System::Threading; - -namespace CefSharp -{ - namespace Wrappers - { - private class CefBrowserUnmanagedWrapper - { - CefRefPtr _cefBrowser; - - public: - CefRefPtr Frame; - gcroot WaitHandle; - - CefBrowserUnmanagedWrapper(CefRefPtr cefBrowser) - { - _cefBrowser = cefBrowser; - WaitHandle = gcnew AutoResetEvent(false); - } - - void GetFrameCallback(int64 frameId) - { - Frame = _cefBrowser->GetFrame(frameId); - WaitHandle->Set(); - } - - IMPLEMENT_REFCOUNTING(CefBrowserUnmanagedWrapper); - }; - - public ref class CefBrowserWrapper - { - CefRefPtr* _cefBrowser; - int _browserId; - CefRefPtr* _unmanagedWrapper; - - public: - property int BrowserId - { - int get() { return _browserId; } - private: void set(int value) { _browserId = value; } - } - - CefBrowserWrapper(CefRefPtr cefBrowser) - { - _cefBrowser = &cefBrowser; - _browserId = cefBrowser->GetIdentifier(); - - // TODO: Should be deallocated at some point to avoid leaking memory. - _unmanagedWrapper = new CefRefPtr(new CefBrowserUnmanagedWrapper(cefBrowser)); - } - - System::IntPtr GetFrame(long frameId) - { - // TODO: Could we do something genericly useful here using C++ lambdas? To avoid having to make a lot of of these... - // TODO: DON'T USE AUTORESETEVENT STUPIDITY! Even though the code below compiles & runs correctly, it deadlocks the - // thread from which the request came, which is very, very stupid, especially since V8 and Chromium are built - // with asynchrony in mind. Instead, we should re-think this API to utilize WCF callbacks instead: - // http://idunno.org/archive/2008/05/29/wcf-callbacks-a-beginners-guide.aspx - // That feels much more like 2013, and not 1994... :) - CefPostTask(CefThreadId::TID_RENDERER, NewCefRunnableMethod(_unmanagedWrapper->get(), &CefBrowserUnmanagedWrapper::GetFrameCallback, frameId)); - _unmanagedWrapper->get()->WaitHandle->WaitOne(); - - // TODO: Wrap in a CefFrameWrapper, obviously... - return (System::IntPtr)&_unmanagedWrapper->get()->Frame; - } - }; - } -} diff --git a/CefSharp/Wrappers/CefSubprocessWrapper.h b/CefSharp/Wrappers/CefSubprocessWrapper.h new file mode 100644 index 0000000000..9059f98085 --- /dev/null +++ b/CefSharp/Wrappers/CefSubprocessWrapper.h @@ -0,0 +1,124 @@ +// Copyright © 2010-2013 The CefSharp Project. All rights reserved. +// +// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + +#include "include/cef_browser.h" +#include "include/cef_runnable.h" + +using namespace System::Threading; + +namespace CefSharp +{ + namespace Wrappers + { + private class CefBrowserUnmanagedWrapper + { + CefRefPtr _cefBrowser; + + public: + gcroot WaitHandle; + gcroot EvaluateScriptResult; + gcroot EvaluateScriptExceptionMessage; + + CefBrowserUnmanagedWrapper(CefRefPtr cefBrowser) + { + _cefBrowser = cefBrowser; + WaitHandle = gcnew AutoResetEvent(false); + } + + void EvaluateScriptCallback(int64 frameId, CefString script, double timeout) + { + // TODO: Do something about the timeout... + + auto frame = _cefBrowser->GetFrame(frameId); + CefRefPtr context = frame->GetV8Context(); + + if (context.get() && context->Enter()) + { + EvaluateScriptInContext(context, script); + context->Exit(); + } + WaitHandle->Set(); + } + + void EvaluateScriptInContext(CefRefPtr context, CefString script) + { + CefRefPtr result; + CefRefPtr exception; + + EvaluateScriptExceptionMessage = nullptr; + + bool success = context->Eval(script, result, exception); + if (success) + { + try + { + EvaluateScriptResult = TypeUtils::ConvertFromCef(result); + } + catch (Exception^ ex) + { + EvaluateScriptExceptionMessage = ex->Message; + } + } + else if (exception.get()) + { + EvaluateScriptExceptionMessage = StringUtils::ToClr(exception->GetMessage()); + } + else + { + EvaluateScriptExceptionMessage = "Failed to evaluate script"; + } + } + + IMPLEMENT_REFCOUNTING(CefBrowserUnmanagedWrapper); + }; + + // "Master class" for wrapping everything that the CefSubprocess needs. + public ref class CefSubprocessWrapper + { + CefRefPtr* _cefBrowser; + int _browserId; + CefRefPtr* _unmanagedWrapper; + + public: + property int BrowserId + { + int get() { return _browserId; } + private: void set(int value) { _browserId = value; } + } + + CefSubprocessWrapper(CefRefPtr cefBrowser) + { + _cefBrowser = &cefBrowser; + _browserId = cefBrowser->GetIdentifier(); + + // TODO: Should be deallocated at some point to avoid leaking memory. + _unmanagedWrapper = new CefRefPtr(new CefBrowserUnmanagedWrapper(cefBrowser)); + } + + Object^ CefSubprocessWrapper::EvaluateScript(int frameId, String^ script, double timeout) + { + auto unmanagedWrapper = _unmanagedWrapper->get(); + + // TODO: Could we do something genericly useful here using C++ lambdas? To avoid having to make a lot of of these... + // TODO: DON'T USE AUTORESETEVENT STUPIDITY! Even though the code below compiles & runs correctly, it deadlocks the + // thread from which the request came, which is very, very stupid, especially since V8 and Chromium are built + // with asynchrony in mind. Instead, we should re-think this API to utilize WCF callbacks instead: + // http://idunno.org/archive/2008/05/29/wcf-callbacks-a-beginners-guide.aspx + // That feels much more like 2013, and not 1994... :) + // TODO: How about concurrency? One way to easily resolve it is to new() up something unique here and use that to + // invoke the method. + CefPostTask(CefThreadId::TID_RENDERER, NewCefRunnableMethod(unmanagedWrapper, + &CefBrowserUnmanagedWrapper::EvaluateScriptCallback, frameId, StringUtils::ToNative(script), timeout)); + unmanagedWrapper->WaitHandle->WaitOne(); + + if (static_cast(unmanagedWrapper->EvaluateScriptExceptionMessage) != nullptr) + { + throw gcnew FaultException(unmanagedWrapper->EvaluateScriptExceptionMessage); + } + + return unmanagedWrapper->EvaluateScriptResult; + } + }; + } +}