// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/process/process.h"
#include "base/run_loop.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/preloading/scoped_prewarm_feature_list.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/performance_manager/public/features.h"
#include "content/public/browser/child_process_launcher_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "media/base/media_switches.h"
#include "net/base/filename_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"

#if BUILDFLAG(IS_MAC)
#include "content/public/browser/browser_child_process_host.h"
#endif  // BUILDFLAG(IS_MAC)

using content::WebContents;

namespace {

int RenderProcessHostCount() {
  return content::RenderProcessHost::GetCurrentRenderProcessCountForTesting();
}

WebContents* FindFirstDevToolsContents() {
  for (content::WebContents* web_contents : content::GetAllWebContents()) {
    if (web_contents->GetURL().SchemeIs(content::kChromeDevToolsScheme) &&
        web_contents->GetPrimaryMainFrame()
            ->GetProcess()
            ->IsInitializedAndNotDead()) {
      return web_contents;
    }
  }
  return nullptr;
}

// TODO(rvargas) crbug.com/40386210: Remove this code.
base::Process ProcessFromHandle(base::ProcessHandle handle) {
#if BUILDFLAG(IS_WIN)
  if (handle == GetCurrentProcess())
    return base::Process::Current();

  base::ProcessHandle out_handle;
  if (!::DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(),
                         &out_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
    return base::Process();
  }
  handle = out_handle;
#endif  // BUILDFLAG(IS_WIN)
  return base::Process(handle);
}

// Returns true if the priority of `process` is kBestEffort.
bool IsProcessBackgrounded(const base::Process& process) {
#if BUILDFLAG(IS_MAC)
  return process.GetPriority(
             content::BrowserChildProcessHost::GetPortProvider()) ==
         base::Process::Priority::kBestEffort;
#else
  return process.GetPriority() == base::Process::Priority::kBestEffort;
#endif
}

}  // namespace

class ChromeRenderProcessHostTest : public extensions::ExtensionBrowserTest {
 public:
  ChromeRenderProcessHostTest() {
    feature_list_.InitAndDisableFeature(
        performance_manager::features::kTransientKeepAlivePolicy);
  }

  ChromeRenderProcessHostTest(const ChromeRenderProcessHostTest&) = delete;
  ChromeRenderProcessHostTest& operator=(const ChromeRenderProcessHostTest&) =
      delete;

  // Show a tab, activating the current one if there is one, and wait for
  // the renderer process to be created or foregrounded, returning the
  // WebContents associated with the tab.
  WebContents* ShowSingletonTab(const GURL& page) {
    ::ShowSingletonTab(browser(), page);
    WebContents* wc = browser()->tab_strip_model()->GetActiveWebContents();
    CHECK(wc->GetURL() == page);

    WaitForLauncherThread();
    WaitForMessageProcessing(wc);
    return wc;
  }

  // Loads the given url in a new background tab and returns the WebContents
  // associated with the tab.
  WebContents* OpenBackgroundTab(const GURL& page) {
    ui_test_utils::NavigateToURLWithDisposition(
        browser(), page, WindowOpenDisposition::NEW_BACKGROUND_TAB,
        ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);

    TabStripModel* tab_strip = browser()->tab_strip_model();
    WebContents* wc =
        tab_strip->GetWebContentsAt(tab_strip->active_index() + 1);
    CHECK(wc->GetVisibleURL() == page);

    WaitForLauncherThread();
    WaitForMessageProcessing(wc);
    return wc;
  }

  // Ensures that the backgrounding / foregrounding gets a chance to run.
  void WaitForLauncherThread() {
    base::RunLoop run_loop;
    content::GetProcessLauncherTaskRunner()->PostTaskAndReply(
        FROM_HERE, base::DoNothing(), run_loop.QuitWhenIdleClosure());
    run_loop.Run();
  }

  // Implicitly waits for the renderer process associated with the specified
  // WebContents to process outstanding IPC messages by running some JavaScript
  // and waiting for the result.
  void WaitForMessageProcessing(WebContents* wc) {
    ASSERT_EQ(true, content::EvalJs(wc, "true;"));
  }

