Revert of Remove the is_loading_ field from WebContentsImpl (patchset #24 id:460001 of https://codereview.chromium.org/1545973002/ )

Reason for revert:
CHECK(is_loading_) appears to fail (flakily?) on  Linux ChromiumOS Tests (1). More details in the bug.

Original issue's description:
> Remove the is_loading_ field from WebContentsImpl
>
> This CL removes the is_loading_ field from WebContents, in favor of only
> tracking the loading state through the FrameTree. Currently the loading state
> is tracked in both, leading to more complexity in the code.
>
> BUG=571887, 298193
> CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:linux_site_isolation
>
> Committed: https://crrev.com/db73eb6cf1e59e753fb53e8c744620b00606ccd5
> Cr-Commit-Position: refs/heads/master@{#374651}

[email protected],[email protected],[email protected],[email protected],[email protected]
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=571887, 298193

Review URL: https://codereview.chromium.org/1690633002

Cr-Commit-Position: refs/heads/master@{#374680}
diff --git a/content/browser/frame_host/frame_tree.cc b/content/browser/frame_host/frame_tree.cc
index 2fadc12..5e8b2c1 100644
--- a/content/browser/frame_host/frame_tree.cc
+++ b/content/browser/frame_host/frame_tree.cc
@@ -80,44 +80,6 @@
 FrameTree::NodeRange::NodeRange(FrameTree* tree, FrameTreeNode* node_to_skip)
     : tree_(tree), node_to_skip_(node_to_skip) {}
 
-FrameTree::ConstNodeIterator::~ConstNodeIterator() {}
-
-FrameTree::ConstNodeIterator& FrameTree::ConstNodeIterator::operator++() {
-  for (size_t i = 0; i < current_node_->child_count(); ++i) {
-    const FrameTreeNode* child = current_node_->child_at(i);
-    queue_.push(child);
-  }
-
-  if (!queue_.empty()) {
-    current_node_ = queue_.front();
-    queue_.pop();
-  } else {
-    current_node_ = nullptr;
-  }
-
-  return *this;
-}
-
-bool FrameTree::ConstNodeIterator::operator==(
-    const ConstNodeIterator& rhs) const {
-  return current_node_ == rhs.current_node_;
-}
-
-FrameTree::ConstNodeIterator::ConstNodeIterator(
-    const FrameTreeNode* starting_node)
-    : current_node_(starting_node) {}
-
-FrameTree::ConstNodeIterator FrameTree::ConstNodeRange::begin() {
-  return ConstNodeIterator(tree_->root());
-}
-
-FrameTree::ConstNodeIterator FrameTree::ConstNodeRange::end() {
-  return ConstNodeIterator(nullptr);
-}
-
-FrameTree::ConstNodeRange::ConstNodeRange(const FrameTree* tree)
-    : tree_(tree) {}
-
 FrameTree::FrameTree(Navigator* navigator,
                      RenderFrameHostDelegate* render_frame_delegate,
                      RenderViewHostDelegate* render_view_delegate,
@@ -194,10 +156,6 @@
   return NodeRange(this, node_to_skip);
 }
 
-FrameTree::ConstNodeRange FrameTree::ConstNodes() const {
-  return ConstNodeRange(this);
-}
-
 bool FrameTree::AddFrame(
     FrameTreeNode* parent,
     int process_id,
@@ -467,8 +425,8 @@
   load_progress_ = 0.0;
 }
 
-bool FrameTree::IsLoading() const {
-  for (const FrameTreeNode* node : ConstNodes()) {
+bool FrameTree::IsLoading() {
+  for (FrameTreeNode* node : Nodes()) {
     if (node->IsLoading())
       return true;
   }
diff --git a/content/browser/frame_host/frame_tree.h b/content/browser/frame_host/frame_tree.h
index c8596d62..81282c0e 100644
--- a/content/browser/frame_host/frame_tree.h
+++ b/content/browser/frame_host/frame_tree.h
@@ -41,7 +41,6 @@
 class CONTENT_EXPORT FrameTree {
  public:
   class NodeRange;
-  class ConstNodeRange;
 
   class CONTENT_EXPORT NodeIterator {
    public:
@@ -78,41 +77,6 @@
     FrameTreeNode* const node_to_skip_;
   };
 
-  class CONTENT_EXPORT ConstNodeIterator {
-   public:
-    ~ConstNodeIterator();
-
-    ConstNodeIterator& operator++();
-
-    bool operator==(const ConstNodeIterator& rhs) const;
-    bool operator!=(const ConstNodeIterator& rhs) const {
-      return !(*this == rhs);
-    }
-
-    const FrameTreeNode* operator*() { return current_node_; }
-
-   private:
-    friend class ConstNodeRange;
-
-    ConstNodeIterator(const FrameTreeNode* starting_node);
-
-    const FrameTreeNode* current_node_;
-    std::queue<const FrameTreeNode*> queue_;
-  };
-
-  class CONTENT_EXPORT ConstNodeRange {
-   public:
-    ConstNodeIterator begin();
-    ConstNodeIterator end();
-
-   private:
-    friend class FrameTree;
-
-    ConstNodeRange(const FrameTree* tree);
-
-    const FrameTree* const tree_;
-  };
-
   // Each FrameTreeNode will default to using the given |navigator| for
   // navigation tasks in the frame.
   // A set of delegates are remembered here so that we can create
@@ -145,10 +109,6 @@
   // breadth-first traversal order.
   NodeRange Nodes();
 
-  // Returns a range to iterate over all FrameTreeNodes in the frame tree in
-  // breadth-first traversal order. All FrameTreeNodes returned will be const.
-  ConstNodeRange ConstNodes() const;
-
   // Adds a new child frame to the frame tree. |process_id| is required to
   // disambiguate |new_routing_id|, and it must match the process of the
   // |parent| node. Otherwise no child is added and this method returns false.
@@ -227,7 +187,7 @@
   void ResetLoadProgress();
 
   // Returns true if at least one of the nodes in this FrameTree is loading.
-  bool IsLoading() const;
+  bool IsLoading();
 
   // Set page-level focus in all SiteInstances involved in rendering
   // this FrameTree, not including the current main frame's
diff --git a/content/browser/frame_host/frame_tree_node.cc b/content/browser/frame_host/frame_tree_node.cc
index b8415a7..cccb3ff1 100644
--- a/content/browser/frame_host/frame_tree_node.cc
+++ b/content/browser/frame_host/frame_tree_node.cc
@@ -287,26 +287,24 @@
     scoped_ptr<NavigationRequest> navigation_request) {
   CHECK(IsBrowserSideNavigationEnabled());
 
-  bool was_previously_loading = frame_tree()->IsLoading();
-
   // There's no need to reset the state: there's still an ongoing load, and the
   // RenderFrameHostManager will take care of updates to the speculative
   // RenderFrameHost in DidCreateNavigationRequest below.
-  if (was_previously_loading)
-    ResetNavigationRequest(true);
-
-  navigation_request_ = std::move(navigation_request);
-  render_manager()->DidCreateNavigationRequest(*navigation_request_);
+  ResetNavigationRequest(true);
 
   // Force the throbber to start to keep it in sync with what is happening in
   // the UI. Blink doesn't send throb notifications for JavaScript URLs, so it
   // is not done here either.
-  if (!navigation_request_->common_params().url.SchemeIs(
+  if (!navigation_request->common_params().url.SchemeIs(
           url::kJavaScriptScheme)) {
     // TODO(fdegans): Check if this is a same-document navigation and set the
     // proper argument.
-    DidStartLoading(true, was_previously_loading);
+    DidStartLoading(true);
   }
+
+  navigation_request_ = std::move(navigation_request);
+
+  render_manager()->DidCreateNavigationRequest(*navigation_request_);
 }
 
 void FrameTreeNode::ResetNavigationRequest(bool keep_state) {
@@ -332,8 +330,7 @@
   loading_progress_ = kLoadingProgressNotStarted;
 }
 
-void FrameTreeNode::DidStartLoading(bool to_different_document,
-                                    bool was_previously_loading) {
+void FrameTreeNode::DidStartLoading(bool to_different_document) {
   // Any main frame load to a new document should reset the load progress since
   // it will replace the current page and any frames. The WebContents will
   // be notified when DidChangeLoadProgress is called.
@@ -341,7 +338,7 @@
     frame_tree_->ResetLoadProgress();
 
   // Notify the WebContents.
-  if (!was_previously_loading)
+  if (!frame_tree_->IsLoading())
     navigator()->GetDelegate()->DidStartLoading(this, to_different_document);
 
   // Set initial load progress and update overall progress. This will notify
@@ -408,26 +405,4 @@
   FOR_EACH_OBSERVER(Observer, observers_, OnFrameTreeNodeFocused(this));
 }
 
-void FrameTreeNode::BeforeUnloadCanceled() {
-  if (!IsMainFrame())
-    return;
-
-  RenderFrameHostImpl* current_frame_host =
-      render_manager_.current_frame_host();
-  DCHECK(current_frame_host);
-  current_frame_host->ResetLoadingState();
-
-  if (IsBrowserSideNavigationEnabled()) {
-    RenderFrameHostImpl* speculative_frame_host =
-        render_manager_.speculative_frame_host();
-    if (speculative_frame_host)
-      speculative_frame_host->ResetLoadingState();
-  } else {
-    RenderFrameHostImpl* pending_frame_host =
-        render_manager_.pending_frame_host();
-    if (pending_frame_host)
-      pending_frame_host->ResetLoadingState();
-  }
-}
-
 }  // namespace content
diff --git a/content/browser/frame_host/frame_tree_node.h b/content/browser/frame_host/frame_tree_node.h
index 4ea61235..eac4977 100644
--- a/content/browser/frame_host/frame_tree_node.h
+++ b/content/browser/frame_host/frame_tree_node.h
@@ -235,11 +235,7 @@
   // A RenderFrameHost in this node started loading.
   // |to_different_document| will be true unless the load is a fragment
   // navigation, or triggered by history.pushState/replaceState.
-  // |was_previously_loading| is false if the FrameTree was not loading before.
-  // The caller is required to provide this boolean as the delegate should only
-  // be called if the FrameTree went from non-loading to loading state.
-  // However, when it is called, the FrameTree should be in a loading state.
-  void DidStartLoading(bool to_different_document, bool was_previously_loading);
+  void DidStartLoading(bool to_different_document);
 
   // A RenderFrameHost in this node stopped loading.
   void DidStopLoading();
@@ -261,10 +257,6 @@
   // time and notifies observers.
   void DidFocus();
 
-  // Called when the user closed the modal dialogue for BeforeUnload and
-  // cancelled the navigation. This should stop the loads.
-  void BeforeUnloadCanceled();
-
  private:
   class OpenerDestroyedObserver;
 
diff --git a/content/browser/frame_host/interstitial_page_impl.cc b/content/browser/frame_host/interstitial_page_impl.cc
index 962f5f3..1ad4907b 100644
--- a/content/browser/frame_host/interstitial_page_impl.cc
+++ b/content/browser/frame_host/interstitial_page_impl.cc
@@ -164,6 +164,7 @@
       original_child_id_(web_contents->GetRenderProcessHost()->GetID()),
       original_rvh_id_(web_contents->GetRenderViewHost()->GetRoutingID()),
       should_revert_web_contents_title_(false),
+      web_contents_was_loading_(false),
       resource_dispatcher_host_notified_(false),
       rvh_delegate_view_(new InterstitialPageRVHDelegateView(this)),
       create_view_(true),
@@ -517,6 +518,14 @@
     // Hide the original RVH since we're showing the interstitial instead.
     rwh_view->Hide();
   }
+
+  // Notify the tab we are not loading so the throbber is stopped. It also
+  // causes a WebContentsObserver::DidStopLoading callback that the
+  // AutomationProvider (used by the UI tests) expects to consider a navigation
+  // as complete. Without this, navigating in a UI test to a URL that triggers
+  // an interstitial would hang.
+  web_contents_was_loading_ = controller_->delegate()->IsLoading();
+  controller_->delegate()->SetIsLoading(false, true, NULL);
 }
 
 RendererPreferences InterstitialPageImpl::GetRendererPrefs(
@@ -625,7 +634,9 @@
   Disable();
   action_taken_ = PROCEED_ACTION;
 
-  controller_->delegate()->DidProceedOnInterstitial();
+  // Resumes the throbber, if applicable.
+  if (web_contents_was_loading_)
+    controller_->delegate()->SetIsLoading(true, true, NULL);
 
   // If this is a new navigation, the old page is going away, so we cancel any
   // blocked requests for it.  If it is not a new navigation, then it means the
diff --git a/content/browser/frame_host/interstitial_page_impl.h b/content/browser/frame_host/interstitial_page_impl.h
index c03792d..32361e8 100644
--- a/content/browser/frame_host/interstitial_page_impl.h
+++ b/content/browser/frame_host/interstitial_page_impl.h
@@ -271,6 +271,10 @@
   // revert it to its original value).
   bool should_revert_web_contents_title_;
 
+  // Whether or not the contents was loading resources when the interstitial was
+  // shown.  We restore this state if the user proceeds from the interstitial.
+  bool web_contents_was_loading_;
+
   // Whether the ResourceDispatcherHost has been notified to cancel/resume the
   // resource requests blocked for the RenderViewHost.
   bool resource_dispatcher_host_notified_;
diff --git a/content/browser/frame_host/navigation_controller_delegate.h b/content/browser/frame_host/navigation_controller_delegate.h
index 8a20e2b..9922ac5 100644
--- a/content/browser/frame_host/navigation_controller_delegate.h
+++ b/content/browser/frame_host/navigation_controller_delegate.h
@@ -42,6 +42,7 @@
   virtual void Stop() = 0;
   virtual int32_t GetMaxPageID() = 0;
   virtual int32_t GetMaxPageIDForSiteInstance(SiteInstance* site_instance) = 0;
+  virtual bool IsLoading() const = 0;
   virtual bool IsBeingDestroyed() const = 0;
   virtual bool CanOverscrollContent() const = 0;
 
@@ -70,8 +71,10 @@
       RenderFrameHost* render_frame_host) = 0;
   virtual void AttachInterstitialPage(
       InterstitialPageImpl* interstitial_page) = 0;
-  virtual void DidProceedOnInterstitial() = 0;
   virtual void DetachInterstitialPage() = 0;
+  virtual void SetIsLoading(bool is_loading,
+                            bool to_different_document,
+                            LoadNotificationDetails* details) = 0;
 };
 
 }  // namespace content
diff --git a/content/browser/frame_host/navigation_controller_impl_unittest.cc b/content/browser/frame_host/navigation_controller_impl_unittest.cc
index 4065902..e0f6144 100644
--- a/content/browser/frame_host/navigation_controller_impl_unittest.cc
+++ b/content/browser/frame_host/navigation_controller_impl_unittest.cc
@@ -3467,19 +3467,14 @@
 
   // Suppose it aborts before committing, if it's a 204 or download or due to a
   // stop or a new navigation from the user.  The URL should remain visible.
-  if (IsBrowserSideNavigationEnabled()) {
-    static_cast<NavigatorImpl*>(main_test_rfh()->frame_tree_node()->navigator())
-        ->CancelNavigation(main_test_rfh()->frame_tree_node());
-  } else {
-    FrameHostMsg_DidFailProvisionalLoadWithError_Params params;
-    params.error_code = net::ERR_ABORTED;
-    params.error_description = base::string16();
-    params.url = url;
-    params.showing_repost_interstitial = false;
-    main_test_rfh()->OnMessageReceived(
-        FrameHostMsg_DidFailProvisionalLoadWithError(0, params));
-    main_test_rfh()->OnMessageReceived(FrameHostMsg_DidStopLoading(0));
-  }
+  FrameHostMsg_DidFailProvisionalLoadWithError_Params params;
+  params.error_code = net::ERR_ABORTED;
+  params.error_description = base::string16();
+  params.url = url;
+  params.showing_repost_interstitial = false;
+  main_test_rfh()->OnMessageReceived(
+      FrameHostMsg_DidFailProvisionalLoadWithError(0, params));
+  contents()->SetIsLoading(false, true, NULL);
   EXPECT_EQ(url, controller.GetVisibleEntry()->GetURL());
 
   // If something else later modifies the contents of the about:blank page, then
diff --git a/content/browser/frame_host/navigator_impl.cc b/content/browser/frame_host/navigator_impl.cc
index 9e67f2d..7457ed9d 100644
--- a/content/browser/frame_host/navigator_impl.cc
+++ b/content/browser/frame_host/navigator_impl.cc
@@ -338,24 +338,14 @@
   // Double check that here.
   CheckWebUIRendererDoesNotDisplayNormalURL(dest_render_frame_host, dest_url);
 
-  // In the case of a transfer navigation, set the destination RenderFrameHost
-  // as loading.  This ensures that the RenderFrameHost gets in a loading state
-  // without emitting a spurrious DidStartLoading notification at the
-  // FrameTreeNode level (since the FrameTreeNode was already loading). Note
-  // that this works both for a transfer to a different RenderFrameHost and in
-  // the rare case where the navigation is transferred back to the same
-  // RenderFrameHost.
-  bool is_transfer = entry.transferred_global_request_id().child_id != -1;
-  if (is_transfer)
-    dest_render_frame_host->set_is_loading(true);
-
   // Navigate in the desired RenderFrameHost.
   // We can skip this step in the rare case that this is a transfer navigation
   // which began in the chosen RenderFrameHost, since the request has already
   // been issued.  In that case, simply resume the response.
-  bool is_transfer_to_same = is_transfer &&
-                             entry.transferred_global_request_id().child_id ==
-                                 dest_render_frame_host->GetProcess()->GetID();
+  bool is_transfer_to_same =
+      entry.transferred_global_request_id().child_id != -1 &&
+      entry.transferred_global_request_id().child_id ==
+          dest_render_frame_host->GetProcess()->GetID();
   if (!is_transfer_to_same) {
     navigation_data_.reset(new NavigationMetricsData(navigation_start, dest_url,
                                                      entry.restore_type()));
@@ -381,7 +371,6 @@
             controller_->GetEntryCount()));
   } else {
     // No need to navigate again.  Just resume the deferred request.
-    // Also sets the RenderFrameHost back to a loading state again.
     dest_render_frame_host->GetProcess()->ResumeDeferredNavigation(
         entry.transferred_global_request_id());
   }
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 4b2a6f6..e8a8038 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -214,7 +214,6 @@
   g_routing_id_frame_map.Get().insert(std::make_pair(
       RenderFrameHostID(GetProcess()->GetID(), routing_id_),
       this));
-  site_instance_->AddObserver(this);
 
   if (is_swapped_out) {
     rfh_state_ = STATE_SWAPPED_OUT;
@@ -263,8 +262,6 @@
   g_routing_id_frame_map.Get().erase(
       RenderFrameHostID(GetProcess()->GetID(), routing_id_));
 
-  site_instance_->RemoveObserver(this);
-
   if (delegate_ && render_frame_created_)
     delegate_->RenderFrameDeleted(this);
 
@@ -520,6 +517,7 @@
                         OnDidFailLoadWithError)
     IPC_MESSAGE_HANDLER_GENERIC(FrameHostMsg_DidCommitProvisionalLoad,
                                 OnDidCommitProvisionalLoad(msg))
+    IPC_MESSAGE_HANDLER(FrameHostMsg_DidDropNavigation, OnDidDropNavigation)
     IPC_MESSAGE_HANDLER(FrameHostMsg_UpdateState, OnUpdateState)
     IPC_MESSAGE_HANDLER(FrameHostMsg_OpenURL, OnOpenURL)
     IPC_MESSAGE_HANDLER(FrameHostMsg_DocumentOnLoadCompleted,
@@ -692,13 +690,6 @@
   return NULL;
 }
 
-void RenderFrameHostImpl::RenderProcessGone(SiteInstanceImpl* site_instance) {
-  DCHECK(site_instance == site_instance_.get());
-
-  // The render process is gone, this frame can no longer be loading.
-  ResetLoadingState();
-}
-
 bool RenderFrameHostImpl::CreateRenderFrame(int proxy_routing_id,
                                             int opener_routing_id,
                                             int parent_routing_id,
@@ -1051,6 +1042,15 @@
     pending_commit_ = false;
 }
 
+void RenderFrameHostImpl::OnDidDropNavigation() {
+  // At the end of Navigate(), the FrameTreeNode's DidStartLoading is called to
+  // force the spinner to start, even if the renderer didn't yet begin the load.
+  // If it turns out that the renderer dropped the navigation, the spinner needs
+  // to be turned off.
+  frame_tree_node_->DidStopLoading();
+  navigation_handle_.reset();
+}
+
 void RenderFrameHostImpl::OnUpdateState(const PageState& state) {
   // TODO(creis): Verify the state's ISN matches the last committed FNE.
 
@@ -1316,8 +1316,6 @@
     return;
   }
 
-  ResetLoadingState();
-
   // If this RFH wasn't pending deletion, then it is now swapped out.
   SetState(RenderFrameHostImpl::STATE_SWAPPED_OUT);
 }
@@ -1703,10 +1701,23 @@
 }
 
 void RenderFrameHostImpl::OnDidStartLoading(bool to_different_document) {
-  bool was_previously_loading = frame_tree_node_->frame_tree()->IsLoading();
+  // Any main frame load to a new document should reset the load since it will
+  // replace the current page and any frames.
+  if (to_different_document && !GetParent())
+    is_loading_ = false;
+
+  // This method should never be called when the frame is loading.
+  // Unfortunately, it can happen if a history navigation happens during a
+  // BeforeUnload or Unload event.
+  // TODO(fdegans): Change this to a DCHECK after LoadEventProgress has been
+  // refactored in Blink. See crbug.com/466089
+  if (is_loading_) {
+    LOG(WARNING) << "OnDidStartLoading was called twice.";
+    return;
+  }
+
+  frame_tree_node_->DidStartLoading(to_different_document);
   is_loading_ = true;
-  frame_tree_node_->DidStartLoading(to_different_document,
-                                    was_previously_loading);
 }
 
 void RenderFrameHostImpl::OnDidStopLoading() {
@@ -1902,7 +1913,7 @@
   // Blink doesn't send throb notifications for JavaScript URLs, so it is not
   // done here either.
   if (!common_params.url.SchemeIs(url::kJavaScriptScheme))
-    OnDidStartLoading(true);
+    frame_tree_node_->DidStartLoading(true);
 }
 
 void RenderFrameHostImpl::NavigateToInterstitialURL(const GURL& data_url) {
@@ -2241,18 +2252,6 @@
   return mojo_image_downloader_;
 }
 
