Bug 796080 - MozKeyboard must send notification when user moves cursor. r=vingtetun, a=blocking-basecamp
authorDavid Flanagen <dflanagan@mozilla.com>
Thu, 11 Oct 2012 10:55:07 -0700
changeset 116006 31ecdc024188a380d01cf6472e35286e6b09511c
parent 116005 630fe407ed43a1b70e4cc56913600a4698ca2456
child 116007 97a3487c3b5152e18742d582b8d61260d593c24e
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvingtetun, blocking-basecamp
bugs796080
milestone18.0a2
Bug 796080 - MozKeyboard must send notification when user moves cursor. r=vingtetun, a=blocking-basecamp
b2g/chrome/content/forms.js
--- a/b2g/chrome/content/forms.js
+++ b/b2g/chrome/content/forms.js
@@ -1,17 +1,17 @@
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-dump("###################################### forms.js loaded\n");
+"use strict";
 
-"use strict";
+dump("###################################### forms.js loaded\n");
 
 let Ci = Components.interfaces;
 let Cc = Components.classes;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 XPCOMUtils.defineLazyServiceGetter(Services, "fm",
@@ -32,19 +32,39 @@ let FormAssistant = {
     addEventListener("resize", this, true, false);
     addMessageListener("Forms:Select:Choice", this);
     addMessageListener("Forms:Input:Value", this);
     Services.obs.addObserver(this, "ime-enabled-state-changed", false);
     Services.obs.addObserver(this, "xpcom-shutdown", false);
   },
 
   isKeyboardOpened: false,
-  previousTarget : null,
+  focusedElement : null,
+  selectionStart: 0,
+  selectionEnd: 0,
+
+  setFocusedElement: function fa_setFocusedElement(element) {
+    if (element === this.focusedElement)
+      return;
+
+    if (this.focusedElement) {
+      this.focusedElement.removeEventListener('mousedown', this);
+      this.focusedElement.removeEventListener('mouseup', this);
+    }
+
+    if (element) {
+      element.addEventListener('mousedown', this);
+      element.addEventListener('mouseup', this);
+    }
+
+    this.focusedElement = element;
+  },
+
   handleEvent: function fa_handleEvent(evt) {
-    let previousTarget = this.previousTarget;
+    let focusedElement = this.focusedElement;
     let target = evt.target;
 
     switch (evt.type) {
       case "focus":
         if (this.isKeyboardOpened)
           return;
 
         let ignore = {
@@ -52,48 +72,67 @@ let FormAssistant = {
           file: true,
           checkbox: true,
           radio: true,
           reset: true,
           submit: true,
           image: true
         };
     
-        if (evt.target instanceof HTMLSelectElement) { 
+        if (target instanceof HTMLSelectElement) { 
           content.setTimeout(function showIMEForSelect() {
-            sendAsyncMessage("Forms:Input", getJSON(evt.target));
+            sendAsyncMessage("Forms:Input", getJSON(target));
           });
-          this.previousTarget = evt.target;
-        } else if (evt.target instanceof HTMLOptionElement &&
-                   evt.target.parentNode instanceof HTMLSelectElement) {
+          this.setFocusedElement(target);
+        } else if (target instanceof HTMLOptionElement &&
+                   target.parentNode instanceof HTMLSelectElement) {
+          target = target.parentNode;
           content.setTimeout(function showIMEForSelect() {
-            sendAsyncMessage("Forms:Input", getJSON(evt.target.parentNode));
+            sendAsyncMessage("Forms:Input", getJSON(target));
           });
+          this.setFocusedElement(target);
         } else if ((target instanceof HTMLInputElement && !ignore[target.type]) ||
                     target instanceof HTMLTextAreaElement) {
-          this.isKeyboardOpened = this.tryShowIme(evt.target);
-          this.previousTarget = evt.target;
+          this.isKeyboardOpened = this.tryShowIme(target);
+          this.setFocusedElement(target);
         }
         break;
 
       case "blur":
-        if (this.previousTarget) {
+        if (this.focusedElement) {
           sendAsyncMessage("Forms:Input", { "type": "blur" });
           this.isKeyboardOpened = false;
-          this.previousTarget = null;
+          this.setFocusedElement(null);
+        }
+        break;
+
+      case 'mousedown':
+        // We only listen for this event on the currently focused element.
+        // When the mouse goes down, note the cursor/selection position
+        this.selectionStart = this.focusedElement.selectionStart;
+        this.selectionEnd = this.focusedElement.selectionEnd;
+        break;
+
+      case 'mouseup':
+        // We only listen for this event on the currently focused element.
+        // When the mouse goes up, see if the cursor has moved (or the
+        // selection changed) since the mouse went down. If it has, we
+        // need to tell the keyboard about it
+        if (this.focusedElement.selectionStart !== this.selectionStart ||
+            this.focusedElement.selectionEnd !== this.selectionEnd) {
+          this.tryShowIme(this.focusedElement);
         }
         break;
 
       case "resize":
         if (!this.isKeyboardOpened)
           return;
 
-        let focusedElement = this.previousTarget;
-        if (focusedElement) {
-          focusedElement.scrollIntoView(false);
+        if (this.focusedElement) {
+          this.focusedElement.scrollIntoView(false);
         }
         break;
 
       case "keypress":
         if (evt.keyCode != evt.DOM_VK_ESCAPE || !this.isKeyboardOpened)
           return;
 
         sendAsyncMessage("Forms:Input", { "type": "blur" });
@@ -101,17 +140,17 @@ let FormAssistant = {
 
         evt.preventDefault();
         evt.stopPropagation();
         break;
     }
   },
 
   receiveMessage: function fa_receiveMessage(msg) {
-    let target = this.previousTarget;
+    let target = this.focusedElement;
     if (!target) {
       return;
     }
 
     let json = msg.json;
     switch (msg.name) {
       case "Forms:Input:Value": {
         target.value = json.value;
@@ -154,31 +193,32 @@ let FormAssistant = {
     switch (topic) {
       case "ime-enabled-state-changed":
         let isOpen = this.isKeyboardOpened;
         let shouldOpen = parseInt(data);
         if (shouldOpen && !isOpen) {
           let target = Services.fm.focusedElement;
 
           if (!target || !this.tryShowIme(target)) {
-            this.previousTarget = null;
+            this.setFocusedElement(null);
             return;
           } else {
-            this.previousTarget = target;
+            this.setFocusedElement(target);
           }
         } else if (!shouldOpen && isOpen) {
           sendAsyncMessage("Forms:Input", { "type": "blur" });
         }
         this.isKeyboardOpened = shouldOpen;
         break;
 
       case "xpcom-shutdown":
         Services.obs.removeObserver(this, "ime-enabled-state-changed", false);
         Services.obs.removeObserver(this, "xpcom-shutdown");
         removeMessageListener("Forms:Select:Choice", this);
+        removeMessageListener("Forms:Input:Value", this);
         break;
     }
   },
 
   tryShowIme: function(element) {
     if (!element) {
       return;
     }
@@ -214,20 +254,31 @@ function getJSON(element) {
       case "time":
       case "datetime":
       case "datetime-local":
         type = typeLowerCase;
         break;
     }
   }
 
+  // If Gecko knows about the inputmode attribute, use that value.
+  // Otherwise, query the attribute explicitly, but be sure to convert
+  // to lowercase
+  let inputmode = element.inputmode || element.getAttribute('inputmode');
+  if (inputmode) {
+    inputmode = inputmode.toLowerCase();
+  }
+
   return {
     "type": type.toLowerCase(),
     "choices": getListForElement(element),
-    "value": element.value
+    "value": element.value,
+    "inputmode": inputmode,
+    "selectionStart": element.selectionStart,
+    "selectionEnd": element.selectionEnd
   };
 }
 
 function getListForElement(element) {
   if (!(element instanceof HTMLSelectElement))
     return null;
 
   let optionIndex = 0;