Bug 1446253 - Make EventUtils.synthesizeComposition() dispatch keydown and keyup event in default r?smaug draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 16 Mar 2018 22:35:07 +0900
changeset 768894 834f5bb9ddef30f60e64c7007856873cb3198091
parent 768893 bb24b2dbb81d6fd588cea72cdef9fa74cec5681e
push id103001
push usermasayuki@d-toybox.com
push dateSat, 17 Mar 2018 01:19:37 +0000
reviewerssmaug
bugs1446253
milestone61.0a1
Bug 1446253 - Make EventUtils.synthesizeComposition() dispatch keydown and keyup event in default r?smaug We'll start to dispatch keydown event and keyup event even during composition. So, for testing those events won't break our UI, we should make EventUtils.synhtesizeComposition() and EventUtils.synthesizeCompositionChange() dispatch keydown event and keyup event even if callers don't specify keyboard event explicitly. Typically, "keydown" event is marked as "processed by IME", i.e., keyCode value is set to DOM_VK_PROCESSKEY and key is set to "Process", with our widget which handles native IME and key input. On the other hand, "keyup" is NOT marked as so. Therefore, this patch makes TextInputProcessor emulates this behavior without any new special flags. And for making possible to emulate special cases, this patch adds two flags to nsITextInputProcessor. One is KEY_DONT_MARK_KEYDOWN_AS_PROCESSED. The other is KEY_MARK_KEYUP_AS_PROCESSED. Unfortunately, those flags have opposite meaning but this must be better than making necessary to one flag for emulating usual keydown/keyup events. Finally, this makes some tests specify better keyboard information to synthesizeComposition() and synthesizeCompositionChange() to emulate actual keyboard events during composition. MozReview-Commit-ID: ItYaXILkNQE
browser/base/content/test/about/browser_aboutHome_search_composing.js
dom/base/TextInputProcessor.cpp
dom/interfaces/base/nsITextInputProcessor.idl
dom/plugins/test/mochitest/test_windowless_ime.html
editor/libeditor/tests/test_bug1109465.html
editor/libeditor/tests/test_bug1230473.html
testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html
testing/mochitest/tests/SimpleTest/EventUtils.js
toolkit/content/tests/chrome/file_autocomplete_with_composition.js
widget/WidgetEventImpl.cpp
widget/tests/test_assign_event_data.html
widget/tests/window_composition_text_querycontent.xul
--- a/browser/base/content/test/about/browser_aboutHome_search_composing.js
+++ b/browser/base/content/test/about/browser_aboutHome_search_composing.js
@@ -16,20 +16,17 @@ add_task(async function() {
     await p;
 
     await ContentTask.spawn(browser, null, async function() {
       // Start composition and type "x"
       let input = content.document.querySelector(["#searchText", "#newtab-search-text"]);
       input.focus();
     });
 
-    await BrowserTestUtils.synthesizeComposition({
-      type: "compositionstart",
-      data: ""
-    }, browser);
+    // FYI: "compositionstart" will be dispatched automatically.
     await BrowserTestUtils.synthesizeCompositionChange({
       composition: {
         string: "x",
         clauses: [
           { length: 1, attr: Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE }
         ]
       },
       caret: { start: 1, length: 0 }
--- a/dom/base/TextInputProcessor.cpp
+++ b/dom/base/TextInputProcessor.cpp
@@ -476,18 +476,19 @@ TextInputProcessor::IsValidStateForCompo
 
   return NS_OK;
 }
 
 bool
 TextInputProcessor::IsValidEventTypeForComposition(
                       const WidgetKeyboardEvent& aKeyboardEvent) const
 {
-  // The key event type of composition methods must be "" or "keydown".
-  if (aKeyboardEvent.mMessage == eKeyDown) {
+  // The key event type of composition methods must be "", "keydown" or "keyup".
+  if (aKeyboardEvent.mMessage == eKeyDown ||
+      aKeyboardEvent.mMessage == eKeyUp) {
     return true;
   }
   if (aKeyboardEvent.mMessage == eUnidentifiedEvent &&
       aKeyboardEvent.mSpecifiedEventType &&
       nsDependentAtomString(
         aKeyboardEvent.mSpecifiedEventType).EqualsLiteral("on")) {
     return true;
   }
@@ -506,16 +507,22 @@ TextInputProcessor::MaybeDispatchKeydown
     result.mCanContinue = false;
     return result;
   }
 
   if (!aKeyboardEvent) {
     return result;
   }
 
+  // If the mMessage is eKeyUp, the caller doesn't want TIP to dispatch
+  // eKeyDown event.
+  if (aKeyboardEvent->mMessage == eKeyUp) {
+    return result;
+  }
+
   // Modifier keys are not allowed because managing modifier state in this
   // method makes this messy.
   if (NS_WARN_IF(aKeyboardEvent->IsModifierKeyEvent())) {
     result.mResult = NS_ERROR_INVALID_ARG;
     result.mCanContinue = false;
     return result;
   }
 
@@ -540,17 +547,17 @@ TextInputProcessor::MaybeDispatchKeyupFo
 {
   EventDispatcherResult result;
 
   if (!aKeyboardEvent) {
     return result;
   }
 
   // If the mMessage is eKeyDown, the caller doesn't want TIP to dispatch
-  // keyup event.
+  // eKeyUp event.
   if (aKeyboardEvent->mMessage == eKeyDown) {
     return result;
   }
 
   // If the widget has been destroyed, we can do nothing here.
   result.mResult = IsValidStateForComposition();
   if (NS_FAILED(result.mResult)) {
     result.mCanContinue = false;
@@ -1121,16 +1128,22 @@ TextInputProcessor::KeydownInternal(cons
     if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) {
       return NS_OK;
     }
   } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) {
     return NS_ERROR_INVALID_ARG;
   }
   keyEvent.mModifiers = GetActiveModifiers();
 
+  if (!aAllowToDispatchKeypress &&
+      !(aKeyFlags & KEY_DONT_MARK_KEYDOWN_AS_PROCESSED)) {
+    keyEvent.mKeyCode = NS_VK_PROCESSKEY;
+    keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+  }
+
   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
   rv = IsValidStateForComposition();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsEventStatus status = aConsumedFlags ? nsEventStatus_eConsumeNoDefault :
                                           nsEventStatus_eIgnore;
@@ -1201,16 +1214,21 @@ TextInputProcessor::KeyupInternal(const 
     if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) {
       return NS_OK;
     }
   } else if (NS_WARN_IF(aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT)) {
     return NS_ERROR_INVALID_ARG;
   }
   keyEvent.mModifiers = GetActiveModifiers();
 
+  if (aKeyFlags & KEY_MARK_KEYUP_AS_PROCESSED) {
+    keyEvent.mKeyCode = NS_VK_PROCESSKEY;
+    keyEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+  }
+
   RefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
   rv = IsValidStateForComposition();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsEventStatus status = aDoDefault ? nsEventStatus_eIgnore :
                                       nsEventStatus_eConsumeNoDefault;
--- a/dom/interfaces/base/nsITextInputProcessor.idl
+++ b/dom/interfaces/base/nsITextInputProcessor.idl
@@ -300,16 +300,20 @@ interface nsITextInputProcessor : nsISup
    * compositionstart event hasn't been dispatched yet.  If this is called
    * when compositionstart has already been dispatched, this throws an
    * exception.
    *
    * @param aKeyboardEvent  Key event which causes starting composition.
    *                        If its type value is "keydown", this method
    *                        dispatches only keydown event first.  Otherwise,
    *                        dispatches keydown first and keyup at last.
+   *                        key value and keyCode values of keydown event
+   *                        are set to "Process" and DOM_VK_PROCESSKEY
+   *                        automatically.  You can prevent this behavior
+   *                        with KEY_DONT_MARK_KEYDOWN_AS_PROCESSED.
    * @param aKeyFlags       See KEY_* constants.
    * @return                Returns true if composition starts normally.
    *                        Otherwise, returns false because it might be
    *                        canceled by the web application.
    */
   [optional_argc]
     boolean startComposition([optional] in nsIDOMEvent aKeyboardEvent,
                              [optional] in unsigned long aKeyFlags);
@@ -388,16 +392,20 @@ interface nsITextInputProcessor : nsISup
    * Note that if sum of lengths of appended clauses are not same as composition
    * string or caret offset is larger than the composition string length, this
    * throws an exception.
    *
    * @param aKeyboardEvent  Key event which causes the composition string.
    *                        If its type value is "keydown", this method
    *                        dispatches only keydown event first.  Otherwise,
    *                        dispatches keydown first and keyup at last.
+   *                        key value and keyCode values of keydown event
+   *                        are set to "Process" and DOM_VK_PROCESSKEY
+   *                        automatically.  You can prevent this behavior
+   *                        with KEY_DONT_MARK_KEYDOWN_AS_PROCESSED.
    * @param aKeyFlags       See KEY_* constants.
    * @return                Returns true if there is a composition already or
    *                        starting composition automatically.
    *                        Otherwise, i.e., if it cannot start composition
    *                        automatically, e.g., canceled by web apps, returns
    *                        false.
    */
   [optional_argc]
@@ -408,32 +416,40 @@ interface nsITextInputProcessor : nsISup
   /**
    * commitComposition() will commit composition with the last composition
    * string.  If there is no composition, this will throw an exception.
    *
    * @param aKeyboardEvent  Key event which causes the commit composition.
    *                        If its type value is "keydown", this method
    *                        dispatches only keydown event first.  Otherwise,
    *                        dispatches keydown first and keyup at last.
+   *                        key value and keyCode values of keydown event
+   *                        are set to "Process" and DOM_VK_PROCESSKEY
+   *                        automatically.  You can prevent this behavior
+   *                        with KEY_DONT_MARK_KEYDOWN_AS_PROCESSED.
    * @param aKeyFlags       See KEY_* constants.
    */
   [optional_argc]
     void commitComposition([optional] in nsIDOMEvent aKeyboardEvent,
                            [optional] in unsigned long aKeyFlags);
 
   /**
    * commitCompositionWith() will commit composition with the specific string.
    * If there is no composition, this will start composition and commit it
    * with the specified string.
    *
    * @param aCommitString   The string to be committed.
    * @param aKeyboardEvent  Key event which causes the commit composition.
    *                        If its type value is "keydown", this method
    *                        dispatches only keydown event first.  Otherwise,
    *                        dispatches keydown first and keyup at last.
+   *                        key value and keyCode values of keydown event
+   *                        are set to "Process" and DOM_VK_PROCESSKEY
+   *                        automatically.  You can prevent this behavior
+   *                        with KEY_DONT_MARK_KEYDOWN_AS_PROCESSED.
    * @param aKeyFlags       See KEY_* constants.
    * @return                Returns true if there is a composition already or
    *                        starting composition automatically.
    *                        Otherwise, i.e., if it cannot start composition
    *                        automatically, e.g., canceled by web apps, returns
    *                        false.
    */
   [optional_argc]
@@ -449,16 +465,20 @@ interface nsITextInputProcessor : nsISup
    *
    * Note that if you tries to cancel composition when there is no composition,
    * this throws an exception.
    *
    * @param aKeyboardEvent  Key event which causes the canceling composition.
    *                        If its type value is "keydown", this method
    *                        dispatches only keydown event first.  Otherwise,
    *                        dispatches keydown first and keyup at last.
+   *                        key value and keyCode values of keydown event
+   *                        are set to "Process" and DOM_VK_PROCESSKEY
+   *                        automatically.  You can prevent this behavior
+   *                        with KEY_DONT_MARK_KEYDOWN_AS_PROCESSED.
    * @param aKeyFlags       See KEY_* constants.
    */
   [optional_argc]
     void cancelComposition([optional] in nsIDOMEvent aKeyboardEvent,
                            [optional] in unsigned long aKeyFlags);
 
   // Specifying KEY_DEFAULT_PREVENTED can dispatch key events whose
   // defaultPrevented are true.  Note that if this is specified, keypress event
@@ -483,16 +503,22 @@ interface nsITextInputProcessor : nsISup
   // represents non-printable key.  Note that if .keyCode is initialized with
   // non-zero value, this flag causes throwing an exception.
   const unsigned long KEY_KEEP_KEYCODE_ZERO                        = 0x00000010;
   // If KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT is specified when the key event is
   // a modifier key's, keydown() and keyup() only modifies its modifier state
   // without dispatching key events.  This is useful for testing odd behavior
   // or emulating legacy API behavior.
   const unsigned long KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT         = 0x00000020;
+  // If KEY_DONT_MARK_KEYDOWN_AS_PROCESSED is specified, key value and keyCode
+  // value of keydown event are not changed to "Process" and DOM_VK_PROCESSKEY.
+  const unsigned long KEY_DONT_MARK_KEYDOWN_AS_PROCESSED           = 0x00000040;
+  // If KEY_MARK_KEYUP_AS_PROCESSED is specified, key value and keyCode value
+  // of keyup event are changed to "Process" and DOM_VK_PROCESSKEY.
+  const unsigned long KEY_MARK_KEYUP_AS_PROCESSED                  = 0x00000080;
 
   // These values can be used to do bitwise operation with the return value of
   // the keydown() method.
   const unsigned long KEYEVENT_NOT_CONSUMED                        = 0x00000000;
   const unsigned long KEYDOWN_IS_CONSUMED                          = 0x00000001;
   const unsigned long KEYPRESS_IS_CONSUMED                         = 0x00000002;
 
   /**
--- a/dom/plugins/test/mochitest/test_windowless_ime.html
+++ b/dom/plugins/test/mochitest/test_windowless_ime.html
@@ -10,17 +10,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="plugin-utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script class="testbody" type="text/javascript">
 function runTests() {
   var plugin = document.getElementById("plugin1");
 
   plugin.focus();
-  synthesizeComposition({ type: "compositionstart", data: "" });
+  // FYI: "compositionstart" will be dispatched automatically.
   let data = "composition";
   synthesizeCompositionChange({
     composition: {
       string: data,
       clauses: [
         { length: data.length, attr: COMPOSITION_ATTR_RAW_CLAUSE }
       ]
     },
--- a/editor/libeditor/tests/test_bug1109465.html
+++ b/editor/libeditor/tests/test_bug1109465.html
@@ -31,18 +31,18 @@ SimpleTest.waitForFocus(function() {
   sendString("foo");
   synthesizeKey("KEY_Enter");
   sendString("bar");
   synthesizeKey("KEY_ArrowUp");
   is(t.selectionStart, 3, "Correct start of selection");
   is(t.selectionEnd, 3, "Correct end of selection");
 
   // Compose an IME string
-  synthesizeComposition({ type: "compositionstart" });
   var composingString = "\u306B";
+  // FYI: "compositionstart" will be dispatched automatically.
   synthesizeCompositionChange(
     { "composition":
       { "string": composingString,
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
--- a/editor/libeditor/tests/test_bug1230473.html
+++ b/editor/libeditor/tests/test_bug1230473.html
@@ -51,67 +51,67 @@ SimpleTest.waitForFocus(()=>{
     }
 
     clear();
 
     // Committing at compositionstart
     aEditor.focus();
     aEditor.addEventListener("compositionstart", committer, true);
     synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 1, length: 0 }});
+                                  caret: { start: 1, length: 0 }, key: { key: "a" }});
     aEditor.removeEventListener("compositionstart", committer, true);
     ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionstart event handler");
     is(value(), "", "composition in " + aEditor.id + " shouldn't insert any text since it's committed at compositionstart");
     clear();
 
     // Committing at first compositionupdate
     aEditor.focus();
     aEditor.addEventListener("compositionupdate", committer, true);
     synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 1, length: 0 }});
+                                  caret: { start: 1, length: 0 }, key: { key: "a" }});
     aEditor.removeEventListener("compositionupdate", committer, true);
     ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
     is(value(), "", "composition in " + aEditor.id + " shouldn't have inserted any text since it's committed at first compositionupdate");
     clear();
 
     // Committing at first text (eCompositionChange)
     aEditor.focus();
     aEditor.addEventListener("text", committer, true);
     synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 1, length: 0 }});
+                                  caret: { start: 1, length: 0 }, key: { key: "a" }});
     aEditor.removeEventListener("text", committer, true);
     ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
     is(value(), "", "composition in " + aEditor.id + " should have inserted any text since it's committed at first text");
     clear();
 
     // Committing at second compositionupdate
     aEditor.focus();
-    synthesizeComposition({ type: "compositionstart" });
+    // FYI: "compositionstart" will be dispatched automatically.
     synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 1, length: 0 }});
+                                  caret: { start: 1, length: 0 }, key: { key: "a" }});
     ok(isComposing(), "composition should be in " + aEditor.id + " before dispatching second compositionupdate");
     is(value(), "a", "composition in " + aEditor.id + " should be 'a' before dispatching second compositionupdate");
     aEditor.addEventListener("compositionupdate", committer, true);
     synthesizeCompositionChange({ composition: { string: "ab", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 2, length: 0 }});
+                                  caret: { start: 2, length: 0 }, key: { key: "b" }});
     aEditor.removeEventListener("compositionupdate", committer, true);
     ok(!isComposing(), "composition in " + aEditor.id + " should be committed by compositionupdate event handler");
     todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second compositionupdate");
     clear();
 
     // Committing at second text (eCompositionChange)
     aEditor.focus();
-    synthesizeComposition({ type: "compositionstart" });
+    // FYI: "compositionstart" will be dispatched automatically.
     synthesizeCompositionChange({ composition: { string: "a", clauses: [{length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 1, length: 0 }});
+                                  caret: { start: 1, length: 0 }, key: { key: "a" }});
     ok(isComposing(), "composition should be in " + aEditor.id + " before dispatching second text");
     is(value(), "a", "composition in " + aEditor.id + " should be 'a' before dispatching second text");
     aEditor.addEventListener("text", committer, true);
     synthesizeCompositionChange({ composition: { string: "ab", clauses: [{length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }] },
-                                  caret: { start: 2, length: 0 }});
+                                  caret: { start: 2, length: 0 }, key: { key: "b" }});
     aEditor.removeEventListener("text", committer, true);
     ok(!isComposing(), "composition in " + aEditor.id + " should be committed by text event handler");
     todo_is(value(), "a", "composition in " + aEditor.id + " shouldn't have been modified since it's committed at second text");
     clear();
   }
   runTest(document.getElementById("input"));
   runTest(document.getElementById("textarea"));
   runTest(document.getElementById("div"));
--- a/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html
+++ b/testing/mochitest/tests/Harness_sanity/test_sanityEventUtils.html
@@ -27,32 +27,37 @@
 <p>blah blah blah blah</p>
 <p>blah blah blah blah</p>
 <p>blah blah blah blah</p>
 <p>blah blah blah blah</p>
 </div>
 <script class="testbody" type="text/javascript">
 const kStrictKeyPressEvents =
   SpecialPowers.getBoolPref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content");
+const kStrictKeyDownKeyUpEvents =
+  SpecialPowers.getBoolPref("dom.keyboardevent.dispatch_during_composition");
 
 info("\nProfile::EventUtilsLoadTime: " + (loadTime - start) + "\n");
 function starttest() {
   SimpleTest.waitForFocus(
     function () {
       SimpleTest.waitForExplicitFinish();
       var startTime = new Date();
       var check = false;
-      
+      function doCheck() {
+        check = true;
+      }
+
       /* test send* functions */
-      $("testMouseEvent").addEventListener("click", function() { check=true; });
+      $("testMouseEvent").addEventListener("click", doCheck, {once: true});
       sendMouseEvent({type:'click'}, "testMouseEvent");
       is(check, true, 'sendMouseEvent should dispatch click event');
       
       check = false;
-      $("testKeyEvent").addEventListener("keypress", function() { check = true; }, {once: true});
+      $("testKeyEvent").addEventListener("keypress", doCheck, {once: true});
       $("testKeyEvent").focus();
       sendChar("x");
       is($("testKeyEvent").value, "x", "sendChar should work");
       is(check, true, "sendChar should dispatch keyPress");
       $("testKeyEvent").value = "";
     
       $("testStrEvent").focus();
       sendString("string");
@@ -105,75 +110,131 @@ function starttest() {
        * that we can successfully call it to avoid having setTimeout vary the runtime metric.
        * Testing of this method is currently done here:
        * toolkit/content/tests/chrome/test_mousescroll.xul
        */
       synthesizeWheel($("scrollB"), 5, 5, {'deltaY': 10.0, deltaMode: WheelEvent.DOM_DELTA_LINE});
     
       /* test synthesizeKey* */
       check = false;
-      $("testKeyEvent").addEventListener("keypress", function() { check = true; });
+      $("testKeyEvent").addEventListener("keypress", doCheck, {once:true});
       $("testKeyEvent").focus();
       sendString("a");
       is($("testKeyEvent").value, "a", "synthesizeKey should work");
       is(check, true, "synthesizeKey should dispatch keyPress");
       $("testKeyEvent").value = "";
     
       check = false;
+      $("testKeyEvent").addEventListener("keypress", doCheck, {once:true});
       synthesizeKeyExpectEvent("a", {}, $("testKeyEvent"), "keypress");
       is($("testKeyEvent").value, "a", "synthesizeKey should work");
       is(check, true, "synthesizeKey should dispatch keyPress");
       $("testKeyEvent").value = "";
     
       /* test synthesizeComposition */
+      var description = "";
+      var keydownEvent = null;
+      var keyupEvent = null;
+      function onKeyDown(aEvent) {
+        ok(!keydownEvent, description + "keydown should be fired only once" + (keydownEvent ? keydownEvent.key : "") + ", " + (keyupEvent ? keyupEvent.key : ""));
+        keydownEvent = aEvent;
+      }
+      function onKeyUp(aEvent) {
+        ok(!keyupEvent, description + "keyup should be fired only once");
+        keyupEvent = aEvent;
+      }
+      function resetKeyDownAndKeyUp(aDescription) {
+        description = aDescription + ": ";
+        keydownEvent = null;
+        keyupEvent = null;
+        check = false;
+      }
+      function checkKeyDownAndKeyUp(aKeyDown, aKeyUp) {
+        if (aKeyDown && (!aKeyDown.inComposition || kStrictKeyDownKeyUpEvents)) {
+          is(keydownEvent.keyCode, aKeyDown.keyCode,
+             description + "keydown event should be dispatched (checking keyCode)");
+          is(keydownEvent.key, aKeyDown.key,
+             description + "keydown event should be dispatched (checking key)");
+        } else {
+          is(keydownEvent, null,
+             description + "keydown event shouldn't be fired");
+        }
+        if (aKeyUp && (!aKeyUp.inComposition || kStrictKeyDownKeyUpEvents)) {
+          is(keyupEvent.keyCode, aKeyUp.keyCode,
+             description + "keyup event should be dispatched (checking keyCode)");
+          is(keyupEvent.key, aKeyUp.key,
+             description + "keyup event should be dispatched (checking key)");
+        } else {
+          is(keyupEvent, null,
+             description + "keyup event shouldn't be fired");
+        }
+      }
+      $("textBoxB").addEventListener("keydown", onKeyDown);
+      $("textBoxB").addEventListener("keyup", onKeyUp);
+
       $("textBoxB").focus();
-      check = false;
-      window.addEventListener("compositionstart", function() { check = true; });
-      synthesizeComposition({ type: "compositionstart" });
-      is(check, true, 'synthesizeComposition() should dispatch compositionstart');
-    
-      check = false;
-      window.addEventListener("compositionupdate", function() { check = true; });
-      synthesizeComposition({ type: "compositionupdate", data: "a" });
-      is(check, false, 'synthesizeComposition() should not dispatch compositionupdate without error');
 
-      check = false;
-      window.addEventListener("text", function() { check = true; });
+      // If key event is not specified, fake keydown and keyup events which are
+      // marked as "processed by IME" should be fired.
+      resetKeyDownAndKeyUp("synthesizing eCompositionStart without specifying keyboard event");
+      window.addEventListener("compositionstart", doCheck, {once: true});
+      synthesizeComposition({type: "compositionstart"});
+      ok(check, description + "synthesizeComposition() should dispatch compositionstart");
+      checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"});
+
+      resetKeyDownAndKeyUp("trying to synthesize eCompositionUpdate directly without specifying keyboard event");
+      window.addEventListener("compositionupdate", doCheck, {once: true});
+      synthesizeComposition({type: "compositionupdate", data: "a"});
+      ok(!check, description + "synthesizeComposition() should not dispatch compositionupdate without error");
+      checkKeyDownAndKeyUp(null, null);
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange without specifying keyboard event");
+      window.addEventListener("text", doCheck, {once: true});
       synthesizeCompositionChange(
         { "composition":
           { "string": "a",
             "clauses":
             [
               { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
             ]
           },
           "caret": { "start": 1, "length": 0 }
         }
       );
-      is(check, true, "synthesizeCompositionChange should cause dispatching a DOM text event");
+      ok(check, description + "synthesizeCompositionChange should cause dispatching a DOM text event");
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"});
 
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange for removing clauses without specifying keyboard event");
       synthesizeCompositionChange(
         { "composition":
           { "string": "a",
             "clauses":
             [
               { "length": 0, "attr": 0 }
             ]
           },
           "caret": { "start": 1, "length": 0 }
         }
       );
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"});
 
-      check = false;
-      window.addEventListener("compositionend", function() { check = true; });
-      synthesizeComposition({ type: "compositionend", data: "a" });
-      is(check, false, 'synthesizeComposition() should not dispatch compositionend');
+      resetKeyDownAndKeyUp("trying to synthesize eCompositionEnd directly without specifying keyboard event");
+      window.addEventListener("compositionend", doCheck, {once: true});
+      synthesizeComposition({type: "compositionend", data: "a"});
+      ok(!check, description + "synthesizeComposition() should not dispatch compositionend");
+      checkKeyDownAndKeyUp(null, null);
 
-      synthesizeComposition({ type: "compositioncommit", data: "a" });
-      is(check, true, 'synthesizeComposition() should dispatch compositionend');
+      resetKeyDownAndKeyUp("synthesizing eCompositionCommit without specifying keyboard event");
+      synthesizeComposition({type: "compositioncommit", data: "a"});
+      ok(check, description + "synthesizeComposition() should dispatch compositionend");
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"});
 
       var querySelectedText = synthesizeQuerySelectedText();
       ok(querySelectedText, "query selected text event result is null");
       ok(querySelectedText.succeeded, "query selected text event failed");
       is(querySelectedText.offset, 1,
          "query selected text event returns wrong offset");
       is(querySelectedText.text, "",
          "query selected text event returns wrong selected text");
@@ -183,15 +244,211 @@ function starttest() {
       ok(querySelectedText, "query selected text event result is null");
       ok(querySelectedText.succeeded, "query selected text event failed");
       is(querySelectedText.offset, 0,
          "query selected text event returns wrong offset");
       is(querySelectedText.text, "",
          "query selected text event returns wrong selected text");
       var endTime = new Date();
       info("\nProfile::EventUtilsRunTime: " + (endTime-startTime) + "\n");
+
+      // In most cases, automated tests shouldn't try to synthesize
+      // compositionstart manually.  Let's check if synthesizeCompositionChange()
+      // dispatches compositionstart automatically.
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange without specifying keyboard event when there is no composition");
+      window.addEventListener("compositionstart", doCheck, {once: true});
+      synthesizeCompositionChange(
+        { "composition":
+          { "string": "a",
+            "clauses":
+            [
+              { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
+            ]
+          },
+          "caret": { "start": 1, "length": 0 }
+        }
+      );
+      ok(check, description + "synthesizeCompositionChange should dispatch \"compositionstart\" automatically if there is no composition");
+      checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"});
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionCommitAsIs without specifying keyboard event");
+      synthesizeComposition({type: "compositioncommitasis"});
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"});
+
+      // If key event is specified, keydown event which is marked as "processed
+      // by IME" should be fired and keyup event which is NOT marked as so
+      // should be fired too.
+      resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event");
+      synthesizeComposition({type: "compositionstart", key: {key: "a"}});
+      checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_A, key: "a"});
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event");
+      synthesizeCompositionChange(
+        {"composition":
+          {"string": "b", "clauses": [
+             {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE}
+           ]},
+          "caret": {"start": 1, "length": 0},
+          "key": {key: "b"},
+        }
+      );
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_B, key: "b"});
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event");
+      synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter"}});
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: false, keyCode: KeyboardEvent.DOM_VK_RETURN, key: "Enter"});
+
+      // keyup shouldn't be dispatched automatically if type is specified as keydown
+      resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event whose type is keydown");
+      synthesizeComposition({type: "compositionstart", key: {key: "a", type: "keydown"}});
+      checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           null);
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event whose type is keydown");
+      synthesizeCompositionChange(
+        {"composition":
+          {"string": "b", "clauses": [
+             {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE}
+           ]},
+          "caret": {"start": 1, "length": 0},
+          "key": {key: "b", type: "keydown"},
+        }
+      );
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           null);
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event whose type is keydown");
+      synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter", type: "keydown"}});
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           null);
+
+      // keydown shouldn't be dispatched automatically if type is specified as keyup
+      resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event whose type is keyup");
+      synthesizeComposition({type: "compositionstart", key: {key: "a", type: "keyup"}});
+      checkKeyDownAndKeyUp(null,
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_A, key: "a"});
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event whose type is keyup");
+      synthesizeCompositionChange(
+        {"composition":
+          {"string": "b", "clauses": [
+             {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE}
+           ]},
+          "caret": {"start": 1, "length": 0},
+          "key": {key: "b", type: "keyup"},
+        }
+      );
+      checkKeyDownAndKeyUp(null,
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_B, key: "b"});
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event whose type is keyup");
+      synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter", type: "keyup"}});
+      checkKeyDownAndKeyUp(null,
+                           {inComposition: false, keyCode: KeyboardEvent.DOM_VK_RETURN, key: "Enter"});
+
+      // keydown event shouldn't be marked as "processed by IME" if doNotMarkKeydownAsProcessed is true
+      resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event whose doNotMarkKeydownAsProcessed is true");
+      synthesizeComposition({type: "compositionstart", key: {key: "a", doNotMarkKeydownAsProcessed: true}});
+      checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_A, key: "a"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_A, key: "a"});
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event whose doNotMarkKeydownAsProcessed is true");
+      synthesizeCompositionChange(
+        {"composition":
+          {"string": "b", "clauses": [
+             {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE}
+           ]},
+          "caret": {"start": 1, "length": 0},
+          "key": {key: "b", doNotMarkKeydownAsProcessed: true},
+        }
+      );
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_B, key: "b"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_B, key: "b"});
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event whose doNotMarkKeydownAsProcessed is true");
+      synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter", doNotMarkKeydownAsProcessed: true}});
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_RETURN, key: "Enter"},
+                           {inComposition: false, keyCode: KeyboardEvent.DOM_VK_RETURN, key: "Enter"});
+
+      // keyup event should be marked as "processed by IME" if markKeyupAsProcessed is true
+      resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event whose markKeyupAsProcessed is true");
+      synthesizeComposition({type: "compositionstart", key: {key: "a", markKeyupAsProcessed: true}});
+      checkKeyDownAndKeyUp({inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"});
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event whose markKeyupAsProcessed is true");
+      synthesizeCompositionChange(
+        {"composition":
+          {"string": "b", "clauses": [
+             {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE}
+           ]},
+          "caret": {"start": 1, "length": 0},
+          "key": {key: "b", markKeyupAsProcessed: true},
+        }
+      );
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"});
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event whose markKeyupAsProcessed is true");
+      synthesizeComposition({type: "compositioncommit", data: "c", key: {key: "KEY_Enter", markKeyupAsProcessed: true}});
+      checkKeyDownAndKeyUp({inComposition: true, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"},
+                           {inComposition: false, keyCode: KeyboardEvent.DOM_VK_PROCESSKEY, key: "Process"});
+
+      // If key event is explicitly declared with null, keyboard events shouldn't
+      // be fired for emulating text inputs without keyboard such as voice input or something.
+      resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event as null");
+      synthesizeComposition({type: "compositionstart", key: null});
+      checkKeyDownAndKeyUp(null, null);
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event as null");
+      synthesizeCompositionChange(
+        {"composition":
+          {"string": "b", "clauses": [
+             {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE}
+           ]},
+          "caret": {"start": 1, "length": 0},
+          "key": null,
+        }
+      );
+      checkKeyDownAndKeyUp(null, null);
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event as null");
+      synthesizeComposition({type: "compositioncommit", data: "c", key: null});
+      checkKeyDownAndKeyUp(null, null);
+
+      // If key event is explicitly declared with empty object, keyboard events
+      // shouldn't be fired for emulating text inputs without keyboard such as
+      // voice input or something.
+      resetKeyDownAndKeyUp("synthesizing eCompositionStart with specifying keyboard event as empty");
+      synthesizeComposition({type: "compositionstart", key: {}});
+      checkKeyDownAndKeyUp(null, null);
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionChange with specifying keyboard event as empty");
+      synthesizeCompositionChange(
+        {"composition":
+          {"string": "b", "clauses": [
+             {"length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE}
+           ]},
+          "caret": {"start": 1, "length": 0},
+          "key": {},
+        }
+      );
+      checkKeyDownAndKeyUp(null, null);
+
+      resetKeyDownAndKeyUp("synthesizing eCompositionCommit with specifying keyboard event as empty");
+      synthesizeComposition({type: "compositioncommit", data: "c", key: {}});
+      checkKeyDownAndKeyUp(null, null);
+
+      $("textBoxB").removeEventListener("keydown", onKeyDown);
+      $("textBoxB").removeEventListener("keyup", onKeyUp);
+
       SimpleTest.finish();
     }
   );
 };
 </script>
 </body>
 </html>
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -1464,16 +1464,18 @@ function _guessKeyNameFromKeyCode(aKeyCo
     case KeyboardEvent.DOM_VK_VOLUME_DOWN:
       return "AudioVolumeDown";
     case KeyboardEvent.DOM_VK_VOLUME_UP:
       return "AudioVolumeUp";
     case KeyboardEvent.DOM_VK_META:
       return "Meta";
     case KeyboardEvent.DOM_VK_ALTGR:
       return "AltGraph";
+    case KeyboardEvent.DOM_VK_PROCESSKEY:
+      return "Process";
     case KeyboardEvent.DOM_VK_ATTN:
       return "Attn";
     case KeyboardEvent.DOM_VK_CRSEL:
       return "CrSel";
     case KeyboardEvent.DOM_VK_EXSEL:
       return "ExSel";
     case KeyboardEvent.DOM_VK_EREOF:
       return "EraseEof";
@@ -1776,16 +1778,23 @@ function _createKeyboardEventDictionary(
   }
   var code = "code" in aKeyEvent ?
     aKeyEvent.code :
     _guessCodeFromKeyName(keyName, aKeyEvent.location, aWindow);
   var locationIsDefined = "location" in aKeyEvent;
   if (locationIsDefined && aKeyEvent.location === 0) {
     result.flags |= _EU_Ci.nsITextInputProcessor.KEY_KEEP_KEY_LOCATION_STANDARD;
   }
+  if (aKeyEvent.doNotMarkKeydownAsProcessed) {
+    result.flags |=
+      _EU_Ci.nsITextInputProcessor.KEY_DONT_MARK_KEYDOWN_AS_PROCESSED;
+  }
+  if (aKeyEvent.markKeyupAsProcessed) {
+    result.flags |= _EU_Ci.nsITextInputProcessor.KEY_MARK_KEYUP_AS_PROCESSED;
+  }
   result.dictionary = {
     key: keyName,
     code: code,
     location: locationIsDefined ? aKeyEvent.location : 0,
     repeat: "repeat" in aKeyEvent ? aKeyEvent.repeat === true : false,
     keyCode: keyCode,
   };
   return result;
@@ -1875,54 +1884,84 @@ function _emulateToInactivateModifiers(a
     aTIP.keydown(event,
       aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
     aTIP.keyup(event,
       aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
   }
 }
 
 /**
- * Synthesize a composition event.
+ * Synthesize a composition event and keydown event and keyup events unless
+ * you prevent to dispatch them explicitly (see aEvent.key's explanation).
+ *
+ * Note that you shouldn't call this with "compositionstart" unless you need to
+ * test compositionstart event which is NOT followed by compositionupdate
+ * event immediately.  Typically, native IME starts composition with
+ * a pair of keydown and keyup event and dispatch compositionstart and
+ * compositionupdate (and non-standard text event) between them.  So, in most
+ * cases, you should call synthesizeCompositionChange() directly.
+ * If you call this with compositionstart, keyup event will be fired
+ * immediately after compositionstart.  In other words, you should use
+ * "compositionstart" only when you need to emulate IME which just starts
+ * composition with compositionstart event but does not send composing text to
+ * us until committing the composition.  This is behavior of some Chinese IMEs.
  *
  * @param aEvent               The composition event information.  This must
  *                             have |type| member.  The value must be
  *                             "compositionstart", "compositionend",
  *                             "compositioncommitasis" or "compositioncommit".
+ *
  *                             And also this may have |data| and |locale| which
  *                             would be used for the value of each property of
  *                             the composition event.  Note that the |data| is
  *                             ignored if the event type is "compositionstart"
  *                             or "compositioncommitasis".
- *                             If |key| is specified, the key event may be
- *                             dispatched.  This can emulates changing
- *                             composition state caused by key operation.
- *                             Its key value should start with "KEY_" if the
- *                             value is non-printable key name defined in D3E.
+ *
+ *                             If |key| is undefined, "keydown" and "keyup"
+ *                             events which are marked as "processed by IME"
+ *                             are dispatched.  If |key| is not null, "keydown"
+ *                             and/or "keyup" events are dispatched (if the
+ *                             |key.type| is specified as "keydown", only
+ *                             "keydown" event is dispatched).  Otherwise,
+ *                             i.e., if |key| is null, neither "keydown" nor
+ *                             "keyup" event is dispatched.
+ *
+ *                             If |key.doNotMarkKeydownAsProcessed| is not true,
+ *                             key value and keyCode value of "keydown" event
+ *                             will be set to "Process" and DOM_VK_PROCESSKEY.
+ *                             If |key.markKeyupAsProcessed| is true,
+ *                             key value and keyCode value of "keyup" event
+ *                             will be set to "Process" and DOM_VK_PROCESSKEY.
  * @param aWindow              Optional (If null, current |window| will be used)
  * @param aCallback            Optional (If non-null, use the callback for
  *                             receiving notifications to IME)
  */
 function synthesizeComposition(aEvent, aWindow = window, aCallback)
 {
   var TIP = _getTIP(aWindow, aCallback);
   if (!TIP) {
     return false;
   }
   var KeyboardEvent = _getKeyboardEvent(aWindow);
   var modifiers = _emulateToActivateModifiers(TIP, aEvent.key, aWindow);
   var ret = false;
-  var keyEventDict =
-    "key" in aEvent ?
-      _createKeyboardEventDictionary(aEvent.key.key, aEvent.key, aWindow) :
-      { dictionary: null, flags: 0 };
-  var keyEvent = 
-    "key" in aEvent ?
-      new KeyboardEvent(aEvent.type === "keydown" ? "keydown" : "",
-                        keyEventDict.dictionary) :
-      null;
+  var keyEventDict = {dictionary: null, flags: 0};
+  var keyEvent = null;
+  if (aEvent.key && typeof aEvent.key.key === "string") {
+    keyEventDict =
+      _createKeyboardEventDictionary(aEvent.key.key, aEvent.key, aWindow);
+    keyEvent = new KeyboardEvent(aEvent.key.type === "keydown" ?
+                                   "keydown" :
+                                   aEvent.key.type === "keyup" ?
+                                     "keyup" : "",
+                                 keyEventDict.dictionary)
+  } else if (aEvent.key === undefined) {
+    keyEventDict = _createKeyboardEventDictionary("KEY_Process", {}, aWindow);
+    keyEvent = new KeyboardEvent("", keyEventDict.dictionary)
+  }
   try {
     switch (aEvent.type) {
       case "compositionstart":
         ret = TIP.startComposition(keyEvent, keyEventDict.flags);
         break;
       case "compositioncommitasis":
         ret = TIP.commitComposition(keyEvent, keyEventDict.flags);
         break;
@@ -1931,18 +1970,25 @@ function synthesizeComposition(aEvent, a
                                         keyEventDict.flags);
         break;
     }
   } finally {
     _emulateToInactivateModifiers(TIP, modifiers, aWindow);
   }
 }
 /**
- * Synthesize a compositionchange event which causes a DOM text event and
- * compositionupdate event if it's necessary.
+ * Synthesize eCompositionChange event which causes a DOM text event, may
+ * cause compositionupdate event, and causes keydown event and keyup event
+ * unless you prevent to dispatch them explicitly (see aEvent.key's
+ * explanation).
+ *
+ * Note that if you call this when there is no composition, compositionstart
+ * event will be fired automatically.  This is better than you use
+ * synthesizeComposition("compositionstart") in most cases.  See the
+ * explanation of syntehszeComposition().
  *
  * @param aEvent   The compositionchange event's information, this has
  *                 |composition| and |caret| members.  |composition| has
  *                 |string| and |clauses| members.  |clauses| must be array
  *                 object.  Each object has |length| and |attr|.  And |caret|
  *                 has |start| and |length|.  See the following tree image.
  *
  *                 aEvent
@@ -1970,20 +2016,28 @@ function synthesizeComposition(aEvent, a
  *                 |composition.clauses[0].attr|.
  *
  *                 Set caret position to the |caret.start|. It's offset from
  *                 the start of the composition string.  Set caret length to
  *                 |caret.length|.  If it's larger than 0, it should be wide
  *                 caret.  However, current nsEditor doesn't support wide
  *                 caret, therefore, you should always set 0 now.
  *
- *                 If |key| is specified, the key event may be dispatched.
- *                 This can emulates changing composition state caused by key
- *                 operation.  Its key value should start with "KEY_" if the
- *                 value is non-printable key name defined in D3E.
+ *                 If |key| is undefined, "keydown" and "keyup" events which
+ *                 are marked as "processed by IME" are dispatched.  If |key|
+ *                 is not null, "keydown" and/or "keyup" events are dispatched
+ *                 (if the |key.type| is specified as "keydown", only "keydown"
+ *                 event is dispatched).  Otherwise, i.e., if |key| is null,
+ *                 neither "keydown" nor "keyup" event is dispatched.
+ *                 If |key.doNotMarkKeydownAsProcessed| is not true, key value
+ *                 and keyCode value of "keydown" event will be set to
+ *                 "Process" and DOM_VK_PROCESSKEY.
+ *                 If |key.markKeyupAsProcessed| is true key value and keyCode
+ *                 value of "keyup" event will be set to "Process" and
+ *                 DOM_VK_PROCESSKEY.
  *
  * @param aWindow  Optional (If null, current |window| will be used)
  * @param aCallback     Optional (If non-null, use the callback for receiving
  *                      notifications to IME)
  */
 function synthesizeCompositionChange(aEvent, aWindow = window, aCallback)
 {
   var TIP = _getTIP(aWindow, aCallback);
@@ -2020,25 +2074,30 @@ function synthesizeCompositionChange(aEv
   }
 
   if (aEvent.caret) {
     TIP.setCaretInPendingComposition(aEvent.caret.start);
   }
 
   var modifiers = _emulateToActivateModifiers(TIP, aEvent.key, aWindow);
   try {
-    var keyEventDict =
-      "key" in aEvent ?
-        _createKeyboardEventDictionary(aEvent.key.key, aEvent.key, aWindow) :
-        { dictionary: null, flags: 0 };
-    var keyEvent = 
-      "key" in aEvent ?
-        new KeyboardEvent(aEvent.type === "keydown" ? "keydown" : "",
-                          keyEventDict.dictionary) :
-        null;
+    var keyEventDict = {dictionary: null, flags: 0};
+    var keyEvent = null;
+    if (aEvent.key && typeof aEvent.key.key === "string") {
+      keyEventDict =
+        _createKeyboardEventDictionary(aEvent.key.key, aEvent.key, aWindow);
+      keyEvent = new KeyboardEvent(aEvent.key.type === "keydown" ?
+                                     "keydown" :
+                                     aEvent.key.type === "keyup" ?
+                                       "keyup" : "",
+                                   keyEventDict.dictionary)
+    } else if (aEvent.key === undefined) {
+      keyEventDict = _createKeyboardEventDictionary("KEY_Process", {}, aWindow);
+      keyEvent = new KeyboardEvent("", keyEventDict.dictionary)
+    }
     TIP.flushPendingComposition(keyEvent, keyEventDict.flags);
   } finally {
     _emulateToInactivateModifiers(TIP, modifiers, aWindow);
   }
 }
 
 // Must be synchronized with nsIDOMWindowUtils.
 const QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK          = 0x0000;
--- a/toolkit/content/tests/chrome/file_autocomplete_with_composition.js
+++ b/toolkit/content/tests/chrome/file_autocomplete_with_composition.js
@@ -93,138 +93,135 @@ nsDoTestsForAutoCompleteWithComposition.
           { "composition":
             { "string": "M",
               "clauses":
               [
                 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
               ]
             },
             "caret": { "start": 1, "length": 0 },
-            "key": { key: "M", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M,
-                     shiftKey: true },
+            "key": { key: "M" },
           }, aWindow);
       }, popup: false, value: "M", searchString: ""
     },
     { 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", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O },
+            "key": { key: "o" },
           }, aWindow);
       }, popup: false, value: "Mo", searchString: ""
     },
     { description: "compositionend should open the popup",
       completeDefaultIndex: false,
       execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis",
-          key: { key: "KEY_Enter", code: "Enter" } }, aWindow);
+        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } }, aWindow);
       }, 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", code: "KeyZ", keyCode: KeyboardEvent.DOM_VK_Z },
