summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-31 12:47:41 +0000
committermkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-31 12:47:41 +0000
commitf3693f98ddc553151c930683586b6e7999f28299 (patch)
tree4552b432e015c0868ef5978accdb5694c26da9ca
parent5ecf612f9ad4b992b54bb710795e57c5d26012e7 (diff)
downloadchromium_src-f3693f98ddc553151c930683586b6e7999f28299.zip
chromium_src-f3693f98ddc553151c930683586b6e7999f28299.tar.gz
chromium_src-f3693f98ddc553151c930683586b6e7999f28299.tar.bz2
[android] Fire accessibility events when scrolling sublayers.
This enables the Android WebView to fire accessibility events for sub-layer scrolling. Currently this is only limited to touch-driven scrolling (JavaScript-initiated scrolling only works for the root layer at the moment). BUG=312318 android-only change, trybots are happy NOTRY=true Review URL: https://codereview.chromium.org/48973004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@232097 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--android_webview/java/src/org/chromium/android_webview/AwContents.java14
-rw-r--r--android_webview/java/src/org/chromium/android_webview/ScrollAccessibilityHelper.java79
-rw-r--r--android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java76
-rw-r--r--android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java32
-rw-r--r--content/browser/android/content_view_core_impl.cc8
-rw-r--r--content/browser/android/content_view_core_impl.h1
-rw-r--r--content/browser/renderer_host/render_widget_host_view_android.cc4
-rw-r--r--content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java20
8 files changed, 227 insertions, 7 deletions
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index 7032b72..d962d9f 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -155,6 +155,7 @@ public class AwContents {
private OverScrollGlow mOverScrollGlow;
// This can be accessed on any thread after construction. See AwContentsIoThreadClient.
private final AwSettings mSettings;
+ private final ScrollAccessibilityHelper mScrollAccessibilityHelper;
private boolean mIsPaused;
private boolean mIsViewVisible;
@@ -426,7 +427,6 @@ public class AwContents {
mScrollOffsetManager.onFlingStartGesture(velocityX, velocityY);
}
-
@Override
public void onFlingCancelGesture() {
mScrollOffsetManager.onFlingCancelGesture();
@@ -436,6 +436,11 @@ public class AwContents {
public void onUnhandledFlingStartEvent() {
mScrollOffsetManager.onUnhandledFlingStartEvent();
}
+
+ @Override
+ public void onScrollUpdateGestureConsumed() {
+ mScrollAccessibilityHelper.postViewScrolledAccessibilityEventCallback();
+ }
}
//--------------------------------------------------------------------------------------------
@@ -553,6 +558,7 @@ public class AwContents {
mSettings.setDIPScale(mDIPScale);
mScrollOffsetManager = new AwScrollOffsetManager(new AwScrollOffsetManagerDelegate(),
new OverScroller(mContainerView.getContext()));
+ mScrollAccessibilityHelper = new ScrollAccessibilityHelper(mContainerView);
setOverScrollMode(mContainerView.getOverScrollMode());
setScrollBarStyle(mInternalAccessAdapter.super_getScrollBarStyle());
@@ -1038,6 +1044,10 @@ public class AwContents {
* @see View#onScrollChanged(int,int)
*/
public void onContainerViewScrollChanged(int l, int t, int oldl, int oldt) {
+ // A side-effect of View.onScrollChanged is that the scroll accessibility event being sent
+ // by the base class implementation. This is completely hidden from the base classes and
+ // cannot be prevented, which is why we need the code below.
+ mScrollAccessibilityHelper.removePostedViewScrolledAccessibilityEventCallback();
mScrollOffsetManager.onContainerViewScrollChanged(l, t);
}
@@ -1585,6 +1595,8 @@ public class AwContents {
mComponentCallbacks = null;
}
+ mScrollAccessibilityHelper.removePostedCallbacks();
+
if (mPendingDetachCleanupReferences != null) {
for (int i = 0; i < mPendingDetachCleanupReferences.size(); ++i) {
mPendingDetachCleanupReferences.get(i).cleanupNow();
diff --git a/android_webview/java/src/org/chromium/android_webview/ScrollAccessibilityHelper.java b/android_webview/java/src/org/chromium/android_webview/ScrollAccessibilityHelper.java
new file mode 100644
index 0000000..d866dc9
--- /dev/null
+++ b/android_webview/java/src/org/chromium/android_webview/ScrollAccessibilityHelper.java
@@ -0,0 +1,79 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.android_webview;
+
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * Helper used to post the VIEW_SCROLLED accessibility event.
+ *
+ * TODO(mkosiba): Investigate whether this is behavior we want to share with the chrome/ layer.
+ * TODO(mkosiba): We currently don't handle JS-initiated scrolling for layers other than the root
+ * layer.
+ */
+class ScrollAccessibilityHelper {
+ // This is copied straight out of android.view.ViewConfiguration.
+ private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 100;
+
+ private class HandlerCallback implements Handler.Callback {
+ public static final int MSG_VIEW_SCROLLED = 1;
+
+ private View mEventSender;
+
+ public HandlerCallback(View eventSender) {
+ mEventSender = eventSender;
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch(msg.what) {
+ case MSG_VIEW_SCROLLED:
+ mMsgViewScrolledQueued = false;
+ mEventSender.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ break;
+ default:
+ throw new IllegalStateException(
+ "AccessibilityInjector: unhandled message: " + msg.what);
+ }
+ return true;
+ }
+ }
+
+ private Handler mHandler;
+ private boolean mMsgViewScrolledQueued;
+
+ public ScrollAccessibilityHelper(View eventSender) {
+ mHandler = new Handler(new HandlerCallback(eventSender));
+ }
+
+ /**
+ * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
+ * This event is sent at most once every
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
+ */
+ public void postViewScrolledAccessibilityEventCallback() {
+ if (mMsgViewScrolledQueued)
+ return;
+ mMsgViewScrolledQueued = true;
+
+ Message msg = mHandler.obtainMessage(HandlerCallback.MSG_VIEW_SCROLLED);
+ mHandler.sendMessageDelayed(msg, SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS);
+ }
+
+ public void removePostedViewScrolledAccessibilityEventCallback() {
+ if (!mMsgViewScrolledQueued)
+ return;
+ mMsgViewScrolledQueued = false;
+
+ mHandler.removeMessages(HandlerCallback.MSG_VIEW_SCROLLED);
+ }
+
+ public void removePostedCallbacks() {
+ removePostedViewScrolledAccessibilityEventCallback();
+ }
+}
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java
index 78e4ce7..452d4bf 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java
@@ -279,9 +279,6 @@ public class AndroidScrollIntegrationTest extends AwTestBase {
final int targetScrollYPix = (int) Math.ceil(targetScrollYCss * deviceDIPScale);
final JavascriptEventObserver onscrollObserver = new JavascriptEventObserver();
- Log.w("AndroidScrollIntegrationTest", String.format("scroll in Js (%d, %d) -> (%d, %d)",
- targetScrollXCss, targetScrollYCss, targetScrollXPix, targetScrollYPix));
-
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
@@ -690,4 +687,77 @@ public class AndroidScrollIntegrationTest extends AwTestBase {
break;
}
}
+
+ private static class TestGestureStateListener implements ContentViewCore.GestureStateListener {
+ private CallbackHelper mOnScrollUpdateGestureConsumedHelper = new CallbackHelper();
+
+ public CallbackHelper getOnScrollUpdateGestureConsumedHelper() {
+ return mOnScrollUpdateGestureConsumedHelper;
+ }
+
+ @Override
+ public void onPinchGestureStart() {
+ }
+
+ @Override
+ public void onPinchGestureEnd() {
+ }
+
+ @Override
+ public void onFlingStartGesture(int velocityX, int velocityY) {
+ }
+
+ @Override
+ public void onFlingCancelGesture() {
+ }
+
+ @Override
+ public void onUnhandledFlingStartEvent() {
+ }
+
+ @Override
+ public void onScrollUpdateGestureConsumed() {
+ mOnScrollUpdateGestureConsumedHelper.notifyCalled();
+ }
+ }
+
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testTouchScrollingConsumesScrollByGesture() throws Throwable {
+ final TestAwContentsClient contentsClient = new TestAwContentsClient();
+ final ScrollTestContainerView testContainerView =
+ (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
+ final TestGestureStateListener testGestureStateListener = new TestGestureStateListener();
+ enableJavaScriptOnUiThread(testContainerView.getAwContents());
+
+ final int dragSteps = 10;
+ final int dragStepSize = 24;
+ // Watch out when modifying - if the y or x delta aren't big enough vertical or horizontal
+ // scroll snapping will kick in.
+ final int targetScrollXPix = dragStepSize * dragSteps;
+ final int targetScrollYPix = dragStepSize * dragSteps;
+
+ loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null,
+ "<div>" +
+ " <div style=\"width:10000px; height: 10000px;\"> force scrolling </div>" +
+ "</div>");
+
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ testContainerView.getContentViewCore().setGestureStateListener(
+ testGestureStateListener);
+ }
+ });
+ final CallbackHelper onScrollUpdateGestureConsumedHelper =
+ testGestureStateListener.getOnScrollUpdateGestureConsumedHelper();
+
+ final int callCount = onScrollUpdateGestureConsumedHelper.getCallCount();
+ AwTestTouchUtils.dragCompleteView(testContainerView,
+ 0, -targetScrollXPix, // these need to be negative as we're scrolling down.
+ 0, -targetScrollYPix,
+ dragSteps,
+ null /* completionLatch */);
+ onScrollUpdateGestureConsumedHelper.waitForCallback(callCount);
+ }
}
diff --git a/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java b/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java
index e3c5077..6090143 100644
--- a/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java
+++ b/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java
@@ -8,14 +8,18 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.os.Bundle;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.FrameLayout;
-import android.util.Log;
import org.chromium.android_webview.AwContents;
import org.chromium.content.browser.ContentViewCore;
@@ -147,6 +151,32 @@ public class AwTestContainerView extends FrameLayout {
super.onDraw(canvas);
}
+ @Override
+ public AccessibilityNodeProvider getAccessibilityNodeProvider() {
+ AccessibilityNodeProvider provider =
+ mAwContents.getAccessibilityNodeProvider();
+ return provider == null ? super.getAccessibilityNodeProvider() : provider;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(AwContents.class.getName());
+ mAwContents.onInitializeAccessibilityNodeInfo(info);
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setClassName(AwContents.class.getName());
+ mAwContents.onInitializeAccessibilityEvent(event);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ return mAwContents.performAccessibilityAction(action, arguments);
+ }
+
// TODO: AwContents could define a generic class that holds an implementation similar to
// the one below.
private class InternalAccessAdapter implements AwContents.InternalAccessDelegate {
diff --git a/content/browser/android/content_view_core_impl.cc b/content/browser/android/content_view_core_impl.cc
index 8f09202..c423adb 100644
--- a/content/browser/android/content_view_core_impl.cc
+++ b/content/browser/android/content_view_core_impl.cc
@@ -492,6 +492,14 @@ void ContentViewCoreImpl::UnhandledFlingStartEvent() {
Java_ContentViewCore_unhandledFlingStartEvent(env, j_obj.obj());
}
+void ContentViewCoreImpl::OnScrollUpdateGestureConsumed() {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
+ if (j_obj.is_null())
+ return;
+ Java_ContentViewCore_onScrollUpdateGestureConsumed(env, j_obj.obj());
+}
+
void ContentViewCoreImpl::HasTouchEventHandlers(bool need_touch_events) {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
diff --git a/content/browser/android/content_view_core_impl.h b/content/browser/android/content_view_core_impl.h
index 116174c..f1e43cc 100644
--- a/content/browser/android/content_view_core_impl.h
+++ b/content/browser/android/content_view_core_impl.h
@@ -258,6 +258,7 @@ class ContentViewCoreImpl : public ContentViewCore,
bool HasFocus();
void ConfirmTouchEvent(InputEventAckState ack_result);
void UnhandledFlingStartEvent();
+ void OnScrollUpdateGestureConsumed();
void HasTouchEventHandlers(bool need_touch_events);
void OnSelectionChanged(const std::string& text);
void OnSelectionBoundsChanged(
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index ad0e396..50b8472 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -1027,6 +1027,10 @@ void RenderWidgetHostViewAndroid::UnhandledWheelEvent(
void RenderWidgetHostViewAndroid::GestureEventAck(
int gesture_event_type,
InputEventAckState ack_result) {
+ if (gesture_event_type == WebKit::WebInputEvent::GestureScrollUpdate &&
+ ack_result == INPUT_EVENT_ACK_STATE_CONSUMED) {
+ content_view_core_->OnScrollUpdateGestureConsumed();
+ }
if (gesture_event_type == WebKit::WebInputEvent::GestureFlingStart &&
ack_result == INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS) {
content_view_core_->UnhandledFlingStartEvent();
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
index e4075f5..026f9b4 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
@@ -188,8 +188,8 @@ public class ContentViewCore
}
/**
- * An interface that allows the embedder to be notified when the pinch gesture starts and
- * stops.
+ * An interface that allows the embedder to be notified of events and state changes related to
+ * gesture processing.
*/
public interface GestureStateListener {
/**
@@ -216,6 +216,14 @@ public class ContentViewCore
* Called when a fling event was not handled by the renderer.
*/
void onUnhandledFlingStartEvent();
+
+ /**
+ * Called to indicate that a scroll update gesture had been consumed by the page.
+ * This callback is called whenever any layer is scrolled (like a frame or div). It is
+ * not called when a JS touch handler consumes the event (preventDefault), it is not called
+ * for JS-initiated scrolling.
+ */
+ void onScrollUpdateGestureConsumed();
}
/**
@@ -1307,6 +1315,14 @@ public class ContentViewCore
}
}
+ @SuppressWarnings("unused")
+ @CalledByNative
+ private void onScrollUpdateGestureConsumed() {
+ if (mGestureStateListener != null) {
+ mGestureStateListener.onScrollUpdateGestureConsumed();
+ }
+ }
+
@Override
public boolean sendGesture(int type, long timeMs, int x, int y, Bundle b) {
if (offerGestureToEmbedder(type)) return false;