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