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 ea9bdad924
commit 1f48ecb4be
16 changed files with 484 additions and 220 deletions

View File

@ -33,7 +33,7 @@
// by hand. See the translator.README.txt file in the tools directory for
// more information.
//
// $hash=fc6fbee765ce2b649f5293c8c4b076d36014e4aa$
// $hash=d8d06ee3d6e749d864e585fce8011d113b397220$
//
#ifndef CEF_INCLUDE_CAPI_CEF_FRAME_HANDLER_CAPI_H_
@ -76,6 +76,8 @@ extern "C" {
/// objects are detached at the same time then notifications will be sent for
/// any sub-frame objects before the main frame object. Commands can no longer
/// be routed and will be discarded.
/// - CefFremeHadler::OnFrameDestroyed => An existing main frame or sub-frame
/// object has been destroyed.
/// - cef_frame_handler_t::OnMainFrameChanged => A new main frame object has
/// been assigned to the browser. This will only occur with cross-origin
/// navigation or re-navigation after renderer process termination (due to
@ -85,42 +87,32 @@ extern "C" {
/// - cef_frame_handler_t::OnFrameDetached => Any sub-frame objects have lost
/// their connection to the renderer process. Commands can no longer be routed
/// and will be discarded.
/// - CefFreameHandler::OnFrameDestroyed => Any sub-frame objects have been
/// destroyed.
/// - cef_life_span_handler_t::OnBeforeClose => The browser has been destroyed.
/// - cef_frame_handler_t::OnFrameDetached => The main frame object have lost
/// its connection to the renderer process. Notifications will be sent for any
/// sub-frame objects before the main frame object. Commands can no longer be
/// routed and will be discarded.
/// - CefFreameHandler::OnFrameDestroyed => The main frame object has been
/// destroyed.
/// - cef_frame_handler_t::OnMainFrameChanged => The final main frame object has
/// been removed from the browser.
///
/// Cross-origin navigation and/or loading receives special handling.
/// Special handling applies for cross-origin loading on creation/navigation of
/// sub-frames, and cross-origin loading on creation of new popup browsers. A
/// temporary frame will first be created in the parent frame's renderer
/// process. This temporary frame will never attach and will be discarded after
/// the real cross-origin frame is created in the new/target renderer process.
/// The client will receive creation callbacks for the temporary frame, followed
/// by cross-origin navigation callbacks (2) for the transition from the
/// temporary frame to the real frame. The temporary frame will not receive or
/// execute commands during this transitional period (any sent commands will be
/// discarded).
///
/// When the main frame navigates to a different origin the OnMainFrameChanged
/// callback (2) will be executed with the old and new main frame objects.
///
/// When a new sub-frame is loaded in, or an existing sub-frame is navigated to,
/// a different origin from the parent frame, a temporary sub-frame object will
/// first be created in the parent's renderer process. That temporary sub-frame
/// will then be discarded after the real cross-origin sub-frame is created in
/// the new/target renderer process. The client will receive cross-origin
/// navigation callbacks (2) for the transition from the temporary sub-frame to
/// the real sub-frame. The temporary sub-frame will not receive or execute
/// commands during this transitional period (any sent commands will be
/// discarded).
///
/// When a new popup browser is created in a different origin from the parent
/// browser, a temporary main frame object for the popup will first be created
/// in the parent's renderer process. That temporary main frame will then be
/// discarded after the real cross-origin main frame is created in the
/// new/target renderer process. The client will receive creation and initial
/// navigation callbacks (1) for the temporary main frame, followed by cross-
/// origin navigation callbacks (2) for the transition from the temporary main
/// frame to the real main frame. The temporary main frame may receive and
/// execute commands during this transitional period (any sent commands may be
/// executed, but the behavior is potentially undesirable since they execute in
/// the parent browser's renderer process and not the new/target renderer
/// process).
///
/// Callbacks will not be executed for placeholders that may be created during
/// pre-commit navigation for sub-frames that do not yet exist in the renderer
/// process. Placeholders will have cef_frame_t::get_identifier() == -4.
@ -138,17 +130,33 @@ typedef struct _cef_frame_handler_t {
/// Called when a new frame is created. This will be the first notification
/// that references |frame|. Any commands that require transport to the
/// associated renderer process (LoadRequest, SendProcessMessage, GetSource,
/// etc.) will be queued until OnFrameAttached is called for |frame|.
/// etc.) will be queued. The queued commands will be sent before
/// OnFrameAttached or discarded before OnFrameDestroyed if the frame never
/// attaches.
///
void(CEF_CALLBACK* on_frame_created)(struct _cef_frame_handler_t* self,
struct _cef_browser_t* browser,
struct _cef_frame_t* frame);
///
/// Called when an existing frame is destroyed. This will be the last
/// notification that references |frame| and cef_frame_t::is_valid() will
/// return false (0) for |frame|. If called during browser destruction and
/// after cef_life_span_handler_t::on_before_close() then
/// cef_browser_t::is_valid() will return false (0) for |browser|. Any queued
/// commands that have not been sent will be discarded before this callback.
///
void(CEF_CALLBACK* on_frame_destroyed)(struct _cef_frame_handler_t* self,
struct _cef_browser_t* browser,
struct _cef_frame_t* frame);
///
/// Called when a frame can begin routing commands to/from the associated
/// renderer process. |reattached| will be true (1) if the frame was re-
/// attached after exiting the BackForwardCache. Any commands that were queued
/// have now been dispatched.
/// attached after exiting the BackForwardCache or after encountering a
/// recoverable connection error. Any queued commands will now have been
/// dispatched. This function will not be called for temporary frames created
/// during cross-origin navigation.
///
void(CEF_CALLBACK* on_frame_attached)(struct _cef_frame_handler_t* self,
struct _cef_browser_t* browser,
@ -156,12 +164,19 @@ typedef struct _cef_frame_handler_t {
int reattached);
///
/// Called when a frame loses its connection to the renderer process and will
/// be destroyed. Any pending or future commands will be discarded and
/// cef_frame_t::is_valid() will now return false (0) for |frame|. If called
/// after cef_life_span_handler_t::on_before_close() during browser
/// destruction then cef_browser_t::is_valid() will return false (0) for
/// |browser|.
/// Called when a frame loses its connection to the renderer process. This may
/// occur when a frame is destroyed, enters the BackForwardCache, or
/// encounters a rare connection error. In the case of frame destruction this
/// call will be followed by a (potentially async) call to OnFrameDestroyed.
/// If frame destruction is occuring synchronously then
/// cef_frame_t::is_valid() will return false (0) for |frame|. If called
/// during browser destruction and after
/// cef_life_span_handler_t::on_before_close() then cef_browser_t::is_valid()
/// will return false (0) for |browser|. If, in the non-destruction case, the
/// same frame later exits the BackForwardCache or recovers from a connection
/// error then there will be a follow-up call to OnFrameAttached. This
/// function will not be called for temporary frames created during cross-
/// origin navigation.
///
void(CEF_CALLBACK* on_frame_detached)(struct _cef_frame_handler_t* self,
struct _cef_browser_t* browser,
@ -173,14 +188,14 @@ typedef struct _cef_frame_handler_t {
/// navigation after renderer process termination (due to crashes, etc).
/// |old_frame| will be NULL and |new_frame| will be non-NULL when a main
/// frame is assigned to |browser| for the first time. |old_frame| will be
/// non-NULL and |new_frame| will be NULL and when a main frame is removed
/// from |browser| for the last time. Both |old_frame| and |new_frame| will be
/// non-NULL for cross-origin navigations or re-navigation after renderer
/// process termination. This function will be called after on_frame_created()
/// for |new_frame| and/or after on_frame_detached() for |old_frame|. If
/// called after cef_life_span_handler_t::on_before_close() during browser
/// destruction then cef_browser_t::is_valid() will return false (0) for
/// |browser|.
/// non-NULL and |new_frame| will be NULL when a main frame is removed from
/// |browser| for the last time. Both |old_frame| and |new_frame| will be non-
/// NULL for cross-origin navigations or re-navigation after renderer process
/// termination. This function will be called after on_frame_created() for
/// |new_frame| and/or after on_frame_destroyed() for |old_frame|. If called
/// during browser destruction and after
/// cef_life_span_handler_t::on_before_close() then cef_browser_t::is_valid()
/// will return false (0) for |browser|.
///
void(CEF_CALLBACK* on_main_frame_changed)(struct _cef_frame_handler_t* self,
struct _cef_browser_t* browser,

View File

@ -42,13 +42,13 @@
// way that may cause binary incompatibility with other builds. The universal
// hash value will change if any platform is affected whereas the platform hash
// values will change only if that particular platform is affected.
#define CEF_API_HASH_UNIVERSAL "8d6d53c5732a12b4d663665e3745a93d1cfd742d"
#define CEF_API_HASH_UNIVERSAL "7ce563b3df3a227c06d331774d5fa51c8421a7f1"
#if defined(OS_WIN)
#define CEF_API_HASH_PLATFORM "3593cc6344b391d992421dd985c4ebcc46d8314f"
#define CEF_API_HASH_PLATFORM "0b070263fe1c7304bd90bd7a8338fcc08410d299"
#elif defined(OS_MAC)
#define CEF_API_HASH_PLATFORM "c045e75415a6abc2c29a3e1e05baea7528e2ec28"
#define CEF_API_HASH_PLATFORM "e99938b49f333ccda55ea34dfb36dd65a6b2cacd"
#elif defined(OS_LINUX)
#define CEF_API_HASH_PLATFORM "b1058e8b167cfaa2f0feaccf4b4f23a813db515a"
#define CEF_API_HASH_PLATFORM "45669721b97760d8cda9ea29a5bd070d38267424"
#endif
#ifdef __cplusplus

View File

@ -68,6 +68,8 @@
/// objects are detached at the same time then notifications will be sent for
/// any sub-frame objects before the main frame object. Commands can no longer
/// be routed and will be discarded.
/// - CefFremeHadler::OnFrameDestroyed => An existing main frame or sub-frame
/// object has been destroyed.
/// - CefFrameHandler::OnMainFrameChanged => A new main frame object has been
/// assigned to the browser. This will only occur with cross-origin navigation
/// or re-navigation after renderer process termination (due to crashes, etc).
@ -76,42 +78,32 @@
/// - CefFrameHandler::OnFrameDetached => Any sub-frame objects have lost their
/// connection to the renderer process. Commands can no longer be routed and
/// will be discarded.
/// - CefFreameHandler::OnFrameDestroyed => Any sub-frame objects have been
/// destroyed.
/// - CefLifeSpanHandler::OnBeforeClose => The browser has been destroyed.
/// - CefFrameHandler::OnFrameDetached => The main frame object have lost its
/// connection to the renderer process. Notifications will be sent for any
/// sub-frame objects before the main frame object. Commands can no longer be
/// routed and will be discarded.
/// - CefFreameHandler::OnFrameDestroyed => The main frame object has been
/// destroyed.
/// - CefFrameHandler::OnMainFrameChanged => The final main frame object has
/// been removed from the browser.
///
/// Cross-origin navigation and/or loading receives special handling.
/// Special handling applies for cross-origin loading on creation/navigation of
/// sub-frames, and cross-origin loading on creation of new popup browsers. A
/// temporary frame will first be created in the parent frame's renderer
/// process. This temporary frame will never attach and will be discarded after
/// the real cross-origin frame is created in the new/target renderer process.
/// The client will receive creation callbacks for the temporary frame, followed
/// by cross-origin navigation callbacks (2) for the transition from the
/// temporary frame to the real frame. The temporary frame will not receive or
/// execute commands during this transitional period (any sent commands will be
/// discarded).
///
/// When the main frame navigates to a different origin the OnMainFrameChanged
/// callback (2) will be executed with the old and new main frame objects.
///
/// When a new sub-frame is loaded in, or an existing sub-frame is navigated to,
/// a different origin from the parent frame, a temporary sub-frame object will
/// first be created in the parent's renderer process. That temporary sub-frame
/// will then be discarded after the real cross-origin sub-frame is created in
/// the new/target renderer process. The client will receive cross-origin
/// navigation callbacks (2) for the transition from the temporary sub-frame to
/// the real sub-frame. The temporary sub-frame will not receive or execute
/// commands during this transitional period (any sent commands will be
/// discarded).
///
/// When a new popup browser is created in a different origin from the parent
/// browser, a temporary main frame object for the popup will first be created
/// in the parent's renderer process. That temporary main frame will then be
/// discarded after the real cross-origin main frame is created in the
/// new/target renderer process. The client will receive creation and initial
/// navigation callbacks (1) for the temporary main frame, followed by
/// cross-origin navigation callbacks (2) for the transition from the temporary
/// main frame to the real main frame. The temporary main frame may receive and
/// execute commands during this transitional period (any sent commands may be
/// executed, but the behavior is potentially undesirable since they execute in
/// the parent browser's renderer process and not the new/target renderer
/// process).
///
/// Callbacks will not be executed for placeholders that may be created during
/// pre-commit navigation for sub-frames that do not yet exist in the renderer
/// process. Placeholders will have CefFrame::GetIdentifier() == -4.
@ -126,17 +118,33 @@ class CefFrameHandler : public virtual CefBaseRefCounted {
/// Called when a new frame is created. This will be the first notification
/// that references |frame|. Any commands that require transport to the
/// associated renderer process (LoadRequest, SendProcessMessage, GetSource,
/// etc.) will be queued until OnFrameAttached is called for |frame|.
/// etc.) will be queued. The queued commands will be sent before
/// OnFrameAttached or discarded before OnFrameDestroyed if the frame never
/// attaches.
///
/*--cef()--*/
virtual void OnFrameCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) {}
///
/// Called when an existing frame is destroyed. This will be the last
/// notification that references |frame| and CefFrame::IsValid() will return
/// false for |frame|. If called during browser destruction and after
/// CefLifeSpanHandler::OnBeforeClose() then CefBrowser::IsValid() will return
/// false for |browser|. Any queued commands that have not been sent will be
/// discarded before this callback.
///
/*--cef()--*/
virtual void OnFrameDestroyed(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) {}
///
/// Called when a frame can begin routing commands to/from the associated
/// renderer process. |reattached| will be true if the frame was re-attached
/// after exiting the BackForwardCache. Any commands that were queued have now
/// been dispatched.
/// after exiting the BackForwardCache or after encountering a recoverable
/// connection error. Any queued commands will now have been dispatched. This
/// method will not be called for temporary frames created during cross-origin
/// navigation.
///
/*--cef()--*/
virtual void OnFrameAttached(CefRefPtr<CefBrowser> browser,
@ -144,11 +152,17 @@ class CefFrameHandler : public virtual CefBaseRefCounted {
bool reattached) {}
///
/// Called when a frame loses its connection to the renderer process and will
/// be destroyed. Any pending or future commands will be discarded and
/// CefFrame::IsValid() will now return false for |frame|. If called after
/// CefLifeSpanHandler::OnBeforeClose() during browser destruction then
/// CefBrowser::IsValid() will return false for |browser|.
/// Called when a frame loses its connection to the renderer process. This may
/// occur when a frame is destroyed, enters the BackForwardCache, or
/// encounters a rare connection error. In the case of frame destruction this
/// call will be followed by a (potentially async) call to OnFrameDestroyed.
/// If frame destruction is occuring synchronously then CefFrame::IsValid()
/// will return false for |frame|. If called during browser destruction and
/// after CefLifeSpanHandler::OnBeforeClose() then CefBrowser::IsValid() will
/// return false for |browser|. If, in the non-destruction case, the same
/// frame later exits the BackForwardCache or recovers from a connection error
/// then there will be a follow-up call to OnFrameAttached. This method will
/// not be called for temporary frames created during cross-origin navigation.
///
/*--cef()--*/
virtual void OnFrameDetached(CefRefPtr<CefBrowser> browser,
@ -160,13 +174,13 @@ class CefFrameHandler : public virtual CefBaseRefCounted {
/// re-navigation after renderer process termination (due to crashes, etc).
/// |old_frame| will be NULL and |new_frame| will be non-NULL when a main
/// frame is assigned to |browser| for the first time. |old_frame| will be
/// non-NULL and |new_frame| will be NULL and when a main frame is removed
/// from |browser| for the last time. Both |old_frame| and |new_frame| will be
/// non-NULL and |new_frame| will be NULL when a main frame is removed from
/// |browser| for the last time. Both |old_frame| and |new_frame| will be
/// non-NULL for cross-origin navigations or re-navigation after renderer
/// process termination. This method will be called after OnFrameCreated() for
/// |new_frame| and/or after OnFrameDetached() for |old_frame|. If called
/// after CefLifeSpanHandler::OnBeforeClose() during browser destruction then
/// CefBrowser::IsValid() will return false for |browser|.
/// |new_frame| and/or after OnFrameDestroyed() for |old_frame|. If called
/// during browser destruction and after CefLifeSpanHandler::OnBeforeClose()
/// then CefBrowser::IsValid() will return false for |browser|.
///
/*--cef(optional_param=old_frame,optional_param=new_frame)--*/
virtual void OnMainFrameChanged(CefRefPtr<CefBrowser> browser,

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

View File

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

View File

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

View File

@ -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,

View File

@ -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),

View File

@ -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";

View File

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

View File

@ -199,7 +199,6 @@ void CefRenderFrameObserver::AttachFrame(CefFrameImpl* frame) {
DCHECK(frame);
DCHECK(!frame_);
frame_ = frame;
frame_->OnAttached();
}
void CefRenderFrameObserver::OnLoadStart() {

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=4a3d33abbaa00a373ea515338ed67d96708dbb9c$
// $hash=51da21d569dd41e38cb2dc6e0f2dea0bd88dbdce$
//
#include "libcef_dll/cpptoc/frame_handler_cpptoc.h"
@ -50,6 +50,34 @@ frame_handler_on_frame_created(struct _cef_frame_handler_t* self,
CefBrowserCToCpp::Wrap(browser), CefFrameCToCpp::Wrap(frame));
}
void CEF_CALLBACK
frame_handler_on_frame_destroyed(struct _cef_frame_handler_t* self,
cef_browser_t* browser,
cef_frame_t* frame) {
shutdown_checker::AssertNotShutdown();
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
DCHECK(self);
if (!self) {
return;
}
// Verify param: browser; type: refptr_diff
DCHECK(browser);
if (!browser) {
return;
}
// Verify param: frame; type: refptr_diff
DCHECK(frame);
if (!frame) {
return;
}
// Execute
CefFrameHandlerCppToC::Get(self)->OnFrameDestroyed(
CefBrowserCToCpp::Wrap(browser), CefFrameCToCpp::Wrap(frame));
}
void CEF_CALLBACK
frame_handler_on_frame_attached(struct _cef_frame_handler_t* self,
cef_browser_t* browser,
@ -140,6 +168,7 @@ frame_handler_on_main_frame_changed(struct _cef_frame_handler_t* self,
CefFrameHandlerCppToC::CefFrameHandlerCppToC() {
GetStruct()->on_frame_created = frame_handler_on_frame_created;
GetStruct()->on_frame_destroyed = frame_handler_on_frame_destroyed;
GetStruct()->on_frame_attached = frame_handler_on_frame_attached;
GetStruct()->on_frame_detached = frame_handler_on_frame_detached;
GetStruct()->on_main_frame_changed = frame_handler_on_main_frame_changed;

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=0074492ed580ccc06962a05b6c72bdabae182a51$
// $hash=14e4a39489488582d7965ae71ed1ef174a4f3b08$
//
#include "libcef_dll/ctocpp/frame_handler_ctocpp.h"
@ -48,6 +48,34 @@ void CefFrameHandlerCToCpp::OnFrameCreated(CefRefPtr<CefBrowser> browser,
CefFrameCppToC::Wrap(frame));
}
NO_SANITIZE("cfi-icall")
void CefFrameHandlerCToCpp::OnFrameDestroyed(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) {
shutdown_checker::AssertNotShutdown();
cef_frame_handler_t* _struct = GetStruct();
if (CEF_MEMBER_MISSING(_struct, on_frame_destroyed)) {
return;
}
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
// Verify param: browser; type: refptr_diff
DCHECK(browser.get());
if (!browser.get()) {
return;
}
// Verify param: frame; type: refptr_diff
DCHECK(frame.get());
if (!frame.get()) {
return;
}
// Execute
_struct->on_frame_destroyed(_struct, CefBrowserCppToC::Wrap(browser),
CefFrameCppToC::Wrap(frame));
}
NO_SANITIZE("cfi-icall")
void CefFrameHandlerCToCpp::OnFrameAttached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,

View File

@ -9,7 +9,7 @@
// implementations. See the translator.README.txt file in the tools directory
// for more information.
//
// $hash=a571fa8b3c173d78cfb67eb3e44c8f2c3fb2e089$
// $hash=f33fedc6d7e0d692b03fe7f35319e93c5f31b9b1$
//
#ifndef CEF_LIBCEF_DLL_CTOCPP_FRAME_HANDLER_CTOCPP_H_
@ -36,6 +36,8 @@ class CefFrameHandlerCToCpp : public CefCToCppRefCounted<CefFrameHandlerCToCpp,
// CefFrameHandler methods.
void OnFrameCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override;
void OnFrameDestroyed(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override;
void OnFrameAttached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
bool reattached) override;

View File

@ -6,6 +6,7 @@
#include <map>
#include <memory>
#include <queue>
#include <set>
#include <sstream>
#include <string>
@ -16,7 +17,8 @@
#include "tests/ceftests/test_util.h"
#include "tests/gtest/include/gtest/gtest.h"
// Set to 1 to enable verbose debugging info logging.
// Set to 1 and add `--enable-logging --vmodule=*frame*=1 --log-file=<path>` to
// the command-line to enable verbose debugging info logging.
#define VERBOSE_DEBUGGING 0
namespace {
@ -33,8 +35,9 @@ struct FrameStatus {
MAIN_FRAME_CHANGED_ASSIGNED,
LOAD_START,
LOAD_END,
BEFORE_CLOSE,
FRAME_DETACHED,
BEFORE_CLOSE,
FRAME_DESTROYED,
MAIN_FRAME_CHANGED_REMOVED,
MAIN_FRAME_FINAL_REMOVED,
@ -57,10 +60,12 @@ struct FrameStatus {
return "OnLoadStart";
case LOAD_END:
return "OnLoadEnd";
case BEFORE_CLOSE:
return "OnBeforeClose";
case FRAME_DETACHED:
return "OnFrameDetached";
case BEFORE_CLOSE:
return "OnBeforeClose";
case FRAME_DESTROYED:
return "OnFrameDestroyed";
case MAIN_FRAME_CHANGED_REMOVED:
return "OnMainFrameChanged(changed_removed)";
case MAIN_FRAME_FINAL_REMOVED:
@ -133,7 +138,9 @@ struct FrameStatus {
#endif
return got_callback_[LOAD_END];
}
bool IsDetached() const { return got_callback_[FRAME_DETACHED]; }
bool IsDestroyed() const { return got_callback_[FRAME_DESTROYED]; }
bool IsMain() const { return is_main_; }
void SetIsFirstMain(bool val) {
EXPECT_TRUE(is_main_);
@ -148,17 +155,24 @@ struct FrameStatus {
is_last_main_ = val;
}
void SetIsTemporary(bool val) {
EXPECT_FALSE(is_main_);
is_temporary_ = val;
}
void SetIsTemporary(bool val) { is_temporary_ = val; }
bool IsTemporary() const { return is_temporary_; }
void SetAdditionalDebugInfo(const std::string& debug_info) {
debug_info_ = debug_info;
}
std::string GetDebugString() const { return debug_info_ + ident_str_; }
std::string GetDebugString(bool dump_state = false) const {
std::string result = debug_info_ + ident_str_;
if (dump_state) {
std::stringstream ss;
ss << "\nis_main=" << is_main_ << "\nis_first_main=" << is_first_main_
<< "\nis_last_main=" << is_last_main_
<< "\nis_temporary=" << is_temporary_;
result += ss.str();
}
return result;
}
// The main frame will be reused for same-origin navigations.
void ResetMainLoadStatus() {
@ -200,12 +214,34 @@ struct FrameStatus {
CefRefPtr<CefFrame> frame) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
// A frame is never valid after it's detached.
VerifyFrame(__FUNCTION__, frame, /*expect_valid=*/false);
// Don't check IsValid() here for sub-frames. Invalidation will occur during
// destruction and we may become detached either before or during
// destruction, so the value is not guaranteed.
std::optional<bool> expect_valid;
if (frame->IsMain()) {
// Check IsValid() here for main frames. It will only return true for the
// current main frame.
expect_valid =
frame->GetIdentifier() == browser->GetMainFrame()->GetIdentifier();
}
VerifyFrame(__FUNCTION__, frame, expect_valid);
GotCallback(__FUNCTION__, FRAME_DETACHED);
}
void OnFrameDestroyed(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
// A frame is never valid after it's destroyed.
VerifyFrame(__FUNCTION__, frame, /*expect_valid=*/false);
GotCallback(__FUNCTION__, FRAME_DESTROYED);
}
void OnMainFrameChanged(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> old_frame,
CefRefPtr<CefFrame> new_frame) {
@ -257,7 +293,7 @@ struct FrameStatus {
// Called for all existing frames, not just the target frame.
// We need to track this status to know if the browser should be valid in
// following calls to OnFrameDetached.
// following calls to OnFrameDetached/OnFrameDestroyed.
void OnBeforeClose(CefRefPtr<CefBrowser> browser) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
@ -325,7 +361,6 @@ struct FrameStatus {
if (is_temporary_) {
// Should not receive any queries.
EXPECT_FALSE(is_main_);
EXPECT_EQ(0, delivered_query_ct_);
} else {
// Verify that all expected messages have been sent and received.
@ -357,6 +392,14 @@ struct FrameStatus {
return false;
}
if (is_temporary_) {
// Temporary frames should not connect or load.
if (callback == FRAME_ATTACHED || callback == FRAME_DETACHED ||
callback == LOAD_START || callback == LOAD_END) {
return false;
}
}
if (is_main_) {
if ((callback == MAIN_FRAME_INITIAL_ASSIGNED ||
callback == AFTER_CREATED) &&
@ -373,44 +416,43 @@ struct FrameStatus {
if (callback == MAIN_FRAME_CHANGED_REMOVED && is_last_main_) {
return false;
}
} else if (is_temporary_) {
// For cross-process sub-frame navigation a sub-frame is first created in
// the parent's renderer process. That sub-frame is then discarded after
// the real cross-origin sub-frame is created in a different renderer
// process. These discarded sub-frames will get OnFrameCreated/
// OnFrameAttached immediately followed by OnFrameDetached.
return callback == FRAME_CREATED || callback == FRAME_ATTACHED ||
callback == FRAME_DETACHED;
}
return true;
}
bool IsFlakyCallbackOrder(int callback1, int callback2) const {
if (callback1 == FRAME_ATTACHED &&
(callback2 == MAIN_FRAME_CHANGED_ASSIGNED || callback2 == LOAD_START ||
callback2 == LOAD_END)) {
// Timing of OnFrameAttached is flaky. See issue #3817.
return true;
}
return false;
}
void VerifyCallbackStatus(const std::string& func,
int current_callback) const {
EXPECT_UI_THREAD();
for (int i = 0; i <= CALLBACK_LAST; ++i) {
if (i < current_callback && IsExpectedCallback(i)) {
if (i == FRAME_ATTACHED &&
(current_callback == MAIN_FRAME_CHANGED_ASSIGNED ||
current_callback == LOAD_START || current_callback == LOAD_END)) {
// Timing of OnFrameAttached is flaky. See issue #3817.
if (IsFlakyCallbackOrder(i, current_callback)) {
continue;
}
EXPECT_TRUE(got_callback_[i])
<< "inside " << func << " should already have gotten "
<< GetCallbackName(i);
<< "inside " << func << "\nfor "
<< GetDebugString(/*dump_state=*/true)
<< "\nshould already have gotten " << GetCallbackName(i);
} else {
if (current_callback == FRAME_ATTACHED &&
(i == MAIN_FRAME_CHANGED_ASSIGNED || i == LOAD_START ||
i == LOAD_END)) {
// Timing of OnFrameAttached is flaky. See issue #3817.
if (IsFlakyCallbackOrder(current_callback, i)) {
continue;
}
EXPECT_FALSE(got_callback_[i])
<< "inside " << func << " should not already have gotten "
<< GetCallbackName(i);
<< "inside " << func << "\nfor "
<< GetDebugString(/*dump_state=*/true)
<< "\nshould NOT already have gotten " << GetCallbackName(i);
}
}
}
@ -436,7 +478,8 @@ struct FrameStatus {
// nicely with the concept that "GetMainFrame() always returns a frame that
// can be used", which wouldn't be the case if we returned the old frame
// when calling GetMainFrame() from inside OnFrameCreated (for the new
// frame), OnFrameDetached (for the old frame) or OnMainFrameChanged.
// frame), OnFrameDetached/OnFrameDestoyed (for the old frame) or
// OnMainFrameChanged.
auto main_frame = browser->GetMainFrame();
if (expect_valid) {
EXPECT_TRUE(main_frame) << func;
@ -452,11 +495,13 @@ struct FrameStatus {
void VerifyFrame(const std::string& func,
CefRefPtr<CefFrame> frame,
bool expect_valid = true) const {
if (expect_valid) {
EXPECT_TRUE(frame->IsValid()) << func;
} else {
EXPECT_FALSE(frame->IsValid()) << func;
std::optional<bool> expect_valid = true) const {
if (expect_valid.has_value()) {
if (*expect_valid) {
EXPECT_TRUE(frame->IsValid()) << func;
} else {
EXPECT_FALSE(frame->IsValid()) << func;
}
}
// |frame| should be us. This checks the frame type and ID.
@ -648,6 +693,13 @@ class OrderMainTestHandler : public RoutingTestHandler, public CefFrameHandler {
current_main_frame_->OnFrameDetached(browser, frame);
}
void OnFrameDestroyed(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
EXPECT_UI_THREAD();
EXPECT_TRUE(current_main_frame_);
current_main_frame_->OnFrameDestroyed(browser, frame);
}
void OnMainFrameChanged(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> old_frame,
CefRefPtr<CefFrame> new_frame) override {
@ -1003,14 +1055,14 @@ class FrameStatusMap {
return true;
}
bool AllFramesDetached() const {
bool AllFramesDestroyed() const {
if (size() != expected_frame_ct_) {
return false;
}
Map::const_iterator it = frame_map_.begin();
for (; it != frame_map_.end(); ++it) {
if (!it->second->IsDetached()) {
if (!it->second->IsDestroyed()) {
return false;
}
}
@ -1125,21 +1177,33 @@ class OrderSubTestHandler : public NavigateOrderMainTestHandler {
void OnFrameDetached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
if (!frame->IsMain()) {
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
status->OnFrameDetached(browser, frame);
return;
}
NavigateOrderMainTestHandler::OnFrameDetached(browser, frame);
}
void OnFrameDestroyed(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
if (!frame->IsMain()) {
// Potentially the last notification for an old sub-frame after
// navigation.
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
status->OnFrameDetached(browser, frame);
status->OnFrameDestroyed(browser, frame);
if (map->AllFramesDetached()) {
if (map->AllFramesDestroyed()) {
// Verify results from the previous navigation.
VerifyAndClearSubFrameTestResults(map);
}
return;
}
NavigateOrderMainTestHandler::OnFrameDetached(browser, frame);
NavigateOrderMainTestHandler::OnFrameDestroyed(browser, frame);
}
void OnLoadStart(CefRefPtr<CefBrowser> browser,
@ -1390,23 +1454,45 @@ class CrossOriginOrderSubTestHandler : public OrderSubTestHandler {
mode,
/*expected_frame_ct=*/4U) {}
void OnFrameDetached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
void OnFrameCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
OrderSubTestHandler::OnFrameCreated(browser, frame);
if (!frame->IsMain() &&
loaded_frame_child_ids_.find(ExtractChildId(frame->GetIdentifier())) !=
loaded_frame_child_ids_.end()) {
// Mark sub-frames in the same process as a loaded frame as temporary.
// See below comments in OnFrameDestroyed.
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
status->SetIsTemporary(true);
}
}
void OnLoadStart(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
TransitionType transition_type) override {
OrderSubTestHandler::OnLoadStart(browser, frame, transition_type);
loaded_frame_child_ids_.insert(ExtractChildId(frame->GetIdentifier()));
}
void OnFrameDestroyed(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
// A sub-frame is first created in the parent's renderer process. That
// sub-frame is then discarded after the real cross-origin sub-frame is
// created in a different renderer process. These discarded sub-frames will
// get OnFrameCreated/OnFrameAttached immediately followed by
// OnFrameDetached.
// get OnFrameCreated/OnFrameDestroyed.
if (!frame->IsMain()) {
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
if (status && !status->DidGetCallback(FrameStatus::LOAD_START)) {
status->SetIsTemporary(true);
EXPECT_TRUE(status->IsTemporary());
temp_frame_detached_ct_++;
}
}
OrderSubTestHandler::OnFrameDetached(browser, frame);
OrderSubTestHandler::OnFrameDestroyed(browser, frame);
}
protected:
@ -1431,7 +1517,16 @@ class CrossOriginOrderSubTestHandler : public OrderSubTestHandler {
}
private:
// Parse the format from frame_util::MakeFrameIdentifier to return |child_id|.
static std::string ExtractChildId(const std::string& frame_id) {
const int pos = frame_id.find('-');
CHECK_GT(pos, 0) << frame_id;
return frame_id.substr(0, pos);
}
size_t temp_frame_detached_ct_ = 0U;
std::set<std::string> loaded_frame_child_ids_;
};
} // namespace
@ -1523,6 +1618,7 @@ class PopupOrderMainTestHandler : public OrderMainTestHandler {
temp_main_frame_->SetAdditionalDebugInfo(GetAdditionalDebugInfo() +
"temp ");
temp_main_frame_->SetIsFirstMain(true);
temp_main_frame_->SetIsTemporary(true);
temp_main_frame_->OnFrameCreated(browser, frame);
return;
}
@ -1558,6 +1654,17 @@ class PopupOrderMainTestHandler : public OrderMainTestHandler {
OrderMainTestHandler::OnFrameAttached(browser, frame, reattached);
}
void OnFrameDetached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
if (temp_main_frame_ && temp_main_frame_->IsSame(frame)) {
EXPECT_TRUE(cross_origin_);
temp_main_frame_->OnFrameDetached(browser, frame);
return;
}
OrderMainTestHandler::OnFrameDetached(browser, frame);
}
void OnMainFrameChanged(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> old_frame,
CefRefPtr<CefFrame> new_frame) override {
@ -1570,8 +1677,8 @@ class PopupOrderMainTestHandler : public OrderMainTestHandler {
OrderMainTestHandler::OnMainFrameChanged(browser, old_frame, new_frame);
}
void OnFrameDetached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
void OnFrameDestroyed(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
if (temp_main_frame_ && temp_main_frame_->IsSame(frame)) {
EXPECT_TRUE(cross_origin_);
EXPECT_FALSE(got_temp_destroyed_);
@ -1579,28 +1686,30 @@ class PopupOrderMainTestHandler : public OrderMainTestHandler {
#if VERBOSE_DEBUGGING
LOG(INFO) << temp_main_frame_->GetDebugString()
<< " callback OnFrameDetached(discarded)";
<< " callback OnFrameDestroyed(discarded)";
#endif
// All of the initial main frame callbacks go to the proxy.
EXPECT_TRUE(temp_main_frame_->DidGetCallback(FrameStatus::AFTER_CREATED));
EXPECT_TRUE(temp_main_frame_->DidGetCallback(
FrameStatus::MAIN_FRAME_INITIAL_ASSIGNED));
EXPECT_TRUE(!temp_main_frame_->DidGetCallback(FrameStatus::LOAD_START));
EXPECT_FALSE(temp_main_frame_->DidGetCallback(FrameStatus::LOAD_START));
EXPECT_FALSE(temp_main_frame_->DidGetCallback(FrameStatus::LOAD_END));
EXPECT_TRUE(temp_main_frame_->DidGetCallback(FrameStatus::FRAME_CREATED));
EXPECT_TRUE(
EXPECT_FALSE(
temp_main_frame_->DidGetCallback(FrameStatus::FRAME_ATTACHED));
EXPECT_FALSE(
temp_main_frame_->DidGetCallback(FrameStatus::FRAME_DETACHED));
// Should receive queries for OnFrameCreated, OnAfterCreated,
// OnFrameAttached.
EXPECT_EQ(temp_main_frame_->QueriesDeliveredCount(), 3);
// Temporary frames never attach.
EXPECT_EQ(temp_main_frame_->QueriesDeliveredCount(), 0);
delete temp_main_frame_;
temp_main_frame_ = nullptr;
return;
}
OrderMainTestHandler::OnFrameDetached(browser, frame);
OrderMainTestHandler::OnFrameDestroyed(browser, frame);
}
bool OnQuery(CefRefPtr<CefBrowser> browser,

View File

@ -3593,7 +3593,9 @@ class ExtraInfoNavTestHandler : public TestHandler {
CefRefPtr<CefFrame> frame,
int httpStatusCode) override {
if (popup_opened_) {
DestroyTest();
EXPECT_FALSE(got_load_end_popup_);
got_load_end_popup_.yes();
MaybeDestroyTest();
} else {
GrantPopupPermission(browser->GetHost()->GetRequestContext(),
browser->GetMainFrame()->GetURL());
@ -3641,9 +3643,12 @@ class ExtraInfoNavTestHandler : public TestHandler {
EXPECT_TRUE(args->GetBool(0));
if (popup_opened_) {
EXPECT_TRUE(args->GetBool(1));
EXPECT_FALSE(got_process_message_popup_);
got_process_message_popup_.yes();
MaybeDestroyTest();
} else {
EXPECT_FALSE(args->GetBool(1));
EXPECT_FALSE(got_process_message_main_);
got_process_message_main_.yes();
}
return true;
@ -3653,15 +3658,23 @@ class ExtraInfoNavTestHandler : public TestHandler {
return false;
}
protected:
private:
bool popup_opened_ = false;
TrackCallback got_process_message_main_;
TrackCallback got_process_message_popup_;
TrackCallback got_load_end_popup_;
void MaybeDestroyTest() {
if (got_process_message_popup_ && got_load_end_popup_) {
DestroyTest();
}
}
void DestroyTest() override {
// Verify test expectations.
EXPECT_TRUE(got_process_message_main_);
EXPECT_TRUE(got_process_message_popup_);
EXPECT_TRUE(got_load_end_popup_);
TestHandler::DestroyTest();
}