mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-06-05 21:39:12 +02:00
Improve timing of frame attach/detach (see #3664)
- Move frame attachment from RenderFrameCreated to DidCommitProvisionalLoad. This has a number of advantages: - Significantly reduces the frequency of disconnects by avoiding the GetInterface/DidCommitNavigation race condition. - Stops connecting temporary frames (created during cross-origin navigation), making callback behavior more consistent. - Split frame detach and destruction notifications into separate callbacks. OnFrameDetached now reflects a potentially recoverable state. Add a new OnFrameDestroyed callback for the unrecoverable destruction state.
This commit is contained in:
@ -131,6 +131,9 @@ void CefBrowserInfo::MaybeCreateFrame(content::RenderFrameHost* host) {
|
||||
return;
|
||||
}
|
||||
|
||||
DVLOG(1) << __func__ << ": "
|
||||
<< frame_util::GetFrameDebugString(host->GetGlobalFrameToken());
|
||||
|
||||
const auto global_id = host->GetGlobalId();
|
||||
const bool is_main_frame = (host->GetParent() == nullptr);
|
||||
|
||||
@ -160,7 +163,7 @@ void CefBrowserInfo::MaybeCreateFrame(content::RenderFrameHost* host) {
|
||||
#endif
|
||||
|
||||
// Update the associated RFH, which may have changed.
|
||||
info->frame_->MaybeReAttach(this, host, /*require_detached=*/false);
|
||||
info->frame_->MaybeAttach(this, host);
|
||||
|
||||
if (info->is_speculative_ && !is_speculative) {
|
||||
// Upgrade the frame info from speculative to non-speculative.
|
||||
@ -208,17 +211,20 @@ void CefBrowserInfo::FrameHostStateChanged(
|
||||
content::RenderFrameHost::LifecycleState new_state) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
DVLOG(1) << __func__ << ": "
|
||||
<< frame_util::GetFrameDebugString(host->GetGlobalFrameToken());
|
||||
|
||||
if ((old_state == content::RenderFrameHost::LifecycleState::kPrerendering ||
|
||||
old_state ==
|
||||
content::RenderFrameHost::LifecycleState::kInBackForwardCache) &&
|
||||
new_state == content::RenderFrameHost::LifecycleState::kActive) {
|
||||
if (auto frame = GetFrameForHost(host)) {
|
||||
// Update the associated RFH, which may have changed.
|
||||
frame->MaybeReAttach(this, host, /*require_detached=*/true);
|
||||
frame->MaybeAttach(this, host);
|
||||
|
||||
if (frame->IsMain()) {
|
||||
// Update the main frame object.
|
||||
NotificationStateLock lock_scope(this);
|
||||
// Update the main frame object.
|
||||
SetMainFrame(browser_, frame);
|
||||
}
|
||||
|
||||
@ -251,6 +257,9 @@ void CefBrowserInfo::FrameHostStateChanged(
|
||||
void CefBrowserInfo::RemoveFrame(content::RenderFrameHost* host) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
DVLOG(1) << __func__ << ": "
|
||||
<< frame_util::GetFrameDebugString(host->GetGlobalFrameToken());
|
||||
|
||||
NotificationStateLock lock_scope(this);
|
||||
|
||||
const auto global_id = host->GetGlobalId();
|
||||
@ -281,12 +290,17 @@ void CefBrowserInfo::RemoveFrame(content::RenderFrameHost* host) {
|
||||
const auto& other_frame_info = *it2;
|
||||
if (other_frame_info->frame_) {
|
||||
const bool is_current_main_frame = other_frame_info->IsCurrentMainFrame();
|
||||
if (other_frame_info->frame_->Detach(
|
||||
const auto [frame_detached, frame_destroyed] =
|
||||
other_frame_info->frame_->Detach(
|
||||
CefFrameHostImpl::DetachReason::RENDER_FRAME_DELETED,
|
||||
is_current_main_frame)) {
|
||||
DCHECK(!is_current_main_frame);
|
||||
is_current_main_frame);
|
||||
if (frame_detached) {
|
||||
MaybeNotifyFrameDetached(browser_, other_frame_info->frame_);
|
||||
}
|
||||
if (frame_destroyed) {
|
||||
DCHECK(!is_current_main_frame);
|
||||
MaybeNotifyFrameDestroyed(browser_, other_frame_info->frame_);
|
||||
}
|
||||
}
|
||||
|
||||
frame_info_set_.erase(it2);
|
||||
@ -477,13 +491,21 @@ void CefBrowserInfo::SetMainFrame(CefRefPtr<CefBrowserHostBase> browser,
|
||||
return;
|
||||
}
|
||||
|
||||
DVLOG(1) << __func__ << ": "
|
||||
<< (frame ? frame->GetIdentifier().ToString() : "null");
|
||||
|
||||
CefRefPtr<CefFrameHostImpl> old_frame;
|
||||
if (main_frame_) {
|
||||
old_frame = main_frame_;
|
||||
if (old_frame->Detach(CefFrameHostImpl::DetachReason::NEW_MAIN_FRAME,
|
||||
/*is_current_main_frame=*/false)) {
|
||||
const auto [frame_detached, frame_destroyed] =
|
||||
old_frame->Detach(CefFrameHostImpl::DetachReason::NEW_MAIN_FRAME,
|
||||
/*is_current_main_frame=*/false);
|
||||
if (frame_detached) {
|
||||
MaybeNotifyFrameDetached(browser, old_frame);
|
||||
}
|
||||
if (frame_destroyed) {
|
||||
MaybeNotifyFrameDestroyed(browser, old_frame);
|
||||
}
|
||||
}
|
||||
|
||||
main_frame_ = frame;
|
||||
@ -526,6 +548,24 @@ void CefBrowserInfo::MaybeNotifyFrameDetached(
|
||||
browser, frame));
|
||||
}
|
||||
|
||||
// Passing in |browser| here because |browser_| may already be cleared.
|
||||
void CefBrowserInfo::MaybeNotifyFrameDestroyed(
|
||||
CefRefPtr<CefBrowserHostBase> browser,
|
||||
CefRefPtr<CefFrameHostImpl> frame) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
// Never notify for temporary objects.
|
||||
DCHECK(!frame->is_temporary());
|
||||
|
||||
MaybeExecuteFrameNotification(base::BindOnce(
|
||||
[](CefRefPtr<CefBrowserHostBase> browser,
|
||||
CefRefPtr<CefFrameHostImpl> frame,
|
||||
CefRefPtr<CefFrameHandler> handler) {
|
||||
handler->OnFrameDestroyed(browser, frame);
|
||||
},
|
||||
browser, frame));
|
||||
}
|
||||
|
||||
// Passing in |browser| here because |browser_| may already be cleared.
|
||||
void CefBrowserInfo::MaybeNotifyMainFrameChanged(
|
||||
CefRefPtr<CefBrowserHostBase> browser,
|
||||
@ -563,13 +603,13 @@ void CefBrowserInfo::RemoveAllFrames(
|
||||
// Explicitly Detach everything.
|
||||
for (auto& info : frame_info_set_) {
|
||||
if (info->frame_) {
|
||||
const bool is_current_main_frame = info->IsCurrentMainFrame();
|
||||
if (info->frame_->Detach(
|
||||
[[maybe_unused]] const auto [frame_detached, frame_destroyed] =
|
||||
info->frame_->Detach(
|
||||
CefFrameHostImpl::DetachReason::BROWSER_DESTROYED,
|
||||
is_current_main_frame)) {
|
||||
DCHECK(!is_current_main_frame);
|
||||
MaybeNotifyFrameDetached(old_browser, info->frame_);
|
||||
}
|
||||
info->IsCurrentMainFrame());
|
||||
// Shouldn't need to trigger any notifications at this point.
|
||||
DCHECK(!frame_detached);
|
||||
DCHECK(!frame_destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,6 +166,8 @@ class CefBrowserInfo : public base::RefCountedThreadSafe<CefBrowserInfo> {
|
||||
CefRefPtr<CefBrowserHostBase> browser,
|
||||
CefRefPtr<CefFrameHostImpl> frame,
|
||||
std::vector<CefDraggableRegion> draggable_regions);
|
||||
void MaybeNotifyFrameDetached(CefRefPtr<CefBrowserHostBase> browser,
|
||||
CefRefPtr<CefFrameHostImpl> frame);
|
||||
|
||||
private:
|
||||
friend class base::RefCountedThreadSafe<CefBrowserInfo>;
|
||||
@ -190,8 +192,8 @@ class CefBrowserInfo : public base::RefCountedThreadSafe<CefBrowserInfo> {
|
||||
CefRefPtr<CefFrameHostImpl> frame);
|
||||
|
||||
void MaybeNotifyFrameCreated(CefRefPtr<CefFrameHostImpl> frame);
|
||||
void MaybeNotifyFrameDetached(CefRefPtr<CefBrowserHostBase> browser,
|
||||
CefRefPtr<CefFrameHostImpl> frame);
|
||||
void MaybeNotifyFrameDestroyed(CefRefPtr<CefBrowserHostBase> browser,
|
||||
CefRefPtr<CefFrameHostImpl> frame);
|
||||
void MaybeNotifyMainFrameChanged(CefRefPtr<CefBrowserHostBase> browser,
|
||||
CefRefPtr<CefFrameHostImpl> old_frame,
|
||||
CefRefPtr<CefFrameHostImpl> new_frame);
|
||||
|
@ -500,35 +500,18 @@ bool CefFrameHostImpl::IsDetached() const {
|
||||
return !GetRenderFrameHost();
|
||||
}
|
||||
|
||||
bool CefFrameHostImpl::Detach(DetachReason reason, bool is_current_main_frame) {
|
||||
std::pair<bool, bool> CefFrameHostImpl::Detach(DetachReason reason,
|
||||
bool is_current_main_frame) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
// This method may be called multiple times (e.g. from CefBrowserInfo
|
||||
// SetMainFrame and RemoveFrame).
|
||||
bool is_first_complete_detach = false;
|
||||
|
||||
// Should not be called for temporary frames.
|
||||
CHECK(!is_temporary());
|
||||
|
||||
// Must be a main frame if |is_current_main_frame| is true.
|
||||
CHECK(!is_current_main_frame || is_main_frame_);
|
||||
|
||||
if (!is_current_main_frame) {
|
||||
{
|
||||
base::AutoLock lock_scope(state_lock_);
|
||||
if (browser_info_) {
|
||||
is_first_complete_detach = true;
|
||||
browser_info_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// In case we never attached, clean up.
|
||||
while (!queued_renderer_actions_.empty()) {
|
||||
queued_renderer_actions_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if (render_frame_.is_bound()) {
|
||||
const bool is_bound = render_frame_.is_bound();
|
||||
if (is_bound) {
|
||||
if (VLOG_IS_ON(1)) {
|
||||
std::string reason_str;
|
||||
switch (reason) {
|
||||
@ -554,7 +537,27 @@ bool CefFrameHostImpl::Detach(DetachReason reason, bool is_current_main_frame) {
|
||||
render_frame_host_ = nullptr;
|
||||
}
|
||||
|
||||
return is_first_complete_detach;
|
||||
// This method may be called multiple times (e.g. from CefBrowserInfo
|
||||
// SetMainFrame and RemoveFrame).
|
||||
bool is_first_complete_detach = false;
|
||||
|
||||
if (!is_current_main_frame) {
|
||||
{
|
||||
base::AutoLock lock_scope(state_lock_);
|
||||
if (browser_info_) {
|
||||
DVLOG(1) << __func__ << ": " << GetDebugString() << " invalidated";
|
||||
is_first_complete_detach = true;
|
||||
browser_info_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// In case we never attached, clean up.
|
||||
while (!queued_renderer_actions_.empty()) {
|
||||
queued_renderer_actions_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(is_bound, is_first_complete_detach);
|
||||
}
|
||||
|
||||
void CefFrameHostImpl::DetachRenderFrame() {
|
||||
@ -564,12 +567,11 @@ void CefFrameHostImpl::DetachRenderFrame() {
|
||||
static_cast<uint32_t>(frame_util::ResetReason::kDetached), "Detached");
|
||||
}
|
||||
|
||||
void CefFrameHostImpl::MaybeReAttach(
|
||||
void CefFrameHostImpl::MaybeAttach(
|
||||
scoped_refptr<CefBrowserInfo> browser_info,
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
bool require_detached) {
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
CEF_REQUIRE_UIT();
|
||||
if (render_frame_.is_bound() && render_frame_host_ == render_frame_host) {
|
||||
if (render_frame_host_ == render_frame_host) {
|
||||
// Nothing to do here.
|
||||
return;
|
||||
}
|
||||
@ -577,16 +579,13 @@ void CefFrameHostImpl::MaybeReAttach(
|
||||
// Should not be called for temporary frames.
|
||||
CHECK(!is_temporary());
|
||||
|
||||
// If |require_detached| then we expect that Detach() was called previously.
|
||||
CHECK(!require_detached || !render_frame_.is_bound());
|
||||
// We expect that either this frame has never attached (e.g. when swapping
|
||||
// from speculative to non-speculative) or Detach() was called previously
|
||||
// (e.g. when exiting the bfcache).
|
||||
CHECK(!render_frame_.is_bound());
|
||||
|
||||
if (render_frame_.is_bound()) {
|
||||
// Intentionally not clearing |queued_renderer_actions_|, as we may be
|
||||
// changing RFH during initial browser navigation.
|
||||
DVLOG(1) << __func__ << ": " << GetDebugString()
|
||||
<< " detached (reason=RENDER_FRAME_CHANGED)";
|
||||
DetachRenderFrame();
|
||||
}
|
||||
// Intentionally not clearing |queued_renderer_actions_|, as we may be
|
||||
// changing RFH during initial browser navigation.
|
||||
|
||||
// The RFH may change but the frame token should remain the same.
|
||||
CHECK(*frame_token_ == render_frame_host->GetGlobalFrameToken());
|
||||
@ -653,6 +652,14 @@ void CefFrameHostImpl::SendToRenderFrame(const std::string& function_name,
|
||||
void CefFrameHostImpl::OnRenderFrameDisconnect() {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
DVLOG(1) << __func__ << ": " << GetDebugString();
|
||||
|
||||
if (auto browser_info = GetBrowserInfo()) {
|
||||
if (auto browser = browser_info->browser()) {
|
||||
browser_info->MaybeNotifyFrameDetached(browser, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Reconnect, if any, will be triggered via FrameAttached().
|
||||
render_frame_.reset();
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
@ -140,15 +141,15 @@ class CefFrameHostImpl : public CefFrame, public cef::mojom::BrowserFrame {
|
||||
// implicitly via CefBrowserInfo::browser() returning nullptr. If
|
||||
// |is_current_main_frame| is true then only the RenderFrameHost references
|
||||
// will be released as we want the frame object itself to remain valid.
|
||||
// Returns true if the frame is completely detached for the first time.
|
||||
bool Detach(DetachReason reason, bool is_current_main_frame);
|
||||
// Returns (bool, bool) to indicate if frame detached and/or frame destroyed
|
||||
// notifications should be triggered respectively.
|
||||
std::pair<bool, bool> Detach(DetachReason reason, bool is_current_main_frame);
|
||||
|
||||
// A frame has swapped to active status from prerendering or the back-forward
|
||||
// cache. We may need to re-attach if the RFH has changed. See
|
||||
// https://crbug.com/1179502#c8 for additional background.
|
||||
void MaybeReAttach(scoped_refptr<CefBrowserInfo> browser_info,
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
bool require_detached);
|
||||
// A new frame was created or a frame has swapped to active status from
|
||||
// prerendering or the back-forward cache. Update internal state if the RFH
|
||||
// has changed. See https://crbug.com/1179502#c8 for additional background.
|
||||
void MaybeAttach(scoped_refptr<CefBrowserInfo> browser_info,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
||||
// cef::mojom::BrowserFrame methods forwarded from CefBrowserFrame.
|
||||
void SendMessage(const std::string& name,
|
||||
|
@ -69,7 +69,10 @@ class CefFrameServiceBase : public Interface,
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
|
||||
if (render_frame_host == render_frame_host_) {
|
||||
DVLOG(1) << __func__ << ": RenderFrameHost destroyed.";
|
||||
DVLOG(1) << __func__ << ": "
|
||||
<< frame_util::GetFrameDebugString(
|
||||
render_frame_host->GetGlobalFrameToken())
|
||||
<< " destroyed";
|
||||
if (receiver_.is_bound()) {
|
||||
receiver_.ResetWithReason(
|
||||
static_cast<uint32_t>(frame_util::ResetReason::kDeleted),
|
||||
|
@ -316,20 +316,23 @@ void CefFrameImpl::SendProcessMessage(CefProcessId target_process,
|
||||
}
|
||||
}
|
||||
|
||||
void CefFrameImpl::OnAttached() {
|
||||
// Called indirectly from RenderFrameCreated.
|
||||
ConnectBrowserFrame(ConnectReason::RENDER_FRAME_CREATED);
|
||||
}
|
||||
|
||||
void CefFrameImpl::OnWasShown() {
|
||||
if (browser_connection_state_ == ConnectionState::DISCONNECTED) {
|
||||
// Reconnect a frame that has exited the bfcache.
|
||||
if (browser_connection_state_ == ConnectionState::DISCONNECTED &&
|
||||
did_commit_provisional_load_) {
|
||||
// Reconnect a frame that has exited the bfcache. We ignore temporary
|
||||
// frames that have never called DidCommitProvisionalLoad.
|
||||
ConnectBrowserFrame(ConnectReason::WAS_SHOWN);
|
||||
}
|
||||
}
|
||||
|
||||
void CefFrameImpl::OnDidCommitProvisionalLoad() {
|
||||
did_commit_provisional_load_ = true;
|
||||
if (browser_connection_state_ == ConnectionState::DISCONNECTED) {
|
||||
// Connect after RenderFrameImpl::DidCommitNavigation has potentially
|
||||
// reset the BrowserInterfaceBroker in the browser process. See related
|
||||
// comments in OnDisconnect.
|
||||
ConnectBrowserFrame(ConnectReason::DID_COMMIT);
|
||||
}
|
||||
MaybeInitializeScriptContext();
|
||||
}
|
||||
|
||||
@ -473,8 +476,8 @@ void CefFrameImpl::ConnectBrowserFrame(ConnectReason reason) {
|
||||
if (VLOG_IS_ON(1)) {
|
||||
std::string reason_str;
|
||||
switch (reason) {
|
||||
case ConnectReason::RENDER_FRAME_CREATED:
|
||||
reason_str = "RENDER_FRAME_CREATED";
|
||||
case ConnectReason::DID_COMMIT:
|
||||
reason_str = "DID_COMMIT";
|
||||
break;
|
||||
case ConnectReason::WAS_SHOWN:
|
||||
reason_str = "WAS_SHOWN";
|
||||
|
@ -81,7 +81,6 @@ class CefFrameImpl
|
||||
CefRefPtr<CefProcessMessage> message) override;
|
||||
|
||||
// Forwarded from CefRenderFrameObserver.
|
||||
void OnAttached();
|
||||
void OnWasShown();
|
||||
void OnDidCommitProvisionalLoad();
|
||||
void OnDidFinishLoad();
|
||||
@ -105,7 +104,7 @@ class CefFrameImpl
|
||||
LocalFrameAction action);
|
||||
|
||||
enum class ConnectReason {
|
||||
RENDER_FRAME_CREATED,
|
||||
DID_COMMIT,
|
||||
WAS_SHOWN,
|
||||
RETRY,
|
||||
};
|
||||
|
@ -199,7 +199,6 @@ void CefRenderFrameObserver::AttachFrame(CefFrameImpl* frame) {
|
||||
DCHECK(frame);
|
||||
DCHECK(!frame_);
|
||||
frame_ = frame;
|
||||
frame_->OnAttached();
|
||||
}
|
||||
|
||||
void CefRenderFrameObserver::OnLoadStart() {
|
||||
|
Reference in New Issue
Block a user