Home | History | Annotate | Download | only in ui
      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