Home | History | Annotate | Download | only in tabs
      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