  // When we hit the max number of renderers, verify that the way we do process
  // sharing behaves correctly.  In particular, this test is verifying that even
  // when we hit the max process limit, that renderers of each type will wind up
  // in a process of that type, even if that means creating a new process.
  void TestProcessOverflow() {
    int tab_count = 1;
    int host_count = RenderProcessHostCount();
    WebContents* tab1 = nullptr;
    WebContents* tab2 = nullptr;
    content::RenderProcessHost* rph1 = nullptr;
    content::RenderProcessHost* rph2 = nullptr;
    content::RenderProcessHost* rph3 = nullptr;

    const extensions::Extension* extension =
        LoadExtension(test_data_dir_.AppendASCII("options_page"));

    content::RenderFrameDeletedObserver before_webui_obs(
        content::ConvertToRenderFrameHost(
            browser()->tab_strip_model()->GetActiveWebContents()));

    // Change the first tab to be the omnibox page (WebUI).
    GURL omnibox(chrome::kChromeUIOmniboxURL);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), omnibox));

    // The host objects from the page before the WebUI navigation stick around
    // until the old renderer cleans up and ACKs, which may happen later than
    // the navigation in the WebUI's renderer. So wait for that.
    before_webui_obs.WaitUntilDeleted();

    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
    rph1 = tab1->GetPrimaryMainFrame()->GetProcess();
    EXPECT_EQ(omnibox, tab1->GetURL());
    EXPECT_EQ(host_count, RenderProcessHostCount());

    // Create a new normal tab with a data URL. It should be in its own process.
    GURL page1("data:text/html,hello world1");

    ui_test_utils::TabAddedWaiter add_tab1(browser());
    ::ShowSingletonTab(browser(), page1);
    add_tab1.Wait();

    tab_count++;
    host_count++;
    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
    rph2 = tab1->GetPrimaryMainFrame()->GetProcess();
    EXPECT_EQ(tab1->GetURL(), page1);
    EXPECT_EQ(host_count, RenderProcessHostCount());
    EXPECT_NE(rph1, rph2);

    // Create another data URL tab.  With Site Isolation, this will require its
    // own process, but without Site Isolation, it can share the previous
    // process.
    GURL page2("data:text/html,hello world2");
    ui_test_utils::TabAddedWaiter add_tab2(browser());
    ::ShowSingletonTab(browser(), page2);
    add_tab2.Wait();
    tab_count++;
    if (content::AreAllSitesIsolatedForTesting())
      host_count++;
    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
    EXPECT_EQ(tab2->GetURL(), page2);
    EXPECT_EQ(host_count, RenderProcessHostCount());
    if (content::AreAllSitesIsolatedForTesting())
      EXPECT_NE(tab2->GetPrimaryMainFrame()->GetProcess(), rph2);
    else
      EXPECT_EQ(tab2->GetPrimaryMainFrame()->GetProcess(), rph2);

    // Create another WebUI tab.  Each WebUI tab should get a separate process
    // because of origin locking.
    // Note: intentionally create this tab after the normal tabs to exercise bug
    // 43448 where extension and WebUI tabs could get combined into normal
    // renderers.
    GURL history(chrome::kChromeUIHistoryURL);
    ui_test_utils::TabAddedWaiter add_tab3(browser());
    ::ShowSingletonTab(browser(), history);
    add_tab3.Wait();
    tab_count++;
    host_count++;
    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    tab2 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
    EXPECT_EQ(tab2->GetURL(), GURL(history));
    EXPECT_EQ(host_count, RenderProcessHostCount());
    EXPECT_NE(tab2->GetPrimaryMainFrame()->GetProcess(), rph1);

    // Create an extension tab.  It should be in its own process.
    GURL extension_url("chrome-extension://" + extension->id());
    ui_test_utils::TabAddedWaiter add_tab4(browser());
    ::ShowSingletonTab(browser(), extension_url);

    add_tab4.Wait();
    tab_count++;
    host_count++;
    EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
    tab1 = browser()->tab_strip_model()->GetWebContentsAt(tab_count - 1);
    rph3 = tab1->GetPrimaryMainFrame()->GetProcess();
    EXPECT_EQ(tab1->GetURL(), extension_url);
    EXPECT_EQ(host_count, RenderProcessHostCount());
    EXPECT_NE(rph1, rph3);
    EXPECT_NE(rph2, rph3);
  }

 private:
  // TODO(https://crbug.com/423465927): Explore a better approach to make the
  // existing tests run with the prewarm feature enabled.
  test::ScopedPrewarmFeatureList prewarm_feature_list_{
      test::ScopedPrewarmFeatureList::PrewarmState::kDisabled};
  base::test::ScopedFeatureList feature_list_;
};

