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