• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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