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.
4
5 #include "chrome/browser/ui/views/tabs/tab.h"
6
7 #include "base/strings/utf_string_conversions.h"
8 #include "chrome/browser/ui/views/tabs/tab_controller.h"
9 #include "testing/gtest/include/gtest/gtest.h"
10 #include "ui/base/models/list_selection_model.h"
11 #include "ui/views/controls/button/image_button.h"
12 #include "ui/views/controls/label.h"
13 #include "ui/views/test/views_test_base.h"
14 #include "ui/views/widget/widget.h"
15
16 using views::Widget;
17
18 class FakeTabController : public TabController {
19 public:
FakeTabController()20 FakeTabController() : immersive_style_(false), active_tab_(false) {
21 }
~FakeTabController()22 virtual ~FakeTabController() {}
23
set_immersive_style(bool value)24 void set_immersive_style(bool value) { immersive_style_ = value; }
set_active_tab(bool value)25 void set_active_tab(bool value) { active_tab_ = value; }
26
GetSelectionModel()27 virtual const ui::ListSelectionModel& GetSelectionModel() OVERRIDE {
28 return selection_model_;
29 }
SupportsMultipleSelection()30 virtual bool SupportsMultipleSelection() OVERRIDE { return false; }
SelectTab(Tab * tab)31 virtual void SelectTab(Tab* tab) OVERRIDE {}
ExtendSelectionTo(Tab * tab)32 virtual void ExtendSelectionTo(Tab* tab) OVERRIDE {}
ToggleSelected(Tab * tab)33 virtual void ToggleSelected(Tab* tab) OVERRIDE {}
AddSelectionFromAnchorTo(Tab * tab)34 virtual void AddSelectionFromAnchorTo(Tab* tab) OVERRIDE {}
CloseTab(Tab * tab,CloseTabSource source)35 virtual void CloseTab(Tab* tab, CloseTabSource source) OVERRIDE {}
ShowContextMenuForTab(Tab * tab,const gfx::Point & p,ui::MenuSourceType source_type)36 virtual void ShowContextMenuForTab(Tab* tab,
37 const gfx::Point& p,
38 ui::MenuSourceType source_type) OVERRIDE {}
IsActiveTab(const Tab * tab) const39 virtual bool IsActiveTab(const Tab* tab) const OVERRIDE {
40 return active_tab_;
41 }
IsTabSelected(const Tab * tab) const42 virtual bool IsTabSelected(const Tab* tab) const OVERRIDE {
43 return false;
44 }
IsTabPinned(const Tab * tab) const45 virtual bool IsTabPinned(const Tab* tab) const OVERRIDE { return false; }
MaybeStartDrag(Tab * tab,const ui::LocatedEvent & event,const ui::ListSelectionModel & original_selection)46 virtual void MaybeStartDrag(
47 Tab* tab,
48 const ui::LocatedEvent& event,
49 const ui::ListSelectionModel& original_selection) OVERRIDE {}
ContinueDrag(views::View * view,const ui::LocatedEvent & event)50 virtual void ContinueDrag(views::View* view,
51 const ui::LocatedEvent& event) OVERRIDE {}
EndDrag(EndDragReason reason)52 virtual bool EndDrag(EndDragReason reason) OVERRIDE { return false; }
GetTabAt(Tab * tab,const gfx::Point & tab_in_tab_coordinates)53 virtual Tab* GetTabAt(Tab* tab,
54 const gfx::Point& tab_in_tab_coordinates) OVERRIDE {
55 return NULL;
56 }
OnMouseEventInTab(views::View * source,const ui::MouseEvent & event)57 virtual void OnMouseEventInTab(views::View* source,
58 const ui::MouseEvent& event) OVERRIDE {}
ShouldPaintTab(const Tab * tab,gfx::Rect * clip)59 virtual bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) OVERRIDE {
60 return true;
61 }
IsImmersiveStyle() const62 virtual bool IsImmersiveStyle() const OVERRIDE { return immersive_style_; }
63
64 private:
65 ui::ListSelectionModel selection_model_;
66 bool immersive_style_;
67 bool active_tab_;
68
69 DISALLOW_COPY_AND_ASSIGN(FakeTabController);
70 };
71
72 class TabTest : public views::ViewsTestBase {
73 public:
TabTest()74 TabTest() {}
~TabTest()75 virtual ~TabTest() {}
76
DisableMediaIndicatorAnimation(Tab * tab)77 static void DisableMediaIndicatorAnimation(Tab* tab) {
78 tab->media_indicator_animation_.reset();
79 tab->animating_media_state_ = tab->data_.media_state;
80 }
81
CheckForExpectedLayoutAndVisibilityOfElements(const Tab & tab)82 static void CheckForExpectedLayoutAndVisibilityOfElements(const Tab& tab) {
83 // Check whether elements are visible when they are supposed to be, given
84 // Tab size and TabRendererData state.
85 if (tab.data_.mini) {
86 EXPECT_EQ(1, tab.IconCapacity());
87 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
88 EXPECT_FALSE(tab.ShouldShowIcon());
89 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
90 } else {
91 EXPECT_TRUE(tab.ShouldShowIcon());
92 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
93 }
94 EXPECT_FALSE(tab.ShouldShowCloseBox());
95 } else if (tab.IsActive()) {
96 EXPECT_TRUE(tab.ShouldShowCloseBox());
97 switch (tab.IconCapacity()) {
98 case 0:
99 case 1:
100 EXPECT_FALSE(tab.ShouldShowIcon());
101 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
102 break;
103 case 2:
104 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
105 EXPECT_FALSE(tab.ShouldShowIcon());
106 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
107 } else {
108 EXPECT_TRUE(tab.ShouldShowIcon());
109 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
110 }
111 break;
112 default:
113 EXPECT_LE(3, tab.IconCapacity());
114 EXPECT_TRUE(tab.ShouldShowIcon());
115 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
116 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
117 else
118 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
119 break;
120 }
121 } else { // Tab not active and not mini tab.
122 switch (tab.IconCapacity()) {
123 case 0:
124 EXPECT_FALSE(tab.ShouldShowCloseBox());
125 EXPECT_FALSE(tab.ShouldShowIcon());
126 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
127 break;
128 case 1:
129 EXPECT_FALSE(tab.ShouldShowCloseBox());
130 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
131 EXPECT_FALSE(tab.ShouldShowIcon());
132 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
133 } else {
134 EXPECT_TRUE(tab.ShouldShowIcon());
135 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
136 }
137 break;
138 default:
139 EXPECT_LE(2, tab.IconCapacity());
140 EXPECT_TRUE(tab.ShouldShowIcon());
141 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
142 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
143 else
144 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
145 break;
146 }
147 }
148
149 // Check positioning of elements with respect to each other, and that they
150 // are fully within the contents bounds.
151 const gfx::Rect contents_bounds = tab.GetContentsBounds();
152 if (tab.ShouldShowIcon()) {
153 EXPECT_LE(contents_bounds.x(), tab.favicon_bounds_.x());
154 if (tab.title_->width() > 0)
155 EXPECT_LE(tab.favicon_bounds_.right(), tab.title_->x());
156 EXPECT_LE(contents_bounds.y(), tab.favicon_bounds_.y());
157 EXPECT_LE(tab.favicon_bounds_.bottom(), contents_bounds.bottom());
158 }
159 if (tab.ShouldShowIcon() && tab.ShouldShowMediaIndicator())
160 EXPECT_LE(tab.favicon_bounds_.right(), tab.media_indicator_bounds_.x());
161 if (tab.ShouldShowMediaIndicator()) {
162 if (tab.title_->width() > 0) {
163 EXPECT_LE(tab.title_->bounds().right(),
164 tab.media_indicator_bounds_.x());
165 }
166 EXPECT_LE(tab.media_indicator_bounds_.right(), contents_bounds.right());
167 EXPECT_LE(contents_bounds.y(), tab.media_indicator_bounds_.y());
168 EXPECT_LE(tab.media_indicator_bounds_.bottom(), contents_bounds.bottom());
169 }
170 if (tab.ShouldShowMediaIndicator() && tab.ShouldShowCloseBox()) {
171 // Note: The media indicator can overlap the left-insets of the close box,
172 // but should otherwise be to the left of the close button.
173 EXPECT_LE(tab.media_indicator_bounds_.right(),
174 tab.close_button_->bounds().x() +
175 tab.close_button_->GetInsets().left());
176 }
177 if (tab.ShouldShowCloseBox()) {
178 // Note: The title bounds can overlap the left-insets of the close box,
179 // but should otherwise be to the left of the close button.
180 if (tab.title_->width() > 0) {
181 EXPECT_LE(tab.title_->bounds().right(),
182 tab.close_button_->bounds().x() +
183 tab.close_button_->GetInsets().left());
184 }
185 EXPECT_LE(tab.close_button_->bounds().right(), contents_bounds.right());
186 EXPECT_LE(contents_bounds.y(), tab.close_button_->bounds().y());
187 EXPECT_LE(tab.close_button_->bounds().bottom(), contents_bounds.bottom());
188 }
189 }
190 };
191
TEST_F(TabTest,HitTestTopPixel)192 TEST_F(TabTest, HitTestTopPixel) {
193 Widget widget;
194 Widget::InitParams params(CreateParams(Widget::InitParams::TYPE_WINDOW));
195 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
196 params.bounds.SetRect(10, 20, 300, 400);
197 widget.Init(params);
198
199 FakeTabController tab_controller;
200 Tab tab(&tab_controller);
201 widget.GetContentsView()->AddChildView(&tab);
202 tab.SetBoundsRect(gfx::Rect(gfx::Point(0, 0), Tab::GetStandardSize()));
203
204 // Tabs have some shadow in the top, so by default we don't hit the tab there.
205 int middle_x = tab.width() / 2;
206 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
207
208 // Tabs are slanted, so a click halfway down the left edge won't hit it.
209 int middle_y = tab.height() / 2;
210 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, middle_y)));
211
212 // If the window is maximized, however, we want clicks in the top edge to
213 // select the tab.
214 widget.Maximize();
215 EXPECT_TRUE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
216
217 // But clicks in the area above the slanted sides should still miss.
218 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, 0)));
219 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(tab.width() - 1, 0)));
220 }
221
TEST_F(TabTest,LayoutAndVisibilityOfElements)222 TEST_F(TabTest, LayoutAndVisibilityOfElements) {
223 static const TabMediaState kMediaStatesToTest[] = {
224 TAB_MEDIA_STATE_NONE, TAB_MEDIA_STATE_CAPTURING,
225 TAB_MEDIA_STATE_AUDIO_PLAYING
226 };
227
228 FakeTabController controller;
229 Tab tab(&controller);
230
231 SkBitmap bitmap;
232 bitmap.setConfig(SkBitmap::kARGB_8888_Config, 16, 16);
233 bitmap.allocPixels();
234 TabRendererData data;
235 data.favicon = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
236
237 // Perform layout over all possible combinations, checking for correct
238 // results.
239 for (int is_mini_tab = 0; is_mini_tab < 2; ++is_mini_tab) {
240 for (int is_active_tab = 0; is_active_tab < 2; ++is_active_tab) {
241 for (size_t media_state_index = 0;
242 media_state_index < arraysize(kMediaStatesToTest);
243 ++media_state_index) {
244 const TabMediaState media_state = kMediaStatesToTest[media_state_index];
245 SCOPED_TRACE(::testing::Message()
246 << (is_active_tab ? "Active" : "Inactive") << ' '
247 << (is_mini_tab ? "Mini " : "")
248 << "Tab with media indicator state " << media_state);
249
250 data.mini = !!is_mini_tab;
251 controller.set_active_tab(!!is_active_tab);
252 data.media_state = media_state;
253 tab.SetData(data);
254
255 // Disable the media indicator animation so that the layout/visibility
256 // logic can be tested effectively. If the animation was left enabled,
257 // the ShouldShowMediaIndicator() method would return true during
258 // fade-out transitions.
259 DisableMediaIndicatorAnimation(&tab);
260
261 // Test layout for every width from standard to minimum.
262 gfx::Rect bounds(gfx::Point(0, 0), Tab::GetStandardSize());
263 int min_width;
264 if (is_mini_tab) {
265 bounds.set_width(Tab::GetMiniWidth());
266 min_width = Tab::GetMiniWidth();
267 } else {
268 min_width = is_active_tab ? Tab::GetMinimumSelectedSize().width() :
269 Tab::GetMinimumUnselectedSize().width();
270 }
271 while (bounds.width() >= min_width) {
272 SCOPED_TRACE(::testing::Message() << "bounds=" << bounds.ToString());
273 tab.SetBoundsRect(bounds); // Invokes Tab::Layout().
274 CheckForExpectedLayoutAndVisibilityOfElements(tab);
275 bounds.set_width(bounds.width() - 1);
276 }
277 }
278 }
279 }
280 }
281
282 // Regression test for http://crbug.com/226253. Calling Layout() more than once
283 // shouldn't change the insets of the close button.
TEST_F(TabTest,CloseButtonLayout)284 TEST_F(TabTest, CloseButtonLayout) {
285 FakeTabController tab_controller;
286 Tab tab(&tab_controller);
287 tab.SetBounds(0, 0, 100, 50);
288 tab.Layout();
289 gfx::Insets close_button_insets = tab.close_button_->GetInsets();
290 tab.Layout();
291 gfx::Insets close_button_insets_2 = tab.close_button_->GetInsets();
292 EXPECT_EQ(close_button_insets.top(), close_button_insets_2.top());
293 EXPECT_EQ(close_button_insets.left(), close_button_insets_2.left());
294 EXPECT_EQ(close_button_insets.bottom(), close_button_insets_2.bottom());
295 EXPECT_EQ(close_button_insets.right(), close_button_insets_2.right());
296
297 // Also make sure the close button is sized as large as the tab.
298 EXPECT_EQ(50, tab.close_button_->bounds().height());
299 }
300