tab_strip_model.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
1// Copyright (c) 2012 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/tabs/tab_strip_model.h" 6 7#include <algorithm> 8#include <map> 9#include <string> 10 11#include "base/metrics/histogram.h" 12#include "base/stl_util.h" 13#include "chrome/app/chrome_command_ids.h" 14#include "chrome/browser/browser_shutdown.h" 15#include "chrome/browser/defaults.h" 16#include "chrome/browser/extensions/tab_helper.h" 17#include "chrome/browser/profiles/profile.h" 18#include "chrome/browser/ui/tab_contents/core_tab_helper.h" 19#include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h" 20#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" 21#include "chrome/browser/ui/tabs/tab_strip_model_order_controller.h" 22#include "chrome/common/url_constants.h" 23#include "components/web_modal/web_contents_modal_dialog_manager.h" 24#include "content/public/browser/notification_service.h" 25#include "content/public/browser/notification_types.h" 26#include "content/public/browser/render_process_host.h" 27#include "content/public/browser/user_metrics.h" 28#include "content/public/browser/web_contents.h" 29#include "content/public/browser/web_contents_view.h" 30 31using content::UserMetricsAction; 32using content::WebContents; 33 34namespace { 35 36// Returns true if the specified transition is one of the types that cause the 37// opener relationships for the tab in which the transition occurred to be 38// forgotten. This is generally any navigation that isn't a link click (i.e. 39// any navigation that can be considered to be the start of a new task distinct 40// from what had previously occurred in that tab). 41bool ShouldForgetOpenersForTransition(content::PageTransition transition) { 42 return transition == content::PAGE_TRANSITION_TYPED || 43 transition == content::PAGE_TRANSITION_AUTO_BOOKMARK || 44 transition == content::PAGE_TRANSITION_GENERATED || 45 transition == content::PAGE_TRANSITION_KEYWORD || 46 transition == content::PAGE_TRANSITION_AUTO_TOPLEVEL; 47} 48 49// CloseTracker is used when closing a set of WebContents. It listens for 50// deletions of the WebContents and removes from the internal set any time one 51// is deleted. 52class CloseTracker : public content::NotificationObserver { 53 public: 54 typedef std::vector<WebContents*> Contents; 55 56 explicit CloseTracker(const Contents& contents); 57 virtual ~CloseTracker(); 58 59 // Returns true if there is another WebContents in the Tracker. 60 bool HasNext() const; 61 62 // Returns the next WebContents, or NULL if there are no more. 63 WebContents* Next(); 64 65 private: 66 // NotificationObserver: 67 virtual void Observe(int type, 68 const content::NotificationSource& source, 69 const content::NotificationDetails& details) OVERRIDE; 70 71 Contents contents_; 72 73 content::NotificationRegistrar registrar_; 74 75 DISALLOW_COPY_AND_ASSIGN(CloseTracker); 76}; 77 78CloseTracker::CloseTracker(const Contents& contents) 79 : contents_(contents) { 80 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 81 content::NotificationService::AllBrowserContextsAndSources()); 82} 83 84CloseTracker::~CloseTracker() { 85} 86 87bool CloseTracker::HasNext() const { 88 return !contents_.empty(); 89} 90 91WebContents* CloseTracker::Next() { 92 if (contents_.empty()) 93 return NULL; 94 95 WebContents* web_contents = contents_[0]; 96 contents_.erase(contents_.begin()); 97 return web_contents; 98} 99 100void CloseTracker::Observe(int type, 101 const content::NotificationSource& source, 102 const content::NotificationDetails& details) { 103 WebContents* web_contents = content::Source<WebContents>(source).ptr(); 104 Contents::iterator i = 105 std::find(contents_.begin(), contents_.end(), web_contents); 106 if (i != contents_.end()) 107 contents_.erase(i); 108} 109 110} // namespace 111 112/////////////////////////////////////////////////////////////////////////////// 113// TabStripModel, public: 114 115TabStripModel::TabStripModel(TabStripModelDelegate* delegate, Profile* profile) 116 : delegate_(delegate), 117 profile_(profile), 118 closing_all_(false), 119 in_notify_(false) { 120 DCHECK(delegate_); 121 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 122 content::NotificationService::AllBrowserContextsAndSources()); 123 order_controller_.reset(new TabStripModelOrderController(this)); 124} 125 126TabStripModel::~TabStripModel() { 127 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 128 TabStripModelDeleted()); 129 STLDeleteElements(&contents_data_); 130 order_controller_.reset(); 131} 132 133void TabStripModel::AddObserver(TabStripModelObserver* observer) { 134 observers_.AddObserver(observer); 135} 136 137void TabStripModel::RemoveObserver(TabStripModelObserver* observer) { 138 observers_.RemoveObserver(observer); 139} 140 141bool TabStripModel::ContainsIndex(int index) const { 142 return index >= 0 && index < count(); 143} 144 145void TabStripModel::AppendWebContents(WebContents* contents, 146 bool foreground) { 147 InsertWebContentsAt(count(), contents, 148 foreground ? (ADD_INHERIT_GROUP | ADD_ACTIVE) : 149 ADD_NONE); 150} 151 152void TabStripModel::InsertWebContentsAt(int index, 153 WebContents* contents, 154 int add_types) { 155 delegate_->WillAddWebContents(contents); 156 157 bool active = add_types & ADD_ACTIVE; 158 // Force app tabs to be pinned. 159 extensions::TabHelper* extensions_tab_helper = 160 extensions::TabHelper::FromWebContents(contents); 161 bool pin = extensions_tab_helper->is_app() || add_types & ADD_PINNED; 162 index = ConstrainInsertionIndex(index, pin); 163 164 // In tab dragging situations, if the last tab in the window was detached 165 // then the user aborted the drag, we will have the |closing_all_| member 166 // set (see DetachWebContentsAt) which will mess with our mojo here. We need 167 // to clear this bit. 168 closing_all_ = false; 169 170 // Have to get the active contents before we monkey with |contents_| 171 // otherwise we run into problems when we try to change the active contents 172 // since the old contents and the new contents will be the same... 173 WebContents* active_contents = GetActiveWebContents(); 174 WebContentsData* data = new WebContentsData(contents); 175 data->pinned = pin; 176 if ((add_types & ADD_INHERIT_GROUP) && active_contents) { 177 if (active) { 178 // Forget any existing relationships, we don't want to make things too 179 // confusing by having multiple groups active at the same time. 180 ForgetAllOpeners(); 181 } 182 // Anything opened by a link we deem to have an opener. 183 data->SetGroup(active_contents); 184 } else if ((add_types & ADD_INHERIT_OPENER) && active_contents) { 185 if (active) { 186 // Forget any existing relationships, we don't want to make things too 187 // confusing by having multiple groups active at the same time. 188 ForgetAllOpeners(); 189 } 190 data->opener = active_contents; 191 } 192 193 web_modal::WebContentsModalDialogManager* modal_dialog_manager = 194 web_modal::WebContentsModalDialogManager::FromWebContents(contents); 195 if (modal_dialog_manager) 196 data->blocked = modal_dialog_manager->IsDialogActive(); 197 198 contents_data_.insert(contents_data_.begin() + index, data); 199 200 selection_model_.IncrementFrom(index); 201 202 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 203 TabInsertedAt(contents, index, active)); 204 if (active) { 205 ui::ListSelectionModel new_model; 206 new_model.Copy(selection_model_); 207 new_model.SetSelectedIndex(index); 208 SetSelection(new_model, NOTIFY_DEFAULT); 209 } 210} 211 212WebContents* TabStripModel::ReplaceWebContentsAt(int index, 213 WebContents* new_contents) { 214 delegate_->WillAddWebContents(new_contents); 215 216 DCHECK(ContainsIndex(index)); 217 WebContents* old_contents = GetWebContentsAtImpl(index); 218 219 ForgetOpenersAndGroupsReferencing(old_contents); 220 221 contents_data_[index]->contents = new_contents; 222 223 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 224 TabReplacedAt(this, old_contents, new_contents, index)); 225 226 // When the active WebContents is replaced send out a selection notification 227 // too. We do this as nearly all observers need to treat a replacement of the 228 // selected contents as the selection changing. 229 if (active_index() == index) { 230 FOR_EACH_OBSERVER( 231 TabStripModelObserver, 232 observers_, 233 ActiveTabChanged(old_contents, 234 new_contents, 235 active_index(), 236 TabStripModelObserver::CHANGE_REASON_REPLACED)); 237 } 238 return old_contents; 239} 240 241WebContents* TabStripModel::DiscardWebContentsAt(int index) { 242 DCHECK(ContainsIndex(index)); 243 // Do not discard active tab. 244 if (active_index() == index) 245 return NULL; 246 247 WebContents* null_contents = 248 WebContents::Create(WebContents::CreateParams(profile())); 249 WebContents* old_contents = GetWebContentsAtImpl(index); 250 // Copy over the state from the navigation controller so we preserve the 251 // back/forward history and continue to display the correct title/favicon. 252 null_contents->GetController().CopyStateFrom(old_contents->GetController()); 253 // Replace the tab we're discarding with the null version. 254 ReplaceWebContentsAt(index, null_contents); 255 // Mark the tab so it will reload when we click. 256 contents_data_[index]->discarded = true; 257 // Discard the old tab's renderer. 258 // TODO(jamescook): This breaks script connections with other tabs. 259 // We need to find a different approach that doesn't do that, perhaps based 260 // on navigation to swappedout://. 261 delete old_contents; 262 return null_contents; 263} 264 265WebContents* TabStripModel::DetachWebContentsAt(int index) { 266 CHECK(!in_notify_); 267 if (contents_data_.empty()) 268 return NULL; 269 270 DCHECK(ContainsIndex(index)); 271 272 WebContents* removed_contents = GetWebContentsAtImpl(index); 273 bool was_selected = IsTabSelected(index); 274 int next_selected_index = order_controller_->DetermineNewSelectedIndex(index); 275 delete contents_data_[index]; 276 contents_data_.erase(contents_data_.begin() + index); 277 ForgetOpenersAndGroupsReferencing(removed_contents); 278 if (empty()) 279 closing_all_ = true; 280 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 281 TabDetachedAt(removed_contents, index)); 282 if (empty()) { 283 selection_model_.Clear(); 284 // TabDetachedAt() might unregister observers, so send |TabStripEmpty()| in 285 // a second pass. 286 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, TabStripEmpty()); 287 } else { 288 int old_active = active_index(); 289 selection_model_.DecrementFrom(index); 290 ui::ListSelectionModel old_model; 291 old_model.Copy(selection_model_); 292 if (index == old_active) { 293 NotifyIfTabDeactivated(removed_contents); 294 if (!selection_model_.empty()) { 295 // The active tab was removed, but there is still something selected. 296 // Move the active and anchor to the first selected index. 297 selection_model_.set_active(selection_model_.selected_indices()[0]); 298 selection_model_.set_anchor(selection_model_.active()); 299 } else { 300 // The active tab was removed and nothing is selected. Reset the 301 // selection and send out notification. 302 selection_model_.SetSelectedIndex(next_selected_index); 303 } 304 NotifyIfActiveTabChanged(removed_contents, NOTIFY_DEFAULT); 305 } 306 307 // Sending notification in case the detached tab was selected. Using 308 // NotifyIfActiveOrSelectionChanged() here would not guarantee that a 309 // notification is sent even though the tab selection has changed because 310 // |old_model| is stored after calling DecrementFrom(). 311 if (was_selected) { 312 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 313 TabSelectionChanged(this, old_model)); 314 } 315 } 316 return removed_contents; 317} 318 319void TabStripModel::ActivateTabAt(int index, bool user_gesture) { 320 DCHECK(ContainsIndex(index)); 321 ui::ListSelectionModel new_model; 322 new_model.Copy(selection_model_); 323 new_model.SetSelectedIndex(index); 324 SetSelection(new_model, user_gesture ? NOTIFY_USER_GESTURE : NOTIFY_DEFAULT); 325} 326 327void TabStripModel::AddTabAtToSelection(int index) { 328 DCHECK(ContainsIndex(index)); 329 ui::ListSelectionModel new_model; 330 new_model.Copy(selection_model_); 331 new_model.AddIndexToSelection(index); 332 SetSelection(new_model, NOTIFY_DEFAULT); 333} 334 335void TabStripModel::MoveWebContentsAt(int index, 336 int to_position, 337 bool select_after_move) { 338 DCHECK(ContainsIndex(index)); 339 if (index == to_position) 340 return; 341 342 int first_non_mini_tab = IndexOfFirstNonMiniTab(); 343 if ((index < first_non_mini_tab && to_position >= first_non_mini_tab) || 344 (to_position < first_non_mini_tab && index >= first_non_mini_tab)) { 345 // This would result in mini tabs mixed with non-mini tabs. We don't allow 346 // that. 347 return; 348 } 349 350 MoveWebContentsAtImpl(index, to_position, select_after_move); 351} 352 353void TabStripModel::MoveSelectedTabsTo(int index) { 354 int total_mini_count = IndexOfFirstNonMiniTab(); 355 int selected_mini_count = 0; 356 int selected_count = 357 static_cast<int>(selection_model_.selected_indices().size()); 358 for (int i = 0; i < selected_count && 359 IsMiniTab(selection_model_.selected_indices()[i]); ++i) { 360 selected_mini_count++; 361 } 362 363 // To maintain that all mini-tabs occur before non-mini-tabs we move them 364 // first. 365 if (selected_mini_count > 0) { 366 MoveSelectedTabsToImpl( 367 std::min(total_mini_count - selected_mini_count, index), 0u, 368 selected_mini_count); 369 if (index > total_mini_count - selected_mini_count) { 370 // We're being told to drag mini-tabs to an invalid location. Adjust the 371 // index such that non-mini-tabs end up at a location as though we could 372 // move the mini-tabs to index. See description in header for more 373 // details. 374 index += selected_mini_count; 375 } 376 } 377 if (selected_mini_count == selected_count) 378 return; 379 380 // Then move the non-pinned tabs. 381 MoveSelectedTabsToImpl(std::max(index, total_mini_count), 382 selected_mini_count, 383 selected_count - selected_mini_count); 384} 385 386WebContents* TabStripModel::GetActiveWebContents() const { 387 return GetWebContentsAt(active_index()); 388} 389 390WebContents* TabStripModel::GetWebContentsAt(int index) const { 391 if (ContainsIndex(index)) 392 return GetWebContentsAtImpl(index); 393 return NULL; 394} 395 396int TabStripModel::GetIndexOfWebContents(const WebContents* contents) const { 397 for (size_t i = 0; i < contents_data_.size(); ++i) { 398 if (contents_data_[i]->contents == contents) 399 return i; 400 } 401 return kNoTab; 402} 403 404void TabStripModel::UpdateWebContentsStateAt(int index, 405 TabStripModelObserver::TabChangeType change_type) { 406 DCHECK(ContainsIndex(index)); 407 408 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 409 TabChangedAt(GetWebContentsAtImpl(index), index, change_type)); 410} 411 412void TabStripModel::CloseAllTabs() { 413 // Set state so that observers can adjust their behavior to suit this 414 // specific condition when CloseWebContentsAt causes a flurry of 415 // Close/Detach/Select notifications to be sent. 416 closing_all_ = true; 417 std::vector<int> closing_tabs; 418 for (int i = count() - 1; i >= 0; --i) 419 closing_tabs.push_back(i); 420 InternalCloseTabs(closing_tabs, CLOSE_CREATE_HISTORICAL_TAB); 421} 422 423bool TabStripModel::CloseWebContentsAt(int index, uint32 close_types) { 424 DCHECK(ContainsIndex(index)); 425 std::vector<int> closing_tabs; 426 closing_tabs.push_back(index); 427 return InternalCloseTabs(closing_tabs, close_types); 428} 429 430bool TabStripModel::TabsAreLoading() const { 431 for (WebContentsDataVector::const_iterator iter = contents_data_.begin(); 432 iter != contents_data_.end(); ++iter) { 433 if ((*iter)->contents->IsLoading()) 434 return true; 435 } 436 return false; 437} 438 439WebContents* TabStripModel::GetOpenerOfWebContentsAt(int index) { 440 DCHECK(ContainsIndex(index)); 441 return contents_data_[index]->opener; 442} 443 444void TabStripModel::SetOpenerOfWebContentsAt(int index, 445 WebContents* opener) { 446 DCHECK(ContainsIndex(index)); 447 DCHECK(opener); 448 contents_data_[index]->opener = opener; 449} 450 451int TabStripModel::GetIndexOfNextWebContentsOpenedBy(const WebContents* opener, 452 int start_index, 453 bool use_group) const { 454 DCHECK(opener); 455 DCHECK(ContainsIndex(start_index)); 456 457 // Check tabs after start_index first. 458 for (int i = start_index + 1; i < count(); ++i) { 459 if (OpenerMatches(contents_data_[i], opener, use_group)) 460 return i; 461 } 462 // Then check tabs before start_index, iterating backwards. 463 for (int i = start_index - 1; i >= 0; --i) { 464 if (OpenerMatches(contents_data_[i], opener, use_group)) 465 return i; 466 } 467 return kNoTab; 468} 469 470int TabStripModel::GetIndexOfLastWebContentsOpenedBy(const WebContents* opener, 471 int start_index) const { 472 DCHECK(opener); 473 DCHECK(ContainsIndex(start_index)); 474 475 for (int i = contents_data_.size() - 1; i > start_index; --i) { 476 if (contents_data_[i]->opener == opener) 477 return i; 478 } 479 return kNoTab; 480} 481 482void TabStripModel::TabNavigating(WebContents* contents, 483 content::PageTransition transition) { 484 if (ShouldForgetOpenersForTransition(transition)) { 485 // Don't forget the openers if this tab is a New Tab page opened at the 486 // end of the TabStrip (e.g. by pressing Ctrl+T). Give the user one 487 // navigation of one of these transition types before resetting the 488 // opener relationships (this allows for the use case of opening a new 489 // tab to do a quick look-up of something while viewing a tab earlier in 490 // the strip). We can make this heuristic more permissive if need be. 491 if (!IsNewTabAtEndOfTabStrip(contents)) { 492 // If the user navigates the current tab to another page in any way 493 // other than by clicking a link, we want to pro-actively forget all 494 // TabStrip opener relationships since we assume they're beginning a 495 // different task by reusing the current tab. 496 ForgetAllOpeners(); 497 // In this specific case we also want to reset the group relationship, 498 // since it is now technically invalid. 499 ForgetGroup(contents); 500 } 501 } 502} 503 504void TabStripModel::ForgetAllOpeners() { 505 // Forget all opener memories so we don't do anything weird with tab 506 // re-selection ordering. 507 for (WebContentsDataVector::const_iterator iter = contents_data_.begin(); 508 iter != contents_data_.end(); ++iter) 509 (*iter)->ForgetOpener(); 510} 511 512void TabStripModel::ForgetGroup(WebContents* contents) { 513 int index = GetIndexOfWebContents(contents); 514 DCHECK(ContainsIndex(index)); 515 contents_data_[index]->SetGroup(NULL); 516 contents_data_[index]->ForgetOpener(); 517} 518 519bool TabStripModel::ShouldResetGroupOnSelect(WebContents* contents) const { 520 int index = GetIndexOfWebContents(contents); 521 DCHECK(ContainsIndex(index)); 522 return contents_data_[index]->reset_group_on_select; 523} 524 525void TabStripModel::SetTabBlocked(int index, bool blocked) { 526 DCHECK(ContainsIndex(index)); 527 if (contents_data_[index]->blocked == blocked) 528 return; 529 contents_data_[index]->blocked = blocked; 530 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 531 TabBlockedStateChanged(contents_data_[index]->contents, 532 index)); 533} 534 535void TabStripModel::SetTabPinned(int index, bool pinned) { 536 DCHECK(ContainsIndex(index)); 537 if (contents_data_[index]->pinned == pinned) 538 return; 539 540 if (IsAppTab(index)) { 541 if (!pinned) { 542 // App tabs should always be pinned. 543 NOTREACHED(); 544 return; 545 } 546 // Changing the pinned state of an app tab doesn't affect its mini-tab 547 // status. 548 contents_data_[index]->pinned = pinned; 549 } else { 550 // The tab is not an app tab, its position may have to change as the 551 // mini-tab state is changing. 552 int non_mini_tab_index = IndexOfFirstNonMiniTab(); 553 contents_data_[index]->pinned = pinned; 554 if (pinned && index != non_mini_tab_index) { 555 MoveWebContentsAtImpl(index, non_mini_tab_index, false); 556 index = non_mini_tab_index; 557 } else if (!pinned && index + 1 != non_mini_tab_index) { 558 MoveWebContentsAtImpl(index, non_mini_tab_index - 1, false); 559 index = non_mini_tab_index - 1; 560 } 561 562 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 563 TabMiniStateChanged(contents_data_[index]->contents, 564 index)); 565 } 566 567 // else: the tab was at the boundary and its position doesn't need to change. 568 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 569 TabPinnedStateChanged(contents_data_[index]->contents, 570 index)); 571} 572 573bool TabStripModel::IsTabPinned(int index) const { 574 DCHECK(ContainsIndex(index)); 575 return contents_data_[index]->pinned; 576} 577 578bool TabStripModel::IsMiniTab(int index) const { 579 return IsTabPinned(index) || IsAppTab(index); 580} 581 582bool TabStripModel::IsAppTab(int index) const { 583 WebContents* contents = GetWebContentsAt(index); 584 return contents && extensions::TabHelper::FromWebContents(contents)->is_app(); 585} 586 587bool TabStripModel::IsTabBlocked(int index) const { 588 return contents_data_[index]->blocked; 589} 590 591bool TabStripModel::IsTabDiscarded(int index) const { 592 return contents_data_[index]->discarded; 593} 594 595int TabStripModel::IndexOfFirstNonMiniTab() const { 596 for (size_t i = 0; i < contents_data_.size(); ++i) { 597 if (!IsMiniTab(static_cast<int>(i))) 598 return static_cast<int>(i); 599 } 600 // No mini-tabs. 601 return count(); 602} 603 604int TabStripModel::ConstrainInsertionIndex(int index, bool mini_tab) { 605 return mini_tab ? std::min(std::max(0, index), IndexOfFirstNonMiniTab()) : 606 std::min(count(), std::max(index, IndexOfFirstNonMiniTab())); 607} 608 609void TabStripModel::ExtendSelectionTo(int index) { 610 DCHECK(ContainsIndex(index)); 611 ui::ListSelectionModel new_model; 612 new_model.Copy(selection_model_); 613 new_model.SetSelectionFromAnchorTo(index); 614 SetSelection(new_model, NOTIFY_DEFAULT); 615} 616 617void TabStripModel::ToggleSelectionAt(int index) { 618 DCHECK(ContainsIndex(index)); 619 ui::ListSelectionModel new_model; 620 new_model.Copy(selection_model()); 621 if (selection_model_.IsSelected(index)) { 622 if (selection_model_.size() == 1) { 623 // One tab must be selected and this tab is currently selected so we can't 624 // unselect it. 625 return; 626 } 627 new_model.RemoveIndexFromSelection(index); 628 new_model.set_anchor(index); 629 if (new_model.active() == index || 630 new_model.active() == ui::ListSelectionModel::kUnselectedIndex) 631 new_model.set_active(new_model.selected_indices()[0]); 632 } else { 633 new_model.AddIndexToSelection(index); 634 new_model.set_anchor(index); 635 new_model.set_active(index); 636 } 637 SetSelection(new_model, NOTIFY_DEFAULT); 638} 639 640void TabStripModel::AddSelectionFromAnchorTo(int index) { 641 ui::ListSelectionModel new_model; 642 new_model.Copy(selection_model_); 643 new_model.AddSelectionFromAnchorTo(index); 644 SetSelection(new_model, NOTIFY_DEFAULT); 645} 646 647bool TabStripModel::IsTabSelected(int index) const { 648 DCHECK(ContainsIndex(index)); 649 return selection_model_.IsSelected(index); 650} 651 652void TabStripModel::SetSelectionFromModel( 653 const ui::ListSelectionModel& source) { 654 DCHECK_NE(ui::ListSelectionModel::kUnselectedIndex, source.active()); 655 SetSelection(source, NOTIFY_DEFAULT); 656} 657 658void TabStripModel::AddWebContents(WebContents* contents, 659 int index, 660 content::PageTransition transition, 661 int add_types) { 662 // If the newly-opened tab is part of the same task as the parent tab, we want 663 // to inherit the parent's "group" attribute, so that if this tab is then 664 // closed we'll jump back to the parent tab. 665 bool inherit_group = (add_types & ADD_INHERIT_GROUP) == ADD_INHERIT_GROUP; 666 667 if (transition == content::PAGE_TRANSITION_LINK && 668 (add_types & ADD_FORCE_INDEX) == 0) { 669 // We assume tabs opened via link clicks are part of the same task as their 670 // parent. Note that when |force_index| is true (e.g. when the user 671 // drag-and-drops a link to the tab strip), callers aren't really handling 672 // link clicks, they just want to score the navigation like a link click in 673 // the history backend, so we don't inherit the group in this case. 674 index = order_controller_->DetermineInsertionIndex(transition, 675 add_types & ADD_ACTIVE); 676 inherit_group = true; 677 } else { 678 // For all other types, respect what was passed to us, normalizing -1s and 679 // values that are too large. 680 if (index < 0 || index > count()) 681 index = count(); 682 } 683 684 if (transition == content::PAGE_TRANSITION_TYPED && index == count()) { 685 // Also, any tab opened at the end of the TabStrip with a "TYPED" 686 // transition inherit group as well. This covers the cases where the user 687 // creates a New Tab (e.g. Ctrl+T, or clicks the New Tab button), or types 688 // in the address bar and presses Alt+Enter. This allows for opening a new 689 // Tab to quickly look up something. When this Tab is closed, the old one 690 // is re-selected, not the next-adjacent. 691 inherit_group = true; 692 } 693 InsertWebContentsAt(index, contents, 694 add_types | (inherit_group ? ADD_INHERIT_GROUP : 0)); 695 // Reset the index, just in case insert ended up moving it on us. 696 index = GetIndexOfWebContents(contents); 697 698 if (inherit_group && transition == content::PAGE_TRANSITION_TYPED) 699 contents_data_[index]->reset_group_on_select = true; 700 701 // TODO(sky): figure out why this is here and not in InsertWebContentsAt. When 702 // here we seem to get failures in startup perf tests. 703 // Ensure that the new WebContentsView begins at the same size as the 704 // previous WebContentsView if it existed. Otherwise, the initial WebKit 705 // layout will be performed based on a width of 0 pixels, causing a 706 // very long, narrow, inaccurate layout. Because some scripts on pages (as 707 // well as WebKit's anchor link location calculation) are run on the 708 // initial layout and not recalculated later, we need to ensure the first 709 // layout is performed with sane view dimensions even when we're opening a 710 // new background tab. 711 if (WebContents* old_contents = GetActiveWebContents()) { 712 if ((add_types & ADD_ACTIVE) == 0) { 713 contents->GetView()->SizeContents( 714 old_contents->GetView()->GetContainerSize()); 715 } 716 } 717} 718 719void TabStripModel::CloseSelectedTabs() { 720 InternalCloseTabs(selection_model_.selected_indices(), 721 CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE); 722} 723 724void TabStripModel::SelectNextTab() { 725 SelectRelativeTab(true); 726} 727 728void TabStripModel::SelectPreviousTab() { 729 SelectRelativeTab(false); 730} 731 732void TabStripModel::SelectLastTab() { 733 ActivateTabAt(count() - 1, true); 734} 735 736void TabStripModel::MoveTabNext() { 737 // TODO: this likely needs to be updated for multi-selection. 738 int new_index = std::min(active_index() + 1, count() - 1); 739 MoveWebContentsAt(active_index(), new_index, true); 740} 741 742void TabStripModel::MoveTabPrevious() { 743 // TODO: this likely needs to be updated for multi-selection. 744 int new_index = std::max(active_index() - 1, 0); 745 MoveWebContentsAt(active_index(), new_index, true); 746} 747 748// Context menu functions. 749bool TabStripModel::IsContextMenuCommandEnabled( 750 int context_index, ContextMenuCommand command_id) const { 751 DCHECK(command_id > CommandFirst && command_id < CommandLast); 752 switch (command_id) { 753 case CommandNewTab: 754 case CommandCloseTab: 755 return true; 756 757 case CommandReload: { 758 std::vector<int> indices = GetIndicesForCommand(context_index); 759 for (size_t i = 0; i < indices.size(); ++i) { 760 WebContents* tab = GetWebContentsAt(indices[i]); 761 if (tab) { 762 CoreTabHelperDelegate* core_delegate = 763 CoreTabHelper::FromWebContents(tab)->delegate(); 764 if (!core_delegate || core_delegate->CanReloadContents(tab)) 765 return true; 766 } 767 } 768 return false; 769 } 770 771 case CommandCloseOtherTabs: 772 case CommandCloseTabsToRight: 773 return !GetIndicesClosedByCommand(context_index, command_id).empty(); 774 775 case CommandDuplicate: { 776 std::vector<int> indices = GetIndicesForCommand(context_index); 777 for (size_t i = 0; i < indices.size(); ++i) { 778 if (delegate_->CanDuplicateContentsAt(indices[i])) 779 return true; 780 } 781 return false; 782 } 783 784 case CommandRestoreTab: 785 return delegate_->GetRestoreTabType() != 786 TabStripModelDelegate::RESTORE_NONE; 787 788 case CommandTogglePinned: { 789 std::vector<int> indices = GetIndicesForCommand(context_index); 790 for (size_t i = 0; i < indices.size(); ++i) { 791 if (!IsAppTab(indices[i])) 792 return true; 793 } 794 return false; 795 } 796 797 case CommandBookmarkAllTabs: 798 return browser_defaults::bookmarks_enabled && 799 delegate_->CanBookmarkAllTabs(); 800 801 case CommandSelectByDomain: 802 case CommandSelectByOpener: 803 return true; 804 805 default: 806 NOTREACHED(); 807 } 808 return false; 809} 810 811void TabStripModel::ExecuteContextMenuCommand( 812 int context_index, ContextMenuCommand command_id) { 813 DCHECK(command_id > CommandFirst && command_id < CommandLast); 814 switch (command_id) { 815 case CommandNewTab: 816 content::RecordAction(UserMetricsAction("TabContextMenu_NewTab")); 817 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab", 818 TabStripModel::NEW_TAB_CONTEXT_MENU, 819 TabStripModel::NEW_TAB_ENUM_COUNT); 820 delegate()->AddBlankTabAt(context_index + 1, true); 821 break; 822 823 case CommandReload: { 824 content::RecordAction(UserMetricsAction("TabContextMenu_Reload")); 825 std::vector<int> indices = GetIndicesForCommand(context_index); 826 for (size_t i = 0; i < indices.size(); ++i) { 827 WebContents* tab = GetWebContentsAt(indices[i]); 828 if (tab) { 829 CoreTabHelperDelegate* core_delegate = 830 CoreTabHelper::FromWebContents(tab)->delegate(); 831 if (!core_delegate || core_delegate->CanReloadContents(tab)) 832 tab->GetController().Reload(true); 833 } 834 } 835 break; 836 } 837 838 case CommandDuplicate: { 839 content::RecordAction(UserMetricsAction("TabContextMenu_Duplicate")); 840 std::vector<int> indices = GetIndicesForCommand(context_index); 841 // Copy the WebContents off as the indices will change as tabs are 842 // duplicated. 843 std::vector<WebContents*> tabs; 844 for (size_t i = 0; i < indices.size(); ++i) 845 tabs.push_back(GetWebContentsAt(indices[i])); 846 for (size_t i = 0; i < tabs.size(); ++i) { 847 int index = GetIndexOfWebContents(tabs[i]); 848 if (index != -1 && delegate_->CanDuplicateContentsAt(index)) 849 delegate_->DuplicateContentsAt(index); 850 } 851 break; 852 } 853 854 case CommandCloseTab: { 855 content::RecordAction(UserMetricsAction("TabContextMenu_CloseTab")); 856 InternalCloseTabs(GetIndicesForCommand(context_index), 857 CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE); 858 break; 859 } 860 861 case CommandCloseOtherTabs: { 862 content::RecordAction( 863 UserMetricsAction("TabContextMenu_CloseOtherTabs")); 864 InternalCloseTabs(GetIndicesClosedByCommand(context_index, command_id), 865 CLOSE_CREATE_HISTORICAL_TAB); 866 break; 867 } 868 869 case CommandCloseTabsToRight: { 870 content::RecordAction( 871 UserMetricsAction("TabContextMenu_CloseTabsToRight")); 872 InternalCloseTabs(GetIndicesClosedByCommand(context_index, command_id), 873 CLOSE_CREATE_HISTORICAL_TAB); 874 break; 875 } 876 877 case CommandRestoreTab: { 878 content::RecordAction(UserMetricsAction("TabContextMenu_RestoreTab")); 879 delegate_->RestoreTab(); 880 break; 881 } 882 883 case CommandTogglePinned: { 884 content::RecordAction( 885 UserMetricsAction("TabContextMenu_TogglePinned")); 886 std::vector<int> indices = GetIndicesForCommand(context_index); 887 bool pin = WillContextMenuPin(context_index); 888 if (pin) { 889 for (size_t i = 0; i < indices.size(); ++i) { 890 if (!IsAppTab(indices[i])) 891 SetTabPinned(indices[i], true); 892 } 893 } else { 894 // Unpin from the back so that the order is maintained (unpinning can 895 // trigger moving a tab). 896 for (size_t i = indices.size(); i > 0; --i) { 897 if (!IsAppTab(indices[i - 1])) 898 SetTabPinned(indices[i - 1], false); 899 } 900 } 901 break; 902 } 903 904 case CommandBookmarkAllTabs: { 905 content::RecordAction( 906 UserMetricsAction("TabContextMenu_BookmarkAllTabs")); 907 908 delegate_->BookmarkAllTabs(); 909 break; 910 } 911 912 case CommandSelectByDomain: 913 case CommandSelectByOpener: { 914 std::vector<int> indices; 915 if (command_id == CommandSelectByDomain) 916 GetIndicesWithSameDomain(context_index, &indices); 917 else 918 GetIndicesWithSameOpener(context_index, &indices); 919 ui::ListSelectionModel selection_model; 920 selection_model.SetSelectedIndex(context_index); 921 for (size_t i = 0; i < indices.size(); ++i) 922 selection_model.AddIndexToSelection(indices[i]); 923 SetSelectionFromModel(selection_model); 924 break; 925 } 926 927 default: 928 NOTREACHED(); 929 } 930} 931 932std::vector<int> TabStripModel::GetIndicesClosedByCommand( 933 int index, 934 ContextMenuCommand id) const { 935 DCHECK(ContainsIndex(index)); 936 DCHECK(id == CommandCloseTabsToRight || id == CommandCloseOtherTabs); 937 bool is_selected = IsTabSelected(index); 938 int start; 939 if (id == CommandCloseTabsToRight) { 940 if (is_selected) { 941 start = selection_model_.selected_indices()[ 942 selection_model_.selected_indices().size() - 1] + 1; 943 } else { 944 start = index + 1; 945 } 946 } else { 947 start = 0; 948 } 949 // NOTE: callers expect the vector to be sorted in descending order. 950 std::vector<int> indices; 951 for (int i = count() - 1; i >= start; --i) { 952 if (i != index && !IsMiniTab(i) && (!is_selected || !IsTabSelected(i))) 953 indices.push_back(i); 954 } 955 return indices; 956} 957 958bool TabStripModel::WillContextMenuPin(int index) { 959 std::vector<int> indices = GetIndicesForCommand(index); 960 // If all tabs are pinned, then we unpin, otherwise we pin. 961 bool all_pinned = true; 962 for (size_t i = 0; i < indices.size() && all_pinned; ++i) { 963 if (!IsAppTab(index)) // We never change app tabs. 964 all_pinned = IsTabPinned(indices[i]); 965 } 966 return !all_pinned; 967} 968 969/////////////////////////////////////////////////////////////////////////////// 970// TabStripModel, content::NotificationObserver implementation: 971 972void TabStripModel::Observe(int type, 973 const content::NotificationSource& source, 974 const content::NotificationDetails& details) { 975 switch (type) { 976 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: { 977 // Sometimes, on qemu, it seems like a WebContents object can be destroyed 978 // while we still have a reference to it. We need to break this reference 979 // here so we don't crash later. 980 int index = GetIndexOfWebContents( 981 content::Source<WebContents>(source).ptr()); 982 if (index != TabStripModel::kNoTab) { 983 // Note that we only detach the contents here, not close it - it's 984 // already been closed. We just want to undo our bookkeeping. 985 DetachWebContentsAt(index); 986 } 987 break; 988 } 989 990 default: 991 NOTREACHED(); 992 } 993} 994 995// static 996bool TabStripModel::ContextMenuCommandToBrowserCommand(int cmd_id, 997 int* browser_cmd) { 998 switch (cmd_id) { 999 case CommandNewTab: 1000 *browser_cmd = IDC_NEW_TAB; 1001 break; 1002 case CommandReload: 1003 *browser_cmd = IDC_RELOAD; 1004 break; 1005 case CommandDuplicate: 1006 *browser_cmd = IDC_DUPLICATE_TAB; 1007 break; 1008 case CommandCloseTab: 1009 *browser_cmd = IDC_CLOSE_TAB; 1010 break; 1011 case CommandRestoreTab: 1012 *browser_cmd = IDC_RESTORE_TAB; 1013 break; 1014 case CommandBookmarkAllTabs: 1015 *browser_cmd = IDC_BOOKMARK_ALL_TABS; 1016 break; 1017 default: 1018 *browser_cmd = 0; 1019 return false; 1020 } 1021 1022 return true; 1023} 1024 1025/////////////////////////////////////////////////////////////////////////////// 1026// TabStripModel, private: 1027 1028std::vector<WebContents*> TabStripModel::GetWebContentsFromIndices( 1029 const std::vector<int>& indices) const { 1030 std::vector<WebContents*> contents; 1031 for (size_t i = 0; i < indices.size(); ++i) 1032 contents.push_back(GetWebContentsAtImpl(indices[i])); 1033 return contents; 1034} 1035 1036void TabStripModel::GetIndicesWithSameDomain(int index, 1037 std::vector<int>* indices) { 1038 std::string domain = GetWebContentsAt(index)->GetURL().host(); 1039 if (domain.empty()) 1040 return; 1041 for (int i = 0; i < count(); ++i) { 1042 if (i == index) 1043 continue; 1044 if (GetWebContentsAt(i)->GetURL().host() == domain) 1045 indices->push_back(i); 1046 } 1047} 1048 1049void TabStripModel::GetIndicesWithSameOpener(int index, 1050 std::vector<int>* indices) { 1051 WebContents* opener = contents_data_[index]->group; 1052 if (!opener) { 1053 // If there is no group, find all tabs with the selected tab as the opener. 1054 opener = GetWebContentsAt(index); 1055 if (!opener) 1056 return; 1057 } 1058 for (int i = 0; i < count(); ++i) { 1059 if (i == index) 1060 continue; 1061 if (contents_data_[i]->group == opener || 1062 GetWebContentsAtImpl(i) == opener) { 1063 indices->push_back(i); 1064 } 1065 } 1066} 1067 1068std::vector<int> TabStripModel::GetIndicesForCommand(int index) const { 1069 if (!IsTabSelected(index)) { 1070 std::vector<int> indices; 1071 indices.push_back(index); 1072 return indices; 1073 } 1074 return selection_model_.selected_indices(); 1075} 1076 1077bool TabStripModel::IsNewTabAtEndOfTabStrip(WebContents* contents) const { 1078 const GURL& url = contents->GetURL(); 1079 return url.SchemeIs(chrome::kChromeUIScheme) && 1080 url.host() == chrome::kChromeUINewTabHost && 1081 contents == GetWebContentsAtImpl(count() - 1) && 1082 contents->GetController().GetEntryCount() == 1; 1083} 1084 1085bool TabStripModel::InternalCloseTabs(const std::vector<int>& indices, 1086 uint32 close_types) { 1087 if (indices.empty()) 1088 return true; 1089 1090 CloseTracker close_tracker(GetWebContentsFromIndices(indices)); 1091 1092 // We only try the fast shutdown path if the whole browser process is *not* 1093 // shutting down. Fast shutdown during browser termination is handled in 1094 // BrowserShutdown. 1095 if (browser_shutdown::GetShutdownType() == browser_shutdown::NOT_VALID) { 1096 // Construct a map of processes to the number of associated tabs that are 1097 // closing. 1098 std::map<content::RenderProcessHost*, size_t> processes; 1099 for (size_t i = 0; i < indices.size(); ++i) { 1100 WebContents* closing_contents = GetWebContentsAtImpl(indices[i]); 1101 content::RenderProcessHost* process = 1102 closing_contents->GetRenderProcessHost(); 1103 ++processes[process]; 1104 } 1105 1106 // Try to fast shutdown the tabs that can close. 1107 for (std::map<content::RenderProcessHost*, size_t>::iterator iter = 1108 processes.begin(); iter != processes.end(); ++iter) { 1109 iter->first->FastShutdownForPageCount(iter->second); 1110 } 1111 } 1112 1113 // We now return to our regularly scheduled shutdown procedure. 1114 bool retval = true; 1115 while (close_tracker.HasNext()) { 1116 WebContents* closing_contents = close_tracker.Next(); 1117 int index = GetIndexOfWebContents(closing_contents); 1118 // Make sure we still contain the tab. 1119 if (index == kNoTab) 1120 continue; 1121 1122 CoreTabHelper* core_tab_helper = 1123 CoreTabHelper::FromWebContents(closing_contents); 1124 core_tab_helper->OnCloseStarted(); 1125 1126 // Update the explicitly closed state. If the unload handlers cancel the 1127 // close the state is reset in Browser. We don't update the explicitly 1128 // closed state if already marked as explicitly closed as unload handlers 1129 // call back to this if the close is allowed. 1130 if (!closing_contents->GetClosedByUserGesture()) { 1131 closing_contents->SetClosedByUserGesture( 1132 close_types & CLOSE_USER_GESTURE); 1133 } 1134 1135 if (delegate_->RunUnloadListenerBeforeClosing(closing_contents)) { 1136 retval = false; 1137 continue; 1138 } 1139 1140 InternalCloseTab(closing_contents, index, 1141 (close_types & CLOSE_CREATE_HISTORICAL_TAB) != 0); 1142 } 1143 1144 return retval; 1145} 1146 1147void TabStripModel::InternalCloseTab(WebContents* contents, 1148 int index, 1149 bool create_historical_tabs) { 1150 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 1151 TabClosingAt(this, contents, index)); 1152 1153 // Ask the delegate to save an entry for this tab in the historical tab 1154 // database if applicable. 1155 if (create_historical_tabs) 1156 delegate_->CreateHistoricalTab(contents); 1157 1158 // Deleting the WebContents will call back to us via 1159 // NotificationObserver and detach it. 1160 delete contents; 1161} 1162 1163WebContents* TabStripModel::GetWebContentsAtImpl(int index) const { 1164 CHECK(ContainsIndex(index)) << 1165 "Failed to find: " << index << " in: " << count() << " entries."; 1166 return contents_data_[index]->contents; 1167} 1168 1169void TabStripModel::NotifyIfTabDeactivated(WebContents* contents) { 1170 if (contents) { 1171 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 1172 TabDeactivated(contents)); 1173 } 1174} 1175 1176void TabStripModel::NotifyIfActiveTabChanged(WebContents* old_contents, 1177 NotifyTypes notify_types) { 1178 WebContents* new_contents = GetWebContentsAtImpl(active_index()); 1179 if (old_contents != new_contents) { 1180 int reason = notify_types == NOTIFY_USER_GESTURE 1181 ? TabStripModelObserver::CHANGE_REASON_USER_GESTURE 1182 : TabStripModelObserver::CHANGE_REASON_NONE; 1183 CHECK(!in_notify_); 1184 in_notify_ = true; 1185 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 1186 ActiveTabChanged(old_contents, 1187 new_contents, 1188 active_index(), 1189 reason)); 1190 in_notify_ = false; 1191 // Activating a discarded tab reloads it, so it is no longer discarded. 1192 contents_data_[active_index()]->discarded = false; 1193 } 1194} 1195 1196void TabStripModel::NotifyIfActiveOrSelectionChanged( 1197 WebContents* old_contents, 1198 NotifyTypes notify_types, 1199 const ui::ListSelectionModel& old_model) { 1200 NotifyIfActiveTabChanged(old_contents, notify_types); 1201 1202 if (!selection_model().Equals(old_model)) { 1203 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 1204 TabSelectionChanged(this, old_model)); 1205 } 1206} 1207 1208void TabStripModel::SetSelection( 1209 const ui::ListSelectionModel& new_model, 1210 NotifyTypes notify_types) { 1211 WebContents* old_contents = GetActiveWebContents(); 1212 ui::ListSelectionModel old_model; 1213 old_model.Copy(selection_model_); 1214 if (new_model.active() != selection_model_.active()) 1215 NotifyIfTabDeactivated(old_contents); 1216 selection_model_.Copy(new_model); 1217 NotifyIfActiveOrSelectionChanged(old_contents, notify_types, old_model); 1218} 1219 1220void TabStripModel::SelectRelativeTab(bool next) { 1221 // This may happen during automated testing or if a user somehow buffers 1222 // many key accelerators. 1223 if (contents_data_.empty()) 1224 return; 1225 1226 int index = active_index(); 1227 int delta = next ? 1 : -1; 1228 index = (index + count() + delta) % count(); 1229 ActivateTabAt(index, true); 1230} 1231 1232void TabStripModel::MoveWebContentsAtImpl(int index, 1233 int to_position, 1234 bool select_after_move) { 1235 WebContentsData* moved_data = contents_data_[index]; 1236 contents_data_.erase(contents_data_.begin() + index); 1237 contents_data_.insert(contents_data_.begin() + to_position, moved_data); 1238 1239 selection_model_.Move(index, to_position); 1240 if (!selection_model_.IsSelected(select_after_move) && select_after_move) { 1241 // TODO(sky): why doesn't this code notify observers? 1242 selection_model_.SetSelectedIndex(to_position); 1243 } 1244 1245 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, 1246 TabMoved(moved_data->contents, index, to_position)); 1247} 1248 1249void TabStripModel::MoveSelectedTabsToImpl(int index, 1250 size_t start, 1251 size_t length) { 1252 DCHECK(start < selection_model_.selected_indices().size() && 1253 start + length <= selection_model_.selected_indices().size()); 1254 size_t end = start + length; 1255 int count_before_index = 0; 1256 for (size_t i = start; i < end && 1257 selection_model_.selected_indices()[i] < index + count_before_index; 1258 ++i) { 1259 count_before_index++; 1260 } 1261 1262 // First move those before index. Any tabs before index end up moving in the 1263 // selection model so we use start each time through. 1264 int target_index = index + count_before_index; 1265 size_t tab_index = start; 1266 while (tab_index < end && 1267 selection_model_.selected_indices()[start] < index) { 1268 MoveWebContentsAt(selection_model_.selected_indices()[start], 1269 target_index - 1, false); 1270 tab_index++; 1271 } 1272 1273 // Then move those after the index. These don't result in reordering the 1274 // selection. 1275 while (tab_index < end) { 1276 if (selection_model_.selected_indices()[tab_index] != target_index) { 1277 MoveWebContentsAt(selection_model_.selected_indices()[tab_index], 1278 target_index, false); 1279 } 1280 tab_index++; 1281 target_index++; 1282 } 1283} 1284 1285// static 1286bool TabStripModel::OpenerMatches(const WebContentsData* data, 1287 const WebContents* opener, 1288 bool use_group) { 1289 return data->opener == opener || (use_group && data->group == opener); 1290} 1291 1292void TabStripModel::ForgetOpenersAndGroupsReferencing( 1293 const WebContents* tab) { 1294 for (WebContentsDataVector::const_iterator i = contents_data_.begin(); 1295 i != contents_data_.end(); ++i) { 1296 if ((*i)->group == tab) 1297 (*i)->group = NULL; 1298 if ((*i)->opener == tab) 1299 (*i)->opener = NULL; 1300 } 1301} 1302