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

#include "chrome/browser/lifetime/browser_close_manager.h"

#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "base/callback_list.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/background/extensions/background_mode_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_browsertest_utils.h"
#include "chrome/browser/download/download_core_service.h"
#include "chrome/browser/download/download_core_service_factory.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/glic/glic_pref_names.h"
#include "chrome/browser/lifetime/application_lifetime_desktop.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/sessions/tab_restore_service_load_waiter.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/tabs/tab_enums.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/unload_controller.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/chrome_features.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/download/public/common/download_item.h"
#include "components/download/public/common/download_target_info.h"
#include "components/javascript_dialogs/app_modal_dialog_controller.h"
#include "components/javascript_dialogs/app_modal_dialog_view.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/prefs/pref_service.h"
#include "components/sessions/core/tab_restore_service.h"
#include "components/sessions/core/tab_restore_service_observer.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/slow_download_http_response.h"
#include "content/public/test/test_navigation_observer.h"
#include "net/test/embedded_test_server/embedded_test_server.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/ash_switches.h"
#endif

namespace {

javascript_dialogs::AppModalDialogView* GetNextDialog() {
  javascript_dialogs::AppModalDialogController* dialog =
      ui_test_utils::WaitForAppModalDialog();
  CHECK(dialog->view());
  return dialog->view();
}

// Note: call |PrepareForDialog| on the relevant WebContents or Browser before
// trying to close it, to avoid flakiness. https://crbug.com/40430916
void AcceptClose() {
  GetNextDialog()->AcceptAppModalDialog();
}

// Note: call |PrepareForDialog| on the relevant WebContents or Browser before
// trying to close it, to avoid flakiness. https://crbug.com/40430916
void CancelClose() {
  GetNextDialog()->CancelAppModalDialog();
}

class AllBrowsersClosingCancelledObserver {
 public:
  explicit AllBrowsersClosingCancelledObserver(int count)
      : num_outstanding_(count) {
    closing_all_browsers_subscription_ =
        chrome::AddClosingAllBrowsersCallback(base::BindRepeating(
            &AllBrowsersClosingCancelledObserver::OnClosingAllBrowsersChanged,
            base::Unretained(this)));
  }

  void OnClosingAllBrowsersChanged(bool closing) {
    if (closing) {
      return;
    }

    ASSERT_GT(num_outstanding_, 0);
    if (!--num_outstanding_) {
      run_loop_.Quit();
    }
  }

  void Wait() {
    run_loop_.Run();
    EXPECT_LE(num_outstanding_, 0);
  }

 private:
  base::CallbackListSubscription closing_all_browsers_subscription_;
  int num_outstanding_;
  base::RunLoop run_loop_;
};

class TabRestoreServiceChangesObserver
    : public sessions::TabRestoreServiceObserver {
 public:
  explicit TabRestoreServiceChangesObserver(Profile* profile)
      : service_(TabRestoreServiceFactory::GetForProfile(profile)) {
    if (service_) {
      service_->AddObserver(this);
    }
  }

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

  ~TabRestoreServiceChangesObserver() override {
    if (service_) {
      service_->RemoveObserver(this);
    }
  }

  size_t changes_count() const { return changes_count_; }

 private:
  // sessions::TabRestoreServiceObserver:
  void TabRestoreServiceChanged(sessions::TabRestoreService*) override {
    changes_count_++;
  }

  // sessions::TabRestoreServiceObserver:
  void TabRestoreServiceDestroyed(sessions::TabRestoreService*) override {
    service_ = nullptr;
  }

  raw_ptr<sessions::TabRestoreService, DanglingUntriaged> service_ = nullptr;
  size_t changes_count_ = 0;
};

class TestBrowserCloseManager : public BrowserCloseManager {
 public:
  enum UserChoice {
    USER_CHOICE_USER_CANCELS_CLOSE,
    USER_CHOICE_USER_ALLOWS_CLOSE,
    NO_USER_CHOICE
  };

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

  static void AttemptClose(UserChoice user_choice) {
    scoped_refptr<BrowserCloseManager> browser_close_manager =
        new TestBrowserCloseManager(user_choice);
    browser_shutdown::SetTryingToQuit(true);
    browser_close_manager->StartClosingBrowsers();
  }

 protected:
  ~TestBrowserCloseManager() override = default;

  void ConfirmCloseWithPendingDownloads(
      int download_count,
      base::OnceCallback<void(bool)> callback) override {
    EXPECT_NE(NO_USER_CHOICE, user_choice_);
    switch (user_choice_) {
      case NO_USER_CHOICE:
      case USER_CHOICE_USER_CANCELS_CLOSE: {
        std::move(callback).Run(false);
        break;
      }
      case USER_CHOICE_USER_ALLOWS_CLOSE: {
        std::move(callback).Run(true);
        break;
      }
    }
  }

 private:
  explicit TestBrowserCloseManager(UserChoice user_choice)
      : user_choice_(user_choice) {}

  UserChoice user_choice_;
};

class TestDownloadManagerDelegate : public ChromeDownloadManagerDelegate {
 public:
  explicit TestDownloadManagerDelegate(Profile* profile)
      : ChromeDownloadManagerDelegate(profile) {
    GetDownloadIdReceiverCallback().Run(download::DownloadItem::kInvalidId + 1);
  }
  ~TestDownloadManagerDelegate() override = default;

