Merge "Handle WebKit selection changes synchronously."

This commit is contained in:
Svetoslav Ganov
2013-02-05 21:47:17 +00:00
committed by Android (Google) Code Review
4 changed files with 144 additions and 90 deletions

View File

@@ -318,12 +318,15 @@ class AccessibilityInjector {
/**
* Attempts to handle selection change events when accessibility is using a
* non-JavaScript method.
* <p>
* This must not be called from the main thread.
*
* @param selectionString The selection string.
* @param selection The selection string.
* @param token The selection request token.
*/
public void handleSelectionChangedIfNecessary(String selectionString) {
public void onSelectionStringChangedWebCoreThread(String selection, int token) {
if (mAccessibilityInjectorFallback != null) {
mAccessibilityInjectorFallback.onSelectionStringChange(selectionString);
mAccessibilityInjectorFallback.onSelectionStringChangedWebCoreThread(selection, token);
}
}

View File

@@ -27,8 +27,9 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.webkit.WebViewCore.EventHub;
import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.Stack;
/**
* This class injects accessibility into WebViews with disabled JavaScript or
@@ -48,8 +49,7 @@ import java.util.Stack;
* </p>
* The possible actions are invocations to
* {@link #setCurrentAxis(int, boolean, String)}, or
* {@link #traverseCurrentAxis(int, boolean, String)}
* {@link #traverseGivenAxis(int, int, boolean, String)}
* {@link #traverseGivenAxis(int, int, boolean, String, boolean)}
* {@link #performAxisTransition(int, int, boolean, String)}
* referred via the values of:
* {@link #ACTION_SET_CURRENT_AXIS},
@@ -74,6 +74,9 @@ class AccessibilityInjectorFallback {
private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
/** Timeout after which asynchronous granular movement is aborted. */
private static final int MODIFY_SELECTION_TIMEOUT = 500;
// WebView navigation axes from WebViewCore.h, plus an additional axis for
// the default behavior.
private static final int NAVIGATION_AXIS_CHARACTER = 0;
@@ -81,7 +84,8 @@ class AccessibilityInjectorFallback {
private static final int NAVIGATION_AXIS_SENTENCE = 2;
@SuppressWarnings("unused")
private static final int NAVIGATION_AXIS_HEADING = 3;
private static final int NAVIGATION_AXIS_SIBLING = 5;
@SuppressWarnings("unused")
private static final int NAVIGATION_AXIS_SIBLING = 4;
@SuppressWarnings("unused")
private static final int NAVIGATION_AXIS_PARENT_FIRST_CHILD = 5;
private static final int NAVIGATION_AXIS_DOCUMENT = 6;
@@ -99,8 +103,11 @@ class AccessibilityInjectorFallback {
private final WebViewClassic mWebView;
private final WebView mWebViewInternal;
// events scheduled for sending as soon as we receive the selected text
private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
// Event scheduled for sending as soon as we receive the selected text.
private AccessibilityEvent mScheduledEvent;
// Token required to send the scheduled event.
private int mScheduledToken = 0;
// the current traversal axis
private int mCurrentAxis = 2; // sentence
@@ -114,6 +121,15 @@ class AccessibilityInjectorFallback {
// keep track of last direction
private int mLastDirection;
// Lock used for asynchronous selection callback.
private final Object mCallbackLock = new Object();
// Whether the asynchronous selection callback was received.
private boolean mCallbackReceived;
// Whether the asynchronous selection callback succeeded.
private boolean mCallbackResult;
/**
* Creates a new injector associated with a given {@link WebViewClassic}.
*
@@ -174,8 +190,8 @@ class AccessibilityInjectorFallback {
}
mLastDirection = direction;
sendEvent = (binding.getSecondArgument(i) == 1);
mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
contentDescription);
mLastDownEventHandled = traverseGivenAxis(
direction, mCurrentAxis, sendEvent, contentDescription, false);
break;
case ACTION_TRAVERSE_GIVEN_AXIS:
direction = binding.getFirstArgument(i);
@@ -187,7 +203,7 @@ class AccessibilityInjectorFallback {
mLastDirection = direction;
axis = binding.getSecondArgument(i);
sendEvent = (binding.getThirdArgument(i) == 1);
traverseGivenAxis(direction, axis, sendEvent, contentDescription);
traverseGivenAxis(direction, axis, sendEvent, contentDescription, false);
mLastDownEventHandled = true;
break;
case ACTION_PERFORM_AXIS_TRANSITION:
@@ -207,7 +223,7 @@ class AccessibilityInjectorFallback {
mLastDirection = binding.getFirstArgument(i);
sendEvent = (binding.getSecondArgument(i) == 1);
traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
sendEvent, contentDescription);
sendEvent, contentDescription, false);
mLastDownEventHandled = false;
} else {
mLastDownEventHandled = true;
@@ -222,8 +238,7 @@ class AccessibilityInjectorFallback {
}
/**
* Set the current navigation axis which will be used while
* calling {@link #traverseCurrentAxis(int, boolean, String)}.
* Set the current navigation axis.
*
* @param axis The axis to set.
* @param sendEvent Whether to send an accessibility event to
@@ -255,20 +270,6 @@ class AccessibilityInjectorFallback {
}
}
/**
* Traverse the document along the current navigation axis.
*
* @param direction The direction of traversal.
* @param sendEvent Whether to send an accessibility event to
* announce the change.
* @param contentDescription A description of the performed action.
* @see #setCurrentAxis(int, boolean, String)
*/
private boolean traverseCurrentAxis(int direction, boolean sendEvent,
String contentDescription) {
return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
}
boolean performAccessibilityAction(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
@@ -276,14 +277,14 @@ class AccessibilityInjectorFallback {
final int direction = getDirectionForAction(action);
final int axis = getAxisForGranularity(arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
return traverseGivenAxis(direction, axis, true, null);
return traverseGivenAxis(direction, axis, true, null, true);
}
case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: {
final int direction = getDirectionForAction(action);
// TODO: Add support for moving by object.
final int axis = NAVIGATION_AXIS_SENTENCE;
return traverseGivenAxis(direction, axis, true, null);
return traverseGivenAxis(direction, axis, true, null, true);
}
default:
return false;
@@ -293,7 +294,7 @@ class AccessibilityInjectorFallback {
/**
* Returns the {@link WebView}-defined direction for the given
* {@link AccessibilityNodeInfo}-defined action.
*
*
* @param action An accessibility action identifier.
* @return A web view navigation direction.
*/
@@ -313,7 +314,7 @@ class AccessibilityInjectorFallback {
/**
* Returns the {@link WebView}-defined axis for the given
* {@link AccessibilityNodeInfo}-defined granularity.
*
*
* @param granularity An accessibility granularity identifier.
* @return A web view navigation axis.
*/
@@ -345,20 +346,20 @@ class AccessibilityInjectorFallback {
* @param contentDescription A description of the performed action.
*/
private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
String contentDescription) {
WebViewCore webViewCore = mWebView.getWebViewCore();
String contentDescription, boolean sychronous) {
final WebViewCore webViewCore = mWebView.getWebViewCore();
if (webViewCore == null) {
return false;
}
AccessibilityEvent event = null;
if (sendEvent) {
event = getPartialyPopulatedAccessibilityEvent(
final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
// the text will be set upon receiving the selection string
// The text will be set upon receiving the selection string.
event.setContentDescription(contentDescription);
mScheduledEvent = event;
mScheduledToken++;
}
mScheduledEventStack.push(event);
// if the axis is the default let WebView handle the event which will
// result in cursor ring movement and selection of its content
@@ -366,27 +367,78 @@ class AccessibilityInjectorFallback {
return false;
}
webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
return true;
final SomeArgs args = SomeArgs.obtain();
args.argi1 = direction;
args.argi2 = axis;
args.argi3 = mScheduledToken;
// If we don't need synchronous results, just return true.
if (!sychronous) {
webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
return true;
}
final boolean callbackResult;
synchronized (mCallbackLock) {
mCallbackReceived = false;
// Asynchronously changes the selection in WebView, which responds by
// calling onSelectionStringChanged().
webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
try {
mCallbackLock.wait(MODIFY_SELECTION_TIMEOUT);
} catch (InterruptedException e) {
// Do nothing.
}
callbackResult = mCallbackResult;
}
return (mCallbackReceived && callbackResult);
}
/**
* Called when the <code>selectionString</code> has changed.
*/
public void onSelectionStringChange(String selectionString) {
if (DEBUG) {
Log.d(LOG_TAG, "Selection string: " + selectionString);
/* package */ void onSelectionStringChangedWebCoreThread(
final String selection, final int token) {
synchronized (mCallbackLock) {
mCallbackReceived = true;
mCallbackResult = (selection != null);
mCallbackLock.notifyAll();
}
mIsLastSelectionStringNull = (selectionString == null);
if (mScheduledEventStack.isEmpty()) {
// Managing state and sending events must take place on the UI thread.
mWebViewInternal.post(new Runnable() {
@Override
public void run() {
onSelectionStringChangedMainThread(selection, token);
}
});
}
private void onSelectionStringChangedMainThread(String selection, int token) {
if (DEBUG) {
Log.d(LOG_TAG, "Selection string: " + selection);
}
if (token != mScheduledToken) {
if (DEBUG) {
Log.d(LOG_TAG, "Selection string has incorrect token: " + token);
}
return;
}
AccessibilityEvent event = mScheduledEventStack.pop();
if ((event != null) && (selectionString != null)) {
event.getText().add(selectionString);
mIsLastSelectionStringNull = (selection == null);
final AccessibilityEvent event = mScheduledEvent;
mScheduledEvent = null;
if ((event != null) && (selection != null)) {
event.getText().add(selection);
event.setFromIndex(0);
event.setToIndex(selectionString.length());
event.setToIndex(selection.length());
sendAccessibilityEvent(event);
event.recycle();
}
}

View File

@@ -1024,30 +1024,26 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
static final int UPDATE_MATCH_COUNT = 126;
static final int CENTER_FIT_RECT = 127;
static final int SET_SCROLLBAR_MODES = 129;
static final int SELECTION_STRING_CHANGED = 130;
static final int HIT_TEST_RESULT = 131;
static final int SAVE_WEBARCHIVE_FINISHED = 132;
static final int SET_AUTOFILLABLE = 133;
static final int AUTOFILL_COMPLETE = 134;
static final int SCREEN_ON = 136;
static final int UPDATE_ZOOM_DENSITY = 139;
static final int EXIT_FULLSCREEN_VIDEO = 140;
static final int COPY_TO_CLIPBOARD = 141;
static final int INIT_EDIT_FIELD = 142;
static final int REPLACE_TEXT = 143;
static final int CLEAR_CARET_HANDLE = 144;
static final int KEY_PRESS = 145;
static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
static final int FOCUS_NODE_CHANGED = 147;
static final int AUTOFILL_FORM = 148;
static final int SCROLL_EDIT_TEXT = 149;
static final int EDIT_TEXT_SIZE_CHANGED = 150;
static final int SHOW_CARET_HANDLE = 151;
static final int UPDATE_CONTENT_BOUNDS = 152;
static final int SCROLL_HANDLE_INTO_VIEW = 153;
static final int HIT_TEST_RESULT = 130;
static final int SAVE_WEBARCHIVE_FINISHED = 131;
static final int SET_AUTOFILLABLE = 132;
static final int AUTOFILL_COMPLETE = 133;
static final int SCREEN_ON = 134;
static final int UPDATE_ZOOM_DENSITY = 135;
static final int EXIT_FULLSCREEN_VIDEO = 136;
static final int COPY_TO_CLIPBOARD = 137;
static final int INIT_EDIT_FIELD = 138;
static final int REPLACE_TEXT = 139;
static final int CLEAR_CARET_HANDLE = 140;
static final int KEY_PRESS = 141;
static final int RELOCATE_AUTO_COMPLETE_POPUP = 142;
static final int FOCUS_NODE_CHANGED = 143;
static final int AUTOFILL_FORM = 144;
static final int SCROLL_EDIT_TEXT = 145;
static final int EDIT_TEXT_SIZE_CHANGED = 146;
static final int SHOW_CARET_HANDLE = 147;
static final int UPDATE_CONTENT_BOUNDS = 148;
static final int SCROLL_HANDLE_INTO_VIEW = 149;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -1766,6 +1762,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
}
/* package */ void handleSelectionChangedWebCoreThread(String selection, int token) {
if (isAccessibilityInjectionEnabled()) {
getAccessibilityInjector().onSelectionStringChangedWebCoreThread(selection, token);
}
}
private boolean isAccessibilityInjectionEnabled() {
final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
if (!manager.isEnabled()) {
@@ -7496,13 +7498,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mVerticalScrollBarMode = msg.arg2;
break;
case SELECTION_STRING_CHANGED:
if (isAccessibilityInjectionEnabled()) {
getAccessibilityInjector()
.handleSelectionChangedIfNecessary((String) msg.obj);
}
break;
case FOCUS_NODE_CHANGED:
mIsEditingText = (msg.arg1 == mFieldPointer);
if (mAutoCompletePopup != null && !mIsEditingText) {

View File

@@ -41,6 +41,8 @@ import android.view.View;
import android.webkit.WebViewClassic.FocusNodeHref;
import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
import com.android.internal.os.SomeArgs;
import junit.framework.Assert;
import java.io.OutputStream;
@@ -1545,12 +1547,14 @@ public final class WebViewCore {
case MODIFY_SELECTION:
mTextSelectionChangeReason
= TextSelectionData.REASON_ACCESSIBILITY_INJECTOR;
String modifiedSelectionString =
nativeModifySelection(mNativeClass, msg.arg1,
msg.arg2);
mWebViewClassic.mPrivateHandler.obtainMessage(
WebViewClassic.SELECTION_STRING_CHANGED,
modifiedSelectionString).sendToTarget();
final SomeArgs args = (SomeArgs) msg.obj;
final String modifiedSelectionString = nativeModifySelection(
mNativeClass, args.argi1, args.argi2);
// If accessibility is on, the main thread may be
// waiting for a response. Send on webcore thread.
mWebViewClassic.handleSelectionChangedWebCoreThread(
modifiedSelectionString, args.argi3);
args.recycle();
mTextSelectionChangeReason
= TextSelectionData.REASON_UNKNOWN;
break;