Rewrite flaky CancelBeforeUnloadResetsURL test and update fix.

We can invalidate the URL in the address bar without waiting for the
beforeunload ACK from the renderer, making the test easier to write.
We're keeping the invalidation in DidCancelLoading after the ACK as
well, since it's not clear whether that's safe to remove.

Also, the test can be implemented in content/ rather than chrome/.

BUG=739773
TEST=No behavior change, new test stays green.

Cq-Include-Trybots: master.tryserver.chromium.linux:linux_site_isolation
Change-Id: I66450965b5a8ac517211194590f47044d6ab50d3
Reviewed-on: https://chromium-review.googlesource.com/750330
Reviewed-by: Nate Chapin <[email protected]>
Reviewed-by: Scott Violet <[email protected]>
Reviewed-by: Avi Drissman <[email protected]>
Commit-Queue: Charlie Reis <[email protected]>
Cr-Commit-Position: refs/heads/master@{#513826}
diff --git a/content/browser/frame_host/render_frame_host_delegate.h b/content/browser/frame_host/render_frame_host_delegate.h
index e29b84f..22a2cf2 100644
--- a/content/browser/frame_host/render_frame_host_delegate.h
+++ b/content/browser/frame_host/render_frame_host_delegate.h
@@ -130,6 +130,9 @@
   virtual void RunFileChooser(RenderFrameHost* render_frame_host,
                               const FileChooserParams& params) {}
 
+  // The pending page load was canceled, so the address bar should be updated.
+  virtual void DidCancelLoading() {}
+
   // Another page accessed the top-level initial empty document, which means it
   // is no longer safe to display a pending URL without risking a URL spoof.
   virtual void DidAccessInitialDocument() {}
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index da5c7dc9..06980ed 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -1833,8 +1833,11 @@
   }
 
   // If canceled, notify the delegate to cancel its pending navigation entry.
+  // This is usually redundant with the dialog closure code in WebContentsImpl's
+  // OnDialogClosed, but there may be some cases that Blink returns !proceed
+  // without showing the dialog. We also update the address bar here to be safe.
   if (!proceed)
