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/renderer_host/render_view_host_impl.h" 13 #include "content/browser/web_contents/web_contents_impl.h" 14 #include "content/browser/web_contents/web_contents_view_aura.h" 15 #include "content/public/browser/web_contents_view.h" 16 #include "content/public/common/content_switches.h" 17 #include "content/public/test/browser_test_utils.h" 18 #include "content/public/test/test_utils.h" 19 #include "content/shell/browser/shell.h" 20 #include "content/test/content_browser_test.h" 21 #include "content/test/content_browser_test_utils.h" 22 #include "ui/aura/root_window.h" 23 #include "ui/aura/test/event_generator.h" 24 #include "ui/aura/window.h" 25 #include "ui/base/ui_base_switches.h" 26 #include "ui/compositor/scoped_animation_duration_scale_mode.h" 27 #include "ui/events/event_utils.h" 28 29 namespace content { 30 31 class TestTouchEditableImplAura : public TouchEditableImplAura { 32 public: 33 TestTouchEditableImplAura() 34 : selection_changed_callback_arrived_(false), 35 waiting_for_selection_changed_callback_(false), 36 gesture_ack_callback_arrived_(false), 37 waiting_for_gesture_ack_callback_(false) {} 38 39 virtual void Reset() { 40 selection_changed_callback_arrived_ = false; 41 waiting_for_selection_changed_callback_ = false; 42 gesture_ack_callback_arrived_ = false; 43 waiting_for_gesture_ack_callback_ = false; 44 } 45 46 virtual void OnSelectionOrCursorChanged(const gfx::Rect& anchor, 47 const gfx::Rect& focus) OVERRIDE { 48 selection_changed_callback_arrived_ = true; 49 TouchEditableImplAura::OnSelectionOrCursorChanged(anchor, focus); 50 if (waiting_for_selection_changed_callback_) 51 selection_changed_wait_run_loop_->Quit(); 52 } 53 54 virtual void GestureEventAck(int gesture_event_type) OVERRIDE { 55 gesture_ack_callback_arrived_ = true; 56 TouchEditableImplAura::GestureEventAck(gesture_event_type); 57 if (waiting_for_gesture_ack_callback_) 58 gesture_ack_wait_run_loop_->Quit(); 59 } 60 61 virtual void WaitForSelectionChangeCallback() { 62 if (selection_changed_callback_arrived_) 63 return; 64 waiting_for_selection_changed_callback_ = true; 65 selection_changed_wait_run_loop_.reset(new base::RunLoop()); 66 selection_changed_wait_run_loop_->Run(); 67 } 68 69 virtual void WaitForGestureAck() { 70 if (gesture_ack_callback_arrived_) 71 return; 72 waiting_for_gesture_ack_callback_ = true; 73 gesture_ack_wait_run_loop_.reset(new base::RunLoop()); 74 gesture_ack_wait_run_loop_->Run(); 75 } 76 77 protected: 78 virtual ~TestTouchEditableImplAura() {} 79 80 private: 81 bool selection_changed_callback_arrived_; 82 bool waiting_for_selection_changed_callback_; 83 bool gesture_ack_callback_arrived_; 84 bool waiting_for_gesture_ack_callback_; 85 scoped_ptr<base::RunLoop> selection_changed_wait_run_loop_; 86 scoped_ptr<base::RunLoop> gesture_ack_wait_run_loop_; 87 88 DISALLOW_COPY_AND_ASSIGN(TestTouchEditableImplAura); 89 }; 90 91 // This class ignores mouse-moved, mouse-entered and mouse-exited events 92 // without passing them to TouchEditableImplAura. Normally, we should not 93 // receive these events; but, sometimes we receive them which breaks the tests 94 // and makes them flaky: crbug.com/276992. 95 class TestTouchEditableImplAuraIgnoreMouseMovement 96 : public TestTouchEditableImplAura { 97 public: 98 TestTouchEditableImplAuraIgnoreMouseMovement() {} 99 100 virtual bool HandleInputEvent(const ui::Event* event) OVERRIDE { 101 if (event->type() == ui::ET_MOUSE_ENTERED || 102 event->type() == ui::ET_MOUSE_MOVED || 103 event->type() == ui::ET_MOUSE_EXITED) { 104 return false; 105 } 106 return TestTouchEditableImplAura::HandleInputEvent(event); 107 } 108 109 protected: 110 virtual ~TestTouchEditableImplAuraIgnoreMouseMovement() {} 111 112 private: 113 DISALLOW_COPY_AND_ASSIGN(TestTouchEditableImplAuraIgnoreMouseMovement); 114 }; 115 116 class TouchEditableImplAuraTest : public ContentBrowserTest { 117 public: 118 TouchEditableImplAuraTest() {} 119 120 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 121 command_line->AppendSwitch(switches::kEnableTouchEditing); 122 } 123 124 // Executes the javascript synchronously and makes sure the returned value is 125 // freed properly. 126 void ExecuteSyncJSFunction(RenderViewHost* rvh, const std::string& jscript) { 127 scoped_ptr<base::Value> value = 128 content::ExecuteScriptAndGetValue(rvh, jscript); 129 } 130 131 // Starts the test server and navigates to the given url. Sets a large enough 132 // size to the root window. Returns after the navigation to the url is 133 // complete. 134 void StartTestWithPage(const std::string& url) { 135 ASSERT_TRUE(test_server()->Start()); 136 GURL test_url(test_server()->GetURL(url)); 137 NavigateToURL(shell(), test_url); 138 aura::Window* content = 139 shell()->web_contents()->GetView()->GetContentNativeView(); 140 content->GetDispatcher()->SetHostSize(gfx::Size(800, 600)); 141 } 142 143 void TestTouchSelectionOriginatingFromWebpage() { 144 ASSERT_NO_FATAL_FAILURE( 145 StartTestWithPage("files/touch_selection.html")); 146 WebContentsImpl* web_contents = 147 static_cast<WebContentsImpl*>(shell()->web_contents()); 148 RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>( 149 web_contents->GetRenderViewHost()); 150 WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>( 151 web_contents->GetView()); 152 TestTouchEditableImplAura* touch_editable = 153 new TestTouchEditableImplAuraIgnoreMouseMovement; 154 view_aura->SetTouchEditableForTest(touch_editable); 155 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 156 web_contents->GetRenderWidgetHostView()); 157 aura::Window* content = web_contents->GetView()->GetContentNativeView(); 158 aura::test::EventGenerator generator(content->GetRootWindow(), content); 159 gfx::Rect bounds = content->GetBoundsInRootWindow(); 160 161 touch_editable->Reset(); 162 ExecuteSyncJSFunction(view_host, "select_all_text()"); 163 touch_editable->WaitForSelectionChangeCallback(); 164 165 // Tap inside selection to bring up selection handles. 166 generator.GestureTapAt(gfx::Point(bounds.x() + 10, bounds.y() + 10)); 167 EXPECT_EQ(touch_editable->rwhva_, rwhva); 168 169 scoped_ptr<base::Value> value = 170 content::ExecuteScriptAndGetValue(view_host, "get_selection()"); 171 std::string selection; 172 value->GetAsString(&selection); 173 174 // Check if selection handles are showing. 175 EXPECT_TRUE(touch_editable->touch_selection_controller_.get()); 176 EXPECT_STREQ("Some text we can select", selection.c_str()); 177 178 // Lets move the handles a bit to modify the selection 179 touch_editable->Reset(); 180 generator.GestureScrollSequence( 181 gfx::Point(10, 47), 182 gfx::Point(30, 47), 183 base::TimeDelta::FromMilliseconds(20), 184 5); 185 touch_editable->WaitForSelectionChangeCallback(); 186 187 EXPECT_TRUE(touch_editable->touch_selection_controller_.get()); 188 value = content::ExecuteScriptAndGetValue(view_host, "get_selection()"); 189 value->GetAsString(&selection); 190 191 // It is hard to tell what exactly the selection would be now. But it would 192 // definitely be less than whatever was selected before. 193 EXPECT_GT(std::strlen("Some text we can select"), selection.size()); 194 } 195 196 void TestTouchSelectionOnLongPress() { 197 ASSERT_NO_FATAL_FAILURE( 198 StartTestWithPage("files/touch_selection.html")); 199 WebContentsImpl* web_contents = 200 static_cast<WebContentsImpl*>(shell()->web_contents()); 201 RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>( 202 web_contents->GetRenderViewHost()); 203 WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>( 204 web_contents->GetView()); 205 TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura; 206 view_aura->SetTouchEditableForTest(touch_editable); 207 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 208 web_contents->GetRenderWidgetHostView()); 209 aura::Window* content = web_contents->GetView()->GetContentNativeView(); 210 aura::test::EventGenerator generator(content->GetRootWindow(), content); 211 gfx::Rect bounds = content->GetBoundsInRootWindow(); 212 EXPECT_EQ(touch_editable->rwhva_, rwhva); 213 214 // Long press to select word. 215 ui::GestureEvent long_press(ui::ET_GESTURE_LONG_PRESS, 216 10, 217 10, 218 0, 219 ui::EventTimeForNow(), 220 ui::GestureEventDetails( 221 ui::ET_GESTURE_LONG_PRESS, 0, 0), 222 1); 223 touch_editable->Reset(); 224 rwhva->OnGestureEvent(&long_press); 225 touch_editable->WaitForSelectionChangeCallback(); 226 227 // Check if selection handles are showing. 228 ui::TouchSelectionController* controller = 229 touch_editable->touch_selection_controller_.get(); 230 EXPECT_TRUE(controller); 231 232 scoped_ptr<base::Value> value = 233 content::ExecuteScriptAndGetValue(view_host, "get_selection()"); 234 std::string selection; 235 value->GetAsString(&selection); 236 EXPECT_STREQ("Some", selection.c_str()); 237 } 238 239 void TestTouchSelectionHiddenWhenScrolling() { 240 ASSERT_NO_FATAL_FAILURE( 241 StartTestWithPage("files/touch_selection.html")); 242 WebContentsImpl* web_contents = 243 static_cast<WebContentsImpl*>(shell()->web_contents()); 244 RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>( 245 web_contents->GetRenderViewHost()); 246 WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>( 247 web_contents->GetView()); 248 TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura; 249 view_aura->SetTouchEditableForTest(touch_editable); 250 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 251 web_contents->GetRenderWidgetHostView()); 252 aura::Window* content = web_contents->GetView()->GetContentNativeView(); 253 aura::test::EventGenerator generator(content->GetRootWindow(), content); 254 gfx::Rect bounds = content->GetBoundsInRootWindow(); 255 EXPECT_EQ(touch_editable->rwhva_, rwhva); 256 257 // Long press to select word. 258 ui::GestureEvent long_press(ui::ET_GESTURE_LONG_PRESS, 259 10, 260 10, 261 0, 262 ui::EventTimeForNow(), 263 ui::GestureEventDetails( 264 ui::ET_GESTURE_LONG_PRESS, 0, 0), 265 1); 266 touch_editable->Reset(); 267 rwhva->OnGestureEvent(&long_press); 268 touch_editable->WaitForSelectionChangeCallback(); 269 270 // Check if selection handles are showing. 271 ui::TouchSelectionController* controller = 272 touch_editable->touch_selection_controller_.get(); 273 EXPECT_TRUE(controller); 274 275 scoped_ptr<base::Value> value = 276 content::ExecuteScriptAndGetValue(view_host, "get_selection()"); 277 std::string selection; 278 value->GetAsString(&selection); 279 EXPECT_STREQ("Some", selection.c_str()); 280 281 // Start scrolling. Handles should get hidden. 282 ui::GestureEvent scroll_begin(ui::ET_GESTURE_SCROLL_BEGIN, 283 10, 284 10, 285 0, 286 ui::EventTimeForNow(), 287 ui::GestureEventDetails( 288 ui::ET_GESTURE_LONG_PRESS, 0, 0), 289 1); 290 rwhva->OnGestureEvent(&scroll_begin); 291 EXPECT_FALSE(touch_editable->touch_selection_controller_.get()); 292 293 // Handles should come back after scroll ends. 294 ui::GestureEvent scroll_end(ui::ET_GESTURE_SCROLL_END, 295 10, 296 10, 297 0, 298 ui::EventTimeForNow(), 299 ui::GestureEventDetails( 300 ui::ET_GESTURE_LONG_PRESS, 0, 0), 301 1); 302 rwhva->OnGestureEvent(&scroll_end); 303 EXPECT_TRUE(touch_editable->touch_selection_controller_.get()); 304 } 305 306 void TestTouchCursorInTextfield() { 307 ASSERT_NO_FATAL_FAILURE( 308 StartTestWithPage("files/touch_selection.html")); 309 WebContentsImpl* web_contents = 310 static_cast<WebContentsImpl*>(shell()->web_contents()); 311 RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>( 312 web_contents->GetRenderViewHost()); 313 WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>( 314 web_contents->GetView()); 315 TestTouchEditableImplAura* touch_editable = 316 new TestTouchEditableImplAuraIgnoreMouseMovement; 317 view_aura->SetTouchEditableForTest(touch_editable); 318 RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>( 319 web_contents->GetRenderWidgetHostView()); 320 aura::Window* content = web_contents->GetView()->GetContentNativeView(); 321 aura::test::EventGenerator generator(content->GetRootWindow(), content); 322 gfx::Rect bounds = content->GetBoundsInRootWindow(); 323 EXPECT_EQ(touch_editable->rwhva_, rwhva); 324 325 ExecuteSyncJSFunction(view_host, "focus_textfield()"); 326 touch_editable->WaitForSelectionChangeCallback(); 327 328 // Tap textfield 329 touch_editable->Reset(); 330 generator.GestureTapAt(gfx::Point(bounds.x() + 50, bounds.y() + 40)); 331 touch_editable->WaitForSelectionChangeCallback(); 332 // No Tap Down Ack is coming, it's async. 333 touch_editable->Reset(); 334 touch_editable->WaitForGestureAck(); // Wait for Tap Ack. 335 336 // Check if cursor handle is showing. 337 ui::TouchSelectionController* controller = 338 touch_editable->touch_selection_controller_.get(); 339 EXPECT_NE(ui::TEXT_INPUT_TYPE_NONE, touch_editable->text_input_type_); 340 EXPECT_TRUE(controller); 341 342 scoped_ptr<base::Value> value = 343 content::ExecuteScriptAndGetValue(view_host, "get_cursor_position()"); 344 int cursor_pos = -1; 345 value->GetAsInteger(&cursor_pos); 346 EXPECT_NE(-1, cursor_pos); 347 348 // Move the cursor handle. 349 generator.GestureScrollSequence( 350 gfx::Point(50, 59), 351 gfx::Point(10, 59), 352 base::TimeDelta::FromMilliseconds(20), 353 1); 354 touch_editable->WaitForSelectionChangeCallback(); 355 EXPECT_TRUE(touch_editable->touch_selection_controller_.get()); 356 value = content::ExecuteScriptAndGetValue(view_host, 357 "get_cursor_position()"); 358 int new_cursor_pos = -1; 359 value->GetAsInteger(&new_cursor_pos); 360 EXPECT_NE(-1, new_cursor_pos); 361 // Cursor should have moved. 362 EXPECT_NE(new_cursor_pos, cursor_pos); 363 } 364 365 private: 366 DISALLOW_COPY_AND_ASSIGN(TouchEditableImplAuraTest); 367 }; 368 369 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 370 TouchSelectionOriginatingFromWebpageTest) { 371 TestTouchSelectionOriginatingFromWebpage(); 372 } 373 374 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 375 TestTouchSelectionHiddenWhenScrolling) { 376 TestTouchSelectionHiddenWhenScrolling(); 377 } 378 379 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 380 TouchSelectionOnLongPressTest) { 381 TestTouchSelectionOnLongPress(); 382 } 383 384 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest, 385 TouchCursorInTextfieldTest) { 386 TestTouchCursorInTextfield(); 387 } 388 389 } // namespace content 390