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