diff --git a/libcef/browser/browser_frame.cc b/libcef/browser/browser_frame.cc index 6aec8e23b..911b8e5e9 100644 --- a/libcef/browser/browser_frame.cc +++ b/libcef/browser/browser_frame.cc @@ -35,18 +35,20 @@ void CefBrowserFrame::RegisterBrowserInterfaceBindersForFrame( void CefBrowserFrame::SendMessage(const std::string& name, base::Value arguments) { - // Always associate with the newly created RFH, which may be speculative when + // Always send to the newly created RFH, which may be speculative when // navigating cross-origin. if (auto host = GetFrameHost(/*prefer_speculative=*/true)) { host->SendMessage(name, std::move(arguments)); } } -void CefBrowserFrame::FrameAttached() { +void CefBrowserFrame::FrameAttached( + mojo::PendingRemote render_frame, + bool reattached) { // Always send to the newly created RFH, which may be speculative when // navigating cross-origin. if (auto host = GetFrameHost(/*prefer_speculative=*/true)) { - host->FrameAttached(); + host->FrameAttached(std::move(render_frame), reattached); } } diff --git a/libcef/browser/browser_frame.h b/libcef/browser/browser_frame.h index 7a48cc224..8f5dd8183 100644 --- a/libcef/browser/browser_frame.h +++ b/libcef/browser/browser_frame.h @@ -36,7 +36,8 @@ class CefBrowserFrame private: // cef::mojom::BrowserFrame methods: void SendMessage(const std::string& name, base::Value arguments) override; - void FrameAttached() override; + void FrameAttached(mojo::PendingRemote render_frame, + bool reattached) override; void DidFinishFrameLoad(const GURL& validated_url, int32_t http_status_code) override; void UpdateDraggableRegions( diff --git a/libcef/browser/browser_info.cc b/libcef/browser/browser_info.cc index b0ba616f1..e80573afb 100644 --- a/libcef/browser/browser_info.cc +++ b/libcef/browser/browser_info.cc @@ -168,10 +168,13 @@ void CefBrowserInfo::FrameHostStateChanged( content::RenderFrameHost::LifecycleState::kInBackForwardCache) && new_state == content::RenderFrameHost::LifecycleState::kActive) { if (auto frame = GetFrameForHost(host)) { + // Should only occur for the main frame. + CHECK(frame->IsMain()); + // Update the associated RFH, which may have changed. frame->MaybeReAttach(this, host); - if (frame->IsMain()) { + { // Update the main frame object. NotificationStateLock lock_scope(this); SetMainFrame(browser_, frame); diff --git a/libcef/browser/frame_host_impl.cc b/libcef/browser/frame_host_impl.cc index 1b59d7d19..5891f5678 100644 --- a/libcef/browser/frame_host_impl.cc +++ b/libcef/browser/frame_host_impl.cc @@ -449,13 +449,12 @@ bool CefFrameHostImpl::Detach() { } // In case we never attached, clean up. - while (!queued_actions_.empty()) { - queued_actions_.pop(); + while (!queued_renderer_actions_.empty()) { + queued_renderer_actions_.pop(); } render_frame_.reset(); render_frame_host_ = nullptr; - is_attached_ = false; return first_detach; } @@ -464,14 +463,14 @@ void CefFrameHostImpl::MaybeReAttach( scoped_refptr browser_info, content::RenderFrameHost* render_frame_host) { CEF_REQUIRE_UIT(); - if (is_attached_ && render_frame_host_ == render_frame_host) { + if (render_frame_.is_bound() && render_frame_host_ == render_frame_host) { // Nothing to do here. return; } // We expect that Detach() was called previously. CHECK(!is_temporary()); - CHECK(!is_attached_); + CHECK(!render_frame_.is_bound()); CHECK(!render_frame_host_); // The RFH may change but the GlobalId should remain the same. @@ -486,8 +485,7 @@ void CefFrameHostImpl::MaybeReAttach( render_frame_host_ = render_frame_host; RefreshAttributes(); - // Restore the RenderFrame connection. - FrameAttachedInternal(/*reattached=*/true); + // We expect a reconnect to be triggered via FrameAttached(). } // kMainFrameId must be -1 to align with renderer expectations. @@ -517,20 +515,6 @@ CefRefPtr CefFrameHostImpl::GetBrowserHostBase() const { return nullptr; } -const mojo::Remote& -CefFrameHostImpl::GetRenderFrame() { - CEF_REQUIRE_UIT(); - DCHECK(is_attached_); - - if (!render_frame_.is_bound() && render_frame_host_ && - render_frame_host_->GetRemoteInterfaces()) { - // Connects to a CefFrameImpl that already exists in the renderer process. - render_frame_host_->GetRemoteInterfaces()->GetInterface( - render_frame_.BindNewPipeAndPassReceiver()); - } - return render_frame_; -} - void CefFrameHostImpl::SendToRenderFrame(const std::string& function_name, RenderFrameAction action) { if (!CEF_CURRENTLY_ON_UIT()) { @@ -553,18 +537,22 @@ void CefFrameHostImpl::SendToRenderFrame(const std::string& function_name, return; } - if (!is_attached_) { + if (!render_frame_.is_bound()) { // Queue actions until we're notified by the renderer that it's ready to // handle them. - queued_actions_.push(std::make_pair(function_name, std::move(action))); + queued_renderer_actions_.push( + std::make_pair(function_name, std::move(action))); return; } - auto& render_frame = GetRenderFrame(); - if (!render_frame) - return; + std::move(action).Run(render_frame_); +} - std::move(action).Run(render_frame); +void CefFrameHostImpl::OnRenderFrameDisconnect() { + CEF_REQUIRE_UIT(); + + // Reconnect, if any, will be triggered via FrameAttached(). + render_frame_.reset(); } void CefFrameHostImpl::SendMessage(const std::string& name, @@ -581,12 +569,11 @@ void CefFrameHostImpl::SendMessage(const std::string& name, } } -void CefFrameHostImpl::FrameAttached() { - FrameAttachedInternal(/*reattached=*/false); -} - -void CefFrameHostImpl::FrameAttachedInternal(bool reattached) { +void CefFrameHostImpl::FrameAttached( + mojo::PendingRemote render_frame_remote, + bool reattached) { CEF_REQUIRE_UIT(); + CHECK(render_frame_remote); auto browser_info = GetBrowserInfo(); if (!browser_info) { @@ -594,27 +581,32 @@ void CefFrameHostImpl::FrameAttachedInternal(bool reattached) { return; } - DCHECK(!is_attached_); - if (!is_attached_) { - is_attached_ = true; - - auto& render_frame = GetRenderFrame(); - while (!queued_actions_.empty()) { - if (render_frame) { - std::move(queued_actions_.front().second).Run(render_frame); - } - queued_actions_.pop(); - } - - browser_info->MaybeExecuteFrameNotification(base::BindOnce( - [](CefRefPtr self, bool reattached, - CefRefPtr handler) { - if (auto browser = self->GetBrowserHostBase()) { - handler->OnFrameAttached(browser, self, reattached); - } - }, - CefRefPtr(this), reattached)); + if (reattached) { + LOG(INFO) << (is_main_frame_ ? "main" : "sub") << "frame " + << frame_util::GetFrameDebugString(frame_id_) + << " has reconnected"; } + + render_frame_.Bind(std::move(render_frame_remote)); + render_frame_.set_disconnect_handler( + base::BindOnce(&CefFrameHostImpl::OnRenderFrameDisconnect, this)); + + // Notify the renderer process that it can start sending messages. + render_frame_->FrameAttachedAck(); + + while (!queued_renderer_actions_.empty()) { + std::move(queued_renderer_actions_.front().second).Run(render_frame_); + queued_renderer_actions_.pop(); + } + + browser_info->MaybeExecuteFrameNotification(base::BindOnce( + [](CefRefPtr self, bool reattached, + CefRefPtr handler) { + if (auto browser = self->GetBrowserHostBase()) { + handler->OnFrameAttached(browser, self, reattached); + } + }, + CefRefPtr(this), reattached)); } void CefFrameHostImpl::DidFinishFrameLoad(const GURL& validated_url, diff --git a/libcef/browser/frame_host_impl.h b/libcef/browser/frame_host_impl.h index 4b3dd572a..1158b9f6a 100644 --- a/libcef/browser/frame_host_impl.h +++ b/libcef/browser/frame_host_impl.h @@ -130,7 +130,8 @@ class CefFrameHostImpl : public CefFrame, public cef::mojom::BrowserFrame { // cef::mojom::BrowserFrame methods forwarded from CefBrowserFrame. void SendMessage(const std::string& name, base::Value arguments) override; - void FrameAttached() override; + void FrameAttached(mojo::PendingRemote render_frame, + bool reattached) override; void DidFinishFrameLoad(const GURL& validated_url, int32_t http_status_code) override; void UpdateDraggableRegions( @@ -152,17 +153,14 @@ class CefFrameHostImpl : public CefFrame, public cef::mojom::BrowserFrame { scoped_refptr GetBrowserInfo() const; CefRefPtr GetBrowserHostBase() const; - // Returns the remote RenderFrame object. - using RenderFrameType = mojo::Remote; - const RenderFrameType& GetRenderFrame(); - // Send an action to the remote RenderFrame. This will queue the action if the // remote frame is not yet attached. + using RenderFrameType = mojo::Remote; using RenderFrameAction = base::OnceCallback; void SendToRenderFrame(const std::string& function_name, RenderFrameAction action); - void FrameAttachedInternal(bool reattached); + void OnRenderFrameDisconnect(); const bool is_main_frame_; @@ -179,9 +177,8 @@ class CefFrameHostImpl : public CefFrame, public cef::mojom::BrowserFrame { // The following members are only accessed on the UI thread. content::RenderFrameHost* render_frame_host_ = nullptr; - bool is_attached_ = false; - - std::queue> queued_actions_; + std::queue> + queued_renderer_actions_; mojo::Remote render_frame_; diff --git a/libcef/common/mojom/cef.mojom b/libcef/common/mojom/cef.mojom index 821cc92f9..f555f00a4 100644 --- a/libcef/common/mojom/cef.mojom +++ b/libcef/common/mojom/cef.mojom @@ -19,21 +19,6 @@ struct DraggableRegionEntry { bool draggable; }; -// Interface for communicating with a frame in the browser process. -interface BrowserFrame { - // Send a message to the browser process. - SendMessage(string name, mojo_base.mojom.ListValue arguments); - - // The render frame is ready to begin handling actions. - FrameAttached(); - - // The render frame has finished loading. - DidFinishFrameLoad(url.mojom.Url validated_url, int32 http_status_code); - - // Draggable regions have updated. - UpdateDraggableRegions(array? regions); -}; - // Structure passed to LoadRequest(). struct RequestParams { // Request method. @@ -64,6 +49,9 @@ struct RequestParams { // Interface for communicating with a frame in the renderer process. interface RenderFrame { + // Browser process has received the FrameAttached() message. + FrameAttachedAck(); + // Send a message to the render process. SendMessage(string name, mojo_base.mojom.ListValue arguments); @@ -91,6 +79,22 @@ interface RenderFrame { MoveOrResizeStarted(); }; +// Interface for communicating with a frame in the browser process. +interface BrowserFrame { + // Send a message to the browser process. + SendMessage(string name, mojo_base.mojom.ListValue arguments); + + // The render frame is ready to begin handling actions. + FrameAttached(pending_remote render_frame, + bool reattached); + + // The render frame has finished loading. + DidFinishFrameLoad(url.mojom.Url validated_url, int32 http_status_code); + + // Draggable regions have updated. + UpdateDraggableRegions(array? regions); +}; + struct CrossOriginWhiteListEntry { string source_origin; string target_protocol; diff --git a/libcef/renderer/blink_glue.cc b/libcef/renderer/blink_glue.cc index fb6baf2f3..ad57018d2 100644 --- a/libcef/renderer/blink_glue.cc +++ b/libcef/renderer/blink_glue.cc @@ -29,12 +29,14 @@ #include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" #include "third_party/blink/renderer/core/loader/frame_load_request.h" +#include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/script/classic_script.h" #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h" #include "third_party/blink/renderer/platform/bindings/v8_binding.h" #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h" #include "third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h" #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h" +#include "third_party/blink/renderer/platform/scheduler/public/page_scheduler.h" #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" #undef LOG @@ -86,6 +88,14 @@ void GoForward(blink::WebView* view) { } } +bool IsInBackForwardCache(blink::WebLocalFrame* frame) { + blink::Frame* core_frame = blink::WebFrame::ToCoreFrame(*frame); + return blink::To(core_frame) + ->GetPage() + ->GetPageScheduler() + ->IsInBackForwardCache(); +} + blink::WebString DumpDocumentText(blink::WebLocalFrame* frame) { // We use the document element's text instead of the body text here because // not all documents have a body, such as XML documents. diff --git a/libcef/renderer/blink_glue.h b/libcef/renderer/blink_glue.h index 9f452e04c..0d0494610 100644 --- a/libcef/renderer/blink_glue.h +++ b/libcef/renderer/blink_glue.h @@ -39,6 +39,8 @@ BLINK_EXPORT bool CanGoForward(blink::WebView* view); BLINK_EXPORT void GoBack(blink::WebView* view); BLINK_EXPORT void GoForward(blink::WebView* view); +BLINK_EXPORT bool IsInBackForwardCache(blink::WebLocalFrame* frame); + // Returns the text of the document element. BLINK_EXPORT blink::WebString DumpDocumentText(blink::WebLocalFrame* frame); // Returns the markup of the document element. diff --git a/libcef/renderer/frame_impl.cc b/libcef/renderer/frame_impl.cc index 6c5effdb5..b2a5af2b6 100644 --- a/libcef/renderer/frame_impl.cc +++ b/libcef/renderer/frame_impl.cc @@ -48,6 +48,19 @@ #include "third_party/blink/public/web/web_script_source.h" #include "third_party/blink/public/web/web_view.h" +namespace { + +// Maximum number of times to retry the browser connection. +constexpr size_t kConnectionRetryMaxCt = 3U; + +// Length of time to wait before initiating a browser connection retry. +constexpr auto kConnectionRetryDelay = base::Seconds(1); + +// Length of time to wait for the browser connection ACK before timing out. +constexpr auto kConnectionTimeout = base::Seconds(4); + +} // namespace + CefFrameImpl::CefFrameImpl(CefBrowserImpl* browser, blink::WebLocalFrame* frame, int64_t frame_id) @@ -254,15 +267,16 @@ void CefFrameImpl::SendProcessMessage(CefProcessId target_process, if (!message || !message->IsValid()) return; - if (!frame_) - return; - - auto& browser_frame = GetBrowserFrame(); - if (!browser_frame) - return; - - auto impl = static_cast(message.get()); - browser_frame->SendMessage(impl->GetName(), impl->TakeArgumentList()); + SendToBrowserFrame( + __FUNCTION__, + base::BindOnce( + [](CefRefPtr message, + const BrowserFrameType& browser_frame) { + auto impl = static_cast(message.get()); + browser_frame->SendMessage(impl->GetName(), + impl->TakeArgumentList()); + }, + message)); } std::unique_ptr CefFrameImpl::CreateURLLoader() { @@ -298,14 +312,15 @@ CefFrameImpl::CreateResourceLoadInfoNotifierWrapper() { return nullptr; } -void CefFrameImpl::OnAttached(service_manager::BinderRegistry* registry) { +void CefFrameImpl::OnAttached() { // Called indirectly from RenderFrameCreated. - registry->AddInterface(base::BindRepeating( - &CefFrameImpl::BindRenderFrameReceiver, weak_ptr_factory_.GetWeakPtr())); + ConnectBrowserFrame(); +} - auto& browser_frame = GetBrowserFrame(); - if (browser_frame) { - browser_frame->FrameAttached(); +void CefFrameImpl::OnWasShown() { + if (browser_connection_state_ == ConnectionState::DISCONNECTED) { + // Reconnect a frame that has exited the bfcache. + ConnectBrowserFrame(); } } @@ -317,10 +332,15 @@ void CefFrameImpl::OnDidFinishLoad() { blink::WebDocumentLoader* dl = frame_->GetDocumentLoader(); const int http_status_code = dl->GetResponse().HttpStatusCode(); - auto& browser_frame = GetBrowserFrame(); - if (browser_frame) { - browser_frame->DidFinishFrameLoad(dl->GetUrl(), http_status_code); - } + + SendToBrowserFrame(__FUNCTION__, + base::BindOnce( + [](const GURL& url, int http_status_code, + const BrowserFrameType& browser_frame) { + browser_frame->DidFinishFrameLoad(url, + http_status_code); + }, + dl->GetUrl(), http_status_code)); CefRefPtr app = CefAppManager::Get()->GetApplication(); if (app) { @@ -356,22 +376,28 @@ void CefFrameImpl::OnDraggableRegionsChanged() { } } - auto& browser_frame = GetBrowserFrame(); - if (browser_frame) { - browser_frame->UpdateDraggableRegions( - regions.empty() ? absl::nullopt - : absl::make_optional(std::move(regions))); - } + using RegionsArg = + absl::optional>; + RegionsArg regions_arg = + regions.empty() ? absl::nullopt : absl::make_optional(std::move(regions)); + + SendToBrowserFrame( + __FUNCTION__, + base::BindOnce( + [](RegionsArg regions_arg, const BrowserFrameType& browser_frame) { + browser_frame->UpdateDraggableRegions(std::move(regions_arg)); + }, + std::move(regions_arg))); } void CefFrameImpl::OnContextCreated() { context_created_ = true; CHECK(frame_); - while (!queued_actions_.empty()) { - auto& action = queued_actions_.front(); + while (!queued_context_actions_.empty()) { + auto& action = queued_context_actions_.front(); std::move(action.second).Run(frame_); - queued_actions_.pop(); + queued_context_actions_.pop(); } } @@ -382,21 +408,31 @@ void CefFrameImpl::OnDetached() { // keep |this| alive until after this method returns. CefRefPtr self = this; + frame_ = nullptr; + browser_->FrameDetached(frame_id_); - receivers_.Clear(); - browser_frame_.reset(); + OnBrowserFrameDisconnect(); + browser_ = nullptr; - frame_ = nullptr; url_loader_factory_.reset(); - // In case we're destroyed without the context being created. - while (!queued_actions_.empty()) { - auto& action = queued_actions_.front(); + // In case we never attached. + while (!queued_browser_actions_.empty()) { + auto& action = queued_browser_actions_.front(); LOG(WARNING) << action.first << " sent to detached frame " << frame_util::GetFrameDebugString(frame_id_) << " will be ignored"; - queued_actions_.pop(); + queued_browser_actions_.pop(); + } + + // In case we're destroyed without the context being created. + while (!queued_context_actions_.empty()) { + auto& action = queued_context_actions_.front(); + LOG(WARNING) << action.first << " sent to detached frame " + << frame_util::GetFrameDebugString(frame_id_) + << " will be ignored"; + queued_context_actions_.pop(); } } @@ -405,7 +441,8 @@ void CefFrameImpl::ExecuteOnLocalFrame(const std::string& function_name, CEF_REQUIRE_RT_RETURN_VOID(); if (!context_created_) { - queued_actions_.push(std::make_pair(function_name, std::move(action))); + queued_context_actions_.push( + std::make_pair(function_name, std::move(action))); return; } @@ -418,21 +455,140 @@ void CefFrameImpl::ExecuteOnLocalFrame(const std::string& function_name, } } -const mojo::Remote& CefFrameImpl::GetBrowserFrame() { +void CefFrameImpl::ConnectBrowserFrame() { + DCHECK(browser_connection_state_ == ConnectionState::DISCONNECTED || + browser_connection_state_ == ConnectionState::RECONNECT_PENDING); + + // Don't attempt to connect an invalid or bfcache'd frame. If a bfcache'd + // frame returns to active status a reconnect will be triggered via + // OnWasShown(). + if (!frame_ || blink_glue::IsInBackForwardCache(frame_)) { + browser_connection_state_ = ConnectionState::DISCONNECTED; + browser_connect_timer_.Stop(); + LOG(INFO) << "Connection retry canceled for frame " + << frame_util::GetFrameDebugString(frame_id_); + return; + } + + if (browser_connect_retry_ct_ > 0) { + LOG(INFO) << "Connection retry " << browser_connect_retry_ct_ << "/" + << kConnectionRetryMaxCt << " for frame " + << frame_util::GetFrameDebugString(frame_id_); + } + + browser_connection_state_ = ConnectionState::CONNECTION_PENDING; + browser_connect_timer_.Start(FROM_HERE, kConnectionTimeout, this, + &CefFrameImpl::OnBrowserFrameTimeout); + + auto& browser_frame = GetBrowserFrame(/*expect_acked=*/false); + CHECK(browser_frame); + + // If the channel is working we should get a call to FrameAttachedAck(). + // Otherwise, OnBrowserFrameDisconnect() should be called to retry the + // connection. + browser_frame->FrameAttached(receiver_.BindNewPipeAndPassRemote(), + browser_connect_retry_ct_ > 0); + receiver_.set_disconnect_handler( + base::BindOnce(&CefFrameImpl::OnBrowserFrameDisconnect, this)); +} + +const mojo::Remote& CefFrameImpl::GetBrowserFrame( + bool expect_acked) { + DCHECK_EQ(expect_acked, + browser_connection_state_ == ConnectionState::CONNECTION_ACKED); + if (!browser_frame_.is_bound()) { auto render_frame = content::RenderFrameImpl::FromWebFrame(frame_); if (render_frame) { // Triggers creation of a CefBrowserFrame in the browser process. render_frame->GetBrowserInterfaceBroker()->GetInterface( browser_frame_.BindNewPipeAndPassReceiver()); + browser_frame_.set_disconnect_handler( + base::BindOnce(&CefFrameImpl::OnBrowserFrameDisconnect, this)); } } return browser_frame_; } -void CefFrameImpl::BindRenderFrameReceiver( - mojo::PendingReceiver receiver) { - receivers_.Add(this, std::move(receiver)); +void CefFrameImpl::OnBrowserFrameTimeout() { + LOG(ERROR) << "Connection timeout for frame " + << frame_util::GetFrameDebugString(frame_id_); + OnBrowserFrameDisconnect(); +} + +void CefFrameImpl::OnBrowserFrameDisconnect() { + // Ignore multiple calls in close proximity (which may occur if both + // |browser_frame_| and |receiver_| disconnect). |frame_| will be nullptr + // when called from/after OnDetached(). + if (frame_ && + browser_connection_state_ == ConnectionState::RECONNECT_PENDING) { + return; + } + + browser_frame_.reset(); + receiver_.reset(); + browser_connection_state_ = ConnectionState::DISCONNECTED; + browser_connect_timer_.Stop(); + + // Only retry if the frame is still valid. + if (frame_) { + if (browser_connect_retry_ct_++ < kConnectionRetryMaxCt) { + // Retry after a delay in case the frame is currently navigating, being + // destroyed, or entering the bfcache. In the navigation case the retry + // will likely succeed. In the destruction case the retry will be + // ignored/canceled due to OnDetached(). In the bfcache case the status + // may not be updated immediately, so we allow the reconnect timer to + // trigger and check the status in ConnectBrowserFrame() instead. + browser_connection_state_ = ConnectionState::RECONNECT_PENDING; + browser_connect_timer_.Start(FROM_HERE, kConnectionRetryDelay, this, + &CefFrameImpl::ConnectBrowserFrame); + } else { + // Trigger a crash in official builds. + LOG(FATAL) << "Connection retry failure for frame " + << frame_util::GetFrameDebugString(frame_id_); + } + } +} + +void CefFrameImpl::SendToBrowserFrame(const std::string& function_name, + BrowserFrameAction action) { + if (!frame_) { + // We've been detached. + LOG(WARNING) << function_name << " sent to detached frame " + << frame_util::GetFrameDebugString(frame_id_) + << " will be ignored"; + return; + } + + if (browser_connection_state_ != ConnectionState::CONNECTION_ACKED) { + // Queue actions until we're notified by the browser that it's ready to + // handle them. + queued_browser_actions_.push( + std::make_pair(function_name, std::move(action))); + return; + } + + auto& browser_frame = GetBrowserFrame(); + CHECK(browser_frame); + + std::move(action).Run(browser_frame); +} + +void CefFrameImpl::FrameAttachedAck() { + // Sent from the browser process in response to ConnectBrowserFrame() sending + // FrameAttached(). + CHECK_EQ(ConnectionState::CONNECTION_PENDING, browser_connection_state_); + browser_connection_state_ = ConnectionState::CONNECTION_ACKED; + browser_connect_retry_ct_ = 0; + browser_connect_timer_.Stop(); + + auto& browser_frame = GetBrowserFrame(); + CHECK(browser_frame); + + while (!queued_browser_actions_.empty()) { + std::move(queued_browser_actions_.front().second).Run(browser_frame); + queued_browser_actions_.pop(); + } } void CefFrameImpl::SendMessage(const std::string& name, base::Value arguments) { diff --git a/libcef/renderer/frame_impl.h b/libcef/renderer/frame_impl.h index a9bfdb3fa..9b8e42132 100644 --- a/libcef/renderer/frame_impl.h +++ b/libcef/renderer/frame_impl.h @@ -6,16 +6,18 @@ #define CEF_LIBCEF_RENDERER_FRAME_IMPL_H_ #pragma once +#include #include + #include "include/cef_frame.h" #include "include/cef_v8.h" #include "base/memory/weak_ptr.h" +#include "base/timer/timer.h" #include "cef/libcef/common/mojom/cef.mojom.h" #include "mojo/public/cpp/bindings/pending_receiver.h" -#include "mojo/public/cpp/bindings/receiver_set.h" +#include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/remote.h" -#include "services/service_manager/public/cpp/binder_registry.h" namespace base { class ListValue; @@ -84,7 +86,8 @@ class CefFrameImpl : public CefFrame, public cef::mojom::RenderFrame { CreateResourceLoadInfoNotifierWrapper(); // Forwarded from CefRenderFrameObserver. - void OnAttached(service_manager::BinderRegistry* registry); + void OnAttached(); + void OnWasShown(); void OnDidFinishLoad(); void OnDraggableRegionsChanged(); void OnContextCreated(); @@ -100,13 +103,29 @@ class CefFrameImpl : public CefFrame, public cef::mojom::RenderFrame { void ExecuteOnLocalFrame(const std::string& function_name, LocalFrameAction action); - // Returns the remote BrowserFrame object. - const mojo::Remote& GetBrowserFrame(); + // Initiate the connection to the BrowserFrame channel. + void ConnectBrowserFrame(); - void BindRenderFrameReceiver( - mojo::PendingReceiver receiver); + // Returns the remote BrowserFrame object. + using BrowserFrameType = mojo::Remote; + const BrowserFrameType& GetBrowserFrame(bool expect_acked = true); + + // Called if the BrowserFrame connection attempt times out. + void OnBrowserFrameTimeout(); + + // Called if/when the BrowserFrame channel is disconnected. This may occur due + // to frame navigation, destruction, or insertion into the bfcache (when the + // browser-side frame representation is destroyed and closes the connection). + void OnBrowserFrameDisconnect(); + + // Send an action to the remote BrowserFrame. This will queue the action if + // the remote frame is not yet attached. + using BrowserFrameAction = base::OnceCallback; + void SendToBrowserFrame(const std::string& function_name, + BrowserFrameAction action); // cef::mojom::RenderFrame methods: + void FrameAttachedAck() override; void SendMessage(const std::string& name, base::Value arguments) override; void SendCommand(const std::string& command) override; void SendCommandWithResponse( @@ -125,11 +144,27 @@ class CefFrameImpl : public CefFrame, public cef::mojom::RenderFrame { const int64 frame_id_; bool context_created_ = false; - std::queue> queued_actions_; + std::queue> queued_context_actions_; + + // Number of times that browser reconnect has been attempted. + size_t browser_connect_retry_ct_ = 0; + + // Current browser connection state. + enum class ConnectionState { + DISCONNECTED, + CONNECTION_PENDING, + CONNECTION_ACKED, + RECONNECT_PENDING, + } browser_connection_state_ = ConnectionState::DISCONNECTED; + + base::OneShotTimer browser_connect_timer_; + + std::queue> + queued_browser_actions_; std::unique_ptr url_loader_factory_; - mojo::ReceiverSet receivers_; + mojo::Receiver receiver_{this}; mojo::Remote browser_frame_; diff --git a/libcef/renderer/render_frame_observer.cc b/libcef/renderer/render_frame_observer.cc index 69249e93e..331b06ce4 100644 --- a/libcef/renderer/render_frame_observer.cc +++ b/libcef/renderer/render_frame_observer.cc @@ -50,6 +50,12 @@ void CefRenderFrameObserver::DidCommitProvisionalLoad( OnLoadStart(); } +void CefRenderFrameObserver::WasShown() { + if (frame_) { + frame_->OnWasShown(); + } +} + void CefRenderFrameObserver::DidFailProvisionalLoad() { if (frame_) { OnLoadError(); @@ -199,7 +205,7 @@ void CefRenderFrameObserver::AttachFrame(CefFrameImpl* frame) { DCHECK(frame); DCHECK(!frame_); frame_ = frame; - frame_->OnAttached(®istry_); + frame_->OnAttached(); } void CefRenderFrameObserver::OnLoadStart() { diff --git a/libcef/renderer/render_frame_observer.h b/libcef/renderer/render_frame_observer.h index 26b20ff19..496483245 100644 --- a/libcef/renderer/render_frame_observer.h +++ b/libcef/renderer/render_frame_observer.h @@ -27,6 +27,7 @@ class CefRenderFrameObserver : public content::RenderFrameObserver { ~CefRenderFrameObserver() override; // RenderFrameObserver methods: + void WasShown() override; void DidCommitProvisionalLoad(ui::PageTransition transition) override; void DidFailProvisionalLoad() override; void DidFinishLoad() override;