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