+            "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", code: "KeyI", keyCode: KeyboardEvent.DOM_VK_I },
+            "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", code: "Enter" } }, 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", code: "KeyL", keyCode: KeyboardEvent.DOM_VK_L },
+            "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", code: "KeyL", keyCode: KeyboardEvent.DOM_VK_L },
+            "key": { key: "l" },
           }, aWindow);
       }, popup: false, value: "Mozill", 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 }
+            "caret": { "start": 0, "length": 0 },
+            "key": { key: "KEY_Backspace" },
           }, aWindow);
       }, popup: false, value: "Mozi", searchString: "Mozi"
     },
     { description: "cancled compositionend should reopen the popup",
       completeDefaultIndex: false,
       execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommit", data: "",
-          key: { key: "KEY_Escape", code: "Escape" } }, 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",
       completeDefaultIndex: false,
       execute(aWindow) {
         synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow);
@@ -233,193 +230,191 @@ nsDoTestsForAutoCompleteWithComposition.
           { "composition":
             { "string": "z",
               "clauses":
               [
                 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
               ]
             },
             "caret": { "start": 1, "length": 0 },
-            "key": { key: "z", code: "KeyZ", keyCode: KeyboardEvent.DOM_VK_Z },
+            "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", code: "KeyI", keyCode: KeyboardEvent.DOM_VK_I },
+            "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 }
+            "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", code: "Escape" } }, aWindow);
+        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } }, aWindow);
       }, 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",
       completeDefaultIndex: false,
       execute(aWindow) {
-        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
-        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
       }, 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", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M,
