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