summaryrefslogtreecommitdiffstats
path: root/chrome/browser/tabs
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-08 16:06:29 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-08 16:06:29 +0000
commitc0df4c5d2ed5b2f4e1358476aba914399bccc306 (patch)
treebbe17f36f0731401dbacaae8cd9868c16779e66d /chrome/browser/tabs
parentd2879af4320bd17bd9082bf724f5222f6060f3d0 (diff)
downloadchromium_src-c0df4c5d2ed5b2f4e1358476aba914399bccc306.zip
chromium_src-c0df4c5d2ed5b2f4e1358476aba914399bccc306.tar.gz
chromium_src-c0df4c5d2ed5b2f4e1358476aba914399bccc306.tar.bz2
Fixes crash in making a tab phantom. Here was the sequence when making
a tab phantom: 1. close the old contents (sending out TabClosingAt). 2. TabSelectedAt 3. TabReplatedAt The problem is is Browser::TabSelectedAt processes updates for the old contents, resulting in: 1. close the old contents (sending out TabClosingAt). 2. TabSelectedAt a UpdateTabContentsStateAt (for old tabcontents) b TabChangedAt (for old tabcontents) 3. TabReplatedAt So, 2b is sent for a tabcontents that observers was already told closed in step 1. This triggered a DCHECK in extensions code. I've changed it so we send out TabSelectedAt after the new contents are swapped in. I also fixed a bug where I wasn't telling observers the tab strip empty if the last non-phantom tab is made phantom. BUG=34137 TEST=none Review URL: http://codereview.chromium.org/579024 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38363 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/tabs')
-rw-r--r--chrome/browser/tabs/tab_strip_model.cc26
-rw-r--r--chrome/browser/tabs/tab_strip_model_unittest.cc146
2 files changed, 153 insertions, 19 deletions
diff --git a/chrome/browser/tabs/tab_strip_model.cc b/chrome/browser/tabs/tab_strip_model.cc
index 167604f..1d9f544 100644
--- a/chrome/browser/tabs/tab_strip_model.cc
+++ b/chrome/browser/tabs/tab_strip_model.cc
@@ -877,11 +877,21 @@ bool TabStripModel::ShouldMakePhantomOnClose(int index) {
}
void TabStripModel::MakePhantom(int index) {
- if (selected_index_ == index) {
+ TabContents* old_contents = GetContentsAt(index);
+ TabContents* new_contents = old_contents->CloneAndMakePhantom();
+
+ contents_data_[index]->contents = new_contents;
+
+ // And notify observers.
+ FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
+ TabReplacedAt(old_contents, new_contents, index));
+
+ if (selected_index_ == index && HasNonPhantomTabs()) {
// Change the selection, otherwise we're going to force the phantom tab
// to become selected.
- // NOTE: we must do this before switching the TabContents, otherwise
- // observers are notified with the wrong tab contents.
+ // NOTE: we must do this after the call to Replace otherwise browser's
+ // TabSelectedAt will send out updates for the old TabContents which we've
+ // already told observers has been closed (we sent out TabClosing at).
int new_selected_index =
order_controller_->DetermineNewSelectedIndex(index, false);
new_selected_index = IndexOfNextNonPhantomTab(new_selected_index,
@@ -889,14 +899,8 @@ void TabStripModel::MakePhantom(int index) {
SelectTabContentsAt(new_selected_index, true);
}
- TabContents* old_contents = GetContentsAt(index);
- TabContents* new_contents = old_contents->CloneAndMakePhantom();
-
- contents_data_[index]->contents = new_contents;
-
- // And notify observers.
- FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
- TabReplacedAt(old_contents, new_contents, index));
+ if (!HasNonPhantomTabs())
+ FOR_EACH_OBSERVER(TabStripModelObserver, observers_, TabStripEmpty());
}
diff --git a/chrome/browser/tabs/tab_strip_model_unittest.cc b/chrome/browser/tabs/tab_strip_model_unittest.cc
index 0975ff4..11f2f73 100644
--- a/chrome/browser/tabs/tab_strip_model_unittest.cc
+++ b/chrome/browser/tabs/tab_strip_model_unittest.cc
@@ -144,6 +144,9 @@ class TabStripModelTest : public RenderViewHostTestHarness {
if (model.IsTabPinned(i))
actual += "p";
+
+ if (model.IsPhantomTab(i))
+ actual += "h";
}
return actual;
}
@@ -178,7 +181,8 @@ class MockTabStripModelObserver : public TabStripModelObserver {
SELECT,
MOVE,
CHANGE,
- PINNED
+ PINNED,
+ REPLACED
};
struct State {
@@ -214,13 +218,13 @@ class MockTabStripModelObserver : public TabStripModelObserver {
bool StateEquals(int index, const State& state) {
State* s = GetStateAt(index);
- EXPECT_EQ(s->src_contents, state.src_contents);
- EXPECT_EQ(s->dst_contents, state.dst_contents);
- EXPECT_EQ(s->src_index, state.src_index);
- 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->action, state.action);
+ EXPECT_EQ(state.src_contents, s->src_contents);
+ EXPECT_EQ(state.dst_contents, s->dst_contents);
+ EXPECT_EQ(state.src_index, s->src_index);
+ EXPECT_EQ(state.dst_index, s->dst_index);
+ EXPECT_EQ(state.user_gesture, s->user_gesture);
+ EXPECT_EQ(state.foreground, s->foreground);
+ EXPECT_EQ(state.action, s->action);
return (s->src_contents == state.src_contents &&
s->dst_contents == state.dst_contents &&
s->src_index == state.src_index &&
@@ -265,6 +269,12 @@ class MockTabStripModelObserver : public TabStripModelObserver {
TabChangeType change_type) {
states_.push_back(new State(contents, index, CHANGE));
}
+ virtual void TabReplacedAt(TabContents* old_contents,
+ TabContents* new_contents, int index) {
+ State* s = new State(new_contents, index, REPLACED);
+ s ->src_contents = old_contents;
+ states_.push_back(s);
+ }
virtual void TabPinnedStateChanged(TabContents* contents, int index) {
states_.push_back(new State(contents, index, PINNED));
}
@@ -1616,3 +1626,123 @@ TEST_F(TabStripModelTest, Pinning) {
tabstrip.CloseAllTabs();
}
+
+// Tests various permutations of making a tab phantom.
+TEST_F(TabStripModelTest, Phantom) {
+ 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, and make it phantom.
+ {
+ tabstrip.SetTabPinned(0, true);
+
+ observer.ClearStates();
+
+ tabstrip.CloseTabContentsAt(0);
+
+ // The tabcontents should have changed.
+ TabContents* old_contents1 = contents1;
+ TabContents* new_contents1 = tabstrip.GetTabContentsAt(0);
+ ASSERT_TRUE(new_contents1 != contents1);
+ contents1 = new_contents1;
+ SetID(contents1, 1);
+
+ // Verify the state.
+ EXPECT_EQ("1ph 2 3", GetPinnedState(tabstrip));
+
+ // We should have gotten notification of the following:
+ // . tab closing.
+ // . selection changed.
+ // . tab replaced.
+ ASSERT_EQ(3, observer.GetStateCount());
+ State state(old_contents1, 0, MockTabStripModelObserver::CLOSE);
+ EXPECT_TRUE(observer.StateEquals(0, state));
+ state = State(contents1, 0, MockTabStripModelObserver::REPLACED);
+ state.src_contents = old_contents1;
+ EXPECT_TRUE(observer.StateEquals(1, state));
+ state = State(contents2, 1, MockTabStripModelObserver::SELECT);
+ state.src_contents = contents1;
+ state.user_gesture = true;
+ EXPECT_TRUE(observer.StateEquals(2, state));
+
+ observer.ClearStates();
+ }
+
+ {
+ tabstrip.SetTabPinned(1, true);
+ observer.ClearStates();
+
+ // Close the second tab, which should make it phantom.
+ tabstrip.CloseTabContentsAt(1);
+
+ // The tabcontents should have changed.
+ TabContents* new_contents2 = tabstrip.GetTabContentsAt(1);
+ ASSERT_TRUE(new_contents2 != contents2);
+ contents2 = new_contents2;
+ SetID(contents2, 2);
+
+ EXPECT_EQ("1ph 2ph 3", GetPinnedState(tabstrip));
+
+ EXPECT_EQ(2, tabstrip.selected_index());
+
+ contents2 = tabstrip.GetTabContentsAt(1);
+
+ observer.ClearStates();
+ }
+
+ {
+ tabstrip.SetTabPinned(2, true);
+ observer.ClearStates();
+
+ // Close the last tab, we should get a tabstrip empty notification.
+ tabstrip.CloseTabContentsAt(2);
+
+ // The tabcontents should have changed.
+ TabContents* old_contents3 = contents3;
+ TabContents* new_contents3 = tabstrip.GetTabContentsAt(2);
+ ASSERT_TRUE(new_contents3 != contents3);
+ contents3 = new_contents3;
+ SetID(contents3, 3);
+
+ EXPECT_EQ("1ph 2ph 3ph", GetPinnedState(tabstrip));
+
+ // We should have gotten notification of the following:
+ // . tab closing.
+ // . tab replaced.
+ // . tabstrip empty.
+ ASSERT_EQ(2, observer.GetStateCount());
+ State state(old_contents3, 2, MockTabStripModelObserver::CLOSE);
+ EXPECT_TRUE(observer.StateEquals(0, state));
+ state = State(contents3, 2, MockTabStripModelObserver::REPLACED);
+ state.src_contents = old_contents3;
+ EXPECT_TRUE(observer.StateEquals(1, state));
+ EXPECT_TRUE(observer.empty());
+
+ observer.ClearStates();
+ }
+}