class ChromeRenderProcessHostTestWithCommandLine
    : public ChromeRenderProcessHostTest {
 public:
  ChromeRenderProcessHostTestWithCommandLine() = default;

  ChromeRenderProcessHostTestWithCommandLine(
      const ChromeRenderProcessHostTestWithCommandLine&) = delete;
  ChromeRenderProcessHostTestWithCommandLine& operator=(
      const ChromeRenderProcessHostTestWithCommandLine&) = delete;

  ~ChromeRenderProcessHostTestWithCommandLine() override = default;

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    ChromeRenderProcessHostTest::SetUpCommandLine(command_line);
    command_line->AppendSwitchASCII(switches::kRendererProcessLimit, "1");
  }
};

// TODO(crbug.com/497106715): Fix and re-enable test
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, DISABLED_ProcessPerTab) {
  // Set max renderers to 1 to force running out of processes.
  content::RenderProcessHost::SetMaxRendererProcessCount(1);

  base::CommandLine& parsed_command_line =
      *base::CommandLine::ForCurrentProcess();
  parsed_command_line.AppendSwitch(switches::kProcessPerTab);

  int tab_count = 1;
  int host_count = RenderProcessHostCount();

  content::RenderFrameDeletedObserver before_webui_obs(
      content::ConvertToRenderFrameHost(
          browser()->tab_strip_model()->GetActiveWebContents()));

  // Change the first tab to be a WebUI page.
  GURL omnibox(chrome::kChromeUIOmniboxURL);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), omnibox));

  // The host objects from the page before the WebUI navigation stick around
  // until the old renderer cleans up and ACKs, which may happen later than the
  // navigation in the WebUI's renderer. So wait for that.
  before_webui_obs.WaitUntilDeleted();

  // Expect just the WebUI tab's process to be around.
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  // Create a new normal tab with a data URL.  It should be in its own process.
  GURL page1("data:text/html,hello world1");
  content::TestNavigationObserver navigation_observer1(page1);
  navigation_observer1.StartWatchingNewWebContents();
  ui_test_utils::TabAddedWaiter add_tab1(browser());
  ::ShowSingletonTab(browser(), page1);
  add_tab1.Wait();
  navigation_observer1.Wait();
  tab_count++;
  host_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  // Create another data URL tab.  With Site Isolation, this will require its
  // own process, but without Site Isolation, it can share the previous process.
  GURL page2("data:text/html,hello world2");
  content::TestNavigationObserver navigation_observer2(page2);
  navigation_observer2.StartWatchingNewWebContents();
  ui_test_utils::TabAddedWaiter add_tab2(browser());
  ::ShowSingletonTab(browser(), page2);
  add_tab2.Wait();
  navigation_observer2.Wait();
  tab_count++;
  if (content::AreAllSitesIsolatedForTesting())
    host_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  // Create another omnibox tab.  It should share the process with the other
  // WebUI.
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), omnibox, WindowOpenDisposition::NEW_FOREGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  tab_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  // Create another omnibox tab.  It should share the process with the other
  // WebUI.
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), omnibox, WindowOpenDisposition::NEW_FOREGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  tab_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());
}