  bool DetermineDownloadTarget(
      download::DownloadItem* item,
      download::DownloadTargetCallback* callback) override {
    auto set_dangerous = [](download::DownloadTargetCallback callback,
                            download::DownloadTargetInfo target_info) {
      target_info.danger_type = download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL;
      std::move(callback).Run(std::move(target_info));
    };

    download::DownloadTargetCallback dangerous_callback =
        base::BindOnce(set_dangerous, std::move(*callback));
    bool run = ChromeDownloadManagerDelegate::DetermineDownloadTarget(
        item, &dangerous_callback);
    // ChromeDownloadManagerDelegate::DetermineDownloadTarget() needs to run the
    // |callback|.
    DCHECK(run);
    DCHECK(!dangerous_callback);
    return true;
  }
};

#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
class FakeBackgroundModeManager : public BackgroundModeManager {
 public:
  FakeBackgroundModeManager()
      : BackgroundModeManager(*base::CommandLine::ForCurrentProcess(),
                              &g_browser_process->profile_manager()
                                   ->GetProfileAttributesStorage()),
        suspended_(false) {}

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

  void SuspendBackgroundMode() override {
    BackgroundModeManager::SuspendBackgroundMode();
    suspended_ = true;
  }

  void ResumeBackgroundMode() override {
    BackgroundModeManager::ResumeBackgroundMode();
    suspended_ = false;
  }

  bool IsBackgroundModeSuspended() { return suspended_; }

 private:
  bool suspended_;
};
#endif  // BUILDFLAG(ENABLE_BACKGROUND_MODE)

}  // namespace

class BrowserCloseManagerBrowserTest : public InProcessBrowserTest {
 protected:
  void SetUpOnMainThread() override {
    SessionStartupPref::SetStartupPref(
        browser()->GetProfile(), SessionStartupPref(SessionStartupPref::LAST));
    browsers_.push_back(browser());
    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
        &content::SlowDownloadHttpResponse::HandleSlowDownloadRequest));
    ASSERT_TRUE(embedded_test_server()->Start());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
#if BUILDFLAG(IS_CHROMEOS)
    command_line->AppendSwitch(
        ash::switches::kIgnoreUserProfileMappingForTests);
#endif
  }

  void CreateStalledDownload(BrowserWindowInterface* browser) {
    ASSERT_TRUE(embedded_test_server()->Started());

    GURL slow_download_url = embedded_test_server()->GetURL(
        content::SlowDownloadHttpResponse::kKnownSizeUrl);

    content::DownloadTestObserverInProgress observer(
        browser->GetProfile()->GetDownloadManager(), 1);
    SetPromptForDownload(browser, false);
    ui_test_utils::NavigateToURLWithDisposition(
        browser, slow_download_url, WindowOpenDisposition::NEW_BACKGROUND_TAB,
        ui_test_utils::BROWSER_TEST_NO_WAIT);
    observer.WaitForFinished();
    EXPECT_EQ(1UL, observer.NumDownloadsSeenInState(
                       download::DownloadItem::IN_PROGRESS));
  }

  void PrepareForDialog(content::WebContents* web_contents) {
    content::PrepContentsForBeforeUnloadTest(web_contents);
  }

  void PrepareForDialog(BrowserWindowInterface* browser) {
    for (int i = 0; i < browser->GetTabStripModel()->count(); i++) {
      PrepareForDialog(browser->GetTabStripModel()->GetWebContentsAt(i));
    }
  }

  void WaitForAllBrowsersToClose() {
    // Wait until all browser KeepAlive instances have been reset, indicating
    // all browsers have been closed and destroyed.
    EXPECT_TRUE(base::test::RunUntil([&]() {
      return KeepAliveRegistry::GetInstance()->IsOriginRegistered(
                 KeepAliveOrigin::BROWSER) == 0;
    }));
  }

  std::vector<raw_ptr<BrowserWindowInterface, VectorExperimental>> browsers_;
};

IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest, TestSingleTabShutdown) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browser());

  AllBrowsersClosingCancelledObserver cancel_observer(1);
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  ui_test_utils::BrowserDestroyedObserver observer(browser());
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  observer.Wait();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestShutdownMoreThanOnce) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browser());

  AllBrowsersClosingCancelledObserver cancel_observer(1);
  chrome::CloseAllBrowsersAndQuit();
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  ui_test_utils::BrowserDestroyedObserver observer(browser());
  chrome::CloseAllBrowsersAndQuit();
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  observer.Wait();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest, PRE_TestSessionRestore) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/beforeunload.html"))));
  AddBlankTabAndShow(browser());
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL(chrome::kChromeUIAboutURL))));
  PrepareForDialog(browser());

  AllBrowsersClosingCancelledObserver cancel_observer(1);
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());

  browser()->tab_strip_model()->CloseWebContentsAt(
      1, TabCloseTypes::CLOSE_USER_GESTURE);
  content::TestNavigationObserver navigation_observer(
      browser()->tab_strip_model()->GetActiveWebContents(), 1);
  ASSERT_NO_FATAL_FAILURE(NavigateToURLWithDisposition(
      browser(), GURL(chrome::kChromeUIVersionURL),
      WindowOpenDisposition::CURRENT_TAB, ui_test_utils::BROWSER_TEST_NO_WAIT));
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  navigation_observer.Wait();

  ui_test_utils::BrowserDestroyedObserver observer(browser());
  chrome::CloseAllBrowsersAndQuit();
  observer.Wait();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Test that the tab closed after the aborted shutdown attempt is not re-opened
