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
33namespace {
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.
38class 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
100BrowserActivityObserver* activity_observer = NULL;
101
102// Type used to indicate only the type should be matched.
103const int kMatchNothing                 = 0;
104
105// See BrowserMatches for details.
106const int kMatchOriginalProfile         = 1 << 0;
107
108// See BrowserMatches for details.
109const 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.
118bool 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.
145template <class T>
146Browser* 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
161BrowserList::BrowserVector BrowserList::browsers_;
162ObserverList<BrowserList::Observer> BrowserList::observers_;
163
164// static
165void 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
188void 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
196void 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
207bool BrowserList::signout_ = false;
208
209#endif
210
211// static
212void 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
242void 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
290void BrowserList::AddObserver(BrowserList::Observer* observer) {
291  observers_.AddObserver(observer);
292}
293
294// static
295void BrowserList::RemoveObserver(BrowserList::Observer* observer) {
296  observers_.RemoveObserver(observer);
297}
298
299#if defined(OS_CHROMEOS)
300// static
301bool 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
311bool 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
325void 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
378void 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
393void 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
412void 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
455bool 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
464int BrowserList::keep_alive_count_ = 0;
465
466// static
467void 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
476void 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
492bool BrowserList::WillKeepAlive() {
493  return keep_alive_count_ > 0;
494}
495
496// static
497BrowserList::BrowserVector BrowserList::last_active_browsers_;
498
499// static
500void 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
508Browser* BrowserList::GetLastActive() {
509  if (!last_active_browsers_.empty())
510    return *(last_active_browsers_.rbegin());
511
512  return NULL;
513}
514
515// static
516Browser* 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
525Browser* 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
538Browser* 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
551Browser* BrowserList::FindBrowserWithProfile(Profile* p) {
552  return FindBrowserWithType(p, Browser::TYPE_ANY, false);
553}
554
555// static
556Browser* 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
566size_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
577size_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
590bool 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
600void 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
608TabContentsIterator::TabContentsIterator()
609    : browser_iterator_(BrowserList::begin()),
610      web_view_index_(-1),
611      cur_(NULL) {
612    Advance();
613  }
614
615void 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