Support reconnect of the mojo frame channel (fixes issue #3260)

The mojo channel used for frame communication may disconnect for a variety of
reasons including frame navigation, frame destruction, or insertion into the
BackForwardCache (when the browser-side frame representation is destroyed and
closes the connection). When disconnect occurs we now evaluate the situation
and reconnect if appropriate.

Connections are now initiated solely from the renderer process and the
RenderFrame is passed as an argument to FrameAttached() instead of being
retrieved independently. Messages are queued while the frame is disconnected
and sent only after FrameAttachedAck() is received from the browser process.
The renderer process will be crashed intentionally with a "connection retry
failure" message if the reconnect fails 3 times in a row.
This commit is contained in:
Marshall Greenblatt
2022-02-10 16:52:36 -05:00
parent 80caf947f3
commit 3d1bbaf54f
12 changed files with 340 additions and 131 deletions

View File

@@ -35,18 +35,20 @@ void CefBrowserFrame::RegisterBrowserInterfaceBindersForFrame(
void CefBrowserFrame::SendMessage(const std::string& name, void CefBrowserFrame::SendMessage(const std::string& name,
base::Value arguments) { 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. // navigating cross-origin.
if (auto host = GetFrameHost(/*prefer_speculative=*/true)) { if (auto host = GetFrameHost(/*prefer_speculative=*/true)) {
host->SendMessage(name, std::move(arguments)); host->SendMessage(name, std::move(arguments));
} }
} }
void CefBrowserFrame::FrameAttached() { void CefBrowserFrame::FrameAttached(
mojo::PendingRemote<cef::mojom::RenderFrame> render_frame,
bool reattached) {
// Always send to the newly created RFH, which may be speculative when // Always send to the newly created RFH, which may be speculative when
// navigating cross-origin. // navigating cross-origin.
if (auto host = GetFrameHost(/*prefer_speculative=*/true)) { if (auto host = GetFrameHost(/*prefer_speculative=*/true)) {
host->FrameAttached(); host->FrameAttached(std::move(render_frame), reattached);
} }
} }

View File

@@ -36,7 +36,8 @@ class CefBrowserFrame
private: private:
// cef::mojom::BrowserFrame methods: // cef::mojom::BrowserFrame methods:
void SendMessage(const std::string& name, base::Value arguments) override; void SendMessage(const std::string& name, base::Value arguments) override;
void FrameAttached() override; void FrameAttached(mojo::PendingRemote<cef::mojom::RenderFrame> render_frame,
bool reattached) override;
void DidFinishFrameLoad(const GURL& validated_url, void DidFinishFrameLoad(const GURL& validated_url,
int32_t http_status_code) override; int32_t http_status_code) override;
void UpdateDraggableRegions( void UpdateDraggableRegions(

View File

@@ -168,10 +168,13 @@ void CefBrowserInfo::FrameHostStateChanged(
content::RenderFrameHost::LifecycleState::kInBackForwardCache) && content::RenderFrameHost::LifecycleState::kInBackForwardCache) &&
new_state == content::RenderFrameHost::LifecycleState::kActive) { new_state == content::RenderFrameHost::LifecycleState::kActive) {
if (auto frame = GetFrameForHost(host)) { if (auto frame = GetFrameForHost(host)) {
// Should only occur for the main frame.
CHECK(frame->IsMain());
// Update the associated RFH, which may have changed. // Update the associated RFH, which may have changed.
frame->MaybeReAttach(this, host); frame->MaybeReAttach(this, host);
if (frame->IsMain()) { {
// Update the main frame object. // Update the main frame object.
NotificationStateLock lock_scope(this); NotificationStateLock lock_scope(this);
SetMainFrame(browser_, frame); SetMainFrame(browser_, frame);

View File

@@ -449,13 +449,12 @@ bool CefFrameHostImpl::Detach() {
} }
// In case we never attached, clean up. // In case we never attached, clean up.
while (!queued_actions_.empty()) { while (!queued_renderer_actions_.empty()) {
queued_actions_.pop(); queued_renderer_actions_.pop();
} }
render_frame_.reset(); render_frame_.reset();
render_frame_host_ = nullptr; render_frame_host_ = nullptr;
is_attached_ = false;
return first_detach; return first_detach;
} }
@@ -464,14 +463,14 @@ void CefFrameHostImpl::MaybeReAttach(
scoped_refptr<CefBrowserInfo> browser_info, scoped_refptr<CefBrowserInfo> browser_info,
content::RenderFrameHost* render_frame_host) { content::RenderFrameHost* render_frame_host) {
CEF_REQUIRE_UIT(); 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. // Nothing to do here.
return; return;
} }
// We expect that Detach() was called previously. // We expect that Detach() was called previously.
CHECK(!is_temporary()); CHECK(!is_temporary());
CHECK(!is_attached_); CHECK(!render_frame_.is_bound());
CHECK(!render_frame_host_); CHECK(!render_frame_host_);
// The RFH may change but the GlobalId should remain the same. // The RFH may change but the GlobalId should remain the same.
@@ -486,8 +485,7 @@ void CefFrameHostImpl::MaybeReAttach(
render_frame_host_ = render_frame_host; render_frame_host_ = render_frame_host;
RefreshAttributes(); RefreshAttributes();
// Restore the RenderFrame connection. // We expect a reconnect to be triggered via FrameAttached().
FrameAttachedInternal(/*reattached=*/true);
} }
// kMainFrameId must be -1 to align with renderer expectations. // kMainFrameId must be -1 to align with renderer expectations.
@@ -517,20 +515,6 @@ CefRefPtr<CefBrowserHostBase> CefFrameHostImpl::GetBrowserHostBase() const {
return nullptr; return nullptr;
} }
const mojo::Remote<cef::mojom::RenderFrame>&
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, void CefFrameHostImpl::SendToRenderFrame(const std::string& function_name,
RenderFrameAction action) { RenderFrameAction action) {
if (!CEF_CURRENTLY_ON_UIT()) { if (!CEF_CURRENTLY_ON_UIT()) {
@@ -553,18 +537,22 @@ void CefFrameHostImpl::SendToRenderFrame(const std::string& function_name,
return; return;
} }
if (!is_attached_) { if (!render_frame_.is_bound()) {
// Queue actions until we're notified by the renderer that it's ready to // Queue actions until we're notified by the renderer that it's ready to
// handle them. // 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; return;
} }
auto& render_frame = GetRenderFrame(); std::move(action).Run(render_frame_);
if (!render_frame) }
return;
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, void CefFrameHostImpl::SendMessage(const std::string& name,
@@ -581,12 +569,11 @@ void CefFrameHostImpl::SendMessage(const std::string& name,
} }
} }
void CefFrameHostImpl::FrameAttached() { void CefFrameHostImpl::FrameAttached(
FrameAttachedInternal(/*reattached=*/false); mojo::PendingRemote<cef::mojom::RenderFrame> render_frame_remote,
} bool reattached) {
void CefFrameHostImpl::FrameAttachedInternal(bool reattached) {
CEF_REQUIRE_UIT(); CEF_REQUIRE_UIT();
CHECK(render_frame_remote);
auto browser_info = GetBrowserInfo(); auto browser_info = GetBrowserInfo();
if (!browser_info) { if (!browser_info) {
@@ -594,27 +581,32 @@ void CefFrameHostImpl::FrameAttachedInternal(bool reattached) {
return; return;
} }
DCHECK(!is_attached_); if (reattached) {
if (!is_attached_) { LOG(INFO) << (is_main_frame_ ? "main" : "sub") << "frame "
is_attached_ = true; << frame_util::GetFrameDebugString(frame_id_)
<< " has reconnected";
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<CefFrameHostImpl> self, bool reattached,
CefRefPtr<CefFrameHandler> handler) {
if (auto browser = self->GetBrowserHostBase()) {
handler->OnFrameAttached(browser, self, reattached);
}
},
CefRefPtr<CefFrameHostImpl>(this), reattached));
} }
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<CefFrameHostImpl> self, bool reattached,
CefRefPtr<CefFrameHandler> handler) {
if (auto browser = self->GetBrowserHostBase()) {
handler->OnFrameAttached(browser, self, reattached);
}
},
CefRefPtr<CefFrameHostImpl>(this), reattached));
} }
void CefFrameHostImpl::DidFinishFrameLoad(const GURL& validated_url, void CefFrameHostImpl::DidFinishFrameLoad(const GURL& validated_url,

View File

@@ -130,7 +130,8 @@ class CefFrameHostImpl : public CefFrame, public cef::mojom::BrowserFrame {
// cef::mojom::BrowserFrame methods forwarded from CefBrowserFrame. // cef::mojom::BrowserFrame methods forwarded from CefBrowserFrame.
void SendMessage(const std::string& name, base::Value arguments) override; void SendMessage(const std::string& name, base::Value arguments) override;
void FrameAttached() override; void FrameAttached(mojo::PendingRemote<cef::mojom::RenderFrame> render_frame,
bool reattached) override;
void DidFinishFrameLoad(const GURL& validated_url, void DidFinishFrameLoad(const GURL& validated_url,
int32_t http_status_code) override; int32_t http_status_code) override;
void UpdateDraggableRegions( void UpdateDraggableRegions(
@@ -152,17 +153,14 @@ class CefFrameHostImpl : public CefFrame, public cef::mojom::BrowserFrame {
scoped_refptr<CefBrowserInfo> GetBrowserInfo() const; scoped_refptr<CefBrowserInfo> GetBrowserInfo() const;
CefRefPtr<CefBrowserHostBase> GetBrowserHostBase() const; CefRefPtr<CefBrowserHostBase> GetBrowserHostBase() const;
// Returns the remote RenderFrame object.
using RenderFrameType = mojo::Remote<cef::mojom::RenderFrame>;
const RenderFrameType& GetRenderFrame();
// Send an action to the remote RenderFrame. This will queue the action if the // Send an action to the remote RenderFrame. This will queue the action if the
// remote frame is not yet attached. // remote frame is not yet attached.
using RenderFrameType = mojo::Remote<cef::mojom::RenderFrame>;
using RenderFrameAction = base::OnceCallback<void(const RenderFrameType&)>; using RenderFrameAction = base::OnceCallback<void(const RenderFrameType&)>;
void SendToRenderFrame(const std::string& function_name, void SendToRenderFrame(const std::string& function_name,
RenderFrameAction action); RenderFrameAction action);
void FrameAttachedInternal(bool reattached); void OnRenderFrameDisconnect();
const bool is_main_frame_; 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. // The following members are only accessed on the UI thread.
content::RenderFrameHost* render_frame_host_ = nullptr; content::RenderFrameHost* render_frame_host_ = nullptr;
bool is_attached_ = false; std::queue<std::pair<std::string, RenderFrameAction>>
queued_renderer_actions_;
std::queue<std::pair<std::string, RenderFrameAction>> queued_actions_;
mojo::Remote<cef::mojom::RenderFrame> render_frame_; mojo::Remote<cef::mojom::RenderFrame> render_frame_;

View File

@@ -19,21 +19,6 @@ struct DraggableRegionEntry {
bool draggable; 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<DraggableRegionEntry>? regions);
};
// Structure passed to LoadRequest(). // Structure passed to LoadRequest().
struct RequestParams { struct RequestParams {
// Request method. // Request method.
@@ -64,6 +49,9 @@ struct RequestParams {
// Interface for communicating with a frame in the renderer process. // Interface for communicating with a frame in the renderer process.
interface RenderFrame { interface RenderFrame {
// Browser process has received the FrameAttached() message.
FrameAttachedAck();
// Send a message to the render process. // Send a message to the render process.
SendMessage(string name, mojo_base.mojom.ListValue arguments); SendMessage(string name, mojo_base.mojom.ListValue arguments);
@@ -91,6 +79,22 @@ interface RenderFrame {
MoveOrResizeStarted(); 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<RenderFrame> 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<DraggableRegionEntry>? regions);
};
struct CrossOriginWhiteListEntry { struct CrossOriginWhiteListEntry {
string source_origin; string source_origin;
string target_protocol; string target_protocol;

View File

@@ -29,12 +29,14 @@
#include "third_party/blink/renderer/core/frame/settings.h" #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/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/loader/frame_load_request.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/core/script/classic_script.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.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/bindings/v8_binding.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.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/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/frame_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/public/page_scheduler.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h" #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#undef LOG #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<blink::LocalFrame>(core_frame)
->GetPage()
->GetPageScheduler()
->IsInBackForwardCache();
}
blink::WebString DumpDocumentText(blink::WebLocalFrame* frame) { blink::WebString DumpDocumentText(blink::WebLocalFrame* frame) {
// We use the document element's text instead of the body text here because // We use the document element's text instead of the body text here because
// not all documents have a body, such as XML documents. // not all documents have a body, such as XML documents.

View File

@@ -39,6 +39,8 @@ BLINK_EXPORT bool CanGoForward(blink::WebView* view);
BLINK_EXPORT void GoBack(blink::WebView* view); BLINK_EXPORT void GoBack(blink::WebView* view);
BLINK_EXPORT void GoForward(blink::WebView* view); BLINK_EXPORT void GoForward(blink::WebView* view);
BLINK_EXPORT bool IsInBackForwardCache(blink::WebLocalFrame* frame);
// Returns the text of the document element. // Returns the text of the document element.
BLINK_EXPORT blink::WebString DumpDocumentText(blink::WebLocalFrame* frame); BLINK_EXPORT blink::WebString DumpDocumentText(blink::WebLocalFrame* frame);
// Returns the markup of the document element. // Returns the markup of the document element.

View File

@@ -48,6 +48,19 @@
#include "third_party/blink/public/web/web_script_source.h" #include "third_party/blink/public/web/web_script_source.h"
#include "third_party/blink/public/web/web_view.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, CefFrameImpl::CefFrameImpl(CefBrowserImpl* browser,
blink::WebLocalFrame* frame, blink::WebLocalFrame* frame,
int64_t frame_id) int64_t frame_id)
@@ -254,15 +267,16 @@ void CefFrameImpl::SendProcessMessage(CefProcessId target_process,
if (!message || !message->IsValid()) if (!message || !message->IsValid())
return; return;
if (!frame_) SendToBrowserFrame(
return; __FUNCTION__,
base::BindOnce(
auto& browser_frame = GetBrowserFrame(); [](CefRefPtr<CefProcessMessage> message,
if (!browser_frame) const BrowserFrameType& browser_frame) {
return; auto impl = static_cast<CefProcessMessageImpl*>(message.get());
browser_frame->SendMessage(impl->GetName(),
auto impl = static_cast<CefProcessMessageImpl*>(message.get()); impl->TakeArgumentList());
browser_frame->SendMessage(impl->GetName(), impl->TakeArgumentList()); },
message));
} }
std::unique_ptr<blink::WebURLLoader> CefFrameImpl::CreateURLLoader() { std::unique_ptr<blink::WebURLLoader> CefFrameImpl::CreateURLLoader() {
@@ -298,14 +312,15 @@ CefFrameImpl::CreateResourceLoadInfoNotifierWrapper() {
return nullptr; return nullptr;
} }
void CefFrameImpl::OnAttached(service_manager::BinderRegistry* registry) { void CefFrameImpl::OnAttached() {
// Called indirectly from RenderFrameCreated. // Called indirectly from RenderFrameCreated.
registry->AddInterface(base::BindRepeating( ConnectBrowserFrame();
&CefFrameImpl::BindRenderFrameReceiver, weak_ptr_factory_.GetWeakPtr())); }
auto& browser_frame = GetBrowserFrame(); void CefFrameImpl::OnWasShown() {
if (browser_frame) { if (browser_connection_state_ == ConnectionState::DISCONNECTED) {
browser_frame->FrameAttached(); // Reconnect a frame that has exited the bfcache.
ConnectBrowserFrame();
} }
} }
@@ -317,10 +332,15 @@ void CefFrameImpl::OnDidFinishLoad() {
blink::WebDocumentLoader* dl = frame_->GetDocumentLoader(); blink::WebDocumentLoader* dl = frame_->GetDocumentLoader();
const int http_status_code = dl->GetResponse().HttpStatusCode(); const int http_status_code = dl->GetResponse().HttpStatusCode();
auto& browser_frame = GetBrowserFrame();
if (browser_frame) { SendToBrowserFrame(__FUNCTION__,
browser_frame->DidFinishFrameLoad(dl->GetUrl(), http_status_code); 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<CefApp> app = CefAppManager::Get()->GetApplication(); CefRefPtr<CefApp> app = CefAppManager::Get()->GetApplication();
if (app) { if (app) {
@@ -356,22 +376,28 @@ void CefFrameImpl::OnDraggableRegionsChanged() {
} }
} }
auto& browser_frame = GetBrowserFrame(); using RegionsArg =
if (browser_frame) { absl::optional<std::vector<cef::mojom::DraggableRegionEntryPtr>>;
browser_frame->UpdateDraggableRegions( RegionsArg regions_arg =
regions.empty() ? absl::nullopt regions.empty() ? absl::nullopt : absl::make_optional(std::move(regions));
: 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() { void CefFrameImpl::OnContextCreated() {
context_created_ = true; context_created_ = true;
CHECK(frame_); CHECK(frame_);
while (!queued_actions_.empty()) { while (!queued_context_actions_.empty()) {
auto& action = queued_actions_.front(); auto& action = queued_context_actions_.front();
std::move(action.second).Run(frame_); 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. // keep |this| alive until after this method returns.
CefRefPtr<CefFrameImpl> self = this; CefRefPtr<CefFrameImpl> self = this;
frame_ = nullptr;
browser_->FrameDetached(frame_id_); browser_->FrameDetached(frame_id_);
receivers_.Clear(); OnBrowserFrameDisconnect();
browser_frame_.reset();
browser_ = nullptr; browser_ = nullptr;
frame_ = nullptr;
url_loader_factory_.reset(); url_loader_factory_.reset();
// In case we're destroyed without the context being created. // In case we never attached.
while (!queued_actions_.empty()) { while (!queued_browser_actions_.empty()) {
auto& action = queued_actions_.front(); auto& action = queued_browser_actions_.front();
LOG(WARNING) << action.first << " sent to detached frame " LOG(WARNING) << action.first << " sent to detached frame "
<< frame_util::GetFrameDebugString(frame_id_) << frame_util::GetFrameDebugString(frame_id_)
<< " will be ignored"; << " 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(); CEF_REQUIRE_RT_RETURN_VOID();
if (!context_created_) { 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; return;
} }
@@ -418,21 +455,140 @@ void CefFrameImpl::ExecuteOnLocalFrame(const std::string& function_name,
} }
} }
const mojo::Remote<cef::mojom::BrowserFrame>& 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<cef::mojom::BrowserFrame>& CefFrameImpl::GetBrowserFrame(
bool expect_acked) {
DCHECK_EQ(expect_acked,
browser_connection_state_ == ConnectionState::CONNECTION_ACKED);
if (!browser_frame_.is_bound()) { if (!browser_frame_.is_bound()) {
auto render_frame = content::RenderFrameImpl::FromWebFrame(frame_); auto render_frame = content::RenderFrameImpl::FromWebFrame(frame_);
if (render_frame) { if (render_frame) {
// Triggers creation of a CefBrowserFrame in the browser process. // Triggers creation of a CefBrowserFrame in the browser process.
render_frame->GetBrowserInterfaceBroker()->GetInterface( render_frame->GetBrowserInterfaceBroker()->GetInterface(
browser_frame_.BindNewPipeAndPassReceiver()); browser_frame_.BindNewPipeAndPassReceiver());
browser_frame_.set_disconnect_handler(
base::BindOnce(&CefFrameImpl::OnBrowserFrameDisconnect, this));
} }
} }
return browser_frame_; return browser_frame_;
} }
void CefFrameImpl::BindRenderFrameReceiver( void CefFrameImpl::OnBrowserFrameTimeout() {
mojo::PendingReceiver<cef::mojom::RenderFrame> receiver) { LOG(ERROR) << "Connection timeout for frame "
receivers_.Add(this, std::move(receiver)); << 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) { void CefFrameImpl::SendMessage(const std::string& name, base::Value arguments) {

View File

@@ -6,16 +6,18 @@
#define CEF_LIBCEF_RENDERER_FRAME_IMPL_H_ #define CEF_LIBCEF_RENDERER_FRAME_IMPL_H_
#pragma once #pragma once
#include <queue>
#include <string> #include <string>
#include "include/cef_frame.h" #include "include/cef_frame.h"
#include "include/cef_v8.h" #include "include/cef_v8.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "cef/libcef/common/mojom/cef.mojom.h" #include "cef/libcef/common/mojom/cef.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.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 "mojo/public/cpp/bindings/remote.h"
#include "services/service_manager/public/cpp/binder_registry.h"
namespace base { namespace base {
class ListValue; class ListValue;
@@ -84,7 +86,8 @@ class CefFrameImpl : public CefFrame, public cef::mojom::RenderFrame {
CreateResourceLoadInfoNotifierWrapper(); CreateResourceLoadInfoNotifierWrapper();
// Forwarded from CefRenderFrameObserver. // Forwarded from CefRenderFrameObserver.
void OnAttached(service_manager::BinderRegistry* registry); void OnAttached();
void OnWasShown();
void OnDidFinishLoad(); void OnDidFinishLoad();
void OnDraggableRegionsChanged(); void OnDraggableRegionsChanged();
void OnContextCreated(); void OnContextCreated();
@@ -100,13 +103,29 @@ class CefFrameImpl : public CefFrame, public cef::mojom::RenderFrame {
void ExecuteOnLocalFrame(const std::string& function_name, void ExecuteOnLocalFrame(const std::string& function_name,
LocalFrameAction action); LocalFrameAction action);
// Returns the remote BrowserFrame object. // Initiate the connection to the BrowserFrame channel.
const mojo::Remote<cef::mojom::BrowserFrame>& GetBrowserFrame(); void ConnectBrowserFrame();
void BindRenderFrameReceiver( // Returns the remote BrowserFrame object.
mojo::PendingReceiver<cef::mojom::RenderFrame> receiver); using BrowserFrameType = mojo::Remote<cef::mojom::BrowserFrame>;
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(const BrowserFrameType&)>;
void SendToBrowserFrame(const std::string& function_name,
BrowserFrameAction action);
// cef::mojom::RenderFrame methods: // cef::mojom::RenderFrame methods:
void FrameAttachedAck() override;
void SendMessage(const std::string& name, base::Value arguments) override; void SendMessage(const std::string& name, base::Value arguments) override;
void SendCommand(const std::string& command) override; void SendCommand(const std::string& command) override;
void SendCommandWithResponse( void SendCommandWithResponse(
@@ -125,11 +144,27 @@ class CefFrameImpl : public CefFrame, public cef::mojom::RenderFrame {
const int64 frame_id_; const int64 frame_id_;
bool context_created_ = false; bool context_created_ = false;
std::queue<std::pair<std::string, LocalFrameAction>> queued_actions_; std::queue<std::pair<std::string, LocalFrameAction>> 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<std::pair<std::string, BrowserFrameAction>>
queued_browser_actions_;
std::unique_ptr<blink::WebURLLoaderFactory> url_loader_factory_; std::unique_ptr<blink::WebURLLoaderFactory> url_loader_factory_;
mojo::ReceiverSet<cef::mojom::RenderFrame> receivers_; mojo::Receiver<cef::mojom::RenderFrame> receiver_{this};
mojo::Remote<cef::mojom::BrowserFrame> browser_frame_; mojo::Remote<cef::mojom::BrowserFrame> browser_frame_;

View File

@@ -50,6 +50,12 @@ void CefRenderFrameObserver::DidCommitProvisionalLoad(
OnLoadStart(); OnLoadStart();
} }
void CefRenderFrameObserver::WasShown() {
if (frame_) {
frame_->OnWasShown();
}
}
void CefRenderFrameObserver::DidFailProvisionalLoad() { void CefRenderFrameObserver::DidFailProvisionalLoad() {
if (frame_) { if (frame_) {
OnLoadError(); OnLoadError();
@@ -199,7 +205,7 @@ void CefRenderFrameObserver::AttachFrame(CefFrameImpl* frame) {
DCHECK(frame); DCHECK(frame);
DCHECK(!frame_); DCHECK(!frame_);
frame_ = frame; frame_ = frame;
frame_->OnAttached(&registry_); frame_->OnAttached();
} }
void CefRenderFrameObserver::OnLoadStart() { void CefRenderFrameObserver::OnLoadStart() {

View File

@@ -27,6 +27,7 @@ class CefRenderFrameObserver : public content::RenderFrameObserver {
~CefRenderFrameObserver() override; ~CefRenderFrameObserver() override;
// RenderFrameObserver methods: // RenderFrameObserver methods:
void WasShown() override;
void DidCommitProvisionalLoad(ui::PageTransition transition) override; void DidCommitProvisionalLoad(ui::PageTransition transition) override;
void DidFailProvisionalLoad() override; void DidFailProvisionalLoad() override;
void DidFinishLoad() override; void DidFinishLoad() override;