Bug 753076 - Allow d-pad keys interoperate with text entry as well as virtual cursor. r=davidb
authorEitan Isaacson <eitan@monotonous.org>
Tue, 15 May 2012 10:41:26 -0700
changeset 94036 f24cb2b6f8f9fb122ab739cc17e0082bf1d5ccc0
parent 94035 d124a9678e939f7bf95d71c05c6a0f71fd2e751b
child 94037 836e451c8b772209ca0e385147579dfb393add88
push id9421
push usereisaacson@mozilla.com
push dateTue, 15 May 2012 17:41:35 +0000
treeherdermozilla-inbound@f24cb2b6f8f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdavidb
bugs753076
milestone15.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 753076 - Allow d-pad keys interoperate with text entry as well as virtual cursor. r=davidb
accessible/src/jsat/AccessFu.jsm
accessible/src/jsat/VirtualCursorController.jsm
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -194,16 +194,31 @@ var AccessFu = {
   _handleAccEvent: function _handleAccEvent(aEvent) {
     switch (aEvent.eventType) {
       case Ci.nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED:
         {
           let pivot = aEvent.accessible.
             QueryInterface(Ci.nsIAccessibleCursorable).virtualCursor;
           let event = aEvent.
             QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
+          let position = pivot.position;
+          let doc = aEvent.DOMNode;
+
+          if (doc instanceof Ci.nsIDOMDocument && position.DOMNode) {
+            // Set the caret to the start of the pivot position, and move
+            // the focus in the same manner as browse with caret mode.
+            // This blurs the focus on the previous pivot position (if it
+            // was activated), and keeps us in a predictable spot for tab
+            // focus.
+            let sel = doc.getSelection();
+            sel.collapse(position.DOMNode, 0);
+            Cc["@mozilla.org/focus-manager;1"]
+              .getService(Ci.nsIFocusManager).moveFocus(
+                doc.defaultView, null, Ci.nsIFocusManager.MOVEFOCUS_CARET, 0);
+          }
 
           let newContext = this.getNewContext(event.oldAccessible,
                                               pivot.position);
           this.presenters.forEach(
             function(p) {
               p.pivotChanged(pivot.position, newContext);
             });
           break;
--- a/accessible/src/jsat/VirtualCursorController.jsm
+++ b/accessible/src/jsat/VirtualCursorController.jsm
@@ -13,77 +13,102 @@ var EXPORTED_SYMBOLS = ['VirtualCursorCo
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 
 var gAccRetrieval = Cc['@mozilla.org/accessibleRetrieval;1'].
   getService(Ci.nsIAccessibleRetrieval);
 
 var VirtualCursorController = {
+  NOT_EDITABLE: 0,
+  SINGLE_LINE_EDITABLE: 1,
+  MULTI_LINE_EDITABLE: 2,
+
   attach: function attach(aWindow) {
     this.chromeWin = aWindow;
-    this.chromeWin.document.addEventListener('keypress', this._onkeypress, true);
+    this.chromeWin.document.addEventListener('keypress', this, true);
   },
 
   detach: function detach() {
-    this.chromeWin.document.removeEventListener('keypress', this._onkeypress,
-                                                true);
+    this.chromeWin.document.removeEventListener('keypress', this, true);
   },
 
   _getBrowserApp: function _getBrowserApp() {
     switch (Services.appinfo.OS) {
       case 'Android':
         return this.chromeWin.BrowserApp;
       default:
         return this.chromeWin.gBrowser;
     }
   },
 
-  _onkeypress: function _onkeypress(aEvent) {
-    let document = VirtualCursorController._getBrowserApp().
-      selectedBrowser.contentDocument;
-
-    dump('keypress ' + aEvent.keyCode + '\n');
+  handleEvent: function handleEvent(aEvent) {
+    let document = this._getBrowserApp().selectedBrowser.contentDocument;
+    let target = aEvent.target;
 
     switch (aEvent.keyCode) {
       case aEvent.DOM_VK_END:
-        VirtualCursorController.moveForward(document, true);
+        this.moveForward(document, true);
         break;
       case aEvent.DOM_VK_HOME:
-        VirtualCursorController.moveBackward(document, true);
+        this.moveBackward(document, true);
         break;
       case aEvent.DOM_VK_RIGHT:
-        VirtualCursorController.moveForward(document, aEvent.shiftKey);
+        if (this._isEditableText(target) &&
+            target.selectionEnd != target.textLength)
+          // Don't move forward if caret is not at end of entry.
+          // XXX: Fix for rtl
+          return;
+        this.moveForward(document, aEvent.shiftKey);
         break;
       case aEvent.DOM_VK_LEFT:
-        VirtualCursorController.moveBackward(document, aEvent.shiftKey);
+        if (this._isEditableText(target) &&
+            target.selectionEnd != 0)
+          // Don't move backward if caret is not at start of entry.
+          // XXX: Fix for rtl
+          return;
+        this.moveBackward(document, aEvent.shiftKey);
         break;
       case aEvent.DOM_VK_UP:
+        if (this._isEditableText(target) == this.MULTI_LINE_EDITABLE &&
+            target.selectionEnd != 0)
+          // Don't blur content if caret is not at start of text area.
+          return;
         if (Services.appinfo.OS == 'Android')
           // Return focus to native Android browser chrome.
           Cc['@mozilla.org/android/bridge;1'].
             getService(Ci.nsIAndroidBridge).handleGeckoMessage(
               JSON.stringify({ gecko: { type: 'ToggleChrome:Focus' } }));
         break;
       case aEvent.DOM_VK_RETURN:
-        // XXX: It is true that desktop does not map the keypad enter key to
-        // DOM_VK_ENTER. So for desktop we require a ctrl+return instead.
-        if (Services.appinfo.OS == 'Android' || !aEvent.ctrlKey)
+      case aEvent.DOM_VK_ENTER:
+        if (this._isEditableText(target))
           return;
-      case aEvent.DOM_VK_ENTER:
-        VirtualCursorController.activateCurrent(document);
+        this.activateCurrent(document);
         break;
       default:
         return;
     }
 
     aEvent.preventDefault();
     aEvent.stopPropagation();
   },
 
+  _isEditableText: function _isEditableText(aElement) {
+    // XXX: Support contentEditable and design mode
+    if (aElement instanceof Ci.nsIDOMHTMLInputElement &&
+        aElement.mozIsTextField(false))
+      return this.SINGLE_LINE_EDITABLE;
+
+    if (aElement instanceof Ci.nsIDOMHTMLTextAreaElement)
+      return this.MULTI_LINE_EDITABLE;
+
+    return this.NOT_EDITABLE;
+  },
+
   moveForward: function moveForward(document, last) {
     let virtualCursor = this.getVirtualCursor(document);
     if (last) {
       virtualCursor.moveLast(this.SimpleTraversalRule);
     } else {
       virtualCursor.moveNext(this.SimpleTraversalRule);
     }
   },