-                     shiftKey: true },
+            "key": { key: "M" },
           }, aWindow);
       }, popup: false, value: "M", searchString: ""
     },
     { 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", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O },
+            "key": { key: "o" },
           }, aWindow);
       }, popup: false, value: "Mo", searchString: ""
     },
     { 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 }
+            "caret": { "start": 0, "length": 0 },
+            "key": { key: "KEY_Backspace" },
           }, aWindow);
       }, popup: false, value: "", searchString: ""
     },
     { description: "canceled compositionend shouldn't open the popup if it was closed",
       completeDefaultIndex: false,
       execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis",
-          key: { key: "KEY_Escape", code: "Escape" } }, aWindow);
+        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } }, aWindow);
       }, 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("VK_DOWN", {}, 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", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M,
-                     shiftKey: true },
+            "key": { key: "M" },
           }, aWindow);
       }, popup: false, value: "M", searchString: ""
     },
     { 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", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O },
+            "key": { key: "o" },
           }, aWindow);
       }, popup: false, value: "Mo", searchString: ""
     },
     { 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 }
+            "caret": { "start": 0, "length": 0 },
+            "key": { key: "KEY_Backspace" },
           }, aWindow);
       }, 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", code: "Escape" } }, 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", { shiftKey: true }, aWindow);