-void RenderFrameHostImpl::ResetLoadingState() {
-  if (is_loading()) {
-    // When pending deletion, just set the loading state to not loading.
-    // Otherwise, OnDidStopLoading will take care of that, as well as sending
-    // notification to the FrameTreeNode about the change in loading state.
-    if (rfh_state_ != STATE_DEFAULT)
-      is_loading_ = false;
-    else
-      OnDidStopLoading();
-  }
-}
-
 bool RenderFrameHostImpl::IsSameSiteInstance(
     RenderFrameHostImpl* other_render_frame_host) {
   // As a sanity check, make sure the frame belongs to the same BrowserContext.
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index a4ba3a46..9767a4a 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -96,9 +96,9 @@
   CREATE_RF_HIDDEN = 1 << 1,
 };
 
-class CONTENT_EXPORT RenderFrameHostImpl : public RenderFrameHost,
-                                           public BrowserAccessibilityDelegate,
-                                           public SiteInstanceImpl::Observer {
+class CONTENT_EXPORT RenderFrameHostImpl
+    : public RenderFrameHost,
+      public BrowserAccessibilityDelegate {
  public:
   using AXTreeSnapshotCallback =
       base::Callback<void(
@@ -207,9 +207,6 @@
   gfx::AcceleratedWidget AccessibilityGetAcceleratedWidget() override;
   gfx::NativeViewAccessible AccessibilityGetNativeViewAccessible() override;
 
-  // SiteInstanceImpl::Observer
-  void RenderProcessGone(SiteInstanceImpl* site_instance) override;
-
   // Creates a RenderFrame in the renderer process.
   bool CreateRenderFrame(int proxy_routing_id,
                          int opener_routing_id,
@@ -250,12 +247,6 @@
   // call FrameTreeNode::IsLoading.
   bool is_loading() const { return is_loading_; }
 
-  // Sets this RenderFrameHost loading state. This is only used in the case of
-  // transfer navigations, where no DidStart/DidStopLoading notifications
-  // should be sent during the transfer.
-  // TODO(clamy): Remove this once PlzNavigate ships.
-  void set_is_loading(bool is_loading) { is_loading_ = is_loading; }
-
   // This returns the RenderFrameHost's owned RenderWidgetHost if it has one,
   // or else it returns nullptr.
   // If the RenderFrameHost is the page's main frame, this returns instead a
@@ -536,10 +527,6 @@
   // Returns the Mojo ImageDownloader service.
   const image_downloader::ImageDownloaderPtr& GetMojoImageDownloader();
 
-  // Resets the loading state. Following this call, the RenderFrameHost will be
-  // in a non-loading state.
-  void ResetLoadingState();
-
  protected:
   friend class RenderFrameHostFactory;
 
@@ -585,6 +572,7 @@
       const base::string16& error_description,
       bool was_ignored_by_handler);
   void OnDidCommitProvisionalLoad(const IPC::Message& msg);
+  void OnDidDropNavigation();
   void OnUpdateState(const PageState& state);
   void OnBeforeUnloadACK(
       bool proceed,
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index 766e968..cd1684b 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -437,7 +437,6 @@
   // should probably cancel the request in that case.
   DCHECK(pending_render_frame_host == pending_render_frame_host_.get() ||
          pending_render_frame_host == render_frame_host_.get());
-  DCHECK(frame_tree_node_->IsLoading());
 
   // Store the transferring request so that we can release it if the transfer
   // navigation matches.
@@ -449,11 +448,6 @@
       pending_render_frame_host->PassNavigationHandleOwnership();
   DCHECK(transfer_navigation_handle_);
 
-  // Set the transferring RenderFrameHost as not loading, so that it does not
-  // emit a DidStopLoading notification if it is destroyed when creating the
-  // new navigating RenderFrameHost.
-  pending_render_frame_host->set_is_loading(false);
-
   // Sanity check that the params are for the correct frame and process.
   // These should match the RenderFrameHost that made the request.
   // If it started as a cross-process navigation via OpenURL, this is the
@@ -486,11 +480,6 @@
   // If the navigation continued, the NavigationHandle should have been
   // transfered to a RenderFrameHost. In the other cases, it should be cleared.
   transfer_navigation_handle_.reset();
-
-  // If the navigation in the new renderer did not start, inform the
-  // FrameTreeNode that it stopped loading.
-  if (!frame_tree_node_->IsLoading())
-    frame_tree_node_->DidStopLoading();
 }
 
 void RenderFrameHostManager::DidNavigateFrame(
@@ -923,12 +912,8 @@
 void RenderFrameHostManager::CleanUpNavigation() {
   CHECK(IsBrowserSideNavigationEnabled());
   render_frame_host_->ClearPendingWebUI();
-  if (speculative_render_frame_host_) {
-    bool was_loading = speculative_render_frame_host_->is_loading();
+  if (speculative_render_frame_host_)
     DiscardUnusedFrame(UnsetSpeculativeRenderFrameHost());
-    if (was_loading)
-      frame_tree_node_->DidStopLoading();
-  }
 }
 
 // PlzNavigate
@@ -2017,7 +2002,6 @@
     // now to make sure the sad tab shows up, etc.
     DCHECK(!render_frame_host_->IsRenderFrameLive());
     DCHECK(!render_frame_host_->render_view_host()->IsRenderViewLive());
-    render_frame_host_->ResetLoadingState();
     delegate_->RenderProcessGoneFromRenderManager(
         render_frame_host_->render_view_host());
   }
@@ -2269,11 +2253,7 @@
   TRACE_EVENT1("navigation", "RenderFrameHostManager::CancelPending",
                "FrameTreeNode id", frame_tree_node_->frame_tree_node_id());
   render_frame_host_->ClearPendingWebUI();
-
-  bool pending_was_loading = pending_render_frame_host_->is_loading();
   DiscardUnusedFrame(UnsetPendingRenderFrameHost());
-  if (pending_was_loading)
-    frame_tree_node_->DidStopLoading();
 }
 
 scoped_ptr<RenderFrameHostImpl>
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h
index 81967cf..b54de7f 100644
--- a/content/browser/site_instance_impl.h
+++ b/content/browser/site_instance_impl.h
@@ -23,11 +23,11 @@
 class CONTENT_EXPORT SiteInstanceImpl : public SiteInstance,
                                         public RenderProcessHostObserver {
  public:
-  class CONTENT_EXPORT Observer {
+  class Observer {
    public:
     // Called when this SiteInstance transitions to having no active frames,
     // as measured by active_frame_count().
-    virtual void ActiveFrameCountIsZero(SiteInstanceImpl* site_instance) {}
+    virtual void ActiveFrameCountIsZero(SiteInstanceImpl* site_instance) = 0;
 
     // Called when the renderer process of this SiteInstance has exited.
     virtual void RenderProcessGone(SiteInstanceImpl* site_instance) = 0;
diff --git a/content/browser/web_contents/aura/overscroll_navigation_overlay_unittest.cc b/content/browser/web_contents/aura/overscroll_navigation_overlay_unittest.cc
index 25694d7..4649e8c6 100644
--- a/content/browser/web_contents/aura/overscroll_navigation_overlay_unittest.cc
+++ b/content/browser/web_contents/aura/overscroll_navigation_overlay_unittest.cc
@@ -307,6 +307,7 @@
   // this is a "safety net" in case we mis-identify the destination webpage
   // (which can happen if a new navigation is performed while while a GestureNav
   // navigation is in progress).
+  contents()->TestSetIsLoading(true);
   contents()->TestSetIsLoading(false);
   EXPECT_FALSE(GetOverlay()->web_contents());
   NavigationEntry* pending = contents()->GetController().GetPendingEntry();
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 20db239..d4f6bec 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -338,6 +338,7 @@
                   this,
                   this,
                   this),
+      is_loading_(false),
       is_load_to_different_document_(false),
       crashed_status_(base::TERMINATION_STATUS_STILL_RUNNING),
       crashed_error_code_(0),
@@ -346,7 +347,6 @@
       upload_size_(0),
       upload_position_(0),
       is_resume_pending_(false),
-      paused_throbber_for_interstitial_(false),
       displayed_insecure_content_(false),
       has_accessed_initial_document_(false),
       theme_color_(SK_ColorTRANSPARENT),
@@ -371,8 +371,8 @@
       force_disable_overscroll_content_(false),
       last_dialog_suppressed_(false),
       geolocation_service_context_(new GeolocationServiceContext()),
-      accessibility_mode_(BrowserAccessibilityStateImpl::GetInstance()
-                              ->accessibility_mode()),
+      accessibility_mode_(
+          BrowserAccessibilityStateImpl::GetInstance()->accessibility_mode()),
       audio_stream_monitor_(this),
       virtual_keyboard_requested_(false),
       page_scale_factor_is_one_(true),
@@ -896,7 +896,7 @@
   // Reload the page if a load is currently in progress to avoid having
   // different parts of the page loaded using different user agents.
   NavigationEntry* entry = controller_.GetVisibleEntry();
-  if (IsLoading() && entry != NULL && entry->GetIsOverridingUserAgent())
+  if (is_loading_ && entry != NULL && entry->GetIsOverridingUserAgent())
     controller_.ReloadIgnoringCache(true);
 
   FOR_EACH_OBSERVER(WebContentsObserver, observers_,
@@ -1036,11 +1036,11 @@
 }
 
 bool WebContentsImpl::IsLoading() const {
-  return frame_tree_.IsLoading() && !paused_throbber_for_interstitial_;
+  return is_loading_;
 }
 
 bool WebContentsImpl::IsLoadingToDifferentDocument() const {
-  return IsLoading() && is_load_to_different_document_;
+  return is_loading_ && is_load_to_different_document_;
 }
 
 bool WebContentsImpl::IsWaitingForResponse() const {
@@ -2309,18 +2309,6 @@
 
   FOR_EACH_OBSERVER(WebContentsObserver, observers_,
                     DidAttachInterstitialPage());
-
-  // Stop the throbber if needed while the interstitial page is shown.
-  if (IsLoading())
-    LoadingStateChanged(false, true, true, nullptr);
-}
-
-void WebContentsImpl::DidProceedOnInterstitial() {
-  // Restart the throbber now that the interstitial page is going away.
-  if (paused_throbber_for_interstitial_) {
-    if (frame_tree_.IsLoading())
-      LoadingStateChanged(true, true, false, nullptr);
-  }
 }
 
 void WebContentsImpl::DetachInterstitialPage() {
@@ -2328,11 +2316,6 @@
     GetRenderManager()->remove_interstitial_page();
   FOR_EACH_OBSERVER(WebContentsObserver, observers_,
                     DidDetachInterstitialPage());
-  // Restart the throbber now that the interstitial page is going away.
-  if (paused_throbber_for_interstitial_) {
-    if (frame_tree_.IsLoading())
-      LoadingStateChanged(true, true, false, nullptr);
-  }
 }
 
 void WebContentsImpl::SetHistoryOffsetAndLength(int history_offset,
@@ -3605,17 +3588,12 @@
 
 // Notifies the RenderWidgetHost instance about the fact that the page is
 // loading, or done loading.
-void WebContentsImpl::LoadingStateChanged(bool is_loading,
-                                          bool to_different_document,
-                                          bool pause_throbber_for_interstitial,
-                                          LoadNotificationDetails* details) {
-  // Do not send notifications about loading while the interstitial is showing.
-  if (paused_throbber_for_interstitial_ && pause_throbber_for_interstitial)
+void WebContentsImpl::SetIsLoading(bool is_loading,
+                                   bool to_different_document,
+                                   LoadNotificationDetails* details) {
+  if (is_loading == is_loading_)
     return;
 
-  // Update whether the interstitial state.
-  paused_throbber_for_interstitial_ = pause_throbber_for_interstitial;
-
   if (!is_loading) {
     load_state_ = net::LoadStateWithParam(net::LOAD_STATE_IDLE,
                                           base::string16());
@@ -3626,6 +3604,7 @@
 
   GetRenderManager()->SetIsLoading(is_loading);
 
+  is_loading_ = is_loading;
   waiting_for_response_ = is_loading;
   is_load_to_different_document_ = to_different_document;
 
@@ -3978,13 +3957,15 @@
   if (delegate_)
     delegate_->HideValidationMessage(this);
 
+  SetIsLoading(false, true, nullptr);
+  NotifyDisconnected();
+  SetIsCrashed(status, error_code);
+
   // Reset the loading progress. TODO(avi): What does it mean to have a
   // "renderer crash" when there is more than one renderer process serving a
   // webpage? Once this function is called at a more granular frame level, we
   // probably will need to more granularly reset the state here.
   ResetLoadProgressState();
-  NotifyDisconnected();
-  SetIsCrashed(status, error_code);
 
   FOR_EACH_OBSERVER(WebContentsObserver,
                     observers_,
@@ -4078,8 +4059,7 @@
 
 void WebContentsImpl::DidStartLoading(FrameTreeNode* frame_tree_node,
                                       bool to_different_document) {
-  LoadingStateChanged(true, to_different_document,
-                      paused_throbber_for_interstitial_, nullptr);
+  SetIsLoading(true, to_different_document, nullptr);
 
   // Notify accessibility that the user is navigating away from the
   // current document.
@@ -4113,8 +4093,7 @@
         controller_.GetCurrentEntryIndex()));
   }
 
-  LoadingStateChanged(false, true, paused_throbber_for_interstitial_,
-                      details.get());
+  SetIsLoading(false, true, details.get());
 }
 
 void WebContentsImpl::DidChangeLoadProgress() {
@@ -4651,8 +4630,10 @@
   last_dialog_suppressed_ = dialog_was_suppressed;
 
   if (is_showing_before_unload_dialog_ && !success) {
+    // If a beforeunload dialog is canceled, we need to stop the throbber from
+    // spinning, since we forced it to start spinning in Navigate.
     if (rfh)
-      rfh->frame_tree_node()->BeforeUnloadCanceled();
+      DidStopLoading();
     controller_.DiscardNonCommittedEntries();
 
     FOR_EACH_OBSERVER(WebContentsObserver, observers_,
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 6fdc6877..6a5ba60 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -706,8 +706,12 @@
   // Unsets the currently showing interstitial.
   void DetachInterstitialPage() override;
 
-  // Unpause the throbber if it was paused.
-  void DidProceedOnInterstitial() override;
+  // Changes the IsLoading state and notifies the delegate as needed.
+  // |details| is used to provide details on the load that just finished
+  // (but can be null if not applicable).
+  void SetIsLoading(bool is_loading,
+                    bool to_different_document,
+                    LoadNotificationDetails* details) override;
 
   typedef base::Callback<void(WebContents*)> CreatedCallback;
 
@@ -1012,16 +1016,6 @@
   // the main frame if empty).
   WebUI* CreateWebUI(const GURL& url, const std::string& frame_name);
 
-  // Notifies the delegate of a change in loading state.
-  // |details| is used to provide details on the load that just finished
-  // (but can be null if not applicable).
-  // |pause_throbber_for_interstitial_| will be used to update
-  // pause_throbber_for_interstitial_.
-  void LoadingStateChanged(bool is_loading,
-                           bool to_different_document,
-                           bool pause_throbber_for_interstitial,
-                           LoadNotificationDetails* details);
-
   // Data for core operation ---------------------------------------------------
 
   // Delegate for notifying our owner about stuff. Not owned by us.
@@ -1079,6 +1073,9 @@
 
   // Data for loading state ----------------------------------------------------
 
+  // Indicates whether we're currently loading a resource.
+  bool is_loading_;
+
   // Indicates whether the current load is to a different document. Only valid
   // if is_loading_ is true.
   bool is_load_to_different_document_;
@@ -1112,10 +1109,6 @@
   // See ResumeLoadingCreatedWebContents.
   bool is_resume_pending_;
 
-  // Whether the throbber is suspended while an interstial page is showing.
-  // This is set to false when the user proceeds in the interstitial.
-  bool paused_throbber_for_interstitial_;
-
   // Data for current page -----------------------------------------------------
 
   // When a title cannot be taken from any entry, this title will be used.
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index e6c5bb89..532cf01 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -108,13 +108,8 @@
       const LoadCommittedDetails& load_details) override {
     if (!done_) {
       done_ = true;
-      shell_->LoadURL(url_);
-
-      // There should be a pending entry.
-      CHECK(shell_->web_contents()->GetController().GetPendingEntry());
-
-      // Now that there is a pending entry, stop the load.
       shell_->Stop();
+      shell_->LoadURL(url_);
     }
   }
 
@@ -194,8 +189,16 @@
   int loadingStateToDifferentDocumentCount_;
 };
 
+// See: http://crbug.com/298193
+#if defined(OS_WIN) || defined(OS_LINUX)
+#define MAYBE_DidStopLoadingDetails DISABLED_DidStopLoadingDetails
+#else
+#define MAYBE_DidStopLoadingDetails DidStopLoadingDetails
+#endif
+
 // Test that DidStopLoading includes the correct URL in the details.
-IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, DidStopLoadingDetails) {
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
+                       MAYBE_DidStopLoadingDetails) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
   LoadStopNotificationObserver load_observer(
@@ -209,15 +212,20 @@
             load_observer.controller_);
 }
 
+// See: http://crbug.com/298193
+#if defined(OS_WIN) || defined(OS_LINUX)
+#define MAYBE_DidStopLoadingDetailsWithPending \
+  DISABLED_DidStopLoadingDetailsWithPending
+#else
+#define MAYBE_DidStopLoadingDetailsWithPending DidStopLoadingDetailsWithPending
+#endif
+
 // Test that DidStopLoading includes the correct URL in the details when a
 // pending entry is present.
 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
-                       DidStopLoadingDetailsWithPending) {
+                       MAYBE_DidStopLoadingDetailsWithPending) {
   ASSERT_TRUE(embedded_test_server()->Start());
-  // TODO(clamy): Add a cross-process navigation case as well once
-  // crbug.com/581024 is fixed.
-  GURL url1 = embedded_test_server()->GetURL("/title1.html");
-  GURL url2 = embedded_test_server()->GetURL("/title2.html");
+  GURL url("data:text/html,<div>test</div>");
 
   // Listen for the first load to stop.
   LoadStopNotificationObserver load_observer(
@@ -226,11 +234,11 @@
   // We will hear a DidStopLoading from the first load as the new load
   // is started.
   NavigateOnCommitObserver commit_observer(
-      shell(), url2);
-  NavigateToURL(shell(), url1);
+      shell(), embedded_test_server()->GetURL("/title2.html"));
+  NavigateToURL(shell(), url);
   load_observer.Wait();
 
-  EXPECT_EQ(url1, load_observer.url_);
+  EXPECT_EQ(url, load_observer.url_);
   EXPECT_EQ(0, load_observer.session_index_);
   EXPECT_EQ(&shell()->web_contents()->GetController(),
             load_observer.controller_);