Don't show hung-renderer-dialog for background tabs.

Bug: 881812
Change-Id: I7e9eddb4386baac05e9292463f9f597132c5c14e
Reviewed-on: https://chromium-review.googlesource.com/1222684
Commit-Queue: Ɓukasz Anforowicz <[email protected]>
Reviewed-by: Gabriel Charette <[email protected]>
Reviewed-by: Alex Moshchuk <[email protected]>
Cr-Commit-Position: refs/heads/master@{#593270}
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index a7b5746a..8f1dd731 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -38,6 +38,7 @@
 #include "base/test/test_timeouts.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "cc/input/touch_action.h"
 #include "components/network_session_configurator/common/network_switches.h"
@@ -13093,9 +13094,13 @@
 
   ~UnresponsiveRendererObserver() override {}
 
-  RenderProcessHost* Wait() {
-    if (!captured_render_process_host_)
+  RenderProcessHost* Wait(base::TimeDelta timeout = base::TimeDelta::Max()) {
+    if (!captured_render_process_host_) {
+      base::OneShotTimer timer;
+      timer.Start(FROM_HERE, timeout, run_loop_.QuitClosure());
       run_loop_.Run();
+      timer.Stop();
+    }
     return captured_render_process_host_;
   }
 
@@ -13114,26 +13119,20 @@
 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
                        CommitTimeoutForHungRenderer) {
   // Navigate first tab to a.com.
-  GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
-  EXPECT_TRUE(NavigateToURL(shell(), url));
+  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), a_url));
   RenderProcessHost* a_process =
       shell()->web_contents()->GetMainFrame()->GetProcess();
 
-  // Use window.open to open b.com in a second tab.  Using a renderer-initiated
-  // navigation is important to leave a.com and b.com SiteInstances in the same
+  // Open b.com in a second tab.  Using a renderer-initiated navigation is
+  // important to leave a.com and b.com SiteInstances in the same
   // BrowsingInstance (so the b.com -> a.com navigation in the next test step
   // will reuse the process associated with the first a.com tab).
-  const char* kWindowOpenScript = R"(
-      var anchor = document.createElement("a");
-      anchor.href = "/cross-site/b.com/title2.html";
-      anchor.target = "_blank";
-      document.body.appendChild(anchor);
-      anchor.click(); )";
-  WebContentsAddedObserver new_window_observer;
-  EXPECT_TRUE(ExecuteScript(shell()->web_contents(), kWindowOpenScript));
-  WebContents* new_window = new_window_observer.GetWebContents();
-  EXPECT_TRUE(WaitForLoadStop(new_window));
-  RenderProcessHost* b_process = new_window->GetMainFrame()->GetProcess();
+  GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
+  Shell* new_shell = OpenPopup(shell()->web_contents(), b_url, "newtab");
+  WebContents* new_contents = new_shell->web_contents();
+  EXPECT_TRUE(WaitForLoadStop(new_contents));
+  RenderProcessHost* b_process = new_contents->GetMainFrame()->GetProcess();
   EXPECT_NE(a_process, b_process);
 
   // Hang the first tab's renderer.
@@ -13144,13 +13143,10 @@
   // the hung process.
   NavigationHandleImpl::SetCommitTimeoutForTesting(
       base::TimeDelta::FromMilliseconds(100));
-  const char* kNavigationScript = R"(
-      var anchor = document.createElement("a");
-      anchor.href = "/cross-site/a.com/title3.html";
-      document.body.appendChild(anchor);
-      anchor.click(); )";
-  UnresponsiveRendererObserver unresponsive_renderer_observer(new_window);
-  EXPECT_TRUE(ExecuteScript(new_window, kNavigationScript));
+  GURL hung_url(embedded_test_server()->GetURL("a.com", "/title3.html"));
+  UnresponsiveRendererObserver unresponsive_renderer_observer(new_contents);
+  EXPECT_TRUE(
+      ExecJs(new_contents, JsReplace("window.location = $1", hung_url)));
 
   // Verify that we will be notified about the unresponsive renderer.  Before
   // changes in https://crrev.com/c/1089797, the test would hang here forever.
