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;