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:
Marshall Greenblatt
2024-11-27 17:08:42 -05:00
parent 7f253f83a2
commit 35fc888c72
16 changed files with 484 additions and 220 deletions

View File

@ -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);
}
}