Reduce the frequency of connection-related renderer crashes (see #3664)

- Use ResetWithReason to report intentional browser side disconnects of
  existing Mojo connections. Don't retry for those disconnects.
- Add set_disconnect_with_reason_and_result_handler in Chromium/Mojo to
  expose the MojoResult code for failed connections, allowing
  identification of connections that are intentionally unbound on the
  browser side.
- Optimize initial reconnect delay for known disconnect cases such as
  navigation-related and bfcache changes.
- Remove connection timeout and increase total connection deadline by
  100% to further reduce crash rates on slower machines.
- Only fail fatally for main frames (not sub-frames) in cases where the
  connection fails or disconnects for unknown reasons.
- Improve connection debug logging when running with
  `--enable-logging --vmodule=*frame*=1 --log-file=C:\temp\log.txt`
This commit is contained in:
Marshall Greenblatt
2024-11-22 16:12:52 -05:00
parent a02960b2fc
commit 7f253f83a2
11 changed files with 691 additions and 190 deletions

View File

@@ -17,7 +17,12 @@
CefBrowserFrame::CefBrowserFrame(
content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<cef::mojom::BrowserFrame> receiver)
: FrameServiceBase(render_frame_host, std::move(receiver)) {}
: CefFrameServiceBase(render_frame_host, std::move(receiver)) {
DVLOG(1) << __func__ << ": frame "
<< frame_util::GetFrameDebugString(
render_frame_host->GetGlobalFrameToken())
<< " bound";
}
CefBrowserFrame::~CefBrowserFrame() = default;
@@ -62,13 +67,15 @@ void CefBrowserFrame::FrameAttached(
if (auto host = GetFrameHost(/*prefer_speculative=*/true, &is_excluded)) {
host->FrameAttached(std::move(render_frame), reattached);
} else if (is_excluded) {
VLOG(1) << "frame "
<< frame_util::GetFrameDebugString(
render_frame_host()->GetGlobalFrameToken())
<< " attach denied";
DVLOG(1) << __func__ << ": frame "
<< frame_util::GetFrameDebugString(
render_frame_host()->GetGlobalFrameToken())
<< " attach denied";
mojo::Remote<cef::mojom::RenderFrame> render_frame_remote;
render_frame_remote.Bind(std::move(render_frame));
render_frame_remote->FrameAttachedAck(/*allow=*/false);
render_frame_remote.ResetWithReason(
static_cast<uint32_t>(frame_util::ResetReason::kExcluded), "Excluded");
}
}

View File

@@ -16,8 +16,7 @@
// association with the RenderFrameHost (which may be speculative, etc.), and so
// that messages are always routed to the most appropriate CefFrameHostImpl
// instance. Lifespan is tied to the RFH via FrameServiceBase.
class CefBrowserFrame
: public content::FrameServiceBase<cef::mojom::BrowserFrame> {
class CefBrowserFrame : public CefFrameServiceBase<cef::mojom::BrowserFrame> {
public:
CefBrowserFrame(content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<cef::mojom::BrowserFrame> receiver);
@@ -44,9 +43,6 @@ class CefBrowserFrame
std::optional<std::vector<cef::mojom::DraggableRegionEntryPtr>> regions)
override;
// FrameServiceBase methods:
bool ShouldCloseOnFinishNavigation() const override { return false; }
CefRefPtr<CefFrameHostImpl> GetFrameHost(bool prefer_speculative,
bool* is_excluded = nullptr) const;
};

View File

@@ -101,6 +101,7 @@ CefFrameHostImpl::CefFrameHostImpl(scoped_refptr<CefBrowserInfo> browser_info,
: render_frame_host->GetParent()->GetGlobalFrameToken()),
render_frame_host_(render_frame_host) {
DCHECK(browser_info_);
DVLOG(1) << __func__ << ": " << GetDebugString() << " created ";
}
CefFrameHostImpl::~CefFrameHostImpl() {
@@ -502,24 +503,6 @@ bool CefFrameHostImpl::IsDetached() const {
bool CefFrameHostImpl::Detach(DetachReason reason, bool is_current_main_frame) {
CEF_REQUIRE_UIT();
if (VLOG_IS_ON(1)) {
std::string reason_str;
switch (reason) {
case DetachReason::RENDER_FRAME_DELETED:
reason_str = "RENDER_FRAME_DELETED";
break;
case DetachReason::NEW_MAIN_FRAME:
reason_str = "NEW_MAIN_FRAME";
break;
case DetachReason::BROWSER_DESTROYED:
reason_str = "BROWSER_DESTROYED";
break;
};
VLOG(1) << GetDebugString() << " detached (reason=" << reason_str
<< ", is_connected=" << render_frame_.is_bound() << ")";
}
// This method may be called multiple times (e.g. from CefBrowserInfo
// SetMainFrame and RemoveFrame).
bool is_first_complete_detach = false;
@@ -546,15 +529,41 @@ bool CefFrameHostImpl::Detach(DetachReason reason, bool is_current_main_frame) {
}
if (render_frame_.is_bound()) {
render_frame_->FrameDetached();
if (VLOG_IS_ON(1)) {
std::string reason_str;
switch (reason) {
case DetachReason::RENDER_FRAME_DELETED:
reason_str = "RENDER_FRAME_DELETED";
break;
case DetachReason::NEW_MAIN_FRAME:
reason_str = "NEW_MAIN_FRAME";
break;
case DetachReason::BROWSER_DESTROYED:
reason_str = "BROWSER_DESTROYED";
break;
};
DVLOG(1) << __func__ << ": " << GetDebugString()
<< " detached (reason=" << reason_str << ")";
}
DetachRenderFrame();
}
render_frame_.reset();
render_frame_host_ = nullptr;
if (render_frame_host_) {
DVLOG(1) << __func__ << ": " << GetDebugString() << " host cleared";
render_frame_host_ = nullptr;
}
return is_first_complete_detach;
}
void CefFrameHostImpl::DetachRenderFrame() {
CEF_REQUIRE_UIT();
DCHECK(render_frame_.is_bound());
render_frame_.ResetWithReason(
static_cast<uint32_t>(frame_util::ResetReason::kDetached), "Detached");
}
void CefFrameHostImpl::MaybeReAttach(
scoped_refptr<CefBrowserInfo> browser_info,
content::RenderFrameHost* render_frame_host,
@@ -571,16 +580,12 @@ void CefFrameHostImpl::MaybeReAttach(
// If |require_detached| then we expect that Detach() was called previously.
CHECK(!require_detached || !render_frame_.is_bound());
if (render_frame_host_) {
if (render_frame_.is_bound()) {
// Intentionally not clearing |queued_renderer_actions_|, as we may be
// changing RFH during initial browser navigation.
VLOG(1) << GetDebugString()
<< " detached (reason=RENDER_FRAME_CHANGED, is_connected="
<< render_frame_.is_bound() << ")";
if (render_frame_.is_bound()) {
render_frame_->FrameDetached();
}
render_frame_.reset();
DVLOG(1) << __func__ << ": " << GetDebugString()
<< " detached (reason=RENDER_FRAME_CHANGED)";
DetachRenderFrame();
}
// The RFH may change but the frame token should remain the same.
@@ -591,6 +596,7 @@ void CefFrameHostImpl::MaybeReAttach(
browser_info_ = browser_info;
}
DVLOG(1) << __func__ << ": " << GetDebugString() << " host changed";
render_frame_host_ = render_frame_host;
RefreshAttributes();
@@ -689,7 +695,8 @@ void CefFrameHostImpl::FrameAttached(
return;
}
VLOG(1) << GetDebugString() << " " << (reattached ? "re" : "") << "connected";
DVLOG(1) << __func__ << ": " << GetDebugString() << " "
<< (reattached ? "re" : "") << "connected";
render_frame_.Bind(std::move(render_frame_remote));
render_frame_.set_disconnect_handler(

View File

@@ -184,6 +184,8 @@ class CefFrameHostImpl : public CefFrame, public cef::mojom::BrowserFrame {
void OnRenderFrameDisconnect();
void DetachRenderFrame();
std::string GetDebugString() const;
const bool is_main_frame_;

View File

@@ -11,6 +11,7 @@
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/threading/thread_checker.h"
#include "cef/libcef/common/frame_util.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
@@ -19,47 +20,36 @@
#include "mojo/public/cpp/bindings/receiver.h"
#include "url/origin.h"
namespace content {
// Base class for mojo interface implementations tied to a document's lifetime.
// The service will be destroyed when any of the following happens:
// 1. mojo interface connection error happened,
// 2. the RenderFrameHost was deleted, or
// 3. navigation was committed on the RenderFrameHost (not same document) and
// ShouldCloseOnFinishNavigation() returns true.
//
// WARNING: To avoid race conditions, subclasses MUST only get the origin via
// origin() instead of from |render_frame_host| passed in the constructor.
// See https://crbug.com/769189 for an example of such a race.
// Base class for mojo interface implementations tied to a RenderFrameHost
// lifetime. The service will be destroyed on mojo interface connection error
// or RFH deletion.
//
// Based on the old implementation of DocumentServiceBase that existed prior to
// https://crrev.com/2809effa24. CEF requires the old implementation to support
// bindings that outlive navigation.
template <typename Interface>
class FrameServiceBase : public Interface, public WebContentsObserver {
class CefFrameServiceBase : public Interface,
public content::WebContentsObserver {
public:
FrameServiceBase(RenderFrameHost* render_frame_host,
mojo::PendingReceiver<Interface> pending_receiver)
: WebContentsObserver(
WebContents::FromRenderFrameHost(render_frame_host)),
CefFrameServiceBase(content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<Interface> pending_receiver)
: content::WebContentsObserver(
content::WebContents::FromRenderFrameHost(render_frame_host)),
render_frame_host_(render_frame_host),
origin_(render_frame_host_->GetLastCommittedOrigin()),
receiver_(this, std::move(pending_receiver)) {
// |this| owns |receiver_|, so unretained is safe.
receiver_.set_disconnect_handler(
base::BindOnce(&FrameServiceBase::Close, base::Unretained(this)));
base::BindOnce(&CefFrameServiceBase::Close, base::Unretained(this)));
}
protected:
// Make the destructor private since |this| can only be deleted by Close().
~FrameServiceBase() override = default;
// All subclasses should use this function to obtain the origin instead of
// trying to get it from the RenderFrameHost pointer directly.
const url::Origin& origin() const { return origin_; }
~CefFrameServiceBase() override = default;
// Returns the RenderFrameHost held by this object.
RenderFrameHost* render_frame_host() const { return render_frame_host_; }
content::RenderFrameHost* render_frame_host() const {
return render_frame_host_;
}
// Subclasses can use this to check thread safety.
// For example: DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -72,44 +62,23 @@ class FrameServiceBase : public Interface, public WebContentsObserver {
// Use WebContents::From(render_frame_host()) instead, but please keep in mind
// that the render_frame_host() might not be active. See
// RenderFrameHost::IsActive() for details.
using WebContentsObserver::web_contents;
using content::WebContentsObserver::web_contents;
// WebContentsObserver implementation.
void RenderFrameDeleted(RenderFrameHost* render_frame_host) final {
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) final {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (render_frame_host == render_frame_host_) {
DVLOG(1) << __func__ << ": RenderFrame destroyed.";
DVLOG(1) << __func__ << ": RenderFrameHost destroyed.";
if (receiver_.is_bound()) {
receiver_.ResetWithReason(
static_cast<uint32_t>(frame_util::ResetReason::kDeleted),
"Deleted");
}
Close();
}
}
void DidFinishNavigation(NavigationHandle* navigation_handle) final {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!ShouldCloseOnFinishNavigation()) {
return;
}
if (!navigation_handle->HasCommitted() ||
navigation_handle->IsSameDocument() ||
navigation_handle->IsPageActivation()) {
return;
}
if (navigation_handle->GetRenderFrameHost() == render_frame_host_) {
// FrameServiceBase is destroyed either when RenderFrameHost is
// destroyed (covered by RenderFrameDeleted) or when a new document
// commits in the same RenderFrameHost (covered by DidFinishNavigation).
// Only committed non-same-document non-bfcache non-prerendering
// activation navigations replace a document in existing RenderFrameHost.
DVLOG(1) << __func__ << ": Close connection on navigation.";
Close();
}
}
// Used for CEF bindings that outlive navigation.
virtual bool ShouldCloseOnFinishNavigation() const { return true; }
// Stops observing WebContents and delete |this|.
void Close() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
@@ -117,11 +86,8 @@ class FrameServiceBase : public Interface, public WebContentsObserver {
delete this;
}
const raw_ptr<RenderFrameHost> render_frame_host_ = nullptr;
const url::Origin origin_;
const raw_ptr<content::RenderFrameHost> render_frame_host_ = nullptr;
mojo::Receiver<Interface> receiver_;
};
} // namespace content
#endif // CEF_LIBCEF_BROWSER_FRAME_SERVICE_BASE_H_

View File

@@ -86,6 +86,16 @@ std::string GetFrameDebugString(
std::string GetFrameDebugString(
const content::GlobalRenderFrameHostToken& global_token);
// Used in combination with ResetWithReason() to report mojo connection
// disconnect reasons. |kNoReason| (0) is the default when a mojo connection
// disconnects without a specified reason.
enum class ResetReason : uint32_t {
kNoReason,
kDeleted,
kDetached,
kExcluded,
};
} // namespace frame_util
#endif // CEF_LIBCEF_COMMON_FRAME_UTIL_H_

View File

@@ -54,9 +54,6 @@ interface RenderFrame {
// |allow| will be false if we don't want to attach the frame.
FrameAttachedAck(bool allow);
// Browser process has intentionally detached.
FrameDetached();
// Send a message to the render process.
SendMessage(string name, mojo_base.mojom.ListValue arguments);

View File

@@ -54,11 +54,15 @@ 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(10);
// Length of time to wait before initiating a browser connection retry. The
// short value is optimized for navigation-related disconnects (time delta
// between CefFrameImpl::OnDisconnect and CefFrameHostImpl::MaybeReAttach) which
// should take << 10ms in normal circumstances (reasonably fast machine, limited
// redirects). The long value is optimized for slower machines or navigations
// with many redirects to reduce overall failure rates. See related comments in
// CefFrameImpl::OnDisconnect.
constexpr auto kConnectionRetryDelayShort = base::Milliseconds(25);
constexpr auto kConnectionRetryDelayLong = base::Seconds(3);
std::string GetDebugString(blink::WebLocalFrame* frame) {
return "frame " + render_frame_util::GetIdentifier(frame);
@@ -423,7 +427,7 @@ void CefFrameImpl::OnDetached() {
browser_->FrameDetached(frame_);
frame_ = nullptr;
OnDisconnect(DisconnectReason::DETACHED, 0, std::string());
OnDisconnect(DisconnectReason::DETACHED, 0, std::string(), MOJO_RESULT_OK);
browser_ = nullptr;
@@ -480,26 +484,26 @@ void CefFrameImpl::ConnectBrowserFrame(ConnectReason reason) {
"RETRY %zu/%zu", browser_connect_retry_ct_, kConnectionRetryMaxCt);
break;
}
VLOG(1) << frame_debug_str_ << " connection request (reason=" << reason_str
<< ")";
DVLOG(1) << __func__ << ": " << frame_debug_str_
<< " connection request (reason=" << reason_str << ")";
}
browser_connect_timer_.Stop();
// 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_ || attach_denied_ || blink_glue::IsInBackForwardCache(frame_)) {
browser_connection_state_ = ConnectionState::DISCONNECTED;
browser_connect_timer_.Stop();
VLOG(1) << frame_debug_str_ << " connection retry canceled (reason="
<< (frame_ ? (attach_denied_ ? "ATTACH_DENIED" : "BFCACHED")
: "INVALID")
<< ")";
DVLOG(1) << __func__ << ": " << frame_debug_str_
<< " connection retry canceled (reason="
<< (frame_ ? (attach_denied_ ? "ATTACH_DENIED" : "BFCACHED")
: "INVALID")
<< ")";
return;
}
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);
@@ -514,7 +518,7 @@ void CefFrameImpl::ConnectBrowserFrame(ConnectReason reason) {
// connection.
browser_frame->FrameAttached(receiver_.BindNewPipeAndPassRemote(),
reattached);
receiver_.set_disconnect_with_reason_handler(
receiver_.set_disconnect_with_reason_and_result_handler(
base::BindOnce(&CefFrameImpl::OnRenderFrameDisconnect, this));
}
@@ -529,28 +533,25 @@ const mojo::Remote<cef::mojom::BrowserFrame>& CefFrameImpl::GetBrowserFrame(
// Triggers creation of a CefBrowserFrame in the browser process.
render_frame->GetBrowserInterfaceBroker().GetInterface(
browser_frame_.BindNewPipeAndPassReceiver());
browser_frame_.set_disconnect_with_reason_handler(
browser_frame_.set_disconnect_with_reason_and_result_handler(
base::BindOnce(&CefFrameImpl::OnBrowserFrameDisconnect, this));
}
}
return browser_frame_;
}
void CefFrameImpl::OnBrowserFrameTimeout() {
LOG(ERROR) << frame_debug_str_ << " connection timeout";
OnDisconnect(DisconnectReason::CONNECT_TIMEOUT, 0, std::string());
}
void CefFrameImpl::OnBrowserFrameDisconnect(uint32_t custom_reason,
const std::string& description) {
const std::string& description,
MojoResult error_result) {
OnDisconnect(DisconnectReason::BROWSER_FRAME_DISCONNECT, custom_reason,
description);
description, error_result);
}
void CefFrameImpl::OnRenderFrameDisconnect(uint32_t custom_reason,
const std::string& description) {
const std::string& description,
MojoResult error_result) {
OnDisconnect(DisconnectReason::RENDER_FRAME_DISCONNECT, custom_reason,
description);
description, error_result);
}
// static
@@ -560,18 +561,13 @@ std::string CefFrameImpl::GetDisconnectDebugString(
bool frame_is_main,
DisconnectReason reason,
uint32_t custom_reason,
const std::string& description) {
const std::string& description,
MojoResult error_result) {
std::string reason_str;
switch (reason) {
case DisconnectReason::DETACHED:
reason_str = "DETACHED";
break;
case DisconnectReason::BROWSER_FRAME_DETACHED:
reason_str = "BROWSER_FRAME_DETACHED";
break;
case DisconnectReason::CONNECT_TIMEOUT:
reason_str = "CONNECT_TIMEOUT";
break;
case DisconnectReason::RENDER_FRAME_DISCONNECT:
reason_str = "RENDER_FRAME_DISCONNECT";
break;
@@ -604,7 +600,8 @@ std::string CefFrameImpl::GetDisconnectDebugString(
state_str += ", SUB_FRAME";
}
if (custom_reason > 0) {
if (custom_reason !=
static_cast<uint32_t>(frame_util::ResetReason::kNoReason)) {
state_str += ", custom_reason=" + base::NumberToString(custom_reason);
}
@@ -612,12 +609,17 @@ std::string CefFrameImpl::GetDisconnectDebugString(
state_str += ", description=" + description;
}
if (error_result != MOJO_RESULT_OK) {
state_str += ", error_result=" + base::NumberToString(error_result);
}
return "(reason=" + reason_str + ", current_state=" + state_str + ")";
}
void CefFrameImpl::OnDisconnect(DisconnectReason reason,
uint32_t custom_reason,
const std::string& description) {
const std::string& description,
MojoResult error_result) {
// 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().
@@ -626,56 +628,108 @@ void CefFrameImpl::OnDisconnect(DisconnectReason reason,
return;
}
if (attach_denied_) {
VLOG(1) << frame_debug_str_ << " connection attach denied";
// Ignore additional calls if we're already disconnected. DETACHED,
// RENDER_FRAME_DISCONNECT and/or BROWSER_FRAME_DISCONNECT may arrive in any
// order.
if (browser_connection_state_ == ConnectionState::DISCONNECTED) {
return;
}
const auto connection_state = browser_connection_state_;
const bool frame_is_valid = !!frame_;
const bool frame_is_main = frame_ && frame_->IsOutermostMainFrame();
VLOG(1) << frame_debug_str_ << " disconnected "
<< GetDisconnectDebugString(connection_state, frame_is_valid,
frame_is_main, reason, custom_reason,
description);
DVLOG(1) << __func__ << ": " << frame_debug_str_ << " disconnected "
<< GetDisconnectDebugString(connection_state, frame_is_valid,
frame_is_main, reason, custom_reason,
description, error_result);
browser_frame_.reset();
receiver_.reset();
browser_connection_state_ = ConnectionState::DISCONNECTED;
browser_connect_timer_.Stop();
// Only retry if the frame is still valid and the browser process has not
// True if the frame was previously bound/connected and then intentionally
// detached (Receiver::ResetWithReason called) from the browser process side.
const bool connected_and_intentionally_detached =
(reason == DisconnectReason::BROWSER_FRAME_DISCONNECT ||
reason == DisconnectReason::RENDER_FRAME_DISCONNECT) &&
custom_reason !=
static_cast<uint32_t>(frame_util::ResetReason::kNoReason);
// Don't retry if the frame is invalid or if the browser process has
// intentionally detached.
if (frame_ && reason != DisconnectReason::BROWSER_FRAME_DETACHED) {
if (browser_connect_retry_ct_++ < kConnectionRetryMaxCt) {
VLOG(1) << frame_debug_str_ << " connection retry scheduled";
if (!browser_connect_retry_log_.empty()) {
browser_connect_retry_log_ += "; ";
}
browser_connect_retry_log_ += GetDisconnectDebugString(
connection_state, frame_is_valid, frame_is_main, reason,
custom_reason, description);
// 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,
base::BindOnce(&CefFrameImpl::ConnectBrowserFrame, this,
ConnectReason::RETRY));
} else {
// Trigger a crash in official builds.
LOG(FATAL) << frame_debug_str_ << " connection retry failed "
<< GetDisconnectDebugString(connection_state, frame_is_valid,
frame_is_main, reason,
custom_reason, description)
<< ", prior disconnects: " << browser_connect_retry_log_;
}
if (!frame_ || attach_denied_ || connected_and_intentionally_detached) {
return;
}
// True if the connection was closed (binding declined) from the browser
// process side. This can occur during navigation or if a matching
// RenderFrameHost is not currently available (like for bfcache'd frames).
// When navigating there is a race in the browser process between
// BrowserInterfaceBrokerImpl::GetInterface and RenderFrameHostImpl::
// DidCommitNavigation. The connection will be closed if the GetInterface call
// from the renderer is still in-flight when DidCommitNavigation calls
// |broker_receiver_.reset()|. If, however, the GetInterface call arrives
// first (BrowserInterfaceBrokerImpl::GetInterface called and the
// PendingReceiver bound) then the binding will be successful and remain
// connected until the connection is closed for some other reason (like the
// Receiver being reset or the renderer process terminating).
const bool connection_binding_declined =
(reason == DisconnectReason::BROWSER_FRAME_DISCONNECT ||
reason == DisconnectReason::RENDER_FRAME_DISCONNECT) &&
error_result == MOJO_RESULT_FAILED_PRECONDITION;
if (browser_connect_retry_ct_++ < kConnectionRetryMaxCt) {
DVLOG(1) << __func__ << ": " << frame_debug_str_
<< " connection retry scheduled (" << browser_connect_retry_ct_
<< "/" << kConnectionRetryMaxCt << ")";
if (!browser_connect_retry_log_.empty()) {
browser_connect_retry_log_ += "; ";
}
browser_connect_retry_log_ += GetDisconnectDebugString(
connection_state, frame_is_valid, frame_is_main, reason, custom_reason,
description, error_result);
// Use a shorter delay for the first retry attempt after the browser process
// intentionally declines the connection. This will improve load performance
// in normal circumstances (reasonably fast machine and navigations with
// limited redirects).
const auto retry_delay =
connection_binding_declined && browser_connect_retry_ct_ == 1
? kConnectionRetryDelayShort
: kConnectionRetryDelayLong;
// Retry after a delay in case the frame is currently navigating or entering
// the bfcache. In the navigation case the retry will likely succeed. In the
// bfcache case the status may not be updated immediately, so we allow the
// reconnect timer to trigger and then check the status in
// ConnectBrowserFrame() instead.
browser_connection_state_ = ConnectionState::RECONNECT_PENDING;
browser_connect_timer_.Start(
FROM_HERE, retry_delay,
base::BindOnce(&CefFrameImpl::ConnectBrowserFrame, this,
ConnectReason::RETRY));
return;
}
DVLOG(1) << __func__ << ": " << frame_debug_str_
<< " connection retry limit exceeded";
// Don't crash on retry failures in cases where the browser process has
// intentionally declined the connection and we have never been previously
// connected. Also don't crash for sub-frame connection failures as those are
// less likely to be important functionally. We still crash for other main
// frame connection errors or in cases where a previously connected main frame
// was disconnected without first being intentionally deleted or detached.
const bool ignore_retry_failure =
(connection_binding_declined && !ever_connected_) || !frame_is_main;
// Trigger a crash in official builds.
LOG_IF(FATAL, !ignore_retry_failure)
<< frame_debug_str_ << " connection retry failed "
<< GetDisconnectDebugString(connection_state, frame_is_valid,
frame_is_main, reason, custom_reason,
description, error_result)
<< ", prior disconnects: " << browser_connect_retry_log_;
}
void CefFrameImpl::SendToBrowserFrame(const std::string& function_name,
@@ -733,7 +787,9 @@ void CefFrameImpl::FrameAttachedAck(bool allow) {
browser_connection_state_ = ConnectionState::CONNECTION_ACKED;
browser_connect_retry_ct_ = 0;
browser_connect_retry_log_.clear();
browser_connect_timer_.Stop();
DVLOG(1) << __func__ << ": " << frame_debug_str_
<< " connection acked allow=" << allow;
if (!allow) {
// This will be followed by a connection disconnect from the browser side.
@@ -744,6 +800,8 @@ void CefFrameImpl::FrameAttachedAck(bool allow) {
return;
}
ever_connected_ = true;
auto& browser_frame = GetBrowserFrame();
CHECK(browser_frame);
@@ -753,12 +811,6 @@ void CefFrameImpl::FrameAttachedAck(bool allow) {
}
}
void CefFrameImpl::FrameDetached() {
// Sent from the browser process in response to CefFrameHostImpl::Detach().
CHECK_EQ(ConnectionState::CONNECTION_ACKED, browser_connection_state_);
OnDisconnect(DisconnectReason::BROWSER_FRAME_DETACHED, 0, std::string());
}
void CefFrameImpl::SendMessage(const std::string& name,
base::Value::List arguments) {
if (auto app = CefAppManager::Get()->GetApplication()) {

View File

@@ -117,20 +117,17 @@ class CefFrameImpl
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 the BrowserFrame connection is disconnected.
void OnBrowserFrameDisconnect(uint32_t custom_reason,
const std::string& description);
const std::string& description,
MojoResult error_result);
// Called if the RenderFrame connection is disconnected.
void OnRenderFrameDisconnect(uint32_t custom_reason,
const std::string& description);
const std::string& description,
MojoResult error_result);
enum class DisconnectReason {
DETACHED,
BROWSER_FRAME_DETACHED,
CONNECT_TIMEOUT,
RENDER_FRAME_DISCONNECT,
BROWSER_FRAME_DISCONNECT,
};
@@ -140,7 +137,8 @@ class CefFrameImpl
// representation is destroyed and closes the connection).
void OnDisconnect(DisconnectReason reason,
uint32_t custom_reason,
const std::string& description);
const std::string& description,
MojoResult error_result);
// Send an action to the remote BrowserFrame. This will queue the action if
// the remote frame is not yet attached.
@@ -152,7 +150,6 @@ class CefFrameImpl
// cef::mojom::RenderFrame methods:
void FrameAttachedAck(bool allow) override;
void FrameDetached() override;
void SendMessage(const std::string& name,
base::Value::List arguments) override;
void SendSharedMemoryRegion(const std::string& name,
@@ -190,6 +187,8 @@ class CefFrameImpl
// Log of reasons why the reconnect failed.
std::string browser_connect_retry_log_;
bool ever_connected_ = false;
// Current browser connection state.
enum class ConnectionState {
DISCONNECTED,
@@ -203,7 +202,8 @@ class CefFrameImpl
bool frame_is_main,
DisconnectReason reason,
uint32_t custom_reason,
const std::string& description);
const std::string& description,
MojoResult error_result);
base::OneShotTimer browser_connect_timer_;