Bug 1162360 - Dispatches focus and blur message synchronously, r=yxl
authorTim Chien <timdream@gmail.com>
Thu, 21 May 2015 20:13:00 +0200
changeset 266950 67051665e7e59334fcab57ef40f6b703f118b819
parent 266949 8d87fb4830b5c8696b7f01bb5ddbba5ec754928e
child 266951 11c6bc8c844f17bcf885493c592da0ed54a2c3ae
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-esr52@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyxl
bugs1162360
milestone41.0a1
Bug 1162360 - Dispatches focus and blur message synchronously, r=yxl
dom/inputmethod/forms.js
dom/inputmethod/mochitest/file_test_contenteditable.html
dom/inputmethod/mochitest/mochitest.ini
dom/inputmethod/mochitest/test_bug1059163.html
dom/inputmethod/mochitest/test_sync_edit.html
dom/inputmethod/mochitest/test_two_inputs.html
dom/inputmethod/mochitest/test_two_selects.html
--- a/dom/inputmethod/forms.js
+++ b/dom/inputmethod/forms.js
@@ -13,20 +13,16 @@ 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
@@ -456,23 +452,16 @@ 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) {
@@ -677,45 +666,23 @@ let FormAssistant = {
   handleFocus: function fa_handleFocus(target) {
     if (this.focusedElement === target)
       return;
 
     if (target instanceof HTMLOptionElement)
       target = target.parentNode;
 
     this.setFocusedElement(target);
-
-    let count = this._focusCounter;
-    this.waitForNextTick(function fa_handleFocusSync() {
-      if (count !== this._focusCounter) {
-        return;
-      }
-
-      let isHandlingFocus = this.sendInputState(target);
-      this.isHandlingFocus = isHandlingFocus;
-    }.bind(this));
+    this.isHandlingFocus = this.sendInputState(target);
   },
 
   unhandleFocus: function fa_unhandleFocus() {
     this.setFocusedElement(null);
-
-    let count = this._focusCounter;
-
-    // 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 fa_unhandleFocusSync() {
-      if (count !== this._focusCounter ||
-          !this.isHandlingFocus) {
-        return;
-      }
-
-      this.isHandlingFocus = false;
-      sendAsyncMessage("Forms:Input", { "type": "blur" });
-    }.bind(this));
+    this.isHandlingFocus = false;
+    sendAsyncMessage("Forms:Input", { "type": "blur" });
   },
 
   isFocusableElement: function fa_isFocusableElement(element) {
     if (element instanceof HTMLSelectElement ||
         element instanceof HTMLTextAreaElement)
       return true;
 
     if (element instanceof HTMLOptionElement &&
deleted file mode 100644
--- a/dom/inputmethod/mochitest/file_test_contenteditable.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<body>
-<div id="text" contenteditable>Jan Jongboom</div>
-<script type="application/javascript;version=1.7">
-  var t = document.querySelector('#text');
-
-  t.focus();
-  var range = document.createRange();
-  range.selectNodeContents(t);
-  range.collapse(false);
-  var selection = window.getSelection();
-  selection.removeAllRanges();
-  selection.addRange(range);
-</script>
-</body>
-</html>
--- a/dom/inputmethod/mochitest/mochitest.ini
+++ b/dom/inputmethod/mochitest/mochitest.ini
@@ -1,17 +1,16 @@
 [DEFAULT]
 # Not supported on Android, bug 983015 for B2G emulator
 skip-if = (toolkit == 'android' || toolkit == 'gonk') || e10s
 support-files =
   inputmethod_common.js
   file_inputmethod.html
   file_inputmethod_1043828.html
   file_test_app.html
-  file_test_contenteditable.html
   file_test_sendkey_cancel.html
   file_test_sms_app.html
   file_test_sms_app_1066515.html
 
 [test_basic.html]
 [test_bug944397.html]
 [test_bug949059.html]
 [test_bug953044.html]
--- a/dom/inputmethod/mochitest/test_bug1059163.html
+++ b/dom/inputmethod/mochitest/test_bug1059163.html
@@ -15,63 +15,69 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.7">
 inputmethod_setup(function() {
   runTest();
 });
 
 // The frame script running in the file
 function appFrameScript() {
+  let document = content.document;
+  let window = content.document.defaultView;
+
+  let t = document.getElementById('text');
+  t.focus();
+
+  let range = document.createRange();
+  range.selectNodeContents(t);
+  range.collapse(false);
+  let selection = window.getSelection();
+  selection.removeAllRanges();
+  selection.addRange(range);
+
   addMessageListener('test:InputMethod:clear', function() {
-    var t = content.document.getElementById('text');
     t.innerHTML = '';
   });
 }
 
 function runTest() {
   let im = navigator.mozInputMethod;
 
   // Set current page as an input method.
   SpecialPowers.wrap(im).setActive(true);
 
   // Create an app frame to recieve keyboard inputs.
   let app = document.createElement('iframe');
-  app.src = 'file_test_contenteditable.html';
+  app.src = 'data:text/html,<html><body><div id="text" contenteditable>Jan Jongboom</div></html>';
   app.setAttribute('mozbrowser', true);
   document.body.appendChild(app);
   app.addEventListener('mozbrowserloadend', function() {
     let mm = SpecialPowers.getBrowserFrameMessageManager(app);
+    mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
+
+    im.oninputcontextchange = function() {
+      if (im.inputcontext) {
+        im.oninputcontextchange = null;
+        register();
+      }
+    };
 
     function register() {
       im.inputcontext.onselectionchange = function() {
         im.inputcontext.onselectionchange = null;
 
         is(im.inputcontext.textBeforeCursor, '', 'textBeforeCursor');
-        is(im.inputcontext.textBeforeCursor, '', 'textAfterCursor');
+        is(im.inputcontext.textAfterCursor, '', 'textAfterCursor');
         is(im.inputcontext.selectionStart, 0, 'selectionStart');
         is(im.inputcontext.selectionEnd, 0, 'selectionEnd');
 
         inputmethod_cleanup();
       };
 
       mm.sendAsyncMessage('test:InputMethod:clear');
     }
-
-    if (im.inputcontext) {
-       register();
-    }
-    else {
-      im.oninputcontextchange = function() {
-        if (im.inputcontext) {
-          im.oninputcontextchange = null;
-          register();
-        }
-      };
-    }
-
-    mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false);
   });
 }
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/inputmethod/mochitest/test_sync_edit.html
+++ b/dom/inputmethod/mochitest/test_sync_edit.html
@@ -20,44 +20,60 @@ inputmethod_setup(function() {
 });
 
 let appFrameScript = function appFrameScript() {
   let input = content.document.body.firstElementChild;
 
   input.focus();
   input.value = 'First1';
   input.blur();
-
-  content.setTimeout(function() {
-    sendAsyncMessage('test:next', {});
-  });
 };
 
 function runTest() {
   let im = navigator.mozInputMethod;
 
   let i = 0;
   im.oninputcontextchange = function() {
-    ok(false, 'Should not receive any inputcontextchange events.');
+    let inputcontext = im.inputcontext;
+    i++;
+    switch (i) {
+      case 1:
+        ok(!!inputcontext, 'Should receive inputcontext from focus().');
+        is(inputcontext.textAfterCursor, 'First');
+
+        break;
+
+      case 2:
+        ok(!!inputcontext, 'Should receive inputcontext from value change.');
+        is(inputcontext.textBeforeCursor, 'First1');
+
+        break;
+
+      case 3:
+        ok(!inputcontext, 'Should lost inputcontext from blur().');
+
+        inputmethod_cleanup();
+        break;
+
+      default:
+        ok(false, 'Unknown event count.');
+
+        inputmethod_cleanup();
+    }
   };
 
   // Set current page as an input method.
   SpecialPowers.wrap(im).setActive(true);
 
   let iframe = document.createElement('iframe');
   iframe.src = 'data:text/html,<html><body><input value="First"></body></html>';
   iframe.setAttribute('mozbrowser', true);
   document.body.appendChild(iframe);
 
   let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-  mm.addMessageListener('test:next', function() {
-    ok(true, '\\o/');
-    inputmethod_cleanup();
-  });
-
   iframe.addEventListener('mozbrowserloadend', function() {
     mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
   });
 }
 
 </script>
 </pre>
 </body>
--- a/dom/inputmethod/mochitest/test_two_inputs.html
+++ b/dom/inputmethod/mochitest/test_two_inputs.html
@@ -29,49 +29,47 @@ let appFrameScript = function appFrameSc
 
   input1.focus();
 
   addMessageListener('test:next', function() {
     i++;
     switch (i) {
       case 2:
         input2.focus();
-
-        break;
-
-      case 3:
-        input2.blur();
-        input2.focus();
+        i++; // keep the same count with the parent frame.
 
         break;
 
       case 4:
         input2.blur();
 
         break;
 
       case 5:
         input2.focus();
-        input2.blur();
-
-        input1.focus();
 
         break;
 
       case 6:
+        input1.focus();
+        i++; // keep the same count with the parent frame.
+
+        break;
+
+      case 8:
         content.document.body.removeChild(input1);
 
         break;
 
-      case 7:
+      case 9:
         input2.focus();
 
         break;
 
-      case 8:
+      case 10:
         content.document.body.removeChild(input2);
 
         break;
     }
   });
 };
 
 function runTest() {
@@ -80,75 +78,83 @@ function runTest() {
   let i = 0;
   im.oninputcontextchange = function(evt) {
     var inputcontext = navigator.mozInputMethod.inputcontext;
 
     i++;
     switch (i) {
       // focus on the first input receives the first input context.
       case 1:
-        ok(!!inputcontext, 'Receving the first input context');
+        ok(!!inputcontext, '1) Receving the first input context');
         is(inputcontext.textAfterCursor, 'First');
 
         mm.sendAsyncMessage('test:next');
         break;
 
-      // focus on the second input (implicitly blur the first input)
-      // results the second input context.
+      // focus on the second input should implicitly blur the first input
       case 2:
-        ok(!!inputcontext, 'Receving the second input context');
-        is(inputcontext.textAfterCursor, 'Second');
+        is(inputcontext, null, '2) Receving null inputcontext');
 
-
-        mm.sendAsyncMessage('test:next');
         break;
 
-      // blur and re-focus on the second input results updated
-      // input context for the second input.
+      // ... and results the second input context.
       case 3:
-        ok(!!inputcontext, 'Receving the second input context');
+        ok(!!inputcontext, '3) Receving the second input context');
         is(inputcontext.textAfterCursor, 'Second');
 
         mm.sendAsyncMessage('test:next');
         break;
 
       // blur on the second input results null input context
       case 4:
-        is(inputcontext, null, 'Receving null inputcontext');
+        is(inputcontext, null, '4) Receving null inputcontext');
 
         mm.sendAsyncMessage('test:next');
         break;
 
-      // focus and blur on the second input sends no message;
-      // focus on the first input receives the first input context.
+      // focus on the second input receives the second input context.
       case 5:
-        ok(!!inputcontext, 'Receving the first input context');
+        ok(!!inputcontext, '5) Receving the second input context');
+        is(inputcontext.textAfterCursor, 'Second');
+
+        mm.sendAsyncMessage('test:next');
+        break;
+
+      // focus on the second input should implicitly blur the first input
+      case 6:
+        is(inputcontext, null, '6) Receving null inputcontext');
+
+        break;
+
+      // ... and results the second input context.
+      case 7:
+        ok(!!inputcontext, '7) Receving the first input context');
         is(inputcontext.textAfterCursor, 'First');
 
         mm.sendAsyncMessage('test:next');
         break;
 
       // remove on the first focused input results null input context
-      case 6:
-        is(inputcontext, null, 'Receving null inputcontext');
+      case 8:
+        is(inputcontext, null, '8) Receving null inputcontext');
 
         mm.sendAsyncMessage('test:next');
         break;
 
       // input context for the second input.
-      case 7:
-        ok(!!inputcontext, 'Receving the second input context');
+      case 9:
+        ok(!!inputcontext, '9) Receving the second input context');
         is(inputcontext.textAfterCursor, 'Second');
 
         mm.sendAsyncMessage('test:next');
         break;
 
       // remove on the second focused input results null input context
-      case 8:
-        is(inputcontext, null, 'Receving null inputcontext');
+      case 10:
+        is(inputcontext, null, '10) Receving null inputcontext');
 
         inputmethod_cleanup();
         break;
 
       default:
         ok(false, 'Receving extra inputcontextchange calls');
         inputmethod_cleanup();
 
--- a/dom/inputmethod/mochitest/test_two_selects.html
+++ b/dom/inputmethod/mochitest/test_two_selects.html
@@ -27,40 +27,48 @@ let appFrameScript = function appFrameSc
 
   select1.focus();
 
   addMessageListener('test:next', function() {
     i++;
     switch (i) {
       case 2:
         select2.focus();
-
-        break;
-
-      case 3:
-        select2.blur();
-        select2.focus();
+        i++; // keep the same count with the parent frame.
 
         break;
 
       case 4:
         select2.blur();
 
         break;
 
       case 5:
         select2.focus();
-        select2.blur();
-
-        select1.focus();
 
         break;
 
       case 6:
-        select1.blur();
+        select1.focus();
+        i++; // keep the same count with the parent frame.
+
+        break;
+
+      case 8:
+        content.document.body.removeChild(select1);
+
+        break;
+
+      case 9:
+        select2.focus();
+
+        break;
+
+      case 10:
+        content.document.body.removeChild(select2);
 
         break;
     }
   });
 };
 
 function runTest() {
   let im = navigator.mozInputMethod;
@@ -68,60 +76,83 @@ function runTest() {
   let i = 0;
   im.oninputcontextchange = function(evt) {
     var inputcontext = navigator.mozInputMethod.inputcontext;
 
     i++;
     switch (i) {
       // focus on the first input receives the first input context.
       case 1:
-        ok(!!inputcontext, 'Receving the first input context');
+        ok(!!inputcontext, '1) Receving the first input context');
         is(inputcontext.textAfterCursor, 'First');
 
         mm.sendAsyncMessage('test:next');
         break;
 
-      // focus on the second input (implicitly blur the first input)
-      // results the second input context.
+      // focus on the second input should implicitly blur the first input
       case 2:
-        ok(!!inputcontext, 'Receving the second input context');
-        is(inputcontext.textAfterCursor, 'Second');
+        is(inputcontext, null, '2) Receving null inputcontext');
 
-
-        mm.sendAsyncMessage('test:next');
         break;
 
-      // blur and re-focus on the second input results updated
-      // input context for the second input.
+      // ... and results the second input context.
       case 3:
-        ok(!!inputcontext, 'Receving the second input context');
+        ok(!!inputcontext, '3) Receving the second input context');
         is(inputcontext.textAfterCursor, 'Second');
 
         mm.sendAsyncMessage('test:next');
         break;
 
       // blur on the second input results null input context
       case 4:
-        is(inputcontext, null, 'Receving null inputcontext');
+        is(inputcontext, null, '4) Receving null inputcontext');
 
         mm.sendAsyncMessage('test:next');
         break;
 
-      // focus and blur on the second input sends no message;
-      // focus on the first input receives the first input context.
+      // focus on the second input receives the second input context.
       case 5:
-        ok(!!inputcontext, 'Receving the first input context');
+        ok(!!inputcontext, '5) Receving the second input context');
+        is(inputcontext.textAfterCursor, 'Second');
+
+        mm.sendAsyncMessage('test:next');
+        break;
+
+      // focus on the second input should implicitly blur the first input
+      case 6:
+        is(inputcontext, null, '6) Receving null inputcontext');
+
+        break;
+
+      // ... and results the second input context.
+      case 7:
+        ok(!!inputcontext, '7) Receving the first input context');
         is(inputcontext.textAfterCursor, 'First');
 
         mm.sendAsyncMessage('test:next');
         break;
 
-      // blur on the first input results null input context
-      case 6:
-        is(inputcontext, null, 'Receving null inputcontext');
+      // remove on the first focused input results null input context
+      case 8:
+        is(inputcontext, null, '8) Receving null inputcontext');
+
+        mm.sendAsyncMessage('test:next');
+        break;
+
+      // input context for the second input.
+      case 9:
+        ok(!!inputcontext, '9) Receving the second input context');
+        is(inputcontext.textAfterCursor, 'Second');
+
+        mm.sendAsyncMessage('test:next');
+        break;
+
+      // remove on the second focused input results null input context
+      case 10:
+        is(inputcontext, null, '10) Receving null inputcontext');
 
         inputmethod_cleanup();
         break;
 
       default:
         ok(false, 'Receving extra inputcontextchange calls');
         inputmethod_cleanup();