Home | History | Annotate | Download | only in tabs
      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