-    render_view_host_->GetDelegate()->DidCancelLoading();
+    delegate_->DidCancelLoading();
 }
 
 bool RenderFrameHostImpl::IsWaitingForUnloadACK() const {
diff --git a/content/browser/frame_host/render_frame_host_impl_browsertest.cc b/content/browser/frame_host/render_frame_host_impl_browsertest.cc
index 85316fd..98235709 100644
--- a/content/browser/frame_host/render_frame_host_impl_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_impl_browsertest.cc
@@ -181,7 +181,8 @@
 class TestJavaScriptDialogManager : public JavaScriptDialogManager,
                                     public WebContentsDelegate {
  public:
-  TestJavaScriptDialogManager() : message_loop_runner_(new MessageLoopRunner) {}
+  TestJavaScriptDialogManager()
+      : message_loop_runner_(new MessageLoopRunner), url_invalidate_count_(0) {}
   ~TestJavaScriptDialogManager() override {}
 
   void Wait() {
@@ -223,12 +224,26 @@
 
   void CancelDialogs(WebContents* web_contents, bool reset_state) override {}
 
+  // Keep track of whether the tab has notified us of a navigation state change
+  // which invalidates the displayed URL.
+  void NavigationStateChanged(WebContents* source,
+                              InvalidateTypes changed_flags) override {
+    if (changed_flags & INVALIDATE_TYPE_URL)
+      url_invalidate_count_++;
+  }
+
+  int url_invalidate_count() { return url_invalidate_count_; }
+  void reset_url_invalidate_count() { url_invalidate_count_ = 0; }
+
  private:
   DialogClosedCallback callback_;
 
   // The MessageLoopRunner used to spin the message loop.
   scoped_refptr<MessageLoopRunner> message_loop_runner_;
 
+  // The number of times NavigationStateChanged has been called.
+  int url_invalidate_count_;
+
   DISALLOW_COPY_AND_ASSIGN(TestJavaScriptDialogManager);
 };
 
@@ -340,6 +355,43 @@
   wc->SetJavaScriptDialogManagerForTesting(nullptr);
 }
 
+// Test for crbug.com/80401.  Canceling a beforeunload dialog should reset
+// the URL to the previous page's URL.
+IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBrowserTest,
+                       CancelBeforeUnloadResetsURL) {
+  WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell()->web_contents());
+  TestJavaScriptDialogManager dialog_manager;
+  wc->SetDelegate(&dialog_manager);
+
+  GURL url(GetTestUrl("render_frame_host", "beforeunload.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), url));
+  PrepContentsForBeforeUnloadTest(wc);
+
+  // Navigate to a page that triggers a cross-site transition.
+  GURL url2(embedded_test_server()->GetURL("foo.com", "/title1.html"));
+  shell()->LoadURL(url2);
+  dialog_manager.Wait();
+
+  // Cancel the dialog.
+  dialog_manager.reset_url_invalidate_count();
+  std::move(dialog_manager.callback()).Run(false, base::string16());
+  EXPECT_FALSE(wc->IsLoading());
+
+  // Verify there are no pending history items after the dialog is cancelled.
+  // (see crbug.com/93858)
+  NavigationEntry* entry = wc->GetController().GetPendingEntry();
+  EXPECT_EQ(nullptr, entry);
+  EXPECT_EQ(url, wc->GetVisibleURL());
+
+  // There should have been at least one NavigationStateChange event for
+  // invalidating the URL in the address bar, to avoid leaving the stale URL
+  // visible.
+  EXPECT_GE(dialog_manager.url_invalidate_count(), 1);
+
+  wc->SetDelegate(nullptr);
+  wc->SetJavaScriptDialogManagerForTesting(nullptr);
+}
+
 namespace {
 
 // A helper to execute some script in a frame just before it is deleted, such
diff --git a/content/browser/renderer_host/render_view_host_delegate.h b/content/browser/renderer_host/render_view_host_delegate.h
index 36d6c48..9b01a0dd 100644
--- a/content/browser/renderer_host/render_view_host_delegate.h
+++ b/content/browser/renderer_host/render_view_host_delegate.h
@@ -96,9 +96,6 @@
   // The page is trying to move the RenderView's representation in the client.
   virtual void RequestMove(const gfx::Rect& new_bounds) {}
 
-  // The pending page load was canceled.
-  virtual void DidCancelLoading() {}
-
   // The RenderView's main frame document element is ready. This happens when
   // the document has finished parsing.
   virtual void DocumentAvailableInMainFrame(RenderViewHost* render_view_host) {}
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 32d969bd..41839f8 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -5696,6 +5696,9 @@
       controller_.DiscardNonCommittedEntries();
     }
 
+    // Update the URL display either way, to avoid showing a stale URL.
+    NotifyNavigationStateChanged(INVALIDATE_TYPE_URL);
+
     for (auto& observer : observers_)
       observer.BeforeUnloadDialogCancelled();
   }
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 43d275d..d94e7c0 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -502,6 +502,7 @@
                               IPC::Message* reply_msg) override;
   void RunFileChooser(RenderFrameHost* render_frame_host,
                       const FileChooserParams& params) override;
+  void DidCancelLoading() override;
   void DidAccessInitialDocument() override;
   void DidChangeName(RenderFrameHost* render_frame_host,
                      const std::string& name) override;
@@ -586,7 +587,6 @@
                        const GURL& url) override;
   void Close(RenderViewHost* render_view_host) override;
   void RequestMove(const gfx::Rect& new_bounds) override;
-  void DidCancelLoading() override;
   void DocumentAvailableInMainFrame(RenderViewHost* render_view_host) override;
   void RouteCloseEvent(RenderViewHost* rvh) override;
   bool DidAddMessageToConsole(int32_t level,
@@ -938,6 +938,8 @@
                            IframeBeforeUnloadParentHang);
   FRIEND_TEST_ALL_PREFIXES(RenderFrameHostImplBrowserTest,
                            BeforeUnloadDialogRequiresGesture);
+  FRIEND_TEST_ALL_PREFIXES(RenderFrameHostImplBrowserTest,
+                           CancelBeforeUnloadResetsURL);
   FRIEND_TEST_ALL_PREFIXES(DevToolsProtocolTest, JavaScriptDialogNotifications);
   FRIEND_TEST_ALL_PREFIXES(DevToolsProtocolTest, JavaScriptDialogInterop);
   FRIEND_TEST_ALL_PREFIXES(DevToolsProtocolTest, BeforeUnloadDialog);