Bug 806996 part.6 Test if nsIWidget::OnIMEFocusChange() is called by nsTextStateManager properly r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 09 Nov 2012 17:40:40 +0900
changeset 112793 92701702794263c5fec380e48099eba7294c5fb7
parent 112792 73a92a915ce3597d7be08e7b748d32fb34f61825
child 112794 f2e49ecb67790ef7e9115e0db5385c0b7fd6cf98
push id17778
push usermasayuki@d-toybox.com
push dateFri, 09 Nov 2012 08:40:54 +0000
treeherdermozilla-inbound@927017027942 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs806996
milestone19.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 806996 part.6 Test if nsIWidget::OnIMEFocusChange() is called by nsTextStateManager properly r=smaug
content/events/src/nsIMEStateManager.cpp
content/events/src/nsIMEStateManager.h
widget/tests/test_imestate.html
--- a/content/events/src/nsIMEStateManager.cpp
+++ b/content/events/src/nsIMEStateManager.cpp
@@ -31,16 +31,18 @@
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "nsIFormControl.h"
 #include "nsIForm.h"
 #include "nsHTMLFormElement.h"
 #include "mozilla/Attributes.h"
 #include "nsEventDispatcher.h"
 #include "TextComposition.h"
+#include "mozilla/Preferences.h"
+#include "nsAsyncDOMEvent.h"
 
 using namespace mozilla;
 using namespace mozilla::widget;
 
 // nsTextStateManager notifies widget of any text and selection changes
 //  in the currently focused editor
 // sTextStateObserver points to the currently active nsTextStateManager
 // sTextStateObserver is null if there is no focused editor
@@ -78,16 +80,17 @@ private:
 /******************************************************************/
 /* nsIMEStateManager                                              */
 /******************************************************************/
 
 nsIContent*    nsIMEStateManager::sContent      = nullptr;
 nsPresContext* nsIMEStateManager::sPresContext  = nullptr;
 bool           nsIMEStateManager::sInstalledMenuKeyboardListener = false;
 bool           nsIMEStateManager::sInSecureInputMode = false;
+bool           nsIMEStateManager::sIsTestingIME = false;
 
 nsTextStateManager* nsIMEStateManager::sTextStateObserver = nullptr;
 TextCompositionArray* nsIMEStateManager::sTextCompositions = nullptr;
 
 void
 nsIMEStateManager::Shutdown()
 {
   MOZ_ASSERT(!sTextCompositions || !sTextCompositions->Length());
@@ -725,16 +728,22 @@ nsTextStateManager::nsTextStateManager(n
   }
   if (!mRootContent && mEditableNode->IsNodeOfType(nsINode::eDOCUMENT)) {
     // The document node is editable, but there are no contents, this document
     // is not editable.
     return;
   }
   NS_ENSURE_TRUE_VOID(mRootContent);
 
+  if (nsIMEStateManager::sIsTestingIME) {
+    nsIDocument* doc = aPresContext->Document();
+    (new nsAsyncDOMEvent(doc, NS_LITERAL_STRING("MozIMEFocusIn"),
+                         false, false))->RunDOMEventWhenSafe();
+  }
+
   nsresult rv = mWidget->OnIMEFocusChange(true);
   if (rv == NS_ERROR_NOT_IMPLEMENTED) {
     return;
   }
   NS_ENSURE_SUCCESS_VOID(rv);
 
   ObserveEditableNode();
 }
