1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/browser_list.h" 6 7 #include "base/logging.h" 8 #include "base/message_loop.h" 9 #include "base/metrics/histogram.h" 10 #include "build/build_config.h" 11 #include "chrome/browser/browser_process.h" 12 #include "chrome/browser/browser_shutdown.h" 13 #include "chrome/browser/profiles/profile_manager.h" 14 #include "chrome/browser/ui/browser_window.h" 15 #include "content/browser/renderer_host/render_process_host.h" 16 #include "content/browser/tab_contents/navigation_controller.h" 17 #include "content/common/notification_registrar.h" 18 #include "content/common/notification_service.h" 19 #include "content/common/result_codes.h" 20 21 #if defined(OS_MACOSX) 22 #include "chrome/browser/chrome_browser_application_mac.h" 23 #endif 24 25 #if defined(OS_CHROMEOS) 26 #include "chrome/browser/chromeos/boot_times_loader.h" 27 #include "chrome/browser/chromeos/cros/cros_library.h" 28 #include "chrome/browser/chromeos/cros/login_library.h" 29 #include "chrome/browser/chromeos/cros/update_library.h" 30 #include "chrome/browser/chromeos/wm_ipc.h" 31 #endif 32 33 namespace { 34 35 // This object is instantiated when the first Browser object is added to the 36 // list and delete when the last one is removed. It watches for loads and 37 // creates histograms of some global object counts. 38 class BrowserActivityObserver : public NotificationObserver { 39 public: 40 BrowserActivityObserver() { 41 registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, 42 NotificationService::AllSources()); 43 } 44 ~BrowserActivityObserver() {} 45 46 private: 47 // NotificationObserver implementation. 48 virtual void Observe(NotificationType type, 49 const NotificationSource& source, 50 const NotificationDetails& details) { 51 DCHECK(type == NotificationType::NAV_ENTRY_COMMITTED); 52 const NavigationController::LoadCommittedDetails& load = 53 *Details<NavigationController::LoadCommittedDetails>(details).ptr(); 54 if (!load.is_main_frame || load.is_auto || load.is_in_page) 55 return; // Don't log for subframes or other trivial types. 56 57 LogRenderProcessHostCount(); 58 LogBrowserTabCount(); 59 } 60 61 // Counts the number of active RenderProcessHosts and logs them. 62 void LogRenderProcessHostCount() const { 63 int hosts_count = 0; 64 for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator()); 65 !i.IsAtEnd(); i.Advance()) 66 ++hosts_count; 67 UMA_HISTOGRAM_CUSTOM_COUNTS("MPArch.RPHCountPerLoad", hosts_count, 68 1, 50, 50); 69 } 70 71 // Counts the number of tabs in each browser window and logs them. This is 72 // different than the number of TabContents objects since TabContents objects 73 // can be used for popups and in dialog boxes. We're just counting toplevel 74 // tabs here. 75 void LogBrowserTabCount() const { 76 int tab_count = 0; 77 for (BrowserList::const_iterator browser_iterator = BrowserList::begin(); 78 browser_iterator != BrowserList::end(); browser_iterator++) { 79 // Record how many tabs each window has open. 80 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountPerWindow", 81 (*browser_iterator)->tab_count(), 1, 200, 50); 82 tab_count += (*browser_iterator)->tab_count(); 83 } 84 // Record how many tabs total are open (across all windows). 85 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountPerLoad", tab_count, 1, 200, 50); 86 87 Browser* browser = BrowserList::GetLastActive(); 88 if (browser) { 89 // Record how many tabs the active window has open. 90 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountActiveWindow", 91 browser->tab_count(), 1, 200, 50); 92 } 93 } 94 95 NotificationRegistrar registrar_; 96 97 DISALLOW_COPY_AND_ASSIGN(BrowserActivityObserver); 98 }; 99 100 BrowserActivityObserver* activity_observer = NULL; 101 102 // Type used to indicate only the type should be matched. 103 const int kMatchNothing = 0; 104 105 // See BrowserMatches for details. 106 const int kMatchOriginalProfile = 1 << 0; 107 108 // See BrowserMatches for details. 109 const int kMatchCanSupportWindowFeature = 1 << 1; 110 111 // Returns true if the specified |browser| matches the specified arguments. 112 // |match_types| is a bitmask dictating what parameters to match: 113 // . If it contains kMatchOriginalProfile then the original profile of the 114 // browser must match |profile->GetOriginalProfile()|. This is used to match 115 // incognito windows. 116 // . If it contains kMatchCanSupportWindowFeature 117 // |CanSupportWindowFeature(window_feature)| must return true. 118 bool BrowserMatches(Browser* browser, 119 Profile* profile, 120 Browser::Type type, 121 Browser::WindowFeature window_feature, 122 uint32 match_types) { 123 if (match_types & kMatchCanSupportWindowFeature && 124 !browser->CanSupportWindowFeature(window_feature)) { 125 return false; 126 } 127 128 if (match_types & kMatchOriginalProfile) { 129 if (browser->profile()->GetOriginalProfile() != 130 profile->GetOriginalProfile()) 131 return false; 132 } else if (browser->profile() != profile) { 133 return false; 134 } 135 136 if (type != Browser::TYPE_ANY && browser->type() != type) 137 return false; 138 139 return true; 140 } 141 142 // Returns the first browser in the specified iterator that returns true from 143 // |BrowserMatches|, or null if no browsers match the arguments. See 144 // |BrowserMatches| for details on the arguments. 145 template <class T> 146 Browser* FindBrowserMatching(const T& begin, 147 const T& end, 148 Profile* profile, 149 Browser::Type type, 150 Browser::WindowFeature window_feature, 151 uint32 match_types) { 152 for (T i = begin; i != end; ++i) { 153 if (BrowserMatches(*i, profile, type, window_feature, match_types)) 154 return *i; 155 } 156 return NULL; 157 } 158 159 } // namespace 160 161 BrowserList::BrowserVector BrowserList::browsers_; 162 ObserverList<BrowserList::Observer> BrowserList::observers_; 163 164 // static 165 void BrowserList::AddBrowser(Browser* browser) { 166 DCHECK(browser); 167 browsers_.push_back(browser); 168 169 g_browser_process->AddRefModule(); 170 171 if (!activity_observer) 172 activity_observer = new BrowserActivityObserver; 173 174 NotificationService::current()->Notify( 175 NotificationType::BROWSER_OPENED, 176 Source<Browser>(browser), 177 NotificationService::NoDetails()); 178 179 // Send out notifications after add has occurred. Do some basic checking to 180 // try to catch evil observers that change the list from under us. 181 size_t original_count = observers_.size(); 182 FOR_EACH_OBSERVER(Observer, observers_, OnBrowserAdded(browser)); 183 DCHECK_EQ(original_count, observers_.size()) 184 << "observer list modified during notification"; 185 } 186 187 // static 188 void BrowserList::MarkAsCleanShutdown() { 189 for (const_iterator i = begin(); i != end(); ++i) { 190 (*i)->profile()->MarkAsCleanShutdown(); 191 } 192 } 193 194 #if defined(OS_CHROMEOS) 195 // static 196 void BrowserList::NotifyWindowManagerAboutSignout() { 197 static bool notified = false; 198 if (!notified) { 199 // Let the window manager know that we're going away before we start closing 200 // windows so it can display a graceful transition to a black screen. 201 chromeos::WmIpc::instance()->NotifyAboutSignout(); 202 notified = true; 203 } 204 } 205 206 // static 207 bool BrowserList::signout_ = false; 208 209 #endif 210 211 // static 212 void BrowserList::NotifyAndTerminate(bool fast_path) { 213 #if defined(OS_CHROMEOS) 214 if (!signout_) return; 215 NotifyWindowManagerAboutSignout(); 216 #endif 217 218 if (fast_path) { 219 NotificationService::current()->Notify(NotificationType::APP_TERMINATING, 220 NotificationService::AllSources(), 221 NotificationService::NoDetails()); 222 } 223 224 #if defined(OS_CHROMEOS) 225 chromeos::CrosLibrary* cros_library = chromeos::CrosLibrary::Get(); 226 if (cros_library->EnsureLoaded()) { 227 // If update has been installed, reboot, otherwise, sign out. 228 if (cros_library->GetUpdateLibrary()->status().status == 229 chromeos::UPDATE_STATUS_UPDATED_NEED_REBOOT) { 230 cros_library->GetUpdateLibrary()->RebootAfterUpdate(); 231 } else { 232 cros_library->GetLoginLibrary()->StopSession(""); 233 } 234 return; 235 } 236 // If running the Chrome OS build, but we're not on the device, fall through 237 #endif 238 AllBrowsersClosedAndAppExiting(); 239 } 240 241 // static 242 void BrowserList::RemoveBrowser(Browser* browser) { 243 RemoveBrowserFrom(browser, &last_active_browsers_); 244 245 // Closing all windows does not indicate quitting the application on the Mac, 246 // however, many UI tests rely on this behavior so leave it be for now and 247 // simply ignore the behavior on the Mac outside of unit tests. 248 // TODO(andybons): Fix the UI tests to Do The Right Thing. 249 bool closing_last_browser = (browsers_.size() == 1); 250 NotificationService::current()->Notify( 251 NotificationType::BROWSER_CLOSED, 252 Source<Browser>(browser), Details<bool>(&closing_last_browser)); 253 254 RemoveBrowserFrom(browser, &browsers_); 255 256 // Do some basic checking to try to catch evil observers 257 // that change the list from under us. 258 size_t original_count = observers_.size(); 259 FOR_EACH_OBSERVER(Observer, observers_, OnBrowserRemoved(browser)); 260 DCHECK_EQ(original_count, observers_.size()) 261 << "observer list modified during notification"; 262 263 // If the last Browser object was destroyed, make sure we try to close any 264 // remaining dependent windows too. 265 if (browsers_.empty()) { 266 delete activity_observer; 267 activity_observer = NULL; 268 } 269 270 g_browser_process->ReleaseModule(); 271 272 // If we're exiting, send out the APP_TERMINATING notification to allow other 273 // modules to shut themselves down. 274 if (browsers_.empty() && 275 (browser_shutdown::IsTryingToQuit() || 276 g_browser_process->IsShuttingDown())) { 277 // Last browser has just closed, and this is a user-initiated quit or there 278 // is no module keeping the app alive, so send out our notification. No need 279 // to call ProfileManager::ShutdownSessionServices() as part of the 280 // shutdown, because Browser::WindowClosing() already makes sure that the 281 // SessionService is created and notified. 282 NotificationService::current()->Notify(NotificationType::APP_TERMINATING, 283 NotificationService::AllSources(), 284 NotificationService::NoDetails()); 285 AllBrowsersClosedAndAppExiting(); 286 } 287 } 288 289 // static 290 void BrowserList::AddObserver(BrowserList::Observer* observer) { 291 observers_.AddObserver(observer); 292 } 293 294 // static 295 void BrowserList::RemoveObserver(BrowserList::Observer* observer) { 296 observers_.RemoveObserver(observer); 297 } 298 299 #if defined(OS_CHROMEOS) 300 // static 301 bool BrowserList::NeedBeforeUnloadFired() { 302 bool need_before_unload_fired = false; 303 for (const_iterator i = begin(); i != end(); ++i) { 304 need_before_unload_fired = need_before_unload_fired || 305 (*i)->TabsNeedBeforeUnloadFired(); 306 } 307 return need_before_unload_fired; 308 } 309 310 // static 311 bool BrowserList::PendingDownloads() { 312 for (const_iterator i = begin(); i != end(); ++i) { 313 bool normal_downloads_are_present = false; 314 bool incognito_downloads_are_present = false; 315 (*i)->CheckDownloadsInProgress(&normal_downloads_are_present, 316 &incognito_downloads_are_present); 317 if (normal_downloads_are_present || incognito_downloads_are_present) 318 return true; 319 } 320 return false; 321 } 322 #endif 323 324 // static 325 void BrowserList::CloseAllBrowsers() { 326 bool session_ending = 327 browser_shutdown::GetShutdownType() == browser_shutdown::END_SESSION; 328 bool use_post = !session_ending; 329 bool force_exit = false; 330 #if defined(USE_X11) 331 if (session_ending) 332 force_exit = true; 333 #endif 334 // Tell everyone that we are shutting down. 335 browser_shutdown::SetTryingToQuit(true); 336 337 // Before we close the browsers shutdown all session services. That way an 338 // exit can restore all browsers open before exiting. 339 ProfileManager::ShutdownSessionServices(); 340 341 // If there are no browsers, send the APP_TERMINATING action here. Otherwise, 342 // it will be sent by RemoveBrowser() when the last browser has closed. 343 if (force_exit || browsers_.empty()) { 344 NotifyAndTerminate(true); 345 return; 346 } 347 #if defined(OS_CHROMEOS) 348 chromeos::BootTimesLoader::Get()->AddLogoutTimeMarker( 349 "StartedClosingWindows", false); 350 #endif 351 for (BrowserList::const_iterator i = BrowserList::begin(); 352 i != BrowserList::end();) { 353 Browser* browser = *i; 354 browser->window()->Close(); 355 if (use_post) { 356 ++i; 357 } else { 358 // This path is hit during logoff/power-down. In this case we won't get 359 // a final message and so we force the browser to be deleted. 360 // Close doesn't immediately destroy the browser 361 // (Browser::TabStripEmpty() uses invoke later) but when we're ending the 362 // session we need to make sure the browser is destroyed now. So, invoke 363 // DestroyBrowser to make sure the browser is deleted and cleanup can 364 // happen. 365 browser->window()->DestroyBrowser(); 366 i = BrowserList::begin(); 367 if (i != BrowserList::end() && browser == *i) { 368 // Destroying the browser should have removed it from the browser list. 369 // We should never get here. 370 NOTREACHED(); 371 return; 372 } 373 } 374 } 375 } 376 377 // static 378 void BrowserList::Exit() { 379 #if defined(OS_CHROMEOS) 380 signout_ = true; 381 // Fast shutdown for ChromeOS when there's no unload processing to be done. 382 if (chromeos::CrosLibrary::Get()->EnsureLoaded() 383 && !NeedBeforeUnloadFired() 384 && !PendingDownloads()) { 385 NotifyAndTerminate(true); 386 return; 387 } 388 #endif 389 CloseAllBrowsersAndExit(); 390 } 391 392 // static 393 void BrowserList::CloseAllBrowsersAndExit() { 394 MarkAsCleanShutdown(); // Don't notify users of crashes beyond this point. 395 NotificationService::current()->Notify( 396 NotificationType::APP_EXITING, 397 NotificationService::AllSources(), 398 NotificationService::NoDetails()); 399 400 #if !defined(OS_MACOSX) 401 // On most platforms, closing all windows causes the application to exit. 402 CloseAllBrowsers(); 403 #else 404 // On the Mac, the application continues to run once all windows are closed. 405 // Terminate will result in a CloseAllBrowsers() call, and once (and if) 406 // that is done, will cause the application to exit cleanly. 407 chrome_browser_application_mac::Terminate(); 408 #endif 409 } 410 411 // static 412 void BrowserList::SessionEnding() { 413 // EndSession is invoked once per frame. Only do something the first time. 414 static bool already_ended = false; 415 // We may get called in the middle of shutdown, e.g. http://crbug.com/70852 416 // In this case, do nothing. 417 if (already_ended || !NotificationService::current()) 418 return; 419 already_ended = true; 420 421 browser_shutdown::OnShutdownStarting(browser_shutdown::END_SESSION); 422 423 NotificationService::current()->Notify( 424 NotificationType::APP_EXITING, 425 NotificationService::AllSources(), 426 NotificationService::NoDetails()); 427 428 // Write important data first. 429 g_browser_process->EndSession(); 430 431 BrowserList::CloseAllBrowsers(); 432 433 // Send out notification. This is used during testing so that the test harness 434 // can properly shutdown before we exit. 435 NotificationService::current()->Notify( 436 NotificationType::SESSION_END, 437 NotificationService::AllSources(), 438 NotificationService::NoDetails()); 439 440 // And shutdown. 441 browser_shutdown::Shutdown(); 442 443 #if defined(OS_WIN) 444 // At this point the message loop is still running yet we've shut everything 445 // down. If any messages are processed we'll likely crash. Exit now. 446 ExitProcess(ResultCodes::NORMAL_EXIT); 447 #elif defined(OS_LINUX) 448 _exit(ResultCodes::NORMAL_EXIT); 449 #else 450 NOTIMPLEMENTED(); 451 #endif 452 } 453 454 // static 455 bool BrowserList::HasBrowserWithProfile(Profile* profile) { 456 return FindBrowserMatching(BrowserList::begin(), 457 BrowserList::end(), 458 profile, Browser::TYPE_ANY, 459 Browser::FEATURE_NONE, 460 kMatchNothing) != NULL; 461 } 462 463 // static 464 int BrowserList::keep_alive_count_ = 0; 465 466 // static 467 void BrowserList::StartKeepAlive() { 468 // Increment the browser process refcount as long as we're keeping the 469 // application alive. 470 if (!WillKeepAlive()) 471 g_browser_process->AddRefModule(); 472 keep_alive_count_++; 473 } 474 475 // static 476 void BrowserList::EndKeepAlive() { 477 DCHECK_GT(keep_alive_count_, 0); 478 keep_alive_count_--; 479 // Allow the app to shutdown again. 480 if (!WillKeepAlive()) { 481 g_browser_process->ReleaseModule(); 482 // If there are no browsers open and we aren't already shutting down, 483 // initiate a shutdown. Also skips shutdown if this is a unit test 484 // (MessageLoop::current() == null). 485 if (browsers_.empty() && !browser_shutdown::IsTryingToQuit() && 486 MessageLoop::current()) 487 CloseAllBrowsers(); 488 } 489 } 490 491 // static 492 bool BrowserList::WillKeepAlive() { 493 return keep_alive_count_ > 0; 494 } 495 496 // static 497 BrowserList::BrowserVector BrowserList::last_active_browsers_; 498 499 // static 500 void BrowserList::SetLastActive(Browser* browser) { 501 RemoveBrowserFrom(browser, &last_active_browsers_); 502 last_active_browsers_.push_back(browser); 503 504 FOR_EACH_OBSERVER(Observer, observers_, OnBrowserSetLastActive(browser)); 505 } 506 507 // static 508 Browser* BrowserList::GetLastActive() { 509 if (!last_active_browsers_.empty()) 510 return *(last_active_browsers_.rbegin()); 511 512 return NULL; 513 } 514 515 // static 516 Browser* BrowserList::GetLastActiveWithProfile(Profile* p) { 517 // We are only interested in last active browsers, so we don't fall back to 518 // all browsers like FindBrowserWith* do. 519 return FindBrowserMatching( 520 BrowserList::begin_last_active(), BrowserList::end_last_active(), p, 521 Browser::TYPE_ANY, Browser::FEATURE_NONE, kMatchNothing); 522 } 523 524 // static 525 Browser* BrowserList::FindBrowserWithType(Profile* p, Browser::Type t, 526 bool match_incognito) { 527 uint32 match_types = match_incognito ? kMatchOriginalProfile : kMatchNothing; 528 Browser* browser = FindBrowserMatching( 529 BrowserList::begin_last_active(), BrowserList::end_last_active(), 530 p, t, Browser::FEATURE_NONE, match_types); 531 // Fall back to a forward scan of all Browsers if no active one was found. 532 return browser ? browser : 533 FindBrowserMatching(BrowserList::begin(), BrowserList::end(), p, t, 534 Browser::FEATURE_NONE, match_types); 535 } 536 537 // static 538 Browser* BrowserList::FindBrowserWithFeature(Profile* p, 539 Browser::WindowFeature feature) { 540 Browser* browser = FindBrowserMatching( 541 BrowserList::begin_last_active(), BrowserList::end_last_active(), 542 p, Browser::TYPE_ANY, feature, kMatchCanSupportWindowFeature); 543 // Fall back to a forward scan of all Browsers if no active one was found. 544 return browser ? browser : 545 FindBrowserMatching(BrowserList::begin(), BrowserList::end(), p, 546 Browser::TYPE_ANY, feature, 547 kMatchCanSupportWindowFeature); 548 } 549 550 // static 551 Browser* BrowserList::FindBrowserWithProfile(Profile* p) { 552 return FindBrowserWithType(p, Browser::TYPE_ANY, false); 553 } 554 555 // static 556 Browser* BrowserList::FindBrowserWithID(SessionID::id_type desired_id) { 557 for (BrowserList::const_iterator i = BrowserList::begin(); 558 i != BrowserList::end(); ++i) { 559 if ((*i)->session_id().id() == desired_id) 560 return *i; 561 } 562 return NULL; 563 } 564 565 // static 566 size_t BrowserList::GetBrowserCountForType(Profile* p, Browser::Type type) { 567 size_t result = 0; 568 for (BrowserList::const_iterator i = BrowserList::begin(); 569 i != BrowserList::end(); ++i) { 570 if (BrowserMatches(*i, p, type, Browser::FEATURE_NONE, kMatchNothing)) 571 ++result; 572 } 573 return result; 574 } 575 576 // static 577 size_t BrowserList::GetBrowserCount(Profile* p) { 578 size_t result = 0; 579 for (BrowserList::const_iterator i = BrowserList::begin(); 580 i != BrowserList::end(); ++i) { 581 if (BrowserMatches(*i, p, Browser::TYPE_ANY, Browser::FEATURE_NONE, 582 kMatchNothing)) { 583 result++; 584 } 585 } 586 return result; 587 } 588 589 // static 590 bool BrowserList::IsOffTheRecordSessionActive() { 591 for (BrowserList::const_iterator i = BrowserList::begin(); 592 i != BrowserList::end(); ++i) { 593 if ((*i)->profile()->IsOffTheRecord()) 594 return true; 595 } 596 return false; 597 } 598 599 // static 600 void BrowserList::RemoveBrowserFrom(Browser* browser, 601 BrowserVector* browser_list) { 602 const iterator remove_browser = 603 std::find(browser_list->begin(), browser_list->end(), browser); 604 if (remove_browser != browser_list->end()) 605 browser_list->erase(remove_browser); 606 } 607 608 TabContentsIterator::TabContentsIterator() 609 : browser_iterator_(BrowserList::begin()), 610 web_view_index_(-1), 611 cur_(NULL) { 612 Advance(); 613 } 614 615 void TabContentsIterator::Advance() { 616 // Unless we're at the beginning (index = -1) or end (iterator = end()), 617 // then the current TabContents should be valid. 618 DCHECK(web_view_index_ || browser_iterator_ == BrowserList::end() || cur_) 619 << "Trying to advance past the end"; 620 621 // Update cur_ to the next TabContents in the list. 622 while (browser_iterator_ != BrowserList::end()) { 623 web_view_index_++; 624 625 while (web_view_index_ >= (*browser_iterator_)->tab_count()) { 626 // advance browsers 627 ++browser_iterator_; 628 web_view_index_ = 0; 629 if (browser_iterator_ == BrowserList::end()) { 630 cur_ = NULL; 631 return; 632 } 633 } 634 635 TabContentsWrapper* next_tab = 636 (*browser_iterator_)->GetTabContentsWrapperAt(web_view_index_); 637 if (next_tab) { 638 cur_ = next_tab; 639 return; 640 } 641 } 642 } 643