-        synthesizeKey("o", { shiftKey: true }, aWindow);
-        synthesizeKey("VK_ESCAPE", {}, aWindow);
+        synthesizeKey("M", {}, aWindow);
+        synthesizeKey("o", {}, aWindow);
+        synthesizeKey("KEY_Escape", {}, aWindow);
       }, popup: false, value: "Mo", 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) {
@@ -427,113 +422,111 @@ nsDoTestsForAutoCompleteWithComposition.
           { "composition":
             { "string": "z",
               "clauses":
               [
                 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
               ]
             },
             "caret": { "start": 1, "length": 0 },
-            "key": { key: "z", code: "KeyZ", keyCode: KeyboardEvent.DOM_VK_Z },
+            "key": { key: "z" },
           }, aWindow);
       }, popup: false, value: "Moz", searchString: "Mo"
     },
     { 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", code: "KeyI", keyCode: KeyboardEvent.DOM_VK_I },
+            "key": { key: "i", },
           }, aWindow);
       }, popup: false, value: "Mozi", 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 }
+            "caret": { "start": 0, "length": 0 },
+            "key": { key: "KEY_Backspace" },
           }, aWindow);
       }, popup: false, 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", code: "Escape" } }, 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("VK_BACK_SPACE", {}, aWindow);
-        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
       }, popup: false, value: "", searchString: ""
     },
     // Testing for nsIAutoCompleteInput.completeDefaultIndex being true.
     { description: "compositionstart shouldn't open the popup (completeDefaultIndex is true)",
       completeDefaultIndex: true,
       execute(aWindow) {
         synthesizeCompositionChange(
           { "composition":
             { "string": "M",
               "clauses":
               [
                 { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
               ]
             },
             "caret": { "start": 1, "length": 0 },
-            "key": { key: "M", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M,
-                     shiftKey: true },
+            "key": { key: "M" },
           }, aWindow);
       }, popup: false, value: "M", searchString: ""
     },
     { description: "modifying composition string shouldn't open the popup (completeDefaultIndex is true)",
       completeDefaultIndex: true,
       execute(aWindow) {
         synthesizeCompositionChange(
           { "composition":
             { "string": "Mo",
               "clauses":
               [
                 { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
               ]
             },
             "caret": { "start": 2, "length": 0 },
-            "key": { key: "o", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O },
+            "key": { key: "o" },
           }, aWindow);
       }, popup: false, value: "Mo", searchString: ""
     },
     { description: "compositionend should open the popup (completeDefaultIndex is true)",
       completeDefaultIndex: true,
       execute(aWindow) {
-        synthesizeComposition({ type: "compositioncommitasis",
-          key: { key: "KEY_Enter", code: "Enter" } }, aWindow);
+        synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } }, aWindow);
       }, popup: true, value: "Mozilla", searchString: "Mo"
     },
     // House keeping...
     { description: "house keeping for next tests",
       completeDefaultIndex: false,
       execute(aWindow) {
-        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
-        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
-        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
-        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
-        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
-        synthesizeKey("VK_BACK_SPACE", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
+        synthesizeKey("KEY_Backspace", {}, aWindow);
       }, popup: false, value: "", searchString: ""
     }
   ]
 };
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -1433,16 +1433,18 @@ WidgetKeyboardEvent::ComputeKeyCodeFromK
     case KEY_NAME_INDEX_AudioVolumeDown:
       return dom::KeyboardEventBinding::DOM_VK_VOLUME_DOWN;
     case KEY_NAME_INDEX_AudioVolumeUp:
       return dom::KeyboardEventBinding::DOM_VK_VOLUME_UP;
     case KEY_NAME_INDEX_Meta:
       return dom::KeyboardEventBinding::DOM_VK_META;
     case KEY_NAME_INDEX_AltGraph:
       return dom::KeyboardEventBinding::DOM_VK_ALTGR;
