Fix DisownOpener and related tests.

BUG=397185
TEST=RenderFrameHostManagerTest.DisownOpener*.
TBR=nasko (just for DisownSameSiteOpener test)

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

Cr-Commit-Position: refs/heads/master@{#291556}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@291556 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index 9863277..c6393a6 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -456,7 +456,8 @@
 
 // TODO(creis): Take in RenderFrameHost instead, since frames can have openers.
 void RenderFrameHostManager::DidDisownOpener(RenderViewHost* render_view_host) {
-  // Notify all swapped out hosts, including the pending RVH.
+  // Notify all RenderViewHosts but the one that notified us.  This is necessary
+  // in case a process swap has occurred while the message was in flight.
   for (RenderFrameProxyHostMap::iterator iter = proxy_hosts_.begin();
        iter != proxy_hosts_.end();
        ++iter) {
@@ -464,6 +465,14 @@
               current_frame_host()->GetSiteInstance());
     iter->second->GetRenderViewHost()->DisownOpener();
   }
+
+  if (render_frame_host_->render_view_host() != render_view_host)
+    render_frame_host_->render_view_host()->DisownOpener();
+
+  if (pending_render_frame_host_ &&
+      pending_render_frame_host_->render_view_host() != render_view_host) {
+    pending_render_frame_host_->render_view_host()->DisownOpener();
+  }
 }
 
 void RenderFrameHostManager::RendererProcessClosing(
@@ -1052,7 +1061,7 @@
 
   // Check if we've already created an RFH for this SiteInstance.  If so, try
   // to re-use the existing one, which has already been initialized.  We'll
-  // remove it from the list of swapped out hosts if it commits.
+  // remove it from the list of proxy hosts below if it will be active.
   RenderFrameProxyHost* proxy = GetRenderFrameProxyHost(instance);
 
   if (proxy) {
diff --git a/content/browser/frame_host/render_frame_host_manager_browsertest.cc b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
index 3f96c31..5b7f1912 100644
--- a/content/browser/frame_host/render_frame_host_manager_browsertest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
@@ -411,10 +411,8 @@
 
 // Test that setting the opener to null in a window affects cross-process
 // navigations, including those to existing entries.  http://crbug.com/156669.
-// Flaky on android: http://crbug.com/397185
-// Flaky on windows and mac: http://crbug.com/291249
-// This test also crashes under ThreadSanitizer, http://crbug.com/356758.
-#if defined(OS_ANDROID) || defined(OS_WIN) || defined(THREAD_SANITIZER) || defined(OS_MACOSX)
+// This test crashes under ThreadSanitizer, http://crbug.com/356758.
+#if defined(THREAD_SANITIZER)
 #define MAYBE_DisownOpener DISABLED_DisownOpener
 #else
 #define MAYBE_DisownOpener DisownOpener
@@ -444,6 +442,7 @@
       &success));
   EXPECT_TRUE(success);
   Shell* new_shell = new_shell_observer.GetShell();
+  EXPECT_TRUE(new_shell->web_contents()->HasOpener());
 
   // Wait for the navigation in the new tab to finish, if it hasn't.
   WaitForLoadStop(new_shell->web_contents());
@@ -460,10 +459,12 @@
   scoped_refptr<SiteInstance> new_site_instance(
       new_shell->web_contents()->GetSiteInstance());
   EXPECT_NE(orig_site_instance, new_site_instance);
+  EXPECT_TRUE(new_shell->web_contents()->HasOpener());
 
   // Now disown the opener.
   EXPECT_TRUE(ExecuteScript(new_shell->web_contents(),
                             "window.opener = null;"));
+  EXPECT_FALSE(new_shell->web_contents()->HasOpener());
 
   // Go back and ensure the opener is still null.
   {
@@ -477,6 +478,7 @@
       "window.domAutomationController.send(window.opener == null);",
       &success));
   EXPECT_TRUE(success);
