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 #include "chrome/browser/ui/views/tabs/tab_controller.h" 7 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" 14 15 using views::Widget; 16 17 class FakeTabController : public TabController { 18 public: 19 FakeTabController() : immersive_style_(false), active_tab_(false) { 20 } 21 virtual ~FakeTabController() {} 22 23 void set_immersive_style(bool value) { immersive_style_ = value; } 24 void set_active_tab(bool value) { active_tab_ = value; } 25 26 virtual const ui::ListSelectionModel& GetSelectionModel() OVERRIDE { 27 return selection_model_; 28 } 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, 36 const gfx::Point& p, 37 ui::MenuSourceType source_type) OVERRIDE {} 38 virtual bool IsActiveTab(const Tab* tab) const OVERRIDE { 39 return active_tab_; 40 } 41 virtual bool IsTabSelected(const Tab* tab) const OVERRIDE { 42 return false; 43 } 44 virtual bool IsTabPinned(const Tab* tab) const OVERRIDE { return false; } 45 virtual void MaybeStartDrag( 46 Tab* tab, 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 { 54 return NULL; 55 } 56 virtual void OnMouseEventInTab(views::View* source, 57 const ui::MouseEvent& event) OVERRIDE {} 58 virtual bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) OVERRIDE { 59 return true; 60 } 61 virtual bool IsImmersiveStyle() const OVERRIDE { return immersive_style_; } 62 63 private: 64 ui::ListSelectionModel selection_model_; 65 bool immersive_style_; 66 bool active_tab_; 67 68 DISALLOW_COPY_AND_ASSIGN(FakeTabController); 69 }; 70 71 class TabTest : public views::ViewsTestBase { 72 public: 73 TabTest() {} 74 virtual ~TabTest() {} 75 76 static void DisableMediaIndicatorAnimation(Tab* tab) { 77 tab->media_indicator_animation_.reset(); 78 tab->animating_media_state_ = tab->data_.media_state; 79 } 80 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. 84 if (tab.data_.mini) { 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()); 89 } else { 90 EXPECT_TRUE(tab.ShouldShowIcon()); 91 EXPECT_FALSE(tab.ShouldShowMediaIndicator()); 92 } 93 EXPECT_FALSE(tab.ShouldShowCloseBox()); 94 } else if (tab.IsActive()) { 95 EXPECT_TRUE(tab.ShouldShowCloseBox()); 96 switch (tab.IconCapacity()) { 97 case 0: 98 case 1: 99 EXPECT_FALSE(tab.ShouldShowIcon()); 100 EXPECT_FALSE(tab.ShouldShowMediaIndicator()); 101 break; 102 case 2: 103 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) { 104 EXPECT_FALSE(tab.ShouldShowIcon()); 105 EXPECT_TRUE(tab.ShouldShowMediaIndicator()); 106 } else { 107 EXPECT_TRUE(tab.ShouldShowIcon()); 108 EXPECT_FALSE(tab.ShouldShowMediaIndicator()); 109 } 110 break; 111 default: 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()); 116 else 117 EXPECT_FALSE(tab.ShouldShowMediaIndicator()); 118 break; 119 } 120 } else { // Tab not active and not mini tab. 121 switch (tab.IconCapacity()) { 122 case 0: 123 EXPECT_FALSE(tab.ShouldShowCloseBox()); 124 EXPECT_FALSE(tab.ShouldShowIcon()); 125 EXPECT_FALSE(tab.ShouldShowMediaIndicator()); 126 break; 127 case 1: 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()); 132 } else { 133 EXPECT_TRUE(tab.ShouldShowIcon()); 134 EXPECT_FALSE(tab.ShouldShowMediaIndicator()); 135 } 136 break; 137 default: 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()); 142 else 143 EXPECT_FALSE(tab.ShouldShowMediaIndicator()); 144 break; 145 } 146 } 147 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()); 157 } 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()); 166 } 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()); 173 } 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()); 181 } 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()); 185 } 186 } 187 }; 188 189 TEST_F(TabTest, HitTestTopPixel) { 190 Widget widget; 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); 194 widget.Init(params); 195 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())); 200 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))); 204 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))); 208 209 // If the window is maximized, however, we want clicks in the top edge to 210 // select the tab. 211 widget.Maximize(); 212 EXPECT_TRUE(tab.HitTestPoint(gfx::Point(middle_x, 0))); 213 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))); 217 } 218 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 223 }; 224 225 FakeTabController controller; 226 Tab tab(&controller); 227 228 SkBitmap bitmap; 229 bitmap.setConfig(SkBitmap::kARGB_8888_Config, 16, 16); 230 bitmap.allocPixels(); 231 TabRendererData data; 232 data.favicon = gfx::ImageSkia::CreateFrom1xBitmap(bitmap); 233 234 // Perform layout over all possible combinations, checking for correct 235 // results. 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); 246 247 data.mini = !!is_mini_tab; 248 controller.set_active_tab(!!is_active_tab); 249 data.media_state = media_state; 250 tab.SetData(data); 251 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); 257 258 // Test layout for every width from standard to minimum. 259 gfx::Rect bounds(gfx::Point(0, 0), Tab::GetStandardSize()); 260 int min_width; 261 if (is_mini_tab) { 262 bounds.set_width(Tab::GetMiniWidth()); 263 min_width = Tab::GetMiniWidth(); 264 } else { 265 min_width = is_active_tab ? Tab::GetMinimumSelectedSize().width() : 266 Tab::GetMinimumUnselectedSize().width(); 267 } 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); 273 } 274 } 275 } 276 } 277 } 278 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); 285 tab.Layout(); 286 gfx::Insets close_button_insets = tab.close_button_->GetInsets(); 287 tab.Layout(); 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()); 293 294 // Also make sure the close button is sized as large as the tab. 295 EXPECT_EQ(50, tab.close_button_->bounds().height()); 296 } 297