Bug 886076 - Part 2: Support movement by granularity in AccessFu. r=eeejay
authorMax Li <maxli@maxli.ca>
Wed, 24 Jul 2013 17:52:57 -0400
changeset 152341 b34e301476781c0deb4e0d7cd1f82fa08856116f
parent 152340 784d2686a389c3ce2618bd7bcad4e83566d89002
child 152342 c8bc029b8553c0b5868d2ec15f8f38d6027aa447
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerseeejay
bugs886076
milestone25.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 886076 - Part 2: Support movement by granularity in AccessFu. r=eeejay
accessible/src/jsat/AccessFu.jsm
accessible/src/jsat/EventManager.jsm
accessible/src/jsat/Presentation.jsm
accessible/src/jsat/TraversalRules.jsm
accessible/src/jsat/Utils.jsm
accessible/src/jsat/content-script.js
mobile/android/base/GeckoAccessibility.java
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -111,17 +111,17 @@ this.AccessFu = {
     TouchAdapter.start();
 
     Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
     Services.obs.addObserver(this, 'in-process-browser-or-app-frame-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:MoveCaret', 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;
     }
@@ -154,17 +154,17 @@ this.AccessFu = {
     Utils.win.removeEventListener('TabSelect', this);
 
     Services.obs.removeObserver(this, 'remote-browser-frame-shown');
     Services.obs.removeObserver(this, 'in-process-browser-or-app-frame-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:MoveCaret');
+    Services.obs.removeObserver(this, 'Accessibility:MoveByGranularity');
 
     if (this.doneCallback) {
       this.doneCallback();
       delete this.doneCallback;
     }
   },
 
   _enableOrDisable: function _enableOrDisable() {
@@ -272,18 +272,18 @@ this.AccessFu = {
         this.Input.activateCurrent(JSON.parse(aData));
         break;
       case 'Accessibility:Focus':
         this._focused = JSON.parse(aData);
         if (this._focused) {
           this.showCurrent(true);
         }
         break;
-      case 'Accessibility:MoveCaret':
-        this.Input.moveCaret(JSON.parse(aData));
+      case 'Accessibility:MoveByGranularity':
+        this.Input.moveByGranularity(JSON.parse(aData));
         break;
       case 'remote-browser-frame-shown':
       case 'in-process-browser-or-app-frame-shown':
       {
         let mm = aSubject.QueryInterface(Ci.nsIFrameLoader).messageManager;
         this._handleMessageManager(mm);
         break;
       }
@@ -783,26 +783,33 @@ var Input = {
 
   moveCursor: function moveCursor(aAction, aRule, aInputType) {
     let mm = Utils.getMessageManager(Utils.CurrentBrowser);
     mm.sendAsyncMessage('AccessFu:MoveCursor',
                         {action: aAction, rule: aRule,
                          origin: 'top', inputType: aInputType});
   },
 
-  moveCaret: function moveCaret(aDetails) {
+  moveByGranularity: function moveByGranularity(aDetails) {
+    const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
+
     if (!this.editState.editing) {
-      return;
+      if (aDetails.granularity === MOVEMENT_GRANULARITY_PARAGRAPH) {
+        this.moveCursor('move' + aDetails.direction, 'Paragraph', 'gesture');
+        return;
+      }
+    } else {
+      aDetails.atStart = this.editState.atStart;
+      aDetails.atEnd = this.editState.atEnd;
     }
 
-    aDetails.atStart = this.editState.atStart;
-    aDetails.atEnd = this.editState.atEnd;
-
     let mm = Utils.getMessageManager(Utils.CurrentBrowser);
-    mm.sendAsyncMessage('AccessFu:MoveCaret', aDetails);
+    let type = this.editState.editing ? 'AccessFu:MoveCaret' :
+                                        'AccessFu:MoveByGranularity';
+    mm.sendAsyncMessage(type, aDetails);
   },
 
   activateCurrent: function activateCurrent(aData) {
     let mm = Utils.getMessageManager(Utils.CurrentBrowser);
     let offset = aData && typeof aData.keyIndex === 'number' ?
                  aData.keyIndex - Output.brailleState.startOffset : -1;
 
     mm.sendAsyncMessage('AccessFu:Activate', {offset: offset});
--- a/accessible/src/jsat/EventManager.jsm
+++ b/accessible/src/jsat/EventManager.jsm
@@ -151,21 +151,22 @@ this.EventManager.prototype = {
           QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
         let position = pivot.position;
         if (position && position.role == ROLE_INTERNAL_FRAME)
           break;
         let event = aEvent.
           QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
         let reason = event.reason;
 
-        if (this.editState.editing)
+        if (this.editState.editing) {
           aEvent.accessibleDocument.takeFocus();
-
+        }
         this.present(
-          Presentation.pivotChanged(position, event.oldAccessible, reason));
+          Presentation.pivotChanged(position, event.oldAccessible, reason,
+                                    pivot.startOffset, pivot.endOffset));
 
         break;
       }
       case EVENT_STATE_CHANGE:
       {
         let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
         if (event.state == Ci.nsIAccessibleStates.STATE_CHECKED &&
             !(event.isExtraState)) {
--- a/accessible/src/jsat/Presentation.jsm
+++ b/accessible/src/jsat/Presentation.jsm
@@ -119,47 +119,63 @@ VisualPresenter.prototype = {
   type: 'Visual',
 
   /**
    * The padding in pixels between the object and the highlight border.
    */
   BORDER_PADDING: 2,
 
   viewportChanged: function VisualPresenter_viewportChanged(aWindow) {
-    let currentAcc = this._displayedAccessibles.get(aWindow);
+    let currentDisplay = this._displayedAccessibles.get(aWindow);
+    if (!currentDisplay) {
+      return null;
+    }
+
+    let currentAcc = currentDisplay.accessible;
+    let start = currentDisplay.startOffset;
+    let end = currentDisplay.endOffset;
     if (Utils.isAliveAndVisible(currentAcc)) {
-      let bounds = Utils.getBounds(currentAcc);
+      let bounds = (start === -1 && end === -1) ? Utils.getBounds(currentAcc) :
+                   Utils.getTextBounds(currentAcc, start, end);
+
       return {
         type: this.type,
         details: {
           method: 'showBounds',
           bounds: bounds,
           padding: this.BORDER_PADDING
         }
       };
     }
 
     return null;
   },
 
   pivotChanged: function VisualPresenter_pivotChanged(aContext, aReason) {
     this._displayedAccessibles.set(aContext.accessible.document.window,
-                                   aContext.accessible);
+                                   { accessible: aContext.accessible,
+                                     startOffset: aContext.startOffset,
+                                     endOffset: aContext.endOffset });
 
     if (!aContext.accessible)
       return {type: this.type, details: {method: 'hideBounds'}};
 
     try {
       aContext.accessible.scrollTo(
         Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
+
+      let bounds = (aContext.startOffset === -1 && aContext.endOffset === -1) ?
+                   aContext.bounds : Utils.getTextBounds(aContext.accessible,
+                                     aContext.startOffset, aContext.endOffset);
+
       return {
         type: this.type,
         details: {
           method: 'showBounds',
-          bounds: aContext.bounds,
+          bounds: bounds,
           padding: this.BORDER_PADDING
         }
       };
     } catch (e) {
       Logger.logException(e, 'Failed to get bounds');
       return null;
     }
   },
@@ -227,37 +243,49 @@ AndroidPresenter.prototype = {
       this.ANDROID_VIEW_FOCUSED;
 
     if (isExploreByTouch) {
       // This isn't really used by TalkBack so this is a half-hearted attempt
       // for now.
       androidEvents.push({eventType: this.ANDROID_VIEW_HOVER_EXIT, text: []});
     }
 
-    let state = Utils.getStates(aContext.accessible)[0];
-
     let brailleOutput = {};
     if (Utils.AndroidSdkVersion >= 16) {
       if (!this._braillePresenter) {
         this._braillePresenter = new BraillePresenter();
       }
       brailleOutput = this._braillePresenter.pivotChanged(aContext, aReason).
                          details;
     }
 
-    androidEvents.push({eventType: (isExploreByTouch) ?
-                          this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
-                        text: UtteranceGenerator.genForContext(aContext).output,
-                        bounds: aContext.bounds,
-                        clickable: aContext.accessible.actionCount > 0,
-                        checkable: !!(state &
-                                      Ci.nsIAccessibleStates.STATE_CHECKABLE),
-                        checked: !!(state &
-                                    Ci.nsIAccessibleStates.STATE_CHECKED),
-                        brailleOutput: brailleOutput});
+    if (aReason === Ci.nsIAccessiblePivot.REASON_TEXT) {
+      if (Utils.AndroidSdkVersion >= 16) {
+        let adjustedText = aContext.textAndAdjustedOffsets;
+
+        androidEvents.push({
+          eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+          text: [adjustedText.text],
+          fromIndex: adjustedText.startOffset,
+          toIndex: adjustedText.endOffset
+        });
+      }
+    } else {
+      let state = Utils.getStates(aContext.accessible)[0];
+      androidEvents.push({eventType: (isExploreByTouch) ?
+                           this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
+                         text: UtteranceGenerator.genForContext(aContext).output,
+                         bounds: aContext.bounds,
+                         clickable: aContext.accessible.actionCount > 0,
+                         checkable: !!(state &
+                                       Ci.nsIAccessibleStates.STATE_CHECKABLE),
+                         checked: !!(state &
+                                     Ci.nsIAccessibleStates.STATE_CHECKED),
+                         brailleOutput: brailleOutput});
+    }
 
 
     return {
       type: this.type,
       details: androidEvents
     };
   },
 
@@ -489,20 +517,19 @@ this.Presentation = {
     } else {
       this.presenters.push(new SpeechPresenter());
       this.presenters.push(new HapticPresenter());
     }
 
     return this.presenters;
   },
 
-  pivotChanged: function Presentation_pivotChanged(aPosition,
-                                                   aOldPosition,
-                                                   aReason) {
-    let context = new PivotContext(aPosition, aOldPosition);
+  pivotChanged: function Presentation_pivotChanged(aPosition, aOldPosition, aReason,
+                                                   aStartOffset, aEndOffset) {
+    let context = new PivotContext(aPosition, aOldPosition, aStartOffset, aEndOffset);
     return [p.pivotChanged(context, aReason)
               for each (p in this.presenters)];
   },
 
   actionInvoked: function Presentation_actionInvoked(aObject, aActionName) {
     return [p.actionInvoked(aObject, aActionName)
               for each (p in this.presenters)];
   },
--- a/accessible/src/jsat/TraversalRules.jsm
+++ b/accessible/src/jsat/TraversalRules.jsm
@@ -38,16 +38,18 @@ const ROLE_LISTITEM = Ci.nsIAccessibleRo
 const ROLE_BUTTONDROPDOWNGRID = Ci.nsIAccessibleRole.ROLE_BUTTONDROPDOWNGRID;
 const ROLE_LISTBOX = Ci.nsIAccessibleRole.ROLE_LISTBOX;
 const ROLE_SLIDER = Ci.nsIAccessibleRole.ROLE_SLIDER;
 const ROLE_HEADING = Ci.nsIAccessibleRole.ROLE_HEADING;
 const ROLE_TERM = Ci.nsIAccessibleRole.ROLE_TERM;
 const ROLE_SEPARATOR = Ci.nsIAccessibleRole.ROLE_SEPARATOR;
 const ROLE_TABLE = Ci.nsIAccessibleRole.ROLE_TABLE;
 const ROLE_INTERNAL_FRAME = Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME;
+const ROLE_PARAGRAPH = Ci.nsIAccessibleRole.ROLE_PARAGRAPH;
+const ROLE_SECTION = Ci.nsIAccessibleRole.ROLE_SECTION;
 
 this.EXPORTED_SYMBOLS = ['TraversalRules'];
 
 Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
 let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
 
@@ -243,16 +245,29 @@ this.TraversalRules = {
 
   List: new BaseTraversalRule(
     [ROLE_LIST,
      ROLE_DEFINITION_LIST]),
 
   PageTab: new BaseTraversalRule(
     [ROLE_PAGETAB]),
 
+  Paragraph: new BaseTraversalRule(
+    [ROLE_PARAGRAPH,
+     ROLE_SECTION],
+    function Paragraph_match(aAccessible) {
+      for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
+        if (child.role === ROLE_TEXT_LEAF) {
+          return FILTER_MATCH | FILTER_IGNORE_SUBTREE;
+        }
+      }
+
+      return FILTER_IGNORE;
+    }),
+
   RadioButton: new BaseTraversalRule(
     [ROLE_RADIOBUTTON,
      ROLE_RADIO_MENU_ITEM]),
 
   Separator: new BaseTraversalRule(
     [ROLE_SEPARATOR]),
 
   Table: new BaseTraversalRule(
--- a/accessible/src/jsat/Utils.jsm
+++ b/accessible/src/jsat/Utils.jsm
@@ -222,16 +222,24 @@ this.Utils = {
   },
 
   getBounds: function getBounds(aAccessible) {
       let objX = {}, objY = {}, objW = {}, objH = {};
       aAccessible.getBounds(objX, objY, objW, objH);
       return new Rect(objX.value, objY.value, objW.value, objH.value);
   },
 
+  getTextBounds: function getTextBounds(aAccessible, aStart, aEnd) {
+      let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
+      let objX = {}, objY = {}, objW = {}, objH = {};
+      accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH,
+                              Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE);
+      return new Rect(objX.value, objY.value, objW.value, objH.value);
+  },
+
   inHiddenSubtree: function inHiddenSubtree(aAccessible) {
     for (let acc=aAccessible; acc; acc=acc.parent) {
       let hidden = Utils.getAttributes(acc).hidden;
       if (hidden && JSON.parse(hidden)) {
         return true;
       }
     }
     return false;
@@ -408,31 +416,73 @@ this.Logger = {
       this._dumpTreeInternal(aLogLevel, aAccessible.getChildAt(i), aIndent + 1);
     }
 };
 
 /**
  * PivotContext: An object that generates and caches context information
  * for a given accessible and its relationship with another accessible.
  */
-this.PivotContext = function PivotContext(aAccessible, aOldAccessible) {
+this.PivotContext = function PivotContext(aAccessible, aOldAccessible,
+                                          aStartOffset, aEndOffset) {
   this._accessible = aAccessible;
   this._oldAccessible =
     this._isDefunct(aOldAccessible) ? null : aOldAccessible;
+  this.startOffset = aStartOffset;
+  this.endOffset = aEndOffset;
 }
 
 PivotContext.prototype = {
   get accessible() {
     return this._accessible;
   },
 
   get oldAccessible() {
     return this._oldAccessible;
   },
 
+  get textAndAdjustedOffsets() {
+    if (this.startOffset === -1 && this.endOffset === -1) {
+      return null;
+    }
+
+    if (!this._textAndAdjustedOffsets) {
+      let result = {startOffset: this.startOffset,
+                    endOffset: this.endOffset,
+                    text: this._accessible.QueryInterface(Ci.nsIAccessibleText).
+                          getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT)};
+      let hypertextAcc = this._accessible.QueryInterface(Ci.nsIAccessibleHyperText);
+
+      // Iterate through the links in backwards order so text replacements don't
+      // affect the offsets of links yet to be processed.
+      for (let i = hypertextAcc.linkCount - 1; i >= 0; i--) {
+        let link = hypertextAcc.getLinkAt(i);
+        let linkText = '';
+        if (link instanceof Ci.nsIAccessibleText) {
+          linkText = link.QueryInterface(Ci.nsIAccessibleText).
+                          getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
+        }
+
+        let start = link.startIndex;
+        let end = link.endIndex;
+        for (let offset of ['startOffset', 'endOffset']) {
+          if (this[offset] >= end) {
+            result[offset] += linkText.length - (end - start);
+          }
+        }
+        result.text = result.text.substring(0, start) + linkText +
+                      result.text.substring(end);
+      }
+
+      this._textAndAdjustedOffsets = result;
+    }
+
+    return this._textAndAdjustedOffsets;
+  },
+
   /**
    * Get a list of |aAccessible|'s ancestry up to the root.
    * @param  {nsIAccessible} aAccessible.
    * @return {Array} Ancestry list.
    */
   _getAncestry: function _getAncestry(aAccessible) {
     let ancestry = [];
     let parent = aAccessible;
--- a/accessible/src/jsat/content-script.js
+++ b/accessible/src/jsat/content-script.js
@@ -3,16 +3,20 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 const ROLE_ENTRY = Ci.nsIAccessibleRole.ROLE_ENTRY;
 const ROLE_INTERNAL_FRAME = Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME;
 
+const MOVEMENT_GRANULARITY_CHARACTER = 1;
+const MOVEMENT_GRANULARITY_WORD = 2;
+const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
+
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
   'resource://gre/modules/accessibility/Presentation.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
   'resource://gre/modules/accessibility/TraversalRules.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
@@ -112,17 +116,18 @@ function showCurrent(aMessage) {
 
   let vc = Utils.getVirtualCursor(content.document);
 
   if (!forwardToChild(vc, showCurrent, aMessage)) {
     if (!vc.position && aMessage.json.move) {
       vc.moveFirst(TraversalRules.Simple);
     } else {
       sendAsyncMessage('AccessFu:Present', Presentation.pivotChanged(
-                         vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE));
+                         vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE,
+                         vc.startOffset, vc.endOffset));
     }
   }
 }
 
 function forwardToParent(aMessage) {
   // XXX: This is a silly way to make a deep copy
   let newJSON = JSON.parse(JSON.stringify(aMessage.json));
   newJSON.origin = 'child';
@@ -221,21 +226,40 @@ function activateContextMenu(aMessage) {
   }
 
   let position = Utils.getVirtualCursor(content.document).position;
   if (!forwardToChild(aMessage, activateContextMenu, position)) {
     sendContextMenuCoordinates(position);
   }
 }
 
+function moveByGranularity(aMessage) {
+  let direction = aMessage.json.direction;
+  let vc = Utils.getVirtualCursor(content.document);
+  let granularity;
+
+  switch(aMessage.json.granularity) {
+    case MOVEMENT_GRANULARITY_CHARACTER:
+      granularity = Ci.nsIAccessiblePivot.CHAR_BOUNDARY;
+      break;
+    case MOVEMENT_GRANULARITY_WORD:
+      granularity = Ci.nsIAccessiblePivot.WORD_BOUNDARY;
+      break;
+    default:
+      return;
+  }
+
+  if (direction === 'Previous') {
+    vc.movePreviousByText(granularity);
+  } else if (direction === 'Next') {
+    vc.moveNextByText(granularity);
+  }
+}
+
 function moveCaret(aMessage) {
-  const MOVEMENT_GRANULARITY_CHARACTER = 1;
-  const MOVEMENT_GRANULARITY_WORD = 2;
-  const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
-
   let direction = aMessage.json.direction;
   let granularity = aMessage.json.granularity;
   let accessible = Utils.getVirtualCursor(content.document).position;
   let accText = accessible.QueryInterface(Ci.nsIAccessibleText);
   let oldOffset = accText.caretOffset;
   let text = accText.getText(0, accText.characterCount);
 
   let start = {}, end = {};
@@ -368,16 +392,17 @@ addMessageListener(
 
     addMessageListener('AccessFu:MoveToPoint', moveToPoint);
     addMessageListener('AccessFu:MoveCursor', moveCursor);
     addMessageListener('AccessFu:ShowCurrent', showCurrent);
     addMessageListener('AccessFu:Activate', activateCurrent);
     addMessageListener('AccessFu:ContextMenu', activateContextMenu);
     addMessageListener('AccessFu:Scroll', scroll);
     addMessageListener('AccessFu:MoveCaret', moveCaret);
+    addMessageListener('AccessFu:MoveByGranularity', moveByGranularity);
 
     if (!eventManager) {
       eventManager = new EventManager(this);
     }
     eventManager.start();
   });
 
 addMessageListener(
@@ -387,13 +412,14 @@ addMessageListener(
 
     removeMessageListener('AccessFu:MoveToPoint', moveToPoint);
     removeMessageListener('AccessFu:MoveCursor', moveCursor);
     removeMessageListener('AccessFu:ShowCurrent', showCurrent);
     removeMessageListener('AccessFu:Activate', activateCurrent);
     removeMessageListener('AccessFu:ContextMenu', activateContextMenu);
     removeMessageListener('AccessFu:Scroll', scroll);
     removeMessageListener('AccessFu:MoveCaret', moveCaret);
+    removeMessageListener('AccessFu:MoveByGranularity', moveByGranularity);
 
     eventManager.stop();
   });
 
 sendAsyncMessage('AccessFu:Ready');
--- a/mobile/android/base/GeckoAccessibility.java
+++ b/mobile/android/base/GeckoAccessibility.java
@@ -359,30 +359,30 @@ public class GeckoAccessibility {
                                     JSONObject movementData = new JSONObject();
                                     try {
                                         movementData.put("direction", "Next");
                                         movementData.put("granularity", granularity);
                                     } catch (JSONException e) {
                                         return true;
                                     }
                                     GeckoAppShell.
-                                        sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveCaret", movementData.toString()));
+                                        sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveByGranularity", movementData.toString()));
                                 }
                                 return true;
                             } else if (action == AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY &&
                                        virtualViewId == VIRTUAL_CURSOR_POSITION) {
                                 JSONObject movementData = new JSONObject();
                                 try {
                                     movementData.put("direction", "Previous");
                                     movementData.put("granularity", arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
                                 } catch (JSONException e) {
                                     return true;
                                 }
                                 GeckoAppShell.
-                                    sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveCaret", movementData.toString()));
+                                    sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:MoveByGranularity", movementData.toString()));
                                 return true;
                             }
                             return host.performAccessibilityAction(action, arguments);
                         }
                     };
 
             return mAccessibilityNodeProvider;
         }