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 "content/browser/web_contents/touch_editable_impl_aura.h" 6 7 #include "base/command_line.h" 8 #include "base/run_loop.h" 9 #include "base/strings/utf_string_conversions.h" 10 #include "base/test/test_timeouts.h" 11 #include "base/values.h" 12 #include "content/browser/web_contents/web_contents_impl.h" 13 #include "content/browser/web_contents/web_contents_view_aura.h" 14 #include "content/public/browser/render_frame_host.h" 15 #include "content/public/common/content_switches.h" 16 #include "content/public/test/browser_test_utils.h" 17 #include "content/public/test/content_browser_test.h" 18 #include "content/public/test/content_browser_test_utils.h" 19 #include "content/public/test/test_utils.h" 20 #include "content/shell/browser/shell.h" 21 #include "third_party/WebKit/public/web/WebInputEvent.h" 22 #include "ui/aura/window.h" 23 #include "ui/aura/window_tree_host.h" 24 #include "ui/base/ui_base_switches.h" 25 #include "ui/compositor/scoped_animation_duration_scale_mode.h" 26 #include "ui/events/event_utils.h" 27 #include "ui/events/test/event_generator.h" 28 29 using blink::WebInputEvent; 30 31 namespace content { 32 33 class TestTouchEditableImplAura : public TouchEditableImplAura { 34 public: 35 TestTouchEditableImplAura() 36 : selection_changed_callback_arrived_(false), 37 waiting_for_selection_changed_callback_(false), 38 waiting_for_gesture_ack_type_(WebInputEvent::Undefined), 39 last_gesture_ack_type_(WebInputEvent::Undefined), 40 fling_stop_callback_arrived_(false), 41 waiting_for_fling_stop_callback_(false) {} 42 43 virtual void Reset() { 44 selection_changed_callback_arrived_ = false; 45 waiting_for_selection_changed_callback_ = false; 46 waiting_for_gesture_ack_type_ = WebInputEvent::Undefined; 47 last_gesture_ack_type_ = WebInputEvent::Undefined; 48 fling_stop_callback_arrived_ = false; 49 waiting_for_fling_stop_callback_ = false; 50 } 51 52 virtual void OnSelectionOrCursorChanged(const gfx::Rect& anchor, 53 const gfx::Rect& focus) OVERRIDE { 54 selection_changed_callback_arrived_ = true; 55 TouchEditableImplAura::OnSelectionOrCursorChanged(anchor, focus); 56 if (waiting_for_selection_changed_callback_) 57 selection_changed_wait_run_loop_->Quit(); 58 } 59 60 virtual void GestureEventAck(int gesture_event_type) OVERRIDE { 61 last_gesture_ack_type_ = 62 static_cast<WebInputEvent::Type>(gesture_event_type); 63 TouchEditableImplAura::GestureEventAck(gesture_event_type); 64 if (waiting_for_gesture_ack_type_ == gesture_event_type) 65 gesture_ack_wait_run_loop_->Quit(); 66 } 67 68 virtual void DidStopFlinging() OVERRIDE { 69 fling_stop_callback_arrived_ = true; 70 TouchEditableImplAura::DidStopFlinging(); 71 if (waiting_for_fling_stop_callback_) 72 fling_stop_wait_run_loop_->Quit(); 73 } 74 75 virtual void WaitForSelectionChangeCallback() { 76 if (selection_changed_callback_arrived_) 77 return; 78 waiting_for_selection_changed_callback_ = true; 79 selection_changed_wait_run_loop_.reset(new base::RunLoop()); 80 selection_changed_wait_run_loop_->Run(); 81 } 82 83 virtual void WaitForGestureAck(WebInputEvent::Type gesture_event_type) { 84 if (last_gesture_ack_type_ == gesture_event_type) 85 return; 86 waiting_for_gesture_ack_type_ = gesture_event_type; 87 gesture_ack_wait_run_loop_.reset(new base::RunLoop()); 88 gesture_ack_wait_run_loop_->Run(); 89 } 90 91 virtual void WaitForFlingStopCallback() { 92 if (fling_stop_callback_arrived_) 93 return; 94 waiting_for_fling_stop_callback_ = true; 95 fling_stop_wait_run_loop_.reset(new base::RunLoop()); 96 fling_stop_wait_run_loop_->Run(); 97 } 98 99 protected: 100 virtual ~TestTouchEditableImplAura() {} 101 102 private: 103 bool selection_changed_callback_arrived_; 104 bool waiting_for_selection_changed_callback_; 105 WebInputEvent::Type waiting_for_gesture_ack_type_; 106 WebInputEvent::Type last_gesture_ack_type_; 107 bool fling_stop_callback_arrived_; 108 bool waiting_for_fling_stop_callback_; 109 scoped_ptr<base::RunLoop> selection_changed_wait_run_loop_; 110 scoped_ptr<base::RunLoop> gesture_ack_wait_run_loop_; 111 scoped_ptr<base::RunLoop> fling_stop_wait_run_loop_; 112 113 DISALLOW_COPY_AND_ASSIGN(TestTouchEditableImplAura); 114 }; 115 116 class TouchEditableImplAuraTest : public ContentBrowserTest { 117 public: 118 TouchEditableImplAuraTest() {} 119 120 protected: 121 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 122 command_line->AppendSwitch(switches::kEnableTouchEditing); 123 } 124 125 // Executes the javascript synchronously and makes sure the returned value is 126 // freed properly. 127 void ExecuteSyncJSFunction(RenderFrameHost* rfh, const std::string& jscript) { 128 scoped_ptr<base::Value> value = 129 content::ExecuteScriptAndGetValue(rfh, jscript); 130 } 131 132 // Starts the test server and navigates to the given url. Sets a large enough 133 // size to the root window. Returns after the navigation to the url is 134 // complete. 135 void StartTestWithPage(const std::string& url) { 136 ASSERT_TRUE(test_server()->Start()); 137 GURL test_url(test_server()->GetURL(url)); 138 NavigateToURL(shell(), test_url); 139 aura::Window* content = shell()->web_contents()->GetContentNativeView(); 140 content->GetHost()->SetBounds(gfx::Rect(800, 600)); 141 } 142 143 RenderWidgetHostViewAura* GetRenderWidgetHostViewAura( 144 TouchEditableImplAura* touch_editable) { 145 return touch_editable->rwhva_; 146 } 147 148 ui::TouchSelectionController* GetTouchSelectionController( 149 TouchEditableImplAura* touch_editable) { 150 return touch_editable->touch_selection_controller_.get(); 151 } 152 153 ui::TextInputType GetTextInputType(TouchEditableImplAura* touch_editable) { 154 return touch_editable->text_input_type_; 155 } 156 157 private: 158 DISALLOW_COPY_AND_ASSIGN(TouchEditableImplAuraTest); 159 }; 160 161 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 162 TouchSelectionOriginatingFromWebpageTest) { 163 ASSERT_NO_FATAL_FAILURE(StartTestWithPage("files/touch_selection.html")); 164 WebContentsImpl* web_contents = 165 static_cast<WebContentsImpl*>(shell()->web_contents()); 166 RenderFrameHost* main_frame = web_contents->GetMainFrame(); 167 WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>( 168 web_contents->GetView()); 169 TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura; 170 view_aura->SetTouchEditableForTest(touch_editable); 171 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 172 web_contents->GetRenderWidgetHostView()); 173 aura::Window* content = web_contents->GetContentNativeView(); 174 ui::test::EventGenerator generator(content->GetRootWindow(), content); 175 gfx::Rect bounds = content->GetBoundsInRootWindow(); 176 177 touch_editable->Reset(); 178 ExecuteSyncJSFunction(main_frame, "select_all_text()"); 179 touch_editable->WaitForSelectionChangeCallback(); 180 181 // Tap inside selection to bring up selection handles. 182 generator.GestureTapAt(gfx::Point(bounds.x() + 10, bounds.y() + 10)); 183 EXPECT_EQ(GetRenderWidgetHostViewAura(touch_editable), rwhva); 184 185 scoped_ptr<base::Value> value = 186 content::ExecuteScriptAndGetValue(main_frame, "get_selection()"); 187 std::string selection; 188 value->GetAsString(&selection); 189 190 // Check if selection handles are showing. 191 EXPECT_TRUE(GetTouchSelectionController(touch_editable)); 192 EXPECT_STREQ("Some text we can select", selection.c_str()); 193 194 // Lets move the handles a bit to modify the selection 195 touch_editable->Reset(); 196 generator.GestureScrollSequence( 197 gfx::Point(10, 47), 198 gfx::Point(30, 47), 199 base::TimeDelta::FromMilliseconds(20), 200 5); 201 touch_editable->WaitForSelectionChangeCallback(); 202 203 EXPECT_TRUE(GetTouchSelectionController(touch_editable)); 204 value = content::ExecuteScriptAndGetValue(main_frame, "get_selection()"); 205 value->GetAsString(&selection); 206 207 // It is hard to tell what exactly the selection would be now. But it would 208 // definitely be less than whatever was selected before. 209 EXPECT_GT(std::strlen("Some text we can select"), selection.size()); 210 } 211 212 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 213 TestTouchSelectionHiddenWhenScrolling) { 214 ASSERT_NO_FATAL_FAILURE(StartTestWithPage("files/touch_selection.html")); 215 WebContentsImpl* web_contents = 216 static_cast<WebContentsImpl*>(shell()->web_contents()); 217 RenderFrameHost* main_frame = web_contents->GetMainFrame(); 218 WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>( 219 web_contents->GetView()); 220 TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura; 221 view_aura->SetTouchEditableForTest(touch_editable); 222 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 223 web_contents->GetRenderWidgetHostView()); 224 EXPECT_EQ(GetRenderWidgetHostViewAura(touch_editable), rwhva); 225 226 // Long press to select word. 227 ui::GestureEvent long_press( 228 10, 229 10, 230 0, 231 ui::EventTimeForNow(), 232 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS)); 233 touch_editable->Reset(); 234 rwhva->OnGestureEvent(&long_press); 235 touch_editable->WaitForSelectionChangeCallback(); 236 237 // Check if selection handles are showing. 238 EXPECT_TRUE(GetTouchSelectionController(touch_editable)); 239 240 scoped_ptr<base::Value> value = 241 content::ExecuteScriptAndGetValue(main_frame, "get_selection()"); 242 std::string selection; 243 value->GetAsString(&selection); 244 EXPECT_STREQ("Some", selection.c_str()); 245 246 // Start scrolling. Handles should get hidden. 247 ui::GestureEvent scroll_begin( 248 10, 249 10, 250 0, 251 ui::EventTimeForNow(), 252 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN)); 253 rwhva->OnGestureEvent(&scroll_begin); 254 EXPECT_FALSE(GetTouchSelectionController(touch_editable)); 255 256 // Handles should come back after scroll ends. 257 ui::GestureEvent scroll_end( 258 10, 259 10, 260 0, 261 ui::EventTimeForNow(), 262 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END)); 263 rwhva->OnGestureEvent(&scroll_end); 264 EXPECT_TRUE(GetTouchSelectionController(touch_editable)); 265 } 266 267 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 268 TestTouchSelectionReshownAfterFling) { 269 ASSERT_NO_FATAL_FAILURE(StartTestWithPage("files/touch_selection.html")); 270 WebContentsImpl* web_contents = 271 static_cast<WebContentsImpl*>(shell()->web_contents()); 272 RenderFrameHost* main_frame = web_contents->GetMainFrame(); 273 WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>( 274 web_contents->GetView()); 275 TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura; 276 view_aura->SetTouchEditableForTest(touch_editable); 277 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 278 web_contents->GetRenderWidgetHostView()); 279 EXPECT_EQ(GetRenderWidgetHostViewAura(touch_editable), rwhva); 280 281 // Long press to select word. 282 ui::GestureEvent long_press( 283 10, 284 10, 285 0, 286 ui::EventTimeForNow(), 287 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS)); 288 touch_editable->Reset(); 289 rwhva->OnGestureEvent(&long_press); 290 touch_editable->WaitForSelectionChangeCallback(); 291 292 // Check if selection handles are showing. 293 EXPECT_TRUE(GetTouchSelectionController(touch_editable)); 294 295 scoped_ptr<base::Value> value = 296 content::ExecuteScriptAndGetValue(main_frame, "get_selection()"); 297 std::string selection; 298 value->GetAsString(&selection); 299 EXPECT_STREQ("Some", selection.c_str()); 300 301 // Start scrolling. Handles should get hidden. 302 ui::GestureEvent scroll_begin( 303 10, 304 10, 305 0, 306 ui::EventTimeForNow(), 307 ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, 0)); 308 rwhva->OnGestureEvent(&scroll_begin); 309 EXPECT_FALSE(GetTouchSelectionController(touch_editable)); 310 311 // Start a fling. Handles should come back after fling stops. 312 ui::GestureEvent fling_start( 313 10, 314 10, 315 0, 316 ui::EventTimeForNow(), 317 ui::GestureEventDetails(ui::ET_SCROLL_FLING_START, 1, 0)); 318 rwhva->OnGestureEvent(&fling_start); 319 touch_editable->WaitForFlingStopCallback(); 320 EXPECT_TRUE(GetTouchSelectionController(touch_editable)); 321 } 322 323 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 324 TouchSelectionOnLongPressTest) { 325 ASSERT_NO_FATAL_FAILURE(StartTestWithPage("files/touch_selection.html")); 326 WebContentsImpl* web_contents = 327 static_cast<WebContentsImpl*>(shell()->web_contents()); 328 RenderFrameHost* main_frame = web_contents->GetMainFrame(); 329 WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>( 330 web_contents->GetView()); 331 TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura; 332 view_aura->SetTouchEditableForTest(touch_editable); 333 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 334 web_contents->GetRenderWidgetHostView()); 335 EXPECT_EQ(GetRenderWidgetHostViewAura(touch_editable), rwhva); 336 337 // Long press to select word. 338 ui::GestureEvent long_press( 339 10, 340 10, 341 0, 342 ui::EventTimeForNow(), 343 ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS)); 344 touch_editable->Reset(); 345 rwhva->OnGestureEvent(&long_press); 346 touch_editable->WaitForSelectionChangeCallback(); 347 348 // Check if selection handles are showing. 349 EXPECT_TRUE(GetTouchSelectionController(touch_editable)); 350 351 scoped_ptr<base::Value> value = 352 content::ExecuteScriptAndGetValue(main_frame, "get_selection()"); 353 std::string selection; 354 value->GetAsString(&selection); 355 EXPECT_STREQ("Some", selection.c_str()); 356 } 357 358 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 359 NoTouchSelectionOnDoubleTapTest) { 360 ASSERT_NO_FATAL_FAILURE(StartTestWithPage("files/touch_selection.html")); 361 WebContentsImpl* web_contents = 362 static_cast<WebContentsImpl*>(shell()->web_contents()); 363 RenderFrameHost* main_frame = web_contents->GetMainFrame(); 364 WebContentsViewAura* view_aura = 365 static_cast<WebContentsViewAura*>(web_contents->GetView()); 366 TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura; 367 view_aura->SetTouchEditableForTest(touch_editable); 368 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 369 web_contents->GetRenderWidgetHostView()); 370 EXPECT_EQ(GetRenderWidgetHostViewAura(touch_editable), rwhva); 371 372 // Double-tap to select word. 373 ui::GestureEventDetails details(ui::ET_GESTURE_TAP); 374 details.set_tap_count(2); 375 ui::GestureEvent double_tap(10, 10, 0, ui::EventTimeForNow(), details); 376 touch_editable->Reset(); 377 rwhva->OnGestureEvent(&double_tap); 378 touch_editable->WaitForSelectionChangeCallback(); 379 380 // Make sure touch selection handles are not showing. 381 EXPECT_FALSE(GetTouchSelectionController(touch_editable)); 382 383 scoped_ptr<base::Value> value = 384 content::ExecuteScriptAndGetValue(main_frame, "get_selection()"); 385 std::string selection; 386 value->GetAsString(&selection); 387 EXPECT_STREQ("Some", selection.c_str()); 388 } 389 390 #if defined(OS_CHROMEOS) 391 // http://crbug.com/396509 392 #define MAYBE_TouchCursorInTextfieldTest DISABLED_TouchCursorInTextfieldTest 393 #else 394 #define MAYBE_TouchCursorInTextfieldTest TouchCursorInTextfieldTest 395 #endif 396 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 397 MAYBE_TouchCursorInTextfieldTest) { 398 ASSERT_NO_FATAL_FAILURE(StartTestWithPage("files/touch_selection.html")); 399 WebContentsImpl* web_contents = 400 static_cast<WebContentsImpl*>(shell()->web_contents()); 401 RenderFrameHost* main_frame = web_contents->GetMainFrame(); 402 WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>( 403 web_contents->GetView()); 404 TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura; 405 view_aura->SetTouchEditableForTest(touch_editable); 406 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 407 web_contents->GetRenderWidgetHostView()); 408 aura::Window* content = web_contents->GetContentNativeView(); 409 ui::test::EventGenerator generator(content->GetRootWindow(), content); 410 gfx::Rect bounds = content->GetBoundsInRootWindow(); 411 EXPECT_EQ(GetRenderWidgetHostViewAura(touch_editable), rwhva); 412 413 ExecuteSyncJSFunction(main_frame, "focus_textfield()"); 414 touch_editable->WaitForSelectionChangeCallback(); 415 416 // Tap textfield 417 touch_editable->Reset(); 418 generator.GestureTapAt(gfx::Point(bounds.x() + 50, bounds.y() + 40)); 419 // Tap Down acks are sent synchronously, while Tap acks are asynchronous. 420 touch_editable->WaitForGestureAck(WebInputEvent::GestureTap); 421 touch_editable->WaitForSelectionChangeCallback(); 422 touch_editable->Reset(); 423 424 // Check if cursor handle is showing. 425 EXPECT_NE(ui::TEXT_INPUT_TYPE_NONE, GetTextInputType(touch_editable)); 426 EXPECT_TRUE(GetTouchSelectionController(touch_editable)); 427 428 scoped_ptr<base::Value> value = 429 content::ExecuteScriptAndGetValue(main_frame, "get_cursor_position()"); 430 int cursor_pos = -1; 431 value->GetAsInteger(&cursor_pos); 432 EXPECT_NE(-1, cursor_pos); 433 434 // Move the cursor handle. 435 generator.GestureScrollSequence( 436 gfx::Point(50, 59), 437 gfx::Point(10, 59), 438 base::TimeDelta::FromMilliseconds(20), 439 1); 440 touch_editable->WaitForSelectionChangeCallback(); 441 EXPECT_TRUE(GetTouchSelectionController(touch_editable)); 442 value = content::ExecuteScriptAndGetValue(main_frame, 443 "get_cursor_position()"); 444 int new_cursor_pos = -1; 445 value->GetAsInteger(&new_cursor_pos); 446 EXPECT_NE(-1, new_cursor_pos); 447 // Cursor should have moved. 448 EXPECT_NE(new_cursor_pos, cursor_pos); 449 } 450 451 } // namespace content 452