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.
5 #include "chrome/browser/ui/views/ash/tab_scrubber.h"
7 #include "ash/display/event_transformation_handler.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 "content/public/browser/notification_service.h"
22 #include "content/public/common/url_constants.h"
23 #include "content/public/test/test_utils.h"
24 #include "ui/aura/window.h"
25 #include "ui/events/event_utils.h"
26 #include "ui/events/test/event_generator.h"
28 #if defined(OS_CHROMEOS)
29 #include "chromeos/chromeos_switches.h"
34 class TabScrubberTest
: public InProcessBrowserTest
,
35 public TabStripModelObserver
{
41 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
42 #if defined(OS_CHROMEOS)
43 command_line
->AppendSwitch(chromeos::switches::kNaturalScrollDefault
);
45 command_line
->AppendSwitch(switches::kOpenAsh
);
48 void SetUpOnMainThread() override
{
49 TabScrubber::GetInstance()->set_activation_delay(0);
51 // Disable external monitor scaling of coordinates.
52 ash::Shell
* shell
= ash::Shell::GetInstance();
53 shell
->event_transformation_handler()->set_transformation_mode(
54 ash::EventTransformationHandler::TRANSFORM_NONE
);
57 void TearDownOnMainThread() override
{
58 browser()->tab_strip_model()->RemoveObserver(this);
61 TabStrip
* GetTabStrip(Browser
* browser
) {
62 aura::Window
* window
= browser
->window()->GetNativeWindow();
63 return BrowserView::GetBrowserViewForNativeWindow(window
)->tabstrip();
66 float GetStartX(Browser
* browser
,
68 TabScrubber::Direction direction
) {
69 return static_cast<float>(TabScrubber::GetStartPoint(
70 GetTabStrip(browser
), index
, direction
).x());
73 float GetTabCenter(Browser
* browser
, int index
) {
74 return static_cast<float>(
75 GetTabStrip(browser
)->tab_at(index
)->bounds().CenterPoint().x());
78 // Sends one scroll event synchronously without initial or final
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 float offset
= GetTabCenter(browser
, index
) -
88 GetStartX(browser
, active_index
, direction
);
89 ui::ScrollEvent
scroll_event(ui::ET_SCROLL
,
91 ui::EventTimeForNow(),
96 event_generator
.Dispatch(&scroll_event
);
105 // Sends asynchronous events and waits for tab at |index| to become
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
;
118 if (index
< active_index
) {
119 direction
= TabScrubber::LEFT
;
122 direction
= TabScrubber::RIGHT
;
125 if (scrub_type
== SKIP_TABS
)
127 float last
= GetStartX(browser
, active_index
, direction
);
128 std::vector
<gfx::PointF
> offsets
;
129 for (int i
= active_index
+ increment
; i
!= (index
+ increment
);
131 float tab_center
= GetTabCenter(browser
, i
);
132 offsets
.push_back(gfx::PointF(tab_center
- last
, 0));
133 last
= GetStartX(browser
, i
, direction
);
134 if (scrub_type
== REPEAT_TABS
) {
135 offsets
.push_back(gfx::PointF(static_cast<float>(increment
), 0));
139 event_generator
.ScrollSequence(gfx::Point(0, 0),
140 base::TimeDelta::FromMilliseconds(100),
143 RunUntilTabActive(browser
, index
);
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
, float 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);
159 event_generator
.ScrollSequence(gfx::Point(0, 0),
160 ui::EventTimeForNow(),
166 RunUntilTabActive(browser
, index
);
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());
179 // TabStripModelObserver overrides.
180 void TabInsertedAt(content::WebContents
* contents
,
182 bool foreground
) override
{}
183 void TabClosingAt(TabStripModel
* tab_strip_model
,
184 content::WebContents
* contents
,
185 int index
) override
{}
186 void TabDetachedAt(content::WebContents
* contents
, int index
) override
{}
187 void TabDeactivated(content::WebContents
* contents
) override
{}
188 void ActiveTabChanged(content::WebContents
* old_contents
,
189 content::WebContents
* new_contents
,
191 int reason
) override
{
192 activation_order_
.push_back(index
);
193 if (index
== target_index_
)
197 void TabSelectionChanged(TabStripModel
* tab_strip_model
,
198 const ui::ListSelectionModel
& old_model
) override
{}
199 void TabMoved(content::WebContents
* contents
,
201 int to_index
) override
{}
202 void TabChangedAt(content::WebContents
* contents
,
204 TabChangeType change_type
) override
{}
205 void TabReplacedAt(TabStripModel
* tab_strip_model
,
206 content::WebContents
* old_contents
,
207 content::WebContents
* new_contents
,
208 int index
) override
{}
209 void TabPinnedStateChanged(content::WebContents
* contents
,
210 int index
) override
{}
211 void TabMiniStateChanged(content::WebContents
* contents
, int index
) override
{
213 void TabBlockedStateChanged(content::WebContents
* contents
,
214 int index
) override
{}
215 void TabStripEmpty() override
{}
216 void TabStripModelDeleted() override
{}
218 // History of tab activation. Scrub() resets it.
219 std::vector
<int> activation_order_
;
222 void RunUntilTabActive(Browser
* browser
, int target
) {
223 base::RunLoop run_loop
;
224 quit_closure_
= content::GetQuitTaskForRunLoop(&run_loop
);
225 browser
->tab_strip_model()->AddObserver(this);
226 target_index_
= target
;
227 content::RunThisRunLoop(&run_loop
);
228 browser
->tab_strip_model()->RemoveObserver(this);
232 base::Closure quit_closure_
;
235 DISALLOW_COPY_AND_ASSIGN(TabScrubberTest
);
240 #if defined(OS_CHROMEOS)
241 // Swipe a single tab in each direction.
242 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, Single
) {
243 AddTabs(browser(), 1);
245 Scrub(browser(), 0, EACH_TAB
);
246 EXPECT_EQ(1U, activation_order_
.size());
247 EXPECT_EQ(0, activation_order_
[0]);
248 EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
250 Scrub(browser(), 1, EACH_TAB
);
251 EXPECT_EQ(1U, activation_order_
.size());
252 EXPECT_EQ(1, activation_order_
[0]);
253 EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
256 // Swipe 4 tabs in each direction. Each of the tabs should become active.
257 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, Multi
) {
258 AddTabs(browser(), 4);
260 Scrub(browser(), 0, EACH_TAB
);
261 ASSERT_EQ(4U, activation_order_
.size());
262 EXPECT_EQ(3, activation_order_
[0]);
263 EXPECT_EQ(2, activation_order_
[1]);
264 EXPECT_EQ(1, activation_order_
[2]);
265 EXPECT_EQ(0, activation_order_
[3]);
266 EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
268 Scrub(browser(), 4, EACH_TAB
);
269 ASSERT_EQ(4U, activation_order_
.size());
270 EXPECT_EQ(1, activation_order_
[0]);
271 EXPECT_EQ(2, activation_order_
[1]);
272 EXPECT_EQ(3, activation_order_
[2]);
273 EXPECT_EQ(4, activation_order_
[3]);
274 EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
277 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, MultiBrowser
) {
278 AddTabs(browser(), 1);
279 Scrub(browser(), 0, EACH_TAB
);
280 EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
282 Browser
* browser2
= CreateBrowser(browser()->profile());
283 browser2
->window()->Activate();
284 ASSERT_TRUE(browser2
->window()->IsActive());
285 ASSERT_FALSE(browser()->window()->IsActive());
286 AddTabs(browser2
, 1);
288 Scrub(browser2
, 0, EACH_TAB
);
289 EXPECT_EQ(0, browser2
->tab_strip_model()->active_index());
292 // Swipe 4 tabs in each direction with an extra swipe within each. The same
293 // 4 tabs should become active.
294 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, Repeated
) {
295 AddTabs(browser(), 4);
297 Scrub(browser(), 0, REPEAT_TABS
);
298 ASSERT_EQ(4U, activation_order_
.size());
299 EXPECT_EQ(3, activation_order_
[0]);
300 EXPECT_EQ(2, activation_order_
[1]);
301 EXPECT_EQ(1, activation_order_
[2]);
302 EXPECT_EQ(0, activation_order_
[3]);
303 EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
305 Scrub(browser(), 4, REPEAT_TABS
);
306 ASSERT_EQ(4U, activation_order_
.size());
307 EXPECT_EQ(1, activation_order_
[0]);
308 EXPECT_EQ(2, activation_order_
[1]);
309 EXPECT_EQ(3, activation_order_
[2]);
310 EXPECT_EQ(4, activation_order_
[3]);
311 EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
314 // Confirm that we get the last tab made active when we skip tabs.
315 // These tests have 5 total tabs. We will only received scroll events
316 // on tabs 0, 2 and 4.
317 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, Skipped
) {
318 AddTabs(browser(), 4);
320 Scrub(browser(), 0, SKIP_TABS
);
321 EXPECT_EQ(2U, activation_order_
.size());
322 EXPECT_EQ(2, activation_order_
[0]);
323 EXPECT_EQ(0, activation_order_
[1]);
324 EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
326 Scrub(browser(), 4, SKIP_TABS
);
327 EXPECT_EQ(2U, activation_order_
.size());
328 EXPECT_EQ(2, activation_order_
[0]);
329 EXPECT_EQ(4, activation_order_
[1]);
330 EXPECT_EQ(4, browser()->tab_strip_model()->active_index());
333 // Confirm that nothing happens when the swipe is small.
334 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, NoChange
) {
335 AddTabs(browser(), 1);
337 SendScrubSequence(browser(), -1, 1);
338 EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
340 SendScrubSequence(browser(), 1, 1);
341 EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
344 // Confirm that very large swipes go to the beginning and and of the tabstrip.
345 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, Bounds
) {
346 AddTabs(browser(), 1);
348 SendScrubSequence(browser(), -10000, 0);
349 EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
351 SendScrubSequence(browser(), 10000, 1);
352 EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
355 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, DeleteHighlighted
) {
356 AddTabs(browser(), 1);
358 SendScrubEvent(browser(), 0);
359 EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
360 browser()->tab_strip_model()->CloseWebContentsAt(0,
361 TabStripModel::CLOSE_NONE
);
362 EXPECT_FALSE(TabScrubber::GetInstance()->IsActivationPending());
365 // Delete the currently highlighted tab. Make sure the TabScrubber is aware.
366 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, DeleteBeforeHighlighted
) {
367 AddTabs(browser(), 2);
369 SendScrubEvent(browser(), 1);
370 EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
371 browser()->tab_strip_model()->CloseWebContentsAt(0,
372 TabStripModel::CLOSE_NONE
);
373 EXPECT_EQ(0, TabScrubber::GetInstance()->highlighted_tab());
376 // Move the currently highlighted tab and confirm it gets tracked.
377 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, MoveHighlighted
) {
378 AddTabs(browser(), 1);
380 SendScrubEvent(browser(), 0);
381 EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
382 browser()->tab_strip_model()->ToggleSelectionAt(0);
383 browser()->tab_strip_model()->ToggleSelectionAt(1);
384 browser()->tab_strip_model()->MoveSelectedTabsTo(1);
385 EXPECT_EQ(1, TabScrubber::GetInstance()->highlighted_tab());
388 // Move a tab to before the highlighted one.
389 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, MoveBefore
) {
390 AddTabs(browser(), 2);
392 SendScrubEvent(browser(), 1);
393 EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
394 browser()->tab_strip_model()->ToggleSelectionAt(0);
395 browser()->tab_strip_model()->ToggleSelectionAt(2);
396 browser()->tab_strip_model()->MoveSelectedTabsTo(2);
397 EXPECT_EQ(0, TabScrubber::GetInstance()->highlighted_tab());
400 // Move a tab to after the highlighted one.
401 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, MoveAfter
) {
402 AddTabs(browser(), 2);
404 SendScrubEvent(browser(), 1);
405 EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
406 browser()->tab_strip_model()->MoveSelectedTabsTo(0);
407 EXPECT_EQ(2, TabScrubber::GetInstance()->highlighted_tab());
410 // Close the browser while an activation is pending.
411 IN_PROC_BROWSER_TEST_F(TabScrubberTest
, CloseBrowser
) {
412 AddTabs(browser(), 1);
414 SendScrubEvent(browser(), 0);
415 EXPECT_TRUE(TabScrubber::GetInstance()->IsActivationPending());
416 browser()->window()->Close();
417 EXPECT_FALSE(TabScrubber::GetInstance()->IsActivationPending());
420 #endif // defined(OS_CHROMEOS)