c83a20e81e6ab78586dad931474e63314686dfff
[platform/framework/web/crosswalk.git] / src / content / public / android / javatests / src / org / chromium / content / browser / input / InsertionHandleTest.java
1 // Copyright 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 package org.chromium.content.browser.input;
6
7 import android.content.ClipData;
8 import android.content.ClipboardManager;
9 import android.content.Context;
10 import android.graphics.Rect;
11 import android.test.suitebuilder.annotation.MediumTest;
12 import android.text.Editable;
13 import android.text.Selection;
14 import android.view.KeyEvent;
15
16 import org.chromium.base.test.util.DisabledTest;
17 import org.chromium.base.test.util.Feature;
18 import org.chromium.base.test.util.UrlUtils;
19 import org.chromium.content.browser.ContentView;
20 import org.chromium.content.browser.RenderCoordinates;
21 import org.chromium.content.browser.test.util.Criteria;
22 import org.chromium.content.browser.test.util.CriteriaHelper;
23 import org.chromium.content.browser.test.util.DOMUtils;
24 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
25 import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
26 import org.chromium.content.browser.test.util.TestTouchUtils;
27 import org.chromium.content.browser.test.util.TouchCommon;
28 import org.chromium.content_shell_apk.ContentShellTestBase;
29
30 /**
31  * Tests the insertion handle that allows users to paste text.
32  */
33 public class InsertionHandleTest extends ContentShellTestBase {
34     private static final String META_DISABLE_ZOOM =
35         "<meta name=\"viewport\" content=\"" +
36         "height=device-height," +
37         "width=device-width," +
38         "initial-scale=1.0," +
39         "minimum-scale=1.0," +
40         "maximum-scale=1.0," +
41         "\" />";
42
43     private static final String TEXTAREA_ID = "textarea";
44     private static final String TEXTAREA_DATA_URL = UrlUtils.encodeHtmlDataUri(
45             "<html><head>" + META_DISABLE_ZOOM + "</head><body>" +
46             "<textarea id=\"" + TEXTAREA_ID + "\" cols=\"20\" rows=\"10\">" +
47             "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor " +
48             "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " +
49             "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute " +
50             "irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " +
51             "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " +
52             "officia deserunt mollit anim id est laborum." +
53             "</textarea>" +
54             "</body></html>");
55
56     private static final String INPUT_TEXT_ID = "input_text";
57     private static final String INPUT_TEXT_DATA_URL = UrlUtils.encodeHtmlDataUri(
58             "<html><head>" + META_DISABLE_ZOOM + "</head><body>" +
59             "<input id=\"input_text\" type=\"text\" value=\"" +
60             "T0D0(cjhopman): put amusing sample text here. Make sure it is at least " +
61             "100 characters.  123456789012345678901234567890\" size=20></input>" +
62             "</body></html>");
63
64     // Offset to compensate for the fact that the handle is below the text.
65     private static final int VERTICAL_OFFSET = 10;
66     private static final int HANDLE_POSITION_TOLERANCE = 20;
67     private static final String PASTE_TEXT = "**test text to paste**";
68
69
70     public void launchWithUrl(String url) throws Throwable {
71         launchContentShellWithUrl(url);
72         assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
73         assertWaitForPageScaleFactorMatch(1.0f);
74
75         // The TestInputMethodManagerWrapper intercepts showSoftInput so that a keyboard is never
76         // brought up.
77         getContentViewCore().getImeAdapterForTest().setInputMethodManagerWrapper(
78                 new TestInputMethodManagerWrapper(getContentViewCore()));
79     }
80
81     @MediumTest
82     @Feature({"TextSelection", "TextInput", "Main"})
83     public void testUnselectHidesHandle() throws Throwable {
84         launchWithUrl(TEXTAREA_DATA_URL);
85         clickNodeToShowInsertionHandle(TEXTAREA_ID);
86
87         // Unselecting should cause the handle to disappear.
88         unselectOnMainSync();
89         assertTrue(waitForHandleShowingEquals(false));
90     }
91
92
93     /**
94      * @MediumTest
95      * @Feature({"TextSelection", "TextInput", "Main"})
96      * http://crbug.com/169648
97      */
98     @DisabledTest
99     public void testKeyEventHidesHandle() throws Throwable {
100         launchWithUrl(TEXTAREA_DATA_URL);
101         clickNodeToShowInsertionHandle(TEXTAREA_ID);
102
103         getInstrumentation().sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_X));
104         getInstrumentation().sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_X));
105         assertTrue(waitForHandleShowingEquals(false));
106     }
107
108     /**
109      * @MediumTest
110      * @Feature({"TextSelection", "TextInput", "Main"})
111      * http://crbug.com/169648
112      */
113     @DisabledTest
114     public void testDragInsertionHandle() throws Throwable {
115         launchWithUrl(TEXTAREA_DATA_URL);
116
117         clickNodeToShowInsertionHandle(TEXTAREA_ID);
118
119         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
120         HandleView handle = ihc.getHandleViewForTest();
121
122         int initialX = handle.getPositionX();
123         int initialY = handle.getPositionY();
124         int dragToX = initialX + 120;
125         int dragToY = initialY + 120;
126
127         dragHandleTo(dragToX, dragToY);
128         assertWaitForHandleNear(dragToX, dragToY);
129     }
130
131
132     @MediumTest
133     @Feature({"TextSelection", "TextInput", "Main"})
134     public void testPasteAtInsertionHandle() throws Throwable {
135         launchWithUrl(TEXTAREA_DATA_URL);
136
137         clickNodeToShowInsertionHandle(TEXTAREA_ID);
138
139         int offset = getSelectionStart();
140         String initialText = getEditableText();
141
142         saveToClipboard(PASTE_TEXT);
143         pasteOnMainSync();
144
145         String expectedText =
146                initialText.substring(0, offset) + PASTE_TEXT + initialText.substring(offset);
147         assertTrue(waitForEditableTextEquals(expectedText));
148         assertTrue(waitForHandleShowingEquals(false));
149     }
150
151     /**
152      * @MediumTest
153      * @Feature({"TextSelection", "TextInput", "Main"})
154      * http://crbug.com/169648
155      */
156     @DisabledTest
157     public void testDragInsertionHandleInputText() throws Throwable {
158         launchWithUrl(INPUT_TEXT_DATA_URL);
159
160         clickNodeToShowInsertionHandle(INPUT_TEXT_ID);
161
162         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
163         HandleView handle = ihc.getHandleViewForTest();
164
165         int initialX = handle.getPositionX();
166         int initialY = handle.getPositionY();
167         int dragToX = initialX + 120;
168         int dragToY = initialY;
169         dragHandleTo(dragToX, dragToY);
170         assertWaitForHandleNear(dragToX, initialY);
171
172         TestTouchUtils.sleepForDoubleTapTimeout(getInstrumentation());
173
174         initialX = handle.getPositionX();
175         initialY = handle.getPositionY();
176         dragToY = initialY + 120;
177         dragHandleTo(initialX, dragToY);
178         // Vertical drag should not change the y-position.
179         assertWaitForHandleNear(initialX, initialY);
180     }
181
182     /**
183      * @MediumTest
184      * @Feature({"TextSelection", "TextInput", "Main"})
185      * http://crbug.com/169648
186      */
187     @DisabledTest
188     public void testDragInsertionHandleInputTextOutsideBounds() throws Throwable {
189         launchWithUrl(INPUT_TEXT_DATA_URL);
190
191         clickNodeToShowInsertionHandle(INPUT_TEXT_ID);
192
193         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
194         HandleView handle = ihc.getHandleViewForTest();
195
196         int initialX = handle.getPositionX();
197         int initialY = handle.getPositionY();
198         int dragToX = initialX;
199         int dragToY = initialY + 150;
200
201         // A vertical drag should not move the insertion handle.
202         dragHandleTo(dragToX, dragToY);
203         assertWaitForHandleNear(initialX, initialY);
204
205         // The input box does not go to the edge of the screen, and neither should the insertion
206         // handle.
207         dragToX = getContentView().getWidth();
208         dragHandleTo(dragToX, dragToY);
209         assertTrue(handle.getPositionX() < dragToX - 100);
210     }
211
212     /**
213      * Tests insertion handle visibility relative to the clipping rectangle.
214      * This is currently not implemented using dragHandleTo, because of issues with
215      * http://crbug.com/169648.
216      */
217     @MediumTest
218     @Feature({"TextSelection", "TextInput", "Main"})
219     public void testInsertionHandleVisiblity() throws Throwable {
220         launchWithUrl(TEXTAREA_DATA_URL);
221         clickNodeToShowInsertionHandle(TEXTAREA_ID);
222
223         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
224         HandleView handle = ihc.getHandleViewForTest();
225
226         assertTrue(handle.isPositionVisible());
227
228         ihc.setVisibleClippingRectangle(
229                 handle.getAdjustedPositionX() + 1, handle.getAdjustedPositionY() + 1,
230                 handle.getAdjustedPositionX() + 100, handle.getAdjustedPositionY() + 100);
231
232         assertFalse(handle.isPositionVisible());
233
234         ihc.setVisibleClippingRectangle(
235                 handle.getAdjustedPositionX() - 1, handle.getAdjustedPositionY() - 1,
236                 handle.getAdjustedPositionX() + 1, handle.getAdjustedPositionY() + 1);
237
238         assertTrue(handle.isPositionVisible());
239     }
240
241     @Override
242     protected void tearDown() throws Exception {
243         super.tearDown();
244         // No way to just clear clipboard, so setting to empty string instead.
245         saveToClipboard("");
246     }
247
248     private void clickNodeToShowInsertionHandle(String nodeId) throws Throwable {
249         // On the first click the keyboard will be displayed but no insertion handles. On the second
250         // click (only if it changes the selection), the insertion handle is displayed. So that the
251         // second click changes the selection, the two clicks should be in sufficiently different
252         // locations.
253         Rect nodeBounds = DOMUtils.getNodeBounds(getContentView(),
254                 new TestCallbackHelperContainer(getContentView()), nodeId);
255
256         RenderCoordinates renderCoordinates = getContentView().getRenderCoordinates();
257         int offsetX = getContentView().getContentViewCore().getViewportSizeOffsetWidthPix();
258         int offsetY = getContentView().getContentViewCore().getViewportSizeOffsetHeightPix();
259         float left = renderCoordinates.fromLocalCssToPix(nodeBounds.left) + offsetX;
260         float right = renderCoordinates.fromLocalCssToPix(nodeBounds.right) + offsetX;
261         float top = renderCoordinates.fromLocalCssToPix(nodeBounds.top) + offsetY;
262         float bottom = renderCoordinates.fromLocalCssToPix(nodeBounds.bottom) + offsetY;
263
264         TouchCommon touchCommon = new TouchCommon(this);
265         touchCommon.singleClickView(getContentView(),
266                 (int)(left + 3 * (right - left) / 4), (int)(top + (bottom - top) / 2));
267
268
269         TestTouchUtils.sleepForDoubleTapTimeout(getInstrumentation());
270         assertTrue(waitForHasSelectionPosition());
271
272         // TODO(cjhopman): Wait for keyboard display finished?
273         touchCommon.singleClickView(getContentView(),
274                 (int)(left + (right - left) / 4), (int)(top + (bottom - top) / 2));
275         assertTrue(waitForHandleShowingEquals(true));
276         assertTrue(waitForHandleViewStopped());
277     }
278
279     private boolean waitForHandleViewStopped() throws Throwable {
280         // If the polling interval is too short, slowly moving may be detected as not moving.
281         final int POLLING_INTERVAL = 200;
282         return CriteriaHelper.pollForCriteria(new Criteria() {
283             int mPositionX = -1;
284             int mPositionY = -1;
285             @Override
286             public boolean isSatisfied() {
287                 int lastPositionX = mPositionX;
288                 int lastPositionY = mPositionY;
289                 InsertionHandleController ihc =
290                         getContentViewCore().getInsertionHandleControllerForTest();
291                 HandleView handle = ihc.getHandleViewForTest();
292                 mPositionX = handle.getPositionX();
293                 mPositionY = handle.getPositionY();
294                 return !handle.isDragging() &&
295                         mPositionX == lastPositionX && mPositionY == lastPositionY;
296             }
297         }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL, POLLING_INTERVAL);
298     }
299
300     private boolean waitForEditableTextEquals(final String expectedText)
301             throws Throwable {
302         return CriteriaHelper.pollForCriteria(new Criteria() {
303             @Override
304             public boolean isSatisfied() {
305                 return getEditableText().trim().equals(expectedText.trim());
306             }
307         });
308     }
309
310     private boolean waitForHasSelectionPosition()
311             throws Throwable {
312         return CriteriaHelper.pollForCriteria(new Criteria() {
313             @Override
314             public boolean isSatisfied() {
315                 int start = getSelectionStart();
316                 int end = getSelectionEnd();
317                 return start > 0 && start == end;
318             }
319         });
320     }
321
322     private void dragHandleTo(int dragToX, int dragToY, int steps) throws Throwable {
323         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
324         HandleView handle = ihc.getHandleViewForTest();
325         int initialX = handle.getPositionX();
326         int initialY = handle.getPositionY();
327         ContentView view = getContentView();
328
329         int fromLocation[] = TestTouchUtils.getAbsoluteLocationFromRelative(view, initialX,
330                 initialY + VERTICAL_OFFSET);
331         int toLocation[] = TestTouchUtils.getAbsoluteLocationFromRelative(view, dragToX,
332                 dragToY + VERTICAL_OFFSET);
333
334         long downTime = TestTouchUtils.dragStart(getInstrumentation(), fromLocation[0],
335                 fromLocation[1]);
336         assertWaitForHandleDraggingEquals(true);
337         TestTouchUtils.dragTo(getInstrumentation(), fromLocation[0], toLocation[0],
338                 fromLocation[1], toLocation[1], steps, downTime);
339         TestTouchUtils.dragEnd(getInstrumentation(), toLocation[0], toLocation[1], downTime);
340         assertWaitForHandleDraggingEquals(false);
341         assertTrue(waitForHandleViewStopped());
342     }
343
344     private void dragHandleTo(int dragToX, int dragToY) throws Throwable {
345         dragHandleTo(dragToX, dragToY, 5);
346     }
347
348     private void assertWaitForHandleDraggingEquals(final boolean expected) throws Throwable {
349         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
350         final HandleView handle = ihc.getHandleViewForTest();
351         assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
352             @Override
353             public boolean isSatisfied() {
354                 return handle.isDragging() == expected;
355             }
356         }));
357     }
358
359     private static boolean isHandleNear(HandleView handle, int x, int y) {
360         return (Math.abs(handle.getPositionX() - x) < HANDLE_POSITION_TOLERANCE) &&
361                 (Math.abs(handle.getPositionY() - VERTICAL_OFFSET - y) < HANDLE_POSITION_TOLERANCE);
362     }
363
364     private void assertWaitForHandleNear(final int x, final int y) throws Throwable {
365         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
366         final HandleView handle = ihc.getHandleViewForTest();
367         assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
368             @Override
369             public boolean isSatisfied() {
370                 return isHandleNear(handle, x, y);
371             }
372         }));
373     }
374
375     private boolean waitForHandleShowingEquals(final boolean shouldBeShowing)
376             throws Throwable {
377         return CriteriaHelper.pollForCriteria(new Criteria() {
378             @Override
379             public boolean isSatisfied() {
380                 InsertionHandleController ihc =
381                         getContentViewCore().getInsertionHandleControllerForTest();
382                 boolean isShowing = ihc != null && ihc.isShowing();
383                 return isShowing == shouldBeShowing;
384             }
385         });
386     }
387
388     private void pasteOnMainSync() {
389         getInstrumentation().runOnMainSync(new Runnable() {
390             @Override
391             public void run() {
392                 getContentViewCore().getInsertionHandleControllerForTest().paste();
393             }
394         });
395     }
396
397     private void unselectOnMainSync() {
398         getInstrumentation().runOnMainSync(new Runnable() {
399             @Override
400             public void run() {
401                 getContentViewCore().getImeAdapterForTest().unselect();
402             }
403         });
404     }
405
406     private int getSelectionStart() {
407         return Selection.getSelectionStart(getEditable());
408     }
409
410     private int getSelectionEnd() {
411         return Selection.getSelectionEnd(getEditable());
412     }
413
414     private Editable getEditable() {
415         return getContentViewCore().getEditableForTest();
416     }
417
418     private String getEditableText() {
419         return getEditable().toString();
420     }
421
422     private void saveToClipboard(String text) {
423         ClipboardManager clipMgr =
424                 (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
425         clipMgr.setPrimaryClip(ClipData.newPlainText(null, text));
426     }
427 }