|  | // 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/containers/contains.h" | 
|  | #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/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.h" | 
|  | #include "chrome/browser/ui/browser_list.h" | 
|  | #include "chrome/browser/ui/browser_window.h" | 
|  | #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.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/common/buildflags.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  |  | 
|  | #if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS) | 
|  | #include "chrome/browser/notifications/notification_ui_manager.h" | 
|  | #endif | 
|  |  | 
|  | #if BUILDFLAG(ENABLE_GLIC) | 
|  | #include "chrome/browser/background/glic/glic_background_mode_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()); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // 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); | 
|  | for (Browser* browser : *BrowserList::GetInstance()) { | 
|  | browser->ResetTryToCloseWindow(); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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. | 
|  | for (Browser* browser : *BrowserList::GetInstance()) { | 
|  | if (browser->TryToCloseWindow( | 
|  | false, base::BindRepeating( | 
|  | &BrowserCloseManager::OnBrowserReportCloseable, this))) { | 
|  | current_browser_ = browser; | 
|  | 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; | 
|  | } | 
|  | bwi->GetBrowserForMigrationOnly() | 
|  | ->window() | 
|  | ->ConfirmBrowserCloseWithPendingDownloads( | 
|  | download_count, Browser::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 | 
|  |  | 
|  | #if BUILDFLAG(ENABLE_GLIC) | 
|  | auto* glic_background_mode_manager = | 
|  | glic::GlicBackgroundModeManager::GetInstance(); | 
|  | if (glic_background_mode_manager) { | 
|  | glic_background_mode_manager->ExitBackgroundMode(); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | ForEachCurrentAndNewBrowserWindowInterfaceOrderedByActivation( | 
|  | [](BrowserWindowInterface* browser_window) { | 
|  | bool ignore_unload_handlers = | 
|  | browser_shutdown::ShouldIgnoreUnloadHandlers(); | 
|  |  | 
|  | Browser* const browser = browser_window->GetBrowserForMigrationOnly(); | 
|  | browser->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(); | 
|  | browser->SynchronouslyDestroyBrowser(); | 
|  |  | 
|  | // Destroying the browser should have removed it from the browser | 
|  | // list. | 
|  | DCHECK(!base::Contains(*BrowserList::GetInstance(), browser)); | 
|  | } | 
|  | return true; | 
|  | }); | 
|  |  | 
|  | #if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS) | 
|  | NotificationUIManager* notification_manager = | 
|  | g_browser_process->notification_ui_manager(); | 
|  | if (notification_manager) { | 
|  | notification_manager->CancelAll(); | 
|  | } | 
|  | #endif | 
|  | } |