@@ -757,16 +766,21 @@ nsTextStateManager::ObserveEditableNode(
   mRootContent->AddMutationObserver(this);
 
   mObserving = true;
 }
 
 void
 nsTextStateManager::Destroy(void)
 {
+  if (nsIMEStateManager::sIsTestingIME && mEditableNode) {
+    nsIDocument* doc = mEditableNode->OwnerDoc();
+    (new nsAsyncDOMEvent(doc, NS_LITERAL_STRING("MozIMEFocusOut"),
+                         false, false))->RunDOMEventWhenSafe();
+  }
   mWidget->OnIMEFocusChange(false);
   if (mObserving && mSel) {
     nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(mSel));
     if (selPrivate)
       selPrivate->RemoveSelectionListener(this);
   }
   mSel = nullptr;
   if (mObserving && mRootContent) {
@@ -1023,16 +1037,22 @@ nsIMEStateManager::CreateTextStateManage
     return; // Sometimes, there are no widgets.
   }
 
   // If it's not text ediable, we don't need to create nsTextStateManager.
   if (!IsEditableIMEState(widget)) {
     return;
   }
 
+  static bool sInitializeIsTestingIME = true;
+  if (sInitializeIsTestingIME) {
+    Preferences::AddBoolVarCache(&sIsTestingIME, "test.IME", false);
+    sInitializeIsTestingIME = false;
+  }
+
   sTextStateObserver = new nsTextStateManager(widget, sPresContext, sContent);
   NS_ADDREF(sTextStateObserver);
 }
 
 nsresult
 nsIMEStateManager::GetFocusSelectionAndRoot(nsISelection** aSel,
                                             nsIContent** aRoot)
 {
--- a/content/events/src/nsIMEStateManager.h
+++ b/content/events/src/nsIMEStateManager.h
@@ -124,16 +124,17 @@ protected:
                                       nsIContent* aContent);
 
   static bool IsEditableIMEState(nsIWidget* aWidget);
 
   static nsIContent*    sContent;
   static nsPresContext* sPresContext;
   static bool           sInstalledMenuKeyboardListener;
   static bool           sInSecureInputMode;
+  static bool           sIsTestingIME;
 
   static nsTextStateManager* sTextStateObserver;
 
   // All active compositions in the process are stored by this array.
   // When you get an item of this array and use it, please be careful.
   // The instances in this array can be destroyed automatically if you do
   // something to cause committing or canceling the composition.
   static mozilla::TextCompositionArray* sTextCompositions;
--- a/widget/tests/test_imestate.html
+++ b/widget/tests/test_imestate.html
@@ -110,16 +110,27 @@
 </div>
 <pre id="test">
 </pre>
 
 <script class="testbody" type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
+function hitEventLoop(aFunc, aTimes)
+{
+  SpecialPowers.getDOMWindowUtils(window).advanceTimeAndRefresh(100);
+
+  if (--aTimes) {
+    setTimeout(hitEventLoop, 0, aFunc, aTimes);
+  } else {
+    setTimeout(aFunc, 20);
+  }
+}
+
 var gUtils = window.
       QueryInterface(Components.interfaces.nsIInterfaceRequestor).
       getInterface(Components.interfaces.nsIDOMWindowUtils);
 var gFM = Components.classes["@mozilla.org/focus-manager;1"].
       getService(Components.interfaces.nsIFocusManager);
 const kIMEEnabledSupported = navigator.platform.indexOf("Mac") == 0 ||
                              navigator.platform.indexOf("Win") == 0 ||
                              navigator.platform.indexOf("Linux") == 0;