// when restoring the session.
// Flaky on chromium.chromeos, chromium.linux, and chromium.mac bots. See
// https://crbug.com/40729351. It was flaky on Windows, but crrev.com/c/2559156,
// which added retries to ReplaceFile, should fix the Windows flakiness.
#if BUILDFLAG(IS_WIN)
#define MAYBE_TestSessionRestore TestSessionRestore
#else
#define MAYBE_TestSessionRestore DISABLED_TestSessionRestore
#endif
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       MAYBE_TestSessionRestore) {
  // The testing framework launches Chrome with about:blank as args.
  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  EXPECT_EQ(
      GURL(chrome::kChromeUIVersionURL),
      browser()->tab_strip_model()->GetWebContentsAt(0)->GetLastCommittedURL());
  EXPECT_EQ(
      GURL("about:blank"),
      browser()->tab_strip_model()->GetWebContentsAt(1)->GetLastCommittedURL());
}

// Test that browser windows are only closed if all browsers are ready to close
// and that all beforeunload dialogs are shown again after a cancel.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest, TestMultipleWindows) {
  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[0]);
  PrepareForDialog(browsers_[1]);

  // Cancel shutdown on the first beforeunload event.
  {
    AllBrowsersClosingCancelledObserver cancel_observer(1);
    chrome::CloseAllBrowsersAndQuit();
    ASSERT_NO_FATAL_FAILURE(CancelClose());
    cancel_observer.Wait();
  }
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
  EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());

  // Cancel shutdown on the second beforeunload event.
  {
    AllBrowsersClosingCancelledObserver cancel_observer(2);
    chrome::CloseAllBrowsersAndQuit();
    ASSERT_NO_FATAL_FAILURE(AcceptClose());
    ASSERT_NO_FATAL_FAILURE(CancelClose());
    cancel_observer.Wait();
  }
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
  EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());

  // Allow shutdown for both beforeunload events.
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Test that tabs in the same window with a beforeunload event that hangs are
// treated the same as the user accepting the close, but do not close the tab
// early.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestHangInBeforeUnloadMultipleTabs) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0],
      embedded_test_server()->GetURL("/beforeunload_hang.html"))));
  AddBlankTabAndShow(browsers_[0]);
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  AddBlankTabAndShow(browsers_[0]);
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0],
      embedded_test_server()->GetURL("/beforeunload_hang.html"))));
  // Disable the hang monitor in the tab that is not expected to hang, so that
  // the dialog is guaranteed to show.
  PrepareForDialog(browsers_[0]->tab_strip_model()->GetWebContentsAt(1));

  AllBrowsersClosingCancelledObserver cancel_observer(1);
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  // All tabs should still be open.
  EXPECT_EQ(3, browsers_[0]->tab_strip_model()->count());

  chrome::CloseAllBrowsersAndQuit();

  ui_test_utils::BrowserDestroyedObserver observer(browsers_[0]);
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  observer.Wait();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Test that tabs in different windows with a beforeunload event that hangs are
// treated the same as the user accepting the close, but do not close the tab
// early.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestHangInBeforeUnloadMultipleWindows) {
  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0],
      embedded_test_server()->GetURL("/beforeunload_hang.html"))));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[2],
      embedded_test_server()->GetURL("/beforeunload_hang.html"))));
  // Disable the hang monitor in the tab that is not expected to hang, so that
  // the dialog is guaranteed to show.
  PrepareForDialog(browsers_[1]);

  AllBrowsersClosingCancelledObserver cancel_observer(2);
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  // All windows should still be open.
  EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
  EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());
  EXPECT_EQ(1, browsers_[2]->tab_strip_model()->count());

  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());

  WaitForAllBrowsersToClose();
}

