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