class ChromeRenderProcessHostBackgroundingTest
    : public ChromeRenderProcessHostTest {
 public:
  ChromeRenderProcessHostBackgroundingTest() {
    feature_list_.InitAndDisableFeature(
        performance_manager::features::kPMLoadingPageVoter);
  }

  ChromeRenderProcessHostBackgroundingTest(
      const ChromeRenderProcessHostBackgroundingTest&) = delete;
  ChromeRenderProcessHostBackgroundingTest& operator=(
      const ChromeRenderProcessHostBackgroundingTest&) = delete;

  ~ChromeRenderProcessHostBackgroundingTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ChromeRenderProcessHostTest::SetUpCommandLine(command_line);

    command_line->AppendSwitch(switches::kProcessPerTab);
  }

  void VerifyProcessIsBackgrounded(WebContents* web_contents) {
    constexpr bool kExpectedIsBackground = true;
    VerifyProcessPriority(web_contents->GetPrimaryMainFrame()->GetProcess(),
                          kExpectedIsBackground);
  }

  void VerifyProcessIsForegrounded(WebContents* web_contents) {
    constexpr bool kExpectedIsBackground = false;
    VerifyProcessPriority(web_contents->GetPrimaryMainFrame()->GetProcess(),
                          kExpectedIsBackground);
  }

  void SetUpOnMainThread() override {
    ASSERT_TRUE(embedded_test_server()->Start());
  }

 private:
  void VerifyProcessPriority(content::RenderProcessHost* process,
                             bool expected_is_backgrounded) {
    EXPECT_TRUE(process->IsInitializedAndNotDead());
    EXPECT_EQ(expected_is_backgrounded,
              process->GetPriority() == base::Process::Priority::kBestEffort);

    if (base::Process::CanSetPriority()) {
      base::Process p = ProcessFromHandle(process->GetProcess().Handle());
      ASSERT_TRUE(p.IsValid());
      EXPECT_EQ(expected_is_backgrounded, IsProcessBackgrounded(p));
    }
  }

  base::test::ScopedFeatureList feature_list_;
};

#define EXPECT_PROCESS_IS_BACKGROUNDED(process_or_tab)                       \
  do {                                                                       \
    SCOPED_TRACE("Verifying that |" #process_or_tab "| is backgrounded..."); \
    VerifyProcessIsBackgrounded(process_or_tab);                             \
  } while (0);

#define EXPECT_PROCESS_IS_FOREGROUNDED(process_or_tab)                       \
  do {                                                                       \
    SCOPED_TRACE("Verifying that |" #process_or_tab "| is foregrounded..."); \
    VerifyProcessIsForegrounded(process_or_tab);                             \
  } while (0);

// Flaky on Mac: https://crbug.com/41416652
#if BUILDFLAG(IS_MAC)
#define MAYBE_MultipleTabs DISABLED_MultipleTabs
#else
#define MAYBE_MultipleTabs MultipleTabs
#endif
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostBackgroundingTest,
                       MAYBE_MultipleTabs) {
  // Change the first tab to be the omnibox page (TYPE_WEBUI).
  GURL omnibox(chrome::kChromeUIOmniboxURL);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), omnibox));

  // Create a new tab. It should be foreground.
  GURL page1("data:text/html,hello world1");
  WebContents* tab1 = ShowSingletonTab(page1);
  {
    SCOPED_TRACE("TEST STEP: Single tab");
    EXPECT_PROCESS_IS_FOREGROUNDED(tab1);
  }

  // Create another tab. It should be foreground, and the first tab should
  // now be background.
  GURL page2("data:text/html,hello world2");
  WebContents* tab2 = ShowSingletonTab(page2);
  {
    SCOPED_TRACE("TEST STEP: 2nd tab opened in foreground");
    EXPECT_NE(tab1->GetPrimaryMainFrame()->GetProcess(),
              tab2->GetPrimaryMainFrame()->GetProcess());
    EXPECT_PROCESS_IS_BACKGROUNDED(tab1);
    EXPECT_PROCESS_IS_FOREGROUNDED(tab2);
  }

  // Load another tab in background. The renderer of the new tab should be
  // backgrounded, while visibility of the other renderers should not change.
  GURL page3("data:text/html,hello world3");
  WebContents* tab3 = OpenBackgroundTab(page3);
  {
    SCOPED_TRACE("TEST STEP: 3rd tab opened in background");
    EXPECT_NE(tab1->GetPrimaryMainFrame()->GetProcess(),
              tab3->GetPrimaryMainFrame()->GetProcess());
    EXPECT_NE(tab2->GetPrimaryMainFrame()->GetProcess(),
              tab3->GetPrimaryMainFrame()->GetProcess());
    EXPECT_PROCESS_IS_BACKGROUNDED(tab1);
    EXPECT_PROCESS_IS_FOREGROUNDED(tab2);
    EXPECT_PROCESS_IS_BACKGROUNDED(tab3);
  }

  // Navigate back to the first page. Its renderer should be in foreground
  // again while the other renderers should be backgrounded.
  ShowSingletonTab(page1);
  {
    SCOPED_TRACE("TEST STEP: First tab activated again");
    EXPECT_PROCESS_IS_FOREGROUNDED(tab1);
    EXPECT_PROCESS_IS_BACKGROUNDED(tab2);
    EXPECT_PROCESS_IS_BACKGROUNDED(tab3);
  }

  // Confirm that |tab3| remains backgrounded after being shown/hidden.
  ShowSingletonTab(page3);
  ShowSingletonTab(page1);
  {
    SCOPED_TRACE("TEST STEP: Third tab activated and deactivated");
    EXPECT_PROCESS_IS_FOREGROUNDED(tab1);
    EXPECT_PROCESS_IS_BACKGROUNDED(tab2);
    EXPECT_PROCESS_IS_BACKGROUNDED(tab3);
  }
}

IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest, ProcessOverflow) {
  // Set max renderers to 1 to force running out of processes.
  content::RenderProcessHost::SetMaxRendererProcessCount(1);
  TestProcessOverflow();
}

// Variation of the ProcessOverflow test, which is driven through command line
// parameter instead of direct function call into the class.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTestWithCommandLine,
                       ProcessOverflowCommandLine) {
  TestProcessOverflow();
}

// Ensure that DevTools opened to debug DevTools is launched in a separate
// process when --process-per-tab is set. See crbug.com/41305755.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
                       DevToolsOnSelfInOwnProcessPPT) {
  base::CommandLine& parsed_command_line =
      *base::CommandLine::ForCurrentProcess();
  parsed_command_line.AppendSwitch(switches::kProcessPerTab);

  int tab_count = 1;
  int host_count = RenderProcessHostCount();

  GURL page1("data:text/html,hello world1");
  ui_test_utils::TabAddedWaiter add_tab(browser());
  ::ShowSingletonTab(browser(), page1);
  add_tab.Wait();
  tab_count++;
  host_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  // DevTools start in docked mode (no new tab), in a separate process.
  chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect(),
                               DevToolsOpenedByAction::kUnknown);
  host_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  WebContents* devtools = FindFirstDevToolsContents();
  DCHECK(devtools);

  // DevTools start in a separate process.
  DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect(),
                                     DevToolsOpenedByAction::kUnknown);
  host_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  // Close docked devtools.
  content::WebContentsDestroyedWatcher close_observer(devtools);

  chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle(),
                               DevToolsOpenedByAction::kUnknown);
  close_observer.Wait();
}

// Ensure that DevTools opened to debug DevTools is launched in a separate
// process. See crbug.com/41305755.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
                       DevToolsOnSelfInOwnProcess) {
  int tab_count = 1;
  int host_count = RenderProcessHostCount();

  GURL page1("data:text/html,hello world1");
  ui_test_utils::TabAddedWaiter add_tab1(browser());
  ::ShowSingletonTab(browser(), page1);
  add_tab1.Wait();
  tab_count++;
  host_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  // DevTools start in docked mode (no new tab), in a separate process.
  chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Inspect(),
                               DevToolsOpenedByAction::kUnknown);
  host_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  WebContents* devtools = FindFirstDevToolsContents();
  DCHECK(devtools);

  // DevTools start in a separate process.
  DevToolsWindow::OpenDevToolsWindow(devtools, DevToolsToggleAction::Inspect(),
                                     DevToolsOpenedByAction::kUnknown);
  host_count++;
  EXPECT_EQ(tab_count, browser()->tab_strip_model()->count());
  EXPECT_EQ(host_count, RenderProcessHostCount());

  // Close docked devtools.
  content::WebContentsDestroyedWatcher close_observer(devtools);
  chrome::ToggleDevToolsWindow(browser(), DevToolsToggleAction::Toggle(),
                               DevToolsOpenedByAction::kUnknown);
  close_observer.Wait();
}

// This class's goal is to close the browser window when a renderer process has
// crashed. It does so by monitoring WebContents for RenderProcessGone event and
// closing the passed in TabStripModel. This is used in the following test case.
class WindowDestroyer : public content::WebContentsObserver {
 public:
  WindowDestroyer(BrowserWindowInterface* browser,
                  content::WebContents* web_contents)
      : content::WebContentsObserver(web_contents),
        tab_strip_model_(browser->GetTabStripModel()),
        observer_(browser) {}

