• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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).
ShouldForgetOpenersForTransition(PageTransition::Type transition)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 
CanCloseTab() const55 bool TabStripModelDelegate::CanCloseTab() const {
56   return true;
57 }
58 
59 ///////////////////////////////////////////////////////////////////////////////
60 // TabStripModel, public:
61 
TabStripModel(TabStripModelDelegate * delegate,Profile * profile)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 
~TabStripModel()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 
AddObserver(TabStripModelObserver * observer)84 void TabStripModel::AddObserver(TabStripModelObserver* observer) {
85   observers_.AddObserver(observer);
86 }
87 
RemoveObserver(TabStripModelObserver * observer)88 void TabStripModel::RemoveObserver(TabStripModelObserver* observer) {
89   observers_.RemoveObserver(observer);
90 }
91 
SetInsertionPolicy(InsertionPolicy policy)92 void TabStripModel::SetInsertionPolicy(InsertionPolicy policy) {
93   order_controller_->set_insertion_policy(policy);
94 }
95 
insertion_policy() const96 TabStripModel::InsertionPolicy TabStripModel::insertion_policy() const {
97   return order_controller_->insertion_policy();
98 }
99 
HasObserver(TabStripModelObserver * observer)100 bool TabStripModel::HasObserver(TabStripModelObserver* observer) {
101   return observers_.HasObserver(observer);
102 }
103 
ContainsIndex(int index) const104 bool TabStripModel::ContainsIndex(int index) const {
105   return index >= 0 && index < count();
106 }
107 
AppendTabContents(TabContentsWrapper * contents,bool foreground)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 
InsertTabContentsAt(int index,TabContentsWrapper * contents,int add_types)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 
ReplaceTabContentsAt(int index,TabContentsWrapper * new_contents)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 
ReplaceNavigationControllerAt(int index,TabContentsWrapper * contents)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 
DetachTabContentsAt(int index)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 
ActivateTabAt(int index,bool user_gesture)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 
MoveTabContentsAt(int index,int to_position,bool select_after_move)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 
MoveSelectedTabsTo(int index)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 
GetSelectedTabContents() const313 TabContentsWrapper* TabStripModel::GetSelectedTabContents() const {
314   return GetTabContentsAt(active_index());
315 }
316 
GetTabContentsAt(int index) const317 TabContentsWrapper* TabStripModel::GetTabContentsAt(int index) const {
318   if (ContainsIndex(index))
319     return GetContentsAt(index);
320   return NULL;
321 }
322 
GetIndexOfTabContents(const TabContentsWrapper * contents) const323 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 
GetWrapperIndex(const TabContents * contents) const334 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 
GetIndexOfController(const NavigationController * controller) const344 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 
UpdateTabContentsStateAt(int index,TabStripModelObserver::TabChangeType change_type)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 
CloseAllTabs()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 
CloseTabContentsAt(int index,uint32 close_types)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 
TabsAreLoading() const380 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 
GetOpenerOfTabContentsAt(int index)389 NavigationController* TabStripModel::GetOpenerOfTabContentsAt(int index) {
390   DCHECK(ContainsIndex(index));
391   return contents_data_.at(index)->opener;
392 }
393 
GetIndexOfNextTabContentsOpenedBy(const NavigationController * opener,int start_index,bool use_group) const394 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 
GetIndexOfFirstTabContentsOpenedBy(const NavigationController * opener,int start_index) const412 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 
GetIndexOfLastTabContentsOpenedBy(const NavigationController * opener,int start_index) const425 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 
TabNavigating(TabContentsWrapper * contents,PageTransition::Type transition)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 
ForgetAllOpeners()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 
ForgetGroup(TabContentsWrapper * contents)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 
ShouldResetGroupOnSelect(TabContentsWrapper * contents) const481 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 
SetTabBlocked(int index,bool blocked)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 
SetTabPinned(int index,bool pinned)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 
IsTabPinned(int index) const537 bool TabStripModel::IsTabPinned(int index) const {
538   DCHECK(ContainsIndex(index));
539   return contents_data_[index]->pinned;
540 }
541 
IsMiniTab(int index) const542 bool TabStripModel::IsMiniTab(int index) const {
543   return IsTabPinned(index) || IsAppTab(index);
544 }
545 
IsAppTab(int index) const546 bool TabStripModel::IsAppTab(int index) const {
547   TabContentsWrapper* contents = GetTabContentsAt(index);
548   return contents && contents->extension_tab_helper()->is_app();
549 }
550 
IsTabBlocked(int index) const551 bool TabStripModel::IsTabBlocked(int index) const {
552   return contents_data_[index]->blocked;
553 }
554 
IndexOfFirstNonMiniTab() const555 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 
ConstrainInsertionIndex(int index,bool mini_tab)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 
ExtendSelectionTo(int index)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 
ToggleSelectionAt(int index)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 
AddSelectionFromAnchorTo(int index)598 void TabStripModel::AddSelectionFromAnchorTo(int index) {
599   int old_active = active_index();
600   selection_model_.AddSelectionFromAnchorTo(index);
601   NotifySelectionChanged(old_active);
602 }
603 
IsTabSelected(int index) const604 bool TabStripModel::IsTabSelected(int index) const {
605   DCHECK(ContainsIndex(index));
606   return selection_model_.IsSelected(index);
607 }
608 
SetSelectionFromModel(const TabStripSelectionModel & source)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 
AddTabContents(TabContentsWrapper * contents,int index,PageTransition::Type transition,int add_types)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 
CloseSelectedTabs()685 void TabStripModel::CloseSelectedTabs() {
686   InternalCloseTabs(selection_model_.selected_indices(),
687                     CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);
688 }
689 
SelectNextTab()690 void TabStripModel::SelectNextTab() {
691   SelectRelativeTab(true);
692 }
693 
SelectPreviousTab()694 void TabStripModel::SelectPreviousTab() {
695   SelectRelativeTab(false);
696 }
697 
SelectLastTab()698 void TabStripModel::SelectLastTab() {
699   ActivateTabAt(count() - 1, true);
700 }
701 
MoveTabNext()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 
MoveTabPrevious()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.
IsContextMenuCommandEnabled(int context_index,ContextMenuCommand command_id) const715 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 
IsContextMenuCommandChecked(int context_index,ContextMenuCommand command_id) const779 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 
ExecuteContextMenuCommand(int context_index,ContextMenuCommand command_id)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 
GetIndicesClosedByCommand(int index,ContextMenuCommand id) const937 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 
WillContextMenuPin(int index)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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
ContextMenuCommandToBrowserCommand(int cmd_id,int * browser_cmd)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 
GetIndicesWithSameDomain(int index,std::vector<int> * indices)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 
GetIndicesWithSameOpener(int index,std::vector<int> * indices)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 
GetIndicesForCommand(int index) const1086 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 
IsNewTabAtEndOfTabStrip(TabContentsWrapper * contents) const1095 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 
InternalCloseTabs(const std::vector<int> & indices,uint32 close_types)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 
InternalCloseTab(TabContentsWrapper * contents,int index,bool create_historical_tabs)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 
GetContentsAt(int index) const1202 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 
NotifyTabSelectedIfChanged(TabContentsWrapper * old_contents,int to_index,bool user_gesture)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 
NotifySelectionChanged(int old_selected_index)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 
SelectRelativeTab(bool next)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 
MoveTabContentsAtImpl(int index,int to_position,bool select_after_move)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 
MoveSelectedTabsToImpl(int index,size_t start,size_t length)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
OpenerMatches(const TabContentsData * data,const NavigationController * opener,bool use_group)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 
ForgetOpenersAndGroupsReferencing(const NavigationController * tab)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