// Test that tabs that are slow to respond are not closed prematurely.
// Regression for crbug.com/40361681 caused some of tabs to be closed even if
// user chose to cancel browser close.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestUnloadMultipleSlowTabs) {
  const int kTabCount = 5;
  const int kResponsiveTabIndex = 2;
  // Create tab strip with all tabs except one responding after
  // RenderViewHostImpl::kUnloadTimeoutMS.
  // Minimum configuration is two slow tabs and then responsive tab.
  // But we also want to check how slow tabs behave in tail.
  for (int i = 0; i < kTabCount; i++) {
    if (i) {
      AddBlankTabAndShow(browsers_[0]);
    }
    ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browsers_[0],
        embedded_test_server()->GetURL((i == kResponsiveTabIndex)
                                           ? "/beforeunload.html"
                                           : "/beforeunload_slow.html"))));
  }
  // Disable the hang monitor in the tab that is not expected to hang, so that
  // the dialog is guaranteed to show.
  PrepareForDialog(
      browsers_[0]->tab_strip_model()->GetWebContentsAt(kResponsiveTabIndex));

  AllBrowsersClosingCancelledObserver cancel_observer(1);
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());

  // All tabs should still be open.
  EXPECT_EQ(kTabCount, browsers_[0]->tab_strip_model()->count());

  // Quit, this time accepting close confirmation dialog.
  ui_test_utils::BrowserDestroyedObserver observer(browsers_[0]);
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  observer.Wait();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Test that tabs in different windows with a slow beforeunload event response
// are treated the same as the user accepting the close, but do not close the
// tab early.
// Regression for crbug.com/40361681 caused CHECK in tabstrip.
// Flaky: https://crbug.com/41375143
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       DISABLED_TestBeforeUnloadMultipleSlowWindows) {
  const int kBrowserCount = 5;
  const int kResponsiveBrowserIndex = 2;
  // Create multiple browsers with all tabs except one responding after
  // RenderViewHostImpl::kUnloadTimeoutMS .
  // Minimum configuration is just one browser with slow tab and then
  // browser with responsive tab.
  // But we also want to check how slow tabs behave in tail and make test
  // more robust.
  for (int i = 0; i < kBrowserCount; i++) {
    if (i) {
      browsers_.push_back(CreateBrowser(browser()->GetProfile()));
    }
    ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browsers_[i],
        embedded_test_server()->GetURL((i == kResponsiveBrowserIndex)
                                           ? "/beforeunload.html"
                                           : "/beforeunload_slow.html"))));
  }
  // Disable the hang monitor in the tab that is not expected to hang, so that
  // the dialog is guaranteed to show.
  PrepareForDialog(browsers_[kResponsiveBrowserIndex]);

  AllBrowsersClosingCancelledObserver cancel_observer(kResponsiveBrowserIndex +
                                                      1);
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());

  // All windows should still be open.
  for (int i = 0; i < kBrowserCount; i++) {
    EXPECT_EQ(1, browsers_[i]->tab_strip_model()->count());
  }

  // Quit, this time accepting close confirmation dialog.
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Test that a window created during shutdown is closed.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestAddWindowDuringShutdown) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[0]);

  chrome::CloseAllBrowsersAndQuit();
  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Test that a window created during shutdown with a beforeunload handler can
// cancel the shutdown.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestAddWindowWithBeforeUnloadDuringShutdown) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[0]);

  AllBrowsersClosingCancelledObserver cancel_observer(2);
  chrome::CloseAllBrowsersAndQuit();
  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[1]);
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
  EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());

  // Allow shutdown for both beforeunload dialogs.
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Test that tabs added during shutdown are closed.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestAddTabDuringShutdown) {
  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[0]);
  PrepareForDialog(browsers_[1]);

  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  AddBlankTabAndShow(browsers_[0]);
  AddBlankTabAndShow(browsers_[1]);
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Test that tabs created during shutdown with beforeunload handlers can cancel
// the shutdown.

IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestAddTabWithBeforeUnloadDuringShutdown) {
  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[0]);
  PrepareForDialog(browsers_[1]);

  AllBrowsersClosingCancelledObserver cancel_observer(2);
  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  AddBlankTabAndShow(browsers_[0]);
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  AddBlankTabAndShow(browsers_[1]);
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[0]);
  PrepareForDialog(browsers_[1]);
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_EQ(2, browsers_[0]->tab_strip_model()->count());
  EXPECT_EQ(2, browsers_[1]->tab_strip_model()->count());

  chrome::CloseAllBrowsersAndQuit();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());

  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// TODO(crbug.com/41314042):
// BrowserCloseManagerBrowserTest.AddBeforeUnloadDuringClosing flaky on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_AddBeforeUnloadDuringClosing DISABLED_AddBeforeUnloadDuringClosing
#else
#define MAYBE_AddBeforeUnloadDuringClosing AddBeforeUnloadDuringClosing
#endif

IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       MAYBE_AddBeforeUnloadDuringClosing) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/title1.html"))));

  // Open second window.
  auto browser_created_observer =
      std::make_optional<ui_test_utils::BrowserCreatedObserver>();
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), embedded_test_server()->GetURL("/beforeunload.html"),
      WindowOpenDisposition::NEW_WINDOW,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
  BrowserWindowInterface* browser2 = browser_created_observer->Wait();
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());
  EXPECT_TRUE(content::WaitForLoadStop(
      browser2->GetTabStripModel()->GetWebContentsAt(0)));

  // Let's work with second window only.
  // This page has beforeunload handler already.
  EXPECT_TRUE(browser2->GetTabStripModel()
                  ->GetWebContentsAt(0)
                  ->NeedToFireBeforeUnloadOrUnloadEvents());
  // This page doesn't have beforeunload handler. Yet.
  ui_test_utils::NavigateToURLWithDisposition(
      browser2, embedded_test_server()->GetURL("/title2.html"),
      WindowOpenDisposition::NEW_FOREGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  EXPECT_TRUE(content::WaitForLoadStop(
      browser2->GetTabStripModel()->GetWebContentsAt(1)));
  EXPECT_FALSE(browser2->GetTabStripModel()
                   ->GetWebContentsAt(1)
                   ->NeedToFireBeforeUnloadOrUnloadEvents());
  EXPECT_EQ(2, browser2->GetTabStripModel()->count());

  PrepareForDialog(browser2);

  // The test.

  TabRestoreServiceChangesObserver restore_observer(browser2->GetProfile());
  chrome::CloseWindow(browser2);
  // Just to be sure CloseWindow doesn't have asynchronous tasks
  // that could have an impact.
  content::RunAllPendingInMessageLoop();

  // Closing browser shouldn't happen because of beforeunload handler.
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());
  // Add beforeunload handler for the 2nd (title2.html) tab which haven't had it
  // yet.
  ASSERT_TRUE(
      content::ExecJs(browser2->GetTabStripModel()->GetWebContentsAt(1),
                      "window.addEventListener('beforeunload', "
                      "function(event) { event.returnValue = 'Foo'; });"));
  EXPECT_TRUE(browser2->GetTabStripModel()
                  ->GetWebContentsAt(1)
                  ->NeedToFireBeforeUnloadOrUnloadEvents());
  // Accept closing the first tab.
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  // Just to be sure accepting a dialog doesn't have asynchronous tasks
  // that could have an impact.
  content::RunAllPendingInMessageLoop();
  // It shouldn't close the whole window/browser.
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());
  EXPECT_EQ(2, browser2->GetTabStripModel()->count());
  // Accept closing the second tab.
  base::MockCallback<BrowserWindowInterface::BrowserDidCloseCallback>
      browser_did_close_callback;
  EXPECT_CALL(browser_did_close_callback, Run).Times(1);
  base::CallbackListSubscription subscription =
      browser2->RegisterBrowserDidClose(browser_did_close_callback.Get());
  ui_test_utils::BrowserDestroyedObserver observer(browser2);
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  observer.Wait();
  // Now the second window/browser should be closed.
  EXPECT_EQ(1u, restore_observer.changes_count());

  // Restore the closed browser.
  browser_created_observer.emplace();
  chrome::OpenWindowWithRestoredTabs(browser()->GetProfile());
  browser2 = browser_created_observer->Wait();
  EXPECT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());

  // Check the restored browser contents.
  EXPECT_EQ(2, browser2->GetTabStripModel()->count());
  EXPECT_EQ(
      embedded_test_server()->GetURL("/beforeunload.html"),
      browser2->GetTabStripModel()->GetWebContentsAt(0)->GetLastCommittedURL());
  EXPECT_EQ(
      embedded_test_server()->GetURL("/title2.html"),
      browser2->GetTabStripModel()->GetWebContentsAt(1)->GetLastCommittedURL());
}

IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestCloseTabDuringShutdown) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[0]);

  AllBrowsersClosingCancelledObserver cancel_observer(1);
  chrome::CloseAllBrowsersAndQuit();

  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[1]);
  browsers_[1]->tab_strip_model()->CloseAllTabs();
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();

  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_FALSE(
      UnloadController::From(browsers_[0])->is_attempting_to_close_browser());
  EXPECT_FALSE(
      UnloadController::From(browsers_[1])->is_attempting_to_close_browser());
  EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
  EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());

  chrome::CloseAllBrowsersAndQuit();
  browsers_[1]->tab_strip_model()->CloseAllTabs();
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());

  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestOpenAndCloseWindowDuringShutdown) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[0]);

  AllBrowsersClosingCancelledObserver cancel_observer(2);
  chrome::CloseAllBrowsersAndQuit();

  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[1]);
  ASSERT_GE(browsers_.size(), 2u);
  EXPECT_FALSE(UnloadController::From(browsers_[1])->HandleBeforeClose());
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
  EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());

  chrome::CloseAllBrowsersAndQuit();
  ASSERT_GE(browsers_.size(), 2u);
  EXPECT_FALSE(UnloadController::From(browsers_[1])->HandleBeforeClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());

  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestCloseWindowDuringShutdown) {
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[0], embedded_test_server()->GetURL("/beforeunload.html"))));
  browsers_.push_back(CreateBrowser(browser()->GetProfile()));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browsers_[1], embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browsers_[0]);
  PrepareForDialog(browsers_[1]);

  AllBrowsersClosingCancelledObserver cancel_observer(1);
  chrome::CloseAllBrowsersAndQuit();

  ASSERT_FALSE(browsers_.empty());
  EXPECT_FALSE(UnloadController::From(browsers_[0])->HandleBeforeClose());
  ASSERT_NO_FATAL_FAILURE(CancelClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_EQ(1, browsers_[0]->tab_strip_model()->count());
  EXPECT_EQ(1, browsers_[1]->tab_strip_model()->count());

  chrome::CloseAllBrowsersAndQuit();
  ASSERT_FALSE(browsers_.empty());
  EXPECT_FALSE(UnloadController::From(browsers_[0])->HandleBeforeClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  ASSERT_NO_FATAL_FAILURE(AcceptClose());

  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Check that it is possible to restore a window with the last session after
// ignoring unload handlers shutdown.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       PRE_RestoreWindowWithIgnoreUnload) {
  ASSERT_NO_FATAL_FAILURE(
      ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
          browser(), embedded_test_server()->GetURL("/beforeunload.html"),
          WindowOpenDisposition::NEW_FOREGROUND_TAB,
          ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP)));
  PrepareForDialog(browser());
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), embedded_test_server()->GetURL("/title2.html"),
      WindowOpenDisposition::NEW_FOREGROUND_TAB,
      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
  // Set silent exit to hit ignore unload handlers flow
  browser_shutdown::OnShutdownStarting(
      browser_shutdown::ShutdownType::kSilentExit);
  ui_test_utils::BrowserDestroyedObserver observer(browser());
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&chrome::CloseAllBrowsers));
  observer.Wait();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       RestoreWindowWithIgnoreUnload) {
  sessions::TabRestoreService* tab_restore_service =
      TabRestoreServiceFactory::GetForProfile(browser()->GetProfile());
  ASSERT_TRUE(tab_restore_service);
  TabRestoreServiceLoadWaiter waiter(tab_restore_service);
  tab_restore_service->LoadTabsFromLastSession();
  waiter.Wait();
  ASSERT_TRUE(!tab_restore_service->entries().empty());
  EXPECT_EQ(tab_restore_service->entries().front()->type,
            sessions::tab_restore::Type::WINDOW);
}

