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