| // 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 <iterator> | 
 | #include <vector> | 
 |  | 
 | #include "base/containers/contains.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/ranges/algorithm.h" | 
 | #include "build/build_config.h" | 
 | #include "chrome/browser/background/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/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 | 
 |  | 
 | 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->NonMaliciousDownloadCount() > 0) { | 
 |     chrome::ScopedTabbedBrowserDisplayer displayer(profile); | 
 |     chrome::ShowDownloads(displayer.browser()); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | BrowserCloseManager::BrowserCloseManager() : current_browser_(nullptr) { | 
 | } | 
 |  | 
 | BrowserCloseManager::~BrowserCloseManager() { | 
 | } | 
 |  | 
 | void BrowserCloseManager::StartClosingBrowsers() { | 
 |   // 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() { | 
 |   browser_shutdown::SetTryingToQuit(false); | 
 |   for (auto* 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 (auto* browser : *BrowserList::GetInstance()) { | 
 |     if (browser->TryToCloseWindow( | 
 |             false, base::BindRepeating( | 
 |                        &BrowserCloseManager::OnBrowserReportCloseable, this))) { | 
 |       current_browser_ = browser; | 
 |       return; | 
 |     } | 
 |   } | 
 |   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::NonMaliciousDownloadCountAllProfiles(); | 
 |   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) { | 
 |   Browser* browser = BrowserList::GetInstance()->GetLastActive(); | 
 |   if (browser == nullptr) { | 
 |     // Background may call CloseAllBrowsers() with no Browsers. In this | 
 |     // case immediately continue with shutting down. | 
 |     std::move(callback).Run(/* proceed= */ true); | 
 |     return; | 
 |   } | 
 |   browser->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 (!browser_shutdown::IsTryingToQuit()) { | 
 |     BackgroundModeManager* background_mode_manager = | 
 |         g_browser_process->background_mode_manager(); | 
 |     if (background_mode_manager) | 
 |       background_mode_manager->SuspendBackgroundMode(); | 
 |   } | 
 |  | 
 |   // Make a copy of the BrowserList to simplify the case where we need to | 
 |   // destroy a Browser during the loop. | 
 |   std::vector<Browser*> browser_list_copy; | 
 |   base::ranges::copy(*BrowserList::GetInstance(), | 
 |                      std::back_inserter(browser_list_copy)); | 
 |  | 
 |   bool ignore_unload_handlers = browser_shutdown::ShouldIgnoreUnloadHandlers(); | 
 |  | 
 |   for (auto* browser : browser_list_copy) { | 
 |     browser->window()->Close(); | 
 |     if (ignore_unload_handlers) { | 
 |       // This path is hit during logoff/power-down. In this case we won't get | 
 |       // a final message and so we force the browser to be deleted. | 
 |       // Close doesn't immediately destroy the browser | 
 |       // (Browser::TabStripEmpty() uses invoke later) but when we're ending the | 
 |       // session we need to make sure the browser is destroyed now. So, invoke | 
 |       // DestroyBrowser to make sure the browser is deleted and cleanup can | 
 |       // happen. | 
 |       while (browser->tab_strip_model()->count()) | 
 |         browser->tab_strip_model()->DetachAndDeleteWebContentsAt(0); | 
 |       browser->window()->DestroyBrowser(); | 
 |       // Destroying the browser should have removed it from the browser list. | 
 |       DCHECK(!base::Contains(*BrowserList::GetInstance(), browser)); | 
 |     } | 
 |   } | 
 |  | 
 | #if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS) | 
 |   NotificationUIManager* notification_manager = | 
 |       g_browser_process->notification_ui_manager(); | 
 |   if (notification_manager) | 
 |     notification_manager->CancelAll(); | 
 | #endif | 
 | } |