diff options
Diffstat (limited to 'chrome/browser/tabs')
-rw-r--r-- | chrome/browser/tabs/tab_strip_model.cc | 120 | ||||
-rw-r--r-- | chrome/browser/tabs/tab_strip_model.h | 89 | ||||
-rw-r--r-- | chrome/browser/tabs/tab_strip_model_order_controller.cc | 6 | ||||
-rw-r--r-- | chrome/browser/tabs/tab_strip_model_unittest.cc | 256 |
4 files changed, 429 insertions, 42 deletions
diff --git a/chrome/browser/tabs/tab_strip_model.cc b/chrome/browser/tabs/tab_strip_model.cc index 318fb64..5f53a44 100644 --- a/chrome/browser/tabs/tab_strip_model.cc +++ b/chrome/browser/tabs/tab_strip_model.cc @@ -6,6 +6,7 @@ #include <algorithm> +#include "base/command_line.h" #include "base/stl_util-inl.h" #include "base/string_util.h" #include "build/build_config.h" @@ -15,6 +16,7 @@ #include "chrome/browser/tabs/tab_strip_model_order_controller.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/chrome_switches.h" #include "chrome/common/notification_service.h" #include "chrome/common/url_constants.h" @@ -89,6 +91,7 @@ void TabStripModel::InsertTabContentsAt(int index, // since the old contents and the new contents will be the same... TabContents* selected_contents = GetSelectedTabContents(); TabContentsData* data = new TabContentsData(contents); + data->pinned = (index != count() && index < IndexOfFirstNonPinnedTab()); if (inherit_group && selected_contents) { if (foreground) { // Forget any existing relationships, we don't want to make things too @@ -159,25 +162,7 @@ void TabStripModel::SelectTabContentsAt(int index, bool user_gesture) { void TabStripModel::MoveTabContentsAt(int index, int to_position, bool select_after_move) { - DCHECK(ContainsIndex(index)); - if (index == to_position) - return; - - TabContentsData* moved_data = contents_data_.at(index); - contents_data_.erase(contents_data_.begin() + index); - contents_data_.insert(contents_data_.begin() + to_position, moved_data); - - // if !select_after_move, keep the same tab selected as was selected before. - if (select_after_move || index == selected_index_) { - selected_index_ = to_position; - } else if (index < selected_index_ && to_position >= selected_index_) { - selected_index_--; - } else if (index > selected_index_ && to_position <= selected_index_) { - selected_index_++; - } - - FOR_EACH_OBSERVER(TabStripModelObserver, observers_, - TabMoved(moved_data->contents, index, to_position)); + MoveTabContentsAtImpl(index, to_position, select_after_move, true); } TabContents* TabStripModel::GetSelectedTabContents() const { @@ -331,6 +316,57 @@ bool TabStripModel::ShouldResetGroupOnSelect(TabContents* contents) const { return contents_data_.at(index)->reset_group_on_select; } +void TabStripModel::SetTabPinned(int index, bool pinned) { + DCHECK(ContainsIndex(index)); + if (contents_data_[index]->pinned == pinned) + return; + + int first_non_pinned_tab = IndexOfFirstNonPinnedTab(); + + contents_data_[index]->pinned = pinned; + + if (pinned && index > first_non_pinned_tab) { + // The tab is being pinned but beyond the pinned tabs. Move it to the end + // of the pinned tabs. + MoveTabContentsAtImpl(index, first_non_pinned_tab, + selected_index() == index, false); + } else if (!pinned && index < first_non_pinned_tab - 1) { + // The tab is being unpinned, but is within the pinned tabs, move it to + // be after the set of pinned tabs. + MoveTabContentsAtImpl(index, first_non_pinned_tab - 1, + selected_index() == index, false); + } else { + // Tab didn't move, but it's pinned state changed. Notify observers. + FOR_EACH_OBSERVER(TabStripModelObserver, observers_, + TabPinnedStateChanged(contents_data_[index]->contents, + index)); + } +} + +bool TabStripModel::IsTabPinned(int index) const { + return contents_data_[index]->pinned; +} + +int TabStripModel::IndexOfFirstNonPinnedTab() const { + for (size_t i = 0; i < contents_data_.size(); ++i) { + if (!contents_data_[i]->pinned) + return static_cast<int>(i); + } + // No pinned tabs. + return count(); +} + +// static +bool TabStripModel::IsTabPinningEnabled() { + static bool checked = false; + static bool enabled = false; + if (!checked) { + enabled = CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableTabPinning); + } + return enabled; +} + void TabStripModel::AddTabContents(TabContents* contents, int index, bool force_index, @@ -419,6 +455,8 @@ bool TabStripModel::IsContextMenuCommandEnabled( return delegate_->CanDuplicateContentsAt(context_index); case CommandRestoreTab: return delegate_->CanRestoreTab(); + case CommandTogglePinned: + return true; default: NOTREACHED(); } @@ -477,6 +515,13 @@ void TabStripModel::ExecuteContextMenuCommand( delegate_->RestoreTab(); break; } + case CommandTogglePinned: { + UserMetrics::RecordAction(L"TabContextMenu_TogglePinned", profile_); + + SelectTabContentsAt(context_index, true); + SetTabPinned(context_index, !IsTabPinned(context_index)); + break; + } default: NOTREACHED(); } @@ -550,6 +595,43 @@ bool TabStripModel::InternalCloseTabContentsAt(int index, return true; } +void TabStripModel::MoveTabContentsAtImpl(int index, int to_position, + bool select_after_move, + bool update_pinned_state) { + DCHECK(ContainsIndex(index)); + if (index == to_position) + return; + + bool pinned_state_changed = !update_pinned_state; + + if (update_pinned_state) { + bool is_pinned = IsTabPinned(index); + if (is_pinned && to_position >= IndexOfFirstNonPinnedTab()) { + contents_data_[index]->pinned = false; + } else if (!is_pinned && to_position < IndexOfFirstNonPinnedTab()) { + contents_data_[index]->pinned = true; + } + pinned_state_changed = is_pinned != contents_data_[index]->pinned; + } + + TabContentsData* moved_data = contents_data_.at(index); + contents_data_.erase(contents_data_.begin() + index); + contents_data_.insert(contents_data_.begin() + to_position, moved_data); + + // if !select_after_move, keep the same tab selected as was selected before. + if (select_after_move || index == selected_index_) { + selected_index_ = to_position; + } else if (index < selected_index_ && to_position >= selected_index_) { + selected_index_--; + } else if (index > selected_index_ && to_position <= selected_index_) { + selected_index_++; + } + + FOR_EACH_OBSERVER(TabStripModelObserver, observers_, + TabMoved(moved_data->contents, index, to_position, + pinned_state_changed)); +} + TabContents* TabStripModel::GetContentsAt(int index) const { CHECK(ContainsIndex(index)) << "Failed to find: " << index << " in: " << count() << " entries."; diff --git a/chrome/browser/tabs/tab_strip_model.h b/chrome/browser/tabs/tab_strip_model.h index fdbe77e..53ff04f 100644 --- a/chrome/browser/tabs/tab_strip_model.h +++ b/chrome/browser/tabs/tab_strip_model.h @@ -73,10 +73,20 @@ class TabStripModelObserver { bool user_gesture) { } // The specified TabContents at |from_index| was moved to |to_index|. + // TODO(sky): nuke and convert all to 4 arg variant. virtual void TabMoved(TabContents* contents, int from_index, int to_index) { } + // The specified TabContents at |from_index| was moved to |to_index|. If + // the pinned state of the tab is changing |pinned_state_changed| is true. + virtual void TabMoved(TabContents* contents, + int from_index, + int to_index, + bool pinned_state_changed) { + TabMoved(contents, from_index, to_index); + } + // The specified TabContents at |index| changed in some way. |contents| may // be an entirely different object and the old value is no longer available // by the time this message is delivered. @@ -92,6 +102,12 @@ class TabStripModelObserver { virtual void TabChangedAt(TabContents* contents, int index, bool loading_only) { } + // Invoked when the pinned state of a tab changes. + // NOTE: this is only invoked if the tab doesn't move as a result of its + // pinned state changing. If the tab moves as a result, the observer is + // notified by way of the TabMoved method with |pinned_state_changed| true. + virtual void TabPinnedStateChanged(TabContents* contents, int index) { } + // The TabStripModel now no longer has any "significant" (user created or // user manipulated) tabs. The implementer may use this as a trigger to try // and close the window containing the TabStripModel, for example... @@ -202,6 +218,16 @@ class TabStripModelDelegate { // them, as well as a higher level API for doing specific Browser-related // tasks like adding new Tabs from just a URL, etc. // +// Each tab may additionally be pinned. The view typically renders pinned tabs +// differently. The model makes sure all pinned tabs are organized at the +// beginning of the tabstrip. Inserting a tab between pinned tabs +// implicitly makes the inserted tab pinned. Similarly moving a tab may pin or +// unpin the tab, again enforcing that all pinned tabs occur at the beginning +// of the tabstrip. Lastly, changing the pinned state of a tab moves the +// tab to be grouped with the pinned or unpinned tabs. For example, if the +// first two tabs are pinned, and the tenth tab is pinned, it is moved to +// become the third tab. +// // A TabStripModel has one delegate that it relies on to perform certain tasks // like creating new TabStripModels (probably hosted in Browser windows) when // required. See TabStripDelegate above for more information. @@ -265,7 +291,8 @@ class TabStripModel : public NotificationObserver { // Adds the specified TabContents in the specified location. If // |inherit_group| is true, the new contents is linked to the current tab's - // group. + // group. If there are pinned tabs at or before |index|, then the newly + // inserted tab is pinned. void InsertTabContentsAt(int index, TabContents* contents, bool foreground, @@ -309,6 +336,7 @@ class TabStripModel : public NotificationObserver { // If |select_after_move| is false, whatever tab was selected before the move // will still be selected, but it's index may have incremented or decremented // one slot. + // See class description for how pinning is effected by this. void MoveTabContentsAt(int index, int to_position, bool select_after_move); // Returns the currently selected TabContents, or NULL if there is none. @@ -384,6 +412,20 @@ class TabStripModel : public NotificationObserver { // should be reset when _any_ selection change occurs in the model. bool ShouldResetGroupOnSelect(TabContents* contents) const; + // Changes the pinned state of the tab at |index|. See description above + // class for details on this. + void SetTabPinned(int index, bool pinned); + + // Returns true if the tab at |index| is pinned. + bool IsTabPinned(int index) const; + + // Returns the index of the first tab that is not pinned. This returns + // |count()| if all of the tabs are pinned, and 0 if no tabs are pinned. + int IndexOfFirstNonPinnedTab() const; + + // Returns true if pinned tabs are enabled in the UI. + static bool IsTabPinningEnabled(); + // Command level API ///////////////////////////////////////////////////////// // Adds a TabContents at the best position in the TabStripModel given the @@ -430,6 +472,7 @@ class TabStripModel : public NotificationObserver { CommandCloseTabsToRight, CommandCloseTabsOpenedBy, CommandRestoreTab, + CommandTogglePinned, CommandLast }; @@ -476,6 +519,10 @@ class TabStripModel : public NotificationObserver { // waiting for the result of an onunload handler. bool InternalCloseTabContentsAt(int index, bool create_historical_tab); + void MoveTabContentsAtImpl(int index, int to_position, + bool select_after_move, + bool update_pinned_state); + TabContents* GetContentsAt(int index) const; // The actual implementation of SelectTabContentsAt. Takes the previously @@ -508,6 +555,26 @@ class TabStripModel : public NotificationObserver { // the TabContents is in the current TabStripModel, unless otherwise // specified in code. struct TabContentsData { + explicit TabContentsData(TabContents* a_contents) + : contents(a_contents), + reset_group_on_select(false), + pinned(false) { + SetGroup(NULL); + } + + // Create a relationship between this TabContents and other TabContentses. + // Used to identify which TabContents to select next after one is closed. + void SetGroup(NavigationController* a_group) { + group = a_group; + opener = a_group; + } + + // Forget the opener relationship so that when this TabContents is closed + // unpredictable re-selection does not occur. + void ForgetOpener() { + opener = NULL; + } + TabContents* contents; // We use NavigationControllers here since they more closely model the // "identity" of a Tab, TabContents can change depending on the URL loaded @@ -533,24 +600,8 @@ class TabStripModel : public NotificationObserver { // relationship is reset to avoid confusing close sequencing. bool reset_group_on_select; - explicit TabContentsData(TabContents* a_contents) - : contents(a_contents), - reset_group_on_select(false) { - SetGroup(NULL); - } - - // Create a relationship between this TabContents and other TabContentses. - // Used to identify which TabContents to select next after one is closed. - void SetGroup(NavigationController* a_group) { - group = a_group; - opener = a_group; - } - - // Forget the opener relationship so that when this TabContents is closed - // unpredictable re-selection does not occur. - void ForgetOpener() { - opener = NULL; - } + // Is the tab pinned? + bool pinned; }; // The TabContents data currently hosted within this TabStripModel. diff --git a/chrome/browser/tabs/tab_strip_model_order_controller.cc b/chrome/browser/tabs/tab_strip_model_order_controller.cc index 1db0525..ad4fb22 100644 --- a/chrome/browser/tabs/tab_strip_model_order_controller.cc +++ b/chrome/browser/tabs/tab_strip_model_order_controller.cc @@ -24,6 +24,7 @@ int TabStripModelOrderController::DetermineInsertionIndex( PageTransition::Type transition, bool foreground) { int tab_count = tabstrip_->count(); + int first_non_pinned_tab = tabstrip_->IndexOfFirstNonPinnedTab(); if (!tab_count) return 0; @@ -33,7 +34,8 @@ int TabStripModelOrderController::DetermineInsertionIndex( // tab, insert it adjacent to the tab that opened that link. // TODO(beng): (http://b/1085481) may want to open right of all locked // tabs? - return tabstrip_->selected_index() + 1; + return std::max(first_non_pinned_tab, + tabstrip_->selected_index() + 1); } NavigationController* opener = &tabstrip_->GetSelectedTabContents()->controller(); @@ -44,7 +46,7 @@ int TabStripModelOrderController::DetermineInsertionIndex( if (index != TabStripModel::kNoTab) return index + 1; // Otherwise insert adjacent to opener... - return tabstrip_->selected_index() + 1; + return std::max(first_non_pinned_tab, tabstrip_->selected_index() + 1); } // In other cases, such as Ctrl+T, open at the end of the strip. return tab_count; diff --git a/chrome/browser/tabs/tab_strip_model_unittest.cc b/chrome/browser/tabs/tab_strip_model_unittest.cc index 19991a3..57795a4 100644 --- a/chrome/browser/tabs/tab_strip_model_unittest.cc +++ b/chrome/browser/tabs/tab_strip_model_unittest.cc @@ -4,6 +4,7 @@ #include "base/file_util.h" #include "base/path_service.h" +#include "base/string_util.h" #include "base/stl_util-inl.h" #include "chrome/browser/dom_ui/new_tab_ui.h" #include "chrome/browser/profile.h" @@ -15,6 +16,7 @@ #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/tabs/tab_strip_model_order_controller.h" #include "chrome/common/pref_names.h" +#include "chrome/common/property_bag.h" #include "chrome/common/url_constants.h" #include "testing/gtest/include/gtest/gtest.h" @@ -98,9 +100,43 @@ class TabStripModelTest : public RenderViewHostTestHarness { //contents()->DidBecomeSelected(); } + // Sets the id of the specified contents. + void SetID(TabContents* contents, int id) { + GetIDAccessor()->SetProperty(contents->property_bag(), id); + } + + // Returns the id of the specified contents. + int GetID(TabContents* contents) { + return *GetIDAccessor()->GetProperty(contents->property_bag()); + } + + // Returns the state of the given tab strip as a string. The state consists + // of the ID of each tab contents followed by a 'p' if pinned. For example, + // if the model consists of two tabs with ids 2 and 1, with the first + // tab pinned, this returns "2p 1". + std::string GetPinnedState(const TabStripModel& model) { + std::string actual; + for (int i = 0; i < model.count(); ++i) { + if (i > 0) + actual += " "; + + actual += IntToString(GetID(model.GetTabContentsAt(i))); + + if (model.IsTabPinned(i)) + actual += "p"; + } + return actual; + } + private: + PropertyAccessor<int>* GetIDAccessor() { + static PropertyAccessor<int> accessor; + return &accessor; + } + std::wstring test_dir_; std::wstring profile_path_; + std::map<TabContents*,int> foo_; ProfileManager pm_; }; @@ -117,7 +153,8 @@ class MockTabStripModelObserver : public TabStripModelObserver { DETACH, SELECT, MOVE, - CHANGE + CHANGE, + PINNED }; struct State { @@ -130,6 +167,7 @@ class MockTabStripModelObserver : public TabStripModelObserver { dst_index(a_dst_index), user_gesture(false), foreground(false), + pinned_state_changed(false), action(a_action) { } @@ -139,6 +177,7 @@ class MockTabStripModelObserver : public TabStripModelObserver { int dst_index; bool user_gesture; bool foreground; + bool pinned_state_changed; TabStripModelObserverAction action; }; @@ -159,6 +198,7 @@ class MockTabStripModelObserver : public TabStripModelObserver { EXPECT_EQ(s->dst_index, state.dst_index); EXPECT_EQ(s->user_gesture, state.user_gesture); EXPECT_EQ(s->foreground, state.foreground); + EXPECT_EQ(s->pinned_state_changed, state.pinned_state_changed); EXPECT_EQ(s->action, state.action); return (s->src_contents == state.src_contents && s->dst_contents == state.dst_contents && @@ -166,6 +206,7 @@ class MockTabStripModelObserver : public TabStripModelObserver { s->dst_index == state.dst_index && s->user_gesture == state.user_gesture && s->foreground == state.foreground && + s->pinned_state_changed == state.pinned_state_changed && s->action == state.action); } @@ -188,9 +229,11 @@ class MockTabStripModelObserver : public TabStripModelObserver { states_.push_back(s); } virtual void TabMoved( - TabContents* contents, int from_index, int to_index) { + TabContents* contents, int from_index, int to_index, + bool pinned_state_changed) { State* s = new State(contents, to_index, MOVE); s->src_index = from_index; + s->pinned_state_changed = pinned_state_changed; states_.push_back(s); } @@ -204,6 +247,9 @@ class MockTabStripModelObserver : public TabStripModelObserver { bool loading_only) { states_.push_back(new State(contents, index, CHANGE)); } + virtual void TabPinnedStateChanged(TabContents* contents, int index) { + states_.push_back(new State(contents, index, PINNED)); + } virtual void TabStripEmpty() { empty_ = true; } @@ -1210,3 +1256,209 @@ TEST_F(TabStripModelTest, NavigationForgettingDoesntAffectNewTab) { strip.CloseAllTabs(); } + +// Tests various permutations of pinning tabs. +TEST_F(TabStripModelTest, Pinning) { + TabStripDummyDelegate delegate(NULL); + TabStripModel tabstrip(&delegate, profile()); + MockTabStripModelObserver observer; + tabstrip.AddObserver(&observer); + + EXPECT_TRUE(tabstrip.empty()); + + typedef MockTabStripModelObserver::State State; + + TabContents* contents1 = CreateTabContents(); + TabContents* contents2 = CreateTabContents(); + TabContents* contents3 = CreateTabContents(); + + SetID(contents1, 1); + SetID(contents2, 2); + SetID(contents3, 3); + + // Note! The ordering of these tests is important, each subsequent test + // builds on the state established in the previous. This is important if you + // ever insert tests rather than append. + + // Initial state, three tabs, first selected. + tabstrip.AppendTabContents(contents1, true); + tabstrip.AppendTabContents(contents2, false); + tabstrip.AppendTabContents(contents3, false); + + observer.ClearStates(); + + // Pin the first tab, this shouldn't visually reorder anything. + { + tabstrip.SetTabPinned(0, true); + + // As the order didn't change, we should get a pinned notification. + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents1, 0, MockTabStripModelObserver::PINNED); + EXPECT_TRUE(observer.StateEquals(0, state)); + + // And verify the state. + EXPECT_EQ("1p 2 3", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + // Unpin the first tab. + { + tabstrip.SetTabPinned(0, false); + + // As the order didn't change, we should get a pinned notification. + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents1, 0, MockTabStripModelObserver::PINNED); + EXPECT_TRUE(observer.StateEquals(0, state)); + + // And verify the state. + EXPECT_EQ("1 2 3", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + // Pin the 3rd tab, which should move it to the front. + { + tabstrip.SetTabPinned(2, true); + + // The pinning should have resulted in a move. + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents3, 0, MockTabStripModelObserver::MOVE); + state.src_index = 2; + state.pinned_state_changed = true; + EXPECT_TRUE(observer.StateEquals(0, state)); + + // And verify the state. + EXPECT_EQ("3p 1 2", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + // Pin the tab "1", which shouldn't move anything. + { + tabstrip.SetTabPinned(1, true); + + // As the order didn't change, we should get a pinned notification. + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents1, 1, MockTabStripModelObserver::PINNED); + EXPECT_TRUE(observer.StateEquals(0, state)); + + // And verify the state. + EXPECT_EQ("3p 1p 2", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + // Move tab "2" to the front, which should pin it. + { + tabstrip.MoveTabContentsAt(2, 0, false); + + // As the order didn't change, we should get a pinned notification. + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents2, 0, MockTabStripModelObserver::MOVE); + state.src_index = 2; + state.pinned_state_changed = true; + EXPECT_TRUE(observer.StateEquals(0, state)); + + // And verify the state. + EXPECT_EQ("2p 3p 1p", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + // Unpin tab "2", which implicitly moves it to the end. + { + tabstrip.SetTabPinned(0, false); + + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents2, 2, MockTabStripModelObserver::MOVE); + state.src_index = 0; + state.pinned_state_changed = true; + EXPECT_TRUE(observer.StateEquals(0, state)); + + // And verify the state. + EXPECT_EQ("3p 1p 2", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + // Drag tab "3" to after "1', which should not change the pinned state. + { + tabstrip.MoveTabContentsAt(0, 1, false); + + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents3, 1, MockTabStripModelObserver::MOVE); + state.src_index = 0; + EXPECT_TRUE(observer.StateEquals(0, state)); + + // And verify the state. + EXPECT_EQ("1p 3p 2", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + // Unpin tab "1". + { + tabstrip.SetTabPinned(0, false); + + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents1, 1, MockTabStripModelObserver::MOVE); + state.src_index = 0; + state.pinned_state_changed = true; + EXPECT_TRUE(observer.StateEquals(0, state)); + + // And verify the state. + EXPECT_EQ("3p 1 2", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + // Unpin tab "3". + { + tabstrip.SetTabPinned(0, false); + + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents3, 0, MockTabStripModelObserver::PINNED); + EXPECT_TRUE(observer.StateEquals(0, state)); + + EXPECT_EQ("3 1 2", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + // Unpin tab "3" again, as it's unpinned nothing should change. + { + tabstrip.SetTabPinned(0, false); + + ASSERT_EQ(0, observer.GetStateCount()); + + EXPECT_EQ("3 1 2", GetPinnedState(tabstrip)); + } + + // Pin "3" and "1". + { + tabstrip.SetTabPinned(0, true); + tabstrip.SetTabPinned(1, true); + + EXPECT_EQ("3p 1p 2", GetPinnedState(tabstrip)); + + observer.ClearStates(); + } + + TabContents* contents4 = CreateTabContents(); + SetID(contents4, 4); + + // Insert "4" between "3" and "1". As "3" and "1" are pinned, "4" should + // be pinned too. + { + tabstrip.InsertTabContentsAt(1, contents4, false, false); + + ASSERT_EQ(1, observer.GetStateCount()); + State state(contents4, 1, MockTabStripModelObserver::INSERT); + EXPECT_TRUE(observer.StateEquals(0, state)); + + EXPECT_EQ("3p 4p 1p 2", GetPinnedState(tabstrip)); + } + + tabstrip.CloseAllTabs(); +} |