  WindowDestroyer(const WindowDestroyer&) = delete;
  WindowDestroyer& operator=(const WindowDestroyer&) = delete;

  // Wait for the browser window to be destroyed.
  void Wait() { observer_.Wait(); }

  void PrimaryMainFrameRenderProcessGone(
      base::TerminationStatus status) override {
    tab_strip_model_->CloseAllTabs();
  }

 private:
  raw_ptr<TabStripModel> tab_strip_model_;
  ui_test_utils::BrowserDestroyedObserver observer_;
};

// Test to ensure that while iterating through all listeners in
// RenderProcessHost and invalidating them, we remove them properly and don't
// access already freed objects. See http://crbug.com/40077716.
// Disabled due to flakiness, see  http://crbug.com/41250793.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostTest,
                       DISABLED_CloseAllTabsDuringProcessDied) {
  GURL url(chrome::kChromeUIOmniboxURL);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  WebContents* wc1 = browser()->tab_strip_model()->GetWebContentsAt(0);

  content::WebContentsAddedObserver wc2_observer;
  content::ExecuteScriptAsync(
      wc1, content::JsReplace("window.open($1, '_blank')", url));
  WebContents* wc2 = wc2_observer.GetWebContents();
  content::TestNavigationObserver nav_observer(wc2, 1);
  nav_observer.Wait();

  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  EXPECT_EQ(wc1->GetPrimaryMainFrame()->GetLastCommittedURL(),
            wc2->GetPrimaryMainFrame()->GetLastCommittedURL());
  EXPECT_EQ(wc1->GetPrimaryMainFrame()->GetProcess(),
            wc2->GetPrimaryMainFrame()->GetProcess());

  // Create an object that will close the window on a process crash.
  WindowDestroyer destroyer(browser(), wc1);

  // Kill the renderer process, simulating a crash. This should the ProcessDied
  // method to be called. Alternatively, RenderProcessHost::OnChannelError can
  // be called to directly force a call to ProcessDied.
  content::ScopedAllowRendererCrashes allow_renderer_crashes(wc1);
  wc1->GetPrimaryMainFrame()->GetProcess()->Shutdown(-1);

  destroyer.Wait();
}

