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
29using blink::WebInputEvent;
30
31namespace content {
32
33class 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
116class 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
161IN_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
212IN_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
267IN_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
323IN_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
358IN_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
396IN_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