+    case KEY_NAME_INDEX_Process:
+      return dom::KeyboardEventBinding::DOM_VK_PROCESSKEY;
     case KEY_NAME_INDEX_Attn:
       return dom::KeyboardEventBinding::DOM_VK_ATTN;
     case KEY_NAME_INDEX_CrSel:
       return dom::KeyboardEventBinding::DOM_VK_CRSEL;
     case KEY_NAME_INDEX_ExSel:
       return dom::KeyboardEventBinding::DOM_VK_EXSEL;
     case KEY_NAME_INDEX_EraseEof:
       return dom::KeyboardEventBinding::DOM_VK_EREOF;
--- a/widget/tests/test_assign_event_data.html
+++ b/widget/tests/test_assign_event_data.html
@@ -238,19 +238,19 @@ const kTests = [
         synthesizeCompositionChange({ "composition":
           { "string": "\u306D",
             "clauses":
             [
               { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
             ]
           },
           "caret": { "start": 1, "length": 0 },
-          "key": { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A },
+          "key": { key: "a" },
         });
-        synthesizeComposition({ type: "compositioncommitasis" });
+        synthesizeComposition({ type: "compositioncommitasis", key: {} });
         setAndObserveCompositionPref(null, runNextTest);
       });
       return true;
     },
     canRun: function () {
       return true;
     },
     todoMismatch: [ ],