@@ -13161,6 +13157,58 @@
   NavigationHandleImpl::SetCommitTimeoutForTesting(base::TimeDelta());
 }
 
+// This is a regression test for https://crbug.com/881812 which complained that
+// the hung renderer dialog used to undesirably show up for background tabs
+// (typically during session restore when many navigations would be happening in
+// backgrounded processes).
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
+                       CommitTimeoutForInvisibleWebContents) {
+  // Navigate first tab to a.com.
+  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), a_url));
+  RenderProcessHost* a_process =
+      shell()->web_contents()->GetMainFrame()->GetProcess();
+
+  // Open b.com in a second tab.  Using a renderer-initiated navigation is
+  // important to leave a.com and b.com SiteInstances in the same
+  // BrowsingInstance (so the b.com -> a.com navigation in the next test step
+  // will reuse the process associated with the first a.com tab).
+  GURL b_url(embedded_test_server()->GetURL("b.com", "/title2.html"));
+  Shell* new_shell = OpenPopup(shell()->web_contents(), b_url, "newtab");
+  WebContents* new_contents = new_shell->web_contents();
+  EXPECT_TRUE(WaitForLoadStop(new_contents));
+  RenderProcessHost* b_process = new_contents->GetMainFrame()->GetProcess();
+  EXPECT_NE(a_process, b_process);
+
+  // Hang the first tab's renderer.
+  const char* kHungScript = "setTimeout(function() { for (;;) {}; }, 0);";
+  EXPECT_TRUE(ExecuteScript(shell()->web_contents(), kHungScript));
+
+  // Hide the second tab.  This should prevent reporting of hangs in this tab
+  // (see https://crbug.com/881812).
+  new_contents->WasHidden();
+  EXPECT_EQ(Visibility::HIDDEN, new_contents->GetVisibility());
+
+  // Attempt to navigate the second tab to a.com.  This will attempt to reuse
+  // the hung process.
+  base::TimeDelta kTimeout = base::TimeDelta::FromMilliseconds(100);
+  NavigationHandleImpl::SetCommitTimeoutForTesting(kTimeout);
+  GURL hung_url(embedded_test_server()->GetURL("a.com", "/title3.html"));
+  UnresponsiveRendererObserver unresponsive_renderer_observer(new_contents);
+  EXPECT_TRUE(
+      ExecJs(new_contents, JsReplace("window.location = $1", hung_url)));
+
+  // Verify that we will not be notified about the unresponsive renderer.
+  // Before changes in https://crrev.com/c/1089797, the test would get notified
+  // and therefore |hung_process| would be non-null.
+  RenderProcessHost* hung_process =
+      unresponsive_renderer_observer.Wait(kTimeout * 10);
+  EXPECT_FALSE(hung_process);
+
+  // Reset the timeout.
+  NavigationHandleImpl::SetCommitTimeoutForTesting(base::TimeDelta());
+}
+
 // Tests that an inner WebContents will reattach to its outer WebContents after
 // a navigation that causes a process swap.
 IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ProcessSwapOnInnerContents) {
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index acf0ba9..09f197b 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -5966,15 +5966,22 @@
 void WebContentsImpl::RendererUnresponsive(
     RenderWidgetHostImpl* render_widget_host,
     base::RepeatingClosure hang_monitor_restarter) {
-  for (auto& observer : observers_)
-    observer.OnRendererUnresponsive(render_widget_host->GetProcess());
-
   if (ShouldIgnoreUnresponsiveRenderer())
     return;
 
+  // Do not report hangs (to task manager, to hang renderer dialog, etc.) for
+  // invisible tabs (like extension background page, background tabs).  See
+  // https://crbug.com/881812 for rationale and for choosing the visibility
+  // (rather than process priority) as the signal here.
+  if (GetVisibility() != Visibility::VISIBLE)
+    return;
+
   if (!render_widget_host->renderer_initialized())
     return;
 
+  for (auto& observer : observers_)
+    observer.OnRendererUnresponsive(render_widget_host->GetProcess());
+
   if (delegate_)
     delegate_->RendererUnresponsive(this, render_widget_host,
                                     std::move(hang_monitor_restarter));