Bug 1467796 - part 2: Make autocomplete use new method TextEditor::ReplaceTextAsAction() which replaces all text with specified text r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 03 Jul 2018 22:25:52 +0900
changeset 487521 fd043d4697737c2abb82a71ec16ba9873606d7be
parent 487520 3403701f0727a33f927e46da1eef2950a93b0e1a
child 487522 48ffc0ee2258cc74a7fcd2ea60eba7c65779b75a
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1467796, 1368544
milestone63.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 1467796 - part 2: Make autocomplete use new method TextEditor::ReplaceTextAsAction() which replaces all text with specified text r=m_kato InputEvent.inputType needs to distinguish whether inserting text is caused by insertText command or replaced by autocomplete or spellchecker. Therefore, nsTextEditorState::SetValue() cannot use TextEditor::InsertTextAsAction() nor TextEditor::DeleteSelectionAsAction(). This patch reuses TextEditor::SetText()'s slow path for the new method. Note that the new method uses EditSubAction::eInsertText as top level edit sub- action because specifying this improves undo/redo behavior. And also this patch modifies test_bug1368544.html. Oddly, only on Android, we get different result. After removing all text with setUserInput(""), TextEditor::DeleteSelectionAsSubAction() removes both text node and non-bogus <br> element from the anonymous-div element. However, only on Android, new <br> element is recreated. I've not understood where this difference comes from yet. MozReview-Commit-ID: GKNksctGik
dom/html/nsTextEditorState.cpp
editor/libeditor/TextEditRules.cpp
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditor.h
editor/libeditor/tests/test_bug1368544.html
toolkit/content/tests/chrome/chrome.ini
toolkit/content/tests/chrome/file_editor_with_autocomplete.js
toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html
toolkit/content/tests/chrome/test_editor_for_textbox_with_autocomplete.xul
--- a/dom/html/nsTextEditorState.cpp
+++ b/dom/html/nsTextEditorState.cpp
@@ -2420,20 +2420,34 @@ nsTextEditorState::SetValue(const nsAStr
         // set the value, restore flags
         {
           AutoRestoreEditorState restoreState(textEditor);
 
           mTextListener->SettingValue(true);
           bool notifyValueChanged = !!(aFlags & eSetValue_Notify);
           mTextListener->SetValueChanged(notifyValueChanged);
 
-          // We preserve the undo history if we are explicitly setting the
-          // value for the user's input, or if we are setting the value for a
-          // XUL text control.
-          if (aFlags & (eSetValue_BySetUserInput | eSetValue_ForXUL)) {
+          if (aFlags & eSetValue_BySetUserInput) {
+            // If the caller inserts text as part of user input, for example,
+            // autocomplete, we need to replace the text as "insert string"
+            // because undo should cancel only this operation (i.e., previous
+            // transactions typed by user shouldn't be merged with this).
+            DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newValue);
+            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+              "Failed to set the new value");
+          } else if (aFlags & eSetValue_ForXUL) {
+            // On XUL <textbox> element, we need to preserve existing undo
+            // transactions.
+            // XXX Do we really need to do such complicated optimization?
+            //     This was landed for web pages which set <textarea> value
+            //     per line (bug 518122).  For example:
+            //       for (;;) {
+            //         textarea.value += oneLineText + "\n";
+            //       }
+            //     However, this path won't be used in web content anymore.
             nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon.get();
             uint32_t currentLength = currentValue.Length();
             uint32_t newlength = newValue.Length();
             if (!currentLength ||
                 !StringBeginsWith(newValue, currentValue)) {
               // Replace the whole text.
               currentLength = 0;
               kungFuDeathGrip->SelectAll();
@@ -2452,16 +2466,19 @@ nsTextEditorState::SetValue(const nsAStr
                 "Failed to remove the text");
             } else {
               DebugOnly<nsresult> rv =
                 textEditor->InsertTextAsAction(insertValue);
               NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                 "Failed to insert the new value");
             }
           } else {
+            // On <input> or <textarea>, we shouldn't preserve existing undo
+            // transactions because other browsers do not preserve them too
+            // and not preserving transactions makes setting value faster.
             AutoDisableUndo disableUndo(textEditor);
             if (selection) {
               // Since we don't use undo transaction, we don't need to store
               // selection state.  SetText will set selection to tail.
               // Note that textEditor will collapse selection to the end.
               // Therefore, it's safe to use RemoveAllRangesTemporarily() here.
               selection->RemoveAllRangesTemporarily();
             }
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -900,19 +900,22 @@ TextEditRules::WillSetText(bool* aCancel
   MOZ_ASSERT(aString);
   MOZ_ASSERT(aString->FindChar(static_cast<char16_t>('\r')) == kNotFound);
 
   CANCEL_OPERATION_IF_READONLY_OR_DISABLED
 
   *aHandled = false;
   *aCancel = false;
 
-  if (!IsPlaintextEditor() || TextEditorRef().IsIMEComposing() ||
+  if (!IsPlaintextEditor() ||
+      TextEditorRef().IsIMEComposing() ||
+      TextEditorRef().IsUndoRedoEnabled() ||
       aMaxLength != -1) {
-    // SetTextImpl only supports plain text editor without IME.
+    // SetTextImpl only supports plain text editor without IME and
+    // when we don't need to make it undoable.
     return NS_OK;
   }
 
   if (IsPasswordEditor() && LookAndFeel::GetEchoPassword() &&
       !DontEchoPassword()) {
     // Echo password timer will implement on InsertText.
     return NS_OK;
   }
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -1131,25 +1131,53 @@ TextEditor::InsertParagraphSeparatorAsAc
   return rv;
 }
 
 nsresult
 TextEditor::SetText(const nsAString& aString)
 {
   MOZ_ASSERT(aString.FindChar(static_cast<char16_t>('\r')) == kNotFound);
 
+  AutoPlaceholderBatch batch(this, nullptr);
+  nsresult rv = SetTextAsSubAction(aString);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
+}
+
+nsresult
+TextEditor::ReplaceTextAsAction(const nsAString& aString)
+{
+  AutoPlaceholderBatch batch(this, nullptr);
+
+  // This should emulates inserting text for better undo/redo behavior.
+  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
+                                      *this, EditSubAction::eInsertText,
+                                      nsIEditor::eNext);
+
+  nsresult rv = SetTextAsSubAction(aString);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return NS_OK;
+}
+
+nsresult
+TextEditor::SetTextAsSubAction(const nsAString& aString)
+{
+  MOZ_ASSERT(mPlaceholderBatch);
+
   if (NS_WARN_IF(!mRules)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   // Protect the edit rules object from dying
   RefPtr<TextEditRules> rules(mRules);
 
-  // delete placeholder txns merge.
-  AutoPlaceholderBatch batch(this, nullptr);
   AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
                                       *this, EditSubAction::eSetText,
                                       nsIEditor::eNext);
 
   // pre-process
   RefPtr<Selection> selection = GetSelection();
   if (NS_WARN_IF(!selection)) {
     return NS_ERROR_NULL_POINTER;
@@ -1164,16 +1192,21 @@ TextEditor::SetText(const nsAString& aSt
     rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   if (cancel) {
     return NS_OK;
   }
   if (!handled) {
+    // Note that do not notify selectionchange caused by selecting all text
+    // because it's preparation of our delete implementation so web apps
+    // shouldn't receive such selectionchange before the first mutation.
+    AutoUpdateViewBatch preventSelectionChangeEvent(this);
+
     // We want to select trailing BR node to remove all nodes to replace all,
     // but TextEditor::SelectEntireDocument doesn't select that BR node.
     if (rules->DocumentIsEmpty()) {
       // if it's empty, don't select entire doc - that would select
       // the bogus node
       Element* rootElement = GetRoot();
       if (NS_WARN_IF(!rootElement)) {
         return NS_ERROR_FAILURE;
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -156,16 +156,24 @@ public:
    * Replace existed string with a string.
    * This is fast path to replace all string when using single line control.
    *
    * @ param aString   the string to be set
    */
   nsresult SetText(const nsAString& aString);
 
   /**
+   * Replace all text in this editor with aString and treat the change as
+   * inserting the string.
+   *
+   * @param aString    The string to set.
+   */
+  nsresult ReplaceTextAsAction(const nsAString& aString);
+
+  /**
    * OnInputParagraphSeparator() is called when user tries to separate current
    * paragraph with Enter key press or something.
    */
   nsresult OnInputParagraphSeparator();
 
   /**
    * OnCompositionStart() is called when editor receives eCompositionStart
    * event which should be handled in this editor.
@@ -259,16 +267,24 @@ protected: // May be called by friends.
    * @param aStripWrappers      Whether the parent blocks should be removed
    *                            when they become empty.
    */
   virtual nsresult
   DeleteSelectionWithTransaction(EDirection aAction,
                                  EStripWrappers aStripWrappers);
 
   /**
+   * Replace existed string with aString.  Caller must guarantee that there
+   * is a placeholder transaction which will have the transaction.
+   *
+   * @ param aString   The string to be set.
+   */
+  nsresult SetTextAsSubAction(const nsAString& aString);
+
+  /**
    * InsertBrElementWithTransaction() creates a <br> element and inserts it
    * before aPointToInsert.  Then, tries to collapse selection at or after the
    * new <br> node if aSelect is not eNone.
    *
    * @param aSelection          The selection of this editor.
    * @param aPointToInsert      The DOM point where should be <br> node inserted
    *                            before.
    * @param aSelect             If eNone, this won't change selection.
--- a/editor/libeditor/tests/test_bug1368544.html
+++ b/editor/libeditor/tests/test_bug1368544.html
@@ -47,19 +47,30 @@ SimpleTest.waitForFocus(() => {
       sendString("AAA");
       synthesizeKey("KEY_Backspace", {repeat: 3});
       is(textarea.value, "", "value is empty");
       ok(editor.rootElement.hasChildNodes(),
          "editor of textarea has child node even if value is empty");
 
       textarea.value = "ABC";
       SpecialPowers.wrap(textarea).setUserInput("");
-      ok(editor.rootElement.hasChildNodes(),
-         "editor of textarea has child node even if value is empty");
-
+      is(textarea.value, "",
+         "textarea should become empty when setUserInput() is called with empty string");
+      if (navigator.appVersion.includes("Android")) {
+        todo(!editor.rootElement.hasChildNodes(),
+             "editor of textarea should have no children when user input emulation set the value to empty");
+        if (editor.rootElement.childNodes.length > 0) {
+          is(editor.rootElement.childNodes.length, 1, "There should be only one <br> node");
+          is(editor.rootElement.firstChild.tagName.toLowerCase(), "br", "The node should be a <br> element node");
+          is(editor.rootElement.firstChild.getAttribute("_moz_editor_bogus_node"), null, "The <br> should not be a bogus node");
+        }
+      } else {
+        ok(!editor.rootElement.hasChildNodes(),
+           "editor of textarea should have no children when user input emulation set the value to empty");
+      }
       textarea.value = "ABC";
       synthesizeKey("KEY_Enter", {repeat: 2});
       textarea.value = "";
       ok(editor.rootElement.hasChildNodes(),
          "editor of textarea has child node even if value is empty");
 
       sendString("AAA");
       is(textarea.value, "AAA", "value is AAA");
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -11,16 +11,17 @@ support-files =
   bug366992_window.xul
   bug409624_window.xul
   bug429723_window.xul
   bug624329_window.xul
   dialog_dialogfocus.xul
   dialog_dialogfocus2.xul
   file_about_networking_wsh.py
   file_autocomplete_with_composition.js
+  file_editor_with_autocomplete.js
   findbar_entireword_window.xul
   findbar_events_window.xul
   findbar_window.xul
   frame_popup_anchor.xul
   frame_popupremoving_frame.xul
   frame_subframe_origin_subframe1.xul
   frame_subframe_origin_subframe2.xul
   popup_childframe_node.xul
@@ -101,16 +102,18 @@ skip-if = (os == 'mac' && os_version == 
 skip-if = toolkit == "cocoa"
 [test_button.xul]
 [test_closemenu_attribute.xul]
 [test_colorpicker_popup.xul]
 [test_contextmenu_list.xul]
 [test_custom_element_base.xul]
 [test_deck.xul]
 [test_dialogfocus.xul]
+[test_editor_for_input_with_autocomplete.html]
+[test_editor_for_textbox_with_autocomplete.xul]
 [test_findbar.xul]
 subsuite = clipboard
 [test_findbar_entireword.xul]
 [test_findbar_events.xul]
 [test_focus_anons.xul]
 [test_frames.xul]
 [test_hiddenitems.xul]
 [test_hiddenpaging.xul]
copy from toolkit/content/tests/chrome/file_autocomplete_with_composition.js
copy to toolkit/content/tests/chrome/file_editor_with_autocomplete.js
--- a/toolkit/content/tests/chrome/file_autocomplete_with_composition.js
+++ b/toolkit/content/tests/chrome/file_editor_with_autocomplete.js
@@ -1,532 +1,377 @@
-// nsDoTestsForAutoCompleteWithComposition tests autocomplete with composition.
-// Users must include SimpleTest.js and EventUtils.js.
+// nsDoTestsForEditorWithAutoComplete tests basic functions of editor with autocomplete.
+// Users must include SimpleTest.js and EventUtils.js, and register "Mozilla" to the autocomplete for the target.
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-function waitForCondition(condition, nextTest) {
-  var tries = 0;
-  var interval = setInterval(function() {
-    if (condition() || tries >= 30) {
-      moveOn();
-    }
-    tries++;
-  }, 100);
-  var moveOn = function() { clearInterval(interval); nextTest(); };
+async function waitForCondition(condition) {
+  return new Promise(resolve => {
+    var tries = 0;
+    var interval = setInterval(function() {
+      if (condition() || tries >= 60) {
+        moveOn();
+      }
+      tries++;
+    }, 100);
+    var moveOn = function() { clearInterval(interval); resolve(); };
+  });
 }
 
-function nsDoTestsForAutoCompleteWithComposition(aDescription,
-                                                 aWindow,
-                                                 aTarget,
-                                                 aAutoCompleteController,
-                                                 aIsFunc,
-                                                 aGetTargetValueFunc,
-                                                 aOnFinishFunc) {
+function nsDoTestsForEditorWithAutoComplete(aDescription,
+                                                  aWindow,
+                                                  aTarget,
+                                                  aAutoCompleteController,
+                                                  aIsFunc,
+                                                  aGetTargetValueFunc) {
   this._description = aDescription;
   this._window = aWindow;
   this._target = aTarget;
   this._controller = aAutoCompleteController;
 
   this._is = aIsFunc;
   this._getTargetValue = aGetTargetValueFunc;
-  this._onFinish = aOnFinishFunc;
 
   this._target.focus();
 
   this._DefaultCompleteDefaultIndex =
     this._controller.input.completeDefaultIndex;
-
-  this._doTests();
 }
 
-nsDoTestsForAutoCompleteWithComposition.prototype = {
+nsDoTestsForEditorWithAutoComplete.prototype = {
   _window: null,
   _target: null,
   _controller: null,
   _DefaultCompleteDefaultIndex: false,
   _description: "",
 
   _is: null,
   _getTargetValue() { return "not initialized"; },
-  _onFinish: null,
 
-  _doTests() {
-    if (++this._testingIndex == this._tests.length) {
-      this._controller.input.completeDefaultIndex =
-        this._DefaultCompleteDefaultIndex;
-      this._onFinish();
-      return;
-    }
-
-    var test = this._tests[this._testingIndex];
-    if (this._controller.input.completeDefaultIndex != test.completeDefaultIndex) {
-      this._controller.input.completeDefaultIndex = test.completeDefaultIndex;
-    }
-    test.execute(this._window);
+  run: async function runTestsImpl() {
+    for (let test of this._tests) {
+      if (this._controller.input.completeDefaultIndex != test.completeDefaultIndex) {
+        this._controller.input.completeDefaultIndex = test.completeDefaultIndex;
+      }
 
-    waitForCondition(() => {
-      return this._controller.searchStatus >=
-             Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
-    },
-    this._checkResult.bind(this));
-  },
+      if (test.execute(this._window, this._target) === false) {
+        continue;
+      }
 
-  _checkResult() {
-    var test = this._tests[this._testingIndex];
-    this._is(this._getTargetValue(), test.value,
-             this._description + ", " + test.description + ": value");
-    this._is(this._controller.searchString, test.searchString,
-             this._description + ", " + test.description + ": searchString");
-    this._is(this._controller.input.popupOpen, test.popup,
-             this._description + ", " + test.description + ": popupOpen");
-    this._doTests();
+      await waitForCondition(() => {
+        return this._controller.searchStatus >=
+               Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+      });
+      this._checkResult(test);
+    }
+    this._controller.input.completeDefaultIndex = this._DefaultCompleteDefaultIndex;
   },
 
-  _testingIndex: -1,
+  _checkResult(aTest) {
+    this._is(this._getTargetValue(), aTest.value,
+             this._description + ", " + aTest.description + ": value");
+    this._is(this._controller.searchString, aTest.searchString,
+             this._description + ", " + aTest.description + ": searchString");
+    this._is(this._controller.input.popupOpen, aTest.popup,
+             this._description + ", " + aTest.description + ": popupOpen");
+    this._is(this._controller.searchStatus, Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH,
+             this._description + ", " + aTest.description + ": status");
+  },
+
   _tests: [
-    // Simple composition when popup hasn't been shown.
-    // The autocomplete popup should not be shown during composition, but
-    // after compositionend, the popup should be shown.
-    { description: "compositionstart shouldn't open the popup",
+    { description: "Undo/Redo behavior check when typed text exactly matches the case: type 'Mo'",
       completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "M",
-              "clauses":
-              [
-                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 1, "length": 0 },
-            "key": { key: "M" },
-          }, aWindow);
-      }, popup: false, value: "M", searchString: ""
+      execute(aWindow, aTarget) {
+        synthesizeKey("M", { shiftKey: true }, aWindow);
+        synthesizeKey("o", {}, aWindow);
+        return true;
+      }, popup: true, value: "Mo", searchString: "Mo"
     },
-    { description: "modifying composition string shouldn't open the popup",
+    { description: "Undo/Redo behavior check when typed text exactly matches the case: select 'Mozilla' to complete the word",
       completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "Mo",
-              "clauses":
-              [
-                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 2, "length": 0 },
-            "key": { key: "o" },
-          }, aWindow);
-      }, popup: false, value: "Mo", searchString: ""
+      execute(aWindow, aTarget) {
+        synthesizeKey("KEY_ArrowDown", {}, aWindow);
+        synthesizeKey("KEY_Enter", {}, aWindow);
+        return true;
+      }, popup: false, value: "Mozilla", searchString: "Mozilla"
     },
-    { description: "compositionend should open the popup",
+    { description: "Undo/Redo behavior check when typed text exactly matches the case: undo the word, but typed text shouldn't be canceled",
       completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } }, aWindow);
+      execute(aWindow, aTarget) {
+        synthesizeKey("z", { accelKey: true }, aWindow);
+        return true;
       }, popup: true, value: "Mo", searchString: "Mo"
     },
-    // If composition starts when popup is shown, the compositionstart event
-    // should cause closing the popup.
-    { description: "compositionstart should close the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "z",
-              "clauses":
-              [
-                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 1, "length": 0 },
-            "key": { key: "z" },
-          }, aWindow);
-      }, popup: false, value: "Moz", searchString: "Mo"
-    },
-    { description: "modifying composition string shouldn't reopen the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "zi",
-              "clauses":
-              [
-                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 2, "length": 0 },
-            "key": { key: "i" },
-          }, aWindow);
-      }, popup: false, value: "Mozi", searchString: "Mo"
-    },
-    { description: "compositionend should research the result and open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } }, aWindow);
-      }, popup: true, value: "Mozi", searchString: "Mozi"
-    },
-    // If composition is cancelled, the value shouldn't be changed.
-    { description: "compositionstart should reclose the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "l",
-              "clauses":
-              [
-                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 1, "length": 0 },
-            "key": { key: "l" },
-          }, aWindow);
-      }, popup: false, value: "Mozil", searchString: "Mozi"
-    },
-    { description: "modifying composition string shouldn't reopen the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "ll",
-              "clauses":
-              [
-                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 2, "length": 0 },
-            "key": { key: "l" },
-          }, aWindow);
-      }, popup: false, value: "Mozill", searchString: "Mozi"
-    },
-    { description: "modifying composition string to empty string shouldn't reopen the popup",
+    { description: "Undo/Redo behavior check when typed text exactly matches the case: undo the typed text",
       completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "",
-              "clauses":
-              [
-                { "length": 0, "attr": 0 }
-              ]
-            },
-            "caret": { "start": 0, "length": 0 },
-            "key": { key: "KEY_Backspace" },
-          }, aWindow);
-      }, popup: false, value: "Mozi", searchString: "Mozi"
+      execute(aWindow, aTarget) {
+        synthesizeKey("z", { accelKey: true }, aWindow);
+        return true;
+      }, popup: false, value: "", searchString: ""
     },
-    { description: "cancled compositionend should reopen the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } }, aWindow);
-      }, popup: true, value: "Mozi", searchString: "Mozi"
-    },
-    // But if composition replaces some characters and canceled, the search
-    // string should be the latest value.
-    { description: "compositionstart with selected string should close the popup",
+    { description: "Undo/Redo behavior check when typed text exactly matches the case: redo the typed text",
       completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow);
-        synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow);
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "z",
-              "clauses":
-              [
-                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 1, "length": 0 },
-            "key": { key: "z" },
-          }, aWindow);
-      }, popup: false, value: "Moz", searchString: "Mozi"
-    },
-    { description: "modifying composition string shouldn't reopen the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "zi",
-              "clauses":
-              [
-                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 2, "length": 0 },
-            "key": { key: "i" },
-          }, aWindow);
-      }, popup: false, value: "Mozi", searchString: "Mozi"
-    },
-    { description: "modifying composition string to empty string shouldn't reopen the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "",
-              "clauses":
-              [
-                { "length": 0, "attr": 0 }
-              ]
-            },
-            "caret": { "start": 0, "length": 0 },
-            "key": { key: "KEY_Backspace" },
-          }, aWindow);
-      }, popup: false, value: "Mo", searchString: "Mozi"
-    },
-    { description: "canceled compositionend should search the result with the latest value",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } }, aWindow);
+      execute(aWindow, aTarget) {
+        synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
+        return true;
       }, popup: true, value: "Mo", searchString: "Mo"
     },
-    // If all characters are removed, the popup should be closed.
-    { description: "the value becomes empty by backspace, the popup should be closed",
+    { description: "Undo/Redo behavior check when typed text exactly matches the case: redo the word",
+      completeDefaultIndex: false,
+      execute(aWindow, aTarget) {
+        synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "Mozilla", searchString: "Mozilla"
+    },
+    { description: "Undo/Redo behavior check when typed text exactly matches the case: removing all text for next test...",
+      completeDefaultIndex: false,
+      execute(aWindow, aTarget) {
+        synthesizeKey("a", { accelKey: true }, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
+        return true;
+      }, popup: false, value: "", searchString: ""
+    },
+
+    { description: "Undo/Redo behavior check when typed text does not match the case: type 'mo'",
+      completeDefaultIndex: false,
+      execute(aWindow, aTarget) {
+        synthesizeKey("m", {}, aWindow);
+        synthesizeKey("o", {}, aWindow);
+        return true;
+      }, popup: true, value: "mo", searchString: "mo"
+    },
+    { description: "Undo/Redo behavior check when typed text does not match the case: select 'Mozilla' to complete the word",
       completeDefaultIndex: false,
-      execute(aWindow) {
+      execute(aWindow, aTarget) {
+        synthesizeKey("KEY_ArrowDown", {}, aWindow);
+        synthesizeKey("KEY_Enter", {}, aWindow);
+        return true;
+      }, popup: false, value: "Mozilla", searchString: "Mozilla"
+    },
+    { description: "Undo/Redo behavior check when typed text does not match the case: undo the word, but typed text shouldn't be canceled",
+      completeDefaultIndex: false,
+      execute(aWindow, aTarget) {
+        synthesizeKey("z", { accelKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "mo", searchString: "mo"
+    },
+    { description: "Undo/Redo behavior check when typed text does not match the case: undo the typed text",
+      completeDefaultIndex: false,
+      execute(aWindow, aTarget) {
+        synthesizeKey("z", { accelKey: true }, aWindow);
+        return true;
+      }, popup: false, value: "", searchString: ""
+    },
+    { description: "Undo/Redo behavior check when typed text does not match the case: redo the typed text",
+      completeDefaultIndex: false,
+      execute(aWindow, aTarget) {
+        synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "mo", searchString: "mo"
+    },
+    { description: "Undo/Redo behavior check when typed text does not match the case: redo the word",
+      completeDefaultIndex: false,
+      execute(aWindow, aTarget) {
+        synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "Mozilla", searchString: "Mozilla"
+    },
+    { description: "Undo/Redo behavior check when typed text does not match the case: removing all text for next test...",
+      completeDefaultIndex: false,
+      execute(aWindow, aTarget) {
+        synthesizeKey("a", { accelKey: true }, aWindow);
         synthesizeKey("KEY_Backspace", {}, aWindow);
-        synthesizeKey("KEY_Backspace", {}, aWindow);
+        return true;
       }, popup: false, value: "", searchString: ""
     },
-    // composition which is canceled shouldn't cause opening the popup.
-    { description: "compositionstart shouldn't open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "M",
-              "clauses":
-              [
-                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 1, "length": 0 },
-            "key": { key: "M" },
-          }, aWindow);
-      }, popup: false, value: "M", searchString: ""
+
+    // Testing for nsIAutoCompleteInput.completeDefaultIndex being true.
+    { description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): type 'Mo'",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("M", { shiftKey: true }, aWindow);
+        synthesizeKey("o", {}, aWindow);
+        return true;
+      }, popup: true, value: "Mozilla", searchString: "Mo"
     },
-    { description: "modifying composition string shouldn't open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "Mo",
-              "clauses":
-              [
-                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 2, "length": 0 },
-            "key": { key: "o" },
-          }, aWindow);
-      }, popup: false, value: "Mo", searchString: ""
+    { description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): select 'Mozilla' to complete the word",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("KEY_ArrowDown", {}, aWindow);
+        synthesizeKey("KEY_Enter", {}, aWindow);
+        return true;
+      }, popup: false, value: "Mozilla", searchString: "Mozilla"
     },
-    { description: "modifying composition string to empty string shouldn't open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "",
-              "clauses":
-              [
-                { "length": 0, "attr": 0 }
-              ]
-            },
-            "caret": { "start": 0, "length": 0 },
-            "key": { key: "KEY_Backspace" },
-          }, aWindow);
-      }, popup: false, value: "", searchString: ""
+    { description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): undo the word, but typed text shouldn't be canceled",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("z", { accelKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "Mo", searchString: "Mo"
     },
-    { description: "canceled compositionend shouldn't open the popup if it was closed",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } }, aWindow);
+    { description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): undo the typed text",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("z", { accelKey: true }, aWindow);
+        return true;
       }, popup: false, value: "", searchString: ""
     },
-    // Down key should open the popup even if the editor is empty.
-    { description: "DOWN key should open the popup even if the value is empty",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeKey("KEY_ArrowDown", {}, aWindow);
-      }, popup: true, value: "", searchString: ""
-    },
-    // If popup is open at starting composition, the popup should be reopened
-    // after composition anyway.
-    { description: "compositionstart shouldn't open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "M",
-              "clauses":
-              [
-                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 1, "length": 0 },
-            "key": { key: "M" },
-          }, aWindow);
-      }, popup: false, value: "M", searchString: ""
+    { description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): redo the typed text",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "Mozilla", searchString: "Mo"
     },
-    { description: "modifying composition string shouldn't open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "Mo",
-              "clauses":
-              [
-                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 2, "length": 0 },
-            "key": { key: "o" },
-          }, aWindow);
-      }, popup: false, value: "Mo", searchString: ""
+    { description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): redo the word",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "Mozilla", searchString: "Mo"
     },
-    { description: "modifying composition string to empty string shouldn't open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "",
-              "clauses":
-              [
-                { "length": 0, "attr": 0 }
-              ]
-            },
-            "caret": { "start": 0, "length": 0 },
-            "key": { key: "KEY_Backspace" },
-          }, aWindow);
+    { description: "Undo/Redo behavior check when typed text exactly matches the case (completeDefaultIndex is true): removing all text for next test...",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("a", { accelKey: true }, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
+        return true;
       }, popup: false, value: "", searchString: ""
     },
-    { description: "canceled compositionend should open the popup if it was opened",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } }, aWindow);
-      }, popup: true, value: "", searchString: ""
-    },
-    // Type normally, and hit escape, the popup should be closed.
-    { description: "ESCAPE should close the popup after typing something",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeKey("M", {}, aWindow);
+
+    { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): type 'mo'",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("m", {}, aWindow);
         synthesizeKey("o", {}, aWindow);
-        synthesizeKey("KEY_Escape", {}, aWindow);
-      }, popup: false, value: "Mo", searchString: "Mo"
+        return true;
+      }, popup: true, value: "mozilla", searchString: "mo"
     },
-    // Even if the popup is closed, composition which is canceled should open
-    // the popup if the value isn't empty.
-    // XXX This might not be good behavior, but anyway, this is minor issue...
-    { description: "compositionstart shouldn't open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "z",
-              "clauses":
-              [
-                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 1, "length": 0 },
-            "key": { key: "z" },
-          }, aWindow);
-      }, popup: false, value: "Moz", searchString: "Mo"
+    { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): select 'Mozilla' to complete the word",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("KEY_ArrowDown", {}, aWindow);
+        synthesizeKey("KEY_Enter", {}, aWindow);
+        return true;
+      }, popup: false, value: "Mozilla", searchString: "Mozilla"
     },
-    { description: "modifying composition string shouldn't open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "zi",
-              "clauses":
-              [
-                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 2, "length": 0 },
-            "key": { key: "i", },
-          }, aWindow);
-      }, popup: false, value: "Mozi", searchString: "Mo"
+    // Different from "exactly matches the case" case, modifying the case causes one additional transaction.
+    // Although we could make this transaction ignored.
+    { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the selected word, but typed text shouldn't be canceled",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("z", { accelKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "mozilla", searchString: "mozilla"
     },
-    { description: "modifying composition string to empty string shouldn't open the popup",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "",
-              "clauses":
-              [
-                { "length": 0, "attr": 0 }
-              ]
-            },
-            "caret": { "start": 0, "length": 0 },
-            "key": { key: "KEY_Backspace" },
-          }, aWindow);
-      }, popup: false, value: "Mo", searchString: "Mo"
+    { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the word, but typed text shouldn't be canceled",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("z", { accelKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "mo", searchString: "mo"
     },
-    { description: "canceled compositionend shouldn't open the popup if the popup was closed",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } }, aWindow);
-      }, popup: true, value: "Mo", searchString: "Mo"
-    },
-    // House keeping...
-    { description: "house keeping for next tests",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeKey("KEY_Backspace", {}, aWindow);
-        synthesizeKey("KEY_Backspace", {}, aWindow);
+    { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): undo the typed text",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("z", { accelKey: true }, aWindow);
+        return true;
       }, popup: false, value: "", searchString: ""
     },
-    // Testing for nsIAutoCompleteInput.completeDefaultIndex being true.
-    { description: "compositionstart shouldn't open the popup (completeDefaultIndex is true)",
+    // XXX This is odd case.  Consistency with undo behavior, this should restore "mo".
+    //     However, looks like that autocomplete automatically restores "mozilla".
+    //     Additionally, looks like that it causes clearing the redo stack.
+    //     Therefore, the following redo operations do nothing.
+    { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the typed text",
       completeDefaultIndex: true,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "M",
-              "clauses":
-              [
-                { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 1, "length": 0 },
-            "key": { key: "M" },
-          }, aWindow);
-      }, popup: false, value: "M", searchString: ""
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "mozilla", searchString: "mo"
     },
-    { description: "modifying composition string shouldn't open the popup (completeDefaultIndex is true)",
+    { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the default index word",
       completeDefaultIndex: true,
-      execute(aWindow) {
-        synthesizeCompositionChange(
-          { "composition":
-            { "string": "Mo",
-              "clauses":
-              [
-                { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
-              ]
-            },
-            "caret": { "start": 2, "length": 0 },
-            "key": { key: "o" },
-          }, aWindow);
-      }, popup: false, value: "Mo", searchString: ""
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "mozilla", searchString: "mo"
     },
-    { description: "compositionend should open the popup (completeDefaultIndex is true)",
+    { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): redo the word",
       completeDefaultIndex: true,
-      execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } }, aWindow);
-      }, popup: true, value: "Mozilla", searchString: "Mo"
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("Z", { accelKey: true, shiftKey: true }, aWindow);
+        return true;
+      }, popup: true, value: "mozilla", searchString: "mo"
     },
-    // House keeping...
-    { description: "house keeping for next tests",
-      completeDefaultIndex: false,
-      execute(aWindow) {
-        synthesizeKey("KEY_Backspace", {}, aWindow);
+    { description: "Undo/Redo behavior check when typed text does not match the case (completeDefaultIndex is true): removing all text for next test...",
+      completeDefaultIndex: true,
+      execute(aWindow, aTarget) {
+        // Undo/Redo behavior on XUL <textbox> with completeDefaultIndex is set to true is unstable. Skip it now.
+        if (aTarget.tagName === "textbox") {
+          return false;
+        }
+        synthesizeKey("a", { accelKey: true }, aWindow);
         synthesizeKey("KEY_Backspace", {}, aWindow);
-        synthesizeKey("KEY_Backspace", {}, aWindow);
-        synthesizeKey("KEY_Backspace", {}, aWindow);
-        synthesizeKey("KEY_Backspace", {}, aWindow);
-        synthesizeKey("KEY_Backspace", {}, aWindow);
+        return true;
       }, popup: false, value: "", searchString: ""
-    }
+    },
   ]
 };
copy from toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html
copy to toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html
--- a/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html
+++ b/toolkit/content/tests/chrome/test_editor_for_input_with_autocomplete.html
@@ -1,62 +1,75 @@
 <!DOCTYPE HTML>
 <html>
 <head>
-  <title>autocomplete with composition tests on HTML input element</title>
+  <title>Basic editor behavior for HTML input element with autocomplete</title>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="file_autocomplete_with_composition.js"></script>
+  <script type="text/javascript" src="file_editor_with_autocomplete.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 
 <div id="content">
   <iframe id="formTarget" name="formTarget"></iframe>
   <form action="data:text/html," target="formTarget">
     <input name="test" id="input"><input type="submit">
   </form>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
-function runTests() {
+async function registerWord(aTarget, aAutoCompleteController) {
+  // Register a word to the form history.
+  aTarget.focus();
+  aTarget.value = "Mozilla";
+  synthesizeKey("KEY_Enter");
+  await waitForCondition(() => {
+    if (aAutoCompleteController.searchStatus == aAutoCompleteController.STATUS_NONE ||
+        aAutoCompleteController.searchStatus == aAutoCompleteController.STATUS_COMPLETE_NO_MATCH) {
+      aAutoCompleteController.startSearch("Mozilla");
+    }
+    return aAutoCompleteController.matchCount > 0;
+  });
+  aTarget.value = "";
+  synthesizeKey("KEY_Escape");
+}
+
+async function runTests() {
   var formFillController =
     SpecialPowers.getFormFillController()
                  .QueryInterface(Ci.nsIAutoCompleteInput);
   var originalFormFillTimeout = formFillController.timeout;
 
   SpecialPowers.attachFormFillControllerTo(window);
   var target = document.getElementById("input");
 
   // Register a word to the form history.
-  target.focus();
-  target.value = "Mozilla";
-  synthesizeKey("KEY_Enter");
-  target.value = "";
+  await registerWord(target, formFillController.controller);
 
-  new nsDoTestsForAutoCompleteWithComposition(
+  let tests1 = new nsDoTestsForEditorWithAutoComplete(
     "Testing on HTML input (asynchronously search)",
     window, target, formFillController.controller, is,
-    function() { return target.value; },
-    function() {
-      target.setAttribute("timeout", 0);
-      new nsDoTestsForAutoCompleteWithComposition(
+    function() { return target.value; });
+  await tests1.run();
+
+  target.setAttribute("timeout", 0);
+  let tests2 = new nsDoTestsForEditorWithAutoComplete(
         "Testing on HTML input (synchronously search)",
         window, target, formFillController.controller, is,
-        function() { return target.value; },
-        function() {
-          formFillController.timeout = originalFormFillTimeout;
-          SpecialPowers.detachFormFillControllerFrom(window);
-          SimpleTest.finish();
-        });
-    });
+        function() { return target.value; });
+  await tests2.run();
+
+  formFillController.timeout = originalFormFillTimeout;
+  SpecialPowers.detachFormFillControllerFrom(window);
+  SimpleTest.finish();
 }
 
 SimpleTest.waitForFocus(runTests);
 
 </script>
 </pre>
 </body>
 </html>
copy from toolkit/content/tests/chrome/test_autocomplete_with_composition_on_textbox.xul
copy to toolkit/content/tests/chrome/test_editor_for_textbox_with_autocomplete.xul
--- a/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_textbox.xul
+++ b/toolkit/content/tests/chrome/test_editor_for_textbox_with_autocomplete.xul
@@ -1,21 +1,21 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
                  type="text/css"?>
-<window title="Testing autocomplete with composition"
+<window title="Basic editor behavior for XUL textbox element with autocomplete"
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" />
   <script type="text/javascript"
-          src="file_autocomplete_with_composition.js" />
+          src="file_editor_with_autocomplete.js" />
 
   <textbox id="textbox" type="autocomplete"
            autocompletesearch="simpleForComposition"/>
 
 <body  xmlns="http://www.w3.org/1999/xhtml">
 <div id="content" style="display: none">
 </div>
 <pre id="test">
@@ -28,17 +28,18 @@
 SimpleTest.waitForExplicitFinish();
 
 const nsIAutoCompleteResult = Ci.nsIAutoCompleteResult;
 
 // This result can't be constructed in-line, because otherwise we leak memory.
 function nsAutoCompleteSimpleResult(aString)
 {
   this.searchString = aString;
-  if (aString == "" || aString == "Mozilla".substr(0, aString.length)) {
+  if (aString == "" ||
+      aString.toLowerCase() == "mozilla".substr(0, aString.length)) {
     this.searchResult = nsIAutoCompleteResult.RESULT_SUCCESS;
     this.matchCount = 1;
     this._value = "Mozilla";
   } else {
     this.searchResult = nsIAutoCompleteResult.RESULT_NOMATCH;
     this.matchCount = 0;
     this._value = "";
   }
@@ -89,36 +90,37 @@ var autoCompleteSimple = {
 
 var componentManager =
   Components.manager
             .QueryInterface(Ci.nsIComponentRegistrar);
 componentManager.registerFactory(autoCompleteSimpleID,
                                  "Test Simple Autocomplete for composition",
                                  autoCompleteSimpleName, autoCompleteSimple);
 
-function runTests()
+async function runTests()
 {
   var target = document.getElementById("textbox");
+
   target.setAttribute("timeout", 1);
-  var test1 = new nsDoTestsForAutoCompleteWithComposition(
+  let tests1 = new nsDoTestsForEditorWithAutoComplete(
     "Testing on XUL textbox (asynchronously search)",
     window, target, target.controller, is,
-    function () { return target.value; },
-    function () {
-      target.setAttribute("timeout", 0);
-      var test2 = new nsDoTestsForAutoCompleteWithComposition(
+    function() { return target.value; });
+  await tests1.run();
+
+  target.setAttribute("timeout", 0);
+  let tests2 = new nsDoTestsForEditorWithAutoComplete(
         "Testing on XUL textbox (synchronously search)",
         window, target, target.controller, is,
-        function () { return target.value; },
-        function () {
-          // Unregister the factory so that we don't get in the way of other
-          // tests
-          componentManager.unregisterFactory(autoCompleteSimpleID,
-                                             autoCompleteSimple);
-          SimpleTest.finish();
-        });
-    });
+        function() { return target.value; });
+  await tests2.run();
+
+  // Unregister the factory so that we don't get in the way of other
+  // tests
+  componentManager.unregisterFactory(autoCompleteSimpleID,
+                                     autoCompleteSimple);
+  SimpleTest.finish();
 }
 
 SimpleTest.waitForFocus(runTests);
 ]]>
 </script>
 </window>