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: 20 FakeTabController() : immersive_style_(false), active_tab_(false) { 21 } 22 virtual ~FakeTabController() {} 23 24 void set_immersive_style(bool value) { immersive_style_ = value; } 25 void set_active_tab(bool value) { active_tab_ = value; } 26 27 virtual const ui::ListSelectionModel& GetSelectionModel() OVERRIDE { 28 return selection_model_; 29 } 30 virtual bool SupportsMultipleSelection() OVERRIDE { return false; } 31 virtual void SelectTab(Tab* tab) OVERRIDE {} 32 virtual void ExtendSelectionTo(Tab* tab) OVERRIDE {} 33 virtual void ToggleSelected(Tab* tab) OVERRIDE {} 34 virtual void AddSelectionFromAnchorTo(Tab* tab) OVERRIDE {} 35 virtual void CloseTab(Tab* tab, CloseTabSource source) OVERRIDE {} 36 virtual void ShowContextMenuForTab(Tab* tab, 37 const gfx::Point& p, 38 ui::MenuSourceType source_type) OVERRIDE {} 39 virtual bool IsActiveTab(const Tab* tab) const OVERRIDE { 40 return active_tab_; 41 } 42 virtual bool IsTabSelected(const Tab* tab) const OVERRIDE { 43 return false; 44 } 45 virtual bool IsTabPinned(const Tab* tab) const OVERRIDE { return false; } 46 virtual void MaybeStartDrag( 47 Tab* tab, 48 const ui::LocatedEvent& event, 49 const ui::ListSelectionModel& original_selection) OVERRIDE {} 50 virtual void ContinueDrag(views::View* view, 51 const ui::LocatedEvent& event) OVERRIDE {} 52 virtual bool EndDrag(EndDragReason reason) OVERRIDE { return false; } 53 virtual Tab* GetTabAt(Tab* tab, 54 const gfx::Point& tab_in_tab_coordinates) OVERRIDE { 55 return NULL; 56 } 57 virtual void OnMouseEventInTab(views::View* source, 58 const ui::MouseEvent& event) OVERRIDE {} 59 virtual bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) OVERRIDE { 60 return true; 61 } 62 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: 74 TabTest() {} 75 virtual ~TabTest() {} 76 77 static void DisableMediaIndicatorAnimation(Tab* tab) { 78 tab->media_indicator_animation_.reset(); 79 tab->animating_media_state_ = tab->data_.media_state; 80 } 81 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 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 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. 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