@@ -129,35 +140,53 @@ const kIMEEnabledSupported = navigator.p
 // we cannot test it on Win32 if the system didn't be installed IME. So,
 // currently we should not run the open state testing.
 const kIMEOpenSupported = false;
 
 function runBasicTest(aIsEditable, aInDesignMode, aDescription)
 {
   function test(aTest)
   {
-    function moveFocus(aTest)
+    function moveFocus(aTest, aFocusEventHandler)
     {
       if (aInDesignMode) {
         if (document.activeElement) {
           document.activeElement.blur();
         }
       } else if (aIsEditable) {
         document.getElementById("display").focus();
       } else if (aTest.expectedEnabled == gUtils.IME_STATUS_ENABLED) {
         document.getElementById("password").focus();
       } else {
         document.getElementById("text").focus();
       }
       var previousFocusedElement = gFM.focusedElement;
       var element = document.getElementById(aTest.id);
+      var subDocument = null;
       if (element.contentDocument) {
+        subDocument = element.contentDocument;
         element = element.contentDocument.documentElement;
       }
+
+      document.addEventListener("MozIMEFocusIn", aFocusEventHandler, true);
+      document.addEventListener("MozIMEFocusOut", aFocusEventHandler, true);
+      if (subDocument) {
+        subDocument.addEventListener("MozIMEFocusIn", aFocusEventHandler, true);
+        subDocument.addEventListener("MozIMEFocusOut", aFocusEventHandler, true);
+      }
+
       element.focus();
+
+      document.removeEventListener("MozIMEFocusIn", aFocusEventHandler, true);
+      document.removeEventListener("MozIMEFocusOut", aFocusEventHandler, true);
+      if (element.contentDocument) {
+        subDocument.removeEventListener("MozIMEFocusIn", aFocusEventHandler, true);
+        subDocument.removeEventListener("MozIMEFocusOut", aFocusEventHandler, true);
+      }
+
       var focusedElement = gFM.focusedElement;
       if (focusedElement) {
         var bindingParent = document.getBindingParent(focusedElement);
         if (bindingParent) {
           focusedElement = bindingParent;
         }
       }
       if (aTest.focusable) {
@@ -181,19 +210,100 @@ function runBasicTest(aIsEditable, aInDe
                                             ", wrong opened state";
       is(gUtils.IMEIsOpen,
          aTest.changeOpened ? aTest.expectedOpened : aOpened, message);
     }
 
     // IME Enabled state testing
     var enabled = gUtils.IME_STATUS_ENABLED;
     if (kIMEEnabledSupported) {
-      if (!moveFocus(aTest)) {
+      var mozIMEFocusInCount = 0;
+      var mozIMEFocusOutCount = 0;
+      var IMEHasFocus = false;
+
+      function onFocus(aEvent)
+      {
+        switch (aEvent.type) {
+          case "MozIMEFocusIn":
+            mozIMEFocusInCount++;
+            IMEHasFocus = true;
+            is(gUtils.IMEStatus, aTest.expectedEnabled,
+               aDescription + ": " + aTest.description +
+                 ", MozIMEFocusIn event must be fired after IME state is updated");
+            break;
+          case "MozIMEFocusOut":
+            mozIMEFocusOutCount++;
+            IMEHasFocus = false;
+            is_not(gUtils.IMEStatus, aTest.expectedEnabled,
+                   aDescription + ": " + aTest.description +
+                     ", MozIMEFocusOut event must be fired before IME state is updated");
+            break;
+        }
+      }
+
+      if (!moveFocus(aTest, onFocus)) {
         return;
       }
+
+      if (aTest.focusable) {
+        if (!aTest.focusEventNotFired) {
+          if (aTest.expectedEnabled == gUtils.IME_STATUS_ENABLED || aTest.expectedEnabled == gUtils.IME_STATUS_PASSWORD) {
+            var toDesignModeEditor = (document.activeElement.contentDocument &&
+                                      (document.activeElement.contentDocument.designMode == "on"));
+            if (aInDesignMode && toDesignModeEditor) {
+              is(mozIMEFocusOutCount, 0,
+                 aDescription + ": " + aTest.description +
+                   ", MozIMEFocusOut event shouldn't be fired in designMode since focus isn't moved from another editor");
+              todo(mozIMEFocusInCount > 0,
+                   aDescription + ": " + aTest.description + ", MozIMEFocusIn event should be fired"); // bug 805766
+            } else {
+              ok(mozIMEFocusOutCount > 0,
+                 aDescription + ": " + aTest.description +
+                   ", MozIMEFocusOut event should be fired for the previous focused editor");
+              if (toDesignModeEditor) {
+                todo(mozIMEFocusInCount > 0,
+                     aDescription + ": " + aTest.description + ", MozIMEFocusIn event should be fired"); // bug 805766
+              } else {
+                ok(mozIMEFocusInCount > 0,
+                   aDescription + ": " + aTest.description + ", MozIMEFocusIn event should be fired");
+              }
+            }
+            if (aInDesignMode || toDesignModeEditor) { // bug 805766
+              todo(IMEHasFocus,
+                   aDescription + ": " + aTest.description +
+                     ", The latest MozIMEFocus* event must be MozIMEFocusIn");
+            } else {
+              ok(IMEHasFocus,
+                 aDescription + ": " + aTest.description +
+                   ", The latest MozIMEFocus* event must be MozIMEFocusIn");
+            }
+          } else {
+            is(mozIMEFocusInCount, 0,
+               aDescription + ": " + aTest.description +
+                 ", MozIMEFocusIn event shouldn't be fired");
+            ok(mozIMEFocusOutCount > 0,
+               aDescription + ": " + aTest.description +
+                 ", MozIMEFocusOut event should be fired");
+            ok(!IMEHasFocus,
+               aDescription + ": " + aTest.description +
+                 ", The latest MozIMEFocus* event must be MozIMEFocusOut");
+          }
+        } else {
+          todo(false, aDescription + ": " + aTest.description +
+               ", In this case, focus event isn't fired, it's a bug, so, couldn't test it");
+        }
+      } else {
+        is(mozIMEFocusInCount, 0,
+           aDescription + ": " + aTest.description +
+             ", MozIMEFocusIn event shouldn't be fired at testing non-focusable element");
+        is(mozIMEFocusOutCount, 0,
+           aDescription + ": " + aTest.description +
+             ", MozIMEFocusOut event shouldn't be fired at testing non-focusable element");
+      }
+
       enabled = gUtils.IMEStatus;
       inputtype = gUtils.focusedInputType;
       is(enabled, aTest.expectedEnabled,
          aDescription + ": " + aTest.description + ", wrong enabled state");
       if (aTest.expectedType && !aInDesignMode) {
         is(inputtype, aTest.expectedType,
            aDescription + ": " + aTest.description + ", wrong input type");
       } else if (aInDesignMode) {
@@ -249,32 +359,35 @@ function runBasicTest(aIsEditable, aInDe
       expectedType: "password" },
     { id: "password_readonly",
       description: "input[type=password][readonly]",
       focusable: !aInDesignMode,
       expectedEnabled: kEnabledStateOnReadonlyField },
     { id: "checkbox",
       description: "input[type=checkbox]",
       focusable: !aInDesignMode,
+      focusEventNotFired: aIsEditable && !aInDesignMode,
       expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "radio",
       description: "input[type=radio]",
       focusable: !aInDesignMode,
+      focusEventNotFired: aIsEditable && !aInDesignMode,
       expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "submit",
       description: "input[type=submit]",
       focusable: !aInDesignMode,
       expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "reset",
       description: "input[type=reset]",
       focusable: !aInDesignMode,
       expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "file",
       description: "input[type=file]",
       focusable: !aInDesignMode,
+      focusEventNotFired: aIsEditable && !aInDesignMode,
       expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "button",
       description: "input[type=button]",
       focusable: !aInDesignMode,
       expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "image",
       description: "input[type=image]",
       focusable: !aInDesignMode,
@@ -316,20 +429,22 @@ function runBasicTest(aIsEditable, aInDe
       expectedEnabled: gUtils.IME_STATUS_ENABLED },
     { id: "textarea_readonly",
       description: "textarea[readonly]",
       focusable: !aInDesignMode,
       expectedEnabled: kEnabledStateOnReadonlyField },
     { id: "select",
       description: "select (dropdown list)",
       focusable: !aInDesignMode,
+      focusEventNotFired: aIsEditable && !aInDesignMode,
       expectedEnabled: kEnabledStateOnNonEditableElement },
     { id: "select_multiple",
       description: "select (list box)",
       focusable: !aInDesignMode,
+      focusEventNotFired: aIsEditable && !aInDesignMode,
       expectedEnabled: kEnabledStateOnNonEditableElement },
 
     // a element
     { id: "a_href",
       description: "a[href]",
       focusable: !aIsEditable && !aInDesignMode,
       expectedEnabled: kEnabledStateOnNonEditableElement },
 
@@ -1237,21 +1352,76 @@ function runTestPasswordFieldOnDialog()
       QueryInterface(Components.interfaces.nsIInterfaceRequestor).
       getInterface(Components.interfaces.nsIDOMWindowUtils);
     is(utils.IMEStatus, utils.IME_STATUS_PASSWORD,
        "IME isn't disabled on a password field of password dialog");
     synthesizeKey("VK_ESCAPE", { }, dialog);
   }
 }
 
+function runBug580388Tests(aCallback)
+{
+  if (document.activeElement) {
+    document.activeElement.blur();
+  }
+
+  var input = document.getElementById("text");
+  input.focus();
+  input.style.overflow = "visible";
+
+  var mozIMEFocusIn = 0;
+  var mozIMEFocusOut = 0;
+  var IMEHasFocus = false;
+
+  var handler = function (aEvent) {
+    switch (aEvent.type) {
+      case "MozIMEFocusIn":
+        mozIMEFocusIn++;
+        IMEHasFocus = true;
+        break;
+      case "MozIMEFocusOut":
+        mozIMEFocusOut++;
+        IMEHasFocus = false;
+        break;
+    }
+  };
+
+  var onInput = function (aEvent) {
+    aEvent.target.style.overflow = "hidden";
+  }
+
+  document.addEventListener("MozIMEFocusIn", handler, true);
+  document.addEventListener("MozIMEFocusOut", handler, true);
+  input.addEventListener("input", onInput, true);
+
+  sendChar("a");
+
+  hitEventLoop(function () {
+    ok(mozIMEFocusOut > 0, "runBug580388Tests(): IME focus must be lost at reframing");
+    ok(mozIMEFocusIn > 0, "runBug580388Tests(): IME focus must be restored after reframing");
+    ok(IMEHasFocus, "runBug580388Tests(): IME must have focus after reframing");
+
+    document.removeEventListener("MozIMEFocusIn", handler, true);
+    document.removeEventListener("MozIMEFocusOut", handler, true);
+    input.removeEventListener("input", onInput, true);
+
+    input.style.overflow = "visible";
+    input.value = "";
+
+    hitEventLoop(aCallback, 20);
+  }, 20);
+}
+
 function runTests()
 {
   if (!kIMEEnabledSupported && !kIMEOpenSupported)
     return;
 
+  SpecialPowers.setBoolPref("test.IME", true);
+
   // test for normal contents.
   runBasicTest(false, false, "Testing of normal contents");
 
   // test for plugin contents
   runPluginTest();
 
   var container = document.getElementById("display");
   // test for contentEditable="true"
@@ -1286,26 +1456,25 @@ function runTests()
 
   // test whether the IME state and composition are not changed unexpectedly
   runEditorFlagChangeTests();
 
   // test password field on dialog
   // XXX temporary disable against failure
   //runTestPasswordFieldOnDialog();
 
-  runASyncTests();
-}
-
-function runASyncTests()
-{
-  // The tests must call onFinish() method.
-  runEditableSubframeTests();
+  // Asynchronous tests
+  runBug580388Tests(function () {
+    // This will call onFinish(), so, this test must be the last.
+    runEditableSubframeTests();
+  });
 }
 
 function onFinish()
 {
+  SpecialPowers.clearUserPref("test.IME");
   SimpleTest.finish();
 }
 
 </script>
 </body>
 
 </html>