// Sets up the browser in order to start the tests with two tabs open: one
// called "no audio" in foreground and another called "audio" in background with
// audio in playing state. Also sets up the variables containing the process
// associated with each tab, the urls of the two pages and the WebContents of
// the "audio" page.
class ChromeRenderProcessHostBackgroundingTestWithAudio
    : public ChromeRenderProcessHostTest {
 public:
  ChromeRenderProcessHostBackgroundingTestWithAudio() {
    // Tests require that each tab has a different process.
    feature_list_.InitAndEnableFeature(features::kDisableProcessReuse);
  }

  ChromeRenderProcessHostBackgroundingTestWithAudio(
      const ChromeRenderProcessHostBackgroundingTestWithAudio&) = delete;
  ChromeRenderProcessHostBackgroundingTestWithAudio& operator=(
      const ChromeRenderProcessHostBackgroundingTestWithAudio&) = delete;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    ChromeRenderProcessHostTest::SetUpCommandLine(command_line);

    command_line->AppendSwitchASCII(
        switches::kAutoplayPolicy,
        switches::autoplay::kNoUserGestureRequiredPolicy);
  }

  void SetUpOnMainThread() override {
    ChromeRenderProcessHostTest::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());

    // Set up the server and get the test pages.
    base::FilePath test_data_dir;
    ASSERT_TRUE(
        base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &test_data_dir));
    embedded_test_server()->ServeFilesFromDirectory(
        test_data_dir.AppendASCII("chrome/test/data/"));
    audio_url_ = embedded_test_server()->GetURL("/extensions/loop_audio.html");
    no_audio_url_ = embedded_test_server()->GetURL("/title1.html");

    embedded_test_server()->StartAcceptingConnections();

    // Open a browser, navigate to the audio page and get its WebContents.
    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), audio_url_));
    audio_tab_web_contents_ =
        browser()->tab_strip_model()->GetActiveWebContents();

    // Create a new tab for the no audio page and confirm that the process of
    // each tab is different and that both are valid.
    audio_process_ =
        ProcessFromHandle(audio_tab_web_contents_->GetPrimaryMainFrame()
                              ->GetProcess()
                              ->GetProcess()
                              .Handle());
    WebContents* wc = ShowSingletonTab(no_audio_url_);
    no_audio_process_ = ProcessFromHandle(
        wc->GetPrimaryMainFrame()->GetProcess()->GetProcess().Handle());
    ASSERT_NE(audio_process_.Pid(), no_audio_process_.Pid());
    ASSERT_TRUE(no_audio_process_.IsValid());
    ASSERT_TRUE(audio_process_.IsValid());
  }

 protected:
  void WaitUntilBackgrounded(const base::Process& lhs,
                             bool lhs_backgrounded,
                             const base::Process& rhs,
                             bool rhs_backgrounded) {
    EXPECT_TRUE(base::test::RunUntil([&]() {
      return IsProcessBackgrounded(lhs) == lhs_backgrounded &&
             IsProcessBackgrounded(rhs) == rhs_backgrounded;
    }));
  }

  GURL audio_url_;
  GURL no_audio_url_;

  base::Process audio_process_;
  base::Process no_audio_process_;

  raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged>
      audio_tab_web_contents_;

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Test to make sure that a process is backgrounded when the audio stops playing
// from the active tab and there is an immediate tab switch.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostBackgroundingTestWithAudio,
                       ProcessPriorityAfterStoppedAudio) {
  // This test is invalid on platforms that can't set priority.
  if (!base::Process::CanSetPriority()) {
    return;
  }

  ShowSingletonTab(audio_url_);

  // Wait until the no audio page is backgrounded and the audio page is not
  // backgrounded.
  WaitUntilBackgrounded(no_audio_process_, true, audio_process_, false);
  // Pause the audio and immediately switch to the no audio tab.
  ASSERT_TRUE(
      content::ExecJs(audio_tab_web_contents_.get(),
                      "document.getElementById('audioPlayer').pause();"));
  ShowSingletonTab(no_audio_url_);

  // Wait until the no audio page is not backgrounded and the audio page is
  // backgrounded.
  WaitUntilBackgrounded(no_audio_process_, false, audio_process_, true);
}

// Test to make sure that a process is backgrounded automatically when audio
// stops playing from a hidden tab.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostBackgroundingTestWithAudio,
                       ProcessPriorityAfterAudioStopsOnNotVisibleTab) {
  // This test is invalid on platforms that can't set priority.
  if (!base::Process::CanSetPriority()) {
    return;
  }

  // Wait until the two pages are not backgrounded.
  WaitUntilBackgrounded(audio_process_, false, no_audio_process_, false);
  // Stop the audio.
  ASSERT_TRUE(
      content::ExecJs(audio_tab_web_contents_.get(),
                      "document.getElementById('audioPlayer').pause();"));

  // Wait until the no audio page is not backgrounded and the audio page is
  // backgrounded.
  WaitUntilBackgrounded(no_audio_process_, false, audio_process_, true);
}

// Test to make sure that a process is un-backgrounded automatically when
// audio
// starts playing from a backgrounded tab.
IN_PROC_BROWSER_TEST_F(ChromeRenderProcessHostBackgroundingTestWithAudio,
                       ProcessPriorityAfterAudioStartsFromBackgroundTab) {
  // This test is invalid on platforms that can't background.
  if (!base::Process::CanSetPriority()) {
    return;
  }

  // Stop the audio.
  ASSERT_TRUE(
      content::ExecJs(audio_tab_web_contents_.get(),
                      "document.getElementById('audioPlayer').pause();"));

  WaitUntilBackgrounded(no_audio_process_, false, audio_process_, true);

  // Start the audio from the backgrounded tab.
  ASSERT_TRUE(
      content::ExecJs(audio_tab_web_contents_.get(),
                      "document.getElementById('audioPlayer').play();"));

  // Wait until the two pages are not backgrounded.
  WaitUntilBackgrounded(no_audio_process_, false, audio_process_, false);
}
