Bug 1455749 - Advertise nodes as editable and focusable (1/2). r=jchen,yzen
authorEitan Isaacson <eitan@monotonous.org>
Fri, 11 May 2018 08:30:00 +0300
changeset 417998 b1089dc357a8f8cc5a940314d29a4ac54517ec56
parent 417997 ce9f1466ec789f59f904b81a3cbd9aa94b33deaf
child 417999 4345a33a957c8bda9e0a3b18f0d05e64a513cd28
push id33984
push usercbrindusan@mozilla.com
push dateSat, 12 May 2018 09:47:51 +0000
treeherdermozilla-central@809b0329507e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen, yzen
bugs1455749
milestone62.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 1455749 - Advertise nodes as editable and focusable (1/2). r=jchen,yzen
accessible/jsat/EventManager.jsm
accessible/jsat/Presentation.jsm
accessible/tests/mochitest/jsat/test_text_editable_navigation.html
accessible/tests/mochitest/jsat/test_text_editing.html
accessible/tests/mochitest/jsat/test_text_navigation_focus.html
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -259,17 +259,19 @@ this.EventManager.prototype = {
       {
         // Put vc where the focus is at
         let acc = aEvent.accessible;
         this._setEditingMode(aEvent);
         if (![Roles.CHROME_WINDOW,
              Roles.DOCUMENT,
              Roles.APPLICATION].includes(acc.role)) {
           this.contentControl.autoMove(acc);
-       }
+        }
+
+        this.present(Presentation.focused(acc));
 
        if (this.inTest) {
         this.sendMsgFunc("AccessFu:Focused");
        }
        break;
       }
       case Events.DOCUMENT_LOAD_COMPLETE:
       {
--- a/accessible/jsat/Presentation.jsm
+++ b/accessible/jsat/Presentation.jsm
@@ -60,39 +60,40 @@ class AndroidPresentor {
         androidEvents.push({
           eventType: AndroidEvents.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
           text: [adjustedText.text],
           fromIndex: adjustedText.startOffset,
           toIndex: adjustedText.endOffset
         });
       }
     } else {
-      let state = Utils.getState(context.accessible);
-      androidEvents.push({eventType: (isExploreByTouch) ?
-                           AndroidEvents.VIEW_HOVER_ENTER : focusEventType,
-                         text: Utils.localize(UtteranceGenerator.genForContext(
-                           context)),
-                         bounds: context.bounds,
-                         clickable: context.accessible.actionCount > 0,
-                         checkable: state.contains(States.CHECKABLE),
-                         checked: state.contains(States.CHECKED)});
+      let info = this._infoFromContext(context);
+      let eventType = isExploreByTouch ?
+        AndroidEvents.VIEW_HOVER_ENTER : focusEventType;
+      androidEvents.push({...info, eventType});
     }
 
     try {
       context.accessibleForBounds.scrollTo(
         Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
     } catch (e) {}
 
     if (context.accessible) {
       this.displayedAccessibles.set(context.accessible.document.window, context);
     }
 
     return androidEvents;
   }
 
+  focused(aObject) {
+    let info = this._infoFromContext(
+      new PivotContext(aObject, null, -1, -1, true, false));
+    return [{ eventType: AndroidEvents.VIEW_FOCUSED, ...info }];
+  }
+
   /**
    * An object's action has been invoked.
    * @param {nsIAccessible} aObject the object that has been invoked.
    * @param {string} aActionName the name of the action.
    */
   actionInvoked(aObject, aActionName) {
     let state = Utils.getState(aObject);
 
@@ -294,11 +295,25 @@ class AndroidPresentor {
    * @param  {string} aModifiedText Optional modified text.
    */
   liveRegion(aAccessible, aIsPolite, aIsHide, aModifiedText) {
     let context = !aModifiedText ?
       new PivotContext(aAccessible, null, -1, -1, true, !!aIsHide) : null;
     return this.announce(
       UtteranceGenerator.genForLiveRegion(context, aIsHide, aModifiedText));
   }
+
+  _infoFromContext(aContext) {
+    let state = Utils.getState(aContext.accessible);
+    return {
+      text: Utils.localize(UtteranceGenerator.genForContext(aContext)),
+      bounds: aContext.bounds,
+      focusable: state.contains(States.FOCUSABLE),
+      focused: state.contains(States.FOCUSED),
+      clickable: aContext.accessible.actionCount > 0,
+      checkable: state.contains(States.CHECKABLE),
+      checked: state.contains(States.CHECKED),
+      editable: state.contains(States.EDITABLE),
+    };
+  }
 }
 
 const Presentation = new AndroidPresentor();
--- a/accessible/tests/mochitest/jsat/test_text_editable_navigation.html
+++ b/accessible/tests/mochitest/jsat/test_text_editable_navigation.html
@@ -31,25 +31,25 @@
       is(aSelectionEvent.toIndex, aTo, "Caret offset (toIndex)");
     }
 
     async function testEditableTextNavigation(doc, runner) {
       // Editable text tests.
       let evt;
 
       evt = await runner.focusSelector("textarea",
-        AndroidEvents.ANNOUNCEMENT,
+        AndroidEvents.VIEW_FOCUSED,
         AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED,
         AndroidEvents.VIEW_TEXT_SELECTION_CHANGED);
-      // XXX: Get rid of announcements, and send focus events instead
-      runner.eventTextMatches(evt[0], ["editing"]);
+      is(evt[0].editable, true, "focused item is editable");
       runner.eventTextMatches(evt[1],
         ["Text content test document",
          "Please refrain from Mayoneggs during this salmonella scare.",
          "text area"]);
+      is(evt[1].focused, true, "a11y focused item is focused");
       is(evt[2].fromIndex, 0, "Correct fromIndex");
       is(evt[2].toIndex, 0, "Correct toIndex");
 
       evt = await runner.activateCurrent(10,
         AndroidEvents.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
         AndroidEvents.VIEW_TEXT_SELECTION_CHANGED);
       checkMoveCaret(...evt, 0, 10);
 
@@ -83,18 +83,18 @@
         AndroidEvents.VIEW_TEXT_SELECTION_CHANGED);
       checkMoveCaret(...evt, 38, 59);
 
       evt = await runner.moveCaretPrevious(MovementGranularity.WORD,
         AndroidEvents.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
         AndroidEvents.VIEW_TEXT_SELECTION_CHANGED);
       checkMoveCaret(...evt, 59, 53);
 
