Bug 1518315 - Clear accessibility focus correctly. r=yzen
authorEitan Isaacson <eitan@monotonous.org>
Mon, 14 Jan 2019 20:05:05 +0000
changeset 513777 a7f3a2c57318fe4cea11857b2fa5bee1411bc96c
parent 513776 bb6af9b92cdf9aa3becad03e149ee1c265805452
child 513778 c5ab98eeb9158bf7838878e9156f1aa4d9126baa
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyzen
bugs1518315
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1518315 - Clear accessibility focus correctly. r=yzen Differential Revision: https://phabricator.services.mozilla.com/D15872
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/AccessibilityTest.kt
@@ -88,16 +88,17 @@ class AccessibilityTest : BaseSessionTes
                         AccessibilityNodeInfo::class.java.getMethod(
                                 "getChildId", Int::class.java).invoke(this, index) as Long
                     else
                         (AccessibilityNodeInfo::class.java.getMethod("getChildNodeIds")
                                 .invoke(this) as SparseLongArray).get(index))
 
     private interface EventDelegate {
         fun onAccessibilityFocused(event: AccessibilityEvent) { }
+        fun onAccessibilityFocusCleared(event: AccessibilityEvent) { }
         fun onClicked(event: AccessibilityEvent) { }
         fun onFocused(event: AccessibilityEvent) { }
         fun onSelected(event: AccessibilityEvent) { }
         fun onScrolled(event: AccessibilityEvent) { }
         fun onTextSelectionChanged(event: AccessibilityEvent) { }
         fun onTextChanged(event: AccessibilityEvent) { }
         fun onTextTraversal(event: AccessibilityEvent) { }
         fun onWinContentChanged(event: AccessibilityEvent) { }
@@ -120,16 +121,17 @@ class AccessibilityTest : BaseSessionTes
         sessionRule.addExternalDelegateUntilTestEnd(
             EventDelegate::class,
         { newDelegate -> (view.parent as View).setAccessibilityDelegate(object : View.AccessibilityDelegate() {
             override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, event: AccessibilityEvent): Boolean {
                 when (event.eventType) {
                     AccessibilityEvent.TYPE_VIEW_FOCUSED -> newDelegate.onFocused(event)
                     AccessibilityEvent.TYPE_VIEW_CLICKED -> newDelegate.onClicked(event)
                     AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED -> newDelegate.onAccessibilityFocused(event)
+                    AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED -> newDelegate.onAccessibilityFocusCleared(event)
                     AccessibilityEvent.TYPE_VIEW_SELECTED -> newDelegate.onSelected(event)
                     AccessibilityEvent.TYPE_VIEW_SCROLLED -> newDelegate.onScrolled(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> newDelegate.onTextSelectionChanged(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> newDelegate.onTextChanged(event)
                     AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY -> newDelegate.onTextTraversal(event)
                     AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> newDelegate.onWinContentChanged(event)
                     AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> newDelegate.onWinStateChanged(event)
                     else -> {}
@@ -187,34 +189,48 @@ class AccessibilityTest : BaseSessionTes
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
                 assertThat("Label accessibility focused", node.className.toString(),
                         equalTo("android.view.View"))
                 assertThat("Text node should not be focusable", node.isFocusable, equalTo(false))
+                assertThat("Text node should be a11y focused", node.isAccessibilityFocused, equalTo(true))
                 assertThat("Text node should not be clickable", node.isClickable, equalTo(false))
             }
         })
 
         provider.performAction(nodeId,
             AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
 
         sessionRule.waitUntilCalled(object : EventDelegate {
             @AssertCalled(count = 1)
             override fun onAccessibilityFocused(event: AccessibilityEvent) {
                 nodeId = getSourceId(event)
                 val node = createNodeInfo(nodeId)
                 assertThat("Editbox accessibility focused", node.className.toString(),
                         equalTo("android.widget.EditText"))
                 assertThat("Entry node should be focusable", node.isFocusable, equalTo(true))
+                assertThat("Entry node should be a11y focused", node.isAccessibilityFocused, equalTo(true))
                 assertThat("Entry node should be clickable", node.isClickable, equalTo(true))
             }
         })
+
+        provider.performAction(nodeId,
+                AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
+
+        sessionRule.waitUntilCalled(object : EventDelegate {
+            @AssertCalled(count = 1)
+            override fun onAccessibilityFocusCleared(event: AccessibilityEvent) {
+                assertThat("Accessibility focused node is now cleared", getSourceId(event), equalTo(nodeId))
+                val node = createNodeInfo(nodeId)
+                assertThat("Entry node should node be a11y focused", node.isAccessibilityFocused, equalTo(false))
+            }
+        })
     }
 
     @Test fun testTextEntryNode() {
         sessionRule.session.loadString("<input aria-label='Name' value='Tobias'>", "text/html")
         waitForInitialFocus()
 
         mainSession.evaluateJS("$('input').focus()")
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
@@ -113,20 +113,16 @@ public class SessionAccessibility {
 
     /* package */ final class NodeProvider extends AccessibilityNodeProvider {
         @Override
         public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualDescendantId) {
             AccessibilityNodeInfo node = null;
             if (mAttached) {
                 node = mSession.getSettings().getFullAccessibilityTree() ?
                         getNodeFromGecko(virtualDescendantId) : getNodeFromCache(virtualDescendantId);
-                if (node != null) {
-                    node.setAccessibilityFocused(mAccessibilityFocusedNode == virtualDescendantId);
-                    node.setFocused(mFocusedNode == virtualDescendantId);
-                }
             }
 
             if (node == null) {
                 Log.w(LOGTAG, "Failed to retrieve accessible node virtualDescendantId=" +
                         virtualDescendantId + " mAttached=" + mAttached);
                 node = AccessibilityNodeInfo.obtain(mView, View.NO_ID);
                 if (Build.VERSION.SDK_INT < 17 || mView.getDisplay() != null) {
                     // When running junit tests we don't have a display
@@ -138,16 +134,19 @@ public class SessionAccessibility {
             return node;
         }
 
         @Override
         public boolean performAction(final int virtualViewId, int action, Bundle arguments) {
             final GeckoBundle data;
 
             switch (action) {
+            case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+                sendEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED, virtualViewId, CLASSNAME_VIEW, null);
+                return true;
             case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
                     if (virtualViewId == View.NO_ID) {
                         sendEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, View.NO_ID, CLASSNAME_WEBVIEW, null);
                     } else {
                         final GeckoBundle nodeInfo = nativeProvider.getNodeInfo(virtualViewId);
                         final int flags = nodeInfo != null ? nodeInfo.getInt("flags") : 0;
                         if ((flags & FLAG_FOCUSED) != 0) {
                             mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityCursorToFocused", null);
@@ -291,17 +290,18 @@ public class SessionAccessibility {
             }
         }
 
         private void populateNodeFromBundle(final AccessibilityNodeInfo node, final GeckoBundle nodeInfo, final boolean fromCache) {
             if (mView == null || nodeInfo == null) {
                 return;
             }
 
-            boolean isRoot = nodeInfo.getInt("id") == View.NO_ID;
+            final int id = nodeInfo.getInt("id");
+            boolean isRoot = id == View.NO_ID;
             if (isRoot) {
                 if (Build.VERSION.SDK_INT < 17 || mView.getDisplay() != null) {
                     // When running junit tests we don't have a display
                     mView.onInitializeAccessibilityNodeInfo(node);
                 }
                 node.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
                 node.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
             } else {
@@ -316,18 +316,16 @@ public class SessionAccessibility {
 
             if (nodeInfo.containsKey("text")) {
                 node.setText(nodeInfo.getString("text"));
             }
 
             // Add actions
             node.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
             node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
-            node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
-            node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
             node.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
             node.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
             node.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
                     AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
                     AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE |
                     AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
             if ((flags & FLAG_CLICKABLE) != 0) {
                 node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
@@ -343,16 +341,24 @@ public class SessionAccessibility {
             node.setLongClickable((flags & FLAG_LONG_CLICKABLE) != 0);
             node.setPassword((flags & FLAG_PASSWORD) != 0);
             node.setScrollable((flags & FLAG_SCROLLABLE) != 0);
             node.setSelected((flags & FLAG_SELECTED) != 0);
             node.setVisibleToUser((flags & FLAG_VISIBLE_TO_USER) != 0);
             // Other boolean properties to consider later:
             // setHeading, setImportantForAccessibility, setScreenReaderFocusable, setShowingHintText, setDismissable
 
+            if (mAccessibilityFocusedNode == id) {
+                node.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+                node.setAccessibilityFocused(true);
+            } else {
+                node.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            }
+            node.setFocused(mFocusedNode == id);
+
             // Bounds
             int[] b = nodeInfo.getIntArray("bounds");
             if (b != null) {
                 final Rect screenBounds = new Rect(b[0], b[1], b[2], b[3]);
                 node.setBoundsInScreen(screenBounds);
 
                 final Matrix matrix = new Matrix();
                 mSession.getClientToScreenMatrix(matrix);
@@ -709,16 +715,21 @@ public class SessionAccessibility {
                 if (cachedBundle != null && eventData != null && eventData.containsKey("selected")) {
                     if (eventData.getInt("selected") != 0) {
                         cachedBundle.putInt("flags", cachedBundle.getInt("flags") | FLAG_SELECTED);
                     } else {
                         cachedBundle.putInt("flags", cachedBundle.getInt("flags") & ~FLAG_SELECTED);
                     }
                 }
                 break;
+            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
+                if (mAccessibilityFocusedNode == sourceId) {
+                    mAccessibilityFocusedNode = 0;
+                }
+                break;
             case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
                 mAccessibilityFocusedNode = sourceId;
                 break;
             case AccessibilityEvent.TYPE_VIEW_FOCUSED:
                 mFocusedNode = sourceId;
                 break;
         }