@@ -263,20 +263,21 @@ const kTests = [
         document.getElementById(this.targetID).focus();
         synthesizeCompositionChange({ "composition":
           { "string": "\u306D",
             "clauses":
             [
               { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
             ]
           },
-          "caret": { "start": 1, "length": 0 }
+          "caret": { "start": 1, "length": 0 },
+          "key": {},
         });
         synthesizeComposition({ type: "compositioncommitasis",
-          key: { key: "KEY_Enter", code: "Enter" } });
+          key: { key: "KEY_Enter" } });
         setAndObserveCompositionPref(null, runNextTest);
       });
       return true;
     },
     canRun: function () {
       return true;
     },
     todoMismatch: [ ],
@@ -328,29 +329,29 @@ const kTests = [
     },
     todoMismatch: [],
   },
   { description: "WidgetTextEvent (text)",
     targetID: "input-text", eventType: "text",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       document.getElementById(this.targetID).focus();
-      synthesizeComposition({ type: "compositioncommit", data: "\u306D" });
+      synthesizeComposition({ type: "compositioncommit", data: "\u306D", key: { key: "," } });
     },
     canRun: function () {
       return true;
     },
     todoMismatch: [ ],
   },
   { description: "WidgetCompositionEvent (compositionupdate)",
     targetID: "input-text", eventType: "compositionupdate",
     dispatchEvent: function () {
       document.getElementById(this.targetID).value = "";
       document.getElementById(this.targetID).focus();
-      synthesizeComposition({ type: "compositioncommit", data: "\u30E9\u30FC\u30E1\u30F3" });
+      synthesizeComposition({ type: "compositioncommit", data: "\u30E9\u30FC\u30E1\u30F3", key: { key: "KEY_Enter" } });
     },
     canRun: function () {
       return true;
     },
     todoMismatch: [ ],
   },
   { description: "InternalEditorInputEvent (input at key input)",
     targetID: "input-text", eventType: "input",
@@ -374,28 +375,29 @@ const kTests = [
       document.getElementById(this.targetID).focus();
       synthesizeCompositionChange({ "composition":
         { "string": "\u30E9\u30FC\u30E1\u30F3",
           "clauses":
           [
             { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
           ]
         },
-        "caret": { "start": 4, "length": 0 }
+        "caret": { "start": 4, "length": 0 },
+        "key": { key: "y" },
       });
     },
     canRun: function () {
       return true;
     },
     todoMismatch: [ ],
   },
   { description: "InternalEditorInputEvent (input at committing)",
     targetID: "input-text", eventType: "input",
     dispatchEvent: function () {
-      synthesizeComposition({ type: "compositioncommitasis" });
+      synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
     },
     canRun: function () {
       return true;
     },
     todoMismatch: [ ],
   },
   { description: "WidgetMouseScrollEvent (DOMMouseScroll, vertical)",
     targetID: "input-text", eventType: "DOMMouseScroll",
--- a/widget/tests/window_composition_text_querycontent.xul
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -314,174 +314,187 @@ function runUndoRedoTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u306D",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "," },
     });
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u306D\u3053",
         "clauses":
         [
           { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 2, "length": 0 }
+      "caret": { "start": 2, "length": 0 },
+      "key": { key: "b" },
     });
 
   // convert
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u732B",
         "clauses":
         [
           { "length": 1,
             "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: " " },
     });
 
   // commit
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
 
   // input raw characters
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u307E",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "j" },
     });
 
   // cancel the composition
-  synthesizeComposition({ type: "compositioncommit", data: "" });
+  synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
 
   // input raw characters
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3080",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "]" },
     });
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3080\u3059",
         "clauses":
         [
           { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 2, "length": 0 }
+      "caret": { "start": 2, "length": 0 },
+      "key": { key: "r" },
     });
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3080\u3059\u3081",
         "clauses":
         [
           { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 3, "length": 0 }
+      "caret": { "start": 3, "length": 0 },
+      "key": { key: "/" },
     });
 
   // convert
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u5A18",
         "clauses":
         [
           { "length": 1,
             "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: " " },
     });
 
   // commit
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
 
   sendString(" meant");
   synthesizeKey("KEY_Backspace");
   synthesizeKey("s \"cat-girl\". She is a ");
 
   // input raw characters
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3088",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "9" },
     });
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3088\u3046",
         "clauses":
         [
           { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 2, "length": 0 }
+      "caret": { "start": 2, "length": 0 },
+      "key": { key: "4" },
     });
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3088\u3046\u304b",
         "clauses":
         [
           { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 3, "length": 0 }
+      "caret": { "start": 3, "length": 0 },
+      "key": { key: "t" },
     });
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3088\u3046\u304b\u3044",
         "clauses":
         [
           { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 4, "length": 0 }
+      "caret": { "start": 4, "length": 0 },
+      "key": { key: "e" },
     });
 
   // convert
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u5996\u602a",
         "clauses":
         [
           { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
         ]
       },
-      "caret": { "start": 2, "length": 0 }
+      "caret": { "start": 2, "length": 0 },
+      "key": { key: " " },
     });
 
   // commit
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
 
   synthesizeKey("KEY_Backspace", {repeat: 12});
 
   var i = 0;
   if (!checkContent("\u732B\u5A18 means \"cat-girl\".",
                     "runUndoRedoTest", "#" + ++i) ||
       !checkSelection(20, "", "runUndoRedoTest", "#" + i)) {
     return;
@@ -659,22 +672,23 @@ function runCompositionCommitAsIsTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #1");
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "Enter" } });
 
   is(result.compositionupdate, false, "runCompositionCommitAsIsTest: compositionupdate shouldn't be fired after dispatching compositioncommitasis #1");
   is(result.compositionend, true, "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #1");
   is(result.text, true, "runCompositionCommitAsIsTest: text should be fired after dispatching compositioncommitasis because it's dispatched when there is composing string #1");
   is(result.input, true, "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #1");
   is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #1");
 
   // compositioncommitasis with committed string.
@@ -682,33 +696,35 @@ function runCompositionCommitAsIsTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #2");
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "KEY_Enter", type: "keydown" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter", type: "keyup" } });
 
   is(result.compositionupdate, false, "runCompositionCommitAsIsTest: compositionupdate shouldn't be fired after dispatching compositioncommitasis #2");
   is(result.compositionend, true, "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #2");
   is(result.text, false, "runCompositionCommitAsIsTest: text shouldn't be fired after dispatching compositioncommitasis because it's dispatched when there is already committed string #2");
   is(result.input, true, "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #2");
   is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have committed string #2");
 
   // compositioncommitasis with committed string.
@@ -716,33 +732,35 @@ function runCompositionCommitAsIsTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitAsIsTest: textarea doesn't have composition string #3");
   synthesizeCompositionChange(
     { "composition":
       { "string": "",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 0, "length": 0 }
+      "caret": { "start": 0, "length": 0 },
+      "key": { key: "KEY_Escape", type: "keydown" },
     });
   is(textarea.value, "", "runCompositionCommitAsIsTest: textarea has non-empty composition string #3");
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape", type: "keyup" } });
 
   is(result.compositionupdate, false, "runCompositionCommitAsIsTest: compositionupdate shouldn't be fired after dispatching compositioncommitasis #3");
   is(result.compositionend, true, "runCompositionCommitAsIsTest: compositionend should be fired after dispatching compositioncommitasis #3");
   is(result.text, false, "runCompositionCommitAsIsTest: text shouldn't be fired after dispatching compositioncommitasis because it's dispatched when there is empty composition string #3");
   is(result.input, true, "runCompositionCommitAsIsTest: input should be fired after dispatching compositioncommitasis #3");
   is(textarea.value, "", "runCompositionCommitAsIsTest: textarea doesn't have committed string #3");
 
   textarea.removeEventListener("compositionupdate", handler, true);
@@ -776,22 +794,23 @@ function runCompositionCommitTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a", type: "keydown" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #1");
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommit", data: "\u3043" });
+  synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "a", type: "keyup" } });
 
   is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #1");
   is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #1");
   is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit because it's dispatched when there is compoing string #1");
   is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #1");
   is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #1");
 
   // compositioncommit with different committed string when there is already committed string
@@ -799,33 +818,35 @@ function runCompositionCommitTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #2");
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "KEY_Enter", type: "keydown" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have committed string #2");
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommit", data: "\u3043" });
+  synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
 
   is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #2");
   is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #2");
   is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit #2");
   is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #2");
   is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #2");
 
   // compositioncommit with empty composition string.