// Mac has its own in-progress download prompt in app_controller_mac.mm, so
// BrowserCloseManager should simply close all browsers. If there are no
// browsers, it should not crash.
#if BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest, TestWithDownloads) {
  ASSERT_NO_FATAL_FAILURE(CreateStalledDownload(browser()));

  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::NO_USER_CHOICE);
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  EXPECT_EQ(1, DownloadCoreService::BlockingShutdownCountAllProfiles());

  // Attempting to close again should not crash.
  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::NO_USER_CHOICE);
}
#else  // BUILDFLAG(IS_MAC)

// Test shutdown with a DANGEROUS_URL download undecided.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestWithDangerousUrlDownload) {
  // Set up the fake delegate that forces the download to be malicious.
  std::unique_ptr<TestDownloadManagerDelegate> test_delegate(
      new TestDownloadManagerDelegate(browser()->GetProfile()));
  DownloadCoreServiceFactory::GetForBrowserContext(browser()->GetProfile())
      ->SetDownloadManagerDelegateForTesting(std::move(test_delegate));

  // Run a dangerous download, but the user doesn't make a decision.
  // This .swf normally would be categorized as DANGEROUS_FILE, but
  // TestDownloadManagerDelegate turns it into DANGEROUS_URL.
  GURL download_url(
      embedded_test_server()->GetURL("/downloads/dangerous/dangerous.swf"));
  content::DownloadTestObserverInterrupted observer(
      browser()->GetProfile()->GetDownloadManager(), 1,
      content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_QUIT);
  ui_test_utils::NavigateToURLWithDisposition(
      browser(), GURL(download_url), WindowOpenDisposition::NEW_BACKGROUND_TAB,
      ui_test_utils::BROWSER_TEST_NO_WAIT);
  observer.WaitForFinished();

  // Check that the download manager has the expected state.
  EXPECT_EQ(1,
            browser()->GetProfile()->GetDownloadManager()->InProgressCount());
  EXPECT_EQ(
      0,
      browser()->GetProfile()->GetDownloadManager()->BlockingShutdownCount());

  // Close the browser with no user action.
  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::NO_USER_CHOICE);
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

// Test shutdown with a download in progress.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest, TestWithDownloads) {
  ASSERT_NO_FATAL_FAILURE(CreateStalledDownload(browser()));
  content::TestNavigationObserver navigation_observer(
      browser()->tab_strip_model()->GetActiveWebContents(), 1);
  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::USER_CHOICE_USER_CANCELS_CLOSE);
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  navigation_observer.Wait();
  EXPECT_EQ(GURL(chrome::kChromeUIDownloadsURL), browser()
                                                     ->tab_strip_model()
                                                     ->GetActiveWebContents()
                                                     ->GetLastCommittedURL());

  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::USER_CHOICE_USER_ALLOWS_CLOSE);
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  if (browser_defaults::kBrowserAliveWithNoWindows) {
    EXPECT_EQ(1, DownloadCoreService::BlockingShutdownCountAllProfiles());
  } else {
    EXPECT_EQ(0, DownloadCoreService::BlockingShutdownCountAllProfiles());
  }
}

// Test shutdown with a download in progress in an off-the-record profile.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestWithOffTheRecordDownloads) {
  Profile* otr_profile =
      browser()->GetProfile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
  BrowserWindowInterface* otr_browser = CreateBrowser(otr_profile);
  {
    ui_test_utils::BrowserDestroyedObserver observer(browser());
    browser()->GetWindow()->Close();
    observer.Wait();
  }
  ASSERT_NO_FATAL_FAILURE(CreateStalledDownload(otr_browser));
  content::TestNavigationObserver navigation_observer(
      otr_browser->tab_strip_model()->GetActiveWebContents(), 1);
  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::USER_CHOICE_USER_CANCELS_CLOSE);
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  navigation_observer.Wait();
  EXPECT_EQ(GURL(chrome::kChromeUIDownloadsURL), otr_browser->tab_strip_model()
                                                     ->GetActiveWebContents()
                                                     ->GetLastCommittedURL());

  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::USER_CHOICE_USER_ALLOWS_CLOSE);
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  EXPECT_EQ(0, DownloadCoreService::BlockingShutdownCountAllProfiles());
}

