• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
BrowserActivityObserver()40   BrowserActivityObserver() {
41     registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
42                    NotificationService::AllSources());
43   }
~BrowserActivityObserver()44   ~BrowserActivityObserver() {}
45 
46  private:
47   // NotificationObserver implementation.
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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.
LogRenderProcessHostCount() const62   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.
LogBrowserTabCount() const75   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.
BrowserMatches(Browser * browser,Profile * profile,Browser::Type type,Browser::WindowFeature window_feature,uint32 match_types)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>
FindBrowserMatching(const T & begin,const T & end,Profile * profile,Browser::Type type,Browser::WindowFeature window_feature,uint32 match_types)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
AddBrowser(Browser * browser)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
MarkAsCleanShutdown()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
NotifyWindowManagerAboutSignout()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
NotifyAndTerminate(bool fast_path)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
RemoveBrowser(Browser * browser)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
AddObserver(BrowserList::Observer * observer)290 void BrowserList::AddObserver(BrowserList::Observer* observer) {
291   observers_.AddObserver(observer);
292 }
293 
294 // static
RemoveObserver(BrowserList::Observer * observer)295 void BrowserList::RemoveObserver(BrowserList::Observer* observer) {
296   observers_.RemoveObserver(observer);
297 }
298 
299 #if defined(OS_CHROMEOS)
300 // static
NeedBeforeUnloadFired()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
PendingDownloads()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
CloseAllBrowsers()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
Exit()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
CloseAllBrowsersAndExit()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
SessionEnding()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
HasBrowserWithProfile(Profile * profile)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
StartKeepAlive()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
EndKeepAlive()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
WillKeepAlive()492 bool BrowserList::WillKeepAlive() {
493   return keep_alive_count_ > 0;
494 }
495 
496 // static
497 BrowserList::BrowserVector BrowserList::last_active_browsers_;
498 
499 // static
SetLastActive(Browser * browser)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
GetLastActive()508 Browser* BrowserList::GetLastActive() {
509   if (!last_active_browsers_.empty())
510     return *(last_active_browsers_.rbegin());
511 
512   return NULL;
513 }
514 
515 // static
GetLastActiveWithProfile(Profile * p)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
FindBrowserWithType(Profile * p,Browser::Type t,bool match_incognito)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
FindBrowserWithFeature(Profile * p,Browser::WindowFeature feature)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
FindBrowserWithProfile(Profile * p)551 Browser* BrowserList::FindBrowserWithProfile(Profile* p) {
552   return FindBrowserWithType(p, Browser::TYPE_ANY, false);
553 }
554 
555 // static
FindBrowserWithID(SessionID::id_type desired_id)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
GetBrowserCountForType(Profile * p,Browser::Type type)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
GetBrowserCount(Profile * p)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
IsOffTheRecordSessionActive()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
RemoveBrowserFrom(Browser * browser,BrowserVector * browser_list)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 
TabContentsIterator()608 TabContentsIterator::TabContentsIterator()
609     : browser_iterator_(BrowserList::begin()),
610       web_view_index_(-1),
611       cur_(NULL) {
612     Advance();
613   }
614 
Advance()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