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/i18n/rtl.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/ui/views/tabs/media_indicator_button.h"
10 #include "chrome/browser/ui/views/tabs/tab_controller.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 #include "ui/base/models/list_selection_model.h"
13 #include "ui/views/controls/button/image_button.h"
14 #include "ui/views/controls/label.h"
15 #include "ui/views/test/views_test_base.h"
16 #include "ui/views/widget/widget.h"
17
18 using views::Widget;
19
20 class FakeTabController : public TabController {
21 public:
FakeTabController()22 FakeTabController() : immersive_style_(false), active_tab_(false) {
23 }
~FakeTabController()24 virtual ~FakeTabController() {}
25
set_immersive_style(bool value)26 void set_immersive_style(bool value) { immersive_style_ = value; }
set_active_tab(bool value)27 void set_active_tab(bool value) { active_tab_ = value; }
28
GetSelectionModel()29 virtual const ui::ListSelectionModel& GetSelectionModel() OVERRIDE {
30 return selection_model_;
31 }
SupportsMultipleSelection()32 virtual bool SupportsMultipleSelection() OVERRIDE { return false; }
SelectTab(Tab * tab)33 virtual void SelectTab(Tab* tab) OVERRIDE {}
ExtendSelectionTo(Tab * tab)34 virtual void ExtendSelectionTo(Tab* tab) OVERRIDE {}
ToggleSelected(Tab * tab)35 virtual void ToggleSelected(Tab* tab) OVERRIDE {}
AddSelectionFromAnchorTo(Tab * tab)36 virtual void AddSelectionFromAnchorTo(Tab* tab) OVERRIDE {}
CloseTab(Tab * tab,CloseTabSource source)37 virtual void CloseTab(Tab* tab, CloseTabSource source) OVERRIDE {}
ToggleTabAudioMute(Tab * tab)38 virtual void ToggleTabAudioMute(Tab* tab) OVERRIDE {}
ShowContextMenuForTab(Tab * tab,const gfx::Point & p,ui::MenuSourceType source_type)39 virtual void ShowContextMenuForTab(Tab* tab,
40 const gfx::Point& p,
41 ui::MenuSourceType source_type) OVERRIDE {}
IsActiveTab(const Tab * tab) const42 virtual bool IsActiveTab(const Tab* tab) const OVERRIDE {
43 return active_tab_;
44 }
IsTabSelected(const Tab * tab) const45 virtual bool IsTabSelected(const Tab* tab) const OVERRIDE {
46 return false;
47 }
IsTabPinned(const Tab * tab) const48 virtual bool IsTabPinned(const Tab* tab) const OVERRIDE { return false; }
MaybeStartDrag(Tab * tab,const ui::LocatedEvent & event,const ui::ListSelectionModel & original_selection)49 virtual void MaybeStartDrag(
50 Tab* tab,
51 const ui::LocatedEvent& event,
52 const ui::ListSelectionModel& original_selection) OVERRIDE {}
ContinueDrag(views::View * view,const ui::LocatedEvent & event)53 virtual void ContinueDrag(views::View* view,
54 const ui::LocatedEvent& event) OVERRIDE {}
EndDrag(EndDragReason reason)55 virtual bool EndDrag(EndDragReason reason) OVERRIDE { return false; }
GetTabAt(Tab * tab,const gfx::Point & tab_in_tab_coordinates)56 virtual Tab* GetTabAt(Tab* tab,
57 const gfx::Point& tab_in_tab_coordinates) OVERRIDE {
58 return NULL;
59 }
OnMouseEventInTab(views::View * source,const ui::MouseEvent & event)60 virtual void OnMouseEventInTab(views::View* source,
61 const ui::MouseEvent& event) OVERRIDE {}
ShouldPaintTab(const Tab * tab,gfx::Rect * clip)62 virtual bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) OVERRIDE {
63 return true;
64 }
IsImmersiveStyle() const65 virtual bool IsImmersiveStyle() const OVERRIDE { return immersive_style_; }
UpdateTabAccessibilityState(const Tab * tab,ui::AXViewState * state)66 virtual void UpdateTabAccessibilityState(const Tab* tab,
67 ui::AXViewState* state) OVERRIDE{};
68
69 private:
70 ui::ListSelectionModel selection_model_;
71 bool immersive_style_;
72 bool active_tab_;
73
74 DISALLOW_COPY_AND_ASSIGN(FakeTabController);
75 };
76
77 class TabTest : public views::ViewsTestBase,
78 public ::testing::WithParamInterface<bool> {
79 public:
TabTest()80 TabTest() {}
~TabTest()81 virtual ~TabTest() {}
82
testing_for_rtl_locale() const83 bool testing_for_rtl_locale() const { return GetParam(); }
84
SetUp()85 virtual void SetUp() OVERRIDE {
86 if (testing_for_rtl_locale()) {
87 original_locale_ = base::i18n::GetConfiguredLocale();
88 base::i18n::SetICUDefaultLocale("he");
89 }
90 views::ViewsTestBase::SetUp();
91 }
92
TearDown()93 virtual void TearDown() OVERRIDE {
94 views::ViewsTestBase::TearDown();
95 if (testing_for_rtl_locale())
96 base::i18n::SetICUDefaultLocale(original_locale_);
97 }
98
CheckForExpectedLayoutAndVisibilityOfElements(const Tab & tab)99 static void CheckForExpectedLayoutAndVisibilityOfElements(const Tab& tab) {
100 // Check whether elements are visible when they are supposed to be, given
101 // Tab size and TabRendererData state.
102 if (tab.data_.mini) {
103 EXPECT_EQ(1, tab.IconCapacity());
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 EXPECT_FALSE(tab.ShouldShowCloseBox());
112 } else if (tab.IsActive()) {
113 EXPECT_TRUE(tab.ShouldShowCloseBox());
114 switch (tab.IconCapacity()) {
115 case 0:
116 case 1:
117 EXPECT_FALSE(tab.ShouldShowIcon());
118 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
119 break;
120 case 2:
121 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
122 EXPECT_FALSE(tab.ShouldShowIcon());
123 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
124 } else {
125 EXPECT_TRUE(tab.ShouldShowIcon());
126 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
127 }
128 break;
129 default:
130 EXPECT_LE(3, tab.IconCapacity());
131 EXPECT_TRUE(tab.ShouldShowIcon());
132 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
133 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
134 else
135 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
136 break;
137 }
138 } else { // Tab not active and not mini tab.
139 switch (tab.IconCapacity()) {
140 case 0:
141 EXPECT_FALSE(tab.ShouldShowCloseBox());
142 EXPECT_FALSE(tab.ShouldShowIcon());
143 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
144 break;
145 case 1:
146 EXPECT_FALSE(tab.ShouldShowCloseBox());
147 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
148 EXPECT_FALSE(tab.ShouldShowIcon());
149 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
150 } else {
151 EXPECT_TRUE(tab.ShouldShowIcon());
152 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
153 }
154 break;
155 default:
156 EXPECT_LE(2, tab.IconCapacity());
157 EXPECT_TRUE(tab.ShouldShowIcon());
158 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
159 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
160 else
161 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
162 break;
163 }
164 }
165
166 // Check positioning of elements with respect to each other, and that they
167 // are fully within the contents bounds.
168 const gfx::Rect contents_bounds = tab.GetContentsBounds();
169 if (tab.ShouldShowIcon()) {
170 EXPECT_LE(contents_bounds.x(), tab.favicon_bounds_.x());
171 if (tab.title_->width() > 0)
172 EXPECT_LE(tab.favicon_bounds_.right(), tab.title_->x());
173 EXPECT_LE(contents_bounds.y(), tab.favicon_bounds_.y());
174 EXPECT_LE(tab.favicon_bounds_.bottom(), contents_bounds.bottom());
175 }
176 if (tab.ShouldShowIcon() && tab.ShouldShowMediaIndicator())
177 EXPECT_LE(tab.favicon_bounds_.right(), GetMediaIndicatorBounds(tab).x());
178 if (tab.ShouldShowMediaIndicator()) {
179 if (tab.title_->width() > 0) {
180 EXPECT_LE(tab.title_->bounds().right(),
181 GetMediaIndicatorBounds(tab).x());
182 }
183 EXPECT_LE(GetMediaIndicatorBounds(tab).right(), contents_bounds.right());
184 EXPECT_LE(contents_bounds.y(), GetMediaIndicatorBounds(tab).y());
185 EXPECT_LE(GetMediaIndicatorBounds(tab).bottom(),
186 contents_bounds.bottom());
187 }
188 if (tab.ShouldShowMediaIndicator() && tab.ShouldShowCloseBox()) {
189 // Note: The media indicator can overlap the left-insets of the close box,
190 // but should otherwise be to the left of the close button.
191 EXPECT_LE(GetMediaIndicatorBounds(tab).right(),
192 tab.close_button_->bounds().x() +
193 tab.close_button_->GetInsets().left());
194 }
195 if (tab.ShouldShowCloseBox()) {
196 // Note: The title bounds can overlap the left-insets of the close box,
197 // but should otherwise be to the left of the close button.
198 if (tab.title_->width() > 0) {
199 EXPECT_LE(tab.title_->bounds().right(),
200 tab.close_button_->bounds().x() +
201 tab.close_button_->GetInsets().left());
202 }
203 EXPECT_LE(tab.close_button_->bounds().right(), contents_bounds.right());
204 EXPECT_LE(contents_bounds.y(), tab.close_button_->bounds().y());
205 EXPECT_LE(tab.close_button_->bounds().bottom(), contents_bounds.bottom());
206 }
207 }
208
209 private:
GetMediaIndicatorBounds(const Tab & tab)210 static gfx::Rect GetMediaIndicatorBounds(const Tab& tab) {
211 if (!tab.media_indicator_button_) {
212 ADD_FAILURE();
213 return gfx::Rect();
214 }
215 return tab.media_indicator_button_->bounds();
216 }
217
218 std::string original_locale_;
219 };
220
TEST_P(TabTest,HitTestTopPixel)221 TEST_P(TabTest, HitTestTopPixel) {
222 if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
223 LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
224 return;
225 }
226
227 Widget widget;
228 Widget::InitParams params(CreateParams(Widget::InitParams::TYPE_WINDOW));
229 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
230 params.bounds.SetRect(10, 20, 300, 400);
231 widget.Init(params);
232
233 FakeTabController tab_controller;
234 Tab tab(&tab_controller);
235 widget.GetContentsView()->AddChildView(&tab);
236 tab.SetBoundsRect(gfx::Rect(gfx::Point(0, 0), Tab::GetStandardSize()));
237
238 // Tabs have some shadow in the top, so by default we don't hit the tab there.
239 int middle_x = tab.width() / 2;
240 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
241
242 // Tabs are slanted, so a click halfway down the left edge won't hit it.
243 int middle_y = tab.height() / 2;
244 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, middle_y)));
245
246 // If the window is maximized, however, we want clicks in the top edge to
247 // select the tab.
248 widget.Maximize();
249 EXPECT_TRUE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
250
251 // But clicks in the area above the slanted sides should still miss.
252 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, 0)));
253 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(tab.width() - 1, 0)));
254 }
255
TEST_P(TabTest,LayoutAndVisibilityOfElements)256 TEST_P(TabTest, LayoutAndVisibilityOfElements) {
257 if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
258 LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
259 return;
260 }
261
262 static const TabMediaState kMediaStatesToTest[] = {
263 TAB_MEDIA_STATE_NONE, TAB_MEDIA_STATE_CAPTURING,
264 TAB_MEDIA_STATE_AUDIO_PLAYING, TAB_MEDIA_STATE_AUDIO_MUTING
265 };
266
267 FakeTabController controller;
268 Tab tab(&controller);
269
270 SkBitmap bitmap;
271 bitmap.allocN32Pixels(16, 16);
272 TabRendererData data;
273 data.favicon = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
274
275 // Perform layout over all possible combinations, checking for correct
276 // results.
277 for (int is_mini_tab = 0; is_mini_tab < 2; ++is_mini_tab) {
278 for (int is_active_tab = 0; is_active_tab < 2; ++is_active_tab) {
279 for (size_t media_state_index = 0;
280 media_state_index < arraysize(kMediaStatesToTest);
281 ++media_state_index) {
282 const TabMediaState media_state = kMediaStatesToTest[media_state_index];
283 SCOPED_TRACE(::testing::Message()
284 << (is_active_tab ? "Active" : "Inactive") << ' '
285 << (is_mini_tab ? "Mini " : "")
286 << "Tab with media indicator state " << media_state);
287
288 data.mini = !!is_mini_tab;
289 controller.set_active_tab(!!is_active_tab);
290 data.media_state = media_state;
291 tab.SetData(data);
292
293 // Test layout for every width from standard to minimum.
294 gfx::Rect bounds(gfx::Point(0, 0), Tab::GetStandardSize());
295 int min_width;
296 if (is_mini_tab) {
297 bounds.set_width(Tab::GetMiniWidth());
298 min_width = Tab::GetMiniWidth();
299 } else {
300 min_width = is_active_tab ? Tab::GetMinimumSelectedSize().width() :
301 Tab::GetMinimumUnselectedSize().width();
302 }
303 while (bounds.width() >= min_width) {
304 SCOPED_TRACE(::testing::Message() << "bounds=" << bounds.ToString());
305 tab.SetBoundsRect(bounds); // Invokes Tab::Layout().
306 CheckForExpectedLayoutAndVisibilityOfElements(tab);
307 bounds.set_width(bounds.width() - 1);
308 }
309 }
310 }
311 }
312 }
313
314 // Regression test for http://crbug.com/226253. Calling Layout() more than once
315 // shouldn't change the insets of the close button.
TEST_P(TabTest,CloseButtonLayout)316 TEST_P(TabTest, CloseButtonLayout) {
317 if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
318 LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
319 return;
320 }
321
322 FakeTabController tab_controller;
323 Tab tab(&tab_controller);
324 tab.SetBounds(0, 0, 100, 50);
325 tab.Layout();
326 gfx::Insets close_button_insets = tab.close_button_->GetInsets();
327 tab.Layout();
328 gfx::Insets close_button_insets_2 = tab.close_button_->GetInsets();
329 EXPECT_EQ(close_button_insets.top(), close_button_insets_2.top());
330 EXPECT_EQ(close_button_insets.left(), close_button_insets_2.left());
331 EXPECT_EQ(close_button_insets.bottom(), close_button_insets_2.bottom());
332 EXPECT_EQ(close_button_insets.right(), close_button_insets_2.right());
333
334 // Also make sure the close button is sized as large as the tab.
335 EXPECT_EQ(50, tab.close_button_->bounds().height());
336 }
337
338 // Test in both a LTR and a RTL locale. Note: The fact that the UI code is
339 // configured for an RTL locale does *not* change how the coordinates are
340 // examined in the tests above because views::View and friends are supposed to
341 // auto-mirror the widgets when painting. Thus, what we're testing here is that
342 // there's no code in Tab that will erroneously subvert this automatic
343 // coordinate translation. http://crbug.com/384179
344 INSTANTIATE_TEST_CASE_P(, TabTest, ::testing::Values(false, true));
345