@@ -833,33 +854,35 @@ function runCompositionCommitTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #3");
   synthesizeCompositionChange(
     { "composition":
       { "string": "",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 0, "length": 0 }
+      "caret": { "start": 0, "length": 0 },
+      "key": { key: "KEY_Enter", type: "keydown" },
     });
   is(textarea.value, "", "runCompositionCommitTest: textarea has non-empty composition string #3");
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommit", data: "\u3043" });
+  synthesizeComposition({ type: "compositioncommit", data: "\u3043", key: { key: "KEY_Enter", type: "keyup" } });
 
   is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #3");
   is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #3");
   is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit #3");
   is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #3");
   is(textarea.value, "\u3043", "runCompositionCommitTest: textarea doesn't have committed string #3");
 
   // compositioncommit with non-empty composition string.
@@ -867,34 +890,35 @@ function runCompositionCommitTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #4");
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommit", data: "" });
+  synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Enter" } });
 
   is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #4");
   is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #4");
   is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit #4");
   is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #4");
   is(textarea.value, "", "runCompositionCommitTest: textarea should be empty #4");
 
   // compositioncommit immediately without compositionstart
   textarea.value = "";
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommit", data: "\u3042" });
+  synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "a" } });
 
   is(result.compositionupdate, true, "runCompositionCommitTest: compositionupdate should be fired after dispatching compositioncommit #5");
   is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
   is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit #5");
   is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should be empty #5");
 
   // compositioncommit with same composition string.
@@ -902,22 +926,23 @@ function runCompositionCommitTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #5");
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommit", data: "\u3042" });
+  synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter" } });
 
   is(result.compositionupdate, false, "runCompositionCommitTest: compositionupdate shouldn't be fired after dispatching compositioncommit #5");
   is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #5");
   is(result.text, true, "runCompositionCommitTest: text should be fired after dispatching compositioncommit because there was composition string #5");
   is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #5");
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #5");
 
   // compositioncommit with same composition string when there is committed string
@@ -925,34 +950,36 @@ function runCompositionCommitTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3042",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "KEY_Enter", type: "keydown" },
     });
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea doesn't have composition string #6");
 
   clearResult();
-  synthesizeComposition({ type: "compositioncommit", data: "\u3042" });
+  synthesizeComposition({ type: "compositioncommit", data: "\u3042", key: { key: "KEY_Enter", type: "keyup" } });
 
   is(result.compositionupdate, false, "runCompositionCommitTest: compositionupdate shouldn't be fired after dispatching compositioncommit #6");
   is(result.compositionend, true, "runCompositionCommitTest: compositionend should be fired after dispatching compositioncommit #6");
   is(result.text, false, "runCompositionCommitTest: text shouldn't be fired after dispatching compositioncommit because there was already committed string #6");
   is(result.input, true, "runCompositionCommitTest: input should be fired after dispatching compositioncommit #6");
   is(textarea.value, "\u3042", "runCompositionCommitTest: textarea should have committed string #6");
 
   textarea.removeEventListener("compositionupdate", handler, true);
@@ -978,17 +1005,18 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "o" },
     });
 
   if (!checkContent("\u3089", "runCompositionTest", "#1-1") ||
       !checkSelection(1, "", "runCompositionTest", "#1-1")) {
     return;
   }
 
   caretRect = synthesizeQueryCaretRect(1);
@@ -1002,17 +1030,18 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC",
         "clauses":
         [
           { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 2, "length": 0 }
+      "caret": { "start": 2, "length": 0 },
+      "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
     });
 
   if (!checkContent("\u3089\u30FC", "runCompositionTest", "#1-2") ||
       !checkSelection(2, "", "runCompositionTest", "#1-2")) {
     return;
   }
 
   caretRect = synthesizeQueryCaretRect(2);
@@ -1035,17 +1064,18 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081",
         "clauses":
         [
           { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 3, "length": 0 }
+      "caret": { "start": 3, "length": 0 },
+      "key": { key: "/" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3") ||
       !checkSelection(3, "", "runCompositionTest", "#1-3")) {
     return;
   }
 
   caretRect = synthesizeQueryCaretRect(3);
@@ -1068,17 +1098,18 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081",
         "clauses":
         [
           { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 2, "length": 0 }
+      "caret": { "start": 2, "length": 0 },
+      "key": { key: "KEY_ArrowLeft" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-1") ||
       !checkSelection(2, "", "runCompositionTest", "#1-3-1")) {
     return;
   }
 
 
@@ -1102,17 +1133,18 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081",
         "clauses":
         [
           { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "KEY_ArrowLeft" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-3-2") ||
       !checkSelection(1, "", "runCompositionTest", "#1-3-2")) {
     return;
   }
 
 
@@ -1135,17 +1167,18 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081\u3093",
         "clauses":
         [
           { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 4, "length": 0 }
+      "caret": { "start": 4, "length": 0 },
+      "key": { key: "y" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-4") ||
       !checkSelection(4, "", "runCompositionTest", "#1-4")) {
     return;
   }
 
 
@@ -1153,98 +1186,104 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081",
         "clauses":
         [
           { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 3, "length": 0 }
+      "caret": { "start": 3, "length": 0 },
+      "key": { key: "KEY_Backspace" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081", "runCompositionTest", "#1-5") ||
       !checkSelection(3, "", "runCompositionTest", "#1-5")) {
     return;
   }
 
   // re-input
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081\u3093",
         "clauses":
         [
           { "length": 4, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 4, "length": 0 }
+      "caret": { "start": 4, "length": 0 },
+      "key": { key: "y" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081\u3093", "runCompositionTest", "#1-6") ||
       !checkSelection(4, "", "runCompositionTest", "#1-6")) {
     return;
   }
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081\u3093\u3055",
         "clauses":
         [
           { "length": 5, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 5, "length": 0 }
+      "caret": { "start": 5, "length": 0 },
+      "key": { key: "x" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081\u3093\u3055", "runCompositionTest", "#1-7") ||
       !checkSelection(5, "", "runCompositionTest", "#1-7")) {
     return;
   }
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044",
         "clauses":
         [
           { "length": 6, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 6, "length": 0 }
+      "caret": { "start": 6, "length": 0 },
+      "key": { key: "e" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044", "runCompositionTest", "#1-8") ||
       !checkSelection(6, "", "runCompositionTest", "#1-8")) {
     return;
   }
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053",
         "clauses":
         [
           { "length": 7, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 7, "length": 0 }
+      "caret": { "start": 7, "length": 0 },
+      "key": { key: "b" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053", "runCompositionTest", "#1-8") ||
       !checkSelection(7, "", "runCompositionTest", "#1-8")) {
     return;
   }
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
         "clauses":
         [
           { "length": 8, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 8, "length": 0 }
+      "caret": { "start": 8, "length": 0 },
+      "key": { key: "4" },
     });
 
   if (!checkContent("\u3089\u30FC\u3081\u3093\u3055\u3044\u3053\u3046",
                     "runCompositionTest", "#1-9") ||
       !checkSelection(8, "", "runCompositionTest", "#1-9")) {
     return;
   }
 
@@ -1255,17 +1294,18 @@ function runCompositionTest()
         "clauses":
         [
           { "length": 4,
             "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
           { "length": 2,
             "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
         ]
       },
-      "caret": { "start": 4, "length": 0 }
+      "caret": { "start": 4, "length": 0 },
+      "key": { key: " " },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
                     "runCompositionTest", "#1-10") ||
       !checkSelection(4, "", "runCompositionTest", "#1-10")) {
     return;
   }
 
@@ -1276,17 +1316,18 @@ function runCompositionTest()
         "clauses":
         [
           { "length": 4,
             "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE },
           { "length": 2,
             "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
         ]
       },
-      "caret": { "start": 6, "length": 0 }
+      "caret": { "start": 6, "length": 0 },
+      "key": { key: "KEY_ArrowLeft", shiftKey: true },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700\u9AD8",
                     "runCompositionTest", "#1-11") ||
       !checkSelection(6, "", "runCompositionTest", "#1-11")) {
     return;
   }
 
@@ -1297,17 +1338,18 @@ function runCompositionTest()
         "clauses":
         [
           { "length": 5,
             "attr": COMPOSITION_ATTR_SELECTED_CLAUSE },
           { "length": 3,
             "attr": COMPOSITION_ATTR_CONVERTED_CLAUSE }
         ]
       },
-      "caret": { "start": 5, "length": 0 }
+      "caret": { "start": 5, "length": 0 },
+      "key": { key: "KEY_ArrowRight" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046",
                     "runCompositionTest", "#1-12") ||
       !checkSelection(5, "", "runCompositionTest", "#1-12")) {
     return;
   }
 
@@ -1347,78 +1389,82 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3057",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "d" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3057",
                     "runCompositionTest", "#2-1") ||
       !checkSelection(8 + 1, "", "runCompositionTest", "#2-1")) {
     return;
   }
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3058",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "r" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058",
                     "runCompositionTest", "#2-2") ||
       !checkSelection(8 + 1, "", "runCompositionTest", "#2-2")) {
     return;
   }
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3058\u3087",
         "clauses":
         [
           { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 2, "length": 0 }
+      "caret": { "start": 2, "length": 0 },
+      "key": { key: ")", code: "Digit9", keyCode: KeyboardEvent.DOM_VK_9, shiftKey: true },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087",
                     "runCompositionTest", "#2-3") ||
       !checkSelection(8 + 2, "", "runCompositionTest", "#2-3")) {
     return;
   }
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3058\u3087\u3046",
         "clauses":
         [
           { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 3, "length": 0 }
+      "caret": { "start": 3, "length": 0 },
+      "key": { key: "4" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
                     "runCompositionTest", "#2-4") ||
       !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
     return;
   }
 
   // commit the composition string
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3055\u884C\u3053\u3046\u3058\u3087\u3046",
                     "runCompositionTest", "#2-4") ||
       !checkSelection(8 + 3, "", "runCompositionTest", "#2-4")) {
     return;
   }
 
   // set selection
@@ -1433,17 +1479,18 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u304A",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "6" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u304A",
                     "runCompositionTest", "#3-2") ||
       !checkSelection(4 + 1, "", "runCompositionTest", "#3-2")) {
     return;
   }
 
@@ -1451,17 +1498,18 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 0, "length": 0 }
+      "caret": { "start": 0, "length": 0 },
+      "key": { key: "KEY_Backspace" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                     "runCompositionTest", "#3-3") ||
       !checkSelection(4, "", "runCompositionTest", "#3-3")) {
     return;
   }
 