// Test shutdown with a download in progress in a regular profile an inconito
// browser is opened and closed. While there are active downloads, closing the
// incognito window shouldn't block on the active downloads which belong to the
// parent profile.
// TODO(crbug.com/40576829): Fix the notification expectation around the
// call to AttemptClose.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       DISABLED_TestWithOffTheRecordWindowAndRegularDownload) {
  Profile* otr_profile =
      browser()->GetProfile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
  BrowserWindowInterface* otr_browser = CreateBrowser(otr_profile);
  ASSERT_NO_FATAL_FAILURE(CreateStalledDownload(browser()));

  content::TestNavigationObserver navigation_observer(
      otr_browser->tab_strip_model()->GetActiveWebContents(), 1);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(otr_browser, GURL("about:blank")));
  navigation_observer.Wait();

  int num_downloads_blocking = 0;
  ASSERT_EQ(UnloadController::DownloadCloseType::kOk,
            UnloadController::From(otr_browser)
                ->OkToCloseWithInProgressDownloads(&num_downloads_blocking));
  ASSERT_EQ(0, num_downloads_blocking);

  {
    ui_test_utils::BrowserDestroyedObserver observer(otr_browser);
    otr_browser->GetWindow()->Close();
    observer.Wait();
  }

  ASSERT_EQ(UnloadController::DownloadCloseType::kBrowserShutdown,
            UnloadController::From(browser())->OkToCloseWithInProgressDownloads(
                &num_downloads_blocking));
  ASSERT_EQ(1, num_downloads_blocking);

  {
    ui_test_utils::BrowserDestroyedObserver observer(browser());
    TestBrowserCloseManager::AttemptClose(
        TestBrowserCloseManager::USER_CHOICE_USER_ALLOWS_CLOSE);
    observer.Wait();
  }

  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  if (browser_defaults::kBrowserAliveWithNoWindows) {
    EXPECT_EQ(1, DownloadCoreService::BlockingShutdownCountAllProfiles());
  } else {
    EXPECT_EQ(0, DownloadCoreService::BlockingShutdownCountAllProfiles());
  }
}

// Test shutdown with a download in progress from one profile, where the only
// open windows are for another profile.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       TestWithDownloadsFromDifferentProfiles) {
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  std::unique_ptr<Profile> other_profile;
  {
    base::FilePath path =
        profile_manager->user_data_dir().AppendASCII("test_profile");
    base::ScopedAllowBlockingForTesting allow_blocking;
    if (!base::PathExists(path)) {
      ASSERT_TRUE(base::CreateDirectory(path));
    }
    other_profile = Profile::CreateProfile(path, nullptr,
                                           Profile::CreateMode::kSynchronous);
  }
  Profile* other_profile_ptr = other_profile.get();
  profile_manager->RegisterTestingProfile(std::move(other_profile), true);
  BrowserWindowInterface* other_profile_browser =
      CreateBrowser(other_profile_ptr);
  ui_test_utils::WaitUntilBrowserBecomeActive(other_profile_browser);

  ASSERT_NO_FATAL_FAILURE(CreateStalledDownload(browser()));
  {
    ui_test_utils::BrowserDestroyedObserver observer(browser());
    browser()->GetWindow()->Close();
    observer.Wait();
  }

  // When the shutdown is cancelled, the downloads page should be opened in a
  // browser for that profile. Because there are no browsers for that profile, a
  // new browser should be opened.
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::USER_CHOICE_USER_CANCELS_CLOSE);
  BrowserWindowInterface* opened_browser = browser_created_observer.Wait();
  ASSERT_TRUE(opened_browser);
  ui_test_utils::WaitUntilBrowserBecomeActive(opened_browser);
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_NE(other_profile_ptr, opened_browser->GetProfile());
  EXPECT_TRUE(content::WaitForLoadStop(
      opened_browser->tab_strip_model()->GetActiveWebContents()));
  EXPECT_EQ(GURL(chrome::kChromeUIDownloadsURL),
            opened_browser->tab_strip_model()
                ->GetActiveWebContents()
                ->GetVisibleURL());
  EXPECT_EQ(GURL("about:blank"), other_profile_browser->tab_strip_model()
                                     ->GetActiveWebContents()
                                     ->GetVisibleURL());

  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::USER_CHOICE_USER_ALLOWS_CLOSE);
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  if (browser_defaults::kBrowserAliveWithNoWindows) {
    EXPECT_EQ(1, DownloadCoreService::BlockingShutdownCountAllProfiles());
  } else {
    EXPECT_EQ(0, DownloadCoreService::BlockingShutdownCountAllProfiles());
  }
}

// Fails on ChromeOS and Linux, times out on Win. crbug.com/41335918
// Test shutdown with downloads in progress and beforeunload handlers.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerBrowserTest,
                       DISABLED_TestBeforeUnloadAndDownloads) {
  ASSERT_NO_FATAL_FAILURE(CreateStalledDownload(browser()));
  ASSERT_NO_FATAL_FAILURE(ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), embedded_test_server()->GetURL("/beforeunload.html"))));
  PrepareForDialog(browser());

  AllBrowsersClosingCancelledObserver cancel_observer(1);
  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::USER_CHOICE_USER_CANCELS_CLOSE);
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  cancel_observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());

  ui_test_utils::BrowserDestroyedObserver observer(browser());
  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::USER_CHOICE_USER_ALLOWS_CLOSE);
  ASSERT_NO_FATAL_FAILURE(AcceptClose());
  observer.Wait();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}

