diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index f111d4a9a0..f49f1286ed 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -50,6 +50,13 @@ namespace CefSharp void CefAppUnmanagedWrapper::OnBrowserDestroyed(CefRefPtr browser) { + if (_separatedPopupBoundObjectsEnable) + { + JavascriptRootObject^ rootObj; + _javascriptRootObjects->TryRemove(browser->GetIdentifier(), rootObj); + _javascriptAsyncRootObjects->TryRemove(browser->GetIdentifier(), rootObj); + } + CefBrowserWrapper^ wrapper; if (_browserWrappers->TryRemove(browser->GetIdentifier(), wrapper)) { @@ -60,41 +67,11 @@ namespace CefSharp void CefAppUnmanagedWrapper::OnContextCreated(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) { - //Send a message to the browser processing signaling that OnContextCreated has been called - //only param is the FrameId. Currently an IPC message is only sent for the main frame - will see - //how viable this solution is and if it's worth expanding to sub/child frames. - if (frame->IsMain()) - { - auto contextCreatedMessage = CefProcessMessage::Create(kOnContextCreatedRequest); - - SetInt64(contextCreatedMessage->GetArgumentList(), 0, frame->GetIdentifier()); - - browser->SendProcessMessage(CefProcessId::PID_BROWSER, contextCreatedMessage); - } - - auto browserWrapper = FindBrowserWrapper(browser->GetIdentifier(), true); - - auto rootObjectWrappers = browserWrapper->JavascriptRootObjectWrappers; - auto frameId = frame->GetIdentifier(); + auto contextCreatedMessage = CefProcessMessage::Create(kOnContextCreatedRequest); - JavascriptRootObjectWrapper^ rootObject; - if (!rootObjectWrappers->TryGetValue(frameId, rootObject)) - { - rootObject = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier(), browserWrapper->BrowserProcess); - rootObjectWrappers->TryAdd(frameId, rootObject); - } + SetInt64(contextCreatedMessage->GetArgumentList(), 0, frame->GetIdentifier()); - if (rootObject->IsBound) - { - LOG(WARNING) << "A context has been created for the same browser / frame without context released called previously"; - } - else - { - if (!Object::ReferenceEquals(_javascriptRootObject, nullptr) || !Object::ReferenceEquals(_javascriptAsyncRootObject, nullptr)) - { - rootObject->Bind(_javascriptRootObject, _javascriptAsyncRootObject, context->GetGlobal()); - } - } + browser->SendProcessMessage(CefProcessId::PID_BROWSER, contextCreatedMessage); }; void CefAppUnmanagedWrapper::OnContextReleased(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) @@ -179,7 +156,8 @@ namespace CefSharp { if (name == kJavascriptCallbackDestroyRequest || name == kJavascriptRootObjectRequest || - name == kJavascriptAsyncMethodCallResponse) + name == kJavascriptAsyncMethodCallResponse || + name == kStartJavascriptBindingRequest) { //If we can't find the browser wrapper then we'll just //ignore this as it's likely already been disposed of @@ -215,7 +193,7 @@ namespace CefSharp return true; } - + //these messages are roughly handled the same way if (name == kEvaluateJavascriptRequest || name == kJavascriptCallbackRequest) { @@ -242,7 +220,7 @@ namespace CefSharp JavascriptRootObjectWrapper^ rootObjectWrapper; browserWrapper->JavascriptRootObjectWrappers->TryGetValue(frameId, rootObjectWrapper); - + //NOTE: In the rare case when when OnContextCreated hasn't been called we need to manually create the rootObjectWrapper //It appears that OnContextCreated is only called for pages that have javascript on them, which makes sense //as without javascript there is no need for a context. @@ -261,14 +239,14 @@ namespace CefSharp if (frame.get()) { auto context = frame->GetV8Context(); - + if (context.get() && context->Enter()) { try { CefRefPtr exception; success = context->Eval(script, result, exception); - + //we need to do this here to be able to store the v8context if (success) { @@ -317,14 +295,14 @@ namespace CefSharp { auto context = callbackWrapper->GetContext(); auto value = callbackWrapper->GetValue(); - + if (context.get() && context->Enter()) { try { auto parameterList = argList->GetList(3); CefV8ValueList params; - + //Needs to be called within the context as for Dictionary (mapped to struct) //a V8Object will be created for (CefV8ValueList::size_type i = 0; i < parameterList->GetSize(); i++) @@ -334,7 +312,7 @@ namespace CefSharp result = value->ExecuteFunction(nullptr, params); success = result.get() != nullptr; - + //we need to do this here to be able to store the v8context if (success) { @@ -386,15 +364,26 @@ namespace CefSharp } else if (name == kJavascriptRootObjectRequest) { - _javascriptAsyncRootObject = DeserializeJsRootObject(argList, 0); - _javascriptRootObject = DeserializeJsRootObject(argList, 1); + if (_separatedPopupBoundObjectsEnable) + { + _javascriptAsyncRootObjects->default[browser->GetIdentifier()] = DeserializeJsRootObject(argList, 0); + _javascriptRootObjects->default[browser->GetIdentifier()] = DeserializeJsRootObject(argList, 1); + } + else + { + _javascriptAsyncRootObject = DeserializeJsRootObject(argList, 0); + _javascriptRootObject = DeserializeJsRootObject(argList, 1); + } + + browser->SendProcessMessage(CefProcessId::PID_BROWSER, CefProcessMessage::Create(kJavascriptRootObjectResponse)); + handled = true; } else if (name == kJavascriptAsyncMethodCallResponse) { auto frameId = GetInt64(argList, 0); auto callbackId = GetInt64(argList, 1); - + JavascriptRootObjectWrapper^ rootObjectWrapper; browserWrapper->JavascriptRootObjectWrappers->TryGetValue(frameId, rootObjectWrapper); @@ -418,6 +407,16 @@ namespace CefSharp } handled = true; } + else if (name == kStartJavascriptBindingRequest) + { + auto frameList = argList->GetList(0); + + for (auto i = 0; i < frameList->GetSize(); i++) + { + auto frameId = GetInt64(frameList, i); + BindObjectForFrame(browser, browserWrapper, frameId); + } + } return handled; }; @@ -454,4 +453,51 @@ namespace CefSharp registrar->AddCustomScheme(StringUtils::ToNative(scheme->SchemeName), scheme->IsStandard, scheme->IsLocal, scheme->IsDisplayIsolated); } } + + void CefAppUnmanagedWrapper::BindObjectForFrame(const CefRefPtr &browser, CefBrowserWrapper^ wrapper, int64 frameId) + { + auto frame = browser->GetFrame(frameId); + auto rootObjectWrappers = wrapper->JavascriptRootObjectWrappers; + + if (frame.get()) + { + JavascriptRootObjectWrapper^ rootObject; + if (!rootObjectWrappers->TryGetValue(frameId, rootObject)) + { + rootObject = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier(), wrapper->BrowserProcess); + rootObjectWrappers->TryAdd(frameId, rootObject); + } + + if (rootObject->IsBound) + { + LOG(WARNING) << "A context has been created for the same browser / frame without context released called previously"; + } + else + { + auto context = frame->GetV8Context(); + try + { + if (context.get() && context->IsValid() && context->Enter()) + { + auto javascriptRootObject = static_cast(_javascriptRootObject); + auto javascriptAsyncRootObject = static_cast(_javascriptAsyncRootObject); + if (_separatedPopupBoundObjectsEnable) + { + _javascriptRootObjects->TryGetValue(browser->GetIdentifier(), javascriptRootObject); + _javascriptAsyncRootObjects->TryGetValue(browser->GetIdentifier(), javascriptAsyncRootObject); + } + if (javascriptRootObject != nullptr || + javascriptAsyncRootObject != nullptr) + { + rootObject->Bind(javascriptRootObject, javascriptAsyncRootObject, context->GetGlobal()); + } + } + } + finally + { + context->Exit(); + } + } + } + } } \ No newline at end of file diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h index 91bedb86d1..9cbf8c8007 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -26,6 +26,7 @@ namespace CefSharp gcroot^> _extensions; gcroot^> _schemes; bool _focusedNodeChangedEnabled; + bool _separatedPopupBoundObjectsEnable; // The serialized registered object data waiting to be used (only contains methods and bound async). gcroot _javascriptAsyncRootObject; @@ -33,10 +34,18 @@ namespace CefSharp // The serialized registered object data waiting to be used. gcroot _javascriptRootObject; + // The serialized registered object data waiting to be used (only contains methods and bound async) when + // separate bound objects are enabled. + gcroot^> _javascriptAsyncRootObjects; + + // The serialized registered object data waiting to be used when separate bound objects are enabled. + gcroot^> _javascriptRootObjects; + + void BindObjectForFrame(const CefRefPtr &browser, CefSharp::CefBrowserWrapper^ wrapper, int64 frameId); public: static const CefString kPromiseCreatorFunction; - CefAppUnmanagedWrapper(List^ schemes, bool enableFocusedNodeChanged, Action^ onBrowserCreated, Action^ onBrowserDestoryed) + CefAppUnmanagedWrapper(List^ schemes, bool enableFocusedNodeChanged, bool enableSeparatedPopupBoundObjects, Action^ onBrowserCreated, Action^ onBrowserDestoryed) { _onBrowserCreated = onBrowserCreated; _onBrowserDestroyed = onBrowserDestoryed; @@ -44,6 +53,9 @@ namespace CefSharp _extensions = gcnew List(); _schemes = schemes; _focusedNodeChangedEnabled = enableFocusedNodeChanged; + _separatedPopupBoundObjectsEnable = enableSeparatedPopupBoundObjects; + _javascriptRootObjects = gcnew ConcurrentDictionary(); + _javascriptAsyncRootObjects = gcnew ConcurrentDictionary(); } ~CefAppUnmanagedWrapper() diff --git a/CefSharp.BrowserSubprocess.Core/SubProcess.h b/CefSharp.BrowserSubprocess.Core/SubProcess.h index 6f3bb212ee..8c4fd4c6af 100644 --- a/CefSharp.BrowserSubprocess.Core/SubProcess.h +++ b/CefSharp.BrowserSubprocess.Core/SubProcess.h @@ -32,8 +32,9 @@ namespace CefSharp auto onBrowserDestroyed = gcnew Action(this, &SubProcess::OnBrowserDestroyed); auto schemes = CefCustomScheme::ParseCommandLineArguments(args); auto enableFocusedNodeChanged = CommandLineArgsParser::HasArgument(args, CefSharpArguments::FocusedNodeChangedEnabledArgument); + auto enableSeparatedBoundObjects = CommandLineArgsParser::HasArgument(args, CefSharpArguments::SeparateBoundObjectsArgument); - _cefApp = new CefAppUnmanagedWrapper(schemes, enableFocusedNodeChanged, onBrowserCreated, onBrowserDestroyed); + _cefApp = new CefAppUnmanagedWrapper(schemes, enableFocusedNodeChanged, enableSeparatedBoundObjects, onBrowserCreated, onBrowserDestroyed); } !SubProcess() diff --git a/CefSharp.Core/CefSettings.h b/CefSharp.Core/CefSettings.h index df3a13817b..cdd8fdf985 100644 --- a/CefSharp.Core/CefSettings.h +++ b/CefSharp.Core/CefSettings.h @@ -41,9 +41,6 @@ namespace CefSharp //Automatically discovered and load a system-wide installation of Pepper Flash. _cefCommandLineArgs->Add("enable-system-flash", "1"); - //Temp workaround for https://github.com/cefsharp/CefSharp/issues/1203 - _cefCommandLineArgs->Add("process-per-tab", "1"); - _focusedNodeChangedEnabled = false; } diff --git a/CefSharp.Core/Internals/CefSharpApp.h b/CefSharp.Core/Internals/CefSharpApp.h index 4886f0af96..5258f37eb8 100644 --- a/CefSharp.Core/Internals/CefSharpApp.h +++ b/CefSharp.Core/Internals/CefSharpApp.h @@ -60,6 +60,11 @@ namespace CefSharp commandLine->AppendArgument(StringUtils::ToNative(CefSharpArguments::WcfEnabledArgument)); } + if (CefSharpSettings::SeparateBoundObjects) + { + commandLine->AppendArgument(StringUtils::ToNative(CefSharpArguments::SeparateBoundObjectsArgument)); + } + if (_cefSettings->_cefCustomSchemes->Count > 0) { String^ argument = "="; diff --git a/CefSharp.Core/Internals/ClientAdapter.cpp b/CefSharp.Core/Internals/ClientAdapter.cpp index f50ed32af2..b4a6e16ee2 100644 --- a/CefSharp.Core/Internals/ClientAdapter.cpp +++ b/CefSharp.Core/Internals/ClientAdapter.cpp @@ -536,21 +536,6 @@ namespace CefSharp void ClientAdapter::OnRenderViewReady(CefRefPtr browser) { - if (!Object::ReferenceEquals(_browserAdapter, nullptr) && !_browserAdapter->IsDisposed && !browser->IsPopup()) - { - auto objectRepository = _browserAdapter->JavascriptObjectRepository; - - if (objectRepository->HasBoundObjects) - { - //transmit async bound objects - auto jsRootObjectMessage = CefProcessMessage::Create(kJavascriptRootObjectRequest); - auto argList = jsRootObjectMessage->GetArgumentList(); - SerializeJsObject(objectRepository->AsyncRootObject, argList, 0); - SerializeJsObject(objectRepository->RootObject, argList, 1); - browser->SendProcessMessage(CefProcessId::PID_RENDERER, jsRootObjectMessage); - } - } - auto handler = _browserControl->RequestHandler; if (handler != nullptr) @@ -571,6 +556,8 @@ namespace CefSharp handler->OnRenderProcessTerminated(_browserControl, browserWrapper, (CefTerminationStatus)status); } + + _boundObjectTransmitHelper->Reset(); } void ClientAdapter::OnResourceRedirect(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefString& newUrl) @@ -696,19 +683,57 @@ namespace CefSharp cef_return_value_t ClientAdapter::OnBeforeResourceLoad(CefRefPtr browser, CefRefPtr frame, CefRefPtr request, CefRefPtr callback) { - auto handler = _browserControl->RequestHandler; - - if (handler == nullptr) + if (!_boundObjectTransmitHelper->Completed) { - return cef_return_value_t::RV_CONTINUE; - } + auto canBind = false; + + if (!Object::ReferenceEquals(_browserAdapter, nullptr) && !_browserAdapter->IsDisposed) + { + auto objectRepository = _browserAdapter->JavascriptObjectRepository; + + if (objectRepository->HasBoundObjects) + { + canBind = true; + } + } + + if (canBind) + { + auto objectRepository = _browserAdapter->JavascriptObjectRepository; + //transmit async bound objects + auto jsRootObjectMessage = CefProcessMessage::Create(kJavascriptRootObjectRequest); + auto argList = jsRootObjectMessage->GetArgumentList(); + SerializeJsObject(objectRepository->AsyncRootObject, argList, 0); + SerializeJsObject(objectRepository->RootObject, argList, 1); + browser->SendProcessMessage(CefProcessId::PID_RENDERER, jsRootObjectMessage); + } + else + { + _boundObjectTransmitHelper->CompleteSync(); + } + } + + auto handler = _browserControl->RequestHandler; auto frameWrapper = gcnew CefFrameWrapper(frame); auto browserWrapper = GetBrowserWrapper(browser->GetIdentifier(), browser->IsPopup()); auto requestWrapper = gcnew CefRequestWrapper(request); auto requestCallback = gcnew CefRequestCallbackWrapper(callback, frameWrapper, requestWrapper); - return (cef_return_value_t)handler->OnBeforeResourceLoad(_browserControl, browserWrapper, frameWrapper, requestWrapper, requestCallback); + if (!_boundObjectTransmitHelper->Completed) + { + auto val = CefReturnValue::Continue; + if (handler != nullptr) + { + val = handler->OnBeforeResourceLoad(_browserControl, browserWrapper, frameWrapper, requestWrapper, _boundObjectTransmitHelper->PassableCallback); + } + return (cef_return_value_t)_boundObjectTransmitHelper->DoYield(requestCallback, val); + } + else if (handler != nullptr) + { + return (cef_return_value_t)handler->OnBeforeResourceLoad(_browserControl, browserWrapper, frameWrapper, requestWrapper, requestCallback); + } + return cef_return_value_t::RV_CONTINUE; } bool ClientAdapter::GetAuthCredentials(CefRefPtr browser, CefRefPtr frame, bool isProxy, @@ -1033,12 +1058,20 @@ namespace CefSharp if (name == kOnContextCreatedRequest) { + auto frameId = GetInt64(argList, 0); + if (_boundObjectTransmitHelper->Completed) + { + //start binding for the corresponding frame + vector frameIds{ frameId }; + SendJavascriptBindMessage(browser, frameIds); + } + auto handler = _browserControl->RenderProcessMessageHandler; if (handler != nullptr) { auto browserWrapper = GetBrowserWrapper(browser->GetIdentifier(), browser->IsPopup()); - CefFrameWrapper frameWrapper(browser->GetFrame(GetInt64(argList, 0))); + CefFrameWrapper frameWrapper(browser->GetFrame(frameId)); handler->OnContextCreated(_browserControl, browserWrapper, %frameWrapper); } @@ -1122,6 +1155,17 @@ namespace CefSharp handled = true; } + else if (name == kJavascriptRootObjectResponse && !_boundObjectTransmitHelper->Completed) + { + vector frameIdentifiers; + browser->GetFrameIdentifiers(frameIdentifiers); + //let's try to initiate bindings for all frames + SendJavascriptBindMessage(browser, frameIdentifiers); + + _boundObjectTransmitHelper->CompleteAsync(); + + handled = true; + } return handled; } @@ -1187,5 +1231,18 @@ namespace CefSharp } } } + + void ClientAdapter::SendJavascriptBindMessage(const CefRefPtr &browser, const vector &frameIdentifiers) + { + auto message = CefProcessMessage::Create(kStartJavascriptBindingRequest); + auto args = message->GetArgumentList(); + auto frameIdList = CefListValue::Create(); + for (auto i = 0; i < frameIdentifiers.size(); i++) + { + SetInt64(frameIdList, i, frameIdentifiers.at(i)); + } + args->SetList(0, frameIdList); + browser->SendProcessMessage(CefProcessId::PID_RENDERER, message); + } } } diff --git a/CefSharp.Core/Internals/ClientAdapter.h b/CefSharp.Core/Internals/ClientAdapter.h index fe92846528..c172c8f0a9 100644 --- a/CefSharp.Core/Internals/ClientAdapter.h +++ b/CefSharp.Core/Internals/ClientAdapter.h @@ -37,6 +37,7 @@ namespace CefSharp HWND _browserHwnd; CefRefPtr _cefBrowser; + gcroot _boundObjectTransmitHelper; gcroot _browser; gcroot^> _popupBrowsers; gcroot _tooltip; @@ -52,11 +53,13 @@ namespace CefSharp IBrowser^ GetBrowserWrapper(int browserId, bool isPopup); + void SendJavascriptBindMessage(const CefRefPtr &browser, const std::vector &frameIdentifiers); public: ClientAdapter(IWebBrowserInternal^ browserControl, IBrowserAdapter^ browserAdapter) : _browserControl(browserControl), _popupBrowsers(gcnew Dictionary()), _pendingTaskRepository(gcnew PendingTaskRepository()), + _boundObjectTransmitHelper(gcnew BoundObjectTransmitHelper()), _browserAdapter(browserAdapter), _browserHwnd(NULL) { diff --git a/CefSharp.Core/Internals/Messaging/Messages.h b/CefSharp.Core/Internals/Messaging/Messages.h index 95f9a56918..3c63dbf6fc 100644 --- a/CefSharp.Core/Internals/Messaging/Messages.h +++ b/CefSharp.Core/Internals/Messaging/Messages.h @@ -26,12 +26,16 @@ namespace CefSharp const CefString kJavascriptCallbackResponse = "JavascriptCallbackDoneResponse"; //Message containing a js root object for js bindings const CefString kJavascriptRootObjectRequest = "JavascriptRootObjectRequest"; + //Message to ack js root object for js bindings + const CefString kJavascriptRootObjectResponse = "JavascriptRootObjectResponse"; //Message from the render process to request a method invocation on a bound object const CefString kJavascriptAsyncMethodCallRequest = "JavascriptAsyncMethodCallRequest"; //Message from the browser process containing the result of a bound method invocation const CefString kJavascriptAsyncMethodCallResponse = "JavascriptAsyncMethodCallResponse"; //Message that signals a new V8Context has been created const CefString kOnContextCreatedRequest = "OnContextCreated"; + //Message that initiates javacript bindings in the subprocess + const CefString kStartJavascriptBindingRequest = "StartJavascriptBindingRequest"; // Message from the render process that an element (or nothing) has // gotten focus. This message is only sent if specified as an // optional message via command line argument when the subprocess is diff --git a/CefSharp.Example/CefExample.cs b/CefSharp.Example/CefExample.cs index 0a74bd922b..804b88feed 100644 --- a/CefSharp.Example/CefExample.cs +++ b/CefSharp.Example/CefExample.cs @@ -24,6 +24,7 @@ public static class CefExample public const string BasicSchemeTestUrl = "custom://cefsharp/SchemeTest.html"; public const string ResponseFilterTestUrl = "custom://cefsharp/ResponseFilterTest.html"; public const string DraggableRegionTestUrl = "custom://cefsharp/DraggableRegionTest.html"; + public const string MultitenantTestUrl = "custom://cefsharp/MultitenantTest.html"; public const string TestResourceUrl = "http://test/resource/load"; public const string RenderProcessCrashedUrl = "http://processcrashed"; public const string TestUnicodeResourceUrl = "http://test/resource/loadUnicode"; diff --git a/CefSharp.Example/CefSharp.Example.csproj b/CefSharp.Example/CefSharp.Example.csproj index 3c653489d7..aaee8d4f6f 100644 --- a/CefSharp.Example/CefSharp.Example.csproj +++ b/CefSharp.Example/CefSharp.Example.csproj @@ -102,6 +102,7 @@ + @@ -128,6 +129,7 @@ + diff --git a/CefSharp.Example/CefSharpSchemeHandler.cs b/CefSharp.Example/CefSharpSchemeHandler.cs index 9e4119dd75..1fd6461239 100644 --- a/CefSharp.Example/CefSharpSchemeHandler.cs +++ b/CefSharp.Example/CefSharpSchemeHandler.cs @@ -40,6 +40,7 @@ static CefSharpSchemeHandler() { "/bootstrap/bootstrap.min.js", Resources.bootstrap_min_js }, { "/BindingTest.html", Resources.BindingTest }, + { "/MultitenantTest.html", Resources.MultitenantTest }, { "/ExceptionTest.html", Resources.ExceptionTest }, { "/PopupTest.html", Resources.PopupTest }, { "/SchemeTest.html", Resources.SchemeTest }, diff --git a/CefSharp.Example/Properties/Resources.Designer.cs b/CefSharp.Example/Properties/Resources.Designer.cs index a0565671af..30a18bb559 100644 --- a/CefSharp.Example/Properties/Resources.Designer.cs +++ b/CefSharp.Example/Properties/Resources.Designer.cs @@ -466,6 +466,31 @@ internal static string MultiBindingTest { } } + /// + /// Looks up a localized string similar to <!DOCTYPE html> + ///<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> + ///<head> + /// <meta charset="utf-8" /> + /// <title>Multitenant Test</title> + /// <script> + /// function onLoad() + /// { + /// var p = document.getElementById('p'); + /// var btn = document.getElementById('btn'); + /// if (window.opener) + /// { + /// btn.parentElement.removeChild(btn); + /// } + /// uniqueObject.id().then(function(res) { + /// p.innerText = res; + /// [rest of string was truncated]";. + /// + internal static string MultitenantTest { + get { + return ResourceManager.GetString("MultitenantTest", resourceCulture); + } + } + /// /// Looks up a localized string similar to <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> ///<html> diff --git a/CefSharp.Example/Properties/Resources.resx b/CefSharp.Example/Properties/Resources.resx index 195d75e65a..765f02ca93 100644 --- a/CefSharp.Example/Properties/Resources.resx +++ b/CefSharp.Example/Properties/Resources.resx @@ -187,4 +187,7 @@ ..\Resources\DraggableRegionTest.html;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + ..\resources\multitenanttest.html;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + \ No newline at end of file diff --git a/CefSharp.Example/Resources/MultitenantTest.html b/CefSharp.Example/Resources/MultitenantTest.html new file mode 100644 index 0000000000..cb3a6a8c3f --- /dev/null +++ b/CefSharp.Example/Resources/MultitenantTest.html @@ -0,0 +1,77 @@ + + + + + Multitenant Test + + + + +

+ + \ No newline at end of file diff --git a/CefSharp.Example/UniqueBoundObject.cs b/CefSharp.Example/UniqueBoundObject.cs new file mode 100644 index 0000000000..49e28d874e --- /dev/null +++ b/CefSharp.Example/UniqueBoundObject.cs @@ -0,0 +1,24 @@ +// Copyright © 2010-2016 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.Threading; + +namespace CefSharp.Example +{ + public class UniqueBoundObject + { + private static int lastId = 0; + private int id; + + public UniqueBoundObject() + { + id = Interlocked.Increment(ref lastId); + } + + public int Id() + { + return id; + } + } +} diff --git a/CefSharp.Wpf.Example/App.xaml.cs b/CefSharp.Wpf.Example/App.xaml.cs index 8d66b29d1f..190adc1838 100644 --- a/CefSharp.Wpf.Example/App.xaml.cs +++ b/CefSharp.Wpf.Example/App.xaml.cs @@ -19,10 +19,10 @@ protected override void OnStartup(StartupEventArgs e) "please make sure you compile in `Release` mode.", "Warning"); } #endif - + CefSharpSettings.SeparateBoundObjects = true; CefExample.Init(true, multiThreadedMessageLoop: true, browserProcessHandler: new BrowserProcessHandler()); base.OnStartup(e); } } -} \ No newline at end of file +} diff --git a/CefSharp.Wpf.Example/Handlers/LifespanHandler.cs b/CefSharp.Wpf.Example/Handlers/LifespanHandler.cs index 8ddf0d341c..7d926a7766 100644 --- a/CefSharp.Wpf.Example/Handlers/LifespanHandler.cs +++ b/CefSharp.Wpf.Example/Handlers/LifespanHandler.cs @@ -5,6 +5,7 @@ using System; using System.Windows; using System.Windows.Interop; +using CefSharp.Example; namespace CefSharp.Wpf.Example.Handlers { @@ -14,79 +15,80 @@ bool ILifeSpanHandler.OnBeforePopup(IWebBrowser browserControl, IBrowser browser { newBrowser = null; - return false; +// return false; //NOTE: This is experimental - //var chromiumWebBrowser = (ChromiumWebBrowser)browserControl; - - //ChromiumWebBrowser chromiumBrowser = null; - - //var windowX = (windowInfo.X == int.MinValue) ? double.NaN : windowInfo.X; - //var windowY = (windowInfo.Y == int.MinValue) ? double.NaN : windowInfo.Y; - //var windowWidth = (windowInfo.Width == int.MinValue) ? double.NaN : windowInfo.Width; - //var windowHeight = (windowInfo.Height == int.MinValue) ? double.NaN : windowInfo.Height; - - //chromiumWebBrowser.Dispatcher.Invoke(() => - //{ - // var owner = Window.GetWindow(chromiumWebBrowser); - // chromiumBrowser = new ChromiumWebBrowser - // { - // Address = targetUrl, - // }; - - // chromiumBrowser.SetAsPopup(); - // chromiumBrowser.LifeSpanHandler = this; - - // var popup = new Window - // { - // Left = windowX, - // Top = windowY, - // Width = windowWidth, - // Height = windowHeight, - // Content = chromiumBrowser, - // Owner = owner, - // Title = targetFrameName - // }; - - // var windowInteropHelper = new WindowInteropHelper(popup); - // //Create the handle Window handle (In WPF there's only one handle per window, not per control) - // var handle = windowInteropHelper.EnsureHandle(); - - // //The parentHandle value will be used to identify monitor info and to act as the parent window for dialogs, - // //context menus, etc. If parentHandle is not provided then the main screen monitor will be used and some - // //functionality that requires a parent window may not function correctly. - // windowInfo.SetAsWindowless(handle, true); - - // popup.Closed += (o, e) => - // { - // var w = o as Window; - // if (w != null && w.Content is IWebBrowser) - // { - // (w.Content as IWebBrowser).Dispose(); - // w.Content = null; - // } - // }; - //}); - - //newBrowser = chromiumBrowser; - - //return false; + var chromiumWebBrowser = (ChromiumWebBrowser)browserControl; + + ChromiumWebBrowser chromiumBrowser = null; + + var windowX = (windowInfo.X == int.MinValue) ? double.NaN : windowInfo.X; + var windowY = (windowInfo.Y == int.MinValue) ? double.NaN : windowInfo.Y; + var windowWidth = (windowInfo.Width == int.MinValue) ? double.NaN : windowInfo.Width; + var windowHeight = (windowInfo.Height == int.MinValue) ? double.NaN : windowInfo.Height; + + chromiumWebBrowser.Dispatcher.Invoke(() => + { + var owner = Window.GetWindow(chromiumWebBrowser); + chromiumBrowser = new ChromiumWebBrowser + { + Address = targetUrl, + }; + chromiumBrowser.RegisterAsyncJsObject("uniqueObject", new UniqueBoundObject()); + + chromiumBrowser.SetAsPopup(); + chromiumBrowser.LifeSpanHandler = this; + + var popup = new Window + { + Left = windowX, + Top = windowY, + Width = windowWidth, + Height = windowHeight, + Content = chromiumBrowser, + Owner = owner, + Title = targetFrameName + }; + + var windowInteropHelper = new WindowInteropHelper(popup); + //Create the handle Window handle (In WPF there's only one handle per window, not per control) + var handle = windowInteropHelper.EnsureHandle(); + + //The parentHandle value will be used to identify monitor info and to act as the parent window for dialogs, + //context menus, etc. If parentHandle is not provided then the main screen monitor will be used and some + //functionality that requires a parent window may not function correctly. + windowInfo.SetAsWindowless(handle, true); + + popup.Closed += (o, e) => + { + var w = o as Window; + if (w != null && w.Content is IWebBrowser) + { + (w.Content as IWebBrowser).Dispose(); + w.Content = null; + } + }; + }); + + newBrowser = chromiumBrowser; + + return false; } void ILifeSpanHandler.OnAfterCreated(IWebBrowser browserControl, IBrowser browser) { //NOTE: This is experimental - //var chromiumWebBrowser = (ChromiumWebBrowser)browserControl; + var chromiumWebBrowser = (ChromiumWebBrowser)browserControl; - //chromiumWebBrowser.Dispatcher.Invoke(() => - //{ - // var owner = Window.GetWindow(chromiumWebBrowser); + chromiumWebBrowser.Dispatcher.Invoke(() => + { + var owner = Window.GetWindow(chromiumWebBrowser); - // if (owner != null && owner.Content == browserControl) - // { - // owner.Show(); - // } - //}); + if (owner != null && owner.Content == browserControl) + { + owner.Show(); + } + }); } bool ILifeSpanHandler.DoClose(IWebBrowser browserControl, IBrowser browser) @@ -97,17 +99,17 @@ bool ILifeSpanHandler.DoClose(IWebBrowser browserControl, IBrowser browser) void ILifeSpanHandler.OnBeforeClose(IWebBrowser browserControl, IBrowser browser) { //NOTE: This is experimental - //var chromiumWebBrowser = (ChromiumWebBrowser)browserControl; + var chromiumWebBrowser = (ChromiumWebBrowser)browserControl; - //chromiumWebBrowser.Dispatcher.Invoke(() => - //{ - // var owner = Window.GetWindow(chromiumWebBrowser); + chromiumWebBrowser.Dispatcher.Invoke(() => + { + var owner = Window.GetWindow(chromiumWebBrowser); - // if (owner != null && owner.Content == browserControl) - // { - // owner.Close(); - // } - //}); + if (owner != null && owner.Content == browserControl) + { + owner.Close(); + } + }); } } } diff --git a/CefSharp.Wpf.Example/MainWindow.xaml b/CefSharp.Wpf.Example/MainWindow.xaml index f4e4df44c0..7362b226ba 100644 --- a/CefSharp.Wpf.Example/MainWindow.xaml +++ b/CefSharp.Wpf.Example/MainWindow.xaml @@ -21,6 +21,7 @@ + diff --git a/CefSharp.Wpf.Example/Views/BrowserTabView.xaml.cs b/CefSharp.Wpf.Example/Views/BrowserTabView.xaml.cs index d62d7a622d..e994d5678d 100644 --- a/CefSharp.Wpf.Example/Views/BrowserTabView.xaml.cs +++ b/CefSharp.Wpf.Example/Views/BrowserTabView.xaml.cs @@ -26,6 +26,7 @@ public BrowserTabView() browser.RequestHandler = new RequestHandler(); browser.RegisterJsObject("bound", new BoundObject(), BindingOptions.DefaultBinder); browser.RegisterAsyncJsObject("boundAsync", new AsyncBoundObject()); + browser.RegisterAsyncJsObject("uniqueObject", new UniqueBoundObject()); // Enable touch scrolling - once properly tested this will likely become the default //browser.IsManipulationEnabled = true; diff --git a/CefSharp/CefSharp.csproj b/CefSharp/CefSharp.csproj index 8801db691d..a41b440009 100644 --- a/CefSharp/CefSharp.csproj +++ b/CefSharp/CefSharp.csproj @@ -92,6 +92,7 @@ + diff --git a/CefSharp/CefSharpSettings.cs b/CefSharp/CefSharpSettings.cs index 76765f7c17..0793b263d4 100644 --- a/CefSharp/CefSharpSettings.cs +++ b/CefSharp/CefSharpSettings.cs @@ -41,5 +41,13 @@ static CefSharpSettings() /// the event handlers are hooked in the static constructor for the ChromiumWebBrowser class ///
public static bool ShutdownOnExit { get; set; } + + /// + /// By default when multiple browser instances are hosted in the same subprocess, there can + /// be only one set of bound objects. Enabling this will allow registering separate objects for them + /// for example when doing window.open and custom popup management with + /// or limiting subprocess count. + /// + public static bool SeparateBoundObjects { get; set; } } } diff --git a/CefSharp/Internals/BoundObjectTransmitHelper.cs b/CefSharp/Internals/BoundObjectTransmitHelper.cs new file mode 100644 index 0000000000..4e1d789f61 --- /dev/null +++ b/CefSharp/Internals/BoundObjectTransmitHelper.cs @@ -0,0 +1,129 @@ +// Copyright © 2010-2016 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.Internals +{ + public sealed class BoundObjectTransmitHelper + { + //dummy IRequestCallback implementation that stores callback values + private class DummyCallback : IRequestCallback + { + private bool isDisposed; + + public bool Allowed { get; private set; } + + public bool Cancelled { get; private set; } + + public void Dispose() + { + isDisposed = true; + } + + public void Continue(bool allow) + { + Allowed = allow; + } + + public void Cancel() + { + Cancelled = true; + } + + public bool IsDisposed + { + get { return isDisposed; } + } + } + + private DummyCallback passableCallback; + private CefReturnValue returnValue; + private IRequestCallback originalCallback; + + //bound objects has arrived to the subprocess + public bool Completed { get; private set; } + + public IRequestCallback PassableCallback + { + get + { + if (passableCallback == null) + { + passableCallback = new DummyCallback(); + } + return passableCallback; + } + } + + //mark it complete without further action (for example when no bound objects) + public void CompleteSync() + { + Completed = true; + } + + //complete after ack received from subprocess for bound object message + public void CompleteAsync() + { + if (Completed) + return; + + switch (returnValue) + { + case CefReturnValue.Cancel: + originalCallback.Cancel(); + break; + case CefReturnValue.ContinueAsync: + ContinueAsyncBasedOnPassableCallback(); + break; + case CefReturnValue.Continue: + originalCallback.Continue(true); + break; + } + + ResetCallbacks(); + Completed = true; + } + + public void Reset() + { + ResetCallbacks(); + Completed = false; + } + + public CefReturnValue DoYield(IRequestCallback originalCallback, CefReturnValue returnValue) + { + this.originalCallback = originalCallback; + this.returnValue = returnValue; + return CefReturnValue.ContinueAsync; + } + + private void ResetCallbacks() + { + if (originalCallback != null) + { + originalCallback.Dispose(); + } + originalCallback = null; + passableCallback = null; + } + + private void ContinueAsyncBasedOnPassableCallback() + { + if (passableCallback != null) + { + if (passableCallback.Cancelled) + { + originalCallback.Cancel(); + } + else + { + originalCallback.Continue(passableCallback.Allowed); + } + } + else + { + originalCallback.Continue(true); + } + } + } +} \ No newline at end of file diff --git a/CefSharp/Internals/CefSharpArguments.cs b/CefSharp/Internals/CefSharpArguments.cs index 01337820f3..8775f55532 100644 --- a/CefSharp/Internals/CefSharpArguments.cs +++ b/CefSharp/Internals/CefSharpArguments.cs @@ -9,5 +9,6 @@ public static class CefSharpArguments public const string WcfEnabledArgument = "--wcf-enabled"; public const string CustomSchemeArgument = "--custom-scheme"; public const string FocusedNodeChangedEnabledArgument = "--focused-node-enabled"; + public const string SeparateBoundObjectsArgument = "--separate-bound-objects"; } }