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