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/i18n/rtl.h"
      8 #include "base/strings/utf_string_conversions.h"
      9 #include "chrome/browser/ui/views/tabs/media_indicator_button.h"
     10 #include "chrome/browser/ui/views/tabs/tab_controller.h"
     11 #include "testing/gtest/include/gtest/gtest.h"
     12 #include "ui/base/models/list_selection_model.h"
     13 #include "ui/views/controls/button/image_button.h"
     14 #include "ui/views/controls/label.h"
     15 #include "ui/views/test/views_test_base.h"
     16 #include "ui/views/widget/widget.h"
     17 
     18 using views::Widget;
     19 
     20 class FakeTabController : public TabController {
     21  public:
     22   FakeTabController() : immersive_style_(false), active_tab_(false) {
     23   }
     24   virtual ~FakeTabController() {}
     25 
     26   void set_immersive_style(bool value) { immersive_style_ = value; }
     27   void set_active_tab(bool value) { active_tab_ = value; }
     28 
     29   virtual const ui::ListSelectionModel& GetSelectionModel() OVERRIDE {
     30     return selection_model_;
     31   }
     32   virtual bool SupportsMultipleSelection() OVERRIDE { return false; }
     33   virtual void SelectTab(Tab* tab) OVERRIDE {}
     34   virtual void ExtendSelectionTo(Tab* tab) OVERRIDE {}
     35   virtual void ToggleSelected(Tab* tab) OVERRIDE {}
     36   virtual void AddSelectionFromAnchorTo(Tab* tab) OVERRIDE {}
     37   virtual void CloseTab(Tab* tab, CloseTabSource source) OVERRIDE {}
     38   virtual void ToggleTabAudioMute(Tab* tab) OVERRIDE {}
     39   virtual void ShowContextMenuForTab(Tab* tab,
     40                                      const gfx::Point& p,
     41                                      ui::MenuSourceType source_type) OVERRIDE {}
     42   virtual bool IsActiveTab(const Tab* tab) const OVERRIDE {
     43     return active_tab_;
     44   }
     45   virtual bool IsTabSelected(const Tab* tab) const OVERRIDE {
     46     return false;
     47   }
     48   virtual bool IsTabPinned(const Tab* tab) const OVERRIDE { return false; }
     49   virtual void MaybeStartDrag(
     50       Tab* tab,
     51       const ui::LocatedEvent& event,
     52       const ui::ListSelectionModel& original_selection) OVERRIDE {}
     53   virtual void ContinueDrag(views::View* view,
     54                             const ui::LocatedEvent& event) OVERRIDE {}
     55   virtual bool EndDrag(EndDragReason reason) OVERRIDE { return false; }
     56   virtual Tab* GetTabAt(Tab* tab,
     57                         const gfx::Point& tab_in_tab_coordinates) OVERRIDE {
     58     return NULL;
     59   }
     60   virtual void OnMouseEventInTab(views::View* source,
     61                                  const ui::MouseEvent& event) OVERRIDE {}
     62   virtual bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) OVERRIDE {
     63     return true;
     64   }
     65   virtual bool IsImmersiveStyle() const OVERRIDE { return immersive_style_; }
     66   virtual void UpdateTabAccessibilityState(const Tab* tab,
     67                                            ui::AXViewState* state) OVERRIDE{};
     68 
     69  private:
     70   ui::ListSelectionModel selection_model_;
     71   bool immersive_style_;
     72   bool active_tab_;
     73 
     74   DISALLOW_COPY_AND_ASSIGN(FakeTabController);
     75 };
     76 
     77 class TabTest : public views::ViewsTestBase,
     78                 public ::testing::WithParamInterface<bool> {
     79  public:
     80   TabTest() {}
     81   virtual ~TabTest() {}
     82 
     83   bool testing_for_rtl_locale() const { return GetParam(); }
     84 
     85   virtual void SetUp() OVERRIDE {
     86     if (testing_for_rtl_locale()) {
     87       original_locale_ = base::i18n::GetConfiguredLocale();
     88       base::i18n::SetICUDefaultLocale("he");
     89     }
     90     views::ViewsTestBase::SetUp();
     91   }
     92 
     93   virtual void TearDown() OVERRIDE {
     94     views::ViewsTestBase::TearDown();
     95     if (testing_for_rtl_locale())
     96       base::i18n::SetICUDefaultLocale(original_locale_);
     97   }
     98 
     99   static void CheckForExpectedLayoutAndVisibilityOfElements(const Tab& tab) {
    100     // Check whether elements are visible when they are supposed to be, given
    101     // Tab size and TabRendererData state.
    102     if (tab.data_.mini) {
    103       EXPECT_EQ(1, tab.IconCapacity());
    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       EXPECT_FALSE(tab.ShouldShowCloseBox());
    112     } else if (tab.IsActive()) {
    113       EXPECT_TRUE(tab.ShouldShowCloseBox());
    114       switch (tab.IconCapacity()) {
    115         case 0:
    116         case 1:
    117           EXPECT_FALSE(tab.ShouldShowIcon());
    118           EXPECT_FALSE(tab.ShouldShowMediaIndicator());
    119           break;
    120         case 2:
    121           if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
    122             EXPECT_FALSE(tab.ShouldShowIcon());
    123             EXPECT_TRUE(tab.ShouldShowMediaIndicator());
    124           } else {
    125             EXPECT_TRUE(tab.ShouldShowIcon());
    126             EXPECT_FALSE(tab.ShouldShowMediaIndicator());
    127           }
    128           break;
    129         default:
    130           EXPECT_LE(3, tab.IconCapacity());
    131           EXPECT_TRUE(tab.ShouldShowIcon());
    132           if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
    133             EXPECT_TRUE(tab.ShouldShowMediaIndicator());
    134           else
    135             EXPECT_FALSE(tab.ShouldShowMediaIndicator());
    136           break;
    137       }
    138     } else {  // Tab not active and not mini tab.
    139       switch (tab.IconCapacity()) {
    140         case 0:
    141           EXPECT_FALSE(tab.ShouldShowCloseBox());
    142           EXPECT_FALSE(tab.ShouldShowIcon());
    143           EXPECT_FALSE(tab.ShouldShowMediaIndicator());
    144           break;
    145         case 1:
    146           EXPECT_FALSE(tab.ShouldShowCloseBox());
    147           if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
    148             EXPECT_FALSE(tab.ShouldShowIcon());
    149             EXPECT_TRUE(tab.ShouldShowMediaIndicator());
    150           } else {
    151             EXPECT_TRUE(tab.ShouldShowIcon());
    152             EXPECT_FALSE(tab.ShouldShowMediaIndicator());
    153           }
    154           break;
    155         default:
    156           EXPECT_LE(2, tab.IconCapacity());
    157           EXPECT_TRUE(tab.ShouldShowIcon());
    158           if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
    159             EXPECT_TRUE(tab.ShouldShowMediaIndicator());
    160           else
    161             EXPECT_FALSE(tab.ShouldShowMediaIndicator());
    162           break;
    163       }
    164     }
    165 
    166     // Check positioning of elements with respect to each other, and that they
    167     // are fully within the contents bounds.
    168     const gfx::Rect contents_bounds = tab.GetContentsBounds();
    169     if (tab.ShouldShowIcon()) {
    170       EXPECT_LE(contents_bounds.x(), tab.favicon_bounds_.x());
    171       if (tab.title_->width() > 0)
    172         EXPECT_LE(tab.favicon_bounds_.right(), tab.title_->x());
    173       EXPECT_LE(contents_bounds.y(), tab.favicon_bounds_.y());
    174       EXPECT_LE(tab.favicon_bounds_.bottom(), contents_bounds.bottom());
    175     }
    176     if (tab.ShouldShowIcon() && tab.ShouldShowMediaIndicator())
    177       EXPECT_LE(tab.favicon_bounds_.right(), GetMediaIndicatorBounds(tab).x());
    178     if (tab.ShouldShowMediaIndicator()) {
    179       if (tab.title_->width() > 0) {
    180         EXPECT_LE(tab.title_->bounds().right(),
    181                   GetMediaIndicatorBounds(tab).x());
    182       }
    183       EXPECT_LE(GetMediaIndicatorBounds(tab).right(), contents_bounds.right());
    184       EXPECT_LE(contents_bounds.y(), GetMediaIndicatorBounds(tab).y());
    185       EXPECT_LE(GetMediaIndicatorBounds(tab).bottom(),
    186                 contents_bounds.bottom());
    187     }
    188     if (tab.ShouldShowMediaIndicator() && tab.ShouldShowCloseBox()) {
    189       // Note: The media indicator can overlap the left-insets of the close box,
    190       // but should otherwise be to the left of the close button.
    191       EXPECT_LE(GetMediaIndicatorBounds(tab).right(),
    192                 tab.close_button_->bounds().x() +
    193                     tab.close_button_->GetInsets().left());
    194     }
    195     if (tab.ShouldShowCloseBox()) {
    196       // Note: The title bounds can overlap the left-insets of the close box,
    197       // but should otherwise be to the left of the close button.
    198       if (tab.title_->width() > 0) {
    199         EXPECT_LE(tab.title_->bounds().right(),
    200                   tab.close_button_->bounds().x() +
    201                       tab.close_button_->GetInsets().left());
    202       }
    203       EXPECT_LE(tab.close_button_->bounds().right(), contents_bounds.right());
    204       EXPECT_LE(contents_bounds.y(), tab.close_button_->bounds().y());
    205       EXPECT_LE(tab.close_button_->bounds().bottom(), contents_bounds.bottom());
    206     }
    207   }
    208 
    209  private:
    210   static gfx::Rect GetMediaIndicatorBounds(const Tab& tab) {
    211     if (!tab.media_indicator_button_) {
    212       ADD_FAILURE();
    213       return gfx::Rect();
    214     }
    215     return tab.media_indicator_button_->bounds();
    216   }
    217 
    218   std::string original_locale_;
    219 };
    220 
    221 TEST_P(TabTest, HitTestTopPixel) {
    222   if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
    223     LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
    224     return;
    225   }
    226 
    227   Widget widget;
    228   Widget::InitParams params(CreateParams(Widget::InitParams::TYPE_WINDOW));
    229   params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    230   params.bounds.SetRect(10, 20, 300, 400);
    231   widget.Init(params);
    232 
    233   FakeTabController tab_controller;
    234   Tab tab(&tab_controller);
    235   widget.GetContentsView()->AddChildView(&tab);
    236   tab.SetBoundsRect(gfx::Rect(gfx::Point(0, 0), Tab::GetStandardSize()));
    237 
    238   // Tabs have some shadow in the top, so by default we don't hit the tab there.
    239   int middle_x = tab.width() / 2;
    240   EXPECT_FALSE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
    241 
    242   // Tabs are slanted, so a click halfway down the left edge won't hit it.
    243   int middle_y = tab.height() / 2;
    244   EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, middle_y)));
    245 
    246   // If the window is maximized, however, we want clicks in the top edge to
    247   // select the tab.
    248   widget.Maximize();
    249   EXPECT_TRUE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
    250 
    251   // But clicks in the area above the slanted sides should still miss.
    252   EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, 0)));
    253   EXPECT_FALSE(tab.HitTestPoint(gfx::Point(tab.width() - 1, 0)));
    254 }
    255 
    256 TEST_P(TabTest, LayoutAndVisibilityOfElements) {
    257   if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
    258     LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
    259     return;
    260   }
    261 
    262   static const TabMediaState kMediaStatesToTest[] = {
    263     TAB_MEDIA_STATE_NONE, TAB_MEDIA_STATE_CAPTURING,
    264     TAB_MEDIA_STATE_AUDIO_PLAYING, TAB_MEDIA_STATE_AUDIO_MUTING
    265   };
    266 
    267   FakeTabController controller;
    268   Tab tab(&controller);
    269 
    270   SkBitmap bitmap;
    271   bitmap.allocN32Pixels(16, 16);
    272   TabRendererData data;
    273   data.favicon = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
    274 
    275   // Perform layout over all possible combinations, checking for correct
    276   // results.
    277   for (int is_mini_tab = 0; is_mini_tab < 2; ++is_mini_tab) {
    278     for (int is_active_tab = 0; is_active_tab < 2; ++is_active_tab) {
    279       for (size_t media_state_index = 0;
    280            media_state_index < arraysize(kMediaStatesToTest);
    281            ++media_state_index) {
    282         const TabMediaState media_state = kMediaStatesToTest[media_state_index];
    283         SCOPED_TRACE(::testing::Message()
    284                      << (is_active_tab ? "Active" : "Inactive") << ' '
    285                      << (is_mini_tab ? "Mini " : "")
    286                      << "Tab with media indicator state " << media_state);
    287 
    288         data.mini = !!is_mini_tab;
    289         controller.set_active_tab(!!is_active_tab);
    290         data.media_state = media_state;
    291         tab.SetData(data);
    292 
    293         // Test layout for every width from standard to minimum.
    294         gfx::Rect bounds(gfx::Point(0, 0), Tab::GetStandardSize());
    295         int min_width;
    296         if (is_mini_tab) {
    297           bounds.set_width(Tab::GetMiniWidth());
    298           min_width = Tab::GetMiniWidth();
    299         } else {
    300           min_width = is_active_tab ? Tab::GetMinimumSelectedSize().width() :
    301               Tab::GetMinimumUnselectedSize().width();
    302         }
    303         while (bounds.width() >= min_width) {
    304           SCOPED_TRACE(::testing::Message() << "bounds=" << bounds.ToString());
    305           tab.SetBoundsRect(bounds);  // Invokes Tab::Layout().
    306           CheckForExpectedLayoutAndVisibilityOfElements(tab);
    307           bounds.set_width(bounds.width() - 1);
    308         }
    309       }
    310     }
    311   }
    312 }
    313 
    314 // Regression test for http://crbug.com/226253. Calling Layout() more than once
    315 // shouldn't change the insets of the close button.
    316 TEST_P(TabTest, CloseButtonLayout) {
    317   if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
    318     LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
    319     return;
    320   }
    321 
    322   FakeTabController tab_controller;
    323   Tab tab(&tab_controller);
    324   tab.SetBounds(0, 0, 100, 50);
    325   tab.Layout();
    326   gfx::Insets close_button_insets = tab.close_button_->GetInsets();
    327   tab.Layout();
    328   gfx::Insets close_button_insets_2 = tab.close_button_->GetInsets();
    329   EXPECT_EQ(close_button_insets.top(), close_button_insets_2.top());
    330   EXPECT_EQ(close_button_insets.left(), close_button_insets_2.left());
    331   EXPECT_EQ(close_button_insets.bottom(), close_button_insets_2.bottom());
    332   EXPECT_EQ(close_button_insets.right(), close_button_insets_2.right());
    333 
    334   // Also make sure the close button is sized as large as the tab.
    335   EXPECT_EQ(50, tab.close_button_->bounds().height());
    336 }
    337 
    338 // Test in both a LTR and a RTL locale.  Note: The fact that the UI code is
    339 // configured for an RTL locale does *not* change how the coordinates are
    340 // examined in the tests above because views::View and friends are supposed to
    341 // auto-mirror the widgets when painting.  Thus, what we're testing here is that
    342 // there's no code in Tab that will erroneously subvert this automatic
    343 // coordinate translation.  http://crbug.com/384179
    344 INSTANTIATE_TEST_CASE_P(, TabTest, ::testing::Values(false, true));
    345