@@ -1469,27 +1517,28 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3046",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "4" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u3046",
                     "runCompositionTest", "#3-4") ||
       !checkSelection(4 + 1, "", "runCompositionTest", "#3-4")) {
     return;
   }
 
   // cancel the composition
-  synthesizeComposition({ type: "compositioncommit", data: "" });
+  synthesizeComposition({ type: "compositioncommit", data: "", key: { key: "KEY_Escape" } });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                     "runCompositionTest", "#3-5") ||
       !checkSelection(4, "", "runCompositionTest", "#3-5")) {
     return;
   }
 
   // bug 271815, some Chinese IMEs for Linux make empty composition string
@@ -1498,68 +1547,71 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 0, "length": 0 }
+      "caret": { "start": 0, "length": 0 },
+      "key": { key: "a" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                     "runCompositionTest", "#4-1") ||
       !checkSelection(4, "", "runCompositionTest", "#4-1")) {
     return;
   }
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 0, "length": 0 }
+      "caret": { "start": 0, "length": 0 },
+      "key": { key: "b" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                     "runCompositionTest", "#4-2") ||
       !checkSelection(4, "", "runCompositionTest", "#4-2")) {
     return;
   }
 
-  synthesizeComposition({ type: "compositioncommit", data: "\u6700" });
+  synthesizeComposition({ type: "compositioncommit", data: "\u6700", key: { key: "KEY_Enter" } });
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                     "runCompositionTest", "#4-3") ||
       !checkSelection(5, "", "runCompositionTest", "#4-3")) {
     return;
   }
 
   // testing the canceling case
   synthesizeCompositionChange(
     { "composition":
       { "string": "",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 0, "length": 0 }
+      "caret": { "start": 0, "length": 0 },
+      "key": { key: "a" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                     "runCompositionTest", "#4-5") ||
       !checkSelection(5, "", "runCompositionTest", "#4-5")) {
     return;
   }
 
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Escape" } });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                     "runCompositionTest", "#4-6") ||
       !checkSelection(5, "", "runCompositionTest", "#4-6")) {
     return;
   }
 
   // testing whether the empty composition string deletes selected string.
@@ -1568,26 +1620,27 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 0, "length": 0 }
+      "caret": { "start": 0, "length": 0 },
+      "key": { key: "a" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                     "runCompositionTest", "#4-8") ||
       !checkSelection(4, "", "runCompositionTest", "#4-8")) {
     return;
   }
 
-  synthesizeComposition({ type: "compositioncommit", data: "\u9AD8" });
+  synthesizeComposition({ type: "compositioncommit", data: "\u9AD8", key: { key: "KEY_Enter" } });
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
                     "runCompositionTest", "#4-9") ||
       !checkSelection(5, "", "runCompositionTest", "#4-9")) {
     return;
   }
 
   synthesizeKey("KEY_Backspace");
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
@@ -1601,85 +1654,87 @@ function runCompositionTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u6700",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "a" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                     "runCompositionTest", "#5-1") ||
       !checkSelection(4 + 1, "", "runCompositionTest", "#5-1")) {
     return;
   }
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "",
         "clauses":
         [
           { "length": 0, "attr": 0 }
         ]
       },
-      "caret": { "start": 0, "length": 0 }
+      "caret": { "start": 0, "length": 0 },
+      "key": { key: "KEY_Backspace", type: "keydown" },
     });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                     "runCompositionTest", "#5-2") ||
       !checkSelection(4, "", "runCompositionTest", "#5-2")) {
     return;
   }
 
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Backspace", type: "keyup" } });
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                     "runCompositionTest", "#5-3") ||
       !checkSelection(4, "", "runCompositionTest", "#5-3")) {
     return;
   }
 
   // Undo tests for the testcases for bug 23558 and bug 271815
-  synthesizeKey("Z", { accelKey: true });
+  synthesizeKey("z", { accelKey: true });
 
   // XXX this is unexpected behavior, see bug 258291
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                     "runCompositionTest", "#6-1") ||
       !checkSelection(4, "", "runCompositionTest", "#6-1")) {
     return;
   }
 
-  synthesizeKey("Z", { accelKey: true });
+  synthesizeKey("z", { accelKey: true });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u9AD8",
                     "runCompositionTest", "#6-2") ||
       !checkSelection(5, "", "runCompositionTest", "#6-2")) {
     return;
   }
 
-  synthesizeKey("Z", { accelKey: true });
+  synthesizeKey("z", { accelKey: true });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                     "runCompositionTest", "#6-3") ||
       !checkSelection(4, "\u6700", "runCompositionTest", "#6-3")) {
     return;
   }
 
-  synthesizeKey("Z", { accelKey: true });
+  synthesizeKey("z", { accelKey: true });
 
   // XXX this is unexpected behavior, see bug 258291
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3\u6700",
                     "runCompositionTest", "#6-4") ||
       !checkSelection(5, "", "runCompositionTest", "#6-4")) {
     return;
   }
 
-  synthesizeKey("Z", { accelKey: true });
+  synthesizeKey("z", { accelKey: true });
 
   if (!checkContent("\u30E9\u30FC\u30E1\u30F3",
                     "runCompositionTest", "#6-5") ||
       !checkSelection(4, "", "runCompositionTest", "#6-5")) {
     return;
   }
 }
 
@@ -1772,17 +1827,18 @@ function runCompositionEventTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "o" },
     });
 
   is(windowEventCounts["compositionstart"], 1,
      kDescription + "compositionstart hasn't been handled by window #1");
   is(windowEventData["compositionstart"], "",
      kDescription + "data of compositionstart isn't empty (window) #1");
   is(windowEventLocale["compositionstart"], "",
      kDescription + "locale of compositionstart isn't empty (window) #1");
@@ -1823,17 +1879,18 @@ function runCompositionEventTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089\u30FC",
         "clauses":
         [
           { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 2, "length": 0 }
+      "caret": { "start": 2, "length": 0 },
+      "key": { key: "\\", code: "IntlYen", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
     });
 
   is(windowEventCounts["compositionstart"], 1,
      kDescription + "compositionstart has been handled more than once by window #2");
   is(inputEventCounts["compositionstart"], 1,
      kDescription + "compositionstart has been handled more than once by input #2");
 
   is(windowEventCounts["compositionupdate"], 2,
@@ -1859,17 +1916,17 @@ function runCompositionEventTest()
   is(windowEventData["input"], "\u3089\u30FC",
      kDescription + "value of input element wasn't modified (window) #2");
   is(inputEventCounts["input"], 2,
      kDescription + "input hasn't been handled by input #2");
   is(inputEventData["input"], "\u3089\u30FC",
      kDescription + "value of input element wasn't modified (input) #2");
 
   // text event shouldn't cause composition update, e.g., at committing.
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
 
   is(windowEventCounts["compositionstart"], 1,
      kDescription + "compositionstart has been handled more than once by window #3");
   is(inputEventCounts["compositionstart"], 1,
      kDescription + "compositionstart has been handled more than once by input #3");
 
   is(windowEventCounts["compositionupdate"], 2,
      kDescription + "compositionupdate has been fired unexpectedly on window #3");
@@ -1906,20 +1963,21 @@ function runCompositionEventTest()
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u3089",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "o" },
     });
 
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
 
   is(windowEventCounts["compositionstart"], 1,
      kDescription + "compositionstart hasn't been handled by window #4");
   is(windowEventData["compositionstart"], "\u30FC",
      kDescription + "data of compositionstart is empty (window) #4");
   is(windowEventLocale["compositionstart"], "",
      kDescription + "locale of compositionstart isn't empty (window) #4");
   is(inputEventCounts["compositionstart"], 1,
@@ -1963,27 +2021,28 @@ function runCompositionEventTest()
      kDescription + "input hasn't been handled by input #4");
   is(inputEventData["input"], "\u3089\u3089",
      kDescription + "value of input element wasn't modified (input) #4");
 
   // preventDefault() should effect nothing.
   preventDefault = true;
 
   initResults();
-  synthesizeKey("A", { accelKey: true }); // Select All
+  synthesizeKey("a", { accelKey: true }); // Select All
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u306D",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "," },
     });
 
   synthesizeComposition({ type: "compositioncommitasis" });
 
   is(windowEventCounts["compositionstart"], 1,
      kDescription + "compositionstart hasn't been handled by window #5");
   is(windowEventData["compositionstart"], "\u3089\u3089",
      kDescription + "data of compositionstart is empty (window) #5");
@@ -2032,30 +2091,31 @@ function runCompositionEventTest()
      kDescription + "value of input element wasn't modified (input) #5");
 
   prevnetDefault = false;
 
   // stopPropagation() should effect nothing (except event count)
   stopPropagation = true;
 
   initResults();
-  synthesizeKey("A", { accelKey: true }); // Select All
+  synthesizeKey("a", { accelKey: true }); // Select All
 
   synthesizeCompositionChange(
     { "composition":
       { "string": "\u306E",
         "clauses":
         [
           { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
-      "caret": { "start": 1, "length": 0 }
+      "caret": { "start": 1, "length": 0 },
+      "key": { key: "\\", code: "IntlRo", keyCode: KeyboardEvent.DOM_VK_BACKSLASH },
     });
 
-  synthesizeComposition({ type: "compositioncommitasis" });
+  synthesizeComposition({ type: "compositioncommitasis", key: { key: "KEY_Enter" } });
 
   is(windowEventCounts["compositionstart"], 1,
      kDescription + "compositionstart hasn't been handled by window #6");
   is(windowEventData["compositionstart"], "\u306D",
      kDescription + "data of compositionstart is empty #6");
   is(windowEventLocale["compositionstart"], "",
      kDescription + "locale of compositionstart isn't empty #6");
   is(inputEventCounts["compositionstart"], 0,
@@ -2089,17 +2149,17 @@ function runCompositionEventTest()
      kDescription + "value of input element wasn't modified (input) #6");
 
   stopPropagation = false;
 
   // create event and dispatch it.
   initResults();
 
   input.value = "value of input";
-  synthesizeKey("A", { accelKey: true }); // Select All
+  synthesizeKey("a", { accelKey: true }); // Select All
 
   var compositionstart = document.createEvent("CompositionEvent");
   compositionstart.initCompositionEvent("compositionstart",
                                         true, true, document.defaultView,
                                         "start data", "start locale");
   is(compositionstart.type, "compositionstart",
      kDescription + "type doesn't match #7");
   is(compositionstart.data, "start data",