+  EXPECT_FALSE(new_shell->web_contents()->HasOpener());
 
   // Now navigate forward again (creating a new process) and check opener.
   NavigateToURL(new_shell, GetCrossSiteURL("files/title1.html"));
@@ -486,6 +488,7 @@
       "window.domAutomationController.send(window.opener == null);",
       &success));
   EXPECT_TRUE(success);
+  EXPECT_FALSE(new_shell->web_contents()->HasOpener());
 }
 
 // Test that subframes can disown their openers.  http://crbug.com/225528.
diff --git a/content/browser/frame_host/render_frame_host_manager_unittest.cc b/content/browser/frame_host/render_frame_host_manager_unittest.cc
index 7cc3cb1..925ecd7 100644
--- a/content/browser/frame_host/render_frame_host_manager_unittest.cc
+++ b/content/browser/frame_host/render_frame_host_manager_unittest.cc
@@ -1159,6 +1159,130 @@
                    rvh3->GetSiteInstance()));
 }
 
+// Test that a page can disown the opener of the WebContents.
+TEST_F(RenderFrameHostManagerTest, DisownOpener) {
+  const GURL kUrl1("http://www.google.com/");
+  const GURL kUrl2("http://www.chromium.org/");
+
+  // Navigate to an initial URL.
+  contents()->NavigateAndCommit(kUrl1);
+  TestRenderFrameHost* rfh1 = main_test_rfh();
+
+  // Create a new tab and simulate having it be the opener for the main tab.
+  scoped_ptr<TestWebContents> opener1(
+      TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
+  contents()->SetOpener(opener1.get());
+  EXPECT_TRUE(contents()->HasOpener());
+
+  // Navigate to a cross-site URL (different SiteInstance but same
+  // BrowsingInstance).
+  contents()->NavigateAndCommit(kUrl2);
+  TestRenderFrameHost* rfh2 = main_test_rfh();
+  EXPECT_NE(rfh1->GetSiteInstance(), rfh2->GetSiteInstance());
+
+  // Disown the opener from rfh2.
+  rfh2->DidDisownOpener();
+
+  // Ensure the opener is cleared.
+  EXPECT_FALSE(contents()->HasOpener());
+}
+
+// Test that a page can disown a same-site opener of the WebContents.
+TEST_F(RenderFrameHostManagerTest, DisownSameSiteOpener) {
+  const GURL kUrl1("http://www.google.com/");
+  const GURL kUrl2("http://www.chromium.org/");
+
+  // Navigate to an initial URL.
+  contents()->NavigateAndCommit(kUrl1);
+  TestRenderFrameHost* rfh1 = main_test_rfh();
+
+  // Create a new tab and simulate having it be the opener for the main tab.
+  scoped_ptr<TestWebContents> opener1(
+      TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
+  contents()->SetOpener(opener1.get());
+  EXPECT_TRUE(contents()->HasOpener());
+
+  // Disown the opener from rfh1.
+  rfh1->DidDisownOpener();
+
+  // Ensure the opener is cleared even if it is in the same process.
+  EXPECT_FALSE(contents()->HasOpener());
+}
+
+// Test that a page can disown the opener just as a cross-process navigation is
+// in progress.
+TEST_F(RenderFrameHostManagerTest, DisownOpenerDuringNavigation) {
+  const GURL kUrl1("http://www.google.com/");
+  const GURL kUrl2("http://www.chromium.org/");
+
+  // Navigate to an initial URL.
+  contents()->NavigateAndCommit(kUrl1);
+  TestRenderFrameHost* rfh1 = main_test_rfh();
+
+  // Create a new tab and simulate having it be the opener for the main tab.
+  scoped_ptr<TestWebContents> opener1(
+      TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
+  contents()->SetOpener(opener1.get());
+  EXPECT_TRUE(contents()->HasOpener());
+
+  // Navigate to a cross-site URL (different SiteInstance but same
+  // BrowsingInstance).
+  contents()->NavigateAndCommit(kUrl2);
+  TestRenderFrameHost* rfh2 = main_test_rfh();
+  EXPECT_NE(rfh1->GetSiteInstance(), rfh2->GetSiteInstance());
+
+  // Start a back navigation so that rfh1 becomes the pending RFH.
+  contents()->GetController().GoBack();
+  contents()->ProceedWithCrossSiteNavigation();
+
+  // Disown the opener from rfh2.
+  rfh2->DidDisownOpener();
+
+  // Ensure the opener is cleared.
+  EXPECT_FALSE(contents()->HasOpener());
+
+  // The back navigation commits.
+  const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
+  rfh1->SendNavigate(entry1->GetPageID(), entry1->GetURL());
+
+  // Ensure the opener is still cleared.
+  EXPECT_FALSE(contents()->HasOpener());
+}
+
+// Test that a page can disown the opener just after a cross-process navigation
+// commits.
+TEST_F(RenderFrameHostManagerTest, DisownOpenerAfterNavigation) {
+  const GURL kUrl1("http://www.google.com/");
+  const GURL kUrl2("http://www.chromium.org/");
+
+  // Navigate to an initial URL.
+  contents()->NavigateAndCommit(kUrl1);
+  TestRenderFrameHost* rfh1 = main_test_rfh();
+
+  // Create a new tab and simulate having it be the opener for the main tab.
+  scoped_ptr<TestWebContents> opener1(
+      TestWebContents::Create(browser_context(), rfh1->GetSiteInstance()));
+  contents()->SetOpener(opener1.get());
+  EXPECT_TRUE(contents()->HasOpener());
+
+  // Navigate to a cross-site URL (different SiteInstance but same
+  // BrowsingInstance).
+  contents()->NavigateAndCommit(kUrl2);
+  TestRenderFrameHost* rfh2 = main_test_rfh();
+  EXPECT_NE(rfh1->GetSiteInstance(), rfh2->GetSiteInstance());
+
+  // Commit a back navigation before the DidDisownOpener message arrives.
+  // rfh1 will be kept alive because of the opener tab.
+  contents()->GetController().GoBack();
+  contents()->ProceedWithCrossSiteNavigation();
+  const NavigationEntry* entry1 = contents()->GetController().GetPendingEntry();
+  rfh1->SendNavigate(entry1->GetPageID(), entry1->GetURL());
+
+  // Disown the opener from rfh2.
+  rfh2->DidDisownOpener();
+  EXPECT_FALSE(contents()->HasOpener());
+}
+
 // Test that we clean up swapped out RenderViewHosts when a process hosting
 // those associated RenderViews crashes. http://crbug.com/258993
 TEST_F(RenderFrameHostManagerTest, CleanUpSwappedOutRVHOnProcessCrash) {
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index 5de3388..a3b0d2d6 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -1351,9 +1351,6 @@
 }
 
 void RenderViewHostImpl::DisownOpener() {
-  // This should only be called when swapped out.
-  DCHECK(IsSwappedOut());
-
   Send(new ViewMsg_DisownOpener(GetRoutingID()));
 }
 
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 51b9177..623cbf6 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3670,12 +3670,14 @@
 }
 
 void WebContentsImpl::DidDisownOpener(RenderFrameHost* render_frame_host) {
-  if (opener_) {
-    // Clear our opener so that future cross-process navigations don't have an
-    // opener assigned.
-    RemoveDestructionObserver(opener_);
-    opener_ = NULL;
-  }
+  // No action is necessary if the opener has already been cleared.
+  if (!opener_)
+    return;
+
+  // Clear our opener so that future cross-process navigations don't have an
+  // opener assigned.
+  RemoveDestructionObserver(opener_);
+  opener_ = NULL;
 
   // Notify all swapped out RenderViewHosts for this tab.  This is important
   // in case we go back to them, or if another window in those processes tries