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