Bug 1182208 - Add support for android scrolling and range accessibility actions. r=mfinkle r=yzen
authorEitan Isaacson <eitan@monotonous.org>
Fri, 21 Aug 2015 11:32:57 -0700
changeset 291480 581329d6602b9dc55cb16f0c089557e981214ed5
parent 291479 95c5738f4cec950cd584b79432257a75ecad307d
child 291481 4c14cdd926f9969fa875dd2d9d9d69274615fc64
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle, yzen
bugs1182208
milestone43.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 1182208 - Add support for android scrolling and range accessibility actions. r=mfinkle r=yzen
accessible/jsat/AccessFu.jsm
accessible/jsat/ContentControl.jsm
accessible/tests/mochitest/jsat/jsatcommon.js
accessible/tests/mochitest/jsat/test_content_integration.html
mobile/android/base/GeckoAccessibility.java
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -129,16 +129,18 @@ this.AccessFu = { // jshint ignore:line
 
     Services.obs.addObserver(this, 'remote-browser-shown', false);
     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
     Services.obs.addObserver(this, 'Accessibility:NextObject', false);
     Services.obs.addObserver(this, 'Accessibility:PreviousObject', false);
     Services.obs.addObserver(this, 'Accessibility:Focus', false);
     Services.obs.addObserver(this, 'Accessibility:ActivateObject', false);
     Services.obs.addObserver(this, 'Accessibility:LongPress', false);
+    Services.obs.addObserver(this, 'Accessibility:ScrollForward', false);
+    Services.obs.addObserver(this, 'Accessibility:ScrollBackward', false);
     Services.obs.addObserver(this, 'Accessibility:MoveByGranularity', false);
     Utils.win.addEventListener('TabOpen', this);
     Utils.win.addEventListener('TabClose', this);
     Utils.win.addEventListener('TabSelect', this);
 
     if (this.readyCallback) {
       this.readyCallback();
       delete this.readyCallback;
@@ -182,16 +184,18 @@ this.AccessFu = { // jshint ignore:line
 
     Services.obs.removeObserver(this, 'remote-browser-shown');
     Services.obs.removeObserver(this, 'inprocess-browser-shown');
     Services.obs.removeObserver(this, 'Accessibility:NextObject');
     Services.obs.removeObserver(this, 'Accessibility:PreviousObject');
     Services.obs.removeObserver(this, 'Accessibility:Focus');
     Services.obs.removeObserver(this, 'Accessibility:ActivateObject');
     Services.obs.removeObserver(this, 'Accessibility:LongPress');
+    Services.obs.removeObserver(this, 'Accessibility:ScrollForward');
+    Services.obs.removeObserver(this, 'Accessibility:ScrollBackward');
     Services.obs.removeObserver(this, 'Accessibility:MoveByGranularity');
 
     delete this._quicknavModesPref;
     delete this._notifyOutputPref;
 
     if (this.doneCallback) {
       this.doneCallback();
       delete this.doneCallback;
@@ -310,16 +314,22 @@ this.AccessFu = { // jshint ignore:line
         this.Input.moveCursor('movePrevious', 'Simple', 'gesture');
         break;
       case 'Accessibility:ActivateObject':
         this.Input.activateCurrent(JSON.parse(aData));
         break;
       case 'Accessibility:LongPress':
         this.Input.sendContextMenuMessage();
         break;
+      case 'Accessibility:ScrollForward':
+        this.Input.androidScroll('forward');
+        break;
+      case 'Accessibility:ScrollBackward':
+        this.Input.androidScroll('backward');
+        break;
       case 'Accessibility:Focus':
         this._focused = JSON.parse(aData);
         if (this._focused) {
           this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
         }
         break;
       case 'Accessibility:MoveByGranularity':
         this.Input.moveByGranularity(JSON.parse(aData));
@@ -832,16 +842,22 @@ var Input = {
   moveCursor: function moveCursor(aAction, aRule, aInputType, aAdjustRange) {
     let mm = Utils.getMessageManager(Utils.CurrentBrowser);
     mm.sendAsyncMessage('AccessFu:MoveCursor',
                         { action: aAction, rule: aRule,
                           origin: 'top', inputType: aInputType,
                           adjustRange: aAdjustRange });
   },
 
+  androidScroll: function androidScroll(aDirection) {
+    let mm = Utils.getMessageManager(Utils.CurrentBrowser);
+    mm.sendAsyncMessage('AccessFu:AndroidScroll',
+                        { direction: aDirection, origin: 'top' });
+  },
+
   moveByGranularity: function moveByGranularity(aDetails) {
     const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
 
     if (!this.editState.editing) {
       if (aDetails.granularity === MOVEMENT_GRANULARITY_PARAGRAPH) {
         this.moveCursor('move' + aDetails.direction, 'Paragraph', 'gesture');
         return;
       }
--- a/accessible/jsat/ContentControl.jsm
+++ b/accessible/jsat/ContentControl.jsm
@@ -33,17 +33,18 @@ this.ContentControl = function ContentCo
 
 this.ContentControl.prototype = {
   messagesOfInterest: ['AccessFu:MoveCursor',
                        'AccessFu:ClearCursor',
                        'AccessFu:MoveToPoint',
                        'AccessFu:AutoMove',
                        'AccessFu:Activate',
                        'AccessFu:MoveCaret',
-                       'AccessFu:MoveByGranularity'],
+                       'AccessFu:MoveByGranularity',
+                       'AccessFu:AndroidScroll'],
 
   start: function cc_start() {
     let cs = this._contentScope.get();
     for (let message of this.messagesOfInterest) {
       cs.addMessageListener(message, this);
     }
     cs.addEventListener('mousemove', this);
   },
@@ -86,16 +87,37 @@ this.ContentControl.prototype = {
         Logger.warning('ContentControl: Unhandled message:', aMessage.name);
       }
     } catch (x) {
       Logger.logException(
         x, 'Error handling message: ' + JSON.stringify(aMessage.json));
     }
   },
 
+  handleAndroidScroll: function cc_handleAndroidScroll(aMessage) {
+    let vc = this.vc;
+    let position = vc.position;
+
+    if (aMessage.json.origin != 'child' && this.sendToChild(vc, aMessage)) {
+      // Forwarded succesfully to child cursor.
+      return;
+    }
+
+    // Counter-intuitive, but scrolling backward (ie. up), actually should
+    // increase range values.
+    if (this.adjustRange(position, aMessage.json.direction === 'backward')) {
+      return;
+    }
+
+    this._contentScope.get().sendAsyncMessage('AccessFu:DoScroll',
+      { bounds: Utils.getBounds(position, true),
+        page: aMessage.json.direction === 'forward' ? 1 : -1,
+        horizontal: false });
+  },
+
   handleMoveCursor: function cc_handleMoveCursor(aMessage) {
     let origin = aMessage.json.origin;
     let action = aMessage.json.action;
     let adjustRange = aMessage.json.adjustRange;
     let vc = this.vc;
 
     if (origin != 'child' && this.sendToChild(vc, aMessage)) {
       // Forwarded succesfully to child cursor.
--- a/accessible/tests/mochitest/jsat/jsatcommon.js
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -386,16 +386,30 @@ var ContentMessages = {
         action: 'moveNext',
         inputType: 'gesture',
         rule: (aRule || 'Simple'),
         adjustRange: true
       }
     }
   },
 
+  androidScrollForward: function adjustUp() {
+    return {
+      name: 'AccessFu:AndroidScroll',
+      json: { origin: 'top', direction: 'forward' }
+    };
+  },
+
+  androidScrollBackward: function adjustDown() {
+    return {
+      name: 'AccessFu:AndroidScroll',
+      json: { origin: 'top', direction: 'backward' }
+    };
+  },
+
   focusSelector: function focusSelector(aSelector, aBlur) {
     return {
       name: 'AccessFuTest:Focus',
       json: {
         selector: aSelector,
         blur: aBlur
       }
     };
--- a/accessible/tests/mochitest/jsat/test_content_integration.html
+++ b/accessible/tests/mochitest/jsat/test_content_integration.html
@@ -76,16 +76,18 @@
            new ExpectedSwitchAction(false)],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['much range', '6', {'string': 'slider'}, 'such app'])],
           [ContentMessages.moveOrAdjustDown(), new ExpectedValueChange('5')],
+          [ContentMessages.androidScrollForward(), new ExpectedValueChange('6')],
+          [ContentMessages.androidScrollBackward(), new ExpectedValueChange('5')],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['much range', {'string': 'label'}])],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['many option', {'string': 'stateChecked'},
             {'string': 'checkbutton'}, {'string': 'listStart'},
             {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])],
           // uncheck checkbox
           [ContentMessages.activateCurrent(),
--- a/mobile/android/base/GeckoAccessibility.java
+++ b/mobile/android/base/GeckoAccessibility.java
@@ -343,16 +343,18 @@ public class GeckoAccessibility {
                                 info.setClassName(host.getClass().getName());
                                 info.setEnabled(true);
                                 info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
                                 info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
                                 info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
                                 info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
                                 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
                                 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+                                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+                                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
                                 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER |
                                                               AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD |
                                                               AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH);
                                 break;
                             }
                             return info;
                         }
 