#endif  // BUILDFLAG(IS_MAC)

#if BUILDFLAG(ENABLE_BACKGROUND_MODE)

class BrowserCloseManagerWithBackgroundModeBrowserTest
    : public BrowserCloseManagerBrowserTest {
 public:
  BrowserCloseManagerWithBackgroundModeBrowserTest() = default;

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

  void SetUpOnMainThread() override {
    BrowserCloseManagerBrowserTest::SetUpOnMainThread();
    g_browser_process->set_background_mode_manager_for_test(
        std::unique_ptr<BackgroundModeManager>(new FakeBackgroundModeManager));
  }

  bool IsBackgroundModeSuspended() {
    return static_cast<FakeBackgroundModeManager*>(
               g_browser_process->background_mode_manager())
        ->IsBackgroundModeSuspended();
  }
};

// Check that background mode is suspended when closing all browsers unless we
// are quitting and that background mode is resumed when a new browser window is
// opened.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerWithBackgroundModeBrowserTest,
                       CloseAllBrowsersWithBackgroundMode) {
  EXPECT_FALSE(IsBackgroundModeSuspended());
  std::unique_ptr<ScopedKeepAlive> tmp_keep_alive;
  std::unique_ptr<ScopedProfileKeepAlive> tmp_profile_keep_alive;
  Profile* profile = browser()->GetProfile();
  {
    tmp_keep_alive = std::make_unique<ScopedKeepAlive>(
        KeepAliveOrigin::PANEL_VIEW, KeepAliveRestartOption::DISABLED);
    tmp_profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>(
        profile, ProfileKeepAliveOrigin::kBrowserWindow);
    ui_test_utils::BrowserDestroyedObserver observer(browser());
    chrome::CloseAllBrowsers();
    observer.Wait();
  }
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  EXPECT_TRUE(IsBackgroundModeSuspended());

  // Background mode should be resumed when a new browser window is opened.
  chrome::NewEmptyWindow(profile);
  BrowserWindowInterface* new_browser = ui_test_utils::WaitForBrowserToOpen();
  tmp_keep_alive.reset();
  tmp_profile_keep_alive.reset();
  EXPECT_FALSE(IsBackgroundModeSuspended());

  // Background mode should not be suspended when quitting.
  ui_test_utils::BrowserDestroyedObserver observer(new_browser);
  chrome::CloseAllBrowsersAndQuit();
  observer.Wait();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  EXPECT_FALSE(IsBackgroundModeSuspended());
}

// Check that closing the last browser window individually does not affect
// background mode.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerWithBackgroundModeBrowserTest,
                       DISABLED_CloseSingleBrowserWithBackgroundMode) {
  EXPECT_FALSE(IsBackgroundModeSuspended());
  ui_test_utils::BrowserDestroyedObserver observer(browser());
  browser()->GetWindow()->Close();
  observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  EXPECT_FALSE(IsBackgroundModeSuspended());
}

// Check that closing all browsers with no browser windows open suspends
// background mode but does not cause Chrome to quit.
IN_PROC_BROWSER_TEST_F(
    BrowserCloseManagerWithBackgroundModeBrowserTest,
    DISABLED_CloseAllBrowsersWithNoOpenBrowsersWithBackgroundMode) {
  EXPECT_FALSE(IsBackgroundModeSuspended());
  ScopedKeepAlive tmp_keep_alive(KeepAliveOrigin::PANEL_VIEW,
                                 KeepAliveRestartOption::DISABLED);
  ScopedProfileKeepAlive tmp_profile_keep_alive(
      browser()->GetProfile(), ProfileKeepAliveOrigin::kBrowserWindow);
  ui_test_utils::BrowserDestroyedObserver observer(browser());
  browser()->GetWindow()->Close();
  observer.Wait();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  EXPECT_FALSE(IsBackgroundModeSuspended());

  chrome::CloseAllBrowsers();
  EXPECT_FALSE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
  EXPECT_TRUE(IsBackgroundModeSuspended());
}

#endif  // BUILDFLAG(ENABLE_BACKGROUND_MODE)


class BrowserCloseManagerWithGlicBrowserTest
    : public BrowserCloseManagerBrowserTest {
 public:
  BrowserCloseManagerWithGlicBrowserTest() = default;

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

  void SetUp() override {
    feature_list_.InitWithFeatures({features::kGlic}, {});
    BrowserCloseManagerBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    BrowserCloseManagerBrowserTest::SetUpOnMainThread();
    g_browser_process->local_state()->SetBoolean(
        glic::prefs::kGlicLauncherEnabled, true);
  }

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

// Check that closing the last browser window doesn't crash when glic is
// enabled. Regression test for crbug.com/390203045.
IN_PROC_BROWSER_TEST_F(BrowserCloseManagerWithGlicBrowserTest,
                       CloseSingleBrowserWindowWithGlic) {
  TestBrowserCloseManager::AttemptClose(
      TestBrowserCloseManager::NO_USER_CHOICE);
  WaitForAllBrowsersToClose();
  EXPECT_TRUE(browser_shutdown::IsTryingToQuit());
  EXPECT_TRUE(GlobalBrowserCollection::GetInstance()->IsEmpty());
}
