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_strip.h"
      6 
      7 #include "base/message_loop/message_loop.h"
      8 #include "chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h"
      9 #include "chrome/browser/ui/views/tabs/tab.h"
     10 #include "chrome/browser/ui/views/tabs/tab_strip.h"
     11 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
     12 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
     13 #include "chrome/test/base/testing_profile.h"
     14 #include "testing/gtest/include/gtest/gtest.h"
     15 #include "ui/gfx/path.h"
     16 #include "ui/gfx/rect_conversions.h"
     17 #include "ui/gfx/skia_util.h"
     18 #include "ui/views/test/views_test_base.h"
     19 #include "ui/views/view.h"
     20 #include "ui/views/view_targeter.h"
     21 #include "ui/views/widget/widget.h"
     22 
     23 namespace {
     24 
     25 // Walks up the views hierarchy until it finds a tab view. It returns the
     26 // found tab view, on NULL if none is found.
     27 views::View* FindTabView(views::View* view) {
     28   views::View* current = view;
     29   while (current && strcmp(current->GetClassName(), Tab::kViewClassName)) {
     30     current = current->parent();
     31   }
     32   return current;
     33 }
     34 
     35 }  // namespace
     36 
     37 class TestTabStripObserver : public TabStripObserver {
     38  public:
     39   explicit TestTabStripObserver(TabStrip* tab_strip)
     40       : tab_strip_(tab_strip),
     41         last_tab_added_(-1),
     42         last_tab_removed_(-1),
     43         last_tab_moved_from_(-1),
     44         last_tab_moved_to_(-1),
     45         tabstrip_deleted_(false) {
     46     tab_strip_->AddObserver(this);
     47   }
     48 
     49   virtual ~TestTabStripObserver() {
     50     if (tab_strip_)
     51       tab_strip_->RemoveObserver(this);
     52   }
     53 
     54   int last_tab_added() const { return last_tab_added_; }
     55   int last_tab_removed() const { return last_tab_removed_; }
     56   int last_tab_moved_from() const { return last_tab_moved_from_; }
     57   int last_tab_moved_to() const { return last_tab_moved_to_; }
     58   bool tabstrip_deleted() const { return tabstrip_deleted_; }
     59 
     60  private:
     61   // TabStripObserver overrides.
     62   virtual void TabStripAddedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
     63     last_tab_added_ = index;
     64   }
     65 
     66   virtual void TabStripMovedTab(TabStrip* tab_strip,
     67                                 int from_index,
     68                                 int to_index) OVERRIDE {
     69     last_tab_moved_from_ = from_index;
     70     last_tab_moved_to_ = to_index;
     71   }
     72 
     73   virtual void TabStripRemovedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
     74     last_tab_removed_ = index;
     75   }
     76 
     77   virtual void TabStripDeleted(TabStrip* tab_strip) OVERRIDE {
     78     tabstrip_deleted_ = true;
     79     tab_strip_ = NULL;
     80   }
     81 
     82   TabStrip* tab_strip_;
     83   int last_tab_added_;
     84   int last_tab_removed_;
     85   int last_tab_moved_from_;
     86   int last_tab_moved_to_;
     87   bool tabstrip_deleted_;
     88 
     89   DISALLOW_COPY_AND_ASSIGN(TestTabStripObserver);
     90 };
     91 
     92 class TabStripTest : public views::ViewsTestBase {
     93  public:
     94   TabStripTest()
     95       : controller_(NULL),
     96         tab_strip_(NULL) {
     97   }
     98 
     99   virtual ~TabStripTest() {}
    100 
    101   virtual void SetUp() OVERRIDE {
    102     views::ViewsTestBase::SetUp();
    103 
    104     controller_ = new FakeBaseTabStripController;
    105     tab_strip_ = new TabStrip(controller_);
    106     controller_->set_tab_strip(tab_strip_);
    107     // Do this to force TabStrip to create the buttons.
    108     parent_.AddChildView(tab_strip_);
    109     parent_.set_owned_by_client();
    110 
    111     widget_.reset(new views::Widget);
    112     views::Widget::InitParams init_params =
    113         CreateParams(views::Widget::InitParams::TYPE_POPUP);
    114     init_params.ownership =
    115         views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    116     init_params.bounds = gfx::Rect(0, 0, 200, 200);
    117     widget_->Init(init_params);
    118     widget_->SetContentsView(&parent_);
    119   }
    120 
    121   virtual void TearDown() OVERRIDE {
    122     widget_.reset();
    123     views::ViewsTestBase::TearDown();
    124   }
    125 
    126  protected:
    127   // Returns the rectangular hit test region of |tab| in |tab|'s local
    128   // coordinate space.
    129   gfx::Rect GetTabHitTestMask(Tab* tab) {
    130     views::ViewTargeter* targeter = tab->targeter();
    131     DCHECK(targeter);
    132     views::MaskedTargeterDelegate* delegate =
    133         static_cast<views::MaskedTargeterDelegate*>(tab);
    134 
    135     gfx::Path mask;
    136     bool valid_mask = delegate->GetHitTestMask(&mask);
    137     DCHECK(valid_mask);
    138 
    139     return gfx::ToEnclosingRect((gfx::SkRectToRectF(mask.getBounds())));
    140   }
    141 
    142   // Returns the rectangular hit test region of the tab close button of
    143   // |tab| in |tab|'s coordinate space (including padding if |padding|
    144   // is true).
    145   gfx::Rect GetTabCloseHitTestMask(Tab* tab, bool padding) {
    146     gfx::RectF bounds_f = tab->close_button_->GetContentsBounds();
    147     if (padding)
    148       bounds_f = tab->close_button_->GetLocalBounds();
    149     views::View::ConvertRectToTarget(tab->close_button_, tab, &bounds_f);
    150     return gfx::ToEnclosingRect(bounds_f);
    151   }
    152 
    153   // Checks whether |tab| contains |point_in_tabstrip_coords|, where the point
    154   // is in |tab_strip_| coordinates.
    155   bool IsPointInTab(Tab* tab, const gfx::Point& point_in_tabstrip_coords) {
    156     gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
    157     views::View::ConvertPointToTarget(tab_strip_, tab, &point_in_tab_coords);
    158     return tab->HitTestPoint(point_in_tab_coords);
    159   }
    160 
    161   // Owned by TabStrip.
    162   FakeBaseTabStripController* controller_;
    163   // Owns |tab_strip_|.
    164   views::View parent_;
    165   TabStrip* tab_strip_;
    166   scoped_ptr<views::Widget> widget_;
    167 
    168  private:
    169   DISALLOW_COPY_AND_ASSIGN(TabStripTest);
    170 };
    171 
    172 TEST_F(TabStripTest, GetModelCount) {
    173   EXPECT_EQ(0, tab_strip_->GetModelCount());
    174 }
    175 
    176 TEST_F(TabStripTest, IsValidModelIndex) {
    177   EXPECT_FALSE(tab_strip_->IsValidModelIndex(0));
    178 }
    179 
    180 TEST_F(TabStripTest, tab_count) {
    181   EXPECT_EQ(0, tab_strip_->tab_count());
    182 }
    183 
    184 TEST_F(TabStripTest, AddTabAt) {
    185   TestTabStripObserver observer(tab_strip_);
    186   tab_strip_->AddTabAt(0, TabRendererData(), false);
    187   ASSERT_EQ(1, tab_strip_->tab_count());
    188   EXPECT_EQ(0, observer.last_tab_added());
    189   Tab* tab = tab_strip_->tab_at(0);
    190   EXPECT_FALSE(tab == NULL);
    191 }
    192 
    193 // Confirms that TabStripObserver::TabStripDeleted() is sent.
    194 TEST_F(TabStripTest, TabStripDeleted) {
    195   FakeBaseTabStripController* controller = new FakeBaseTabStripController;
    196   TabStrip* tab_strip = new TabStrip(controller);
    197   controller->set_tab_strip(tab_strip);
    198   TestTabStripObserver observer(tab_strip);
    199   delete tab_strip;
    200   EXPECT_TRUE(observer.tabstrip_deleted());
    201 }
    202 
    203 TEST_F(TabStripTest, MoveTab) {
    204   TestTabStripObserver observer(tab_strip_);
    205   tab_strip_->AddTabAt(0, TabRendererData(), false);
    206   tab_strip_->AddTabAt(1, TabRendererData(), false);
    207   tab_strip_->AddTabAt(2, TabRendererData(), false);
    208   ASSERT_EQ(3, tab_strip_->tab_count());
    209   EXPECT_EQ(2, observer.last_tab_added());
    210   Tab* tab = tab_strip_->tab_at(0);
    211   tab_strip_->MoveTab(0, 1, TabRendererData());
    212   EXPECT_EQ(0, observer.last_tab_moved_from());
    213   EXPECT_EQ(1, observer.last_tab_moved_to());
    214   EXPECT_EQ(tab, tab_strip_->tab_at(1));
    215 }
    216 
    217 // Verifies child views are deleted after an animation completes.
    218 TEST_F(TabStripTest, RemoveTab) {
    219   TestTabStripObserver observer(tab_strip_);
    220   controller_->AddTab(0, false);
    221   controller_->AddTab(1, false);
    222   const int child_view_count = tab_strip_->child_count();
    223   EXPECT_EQ(2, tab_strip_->tab_count());
    224   controller_->RemoveTab(0);
    225   EXPECT_EQ(0, observer.last_tab_removed());
    226   // When removing a tab the tabcount should immediately decrement.
    227   EXPECT_EQ(1, tab_strip_->tab_count());
    228   // But the number of views should remain the same (it's animatining closed).
    229   EXPECT_EQ(child_view_count, tab_strip_->child_count());
    230   tab_strip_->SetBounds(0, 0, 200, 20);
    231   // Layout at a different size should force the animation to end and delete
    232   // the tab that was removed.
    233   tab_strip_->Layout();
    234   EXPECT_EQ(child_view_count - 1, tab_strip_->child_count());
    235 
    236   // Remove the last tab to make sure things are cleaned up correctly when
    237   // the TabStrip is destroyed and an animation is ongoing.
    238   controller_->RemoveTab(0);
    239   EXPECT_EQ(0, observer.last_tab_removed());
    240 }
    241 
    242 TEST_F(TabStripTest, VisibilityInOverflow) {
    243   tab_strip_->SetBounds(0, 0, 200, 20);
    244 
    245   // The first tab added to a reasonable-width strip should be visible.  If we
    246   // add enough additional tabs, eventually one should be invisible due to
    247   // overflow.
    248   int invisible_tab_index = 0;
    249   for (; invisible_tab_index < 100; ++invisible_tab_index) {
    250     controller_->AddTab(invisible_tab_index, false);
    251     if (!tab_strip_->tab_at(invisible_tab_index)->visible())
    252       break;
    253   }
    254   EXPECT_GT(invisible_tab_index, 0);
    255   EXPECT_LT(invisible_tab_index, 100);
    256 
    257   // The tabs before the invisible tab should still be visible.
    258   for (int i = 0; i < invisible_tab_index; ++i)
    259     EXPECT_TRUE(tab_strip_->tab_at(i)->visible());
    260 
    261   // Enlarging the strip should result in the last tab becoming visible.
    262   tab_strip_->SetBounds(0, 0, 400, 20);
    263   EXPECT_TRUE(tab_strip_->tab_at(invisible_tab_index)->visible());
    264 
    265   // Shrinking it again should re-hide the last tab.
    266   tab_strip_->SetBounds(0, 0, 200, 20);
    267   EXPECT_FALSE(tab_strip_->tab_at(invisible_tab_index)->visible());
    268 
    269   // Shrinking it still more should make more tabs invisible, though not all.
    270   // All the invisible tabs should be at the end of the strip.
    271   tab_strip_->SetBounds(0, 0, 100, 20);
    272   int i = 0;
    273   for (; i < invisible_tab_index; ++i) {
    274     if (!tab_strip_->tab_at(i)->visible())
    275       break;
    276   }
    277   ASSERT_GT(i, 0);
    278   EXPECT_LT(i, invisible_tab_index);
    279   invisible_tab_index = i;
    280   for (int i = invisible_tab_index + 1; i < tab_strip_->tab_count(); ++i)
    281     EXPECT_FALSE(tab_strip_->tab_at(i)->visible());
    282 
    283   // When we're already in overflow, adding tabs at the beginning or end of
    284   // the strip should not change how many tabs are visible.
    285   controller_->AddTab(tab_strip_->tab_count(), false);
    286   EXPECT_TRUE(tab_strip_->tab_at(invisible_tab_index - 1)->visible());
    287   EXPECT_FALSE(tab_strip_->tab_at(invisible_tab_index)->visible());
    288   controller_->AddTab(0, false);
    289   EXPECT_TRUE(tab_strip_->tab_at(invisible_tab_index - 1)->visible());
    290   EXPECT_FALSE(tab_strip_->tab_at(invisible_tab_index)->visible());
    291 
    292   // If we remove enough tabs, all the tabs should be visible.
    293   for (int i = tab_strip_->tab_count() - 1; i >= invisible_tab_index; --i)
    294     controller_->RemoveTab(i);
    295   EXPECT_TRUE(tab_strip_->tab_at(tab_strip_->tab_count() - 1)->visible());
    296 }
    297 
    298 TEST_F(TabStripTest, ImmersiveMode) {
    299   // Immersive mode defaults to off.
    300   EXPECT_FALSE(tab_strip_->IsImmersiveStyle());
    301 
    302   // Tab strip defaults to normal tab height.
    303   int normal_height = Tab::GetMinimumUnselectedSize().height();
    304   EXPECT_EQ(normal_height, tab_strip_->GetPreferredSize().height());
    305 
    306   // Tab strip can toggle immersive mode.
    307   tab_strip_->SetImmersiveStyle(true);
    308   EXPECT_TRUE(tab_strip_->IsImmersiveStyle());
    309 
    310   // Now tabs have the immersive height.
    311   int immersive_height = Tab::GetImmersiveHeight();
    312   EXPECT_EQ(immersive_height, tab_strip_->GetPreferredSize().height());
    313 
    314   // Sanity-check immersive tabs are shorter than normal tabs.
    315   EXPECT_LT(immersive_height, normal_height);
    316 }
    317 
    318 // Creates a tab strip in stacked layout mode and verifies the correctness
    319 // of hit tests against the visible/occluded regions of a tab and
    320 // visible/occluded tab close buttons.
    321 TEST_F(TabStripTest, TabHitTestMaskWhenStacked) {
    322   tab_strip_->SetBounds(0, 0, 300, 20);
    323 
    324   controller_->AddTab(0, false);
    325   controller_->AddTab(1, true);
    326   controller_->AddTab(2, false);
    327   controller_->AddTab(3, false);
    328   ASSERT_EQ(4, tab_strip_->tab_count());
    329 
    330   Tab* left_tab = tab_strip_->tab_at(0);
    331   left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
    332 
    333   Tab* active_tab = tab_strip_->tab_at(1);
    334   active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
    335   ASSERT_TRUE(active_tab->IsActive());
    336 
    337   Tab* right_tab = tab_strip_->tab_at(2);
    338   right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
    339 
    340   Tab* most_right_tab = tab_strip_->tab_at(3);
    341   most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
    342                                           gfx::Size(200, 20)));
    343 
    344   // Switch to stacked layout mode and force a layout to ensure tabs stack.
    345   tab_strip_->SetStackedLayout(true);
    346   tab_strip_->DoLayout();
    347 
    348 
    349   // Tests involving |left_tab|, which has part of its bounds and its tab
    350   // close button completely occluded by |active_tab|.
    351 
    352   // Bounds of the tab's hit test mask.
    353   gfx::Rect tab_bounds = GetTabHitTestMask(left_tab);
    354   EXPECT_EQ(gfx::Rect(6, 2, 61, 27).ToString(), tab_bounds.ToString());
    355 
    356   // Bounds of the tab close button (without padding) in the tab's
    357   // coordinate space.
    358   gfx::Rect contents_bounds = GetTabCloseHitTestMask(left_tab, false);
    359   // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
    360   //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
    361 
    362   // Verify that the tab close button is completely occluded.
    363   EXPECT_FALSE(tab_bounds.Contains(contents_bounds));
    364 
    365   // Hit tests in the non-occuluded region of the tab.
    366   EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(6, 2, 2, 2)));
    367   EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(6, 2, 1, 1)));
    368   EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(30, 15, 1, 1)));
    369   EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(30, 15, 25, 35)));
    370   EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(-10, -5, 20, 30)));
    371 
    372   // Hit tests in the occluded region of the tab.
    373   EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(70, 15, 2, 2)));
    374   EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(70, -15, 30, 40)));
    375   EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(87, 20, 5, 3)));
    376 
    377   // Hit tests completely outside of the tab.
    378   EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(-20, -25, 1, 1)));
    379   EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(-20, -25, 3, 19)));
    380 
    381   // All hit tests against the tab close button should fail because
    382   // it is occluded by |active_tab|.
    383   views::ImageButton* left_close = left_tab->close_button_;
    384   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
    385   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(1, 1, 5, 10)));
    386   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
    387   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 3, 4)));
    388 
    389 
    390   // Tests involving |active_tab|, which is completely visible.
    391 
    392   tab_bounds = GetTabHitTestMask(active_tab);
    393   EXPECT_EQ(gfx::Rect(6, 2, 108, 27).ToString(), tab_bounds.ToString());
    394   contents_bounds = GetTabCloseHitTestMask(active_tab, false);
    395   // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
    396   //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
    397 
    398   // Verify that the tab close button is not occluded.
    399   EXPECT_TRUE(tab_bounds.Contains(contents_bounds));
    400 
    401   // Bounds of the tab close button (without padding) in the tab's
    402   // coordinate space.
    403   gfx::Rect local_bounds = GetTabCloseHitTestMask(active_tab, true);
    404   EXPECT_EQ(gfx::Rect(81, 0, 39, 29).ToString(), local_bounds.ToString());
    405 
    406   // Hit tests within the tab.
    407   EXPECT_TRUE(active_tab->HitTestRect(gfx::Rect(30, 15, 1, 1)));
    408   EXPECT_TRUE(active_tab->HitTestRect(gfx::Rect(30, 15, 2, 2)));
    409 
    410   // Hit tests against the tab close button. Note that hit tests from either
    411   // mouse or touch should both fail if they are strictly contained within
    412   // the button's padding.
    413   views::ImageButton* active_close = active_tab->close_button_;
    414   EXPECT_FALSE(active_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
    415   EXPECT_FALSE(active_close->HitTestRect(gfx::Rect(1, 1, 2, 2)));
    416   EXPECT_TRUE(active_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
    417   EXPECT_TRUE(active_close->HitTestRect(gfx::Rect(10, 10, 25, 35)));
    418 
    419 
    420   // Tests involving |most_right_tab|, which has part of its bounds occluded
    421   // by |right_tab| but has its tab close button completely visible.
    422 
    423   tab_bounds = GetTabHitTestMask(most_right_tab);
    424   EXPECT_EQ(gfx::Rect(84, 2, 30, 27).ToString(), tab_bounds.ToString());
    425   contents_bounds = GetTabCloseHitTestMask(active_tab, false);
    426   // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
    427   //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
    428   local_bounds = GetTabCloseHitTestMask(active_tab, true);
    429   EXPECT_EQ(gfx::Rect(81, 0, 39, 29).ToString(), local_bounds.ToString());
    430 
    431   // Verify that the tab close button is not occluded.
    432   EXPECT_TRUE(tab_bounds.Contains(contents_bounds));
    433 
    434   // Hit tests in the occluded region of the tab.
    435   EXPECT_FALSE(most_right_tab->HitTestRect(gfx::Rect(20, 15, 1, 1)));
    436   EXPECT_FALSE(most_right_tab->HitTestRect(gfx::Rect(20, 15, 5, 6)));
    437 
    438   // Hit tests in the non-occluded region of the tab.
    439   EXPECT_TRUE(most_right_tab->HitTestRect(gfx::Rect(85, 15, 1, 1)));
    440   EXPECT_TRUE(most_right_tab->HitTestRect(gfx::Rect(85, 15, 2, 2)));
    441 
    442   // Hit tests against the tab close button. Note that hit tests from either
    443   // mouse or touch should both fail if they are strictly contained within
    444   // the button's padding.
    445   views::ImageButton* most_right_close = most_right_tab->close_button_;
    446   EXPECT_FALSE(most_right_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
    447   EXPECT_FALSE(most_right_close->HitTestRect(gfx::Rect(1, 1, 2, 2)));
    448   EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
    449   EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(10, 10, 25, 35)));
    450   EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(-10, 10, 25, 35)));
    451 }
    452 
    453 // Creates a tab strip in stacked layout mode and verifies the correctness
    454 // of hit tests against the visible/occluded region of a partially-occluded
    455 // tab close button.
    456 TEST_F(TabStripTest, ClippedTabCloseButton) {
    457   tab_strip_->SetBounds(0, 0, 220, 20);
    458 
    459   controller_->AddTab(0, false);
    460   controller_->AddTab(1, true);
    461   ASSERT_EQ(2, tab_strip_->tab_count());
    462 
    463   Tab* left_tab = tab_strip_->tab_at(0);
    464   left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
    465 
    466   Tab* active_tab = tab_strip_->tab_at(1);
    467   active_tab->SetBoundsRect(gfx::Rect(gfx::Point(180, 0), gfx::Size(200, 20)));
    468   ASSERT_TRUE(active_tab->IsActive());
    469 
    470   // Switch to stacked layout mode and force a layout to ensure tabs stack.
    471   tab_strip_->SetStackedLayout(true);
    472   tab_strip_->DoLayout();
    473 
    474 
    475   // Tests involving |left_tab|, which has part of its bounds and its tab
    476   // close button partially occluded by |active_tab|.
    477 
    478   // Bounds of the tab's hit test mask.
    479   gfx::Rect tab_bounds = GetTabHitTestMask(left_tab);
    480   EXPECT_EQ(gfx::Rect(6, 2, 91, 27).ToString(), tab_bounds.ToString());
    481 
    482   // Bounds of the tab close button (without padding) in the tab's
    483   // coordinate space.
    484   gfx::Rect contents_bounds = GetTabCloseHitTestMask(left_tab, false);
    485   // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
    486   //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
    487 
    488   // Verify that the tab close button is only partially occluded.
    489   EXPECT_FALSE(tab_bounds.Contains(contents_bounds));
    490   EXPECT_TRUE(tab_bounds.Intersects(contents_bounds));
    491 
    492   views::ImageButton* left_close = left_tab->close_button_;
    493 
    494   // Hit tests from mouse should return true if and only if the location
    495   // is within a visible region.
    496   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(2, 15, 1, 1)));
    497   EXPECT_TRUE(left_close->HitTestRect(gfx::Rect(3, 15, 1, 1)));
    498   EXPECT_TRUE(left_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
    499   EXPECT_TRUE(left_close->HitTestRect(gfx::Rect(15, 12, 1, 1)));
    500   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(16, 10, 1, 1)));
    501 
    502   // All hit tests from touch should return false because the button is
    503   // not fully visible.
    504   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(2, 15, 2, 2)));
    505   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(3, 15, 25, 25)));
    506   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 4, 5)));
    507   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(15, 12, 2, 2)));
    508   EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(16, 10, 20, 20)));
    509 }
    510 
    511 TEST_F(TabStripTest, GetEventHandlerForOverlappingArea) {
    512   tab_strip_->SetBounds(0, 0, 1000, 20);
    513 
    514   controller_->AddTab(0, false);
    515   controller_->AddTab(1, true);
    516   controller_->AddTab(2, false);
    517   controller_->AddTab(3, false);
    518   ASSERT_EQ(4, tab_strip_->tab_count());
    519 
    520   // Verify that the active tab will be a tooltip handler for points that hit
    521   // it.
    522   Tab* left_tab = tab_strip_->tab_at(0);
    523   left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
    524 
    525   Tab* active_tab = tab_strip_->tab_at(1);
    526   active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
    527   ASSERT_TRUE(active_tab->IsActive());
    528 
    529   Tab* right_tab = tab_strip_->tab_at(2);
    530   right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
    531 
    532   Tab* most_right_tab = tab_strip_->tab_at(3);
    533   most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
    534                                           gfx::Size(200, 20)));
    535 
    536   // Test that active tabs gets events from area in which it overlaps with its
    537   // left neighbour.
    538   gfx::Point left_overlap(
    539       (active_tab->x() + left_tab->bounds().right() + 1) / 2,
    540       active_tab->bounds().bottom() - 1);
    541 
    542   // Sanity check that the point is in both active and left tab.
    543   ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
    544   ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));
    545 
    546   EXPECT_EQ(active_tab,
    547             FindTabView(tab_strip_->GetEventHandlerForPoint(left_overlap)));
    548 
    549   // Test that active tabs gets events from area in which it overlaps with its
    550   // right neighbour.
    551   gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
    552                            active_tab->bounds().bottom() - 1);
    553 
    554   // Sanity check that the point is in both active and right tab.
    555   ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
    556   ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));
    557 
    558   EXPECT_EQ(active_tab,
    559             FindTabView(tab_strip_->GetEventHandlerForPoint(right_overlap)));
    560 
    561   // Test that if neither of tabs is active, the left one is selected.
    562   gfx::Point unactive_overlap(
    563       (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
    564       right_tab->bounds().bottom() - 1);
    565 
    566   // Sanity check that the point is in both active and left tab.
    567   ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
    568   ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));
    569 
    570   EXPECT_EQ(right_tab,
    571             FindTabView(tab_strip_->GetEventHandlerForPoint(unactive_overlap)));
    572 }
    573 
    574 TEST_F(TabStripTest, GetTooltipHandler) {
    575   tab_strip_->SetBounds(0, 0, 1000, 20);
    576 
    577   controller_->AddTab(0, false);
    578   controller_->AddTab(1, true);
    579   controller_->AddTab(2, false);
    580   controller_->AddTab(3, false);
    581   ASSERT_EQ(4, tab_strip_->tab_count());
    582 
    583   // Verify that the active tab will be a tooltip handler for points that hit
    584   // it.
    585   Tab* left_tab = tab_strip_->tab_at(0);
    586   left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
    587 
    588   Tab* active_tab = tab_strip_->tab_at(1);
    589   active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
    590   ASSERT_TRUE(active_tab->IsActive());
    591 
    592   Tab* right_tab = tab_strip_->tab_at(2);
    593   right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
    594 
    595   Tab* most_right_tab = tab_strip_->tab_at(3);
    596   most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
    597                                           gfx::Size(200, 20)));
    598 
    599   // Test that active_tab handles tooltips from area in which it overlaps with
    600   // its left neighbour.
    601   gfx::Point left_overlap(
    602       (active_tab->x() + left_tab->bounds().right() + 1) / 2,
    603       active_tab->bounds().bottom() - 1);
    604 
    605   // Sanity check that the point is in both active and left tab.
    606   ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
    607   ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));
    608 
    609   EXPECT_EQ(active_tab,
    610             FindTabView(tab_strip_->GetTooltipHandlerForPoint(left_overlap)));
    611 
    612   // Test that active_tab handles tooltips from area in which it overlaps with
    613   // its right neighbour.
    614   gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
    615                            active_tab->bounds().bottom() - 1);
    616 
    617   // Sanity check that the point is in both active and right tab.
    618   ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
    619   ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));
    620 
    621   EXPECT_EQ(active_tab,
    622             FindTabView(tab_strip_->GetTooltipHandlerForPoint(right_overlap)));
    623 
    624   // Test that if neither of tabs is active, the left one is selected.
    625   gfx::Point unactive_overlap(
    626       (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
    627       right_tab->bounds().bottom() - 1);
    628 
    629   // Sanity check that the point is in both active and left tab.
    630   ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
    631   ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));
    632 
    633   EXPECT_EQ(
    634       right_tab,
    635       FindTabView(tab_strip_->GetTooltipHandlerForPoint(unactive_overlap)));
    636 
    637   // Confirm that tab strip doe not return tooltip handler for points that
    638   // don't hit it.
    639   EXPECT_FALSE(tab_strip_->GetTooltipHandlerForPoint(gfx::Point(-1, 2)));
    640 }
    641