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/ash/tab_scrubber.h"
6
7#include "ash/display/event_transformation_handler.h"
8#include "ash/shell.h"
9#include "base/command_line.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop/message_loop.h"
12#include "base/message_loop/message_loop_proxy.h"
13#include "chrome/browser/ui/browser_tabstrip.h"
14#include "chrome/browser/ui/tabs/tab_strip_model.h"
15#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
16#include "chrome/browser/ui/views/frame/browser_view.h"
17#include "chrome/browser/ui/views/tabs/tab.h"
18#include "chrome/browser/ui/views/tabs/tab_strip.h"
19#include "chrome/common/chrome_switches.h"
20#include "chrome/test/base/in_process_browser_test.h"
21#include "chrome/test/base/ui_test_utils.h"
22#include "content/public/browser/notification_service.h"
23#include "content/public/common/url_constants.h"
24#include "content/public/test/test_utils.h"
25#include "ui/aura/window.h"
26#include "ui/events/event_utils.h"
27#include "ui/events/test/event_generator.h"
28
29#if defined(OS_CHROMEOS)
30#include "chromeos/chromeos_switches.h"
31#endif
32
33namespace {
34
35class TabScrubberTest : public InProcessBrowserTest,
36                        public TabStripModelObserver {
37 public:
38  TabScrubberTest()
39      : target_index_(-1) {
40  }
41
42  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
43#if defined(OS_CHROMEOS)
44    command_line->AppendSwitch(chromeos::switches::kNaturalScrollDefault);
45#endif
46    command_line->AppendSwitch(switches::kOpenAsh);
47  }
48
49  virtual void SetUpOnMainThread() OVERRIDE {
50    TabScrubber::GetInstance()->set_activation_delay(0);
51
52    // Disable external monitor scaling of coordinates.
53    ash::Shell* shell = ash::Shell::GetInstance();
54    shell->event_transformation_handler()->set_transformation_mode(
55        ash::EventTransformationHandler::TRANSFORM_NONE);
56  }
57
58  virtual void TearDownOnMainThread() OVERRIDE {
59    browser()->tab_strip_model()->RemoveObserver(this);
60  }
61
62  TabStrip* GetTabStrip(Browser* browser) {
63    aura::Window* window = browser->window()->GetNativeWindow();
64    return BrowserView::GetBrowserViewForNativeWindow(window)->tabstrip();
65  }
66
67  int GetStartX(Browser* browser,
68                int index,
69                TabScrubber::Direction direction) {
70    return TabScrubber::GetStartPoint(
71        GetTabStrip(browser), index, direction).x();
72  }
73
74  int GetTabCenter(Browser* browser, int index) {
75    return GetTabStrip(browser)->tab_at(index)->bounds().CenterPoint().x();
76  }
77
78  // Sends one scroll event synchronously without initial or final
79  // fling events.
80  void SendScrubEvent(Browser* browser, int index) {
81    aura::Window* window = browser->window()->GetNativeWindow();
82    aura::Window* root = window->GetRootWindow();
83    ui::test::EventGenerator event_generator(root, window);
84    int active_index = browser->tab_strip_model()->active_index();
85    TabScrubber::Direction direction = index < active_index ?
86        TabScrubber::LEFT : TabScrubber::RIGHT;
87    int offset = GetTabCenter(browser, index) -
88        GetStartX(browser, active_index, direction);
89    ui::ScrollEvent scroll_event(ui::ET_SCROLL,
90                                 gfx::Point(0, 0),
91                                 ui::EventTimeForNow(),
92                                 0,
93                                 offset, 0,
94                                 offset, 0,
95                                 3);
96    event_generator.Dispatch(&scroll_event);
97  }
98
99  enum ScrubType {
100    EACH_TAB,
101    SKIP_TABS,
102    REPEAT_TABS,
103  };
104
105  // Sends asynchronous events and waits for tab at |index| to become
106  // active.
107  void Scrub(Browser* browser, int index, ScrubType scrub_type) {
108    aura::Window* window = browser->window()->GetNativeWindow();
109    aura::Window* root = window->GetRootWindow();
110    ui::test::EventGenerator event_generator(root, window);
111    event_generator.set_async(true);
112    activation_order_.clear();
113    int active_index = browser->tab_strip_model()->active_index();
114    ASSERT_NE(index, active_index);
115    ASSERT_TRUE(scrub_type != SKIP_TABS || ((index - active_index) % 2) == 0);
116    TabScrubber::Direction direction;
117    int increment;
118    if (index < active_index) {
119      direction = TabScrubber::LEFT;
120      increment = -1;
121    } else {
122      direction = TabScrubber::RIGHT;
123      increment = 1;
124    }
125    if (scrub_type == SKIP_TABS)
126      increment *= 2;
127    int last = GetStartX(browser, active_index, direction);
128    std::vector<gfx::Point> offsets;
129    for (int i = active_index + increment; i != (index + increment);
130        i += increment) {
131      int tab_center = GetTabCenter(browser, i);
132      offsets.push_back(gfx::Point(tab_center - last, 0));
133      last = GetStartX(browser, i, direction);
134      if (scrub_type == REPEAT_TABS) {
135        offsets.push_back(gfx::Point(increment, 0));
136        last += increment;
137      }
138    }
139    event_generator.ScrollSequence(gfx::Point(0, 0),
140                                   base::TimeDelta::FromMilliseconds(100),
141                                   offsets,
142                                   3);
143    RunUntilTabActive(browser, index);
144  }
145
146  // Sends events and waits for tab at |index| to become active
147  // if it's different from the currently active tab.
148  // If the active tab is expected to stay the same, send events
149  // synchronously (as we don't have anything to wait for).
150  void SendScrubSequence(Browser* browser, int x_offset, int index) {
151    aura::Window* window = browser->window()->GetNativeWindow();
152    aura::Window* root = window->GetRootWindow();
153    ui::test::EventGenerator event_generator(root, window);
154    bool wait_for_active = false;
155    if (index != browser->tab_strip_model()->active_index()) {
156      wait_for_active = true;
157      event_generator.set_async(true);
158    }
159    event_generator.ScrollSequence(gfx::Point(0, 0),
160                                   ui::EventTimeForNow(),
161                                   x_offset,
162                                   0,
163                                   1,
164                                   3);
165    if (wait_for_active)
166      RunUntilTabActive(browser, index);
167  }
168
169  void AddTabs(Browser* browser, int num_tabs) {
170    TabStrip* tab_strip = GetTabStrip(browser);
171    for (int i = 0; i < num_tabs; ++i)
172      AddBlankTabAndShow(browser);
173    ASSERT_EQ(num_tabs + 1, browser->tab_strip_model()->count());
174    ASSERT_EQ(num_tabs, browser->tab_strip_model()->active_index());
175    tab_strip->StopAnimating(true);
176    ASSERT_FALSE(tab_strip->IsAnimating());
177  }
178
179  // TabStripModelObserver overrides.
180  virtual void TabInsertedAt(content::WebContents* contents,
181                             int index,
182                             bool foreground) OVERRIDE {}
183  virtual void TabClosingAt(TabStripModel* tab_strip_model,
184                            content::WebContents* contents,
185                            int index) OVERRIDE {}
186  virtual void TabDetachedAt(content::WebContents* contents,
187                             int index) OVERRIDE {}
188  virtual void TabDeactivated(content::WebContents* contents) OVERRIDE {}
189  virtual void ActiveTabChanged(content::WebContents* old_contents,
190                                content::WebContents* new_contents,
191                                int index,
192                                int reason) OVERRIDE {
193    activation_order_.push_back(index);
194    if (index == target_index_)
195      quit_closure_.Run();
196  }
197
198  virtual void TabSelectionChanged(
199      TabStripModel* tab_strip_model,
200      const ui::ListSelectionModel& old_model) OVERRIDE {}
201  virtual void TabMoved(content::WebContents* contents,
202                        int from_index,
203                        int to_index) OVERRIDE {}
204  virtual void TabChangedAt(content::WebContents* contents,
205                            int index,
206                            TabChangeType change_type) OVERRIDE {}
207  virtual void TabReplacedAt(TabStripModel* tab_strip_model,
208                             content::WebContents* old_contents,
209                             content::WebContents* new_contents,
210                             int index) OVERRIDE {}
211  virtual void TabPinnedStateChanged(content::WebContents* contents,
212                                     int index) OVERRIDE {}
213  virtual void TabMiniStateChanged(content::WebContents* contents,
214                                   int index) OVERRIDE {
215  }
216  virtual void TabBlockedStateChanged(content::WebContents* contents,
217                                      int index) OVERRIDE {}
218  virtual void TabStripEmpty() OVERRIDE {}
219  virtual void TabStripModelDeleted() OVERRIDE {}
220
221  // History of tab activation. Scrub() resets it.
222  std::vector<int> activation_order_;
223
224 private:
225  void RunUntilTabActive(Browser* browser, int target) {
226    base::RunLoop run_loop;
227    quit_closure_ = content::GetQuitTaskForRunLoop(&run_loop);
228    browser->tab_strip_model()->AddObserver(this);
229    target_index_ = target;
230    content::RunThisRunLoop(&run_loop);
231    browser->tab_strip_model()->RemoveObserver(this);
232    target_index_ = -1;
233  }
234
235  base::Closure quit_closure_;
236  int target_index_;
237
238  DISALLOW_COPY_AND_ASSIGN(TabScrubberTest);
239};
240
241}  // namespace
242
243#if defined(OS_CHROMEOS)
244// Swipe a single tab in each direction.
245IN_PROC_BROWSER_TEST_F(TabScrubberTest, Single) {
246  AddTabs(browser(), 1);
247
248  Scrub(browser(), 0, EACH_TAB);
249  EXPECT_EQ(1U, activation_order_.size());
250  EXPECT_EQ(0, activation_order_[0]);
251  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
252
253  Scrub(browser(), 1, EACH_TAB);
254  EXPECT_EQ(1U, activation_order_.size());
255  EXPECT_EQ(1, activation_order_[0]);
256  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
257}
258
259// Swipe 4 tabs in each direction. Each of the tabs should become active.
260IN_PROC_BROWSER_TEST_F(TabScrubberTest, Multi) {
261  AddTabs(browser(), 4);
262
263  Scrub(browser(), 0, EACH_TAB);
264  ASSERT_EQ(4U, activation_order_.size());
265  EXPECT_EQ(3, activation_order_[0]);
266  EXPECT_EQ(2, activation_order_[1]);
267  EXPECT_EQ(1, activation_order_[2]);
268  EXPECT_EQ(0, activation_order_[3]);
269  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
270
271  Scrub(browser(), 4, EACH_TAB);
272  ASSERT_EQ(4U, activation_order_.size());
273  EXPECT_EQ(1, activation_order_[0]);
274  EXPECT_EQ(2, activation_order_[1]);
275  EXPECT_EQ(3, activation_order_[2]);
276  EXPECT_EQ(4, activation_order_[3]);
277  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
278}
279
280IN_PROC_BROWSER_TEST_F(TabScrubberTest, MultiBrowser) {
281  AddTabs(browser(), 1);
282  Scrub(browser(), 0, EACH_TAB);
283  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
284
285  Browser* browser2 = CreateBrowser(browser()->profile());
286  browser2->window()->Activate();
287  ASSERT_TRUE(browser2->window()->IsActive());
288  ASSERT_FALSE(browser()->window()->IsActive());
289  AddTabs(browser2, 1);
290
291  Scrub(browser2, 0, EACH_TAB);
292  EXPECT_EQ(0, browser2->tab_strip_model()->active_index());
293}
294
295// Swipe 4 tabs in each direction with an extra swipe within each. The same
296// 4 tabs should become active.
297IN_PROC_BROWSER_TEST_F(TabScrubberTest, Repeated) {
298  AddTabs(browser(), 4);
299
300  Scrub(browser(), 0, REPEAT_TABS);
301  ASSERT_EQ(4U, activation_order_.size());
302  EXPECT_EQ(3, activation_order_[0]);
303  EXPECT_EQ(2, activation_order_[1]);
304  EXPECT_EQ(1, activation_order_[2]);
305  EXPECT_EQ(0, activation_order_[3]);
306  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
307
308  Scrub(browser(), 4, REPEAT_TABS);
309  ASSERT_EQ(4U, activation_order_.size());
310  EXPECT_EQ(1, activation_order_[0]);
311  EXPECT_EQ(2, activation_order_[1]);
312  EXPECT_EQ(3, activation_order_[2]);
313  EXPECT_EQ(4, activation_order_[3]);
314  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
315}
316
317// Confirm that we get the last tab made active when we skip tabs.
318// These tests have 5 total tabs. We will only received scroll events
319// on tabs 0, 2 and 4.
320IN_PROC_BROWSER_TEST_F(TabScrubberTest, Skipped) {
321  AddTabs(browser(), 4);
322
323  Scrub(browser(), 0, SKIP_TABS);
324  EXPECT_EQ(2U, activation_order_.size());
325  EXPECT_EQ(2, activation_order_[0]);
326  EXPECT_EQ(0, activation_order_[1]);
327  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
328
329  Scrub(browser(), 4, SKIP_TABS);
330  EXPECT_EQ(2U, activation_order_.size());
331  EXPECT_EQ(2, activation_order_[0]);
332  EXPECT_EQ(4, activation_order_[1]);
333  EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
334}
335
336// Confirm that nothing happens when the swipe is small.
337IN_PROC_BROWSER_TEST_F(TabScrubberTest, NoChange) {
338  AddTabs(browser(), 1);
339
340  SendScrubSequence(browser(), -1, 1);
341  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
342
343  SendScrubSequence(browser(), 1, 1);
344  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
345}
346
347// Confirm that very large swipes go to the beginning and and of the tabstrip.
348IN_PROC_BROWSER_TEST_F(TabScrubberTest, Bounds) {
349  AddTabs(browser(), 1);
350
351  SendScrubSequence(browser(), -10000, 0);
352  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
353
354  SendScrubSequence(browser(), 10000, 1);
355  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
356}
357
358IN_PROC_BROWSER_TEST_F(TabScrubberTest, DeleteHighlighted) {
359  AddTabs(browser(), 1);
360
361  SendScrubEvent(browser(), 0);
362  EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
363  browser()->tab_strip_model()->CloseWebContentsAt(0,
364                                                   TabStripModel::CLOSE_NONE);
365  EXPECT_FALSE(TabScrubber::GetInstance()->IsActivationPending());
366}
367
368// Delete the currently highlighted tab. Make sure the TabScrubber is aware.
369IN_PROC_BROWSER_TEST_F(TabScrubberTest, DeleteBeforeHighlighted) {
370  AddTabs(browser(), 2);
371
372  SendScrubEvent(browser(), 1);
373  EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
374  browser()->tab_strip_model()->CloseWebContentsAt(0,
375                                                   TabStripModel::CLOSE_NONE);
376  EXPECT_EQ(0, TabScrubber::GetInstance()->highlighted_tab());
377}
378
379// Move the currently highlighted tab and confirm it gets tracked.
380IN_PROC_BROWSER_TEST_F(TabScrubberTest, MoveHighlighted) {
381  AddTabs(browser(), 1);
382
383  SendScrubEvent(browser(), 0);
384  EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
385  browser()->tab_strip_model()->ToggleSelectionAt(0);
386  browser()->tab_strip_model()->ToggleSelectionAt(1);
387  browser()->tab_strip_model()->MoveSelectedTabsTo(1);
388  EXPECT_EQ(1, TabScrubber::GetInstance()->highlighted_tab());
389}
390
391// Move a tab to before  the highlighted one.
392IN_PROC_BROWSER_TEST_F(TabScrubberTest, MoveBefore) {
393  AddTabs(browser(), 2);
394
395  SendScrubEvent(browser(), 1);
396  EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
397  browser()->tab_strip_model()->ToggleSelectionAt(0);
398  browser()->tab_strip_model()->ToggleSelectionAt(2);
399  browser()->tab_strip_model()->MoveSelectedTabsTo(2);
400  EXPECT_EQ(0, TabScrubber::GetInstance()->highlighted_tab());
401}
402
403// Move a tab to after the highlighted one.
404IN_PROC_BROWSER_TEST_F(TabScrubberTest, MoveAfter) {
405  AddTabs(browser(), 2);
406
407  SendScrubEvent(browser(), 1);
408  EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
409  browser()->tab_strip_model()->MoveSelectedTabsTo(0);
410  EXPECT_EQ(2, TabScrubber::GetInstance()->highlighted_tab());
411}
412
413// Close the browser while an activation is pending.
414IN_PROC_BROWSER_TEST_F(TabScrubberTest, CloseBrowser) {
415  AddTabs(browser(), 1);
416
417  SendScrubEvent(browser(), 0);
418  EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
419  browser()->window()->Close();
420  EXPECT_FALSE(TabScrubber::GetInstance()->IsActivationPending());
421}
422
423#endif  // defined(OS_CHROMEOS)
424