1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/tabs/tab_strip_model.h"
11 #include "apps/ui/web_contents_sizer.h"
12 #include "base/metrics/histogram.h"
13 #include "base/stl_util.h"
14 #include "chrome/app/chrome_command_ids.h"
15 #include "chrome/browser/browser_shutdown.h"
16 #include "chrome/browser/defaults.h"
17 #include "chrome/browser/extensions/tab_helper.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
20 #include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
21 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model_order_controller.h"
23 #include "chrome/common/url_constants.h"
24 #include "components/web_modal/web_contents_modal_dialog_manager.h"
25 #include "content/public/browser/render_process_host.h"
26 #include "content/public/browser/user_metrics.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/browser/web_contents_observer.h"
29 #include "content/public/browser/web_contents_view.h"
31 using base::UserMetricsAction;
32 using content::WebContents;
36 // Returns true if the specified transition is one of the types that cause the
37 // opener relationships for the tab in which the transition occurred to be
38 // forgotten. This is generally any navigation that isn't a link click (i.e.
39 // any navigation that can be considered to be the start of a new task distinct
40 // from what had previously occurred in that tab).
41 bool ShouldForgetOpenersForTransition(content::PageTransition transition) {
42 return transition == content::PAGE_TRANSITION_TYPED ||
43 transition == content::PAGE_TRANSITION_AUTO_BOOKMARK ||
44 transition == content::PAGE_TRANSITION_GENERATED ||
45 transition == content::PAGE_TRANSITION_KEYWORD ||
46 transition == content::PAGE_TRANSITION_AUTO_TOPLEVEL;
49 // CloseTracker is used when closing a set of WebContents. It listens for
50 // deletions of the WebContents and removes from the internal set any time one
54 typedef std::vector<WebContents*> Contents;
56 explicit CloseTracker(const Contents& contents);
57 virtual ~CloseTracker();
59 // Returns true if there is another WebContents in the Tracker.
62 // Returns the next WebContents, or NULL if there are no more.
66 class DeletionObserver : public content::WebContentsObserver {
68 DeletionObserver(CloseTracker* parent, WebContents* web_contents)
69 : WebContentsObserver(web_contents),
73 // Expose web_contents() publicly.
74 using content::WebContentsObserver::web_contents;
77 // WebContentsObserver:
78 virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE {
79 parent_->OnWebContentsDestroyed(this);
82 CloseTracker* parent_;
84 DISALLOW_COPY_AND_ASSIGN(DeletionObserver);
87 void OnWebContentsDestroyed(DeletionObserver* observer);
89 typedef std::vector<DeletionObserver*> Observers;
92 DISALLOW_COPY_AND_ASSIGN(CloseTracker);
95 CloseTracker::CloseTracker(const Contents& contents) {
96 for (size_t i = 0; i < contents.size(); ++i)
97 observers_.push_back(new DeletionObserver(this, contents[i]));
100 CloseTracker::~CloseTracker() {
101 DCHECK(observers_.empty());
104 bool CloseTracker::HasNext() const {
105 return !observers_.empty();
108 WebContents* CloseTracker::Next() {
109 if (observers_.empty())
112 DeletionObserver* observer = observers_[0];
113 WebContents* web_contents = observer->web_contents();
114 observers_.erase(observers_.begin());
119 void CloseTracker::OnWebContentsDestroyed(DeletionObserver* observer) {
120 Observers::iterator i =
121 std::find(observers_.begin(), observers_.end(), observer);
122 if (i != observers_.end()) {
127 NOTREACHED() << "WebContents destroyed that wasn't in the list";
132 ///////////////////////////////////////////////////////////////////////////////
135 // An object to hold a reference to a WebContents that is in a tabstrip, as
136 // well as other various properties it has.
137 class TabStripModel::WebContentsData : public content::WebContentsObserver {
139 WebContentsData(TabStripModel* tab_strip_model, WebContents* a_contents);
141 // Changes the WebContents that this WebContentsData tracks.
142 void SetWebContents(WebContents* contents);
143 WebContents* web_contents() { return contents_; }
145 // Create a relationship between this WebContentsData and other
146 // WebContentses. Used to identify which WebContents to select next after
148 WebContents* group() const { return group_; }
149 void set_group(WebContents* value) { group_ = value; }
150 WebContents* opener() const { return opener_; }
151 void set_opener(WebContents* value) { opener_ = value; }
153 // Alters the properties of the WebContents.
154 bool reset_group_on_select() const { return reset_group_on_select_; }
155 void set_reset_group_on_select(bool value) { reset_group_on_select_ = value; }
156 bool pinned() const { return pinned_; }
157 void set_pinned(bool value) { pinned_ = value; }
158 bool blocked() const { return blocked_; }
159 void set_blocked(bool value) { blocked_ = value; }
160 bool discarded() const { return discarded_; }
161 void set_discarded(bool value) { discarded_ = value; }
164 // Make sure that if someone deletes this WebContents out from under us, it
165 // is properly removed from the tab strip.
166 virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE;
168 // The WebContents being tracked by this WebContentsData. The
169 // WebContentsObserver does keep a reference, but when the WebContents is
170 // deleted, the WebContentsObserver reference is NULLed and thus inaccessible.
171 WebContents* contents_;
173 // The TabStripModel containing this WebContents.
174 TabStripModel* tab_strip_model_;
176 // The group is used to model a set of tabs spawned from a single parent
177 // tab. This value is preserved for a given tab as long as the tab remains
178 // navigated to the link it was initially opened at or some navigation from
179 // that page (i.e. if the user types or visits a bookmark or some other
180 // navigation within that tab, the group relationship is lost). This
181 // property can safely be used to implement features that depend on a
182 // logical group of related tabs.
185 // The owner models the same relationship as group, except it is more
186 // easily discarded, e.g. when the user switches to a tab not part of the
187 // same group. This property is used to determine what tab to select next
188 // when one is closed.
189 WebContents* opener_;
191 // True if our group should be reset the moment selection moves away from
192 // this tab. This is the case for tabs opened in the foreground at the end
193 // of the TabStrip while viewing another Tab. If these tabs are closed
194 // before selection moves elsewhere, their opener is selected. But if
195 // selection shifts to _any_ tab (including their opener), the group
196 // relationship is reset to avoid confusing close sequencing.
197 bool reset_group_on_select_;
199 // Is the tab pinned?
202 // Is the tab interaction blocked by a modal dialog?
205 // Has the tab data been discarded to save memory?
208 DISALLOW_COPY_AND_ASSIGN(WebContentsData);
211 TabStripModel::WebContentsData::WebContentsData(TabStripModel* tab_strip_model,
212 WebContents* contents)
213 : content::WebContentsObserver(contents),
215 tab_strip_model_(tab_strip_model),
218 reset_group_on_select_(false),
224 void TabStripModel::WebContentsData::SetWebContents(WebContents* contents) {
225 contents_ = contents;
229 void TabStripModel::WebContentsData::WebContentsDestroyed(
230 WebContents* web_contents) {
231 DCHECK_EQ(contents_, web_contents);
233 // Note that we only detach the contents here, not close it - it's
234 // already been closed. We just want to undo our bookkeeping.
235 int index = tab_strip_model_->GetIndexOfWebContents(web_contents);
236 DCHECK_NE(TabStripModel::kNoTab, index);
237 tab_strip_model_->DetachWebContentsAt(index);
240 ///////////////////////////////////////////////////////////////////////////////
241 // TabStripModel, public:
243 TabStripModel::TabStripModel(TabStripModelDelegate* delegate, Profile* profile)
244 : delegate_(delegate),
249 order_controller_.reset(new TabStripModelOrderController(this));
252 TabStripModel::~TabStripModel() {
253 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
254 TabStripModelDeleted());
255 STLDeleteElements(&contents_data_);
256 order_controller_.reset();
259 void TabStripModel::AddObserver(TabStripModelObserver* observer) {
260 observers_.AddObserver(observer);
263 void TabStripModel::RemoveObserver(TabStripModelObserver* observer) {
264 observers_.RemoveObserver(observer);
267 bool TabStripModel::ContainsIndex(int index) const {
268 return index >= 0 && index < count();
271 void TabStripModel::AppendWebContents(WebContents* contents,
273 InsertWebContentsAt(count(), contents,
274 foreground ? (ADD_INHERIT_GROUP | ADD_ACTIVE) :
278 void TabStripModel::InsertWebContentsAt(int index,
279 WebContents* contents,
281 delegate_->WillAddWebContents(contents);
283 bool active = add_types & ADD_ACTIVE;
284 // Force app tabs to be pinned.
285 extensions::TabHelper* extensions_tab_helper =
286 extensions::TabHelper::FromWebContents(contents);
287 bool pin = extensions_tab_helper->is_app() || add_types & ADD_PINNED;
288 index = ConstrainInsertionIndex(index, pin);
290 // In tab dragging situations, if the last tab in the window was detached
291 // then the user aborted the drag, we will have the |closing_all_| member
292 // set (see DetachWebContentsAt) which will mess with our mojo here. We need
293 // to clear this bit.
294 closing_all_ = false;
296 // Have to get the active contents before we monkey with the contents
297 // otherwise we run into problems when we try to change the active contents
298 // since the old contents and the new contents will be the same...
299 WebContents* active_contents = GetActiveWebContents();
300 WebContentsData* data = new WebContentsData(this, contents);
301 data->set_pinned(pin);
302 if ((add_types & ADD_INHERIT_GROUP) && active_contents) {
304 // Forget any existing relationships, we don't want to make things too
305 // confusing by having multiple groups active at the same time.
308 // Anything opened by a link we deem to have an opener.
309 data->set_group(active_contents);
310 data->set_opener(active_contents);
311 } else if ((add_types & ADD_INHERIT_OPENER) && active_contents) {
313 // Forget any existing relationships, we don't want to make things too
314 // confusing by having multiple groups active at the same time.
317 data->set_opener(active_contents);
320 web_modal::WebContentsModalDialogManager* modal_dialog_manager =
321 web_modal::WebContentsModalDialogManager::FromWebContents(contents);
322 if (modal_dialog_manager)
323 data->set_blocked(modal_dialog_manager->IsDialogActive());
325 contents_data_.insert(contents_data_.begin() + index, data);
327 selection_model_.IncrementFrom(index);
329 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
330 TabInsertedAt(contents, index, active));
332 ui::ListSelectionModel new_model;
333 new_model.Copy(selection_model_);
334 new_model.SetSelectedIndex(index);
335 SetSelection(new_model, NOTIFY_DEFAULT);
339 WebContents* TabStripModel::ReplaceWebContentsAt(int index,
340 WebContents* new_contents) {
341 delegate_->WillAddWebContents(new_contents);
343 DCHECK(ContainsIndex(index));
344 WebContents* old_contents = GetWebContentsAtImpl(index);
346 ForgetOpenersAndGroupsReferencing(old_contents);
348 contents_data_[index]->SetWebContents(new_contents);
350 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
351 TabReplacedAt(this, old_contents, new_contents, index));
353 // When the active WebContents is replaced send out a selection notification
354 // too. We do this as nearly all observers need to treat a replacement of the
355 // selected contents as the selection changing.
356 if (active_index() == index) {
358 TabStripModelObserver,
360 ActiveTabChanged(old_contents,
363 TabStripModelObserver::CHANGE_REASON_REPLACED));
368 WebContents* TabStripModel::DiscardWebContentsAt(int index) {
369 DCHECK(ContainsIndex(index));
370 // Do not discard active tab.
371 if (active_index() == index)
374 WebContents* null_contents =
375 WebContents::Create(WebContents::CreateParams(profile()));
376 WebContents* old_contents = GetWebContentsAtImpl(index);
377 // Copy over the state from the navigation controller so we preserve the
378 // back/forward history and continue to display the correct title/favicon.
379 null_contents->GetController().CopyStateFrom(old_contents->GetController());
380 // Replace the tab we're discarding with the null version.
381 ReplaceWebContentsAt(index, null_contents);
382 // Mark the tab so it will reload when we click.
383 contents_data_[index]->set_discarded(true);
384 // Discard the old tab's renderer.
385 // TODO(jamescook): This breaks script connections with other tabs.
386 // We need to find a different approach that doesn't do that, perhaps based
387 // on navigation to swappedout://.
389 return null_contents;
392 WebContents* TabStripModel::DetachWebContentsAt(int index) {
394 if (contents_data_.empty())
397 DCHECK(ContainsIndex(index));
399 WebContents* removed_contents = GetWebContentsAtImpl(index);
400 bool was_selected = IsTabSelected(index);
401 int next_selected_index = order_controller_->DetermineNewSelectedIndex(index);
402 delete contents_data_[index];
403 contents_data_.erase(contents_data_.begin() + index);
404 ForgetOpenersAndGroupsReferencing(removed_contents);
407 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
408 TabDetachedAt(removed_contents, index));
410 selection_model_.Clear();
411 // TabDetachedAt() might unregister observers, so send |TabStripEmpty()| in
413 FOR_EACH_OBSERVER(TabStripModelObserver, observers_, TabStripEmpty());
415 int old_active = active_index();
416 selection_model_.DecrementFrom(index);
417 ui::ListSelectionModel old_model;
418 old_model.Copy(selection_model_);
419 if (index == old_active) {
420 NotifyIfTabDeactivated(removed_contents);
421 if (!selection_model_.empty()) {
422 // The active tab was removed, but there is still something selected.
423 // Move the active and anchor to the first selected index.
424 selection_model_.set_active(selection_model_.selected_indices()[0]);
425 selection_model_.set_anchor(selection_model_.active());
427 // The active tab was removed and nothing is selected. Reset the
428 // selection and send out notification.
429 selection_model_.SetSelectedIndex(next_selected_index);
431 NotifyIfActiveTabChanged(removed_contents, NOTIFY_DEFAULT);
434 // Sending notification in case the detached tab was selected. Using
435 // NotifyIfActiveOrSelectionChanged() here would not guarantee that a
436 // notification is sent even though the tab selection has changed because
437 // |old_model| is stored after calling DecrementFrom().
439 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
440 TabSelectionChanged(this, old_model));
443 return removed_contents;
446 void TabStripModel::ActivateTabAt(int index, bool user_gesture) {
447 DCHECK(ContainsIndex(index));
448 ui::ListSelectionModel new_model;
449 new_model.Copy(selection_model_);
450 new_model.SetSelectedIndex(index);
451 SetSelection(new_model, user_gesture ? NOTIFY_USER_GESTURE : NOTIFY_DEFAULT);
454 void TabStripModel::AddTabAtToSelection(int index) {
455 DCHECK(ContainsIndex(index));
456 ui::ListSelectionModel new_model;
457 new_model.Copy(selection_model_);
458 new_model.AddIndexToSelection(index);
459 SetSelection(new_model, NOTIFY_DEFAULT);
462 void TabStripModel::MoveWebContentsAt(int index,
464 bool select_after_move) {
465 DCHECK(ContainsIndex(index));
466 if (index == to_position)
469 int first_non_mini_tab = IndexOfFirstNonMiniTab();
470 if ((index < first_non_mini_tab && to_position >= first_non_mini_tab) ||
471 (to_position < first_non_mini_tab && index >= first_non_mini_tab)) {
472 // This would result in mini tabs mixed with non-mini tabs. We don't allow
477 MoveWebContentsAtImpl(index, to_position, select_after_move);
480 void TabStripModel::MoveSelectedTabsTo(int index) {
481 int total_mini_count = IndexOfFirstNonMiniTab();
482 int selected_mini_count = 0;
484 static_cast<int>(selection_model_.selected_indices().size());
485 for (int i = 0; i < selected_count &&
486 IsMiniTab(selection_model_.selected_indices()[i]); ++i) {
487 selected_mini_count++;
490 // To maintain that all mini-tabs occur before non-mini-tabs we move them
492 if (selected_mini_count > 0) {
493 MoveSelectedTabsToImpl(
494 std::min(total_mini_count - selected_mini_count, index), 0u,
495 selected_mini_count);
496 if (index > total_mini_count - selected_mini_count) {
497 // We're being told to drag mini-tabs to an invalid location. Adjust the
498 // index such that non-mini-tabs end up at a location as though we could
499 // move the mini-tabs to index. See description in header for more
501 index += selected_mini_count;
504 if (selected_mini_count == selected_count)
507 // Then move the non-pinned tabs.
508 MoveSelectedTabsToImpl(std::max(index, total_mini_count),
510 selected_count - selected_mini_count);
513 WebContents* TabStripModel::GetActiveWebContents() const {
514 return GetWebContentsAt(active_index());
517 WebContents* TabStripModel::GetWebContentsAt(int index) const {
518 if (ContainsIndex(index))
519 return GetWebContentsAtImpl(index);
523 int TabStripModel::GetIndexOfWebContents(const WebContents* contents) const {
524 for (size_t i = 0; i < contents_data_.size(); ++i) {
525 if (contents_data_[i]->web_contents() == contents)
531 void TabStripModel::UpdateWebContentsStateAt(int index,
532 TabStripModelObserver::TabChangeType change_type) {
533 DCHECK(ContainsIndex(index));
535 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
536 TabChangedAt(GetWebContentsAtImpl(index), index, change_type));
539 void TabStripModel::CloseAllTabs() {
540 // Set state so that observers can adjust their behavior to suit this
541 // specific condition when CloseWebContentsAt causes a flurry of
542 // Close/Detach/Select notifications to be sent.
544 std::vector<int> closing_tabs;
545 for (int i = count() - 1; i >= 0; --i)
546 closing_tabs.push_back(i);
547 InternalCloseTabs(closing_tabs, CLOSE_CREATE_HISTORICAL_TAB);
550 bool TabStripModel::CloseWebContentsAt(int index, uint32 close_types) {
551 DCHECK(ContainsIndex(index));
552 std::vector<int> closing_tabs;
553 closing_tabs.push_back(index);
554 return InternalCloseTabs(closing_tabs, close_types);
557 bool TabStripModel::TabsAreLoading() const {
558 for (WebContentsDataVector::const_iterator iter = contents_data_.begin();
559 iter != contents_data_.end(); ++iter) {
560 if ((*iter)->web_contents()->IsLoading())
566 WebContents* TabStripModel::GetOpenerOfWebContentsAt(int index) {
567 DCHECK(ContainsIndex(index));
568 return contents_data_[index]->opener();
571 void TabStripModel::SetOpenerOfWebContentsAt(int index,
572 WebContents* opener) {
573 DCHECK(ContainsIndex(index));
575 contents_data_[index]->set_opener(opener);
578 int TabStripModel::GetIndexOfNextWebContentsOpenedBy(const WebContents* opener,
580 bool use_group) const {
582 DCHECK(ContainsIndex(start_index));
584 // Check tabs after start_index first.
585 for (int i = start_index + 1; i < count(); ++i) {
586 if (OpenerMatches(contents_data_[i], opener, use_group))
589 // Then check tabs before start_index, iterating backwards.
590 for (int i = start_index - 1; i >= 0; --i) {
591 if (OpenerMatches(contents_data_[i], opener, use_group))
597 int TabStripModel::GetIndexOfLastWebContentsOpenedBy(const WebContents* opener,
598 int start_index) const {
600 DCHECK(ContainsIndex(start_index));
602 for (int i = contents_data_.size() - 1; i > start_index; --i) {
603 if (contents_data_[i]->opener() == opener)
609 void TabStripModel::TabNavigating(WebContents* contents,
610 content::PageTransition transition) {
611 if (ShouldForgetOpenersForTransition(transition)) {
612 // Don't forget the openers if this tab is a New Tab page opened at the
613 // end of the TabStrip (e.g. by pressing Ctrl+T). Give the user one
614 // navigation of one of these transition types before resetting the
615 // opener relationships (this allows for the use case of opening a new
616 // tab to do a quick look-up of something while viewing a tab earlier in
617 // the strip). We can make this heuristic more permissive if need be.
618 if (!IsNewTabAtEndOfTabStrip(contents)) {
619 // If the user navigates the current tab to another page in any way
620 // other than by clicking a link, we want to pro-actively forget all
621 // TabStrip opener relationships since we assume they're beginning a
622 // different task by reusing the current tab.
624 // In this specific case we also want to reset the group relationship,
625 // since it is now technically invalid.
626 ForgetGroup(contents);
631 void TabStripModel::ForgetAllOpeners() {
632 // Forget all opener memories so we don't do anything weird with tab
633 // re-selection ordering.
634 for (WebContentsDataVector::const_iterator iter = contents_data_.begin();
635 iter != contents_data_.end(); ++iter)
636 (*iter)->set_opener(NULL);
639 void TabStripModel::ForgetGroup(WebContents* contents) {
640 int index = GetIndexOfWebContents(contents);
641 DCHECK(ContainsIndex(index));
642 contents_data_[index]->set_group(NULL);
643 contents_data_[index]->set_opener(NULL);
646 bool TabStripModel::ShouldResetGroupOnSelect(WebContents* contents) const {
647 int index = GetIndexOfWebContents(contents);
648 DCHECK(ContainsIndex(index));
649 return contents_data_[index]->reset_group_on_select();
652 void TabStripModel::SetTabBlocked(int index, bool blocked) {
653 DCHECK(ContainsIndex(index));
654 if (contents_data_[index]->blocked() == blocked)
656 contents_data_[index]->set_blocked(blocked);
658 TabStripModelObserver, observers_,
659 TabBlockedStateChanged(contents_data_[index]->web_contents(),
663 void TabStripModel::SetTabPinned(int index, bool pinned) {
664 DCHECK(ContainsIndex(index));
665 if (contents_data_[index]->pinned() == pinned)
668 if (IsAppTab(index)) {
670 // App tabs should always be pinned.
674 // Changing the pinned state of an app tab doesn't affect its mini-tab
676 contents_data_[index]->set_pinned(pinned);
678 // The tab is not an app tab, its position may have to change as the
679 // mini-tab state is changing.
680 int non_mini_tab_index = IndexOfFirstNonMiniTab();
681 contents_data_[index]->set_pinned(pinned);
682 if (pinned && index != non_mini_tab_index) {
683 MoveWebContentsAtImpl(index, non_mini_tab_index, false);
684 index = non_mini_tab_index;
685 } else if (!pinned && index + 1 != non_mini_tab_index) {
686 MoveWebContentsAtImpl(index, non_mini_tab_index - 1, false);
687 index = non_mini_tab_index - 1;
690 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
691 TabMiniStateChanged(contents_data_[index]->web_contents(),
695 // else: the tab was at the boundary and its position doesn't need to change.
696 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
697 TabPinnedStateChanged(contents_data_[index]->web_contents(),
701 bool TabStripModel::IsTabPinned(int index) const {
702 DCHECK(ContainsIndex(index));
703 return contents_data_[index]->pinned();
706 bool TabStripModel::IsMiniTab(int index) const {
707 return IsTabPinned(index) || IsAppTab(index);
710 bool TabStripModel::IsAppTab(int index) const {
711 WebContents* contents = GetWebContentsAt(index);
712 return contents && extensions::TabHelper::FromWebContents(contents)->is_app();
715 bool TabStripModel::IsTabBlocked(int index) const {
716 return contents_data_[index]->blocked();
719 bool TabStripModel::IsTabDiscarded(int index) const {
720 return contents_data_[index]->discarded();
723 int TabStripModel::IndexOfFirstNonMiniTab() const {
724 for (size_t i = 0; i < contents_data_.size(); ++i) {
725 if (!IsMiniTab(static_cast<int>(i)))
726 return static_cast<int>(i);
732 int TabStripModel::ConstrainInsertionIndex(int index, bool mini_tab) {
733 return mini_tab ? std::min(std::max(0, index), IndexOfFirstNonMiniTab()) :
734 std::min(count(), std::max(index, IndexOfFirstNonMiniTab()));
737 void TabStripModel::ExtendSelectionTo(int index) {
738 DCHECK(ContainsIndex(index));
739 ui::ListSelectionModel new_model;
740 new_model.Copy(selection_model_);
741 new_model.SetSelectionFromAnchorTo(index);
742 SetSelection(new_model, NOTIFY_DEFAULT);
745 void TabStripModel::ToggleSelectionAt(int index) {
746 DCHECK(ContainsIndex(index));
747 ui::ListSelectionModel new_model;
748 new_model.Copy(selection_model());
749 if (selection_model_.IsSelected(index)) {
750 if (selection_model_.size() == 1) {
751 // One tab must be selected and this tab is currently selected so we can't
755 new_model.RemoveIndexFromSelection(index);
756 new_model.set_anchor(index);
757 if (new_model.active() == index ||
758 new_model.active() == ui::ListSelectionModel::kUnselectedIndex)
759 new_model.set_active(new_model.selected_indices()[0]);
761 new_model.AddIndexToSelection(index);
762 new_model.set_anchor(index);
763 new_model.set_active(index);
765 SetSelection(new_model, NOTIFY_DEFAULT);
768 void TabStripModel::AddSelectionFromAnchorTo(int index) {
769 ui::ListSelectionModel new_model;
770 new_model.Copy(selection_model_);
771 new_model.AddSelectionFromAnchorTo(index);
772 SetSelection(new_model, NOTIFY_DEFAULT);
775 bool TabStripModel::IsTabSelected(int index) const {
776 DCHECK(ContainsIndex(index));
777 return selection_model_.IsSelected(index);
780 void TabStripModel::SetSelectionFromModel(
781 const ui::ListSelectionModel& source) {
782 DCHECK_NE(ui::ListSelectionModel::kUnselectedIndex, source.active());
783 SetSelection(source, NOTIFY_DEFAULT);
786 void TabStripModel::AddWebContents(WebContents* contents,
788 content::PageTransition transition,
790 // If the newly-opened tab is part of the same task as the parent tab, we want
791 // to inherit the parent's "group" attribute, so that if this tab is then
792 // closed we'll jump back to the parent tab.
793 bool inherit_group = (add_types & ADD_INHERIT_GROUP) == ADD_INHERIT_GROUP;
795 if (transition == content::PAGE_TRANSITION_LINK &&
796 (add_types & ADD_FORCE_INDEX) == 0) {
797 // We assume tabs opened via link clicks are part of the same task as their
798 // parent. Note that when |force_index| is true (e.g. when the user
799 // drag-and-drops a link to the tab strip), callers aren't really handling
800 // link clicks, they just want to score the navigation like a link click in
801 // the history backend, so we don't inherit the group in this case.
802 index = order_controller_->DetermineInsertionIndex(transition,
803 add_types & ADD_ACTIVE);
804 inherit_group = true;
806 // For all other types, respect what was passed to us, normalizing -1s and
807 // values that are too large.
808 if (index < 0 || index > count())
812 if (transition == content::PAGE_TRANSITION_TYPED && index == count()) {
813 // Also, any tab opened at the end of the TabStrip with a "TYPED"
814 // transition inherit group as well. This covers the cases where the user
815 // creates a New Tab (e.g. Ctrl+T, or clicks the New Tab button), or types
816 // in the address bar and presses Alt+Enter. This allows for opening a new
817 // Tab to quickly look up something. When this Tab is closed, the old one
818 // is re-selected, not the next-adjacent.
819 inherit_group = true;
821 InsertWebContentsAt(index, contents,
822 add_types | (inherit_group ? ADD_INHERIT_GROUP : 0));
823 // Reset the index, just in case insert ended up moving it on us.
824 index = GetIndexOfWebContents(contents);
826 if (inherit_group && transition == content::PAGE_TRANSITION_TYPED)
827 contents_data_[index]->set_reset_group_on_select(true);
829 // TODO(sky): figure out why this is here and not in InsertWebContentsAt. When
830 // here we seem to get failures in startup perf tests.
831 // Ensure that the new WebContentsView begins at the same size as the
832 // previous WebContentsView if it existed. Otherwise, the initial WebKit
833 // layout will be performed based on a width of 0 pixels, causing a
834 // very long, narrow, inaccurate layout. Because some scripts on pages (as
835 // well as WebKit's anchor link location calculation) are run on the
836 // initial layout and not recalculated later, we need to ensure the first
837 // layout is performed with sane view dimensions even when we're opening a
838 // new background tab.
839 if (WebContents* old_contents = GetActiveWebContents()) {
840 if ((add_types & ADD_ACTIVE) == 0) {
841 apps::ResizeWebContents(contents,
842 old_contents->GetView()->GetContainerSize());
847 void TabStripModel::CloseSelectedTabs() {
848 InternalCloseTabs(selection_model_.selected_indices(),
849 CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);
852 void TabStripModel::SelectNextTab() {
853 SelectRelativeTab(true);
856 void TabStripModel::SelectPreviousTab() {
857 SelectRelativeTab(false);
860 void TabStripModel::SelectLastTab() {
861 ActivateTabAt(count() - 1, true);
864 void TabStripModel::MoveTabNext() {
865 // TODO: this likely needs to be updated for multi-selection.
866 int new_index = std::min(active_index() + 1, count() - 1);
867 MoveWebContentsAt(active_index(), new_index, true);
870 void TabStripModel::MoveTabPrevious() {
871 // TODO: this likely needs to be updated for multi-selection.
872 int new_index = std::max(active_index() - 1, 0);
873 MoveWebContentsAt(active_index(), new_index, true);
876 // Context menu functions.
877 bool TabStripModel::IsContextMenuCommandEnabled(
878 int context_index, ContextMenuCommand command_id) const {
879 DCHECK(command_id > CommandFirst && command_id < CommandLast);
880 switch (command_id) {
882 case CommandCloseTab:
885 case CommandReload: {
886 std::vector<int> indices = GetIndicesForCommand(context_index);
887 for (size_t i = 0; i < indices.size(); ++i) {
888 WebContents* tab = GetWebContentsAt(indices[i]);
890 CoreTabHelperDelegate* core_delegate =
891 CoreTabHelper::FromWebContents(tab)->delegate();
892 if (!core_delegate || core_delegate->CanReloadContents(tab))
899 case CommandCloseOtherTabs:
900 case CommandCloseTabsToRight:
901 return !GetIndicesClosedByCommand(context_index, command_id).empty();
903 case CommandDuplicate: {
904 std::vector<int> indices = GetIndicesForCommand(context_index);
905 for (size_t i = 0; i < indices.size(); ++i) {
906 if (delegate_->CanDuplicateContentsAt(indices[i]))
912 case CommandRestoreTab:
913 return delegate_->GetRestoreTabType() !=
914 TabStripModelDelegate::RESTORE_NONE;
916 case CommandTogglePinned: {
917 std::vector<int> indices = GetIndicesForCommand(context_index);
918 for (size_t i = 0; i < indices.size(); ++i) {
919 if (!IsAppTab(indices[i]))
925 case CommandBookmarkAllTabs:
926 return browser_defaults::bookmarks_enabled &&
927 delegate_->CanBookmarkAllTabs();
929 case CommandSelectByDomain:
930 case CommandSelectByOpener:
939 void TabStripModel::ExecuteContextMenuCommand(
940 int context_index, ContextMenuCommand command_id) {
941 DCHECK(command_id > CommandFirst && command_id < CommandLast);
942 switch (command_id) {
944 content::RecordAction(UserMetricsAction("TabContextMenu_NewTab"));
945 UMA_HISTOGRAM_ENUMERATION("Tab.NewTab",
946 TabStripModel::NEW_TAB_CONTEXT_MENU,
947 TabStripModel::NEW_TAB_ENUM_COUNT);
948 delegate()->AddTabAt(GURL(), context_index + 1, true);
951 case CommandReload: {
952 content::RecordAction(UserMetricsAction("TabContextMenu_Reload"));
953 std::vector<int> indices = GetIndicesForCommand(context_index);
954 for (size_t i = 0; i < indices.size(); ++i) {
955 WebContents* tab = GetWebContentsAt(indices[i]);
957 CoreTabHelperDelegate* core_delegate =
958 CoreTabHelper::FromWebContents(tab)->delegate();
959 if (!core_delegate || core_delegate->CanReloadContents(tab))
960 tab->GetController().Reload(true);
966 case CommandDuplicate: {
967 content::RecordAction(UserMetricsAction("TabContextMenu_Duplicate"));
968 std::vector<int> indices = GetIndicesForCommand(context_index);
969 // Copy the WebContents off as the indices will change as tabs are
971 std::vector<WebContents*> tabs;
972 for (size_t i = 0; i < indices.size(); ++i)
973 tabs.push_back(GetWebContentsAt(indices[i]));
974 for (size_t i = 0; i < tabs.size(); ++i) {
975 int index = GetIndexOfWebContents(tabs[i]);
976 if (index != -1 && delegate_->CanDuplicateContentsAt(index))
977 delegate_->DuplicateContentsAt(index);
982 case CommandCloseTab: {
983 content::RecordAction(UserMetricsAction("TabContextMenu_CloseTab"));
984 InternalCloseTabs(GetIndicesForCommand(context_index),
985 CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);
989 case CommandCloseOtherTabs: {
990 content::RecordAction(
991 UserMetricsAction("TabContextMenu_CloseOtherTabs"));
992 InternalCloseTabs(GetIndicesClosedByCommand(context_index, command_id),
993 CLOSE_CREATE_HISTORICAL_TAB);
997 case CommandCloseTabsToRight: {
998 content::RecordAction(
999 UserMetricsAction("TabContextMenu_CloseTabsToRight"));
1000 InternalCloseTabs(GetIndicesClosedByCommand(context_index, command_id),
1001 CLOSE_CREATE_HISTORICAL_TAB);
1005 case CommandRestoreTab: {
1006 content::RecordAction(UserMetricsAction("TabContextMenu_RestoreTab"));
1007 delegate_->RestoreTab();
1011 case CommandTogglePinned: {
1012 content::RecordAction(
1013 UserMetricsAction("TabContextMenu_TogglePinned"));
1014 std::vector<int> indices = GetIndicesForCommand(context_index);
1015 bool pin = WillContextMenuPin(context_index);
1017 for (size_t i = 0; i < indices.size(); ++i) {
1018 if (!IsAppTab(indices[i]))
1019 SetTabPinned(indices[i], true);
1022 // Unpin from the back so that the order is maintained (unpinning can
1023 // trigger moving a tab).
1024 for (size_t i = indices.size(); i > 0; --i) {
1025 if (!IsAppTab(indices[i - 1]))
1026 SetTabPinned(indices[i - 1], false);
1032 case CommandBookmarkAllTabs: {
1033 content::RecordAction(
1034 UserMetricsAction("TabContextMenu_BookmarkAllTabs"));
1036 delegate_->BookmarkAllTabs();
1040 case CommandSelectByDomain:
1041 case CommandSelectByOpener: {
1042 std::vector<int> indices;
1043 if (command_id == CommandSelectByDomain)
1044 GetIndicesWithSameDomain(context_index, &indices);
1046 GetIndicesWithSameOpener(context_index, &indices);
1047 ui::ListSelectionModel selection_model;
1048 selection_model.SetSelectedIndex(context_index);
1049 for (size_t i = 0; i < indices.size(); ++i)
1050 selection_model.AddIndexToSelection(indices[i]);
1051 SetSelectionFromModel(selection_model);
1060 std::vector<int> TabStripModel::GetIndicesClosedByCommand(
1062 ContextMenuCommand id) const {
1063 DCHECK(ContainsIndex(index));
1064 DCHECK(id == CommandCloseTabsToRight || id == CommandCloseOtherTabs);
1065 bool is_selected = IsTabSelected(index);
1067 if (id == CommandCloseTabsToRight) {
1069 start = selection_model_.selected_indices()[
1070 selection_model_.selected_indices().size() - 1] + 1;
1077 // NOTE: callers expect the vector to be sorted in descending order.
1078 std::vector<int> indices;
1079 for (int i = count() - 1; i >= start; --i) {
1080 if (i != index && !IsMiniTab(i) && (!is_selected || !IsTabSelected(i)))
1081 indices.push_back(i);
1086 bool TabStripModel::WillContextMenuPin(int index) {
1087 std::vector<int> indices = GetIndicesForCommand(index);
1088 // If all tabs are pinned, then we unpin, otherwise we pin.
1089 bool all_pinned = true;
1090 for (size_t i = 0; i < indices.size() && all_pinned; ++i) {
1091 if (!IsAppTab(index)) // We never change app tabs.
1092 all_pinned = IsTabPinned(indices[i]);
1098 bool TabStripModel::ContextMenuCommandToBrowserCommand(int cmd_id,
1102 *browser_cmd = IDC_NEW_TAB;
1105 *browser_cmd = IDC_RELOAD;
1107 case CommandDuplicate:
1108 *browser_cmd = IDC_DUPLICATE_TAB;
1110 case CommandCloseTab:
1111 *browser_cmd = IDC_CLOSE_TAB;
1113 case CommandRestoreTab:
1114 *browser_cmd = IDC_RESTORE_TAB;
1116 case CommandBookmarkAllTabs:
1117 *browser_cmd = IDC_BOOKMARK_ALL_TABS;
1127 ///////////////////////////////////////////////////////////////////////////////
1128 // TabStripModel, private:
1130 std::vector<WebContents*> TabStripModel::GetWebContentsFromIndices(
1131 const std::vector<int>& indices) const {
1132 std::vector<WebContents*> contents;
1133 for (size_t i = 0; i < indices.size(); ++i)
1134 contents.push_back(GetWebContentsAtImpl(indices[i]));
1138 void TabStripModel::GetIndicesWithSameDomain(int index,
1139 std::vector<int>* indices) {
1140 std::string domain = GetWebContentsAt(index)->GetURL().host();
1143 for (int i = 0; i < count(); ++i) {
1146 if (GetWebContentsAt(i)->GetURL().host() == domain)
1147 indices->push_back(i);
1151 void TabStripModel::GetIndicesWithSameOpener(int index,
1152 std::vector<int>* indices) {
1153 WebContents* opener = contents_data_[index]->group();
1155 // If there is no group, find all tabs with the selected tab as the opener.
1156 opener = GetWebContentsAt(index);
1160 for (int i = 0; i < count(); ++i) {
1163 if (contents_data_[i]->group() == opener ||
1164 GetWebContentsAtImpl(i) == opener) {
1165 indices->push_back(i);
1170 std::vector<int> TabStripModel::GetIndicesForCommand(int index) const {
1171 if (!IsTabSelected(index)) {
1172 std::vector<int> indices;
1173 indices.push_back(index);
1176 return selection_model_.selected_indices();
1179 bool TabStripModel::IsNewTabAtEndOfTabStrip(WebContents* contents) const {
1180 const GURL& url = contents->GetURL();
1181 return url.SchemeIs(content::kChromeUIScheme) &&
1182 url.host() == chrome::kChromeUINewTabHost &&
1183 contents == GetWebContentsAtImpl(count() - 1) &&
1184 contents->GetController().GetEntryCount() == 1;
1187 bool TabStripModel::InternalCloseTabs(const std::vector<int>& indices,
1188 uint32 close_types) {
1189 if (indices.empty())
1192 CloseTracker close_tracker(GetWebContentsFromIndices(indices));
1194 // We only try the fast shutdown path if the whole browser process is *not*
1195 // shutting down. Fast shutdown during browser termination is handled in
1197 if (browser_shutdown::GetShutdownType() == browser_shutdown::NOT_VALID) {
1198 // Construct a map of processes to the number of associated tabs that are
1200 std::map<content::RenderProcessHost*, size_t> processes;
1201 for (size_t i = 0; i < indices.size(); ++i) {
1202 WebContents* closing_contents = GetWebContentsAtImpl(indices[i]);
1203 if (delegate_->ShouldRunUnloadListenerBeforeClosing(closing_contents))
1205 content::RenderProcessHost* process =
1206 closing_contents->GetRenderProcessHost();
1207 ++processes[process];
1210 // Try to fast shutdown the tabs that can close.
1211 for (std::map<content::RenderProcessHost*, size_t>::iterator iter =
1212 processes.begin(); iter != processes.end(); ++iter) {
1213 iter->first->FastShutdownForPageCount(iter->second);
1217 // We now return to our regularly scheduled shutdown procedure.
1219 while (close_tracker.HasNext()) {
1220 WebContents* closing_contents = close_tracker.Next();
1221 int index = GetIndexOfWebContents(closing_contents);
1222 // Make sure we still contain the tab.
1223 if (index == kNoTab)
1226 CoreTabHelper* core_tab_helper =
1227 CoreTabHelper::FromWebContents(closing_contents);
1228 core_tab_helper->OnCloseStarted();
1230 // Update the explicitly closed state. If the unload handlers cancel the
1231 // close the state is reset in Browser. We don't update the explicitly
1232 // closed state if already marked as explicitly closed as unload handlers
1233 // call back to this if the close is allowed.
1234 if (!closing_contents->GetClosedByUserGesture()) {
1235 closing_contents->SetClosedByUserGesture(
1236 close_types & CLOSE_USER_GESTURE);
1239 if (delegate_->RunUnloadListenerBeforeClosing(closing_contents)) {
1244 InternalCloseTab(closing_contents, index,
1245 (close_types & CLOSE_CREATE_HISTORICAL_TAB) != 0);
1251 void TabStripModel::InternalCloseTab(WebContents* contents,
1253 bool create_historical_tabs) {
1254 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1255 TabClosingAt(this, contents, index));
1257 // Ask the delegate to save an entry for this tab in the historical tab
1258 // database if applicable.
1259 if (create_historical_tabs)
1260 delegate_->CreateHistoricalTab(contents);
1262 // Deleting the WebContents will call back to us via
1263 // WebContentsData::WebContentsDestroyed and detach it.
1267 WebContents* TabStripModel::GetWebContentsAtImpl(int index) const {
1268 CHECK(ContainsIndex(index)) <<
1269 "Failed to find: " << index << " in: " << count() << " entries.";
1270 return contents_data_[index]->web_contents();
1273 void TabStripModel::NotifyIfTabDeactivated(WebContents* contents) {
1275 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1276 TabDeactivated(contents));
1280 void TabStripModel::NotifyIfActiveTabChanged(WebContents* old_contents,
1281 NotifyTypes notify_types) {
1282 WebContents* new_contents = GetWebContentsAtImpl(active_index());
1283 if (old_contents != new_contents) {
1284 int reason = notify_types == NOTIFY_USER_GESTURE
1285 ? TabStripModelObserver::CHANGE_REASON_USER_GESTURE
1286 : TabStripModelObserver::CHANGE_REASON_NONE;
1289 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1290 ActiveTabChanged(old_contents,
1295 // Activating a discarded tab reloads it, so it is no longer discarded.
1296 contents_data_[active_index()]->set_discarded(false);
1300 void TabStripModel::NotifyIfActiveOrSelectionChanged(
1301 WebContents* old_contents,
1302 NotifyTypes notify_types,
1303 const ui::ListSelectionModel& old_model) {
1304 NotifyIfActiveTabChanged(old_contents, notify_types);
1306 if (!selection_model().Equals(old_model)) {
1307 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1308 TabSelectionChanged(this, old_model));
1312 void TabStripModel::SetSelection(
1313 const ui::ListSelectionModel& new_model,
1314 NotifyTypes notify_types) {
1315 WebContents* old_contents = GetActiveWebContents();
1316 ui::ListSelectionModel old_model;
1317 old_model.Copy(selection_model_);
1318 if (new_model.active() != selection_model_.active())
1319 NotifyIfTabDeactivated(old_contents);
1320 selection_model_.Copy(new_model);
1321 NotifyIfActiveOrSelectionChanged(old_contents, notify_types, old_model);
1324 void TabStripModel::SelectRelativeTab(bool next) {
1325 // This may happen during automated testing or if a user somehow buffers
1326 // many key accelerators.
1327 if (contents_data_.empty())
1330 int index = active_index();
1331 int delta = next ? 1 : -1;
1332 index = (index + count() + delta) % count();
1333 ActivateTabAt(index, true);
1336 void TabStripModel::MoveWebContentsAtImpl(int index,
1338 bool select_after_move) {
1339 WebContentsData* moved_data = contents_data_[index];
1340 contents_data_.erase(contents_data_.begin() + index);
1341 contents_data_.insert(contents_data_.begin() + to_position, moved_data);
1343 selection_model_.Move(index, to_position);
1344 if (!selection_model_.IsSelected(select_after_move) && select_after_move) {
1345 // TODO(sky): why doesn't this code notify observers?
1346 selection_model_.SetSelectedIndex(to_position);
1349 ForgetOpenersAndGroupsReferencing(moved_data->web_contents());
1351 FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1352 TabMoved(moved_data->web_contents(), index, to_position));
1355 void TabStripModel::MoveSelectedTabsToImpl(int index,
1358 DCHECK(start < selection_model_.selected_indices().size() &&
1359 start + length <= selection_model_.selected_indices().size());
1360 size_t end = start + length;
1361 int count_before_index = 0;
1362 for (size_t i = start; i < end &&
1363 selection_model_.selected_indices()[i] < index + count_before_index;
1365 count_before_index++;
1368 // First move those before index. Any tabs before index end up moving in the
1369 // selection model so we use start each time through.
1370 int target_index = index + count_before_index;
1371 size_t tab_index = start;
1372 while (tab_index < end &&
1373 selection_model_.selected_indices()[start] < index) {
1374 MoveWebContentsAt(selection_model_.selected_indices()[start],
1375 target_index - 1, false);
1379 // Then move those after the index. These don't result in reordering the
1381 while (tab_index < end) {
1382 if (selection_model_.selected_indices()[tab_index] != target_index) {
1383 MoveWebContentsAt(selection_model_.selected_indices()[tab_index],
1384 target_index, false);
1392 bool TabStripModel::OpenerMatches(const WebContentsData* data,
1393 const WebContents* opener,
1395 return data->opener() == opener || (use_group && data->group() == opener);
1398 void TabStripModel::ForgetOpenersAndGroupsReferencing(
1399 const WebContents* tab) {
1400 for (WebContentsDataVector::const_iterator i = contents_data_.begin();
1401 i != contents_data_.end(); ++i) {
1402 if ((*i)->group() == tab)
1403 (*i)->set_group(NULL);
1404 if ((*i)->opener() == tab)
1405 (*i)->set_opener(NULL);