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