-      evt = await runner.blur(AndroidEvents.ANNOUNCEMENT);
-      runner.eventTextMatches(evt, ["navigating"]);
+      evt = await runner.blur(AndroidEvents.VIEW_FOCUSED);
+      is(evt.editable, false, "Focused out of editable");
     }
 
     function doTest() {
       var doc = currentTabDocument();
 
       addA11yLoadEvent(async function() {
         let runner = new AccessFuContentTestRunner();
         await runner.start();
--- a/accessible/tests/mochitest/jsat/test_text_editing.html
+++ b/accessible/tests/mochitest/jsat/test_text_editing.html
@@ -32,22 +32,22 @@
         runner.eventTextMatches(testSelEvent, text);
         is(testSelEvent.toIndex, insertIndex + addedCount);
         is(testSelEvent.fromIndex, insertIndex + addedCount);
       }
 
       let evt;
 
       evt = await runner.focusSelector("input",
-        AndroidEvents.ANNOUNCEMENT,
+        AndroidEvents.VIEW_FOCUSED,
         AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED,
         AndroidEvents.VIEW_TEXT_SELECTION_CHANGED);
-      // XXX: Get rid of announcements, and send focus events instead
-      runner.eventTextMatches(evt[0], ["editing"]);
+      is(evt[0].editable, true, "focused item is editable");
       runner.eventTextMatches(evt[1], ["Text content test document", "entry"]);
+      is(evt[1].focused, true, "a11y focused item is focused");
       is(evt[2].fromIndex, 0, "Caret at start (fromIndex)");
       is(evt[2].toIndex, 0, "Caret at start (toIndex)");
 
       evt = await runner.typeKey("B",
         AndroidEvents.VIEW_TEXT_CHANGED,
         AndroidEvents.VIEW_TEXT_SELECTION_CHANGED,
         "todo.value-changed");
       checkInsert(evt[0], evt[1], ["B"], 0, 1);
@@ -101,18 +101,18 @@
       checkInsert(evt[0], evt[1], ["Bob Lobla"], 8, 1);
 
       evt = await runner.typeKey("w",
         AndroidEvents.VIEW_TEXT_CHANGED,
         AndroidEvents.VIEW_TEXT_SELECTION_CHANGED,
         "todo.value-changed");
       checkInsert(evt[0], evt[1], ["Bob Loblaw"], 9, 1);
 
-      evt = await runner.blur(AndroidEvents.ANNOUNCEMENT);
-      runner.eventTextMatches(evt, ["navigating"]);
+      evt = await runner.blur(AndroidEvents.VIEW_FOCUSED);
+      is(evt.editable, false, "Focused out of editable");
     }
 
 
     function doTest() {
       var doc = currentTabDocument();
 
       addA11yLoadEvent(async function() {
         let runner = new AccessFuContentTestRunner();
--- a/accessible/tests/mochitest/jsat/test_text_navigation_focus.html
+++ b/accessible/tests/mochitest/jsat/test_text_navigation_focus.html
@@ -23,66 +23,66 @@
   <script type="application/javascript" src="../layout.js"></script>
   <script type="application/javascript" src="jsatcommon.js"></script>
 
   <script type="application/javascript">
     async function testTextNavigationFocus(doc, runner) {
       let evt;
 
       evt = await runner.focusSelector("textarea",
-        AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED,
-        AndroidEvents.ANNOUNCEMENT);
-      // XXX: Get rid of announcements, and send focus events instead
-      runner.eventTextMatches(evt[0],
+        AndroidEvents.VIEW_FOCUSED,
+        AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED);
+      is(evt[0].editable, true, "focused item is editable");
+      is(evt[1].focused, true, "a11y focused item is focused");
+      runner.eventTextMatches(evt[1],
         ["Text content test document",
          "Please refrain from Mayoneggs during this salmonella scare.",
          "text area"]);
-      runner.eventTextMatches(evt[1], ["editing"]);
 
       evt = await runner.moveNext("Simple",
-        AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED,
-        AndroidEvents.ANNOUNCEMENT);
-      runner.eventTextMatches(evt[0], ["So we don't get dessert?", "label"]);
-      runner.eventTextMatches(evt[1], ["navigating"]);
+        AndroidEvents.VIEW_FOCUSED,
+        AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED);
+      is(evt[0].editable, false, "focused out of editable");
+      runner.eventTextMatches(evt[1], ["So we don't get dessert?", "label"]);
       runner.isFocused("html");
 
       evt = await runner.moveNext("Simple",
         AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED);
       runner.eventTextMatches(evt, ["entry"]);
       runner.isFocused("html");
 
       evt = await runner.activateCurrent(0,
         AndroidEvents.VIEW_CLICKED,
-        AndroidEvents.ANNOUNCEMENT,
+        AndroidEvents.VIEW_FOCUSED,
         AndroidEvents.VIEW_TEXT_SELECTION_CHANGED);
-      runner.eventTextMatches(evt[1], ["editing"]);
+      is(evt[1].editable, true, "focused item is editable");
       is(evt[2].fromIndex, 0, "Cursor at start");
       runner.isFocused("input[type=text]");
 
       evt = await runner.movePrevious("Simple",
-        AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED,
-        AndroidEvents.ANNOUNCEMENT);
-      runner.eventTextMatches(evt[0], ["So we don't get dessert?", "label"]);
-      runner.eventTextMatches(evt[1], ["navigating"]);
+        AndroidEvents.VIEW_FOCUSED,
+        AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED);
+      is(evt[0].editable, false, "focused out of editable");
+      runner.eventTextMatches(evt[1], ["So we don't get dessert?", "label"]);
       runner.isFocused("html");
 
       evt = await runner.moveNext("Simple",
         AndroidEvents.VIEW_ACCESSIBILITY_FOCUSED);
       runner.eventTextMatches(evt, ["entry"]);
       runner.isFocused("html");
 
       // XXX: TEXT_SELECTION_CHANGED should be fired here
       evt = await runner.activateCurrent(0,
         AndroidEvents.VIEW_CLICKED,
-        AndroidEvents.ANNOUNCEMENT);
-      runner.eventTextMatches(evt[1], ["editing"]);
+        AndroidEvents.VIEW_FOCUSED);
+      is(evt[1].editable, true, "focused item is editable");
       runner.isFocused("input[type=text]");
 
-      evt = await runner.blur(AndroidEvents.ANNOUNCEMENT);
-      runner.eventTextMatches(evt, ["navigating"]);
+      evt = await runner.blur(AndroidEvents.VIEW_FOCUSED);
+      is(evt.editable, false, "Focused out of editable");
     }
 
 
     function doTest() {
       var doc = currentTabDocument();
 
       addA11yLoadEvent(async function() {
         let runner = new AccessFuContentTestRunner();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/SessionAccessibility.java
@@ -351,16 +351,19 @@ public class SessionAccessibility {
         event.setMaxScrollY(message.getInt("maxScrollY", -1));
     }
 
     private void populateNodeInfoFromJSON(AccessibilityNodeInfo node, final GeckoBundle message) {
         node.setEnabled(message.getBoolean("enabled", true));
         node.setCheckable(message.getBoolean("checkable"));
         node.setChecked(message.getBoolean("checked"));
         node.setPassword(message.getBoolean("password"));
+        node.setFocusable(message.getBoolean("focusable"));
+        node.setFocused(message.getBoolean("focused"));
+        node.setEditable(message.getBoolean("editable"));
 
         final String[] textArray = message.getStringArray("text");
         StringBuilder sb = new StringBuilder();
         if (textArray != null && textArray.length > 0) {
             sb.append(textArray[0] != null ? textArray[0] : "");
             for (int i = 1; i < textArray.length; i++) {
                 sb.append(' ').append(textArray[i] != null ? textArray[i] : "");
             }
@@ -413,17 +416,20 @@ public class SessionAccessibility {
                 return;
             }
 
             if (exitView.equals("movePrevious")) {
                 eventSource = View.NO_ID;
             }
         }
 
-        if (eventSource != View.NO_ID) {
+        if (eventSource != View.NO_ID &&
+                (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED ||
+                 eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED ||
+                 eventType == AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)) {
             // In Jelly Bean we populate an AccessibilityNodeInfo with the minimal amount of data to have
             // it work with TalkBack.
             if (mVirtualContentNode == null) {
                 mVirtualContentNode = AccessibilityNodeInfo.obtain(mView, eventSource);
             }
             populateNodeInfoFromJSON(mVirtualContentNode, message);
         }