// 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 <algorithm>
#include <iterator>
#include <ranges>
#include <vector>

#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "build/build_config.h"
#include "chrome/browser/background/extensions/background_mode_manager.h"
#include "chrome/browser/background/glic/glic_background_mode_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_core_service.h"
#include "chrome/browser/download/download_core_service_factory.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_manager_service.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/unload_controller.h"
#include "chrome/common/buildflags.h"
#include "content/public/browser/web_contents.h"

#if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS)
#include "chrome/browser/notifications/notification_ui_manager.h"
#endif

namespace {

// Navigates a browser window for |profile|, creating one if necessary, to the
// downloads page if there are downloads in progress for |profile|.
void ShowInProgressDownloads(Profile* profile) {
  DownloadCoreService* download_core_service =
      DownloadCoreServiceFactory::GetForBrowserContext(profile);
  if (download_core_service &&
      download_core_service->BlockingShutdownCount() > 0) {
    chrome::ScopedTabbedBrowserDisplayer displayer(profile);
    chrome::ShowDownloads(displayer.browser_window_interface());
  }
}

}  // namespace

BrowserCloseManager::BrowserCloseManager() : current_browser_(nullptr) {}
BrowserCloseManager::~BrowserCloseManager() = default;

void BrowserCloseManager::StartClosingBrowsers() {
  close_timer_.emplace();

  // If the session is ending or a silent exit was requested, skip straight to
  // closing the browsers without waiting for beforeunload dialogs.
  if (browser_shutdown::ShouldIgnoreUnloadHandlers()) {
    // Tell everyone that we are shutting down.
    browser_shutdown::SetTryingToQuit(true);
    CloseBrowsers();
    return;
  }
  TryToCloseBrowsers();
}

void BrowserCloseManager::CancelBrowserClose() {
  // This is the abort endpoint. Record the metric if we haven't already.
  if (close_timer_) {
    base::UmaHistogramMediumTimes("Shutdown.Time.BrowserCloseManager.Canceled",
                                  close_timer_->Elapsed());
    close_timer_.reset();
  }

  browser_shutdown::SetTryingToQuit(false);
  GlobalBrowserCollection::GetInstance()->ForEach(
      [](BrowserWindowInterface* browser) {
        UnloadController::From(browser)->ResetTryToCloseWindow();
        return true;
      },
      BrowserCollection::Order::kCreation);
}

void BrowserCloseManager::TryToCloseBrowsers() {
  // If all browser windows can immediately be closed, fall out of this loop and
  // close the browsers. If any browser window cannot be closed, temporarily
  // stop closing. CallBeforeUnloadHandlers prompts the user and calls
  // OnBrowserReportCloseable with the result. If the user confirms the close,
  // this will trigger TryToCloseBrowsers to try again.
  bool should_stop = false;
  GlobalBrowserCollection::GetInstance()->ForEach(
      [this, &should_stop](BrowserWindowInterface* browser) {
        if (UnloadController::From(browser)->TryToCloseWindow(
                false,
                base::BindRepeating(
                    &BrowserCloseManager::OnBrowserReportCloseable, this))) {
          current_browser_ = browser;
          should_stop = true;
        }
        return !should_stop;
      },
      BrowserCollection::Order::kCreation);
  if (should_stop) {
    return;
  }

  // This is the success endpoint. If we get here, all beforeunload handlers
  // have been processed successfully.
  if (close_timer_) {
    base::UmaHistogramMediumTimes("Shutdown.Time.BrowserCloseManager.Confirmed",
                                  close_timer_->Elapsed());
    close_timer_.reset();
  }

  CheckForDownloadsInProgress();
}

void BrowserCloseManager::OnBrowserReportCloseable(bool proceed) {
  if (!current_browser_) {
    return;
  }

  current_browser_ = nullptr;

  if (proceed) {
    TryToCloseBrowsers();
  } else {
    CancelBrowserClose();
  }
}

void BrowserCloseManager::CheckForDownloadsInProgress() {
#if BUILDFLAG(IS_MAC)
  // Mac has its own in-progress downloads prompt in app_controller_mac.mm.
  CloseBrowsers();
#else
  int download_count = DownloadCoreService::BlockingShutdownCountAllProfiles();
  if (download_count == 0) {
    CloseBrowsers();
    return;
  }

  ConfirmCloseWithPendingDownloads(
      download_count,
      base::BindOnce(&BrowserCloseManager::OnReportDownloadsCancellable, this));
#endif
}

void BrowserCloseManager::ConfirmCloseWithPendingDownloads(
    int download_count,
    base::OnceCallback<void(bool)> callback) {
  BrowserWindowInterface* const bwi =
      GetLastActiveBrowserWindowInterfaceWithAnyProfile();
  if (!bwi) {
    // Background may call CloseAllBrowsers() with no Browsers. In this
    // case immediately continue with shutting down.
    std::move(callback).Run(/* proceed= */ true);
    return;
  }
  BrowserWindow::FromBrowser(bwi)->ConfirmBrowserCloseWithPendingDownloads(
      download_count, UnloadController::DownloadCloseType::kBrowserShutdown,
      std::move(callback));
}

void BrowserCloseManager::OnReportDownloadsCancellable(bool proceed) {
  if (proceed) {
    CloseBrowsers();
    return;
  }

  CancelBrowserClose();

  // Open the downloads page for each profile with downloads in progress.
  std::vector<Profile*> profiles(
      g_browser_process->profile_manager()->GetLoadedProfiles());
  for (Profile* profile : profiles) {
    ShowInProgressDownloads(profile);
    std::vector<Profile*> otr_profiles = profile->GetAllOffTheRecordProfiles();
    for (Profile* otr : otr_profiles) {
      ShowInProgressDownloads(otr);
    }
  }
}

void BrowserCloseManager::CloseBrowsers() {
#if BUILDFLAG(ENABLE_SESSION_SERVICE)
  // Before we close the browsers shutdown all session services. That way an
  // exit can restore all browsers open before exiting.
  ProfileManager::ShutdownSessionServices();
#endif
#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
  if (!browser_shutdown::IsTryingToQuit()) {
    BackgroundModeManager* background_mode_manager =
        g_browser_process->background_mode_manager();
    if (background_mode_manager) {
      background_mode_manager->SuspendBackgroundMode();
    }
  }
#endif

  auto* glic_background_mode_manager =
      glic::GlicBackgroundModeManager::GetInstance();
  if (glic_background_mode_manager) {
    glic_background_mode_manager->ExitBackgroundMode();
  }

  ForEachCurrentAndNewBrowserWindowInterfaceOrderedByActivation(
      [](BrowserWindowInterface* browser_window) {
        bool ignore_unload_handlers =
            browser_shutdown::ShouldIgnoreUnloadHandlers();

        UnloadController::From(browser_window)
            ->set_force_skip_warning_user_on_close(ignore_unload_handlers);
        browser_window->GetWindow()->Close();

        if (ignore_unload_handlers) {
          // This path is hit during logoff/power-down. It could be the case
          // that there are some tabs which would have prevented the browser
          // from closing (Ex: A form with an open dialog asking for permission
          // to leave the current site). Since we are attempting to end the
          // session, we will force skip these warnings and manually close all
          // the tabs to make sure the browser is destroyed and cleanup can
          // happen.
          browser_window->GetTabStripModel()->CloseAllTabs();
          BrowserManagerService::SynchronouslyDestroyBrowser(browser_window);
        }
        return true;
      });

#if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS)
  NotificationUIManager* notification_manager =
      g_browser_process->notification_ui_manager();
  if (notification_manager) {
    notification_manager->CancelAll();
  }
#endif
}
