WebContents should attach after navigations commit
To accommodate attaching MimeHandlerViewGuest though a proxy, the
FrameTreeNode is first navigated to about:blank. The current logic in
FrameNavigationHelper waits for the first commit to about:blank and
then continues the attach process. This current behavior has some
problems which this CL tries to resolve.
1- The helper class listens to WebContentsObserver::DidFinishNavigation
to find out when the navigation in the plugin frame commits; then
immediately attaches to the outer WebContents. This is incorrect as
attaching will synchronously destroy the RenderFrameHost which causes
issues with other observers of the navigations in that frame
(NavigationHandle::GetRenderFrameHost() becomes invalid.
2- There could be many queued up navigations to about:blank and some
of them will commit after attach which causes the same problem as 1).
To address issue 1), this CL will post task the attach to give all the
observers the chance to observe the committed navigation. To address
issue 2) new APIs are introduced to content layer which help to reset
a frame's state (navigation requests and loading state) prior to
attaching.
The CL will also enable a test which was disabled due to crashes caused
by the mentioned problems above.
Bug: 897971, 659750
Change-Id: Ie26a7ca644dcbdb1b18e5a659119ea2f46719c0a
Reviewed-on: https://chromium-review.googlesource.com/c/1298354
Commit-Queue: Ehsan Karamad <[email protected]>
Reviewed-by: James MacLean <[email protected]>
Reviewed-by: Alex Moshchuk <[email protected]>
Cr-Commit-Position: refs/heads/master@{#613683}
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index 4c3c161..4f631c7 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -2754,6 +2754,13 @@
CHECK_EQ(url_to_load, frame_entry->url());
}
+ if (auto* rfh = node->current_frame_host()) {
+ if (rfh->is_attaching_inner_delegate()) {
+ // Avoid starting any new navigations since this node is now preparing for
+ // attaching an inner delegate.
+ return nullptr;
+ }
+ }
if (!IsValidURLForNavigation(node->IsMainFrame(), virtual_url, url_to_load))
return nullptr;
@@ -2852,6 +2859,14 @@
dest_referrer = Referrer();
}
+ if (auto* rfh = frame_tree_node->current_frame_host()) {
+ if (rfh->is_attaching_inner_delegate()) {
+ // Avoid starting any new navigations since this node is now preparing for
+ // attaching an inner delegate.
+ return nullptr;
+ }
+ }
+
if (!IsValidURLForNavigation(frame_tree_node->IsMainFrame(),
entry.GetVirtualURL(), dest_url)) {
return nullptr;
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index a14f897..b26c280f 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -136,6 +136,7 @@
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/isolated_world_ids.h"
+#include "content/public/common/mime_handler_view_mode.h"
#include "content/public/common/navigation_policy.h"
#include "content/public/common/network_service_util.h"
#include "content/public/common/referrer_type_converters.h"
@@ -2798,6 +2799,16 @@
navigation_control_.FlushForTesting();
}
+bool RenderFrameHostImpl::PrepareForInnerWebContentsAttach() {
+ DCHECK(MimeHandlerViewMode::UsesCrossProcessFrame());
+ if (IsCrossProcessSubframe() || !GetParent())
+ return false;
+ ResetNavigationRequests();
+ ResetLoadingState();
+ is_attaching_inner_delegate_ = true;
+ return true;
+}
+
void RenderFrameHostImpl::OnDidAccessInitialDocument() {
delegate_->DidAccessInitialDocument();
}
@@ -3672,6 +3683,12 @@
blink::mojom::BlobURLTokenPtr blob_url_token,
mojom::NavigationClientAssociatedPtrInfo navigation_client,
blink::mojom::NavigationInitiatorPtr navigation_initiator) {
+ if (is_attaching_inner_delegate_) {
+ // Avoid starting any new navigations since this frame is in the process of
+ // attaching an inner delegate.
+ return;
+ }
+
if (!is_active())
return;
@@ -6050,7 +6067,9 @@
if (find_request != navigation_requests_.end()) {
navigation_request_ = std::move(find_request->second);
} else {
- NOTREACHED();
+ // This frame might be used for attaching an inner WebContents in which
+ // case |navigation_requests_| are deleted during attaching.
+ DCHECK(is_attaching_inner_delegate_);
}
}
// Remove the requests from the list of NavigationRequests waiting to commit.
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 3f24c3f..820f2ef 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -259,6 +259,7 @@
bool push_to_renderer_now) override;
bool IsSandboxed(blink::WebSandboxFlags flags) const override;
void FlushNetworkAndNavigationInterfacesForTesting() override;
+ bool PrepareForInnerWebContentsAttach() override;
// IPC::Sender
bool Send(IPC::Message* msg) override;
@@ -802,6 +803,10 @@
void AudioContextPlaybackStarted(int audio_context_id);
void AudioContextPlaybackStopped(int audio_context_id);
+ bool is_attaching_inner_delegate() const {
+ return is_attaching_inner_delegate_;
+ }
+
protected:
friend class RenderFrameHostFactory;
@@ -1478,6 +1483,15 @@
// document or not.
bool is_loading_;
+ // TODO(ekaramad): This flag is used for block navigations that are not using
+ // the network stack from starting and interfering with the inner WebContents
+ // attaching process. Investigate if it can be removed after moving the attach
+ // logic into content layer (https://crbug.com/911161).
+ // When true the RFH is in a transient state where it cancels all navigations
+ // and does not accept any new navigations until an inner WebContents is
+ // attached.
+ bool is_attaching_inner_delegate_ = false;
+
// The unique ID of the latest NavigationEntry that this RenderFrameHost is
// showing. This may change even when this frame hasn't committed a page,
// such as for a new subframe navigation in a different frame. Tracking this
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 6453c19e9..3a7f429 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -1766,11 +1766,26 @@
GetMainFrame()->DispatchBeforeUnload(before_unload_type, false);
}
+bool WebContentsImpl::CanAttachToOuterContentsFrame(
+ RenderFrameHost* outer_contents_frame) {
+ bool web_contents_valid = !node_.outer_web_contents() &&
+ FromRenderFrameHost(outer_contents_frame) != this;
+ bool rfh_valid = outer_contents_frame->GetParent()->GetSiteInstance() ==
+ outer_contents_frame->GetSiteInstance();
+ bool rfh_is_loading = static_cast<RenderFrameHostImpl*>(outer_contents_frame)
+ ->frame_tree_node()
+ ->IsLoading();
+ return web_contents_valid && rfh_valid && !rfh_is_loading;
+}
+
void WebContentsImpl::AttachToOuterWebContentsFrame(
std::unique_ptr<WebContents> current_web_contents,
RenderFrameHost* outer_contents_frame) {
DCHECK(!node_.outer_web_contents());
DCHECK_EQ(current_web_contents.get(), this);
+ DCHECK(CanAttachToOuterContentsFrame(outer_contents_frame));
+ auto* outer_contents_frame_impl =
+ static_cast<RenderFrameHostImpl*>(outer_contents_frame);
RenderFrameHostManager* render_manager = GetRenderManager();
@@ -1791,8 +1806,6 @@
if (!render_manager->GetRenderWidgetHostView())
CreateRenderWidgetHostViewForRenderManager(GetRenderViewHost());
- auto* outer_contents_frame_impl =
- static_cast<RenderFrameHostImpl*>(outer_contents_frame);
// Create a link to our outer WebContents.
node_.ConnectToOuterWebContents(std::move(current_web_contents),
outer_contents_frame_impl);
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 492ad274..010d9c9c 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -364,6 +364,8 @@
Visibility GetVisibility() override;
bool NeedToFireBeforeUnload() override;
void DispatchBeforeUnload(bool auto_cancel) override;
+ bool CanAttachToOuterContentsFrame(
+ RenderFrameHost* outer_contents_frame) final;
void AttachToOuterWebContentsFrame(
std::unique_ptr<WebContents> current_web_contents,
RenderFrameHost* outer_contents_frame) override;
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
index aad9d2c..aaa1d2e 100644
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -347,6 +347,15 @@
// received by the other end. For test use only.
virtual void FlushNetworkAndNavigationInterfacesForTesting() = 0;
+ // Prepares this frame for attaching an inner WebContents to its own (outer)
+ // WebContents. This includes canceling all navigation requests as well as
+ // reseting the loading state. Returns false if attaching is not possible (
+ // if this is a main frame or a cross-process subframe), or true otherwise.
+ // Note: if this is called during an ongoing navigation it is not safe to
+ // attach WebContentses immediately after returning from this function (post
+ // task to ensure all observer calls related to the navigation complete).
+ virtual bool PrepareForInnerWebContentsAttach() = 0;
+
private:
// This interface should only be implemented inside content.
friend class RenderFrameHostImpl;
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 060e2194..d869999 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -528,6 +528,14 @@
// potential discard without causing the dialog to appear.
virtual void DispatchBeforeUnload(bool auto_cancel) = 0;
+ // Returns true if it is safe for this WebContents to attach to the outer
+ // WebContents associated with |render_frame_host| using the method
+ // AttachToOuterWebContentsFrame. If a frame belongs to |this| WebContents or
+ // is in loading state or not in the same SiteInstance as its parent frame it
+ // should not be used for attaching.
+ virtual bool CanAttachToOuterContentsFrame(
+ RenderFrameHost* outer_contents_frame) = 0;
+
// Attaches |current_web_contents| to its container frame
// |outer_contents_frame|.
virtual void AttachToOuterWebContentsFrame(