Bug 1057898 - Tap between two inputs should result one inputcontextchange event. r=yxl
☠☠ backed out by 9b3fbc370631 ☠ ☠
authorTim Chien <timdream@gmail.com>
Thu, 28 Aug 2014 23:38:00 -0400
changeset 202360 5f66ff9f11f2c5e2c6a8b35c9b234c0e51659e07
parent 202359 dfcd20daf182bc8f688a9655333c99a7630ebff9
child 202361 de5cefa8e52e34a979b5fef169b55f5fa8dd027b
push id48402
push userryanvm@gmail.com
push dateFri, 29 Aug 2014 13:35:39 +0000
treeherdermozilla-inbound@de5cefa8e52e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyxl
bugs1057898
milestone34.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 1057898 - Tap between two inputs should result one inputcontextchange event. r=yxl
dom/inputmethod/forms.js
--- a/dom/inputmethod/forms.js
+++ b/dom/inputmethod/forms.js
@@ -13,16 +13,20 @@ 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",
                                    "@mozilla.org/focus-manager;1",
                                    "nsIFocusManager");
 
+XPCOMUtils.defineLazyServiceGetter(Services, "threadManager",
+                                   "@mozilla.org/thread-manager;1",
+                                   "nsIThreadManager");
+
 XPCOMUtils.defineLazyGetter(this, "domWindowUtils", function () {
   return content.QueryInterface(Ci.nsIInterfaceRequestor)
                 .getInterface(Ci.nsIDOMWindowUtils);
 });
 
 const RESIZE_SCROLL_DELAY = 20;
 // In content editable node, when there are hidden elements such as <br>, it
 // may need more than one (usually less than 3 times) move/extend operations
@@ -245,19 +249,16 @@ let FormAssistant = {
       return;
 
     if (this.focusedElement) {
       this.focusedElement.removeEventListener('compositionend', this);
       if (this._observer) {
         this._observer.disconnect();
         this._observer = null;
       }
-      if (!element) {
-        this.focusedElement.blur();
-      }
       if (this._selectionPrivate) {
         this._selectionPrivate.removeSelectionListener(this);
         this._selectionPrivate = null;
       }
     }
 
     this._documentEncoder = null;
     if (this._editor) {
@@ -293,18 +294,19 @@ let FormAssistant = {
       let MutationObserver = element.ownerDocument.defaultView.MutationObserver;
       this._observer = new MutationObserver(function(mutations) {
         var del = [].some.call(mutations, function(m) {
           return [].some.call(m.removedNodes, function(n) {
             return n.contains(element);
           });
         });
         if (del && element === self.focusedElement) {
-          // item was deleted, fake a blur so all state gets set correctly
-          self.handleEvent({ target: element, type: "blur" });
+          self.hideKeyboard();
+          self.selectionStart = -1;
+          self.selectionEnd = -1;
         }
       });
 
       this._observer.observe(element.ownerDocument.body, {
         childList: true,
         subtree: true
       });
     }
@@ -373,18 +375,23 @@ let FormAssistant = {
       case "pagehide":
       case "beforeunload":
         // We are only interested to the pagehide and beforeunload events from
         // the root document.
         if (target && target != content.document) {
           break;
         }
         // fall through
+      case "submit":
+        if (this.focusedElement) {
+          this.focusedElement.blur();
+        }
+        break;
+
       case "blur":
-      case "submit":
         if (this.focusedElement) {
           this.hideKeyboard();
           this.selectionStart = -1;
           this.selectionEnd = -1;
         }
         break;
 
       case "resize":
@@ -429,16 +436,23 @@ let FormAssistant = {
           break;
         }
 
         CompositionManager.onCompositionEnd();
         break;
     }
   },
 
+  waitForNextTick: function(callback) {
+    var tm = Services.threadManager;
+    tm.mainThread.dispatch({
+      run: callback,
+    }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+  },
+
   receiveMessage: function fa_receiveMessage(msg) {
     let target = this.focusedElement;
     let json = msg.json;
 
     // To not break mozKeyboard contextId is optional
     if ('contextId' in json &&
         json.contextId !== this._focusCounter &&
         json.requestId) {
@@ -531,17 +545,20 @@ let FormAssistant = {
         if (valueChanged) {
           let event = target.ownerDocument.createEvent('HTMLEvents');
           event.initEvent('change', true, true);
           target.dispatchEvent(event);
         }
         break;
 
       case "Forms:Select:Blur": {
-        this.setFocusedElement(null);
+        if (this.focusedElement) {
+          this.focusedElement.blur();
+        }
+
         break;
       }
 
       case "Forms:SetSelectionRange":  {
         CompositionManager.endComposition('');
 
         let start = json.selectionStart;
         let end =  json.selectionEnd;
@@ -650,19 +667,30 @@ let FormAssistant = {
     this.setFocusedElement(target);
 
     let kbOpened = this.sendKeyboardState(target);
     if (this.isTextInputElement(target))
       this.isKeyboardOpened = kbOpened;
   },
 
   hideKeyboard: function fa_hideKeyboard() {
-    sendAsyncMessage("Forms:Input", { "type": "blur" });
-    this.isKeyboardOpened = false;
-    this.setFocusedElement(null);
+    var element = this.focusedElement;
+
+    // Wait for the next tick before unset the focused element and etc.
+    // If the user move from one input from another,
+    // the remote process should get one Forms:Input message instead of two.
+    this.waitForNextTick(function() {
+      if (element !== this.focusedElement) {
+        return;
+      }
+
+      this.isKeyboardOpened = false;
+      this.setFocusedElement(null);
+      sendAsyncMessage("Forms:Input", { "type": "blur" });
+    }.bind(this));
   },
 
   isFocusableElement: function fa_isFocusableElement(element) {
     if (element instanceof HTMLSelectElement ||
         element instanceof HTMLTextAreaElement)
       return true;
 
     if (element instanceof HTMLOptionElement &&
@@ -733,17 +761,17 @@ let FormAssistant = {
   _selectionTimeout: null,
 
   // Notify when the selection range changes
   updateSelection: function fa_updateSelection() {
     // A call to setSelectionRange on input field causes 2 selection changes
     // one to [0,0] and one to actual value. Both are sent in same tick.
     // Prevent firing two events in that scenario, always only use the last 1.
     //
-    // It is also a workaround for Bug 1053048, which prevents 
+    // It is also a workaround for Bug 1053048, which prevents
     // getSelectionInfo() accessing selectionStart or selectionEnd in the
     // callback function of nsISelectionListener::NotifySelectionChanged().
     if (this._selectionTimeout) {
       content.clearTimeout(this._selectionTimeout);
     }
     this._selectionTimeout = content.setTimeout(function() {
       if (!this.focusedElement) {
         return;