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 
      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