From 48b1258afa0b786f3d125c25310f885c52973c6a Mon Sep 17 00:00:00 2001 From: cefsharp-ms Date: Thu, 5 May 2016 17:40:11 +0200 Subject: [PATCH] Modified binding so it's always triggered after the bound objects has been transmitted to the subprocess --- .../CefAppUnmanagedWrapper.cpp | 98 +++++++++------ .../CefAppUnmanagedWrapper.h | 2 + CefSharp.Core/Internals/ClientAdapter.cpp | 99 +++++++++++---- CefSharp.Core/Internals/ClientAdapter.h | 3 + CefSharp.Core/Internals/Messaging/Messages.h | 4 + CefSharp/CefSharp.csproj | 1 + .../Internals/BoundObjectTransmitHelper.cs | 115 ++++++++++++++++++ 7 files changed, 262 insertions(+), 60 deletions(-) create mode 100644 CefSharp/Internals/BoundObjectTransmitHelper.cs diff --git a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp index 6c4ee0d78c..58846c7bd8 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.cpp @@ -71,45 +71,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 contextCreatedMessage = CefProcessMessage::Create(kOnContextCreatedRequest); - auto rootObjectWrappers = browserWrapper->JavascriptRootObjectWrappers; - auto frameId = frame->GetIdentifier(); - - 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 - { - JavascriptRootObject^ syncRootObject; - JavascriptRootObject^ asyncRootObject; - _javascriptRootObjects->TryGetValue(browser->GetIdentifier(), syncRootObject); - _javascriptAsyncRootObjects->TryGetValue(browser->GetIdentifier(), asyncRootObject); - if (syncRootObject != nullptr || asyncRootObject != nullptr) - { - rootObject->Bind(syncRootObject, asyncRootObject, context->GetGlobal()); - } - } + browser->SendProcessMessage(CefProcessId::PID_BROWSER, contextCreatedMessage); }; void CefAppUnmanagedWrapper::OnContextReleased(CefRefPtr browser, CefRefPtr frame, CefRefPtr context) @@ -194,7 +160,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 @@ -413,6 +380,8 @@ namespace CefSharp _mainJavascriptRootObject = syncRoot; } + browser->SendProcessMessage(CefProcessId::PID_BROWSER, CefProcessMessage::Create(kJavascriptRootObjectResponse)); + handled = true; } else if (name == kJavascriptAsyncMethodCallResponse) @@ -443,6 +412,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; }; @@ -479,4 +458,47 @@ 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()) + { + JavascriptRootObject^ syncRootObject; + JavascriptRootObject^ asyncRootObject; + _javascriptRootObjects->TryGetValue(browser->GetIdentifier(), syncRootObject); + _javascriptAsyncRootObjects->TryGetValue(browser->GetIdentifier(), asyncRootObject); + if (syncRootObject != nullptr || asyncRootObject != nullptr) + { + rootObject->Bind(syncRootObject, asyncRootObject, 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 cffc059664..c78ab9381d 100644 --- a/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h +++ b/CefSharp.BrowserSubprocess.Core/CefAppUnmanagedWrapper.h @@ -36,6 +36,8 @@ namespace CefSharp // Store the js binding information for the non-popup browser in this subprocess gcroot _mainJavascriptRootObject; gcroot _mainAsyncJavascriptRootObject; + + void BindObjectForFrame(const CefRefPtr &browser, CefSharp::CefBrowserWrapper^ wrapper, int64 frameId); public: static const CefString kPromiseCreatorFunction; diff --git a/CefSharp.Core/Internals/ClientAdapter.cpp b/CefSharp.Core/Internals/ClientAdapter.cpp index f50ed32af2..8574a0ef98 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) @@ -696,19 +681,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 +1056,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 +1153,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 +1229,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/CefSharp.csproj b/CefSharp/CefSharp.csproj index 8801db691d..958eabd818 100644 --- a/CefSharp/CefSharp.csproj +++ b/CefSharp/CefSharp.csproj @@ -93,6 +93,7 @@ + diff --git a/CefSharp/Internals/BoundObjectTransmitHelper.cs b/CefSharp/Internals/BoundObjectTransmitHelper.cs new file mode 100644 index 0000000000..f8eaf52662 --- /dev/null +++ b/CefSharp/Internals/BoundObjectTransmitHelper.cs @@ -0,0 +1,115 @@ +// 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; + } + + originalCallback.Dispose(); + originalCallback = null; + passableCallback = null; + Completed = true; + } + + public CefReturnValue DoYield(IRequestCallback originalCallback, CefReturnValue returnValue) + { + this.originalCallback = originalCallback; + this.returnValue = returnValue; + return CefReturnValue.ContinueAsync; + } + + private void ContinueAsyncBasedOnPassableCallback() + { + if (passableCallback != null) + { + if (passableCallback.Cancelled) + { + originalCallback.Cancel(); + } + else + { + originalCallback.Continue(passableCallback.Allowed); + } + } + else + { + originalCallback.Continue(true); + } + } + } +}