This source file includes following definitions.
- EDITABLE
- NONEDITABLE
- launchWithUrl
- assertWaitForHasSelectionPosition
- testNoneditableSelectionHandles
- Feature
- testEditableSelectionHandles
- Feature
- testEditableSelectionHandlesStartNotVisibleEndVisible
- Feature
- testEditableSelectionHandlesStartVisibleEndNotVisible
- Feature
- testEditableSelectionHandlesStartVisibleEndVisible
- Feature
- testEditableSelectionHandlesStartNotVisibleEndNotVisible
- doSelectionHandleTestVisibility
- doSelectionHandleTest
- dragHandleAndCheckSelectionChange
- assertWaitForSelectionEditableEquals
- assertWaitForHandleViewStopped
- Feature
- testNoneditableSelectionActionBar
- Feature
- testEditableSelectionActionBar
- doSelectionActionBarTest
- getHandlePosition
- isHandleNear
- assertWaitForHandleNear
- assertWaitForEitherHandleNear
- assertWaitForHandlesShowingEquals
- dragHandleTo
- getNodeBoundsPix
- clickNodeToShowSelectionHandles
- clickToDismissHandles
- assertWaitForSelectActionBarShowingEquals
- assertWaitForHasInputConnection
- getImeAdapter
- getSelectionStart
- getSelectionEnd
- getEditable
- getStartHandle
- getEndHandle
package org.chromium.content.browser.input;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.SystemClock;
import android.test.FlakyTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.text.Editable;
import android.text.Selection;
import android.view.MotionEvent;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.content.browser.ContentView;
import org.chromium.content.browser.RenderCoordinates;
import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.browser.test.util.DOMUtils;
import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
import org.chromium.content.browser.test.util.TestTouchUtils;
import org.chromium.content.browser.test.util.TouchCommon;
import org.chromium.content_shell_apk.ContentShellTestBase;
import java.util.concurrent.Callable;
public class SelectionHandleTest extends ContentShellTestBase {
private static final String META_DISABLE_ZOOM =
"<meta name=\"viewport\" content=\"" +
"height=device-height," +
"width=device-width," +
"initial-scale=1.0," +
"minimum-scale=1.0," +
"maximum-scale=1.0," +
"\" />";
private static final String TEXTAREA_ID = "textarea";
private static final String TEXTAREA_DATA_URL = UrlUtils.encodeHtmlDataUri(
"<html><head>" + META_DISABLE_ZOOM + "</head><body>" +
"<textarea id=\"" + TEXTAREA_ID +
"\" cols=\"40\" rows=\"20\" style=\"font-size:6px\">" +
"L r m i s m d l r s t a e , c n e t t r a i i i i g e i , s d d e u m d t m o " +
"i c d d n u l b r e d l r m g a l q a U e i a m n m e i m q i n s r d " +
"e e c t t o u l m o a o i n s u a i u p x a o m d c n e u t D i a t " +
"i u e o o i r p e e d r t n o u t t v l t s e i l m o o e u u i t u l " +
"p r a u . x e t u s n o c e a c p d t t o p o d n , u t n u p q i " +
"o f c a e e u t o l t n m d s l b r m." +
"L r m i s m d l r s t a e , c n e t t r a i i i i g e i , s d d e u m d t m o " +
"i c d d n u l b r e d l r m g a l q a U e i a m n m e i m q i n s r d " +
"e e c t t o u l m o a o i n s u a i u p x a o m d c n e u t D i a t " +
"i u e o o i r p e e d r t n o u t t v l t s e i l m o o e u u i t u l " +
"p r a u . x e t u s n o c e a c p d t t o p o d n , u t n u p q i " +
"o f c a e e u t o l t n m d s l b r m." +
"</textarea>" +
"</body></html>");
private static final String NONEDITABLE_DIV_ID = "noneditable";
private static final String NONEDITABLE_DATA_URL = UrlUtils.encodeHtmlDataUri(
"<html><head>" + META_DISABLE_ZOOM + "</head><body>" +
"<div id=\"" + NONEDITABLE_DIV_ID + "\" style=\"width:200; font-size:6px\">" +
"L r m i s m d l r s t a e , c n e t t r a i i i i g e i , s d d e u m d t m o " +
"i c d d n u l b r e d l r m g a l q a U e i a m n m e i m q i n s r d " +
"e e c t t o u l m o a o i n s u a i u p x a o m d c n e u t D i a t " +
"i u e o o i r p e e d r t n o u t t v l t s e i l m o o e u u i t u l " +
"p r a u . x e t u s n o c e a c p d t t o p o d n , u t n u p q i " +
"o f c a e e u t o l t n m d s l b r m." +
"L r m i s m d l r s t a e , c n e t t r a i i i i g e i , s d d e u m d t m o " +
"i c d d n u l b r e d l r m g a l q a U e i a m n m e i m q i n s r d " +
"e e c t t o u l m o a o i n s u a i u p x a o m d c n e u t D i a t " +
"i u e o o i r p e e d r t n o u t t v l t s e i l m o o e u u i t u l " +
"p r a u . x e t u s n o c e a c p d t t o p o d n , u t n u p q i " +
"o f c a e e u t o l t n m d s l b r m." +
"</div>" +
"</body></html>");
private static final int HANDLE_POSITION_X_TOLERANCE_PIX = 20;
private static final int HANDLE_POSITION_Y_TOLERANCE_PIX = 30;
private enum TestPageType {
EDITABLE(TEXTAREA_ID, TEXTAREA_DATA_URL, true),
NONEDITABLE(NONEDITABLE_DIV_ID, NONEDITABLE_DATA_URL, false);
final String nodeId;
final String dataUrl;
final boolean selectionShouldBeEditable;
TestPageType(String nodeId, String dataUrl, boolean selectionShouldBeEditable) {
this.nodeId = nodeId;
this.dataUrl = dataUrl;
this.selectionShouldBeEditable = selectionShouldBeEditable;
}
}
private void launchWithUrl(String url) throws Throwable {
launchContentShellWithUrl(url);
assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
assertWaitForPageScaleFactorMatch(1.0f);
getImeAdapter().setInputMethodManagerWrapper(
new TestInputMethodManagerWrapper(getContentViewCore()));
}
private void assertWaitForHasSelectionPosition()
throws Throwable {
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
int start = getSelectionStart();
int end = getSelectionEnd();
return start > 0 && start == end;
}
}));
}
@FlakyTest
public void testNoneditableSelectionHandles() throws Throwable {
doSelectionHandleTest(TestPageType.NONEDITABLE);
}
@MediumTest
@Feature({ "TextSelection" })
public void testEditableSelectionHandles() throws Throwable {
doSelectionHandleTest(TestPageType.EDITABLE);
}
@MediumTest
@Feature({ "TextSelection" })
public void testEditableSelectionHandlesStartNotVisibleEndVisible() throws Throwable {
doSelectionHandleTestVisibility(TestPageType.EDITABLE, false, true, new PointF(.5f, .5f),
new PointF(0,1));
}
@MediumTest
@Feature({ "TextSelection" })
public void testEditableSelectionHandlesStartVisibleEndNotVisible() throws Throwable {
doSelectionHandleTestVisibility(TestPageType.EDITABLE, true, false, new PointF(1,0),
new PointF(.5f, .5f));
}
@MediumTest
@Feature({ "TextSelection" })
public void testEditableSelectionHandlesStartVisibleEndVisible() throws Throwable {
doSelectionHandleTestVisibility(TestPageType.EDITABLE, true, true, new PointF(1, 0),
new PointF(0,1));
}
@MediumTest
@Feature({ "TextSelection" })
public void testEditableSelectionHandlesStartNotVisibleEndNotVisible() throws Throwable {
doSelectionHandleTestVisibility(TestPageType.EDITABLE, false, false, new PointF(1,0),
new PointF(1, 0));
}
private void doSelectionHandleTestVisibility(TestPageType pageType,
boolean startHandleVisible, boolean endHandleVisible,
PointF affineTopLeft, PointF affineBottomRight) throws Throwable {
launchWithUrl(pageType.dataUrl);
clickNodeToShowSelectionHandles(pageType.nodeId);
assertWaitForSelectionEditableEquals(pageType.selectionShouldBeEditable);
HandleView startHandle = getStartHandle();
HandleView endHandle = getEndHandle();
Rect nodeWindowBounds = getNodeBoundsPix(pageType.nodeId);
int visibleBoundsLeftX = Math.round(affineTopLeft.x * nodeWindowBounds.left
+ affineTopLeft.y * nodeWindowBounds.right);
int visibleBoundsTopY = Math.round(affineTopLeft.x * nodeWindowBounds.top
+ affineTopLeft.y * nodeWindowBounds.bottom);
int visibleBoundsRightX = Math.round(affineBottomRight.x * nodeWindowBounds.left
+ affineBottomRight.y * nodeWindowBounds.right);
int visibleBoundsBottomY = Math.round(affineBottomRight.x * nodeWindowBounds.top
+ affineBottomRight.y * nodeWindowBounds.bottom);
getContentViewCore().getSelectionHandleControllerForTest().setVisibleClippingRectangle(
visibleBoundsLeftX, visibleBoundsTopY, visibleBoundsRightX, visibleBoundsBottomY);
int leftX = (nodeWindowBounds.left + nodeWindowBounds.centerX()) / 2;
int rightX = (nodeWindowBounds.right + nodeWindowBounds.centerX()) / 2;
int topY = (nodeWindowBounds.top + nodeWindowBounds.centerY()) / 2;
int bottomY = (nodeWindowBounds.bottom + nodeWindowBounds.centerY()) / 2;
dragHandleAndCheckSelectionChange(startHandle, leftX, topY, -1, 0, startHandleVisible);
dragHandleAndCheckSelectionChange(endHandle, rightX, bottomY, 0, 1, endHandleVisible);
clickToDismissHandles();
}
private void doSelectionHandleTest(TestPageType pageType) throws Throwable {
launchWithUrl(pageType.dataUrl);
clickNodeToShowSelectionHandles(pageType.nodeId);
assertWaitForSelectionEditableEquals(pageType.selectionShouldBeEditable);
HandleView startHandle = getStartHandle();
HandleView endHandle = getEndHandle();
Rect nodeWindowBounds = getNodeBoundsPix(pageType.nodeId);
int leftX = (nodeWindowBounds.left + nodeWindowBounds.centerX()) / 2;
int centerX = nodeWindowBounds.centerX();
int rightX = (nodeWindowBounds.right + nodeWindowBounds.centerX()) / 2;
int topY = (nodeWindowBounds.top + nodeWindowBounds.centerY()) / 2;
int centerY = nodeWindowBounds.centerY();
int bottomY = (nodeWindowBounds.bottom + nodeWindowBounds.centerY()) / 2;
dragHandleAndCheckSelectionChange(startHandle, leftX, topY, -1, 0, true);
dragHandleAndCheckSelectionChange(endHandle, rightX, bottomY, 0, 1, true);
dragHandleAndCheckSelectionChange(startHandle, centerX, centerY, 1, 0, true);
dragHandleAndCheckSelectionChange(endHandle, leftX, topY, -1, -1, true);
dragHandleAndCheckSelectionChange(startHandle, rightX, bottomY, 1, 1, true);
clickToDismissHandles();
}
private void dragHandleAndCheckSelectionChange(final HandleView handle,
final int dragToX, final int dragToY,
final int expectedStartChange, final int expectedEndChange,
final boolean expectedHandleVisible) throws Throwable {
String initialText = getContentViewCore().getSelectedText();
final int initialSelectionEnd = getSelectionEnd();
final int initialSelectionStart = getSelectionStart();
dragHandleTo(handle, dragToX, dragToY, 10);
assertWaitForEitherHandleNear(dragToX, dragToY);
if (getContentViewCore().isSelectionEditable()) {
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
int startChange = getSelectionStart() - initialSelectionStart;
if (expectedStartChange != 0) {
if ((int) Math.signum(startChange) != expectedStartChange) return false;
}
int endChange = getSelectionEnd() - initialSelectionEnd;
if (expectedEndChange != 0) {
if ((int) Math.signum(endChange) != expectedEndChange) return false;
}
if (expectedHandleVisible != handle.isPositionVisible()) return false;
return true;
}
}));
}
assertWaitForHandleViewStopped(getStartHandle());
assertWaitForHandleViewStopped(getEndHandle());
}
private void assertWaitForSelectionEditableEquals(final boolean expected) throws Throwable {
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return getContentViewCore().isSelectionEditable() == expected;
}
}));
}
private void assertWaitForHandleViewStopped(final HandleView handle) throws Throwable {
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
private Point position = new Point(-1, -1);
@Override
public boolean isSatisfied() {
Point lastPosition = position;
position = getHandlePosition(handle);
return !handle.isDragging() &&
position.equals(lastPosition);
}
}));
}
@MediumTest
@Feature({ "TextSelection" })
public void testNoneditableSelectionActionBar() throws Throwable {
doSelectionActionBarTest(TestPageType.NONEDITABLE);
}
@MediumTest
@Feature({ "TextSelection" })
public void testEditableSelectionActionBar() throws Throwable {
doSelectionActionBarTest(TestPageType.EDITABLE);
}
private void doSelectionActionBarTest(TestPageType pageType) throws Throwable {
launchWithUrl(pageType.dataUrl);
assertFalse(getContentViewCore().isSelectActionBarShowing());
clickNodeToShowSelectionHandles(pageType.nodeId);
assertWaitForSelectActionBarShowingEquals(true);
clickToDismissHandles();
assertWaitForSelectActionBarShowingEquals(false);
}
private static Point getHandlePosition(final HandleView handle) {
return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Point>() {
@Override
public Point call() {
return new Point(handle.getAdjustedPositionX(), handle.getAdjustedPositionY());
}
});
}
private static boolean isHandleNear(HandleView handle, int x, int y) {
Point position = getHandlePosition(handle);
return (Math.abs(position.x - x) < HANDLE_POSITION_X_TOLERANCE_PIX) &&
(Math.abs(position.y - y) < HANDLE_POSITION_Y_TOLERANCE_PIX);
}
private void assertWaitForHandleNear(final HandleView handle, final int x, final int y)
throws Throwable {
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return isHandleNear(handle, x, y);
}
}));
}
private void assertWaitForEitherHandleNear(final int x, final int y) throws Throwable {
final HandleView startHandle = getStartHandle();
final HandleView endHandle = getEndHandle();
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return isHandleNear(startHandle, x, y) || isHandleNear(endHandle, x, y);
}
}));
}
private void assertWaitForHandlesShowingEquals(final boolean shouldBeShowing) throws Throwable {
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
SelectionHandleController shc =
getContentViewCore().getSelectionHandleControllerForTest();
boolean isShowing = shc != null && shc.isShowing();
return shouldBeShowing == isShowing;
}
}));
}
private void dragHandleTo(final HandleView handle, final int dragToX, final int dragToY,
final int steps) throws Throwable {
ContentView view = getContentView();
assertTrue(ThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
@Override
public Boolean call() {
int adjustedX = handle.getAdjustedPositionX();
int adjustedY = handle.getAdjustedPositionY();
int realX = handle.getPositionX();
int realY = handle.getPositionY();
int realDragToX = dragToX + (realX - adjustedX);
int realDragToY = dragToY + (realY - adjustedY);
ContentView view = getContentView();
int[] fromLocation = TestTouchUtils.getAbsoluteLocationFromRelative(
view, realX, realY);
int[] toLocation = TestTouchUtils.getAbsoluteLocationFromRelative(
view, realDragToX, realDragToY);
long downTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
fromLocation[0], fromLocation[1], 0);
handle.dispatchTouchEvent(event);
if (!handle.isDragging()) return false;
for (int i = 0; i < steps; i++) {
float scale = (float) (i + 1) / steps;
int x = fromLocation[0] + (int) (scale * (toLocation[0] - fromLocation[0]));
int y = fromLocation[1] + (int) (scale * (toLocation[1] - fromLocation[1]));
long eventTime = SystemClock.uptimeMillis();
event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
x, y, 0);
handle.dispatchTouchEvent(event);
}
long upTime = SystemClock.uptimeMillis();
event = MotionEvent.obtain(downTime, upTime, MotionEvent.ACTION_UP,
toLocation[0], toLocation[1], 0);
handle.dispatchTouchEvent(event);
return !handle.isDragging();
}
}));
}
private Rect getNodeBoundsPix(String nodeId) throws Throwable {
Rect nodeBounds = DOMUtils.getNodeBounds(getContentView(),
new TestCallbackHelperContainer(getContentView()), nodeId);
RenderCoordinates renderCoordinates = getContentView().getRenderCoordinates();
int offsetX = getContentView().getContentViewCore().getViewportSizeOffsetWidthPix();
int offsetY = getContentView().getContentViewCore().getViewportSizeOffsetHeightPix();
int left = (int) renderCoordinates.fromLocalCssToPix(nodeBounds.left) + offsetX;
int right = (int) renderCoordinates.fromLocalCssToPix(nodeBounds.right) + offsetX;
int top = (int) renderCoordinates.fromLocalCssToPix(nodeBounds.top) + offsetY;
int bottom = (int) renderCoordinates.fromLocalCssToPix(nodeBounds.bottom) + offsetY;
return new Rect(left, top, right, bottom);
}
private void clickNodeToShowSelectionHandles(String nodeId) throws Throwable {
Rect nodeWindowBounds = getNodeBoundsPix(nodeId);
TouchCommon touchCommon = new TouchCommon(this);
int centerX = nodeWindowBounds.centerX();
int centerY = nodeWindowBounds.centerY();
touchCommon.longPressView(getContentView(), centerX, centerY);
assertWaitForHandlesShowingEquals(true);
assertWaitForHandleViewStopped(getStartHandle());
assertEquals(getStartHandle().getPositionY(), getEndHandle().getPositionY());
}
private void clickToDismissHandles() throws Throwable {
TestTouchUtils.sleepForDoubleTapTimeout(getInstrumentation());
new TouchCommon(this).singleClickView(getContentView(), 0, 0);
assertWaitForHandlesShowingEquals(false);
}
private void assertWaitForSelectActionBarShowingEquals(final boolean shouldBeShowing)
throws InterruptedException {
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return shouldBeShowing == getContentViewCore().isSelectActionBarShowing();
}
}));
}
public void assertWaitForHasInputConnection() {
try {
assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
@Override
public boolean isSatisfied() {
return getContentViewCore().getInputConnectionForTest() != null;
}
}));
} catch (InterruptedException e) {
fail();
}
}
private ImeAdapter getImeAdapter() {
return getContentViewCore().getImeAdapterForTest();
}
private int getSelectionStart() {
return Selection.getSelectionStart(getEditable());
}
private int getSelectionEnd() {
return Selection.getSelectionEnd(getEditable());
}
private Editable getEditable() {
assertWaitForHasInputConnection();
return getContentViewCore().getEditableForTest();
}
private HandleView getStartHandle() {
SelectionHandleController shc = getContentViewCore().getSelectionHandleControllerForTest();
return shc.getStartHandleViewForTest();
}
private HandleView getEndHandle() {
SelectionHandleController shc = getContentViewCore().getSelectionHandleControllerForTest();
return shc.getEndHandleViewForTest();
}
}