@@ -383,16 +385,24 @@ public class GeckoAccessibility {
                             } else if (action == AccessibilityNodeInfo.ACTION_CLICK && virtualViewId == VIRTUAL_CURSOR_POSITION) {
                                 GeckoAppShell.
                                     sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:ActivateObject", null));
                                 return true;
                             } else if (action == AccessibilityNodeInfo.ACTION_LONG_CLICK && virtualViewId == VIRTUAL_CURSOR_POSITION) {
                                 GeckoAppShell.
                                     sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:LongPress", null));
                                 return true;
+                            } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD && virtualViewId == VIRTUAL_CURSOR_POSITION) {
+                                GeckoAppShell.
+                                    sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:ScrollForward", null));
+                                return true;
+                            } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD && virtualViewId == VIRTUAL_CURSOR_POSITION) {
+                                GeckoAppShell.
+                                    sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:ScrollBackward", null));
+                                return true;
                             } else if (action == AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY &&
                                        virtualViewId == VIRTUAL_CURSOR_POSITION) {
                                 // XXX: Self brailling gives this action with a bogus argument instead of an actual click action;
                                 // the argument value is the BRAILLE_CLICK_BASE_INDEX - the index of the routing key that was hit
                                 int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
                                 if (granularity < 0) {
                                     int keyIndex = BRAILLE_CLICK_BASE_INDEX - granularity;
                                     JSONObject activationData = new JSONObject();