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/views/tabs/tab.h"
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"
20 class FakeTabController : public TabController {
22 FakeTabController() : immersive_style_(false), active_tab_(false) {
24 virtual ~FakeTabController() {}
26 void set_immersive_style(bool value) { immersive_style_ = value; }
27 void set_active_tab(bool value) { active_tab_ = value; }
29 virtual const ui::ListSelectionModel& GetSelectionModel() OVERRIDE {
30 return selection_model_;
32 virtual bool SupportsMultipleSelection() OVERRIDE { return false; }
33 virtual void SelectTab(Tab* tab) OVERRIDE {}
34 virtual void ExtendSelectionTo(Tab* tab) OVERRIDE {}
35 virtual void ToggleSelected(Tab* tab) OVERRIDE {}
36 virtual void AddSelectionFromAnchorTo(Tab* tab) OVERRIDE {}
37 virtual void CloseTab(Tab* tab, CloseTabSource source) OVERRIDE {}
38 virtual void ToggleTabAudioMute(Tab* tab) OVERRIDE {}
39 virtual void ShowContextMenuForTab(Tab* tab,
41 ui::MenuSourceType source_type) OVERRIDE {}
42 virtual bool IsActiveTab(const Tab* tab) const OVERRIDE {
45 virtual bool IsTabSelected(const Tab* tab) const OVERRIDE {
48 virtual bool IsTabPinned(const Tab* tab) const OVERRIDE { return false; }
49 virtual void MaybeStartDrag(
51 const ui::LocatedEvent& event,
52 const ui::ListSelectionModel& original_selection) OVERRIDE {}
53 virtual void ContinueDrag(views::View* view,
54 const ui::LocatedEvent& event) OVERRIDE {}
55 virtual bool EndDrag(EndDragReason reason) OVERRIDE { return false; }
56 virtual Tab* GetTabAt(Tab* tab,
57 const gfx::Point& tab_in_tab_coordinates) OVERRIDE {
60 virtual void OnMouseEventInTab(views::View* source,
61 const ui::MouseEvent& event) OVERRIDE {}
62 virtual bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) OVERRIDE {
65 virtual bool IsImmersiveStyle() const OVERRIDE { return immersive_style_; }
66 virtual void UpdateTabAccessibilityState(const Tab* tab,
67 ui::AXViewState* state) OVERRIDE{};
70 ui::ListSelectionModel selection_model_;
71 bool immersive_style_;
74 DISALLOW_COPY_AND_ASSIGN(FakeTabController);
77 class TabTest : public views::ViewsTestBase,
78 public ::testing::WithParamInterface<bool> {
83 bool testing_for_rtl_locale() const { return GetParam(); }
85 virtual void SetUp() OVERRIDE {
86 if (testing_for_rtl_locale()) {
87 original_locale_ = base::i18n::GetConfiguredLocale();
88 base::i18n::SetICUDefaultLocale("he");
90 views::ViewsTestBase::SetUp();
93 virtual void TearDown() OVERRIDE {
94 views::ViewsTestBase::TearDown();
95 if (testing_for_rtl_locale())
96 base::i18n::SetICUDefaultLocale(original_locale_);
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());
108 EXPECT_TRUE(tab.ShouldShowIcon());
109 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
111 EXPECT_FALSE(tab.ShouldShowCloseBox());
112 } else if (tab.IsActive()) {
113 EXPECT_TRUE(tab.ShouldShowCloseBox());
114 switch (tab.IconCapacity()) {
117 EXPECT_FALSE(tab.ShouldShowIcon());
118 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
121 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
122 EXPECT_FALSE(tab.ShouldShowIcon());
123 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
125 EXPECT_TRUE(tab.ShouldShowIcon());
126 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
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());
135 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
138 } else { // Tab not active and not mini tab.
139 switch (tab.IconCapacity()) {
141 EXPECT_FALSE(tab.ShouldShowCloseBox());
142 EXPECT_FALSE(tab.ShouldShowIcon());
143 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
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());
151 EXPECT_TRUE(tab.ShouldShowIcon());
152 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
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());
161 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
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());
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());
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());
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());
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());
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());
210 static gfx::Rect GetMediaIndicatorBounds(const Tab& tab) {
211 if (!tab.media_indicator_button_) {
215 return tab.media_indicator_button_->bounds();
218 std::string original_locale_;
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.";
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);
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()));
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)));
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)));
246 // If the window is maximized, however, we want clicks in the top edge to
249 EXPECT_TRUE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
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)));
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.";
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
267 FakeTabController controller;
268 Tab tab(&controller);
271 bitmap.allocN32Pixels(16, 16);
272 TabRendererData data;
273 data.favicon = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
275 // Perform layout over all possible combinations, checking for correct
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);
288 data.mini = !!is_mini_tab;
289 controller.set_active_tab(!!is_active_tab);
290 data.media_state = media_state;
293 // Test layout for every width from standard to minimum.
294 gfx::Rect bounds(gfx::Point(0, 0), Tab::GetStandardSize());
297 bounds.set_width(Tab::GetMiniWidth());
298 min_width = Tab::GetMiniWidth();
300 min_width = is_active_tab ? Tab::GetMinimumSelectedSize().width() :
301 Tab::GetMinimumUnselectedSize().width();
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);
314 // Regression test for http://crbug.com/226253. Calling Layout() more than once
315 // shouldn't change the insets of the close button.
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.";
322 FakeTabController tab_controller;
323 Tab tab(&tab_controller);
324 tab.SetBounds(0, 0, 100, 50);
326 gfx::Insets close_button_insets = tab.close_button_->GetInsets();
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());
334 // Also make sure the close button is sized as large as the tab.
335 EXPECT_EQ(50, tab.close_button_->bounds().height());
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));