Bug 1119609 part.12 nsITextInputProcessor should take KeyboardEvent as an argument of composition releated methods for dispatching key events around composition events r=smaug, sr=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 19 Feb 2015 15:50:20 +0900
changeset 258625 bcf39cf754249c23b5204fd091ba73475ba9bd31
parent 258624 0ea20218201750faf0c76202e58fb8c18a2dd628
child 258626 faa6c7dc1dd121f9c6634275e718fa21a178e57d
push id721
push userjlund@mozilla.com
push dateTue, 21 Apr 2015 23:03:33 +0000
treeherdermozilla-release@d27c9211ebb3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, smaug
bugs1119609
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1119609 part.12 nsITextInputProcessor should take KeyboardEvent as an argument of composition releated methods for dispatching key events around composition events r=smaug, sr=smaug
dom/base/TextInputProcessor.cpp
dom/base/TextInputProcessor.h
dom/base/test/chrome/window_nsITextInputProcessor.xul
dom/inputmethod/forms.js
dom/interfaces/base/nsITextInputProcessor.idl
testing/mochitest/tests/SimpleTest/EventUtils.js
widget/TextEventDispatcher.h
widget/TextEvents.h
--- a/dom/base/TextInputProcessor.cpp
+++ b/dom/base/TextInputProcessor.cpp
@@ -73,16 +73,31 @@ TextInputProcessor::~TextInputProcessor(
     if (NS_SUCCEEDED(IsValidStateForComposition())) {
       nsRefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
       nsEventStatus status = nsEventStatus_eIgnore;
       mDispatcher->CommitComposition(status, &EmptyString());
     }
   }
 }
 
+bool
+TextInputProcessor::IsComposing() const
+{
+  return mDispatcher && mDispatcher->IsComposing();
+}
+
+NS_IMETHODIMP
+TextInputProcessor::GetHasComposition(bool* aHasComposition)
+{
+  MOZ_RELEASE_ASSERT(aHasComposition, "aHasComposition must not be nullptr");
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  *aHasComposition = IsComposing();
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 TextInputProcessor::BeginInputTransaction(
                       nsIDOMWindow* aWindow,
                       nsITextInputProcessorCallback* aCallback,
                       bool* aSucceeded)
 {
   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
@@ -210,34 +225,168 @@ TextInputProcessor::IsValidStateForCompo
   nsresult rv = mDispatcher->GetState();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
+bool
+TextInputProcessor::IsValidEventTypeForComposition(
+                      const WidgetKeyboardEvent& aKeyboardEvent) const
+{
+  // The key event type of composition methods must be "" or "keydown".
+  if (aKeyboardEvent.message == NS_KEY_DOWN) {
+    return true;
+  }
+  if (aKeyboardEvent.message == NS_USER_DEFINED_EVENT &&
+      aKeyboardEvent.userType &&
+      nsDependentAtomString(aKeyboardEvent.userType).EqualsLiteral("on")) {
+    return true;
+  }
+  return false;
+}
+
+TextInputProcessor::EventDispatcherResult
+TextInputProcessor::MaybeDispatchKeydownForComposition(
+                      const WidgetKeyboardEvent* aKeyboardEvent,
+                      uint32_t aKeyFlags)
+{
+  EventDispatcherResult result;
+
+  result.mResult = IsValidStateForComposition();
+  if (NS_WARN_IF(NS_FAILED(result.mResult))) {
+    result.mCanContinue = false;
+    return result;
+  }
+
+  if (!aKeyboardEvent) {
+    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;
+  }
+
+  result.mResult = KeydownInternal(*aKeyboardEvent, aKeyFlags, false,
+                                   result.mDoDefault);
+  if (NS_WARN_IF(NS_FAILED(result.mResult))) {
+    result.mCanContinue = false;
+    return result;
+  }
+
+  result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition());
+  return result;
+}
+
+TextInputProcessor::EventDispatcherResult
+TextInputProcessor::MaybeDispatchKeyupForComposition(
+                      const WidgetKeyboardEvent* aKeyboardEvent,
+                      uint32_t aKeyFlags)
+{
+  EventDispatcherResult result;
+
+  if (!aKeyboardEvent) {
+    return result;
+  }
+
+  // If the message is NS_KEY_DOWN, the caller doesn't want TIP to dispatch
+  // keyup event.
+  if (aKeyboardEvent->message == NS_KEY_DOWN) {
+    return result;
+  }
+
+  // If the widget has been destroyed, we can do nothing here.
+  result.mResult = IsValidStateForComposition();
+  if (NS_FAILED(result.mResult)) {
+    result.mCanContinue = false;
+    return result;
+  }
+
+  result.mResult = KeyupInternal(*aKeyboardEvent, aKeyFlags, result.mDoDefault);
+  if (NS_WARN_IF(NS_FAILED(result.mResult))) {
+    result.mCanContinue = false;
+    return result;
+  }
+
+  result.mCanContinue = NS_SUCCEEDED(IsValidStateForComposition());
+  return result;
+}
+
+nsresult
+TextInputProcessor::PrepareKeyboardEventForComposition(
+                      nsIDOMKeyEvent* aDOMKeyEvent,
+                      uint32_t& aKeyFlags,
+                      uint8_t aOptionalArgc,
+                      WidgetKeyboardEvent*& aKeyboardEvent)
+{
+  aKeyboardEvent = nullptr;
+
+  aKeyboardEvent =
+    aOptionalArgc && aDOMKeyEvent ?
+      aDOMKeyEvent->GetInternalNSEvent()->AsKeyboardEvent() : nullptr;
+  if (!aKeyboardEvent || aOptionalArgc < 2) {
+    aKeyFlags = 0;
+  }
+
+  if (!aKeyboardEvent) {
+    return NS_OK;
+  }
+
+  if (NS_WARN_IF(!IsValidEventTypeForComposition(*aKeyboardEvent))) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
-TextInputProcessor::StartComposition(bool* aSucceeded)
+TextInputProcessor::StartComposition(nsIDOMKeyEvent* aDOMKeyEvent,
+                                     uint32_t aKeyFlags,
+                                     uint8_t aOptionalArgc,
+                                     bool* aSucceeded)
 {
   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
   *aSucceeded = false;
+
   nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
-  nsresult rv = IsValidStateForComposition();
+
+  WidgetKeyboardEvent* keyboardEvent;
+  nsresult rv =
+    PrepareKeyboardEventForComposition(aDOMKeyEvent, aKeyFlags, aOptionalArgc,
+                                       keyboardEvent);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  nsEventStatus status = nsEventStatus_eIgnore;
-  rv = mDispatcher->StartComposition(status);
+
+  EventDispatcherResult dispatcherResult =
+    MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags);
+  if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
+      !dispatcherResult.mCanContinue) {
+    return dispatcherResult.mResult;
+  }
+
+  if (dispatcherResult.mDoDefault) {
+    nsEventStatus status = nsEventStatus_eIgnore;
+    rv = mDispatcher->StartComposition(status);
+    *aSucceeded = status != nsEventStatus_eConsumeNoDefault &&
+                    mDispatcher && mDispatcher->IsComposing();
+  }
+
+  MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags);
+
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  *aSucceeded = status != nsEventStatus_eConsumeNoDefault &&
-                  mDispatcher && mDispatcher->IsComposing();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextInputProcessor::SetPendingCompositionString(const nsAString& aString)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
   nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
@@ -278,87 +427,176 @@ TextInputProcessor::SetCaretInPendingCom
   nsresult rv = IsValidStateForComposition();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return mDispatcher->SetCaretInPendingComposition(aOffset, 0);
 }
 
 NS_IMETHODIMP
-TextInputProcessor::FlushPendingComposition(bool* aSucceeded)
+TextInputProcessor::FlushPendingComposition(nsIDOMKeyEvent* aDOMKeyEvent,
+                                            uint32_t aKeyFlags,
+                                            uint8_t aOptionalArgc,
+                                            bool* aSucceeded)
 {
   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+  // Even if this doesn't flush pending composition actually, we need to reset
+  // pending composition for starting next composition with new user input.
+  AutoPendingCompositionResetter resetter(this);
+
   *aSucceeded = false;
   nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
-  nsresult rv = IsValidStateForComposition();
+  bool wasComposing = IsComposing();
+
+  WidgetKeyboardEvent* keyboardEvent;
+  nsresult rv =
+    PrepareKeyboardEventForComposition(aDOMKeyEvent, aKeyFlags, aOptionalArgc,
+                                       keyboardEvent);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  nsEventStatus status = nsEventStatus_eIgnore;
-  rv = mDispatcher->FlushPendingComposition(status);
+
+  EventDispatcherResult dispatcherResult =
+    MaybeDispatchKeydownForComposition(keyboardEvent, aKeyFlags);
+  if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
+      !dispatcherResult.mCanContinue) {
+    return dispatcherResult.mResult;
+  }
+
+  // Even if the preceding keydown event was consumed, if the composition
+  // was already started, we shouldn't prevent the change of composition.
+  if (dispatcherResult.mDoDefault || wasComposing) {
+    // Preceding keydown event may cause destroying the widget.
+    if (NS_FAILED(IsValidStateForComposition())) {
+      return NS_OK;
+    }
+    nsEventStatus status = nsEventStatus_eIgnore;
+    rv = mDispatcher->FlushPendingComposition(status);
+    *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
+  }
+
+  MaybeDispatchKeyupForComposition(keyboardEvent, aKeyFlags);
+
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
-  return rv;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
-TextInputProcessor::CommitComposition(const nsAString& aCommitString,
+TextInputProcessor::CommitComposition(nsIDOMKeyEvent* aDOMKeyEvent,
+                                      uint32_t aKeyFlags,
+                                      const nsAString& aCommitString,
                                       uint8_t aOptionalArgc,
                                       bool* aSucceeded)
 {
   MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+
+  WidgetKeyboardEvent* keyboardEvent;
+  nsresult rv =
+    PrepareKeyboardEventForComposition(aDOMKeyEvent, aKeyFlags, aOptionalArgc,
+                                       keyboardEvent);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   const nsAString* commitString =
-    aOptionalArgc >= 1 ? &aCommitString : nullptr;
-  return CommitCompositionInternal(commitString, aSucceeded);
+    aOptionalArgc < 3 ? nullptr: &aCommitString;
+
+  return CommitCompositionInternal(keyboardEvent, aKeyFlags,
+                                   commitString, aSucceeded);
 }
 
 nsresult
-TextInputProcessor::CommitCompositionInternal(const nsAString* aCommitString,
-                                              bool* aSucceeded)
+TextInputProcessor::CommitCompositionInternal(
+                      const WidgetKeyboardEvent* aKeyboardEvent,
+                      uint32_t aKeyFlags,
+                      const nsAString* aCommitString,
+                      bool* aSucceeded)
 {
   if (aSucceeded) {
     *aSucceeded = false;
   }
   nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
-  nsresult rv = IsValidStateForComposition();
+  bool wasComposing = IsComposing();
+
+  EventDispatcherResult dispatcherResult =
+    MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags);
+  if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
+      !dispatcherResult.mCanContinue) {
+    return dispatcherResult.mResult;
+  }
+
+  // Even if the preceding keydown event was consumed, if the composition
+  // was already started, we shouldn't prevent the commit of composition.
+  nsresult rv = NS_OK;
+  if (dispatcherResult.mDoDefault || wasComposing) {
+    // Preceding keydown event may cause destroying the widget.
+    if (NS_FAILED(IsValidStateForComposition())) {
+      return NS_OK;
+    }
+    nsEventStatus status = nsEventStatus_eIgnore;
+    rv = mDispatcher->CommitComposition(status, aCommitString);
+    if (aSucceeded) {
+      *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
+    }
+  }
+
+  MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags);
+
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  nsEventStatus status = nsEventStatus_eIgnore;
-  rv = mDispatcher->CommitComposition(status, aCommitString);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-  if (aSucceeded) {
-    *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
-  }
-  return rv;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
-TextInputProcessor::CancelComposition()
+TextInputProcessor::CancelComposition(nsIDOMKeyEvent* aDOMKeyEvent,
+                                      uint32_t aKeyFlags,
+                                      uint8_t aOptionalArgc)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
-  return CancelCompositionInternal();
+
+  WidgetKeyboardEvent* keyboardEvent;
+  nsresult rv =
+    PrepareKeyboardEventForComposition(aDOMKeyEvent, aKeyFlags, aOptionalArgc,
+                                       keyboardEvent);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return CancelCompositionInternal(keyboardEvent, aKeyFlags);
 }
 
 nsresult
-TextInputProcessor::CancelCompositionInternal()
+TextInputProcessor::CancelCompositionInternal(
+                      const WidgetKeyboardEvent* aKeyboardEvent,
+                      uint32_t aKeyFlags)
 {
   nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
-  nsresult rv = IsValidStateForComposition();
+
+  EventDispatcherResult dispatcherResult =
+    MaybeDispatchKeydownForComposition(aKeyboardEvent, aKeyFlags);
+  if (NS_WARN_IF(NS_FAILED(dispatcherResult.mResult)) ||
+      !dispatcherResult.mCanContinue) {
+    return dispatcherResult.mResult;
+  }
+
+  nsEventStatus status = nsEventStatus_eIgnore;
+  nsresult rv = mDispatcher->CommitComposition(status, &EmptyString());
+
+  MaybeDispatchKeyupForComposition(aKeyboardEvent, aKeyFlags);
+
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  nsEventStatus status = nsEventStatus_eIgnore;
-  return mDispatcher->CommitComposition(status, &EmptyString());
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
                               const IMENotification& aNotification)
 {
   // If This is called while this is being initialized, ignore the call.
   if (!mDispatcher) {
@@ -490,33 +728,43 @@ TextInputProcessor::Keydown(nsIDOMKeyEve
                             uint8_t aOptionalArgc,
                             bool* aDoDefault)
 {
   MOZ_RELEASE_ASSERT(aDoDefault, "aDoDefault must not be nullptr");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
   if (!aOptionalArgc) {
     aKeyFlags = 0;
   }
-  *aDoDefault = false;
   if (NS_WARN_IF(!aDOMKeyEvent)) {
     return NS_ERROR_INVALID_ARG;
   }
-
   WidgetKeyboardEvent* originalKeyEvent =
     aDOMKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
   if (NS_WARN_IF(!originalKeyEvent)) {
     return NS_ERROR_INVALID_ARG;
   }
+  return KeydownInternal(*originalKeyEvent, aKeyFlags, true, *aDoDefault);
+}
+
+nsresult
+TextInputProcessor::KeydownInternal(const WidgetKeyboardEvent& aKeyboardEvent,
+                                    uint32_t aKeyFlags,
+                                    bool aAllowToDispatchKeypress,
+                                    bool& aDoDefault)
+{
+  aDoDefault = false;
+
   // We shouldn't modify the internal WidgetKeyboardEvent.
-  WidgetKeyboardEvent keyEvent(*originalKeyEvent);
+  WidgetKeyboardEvent keyEvent(aKeyboardEvent);
   nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  *aDoDefault = !(aKeyFlags & KEY_DEFAULT_PREVENTED);
+
+  aDoDefault = !(aKeyFlags & KEY_DEFAULT_PREVENTED);
 
   if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) {
     ModifierKeyData modifierKeyData(keyEvent);
     if (WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) {
       // If the modifier key is lockable modifier key such as CapsLock,
       // let's toggle modifier key state at keydown.
       ToggleModifierKey(modifierKeyData);
     } else {
@@ -533,58 +781,69 @@ TextInputProcessor::Keydown(nsIDOMKeyEve
   keyEvent.modifiers = GetActiveModifiers();
 
   nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
   rv = IsValidStateForComposition();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsEventStatus status = *aDoDefault ? nsEventStatus_eIgnore :
-                                       nsEventStatus_eConsumeNoDefault;
+  nsEventStatus status = aDoDefault ? nsEventStatus_eIgnore :
+                                      nsEventStatus_eConsumeNoDefault;
   if (!mDispatcher->DispatchKeyboardEvent(NS_KEY_DOWN, keyEvent, status)) {
     // If keydown event isn't dispatched, we don't need to dispatch keypress
     // events.
     return NS_OK;
   }
 
-  mDispatcher->MaybeDispatchKeypressEvents(keyEvent, status);
+  if (aAllowToDispatchKeypress) {
+    mDispatcher->MaybeDispatchKeypressEvents(keyEvent, status);
+  }
 
-  *aDoDefault = (status != nsEventStatus_eConsumeNoDefault);
+  aDoDefault = (status != nsEventStatus_eConsumeNoDefault);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextInputProcessor::Keyup(nsIDOMKeyEvent* aDOMKeyEvent,
                           uint32_t aKeyFlags,
                           uint8_t aOptionalArgc,
                           bool* aDoDefault)
 {
   MOZ_RELEASE_ASSERT(aDoDefault, "aDoDefault must not be nullptr");
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
   if (!aOptionalArgc) {
     aKeyFlags = 0;
   }
-  *aDoDefault = false;
   if (NS_WARN_IF(!aDOMKeyEvent)) {
     return NS_ERROR_INVALID_ARG;
   }
-
   WidgetKeyboardEvent* originalKeyEvent =
     aDOMKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
   if (NS_WARN_IF(!originalKeyEvent)) {
     return NS_ERROR_INVALID_ARG;
   }
+  return KeyupInternal(*originalKeyEvent, aKeyFlags, *aDoDefault);
+}
+
+nsresult
+TextInputProcessor::KeyupInternal(const WidgetKeyboardEvent& aKeyboardEvent,
+                                  uint32_t aKeyFlags,
+                                  bool& aDoDefault)
+{
+  aDoDefault = false;
+
   // We shouldn't modify the internal WidgetKeyboardEvent.
-  WidgetKeyboardEvent keyEvent(*originalKeyEvent);
+  WidgetKeyboardEvent keyEvent(aKeyboardEvent);
   nsresult rv = PrepareKeyboardEventToDispatch(keyEvent, aKeyFlags);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  *aDoDefault = !(aKeyFlags & KEY_DEFAULT_PREVENTED);
+
+  aDoDefault = !(aKeyFlags & KEY_DEFAULT_PREVENTED);
 
   if (WidgetKeyboardEvent::GetModifierForKeyName(keyEvent.mKeyNameIndex)) {
     if (!WidgetKeyboardEvent::IsLockableModifier(keyEvent.mKeyNameIndex)) {
       // Inactivate modifier flag before dispatching keyup event (i.e., keyup
       // event shouldn't indicate the releasing modifier is active.
       InactivateModifierKey(ModifierKeyData(keyEvent));
     }
     if (aKeyFlags & KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) {
@@ -596,20 +855,20 @@ TextInputProcessor::Keyup(nsIDOMKeyEvent
   keyEvent.modifiers = GetActiveModifiers();
 
   nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
   rv = IsValidStateForComposition();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsEventStatus status = *aDoDefault ? nsEventStatus_eIgnore :
-                                       nsEventStatus_eConsumeNoDefault;
+  nsEventStatus status = aDoDefault ? nsEventStatus_eIgnore :
+                                      nsEventStatus_eConsumeNoDefault;
   mDispatcher->DispatchKeyboardEvent(NS_KEY_UP, keyEvent, status);
-  *aDoDefault = (status != nsEventStatus_eConsumeNoDefault);
+  aDoDefault = (status != nsEventStatus_eConsumeNoDefault);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextInputProcessor::GetModifierState(const nsAString& aModifierKeyName,
                                      bool* aActive)
 {
   MOZ_RELEASE_ASSERT(aActive, "aActive must not be null");
@@ -636,16 +895,34 @@ TextInputProcessor::ShareModifierStateOf
   if (!other->mModifierKeyDataArray) {
     other->mModifierKeyDataArray = new ModifierKeyDataArray();
   }
   mModifierKeyDataArray = other->mModifierKeyDataArray;
   return NS_OK;
 }
 
 /******************************************************************************
+ * TextInputProcessor::AutoPendingCompositionResetter
+ ******************************************************************************/
+TextInputProcessor::AutoPendingCompositionResetter::
+  AutoPendingCompositionResetter(TextInputProcessor* aTIP)
+  : mTIP(aTIP)
+{
+  MOZ_RELEASE_ASSERT(mTIP.get(), "mTIP must not be null");
+}
+
+TextInputProcessor::AutoPendingCompositionResetter::
+  ~AutoPendingCompositionResetter()
+{
+  if (mTIP->mDispatcher) {
+    mTIP->mDispatcher->ClearPendingComposition();
+  }
+}
+
+/******************************************************************************
  * TextInputProcessor::ModifierKeyData
  ******************************************************************************/
 TextInputProcessor::ModifierKeyData::ModifierKeyData(
   const WidgetKeyboardEvent& aKeyboardEvent)
   : mKeyNameIndex(aKeyboardEvent.mKeyNameIndex)
   , mCodeNameIndex(aKeyboardEvent.mCodeNameIndex)
 {
   mModifier = WidgetKeyboardEvent::GetModifierForKeyName(mKeyNameIndex);
--- a/dom/base/TextInputProcessor.h
+++ b/dom/base/TextInputProcessor.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_textinputprocessor_h_
 #define mozilla_dom_textinputprocessor_h_
 
+#include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/TextEventDispatcherListener.h"
 #include "nsAutoPtr.h"
 #include "nsITextInputProcessor.h"
 #include "nsITextInputProcessorCallback.h"
 #include "nsTArray.h"
 
 namespace mozilla {
@@ -36,28 +37,82 @@ public:
                        const IMENotification& aNotification) MOZ_OVERRIDE;
   NS_IMETHOD_(void)
     OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) MOZ_OVERRIDE;
 
 protected:
   virtual ~TextInputProcessor();
 
 private:
+  bool IsComposing() const;
   nsresult BeginInputTransactionInternal(
              nsIDOMWindow* aWindow,
              nsITextInputProcessorCallback* aCallback,
              bool aForTests,
              bool& aSucceeded);
-  nsresult CommitCompositionInternal(const nsAString* aCommitString = nullptr,
-                                     bool* aSucceeded = nullptr);
-  nsresult CancelCompositionInternal();
+  nsresult CommitCompositionInternal(
+             const WidgetKeyboardEvent* aKeyboardEvent = nullptr,
+             uint32_t aKeyFlags = 0,
+             const nsAString* aCommitString = nullptr,
+             bool* aSucceeded = nullptr);
+  nsresult CancelCompositionInternal(
+             const WidgetKeyboardEvent* aKeyboardEvent = nullptr,
+             uint32_t aKeyFlags = 0);
+  nsresult KeydownInternal(const WidgetKeyboardEvent& aKeyboardEvent,
+                           uint32_t aKeyFlags,
+                           bool aAllowToDispatchKeypress,
+                           bool& aDoDefault);
+  nsresult KeyupInternal(const WidgetKeyboardEvent& aKeyboardEvent,
+                         uint32_t aKeyFlags,
+                         bool& aDoDefault);
   nsresult IsValidStateForComposition();
   void UnlinkFromTextEventDispatcher();
   nsresult PrepareKeyboardEventToDispatch(WidgetKeyboardEvent& aKeyboardEvent,
                                           uint32_t aKeyFlags);
+  bool IsValidEventTypeForComposition(
+         const WidgetKeyboardEvent& aKeyboardEvent) const;
+  nsresult PrepareKeyboardEventForComposition(
+             nsIDOMKeyEvent* aDOMKeyEvent,
+             uint32_t& aKeyFlags,
+             uint8_t aOptionalArgc,
+             WidgetKeyboardEvent*& aKeyboardEvent);
+
+  struct EventDispatcherResult
+  {
+    nsresult mResult;
+    bool     mDoDefault;
+    bool     mCanContinue;
+
+    EventDispatcherResult()
+      : mResult(NS_OK)
+      , mDoDefault(true)
+      , mCanContinue(true)
+    {
+    }
+  };
+  EventDispatcherResult MaybeDispatchKeydownForComposition(
+                          const WidgetKeyboardEvent* aKeyboardEvent,
+                          uint32_t aKeyFlags);
+  EventDispatcherResult MaybeDispatchKeyupForComposition(
+                          const WidgetKeyboardEvent* aKeyboardEvent,
+                          uint32_t aKeyFlags);
+
+  /**
+   * AutoPendingCompositionResetter guarantees to clear all pending composition
+   * data in its destructor.
+   */
+  class MOZ_STACK_CLASS AutoPendingCompositionResetter
+  {
+  public:
+    explicit AutoPendingCompositionResetter(TextInputProcessor* aTIP);
+    ~AutoPendingCompositionResetter();
+
+  private:
+    nsRefPtr<TextInputProcessor> mTIP;
+  };
 
   /**
    * TextInputProcessor manages modifier state both with .key and .code.
    * For example, left shift key up shouldn't cause inactivating shift state
    * while right shift key is being pressed.
    */
   struct ModifierKeyData
   {
--- a/dom/base/test/chrome/window_nsITextInputProcessor.xul
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xul
@@ -250,52 +250,52 @@ function runBeginInputTransactionMethodT
      description + "text, compositionend and input events should be fired by TIP1.commitComposition()");
   is(events[0].type, "text",
      description + "events[0] should be text");
   is(events[1].type, "compositionend",
      description + "events[1] should be compositionend");
   is(events[2].type, "input",
      description + "events[2] should be input");
 
-  // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition("bar").
+  // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition(null, 0, "bar").
   events = [];
   input.addEventListener("compositionstart", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransaction(window, simpleCallback),
-       description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   input.addEventListener("compositionupdate", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransaction(window, simpleCallback),
-       description + "TIP2 shouldn't be able to begin input transaction during compositionupdate event handler TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction during compositionupdate event handler TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   input.addEventListener("text", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransaction(window, simpleCallback),
-       description + "TIP2 shouldn't be able to begin input transaction during text event handler TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction during text event handler TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   input.addEventListener("compositionend", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransaction(window, simpleCallback),
-       description + "TIP2 shouldn't be able to begin input transaction during compositionend event handler TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction during compositionend event handler TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   input.addEventListener("input", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransaction(window, simpleCallback),
-       description + "TIP2 shouldn't be able to begin input transaction during input event handler TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction during input event handler TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   TIP1.beginInputTransaction(window, simpleCallback);
-  TIP1.commitComposition("bar");
+  TIP1.commitComposition(null, 0, "bar");
   is(events.length, 5,
-     description + "compositionstart, compositionupdate, text, compositionend and input events should be fired by TIP1.commitComposition(\"bar\")");
+     description + "compositionstart, compositionupdate, text, compositionend and input events should be fired by TIP1.commitComposition(null, 0, \"bar\")");
   is(events[0].type, "compositionstart",
      description + "events[0] should be compositionstart");
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "compositionend",
      description + "events[3] should be compositionend");
@@ -470,52 +470,52 @@ function runBeginInputTransactionMethodT
      description + "text, compositionend and input events should be fired by TIP1.commitComposition()");
   is(events[0].type, "text",
      description + "events[0] should be text");
   is(events[1].type, "compositionend",
      description + "events[1] should be compositionend");
   is(events[2].type, "input",
      description + "events[2] should be input");
 
-  // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition("bar").
+  // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition(null, 0, "bar").
   events = [];
   input.addEventListener("compositionstart", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransactionForTests(window),
-       description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   input.addEventListener("compositionupdate", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransactionForTests(window),
-       description + "TIP2 shouldn't be able to begin input transaction for tests during compositionupdate event handler TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction for tests during compositionupdate event handler TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   input.addEventListener("text", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransactionForTests(window),
-       description + "TIP2 shouldn't be able to begin input transaction for tests during text event handler TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction for tests during text event handler TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   input.addEventListener("compositionend", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransactionForTests(window),
-       description + "TIP2 shouldn't be able to begin input transaction for tests during compositionend event handler TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction for tests during compositionend event handler TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   input.addEventListener("input", function (aEvent) {
     events.push(aEvent);
     input.removeEventListener(aEvent.type, arguments.callee, false);
     ok(!TIP2.beginInputTransactionForTests(window),
-       description + "TIP2 shouldn't be able to begin input transaction for tests during input event handler TIP1.commitComposition(\"bar\");");
+       description + "TIP2 shouldn't be able to begin input transaction for tests during input event handler TIP1.commitComposition(null, 0, \"bar\");");
   }, false);
   TIP1.beginInputTransactionForTests(window);
-  TIP1.commitComposition("bar");
+  TIP1.commitComposition(null, 0, "bar");
   is(events.length, 5,
-     description + "compositionstart, compositionupdate, text, compositionend and input events should be fired by TIP1.commitComposition(\"bar\")");
+     description + "compositionstart, compositionupdate, text, compositionend and input events should be fired by TIP1.commitComposition(null, 0, \"bar\")");
   is(events[0].type, "compositionstart",
      description + "events[0] should be compositionstart");
   is(events[1].type, "compositionupdate",
      description + "events[1] should be compositionupdate");
   is(events[2].type, "text",
      description + "events[2] should be text");
   is(events[3].type, "compositionend",
      description + "events[3] should be compositionend");
@@ -640,29 +640,29 @@ function runBeginInputTransactionMethodT
   } catch (e) {
     ok(e.message.contains("NS_ERROR_NOT_INITIALIZED"),
        description + "TIP1.flushPendingComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
   } finally {
     is(input.value, "",
        description + "The input element should not have commit string");
   }
 
-  // Let's check if commitComposition("bar") throws an exception after ownership is stolen.
+  // Let's check if commitComposition(null, 0, "bar") throws an exception after ownership is stolen.
   ok(TIP1.beginInputTransactionForTests(window),
      description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
   ok(TIP2.beginInputTransactionForTests(window),
      description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition");
   input.value = "";
   try {
-    TIP1.commitComposition("bar");
+    TIP1.commitComposition(null, 0, "bar");
     ok(false,
-       description + "TIP1.commitComposition(\"bar\") should cause throwing an exception because TIP2 took the ownership");
+       description + "TIP1.commitComposition(null, 0, \"bar\") should cause throwing an exception because TIP2 took the ownership");
   } catch (e) {
     ok(e.message.contains("NS_ERROR_NOT_INITIALIZED"),
-       description + "TIP1.commitComposition(\"bar\") should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+       description + "TIP1.commitComposition(null, 0, \"bar\") should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
   } finally {
     is(input.value, "",
        description + "The input element should not have commit string");
   }
 
   // Let's check if keydown() throws an exception after ownership is stolen.
   ok(TIP1.beginInputTransactionForTests(window),
      description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition");
@@ -946,17 +946,17 @@ function runCompositionTests()
      description + "modifying composition string from empty string should cause compositionupdate");
   is(events[0].data, "buzz",
      description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data");
   is(input.value, "FOobuzz",
      description + "new composition string should be appended to the focused editor");
 
   // Committing with different string
   reset();
-  TIP.commitComposition("bar");
+  TIP.commitComposition(null, 0, "bar");
   is(events.length, 2,
      description + "committing with different string should cause compositionupdate and compositionend");
   is(events[0].type, "compositionupdate",
      description + "committing with different string should cause compositionupdate first");
   is(events[0].data, "bar",
      description + "compositionupdate caused by committing with different string should have the committing string in its data");
   is(events[1].type, "compositionend",
      description + "committing with different string should cause compositionend after compositionupdate");
@@ -969,29 +969,29 @@ function runCompositionTests()
   TIP.setPendingCompositionString("buzz");
   TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
   TIP.flushPendingComposition();
   is(input.value, "FOobarbuzz",
      description + "new composition string should be appended to the focused editor");
 
   // Committing with same string
   reset();
-  TIP.commitComposition("buzz");
+  TIP.commitComposition(null, 0, "buzz");
   is(events.length, 1,
      description + "committing with same string should cause only compositionend");
   is(events[0].type, "compositionend",
      description + "committing with same string should cause compositionend");
   is(events[0].data, "buzz",
      description + "compositionend caused by committing with same string should have the committing string in its data");
   is(input.value, "FOobarbuzz",
      description + "new committed string should be appended to the focused editor");
 
   // Inserting commit string directly
   reset();
-  TIP.commitComposition("boo!");
+  TIP.commitComposition(null, 0, "boo!");
   is(events.length, 3,
      description + "committing text directly should cause compositionstart, compositionupdate and compositionend");
   is(events[0].type, "compositionstart",
      description + "committing text directly should cause compositionstart first");
   is(events[1].type, "compositionupdate",
      description + "committing text directly should cause compositionupdate after compositionstart");
   is(events[1].data, "boo!",
      description + "compositionupdate caused by committing text directly should have the committing text in its data");
@@ -1002,16 +1002,566 @@ function runCompositionTests()
   is(input.value, "FOobarbuzzboo!",
      description + "committing text directly should append the committing text to the focused editor");
 
   window.removeEventListener("compositionstart", handler, false);
   window.removeEventListener("compositionupdate", handler, false);
   window.removeEventListener("compositionend", handler, false);
 }
 
+function runCompositionWithKeyEventTests()
+{
+  var description = "runCompositionWithKeyEventTests(): ";
+
+  var TIP = createTIP();
+  ok(TIP.beginInputTransactionForTests(window),
+     description + "TIP.beginInputTransactionForTests() should succeed");
+
+  var events;
+
+  function reset()
+  {
+    events = [];
+  }
+
+  function handler(aEvent)
+  {
+    events.push(aEvent);
+  }
+
+  window.addEventListener("compositionstart", handler, false);
+  window.addEventListener("compositionupdate", handler, false);
+  window.addEventListener("compositionend", handler, false);
+  window.addEventListener("keydown", handler, false);
+  window.addEventListener("keypress", handler, false);
+  window.addEventListener("keyup", handler, false);
+
+  input.value = "";
+  input.focus();
+
+  var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+  var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+  var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+  var convertKeyEvent = new KeyboardEvent("", { key: "Convert", code: "Convert", keyCode: KeyboardEvent.DOM_VK_CONVERT });
+  var backspaceKeyEvent = new KeyboardEvent("", { key: "Backspace", code: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE });
+
+  SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+
+  // nsITextInputProcessor.startComposition()
+  reset();
+  TIP.startComposition(printableKeyEvent);
+  is(events.length, 2,
+     description + "startComposition(printableKeyEvent) should cause keydown and compositionstart");
+  is(events[0].type, "keydown",
+     description + "startComposition(printableKeyEvent) should cause keydown");
+  is(events[1].type, "compositionstart",
+     description + "startComposition(printableKeyEvent) should cause compositionstart");
+  is(input.value, "",
+     description + "startComposition(printableKeyEvent) shouldn't modify the focused editor");
+
+  // Setting composition string "foo" as a raw clause
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+
+  reset();
+  TIP.flushPendingComposition(printableKeyEvent);
+  is(events.length, 1,
+     description + "flushPendingComposition(KeyupInternal) after startComposition() should cause compositionupdate");
+  is(events[0].type, "compositionupdate",
+     description + "flushPendingComposition(KeyupInternal) after startComposition() should cause compositionupdate");
+  is(events[0].data, "foo",
+     description + "compositionupdate caused by flushPendingComposition(KeyupInternal) should have new composition string in its data");
+  is(input.value, "foo",
+     description + "modifying composition string should cause modifying the focused editor");
+
+  // Changing the raw clause to a selected clause
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE);
+
+  reset();
+  TIP.flushPendingComposition(convertKeyEvent);
+  is(events.length, 0,
+     description + "flushPendingComposition(convertKeyEvent) changing only clause information shouldn't cause compositionupdate");
+  is(input.value, "foo",
+     description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+  // Separating the selected clause to two clauses
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+  TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+  TIP.setCaretInPendingComposition(2);
+
+  reset();
+  TIP.flushPendingComposition(convertKeyEvent);
+  is(events.length, 0,
+     description + "flushPendingComposition(convertKeyEvent) separating a clause information shouldn't cause compositionupdate");
+  is(input.value, "foo",
+     description + "separating composition clause shouldn't cause modifying the focused editor");
+
+  // Modifying the composition string
+  TIP.setPendingCompositionString("FOo");
+  TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+  TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+  TIP.setCaretInPendingComposition(2);
+
+  reset();
+  TIP.flushPendingComposition(convertKeyEvent);
+  is(events.length, 1,
+     description + "flushPendingComposition(convertKeyEvent) causing modifying composition string should cause compositionupdate");
+  is(events[0].type, "compositionupdate",
+     description + "flushPendingComposition(convertKeyEvent) causing modifying composition string should cause compositionupdate");
+  is(events[0].data, "FOo",
+     description + "compositionupdate caused by flushPendingComposition(convertKeyEvent) should have new composition string in its data");
+  is(input.value, "FOo",
+     description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+  // Committing the composition string
+  reset();
+  TIP.commitComposition(enterKeyEvent);
+  is(events.length, 2,
+     description + "commitComposition(enterKeyEvent) should cause compositionend and keyup but shoudn't cause compositionupdate");
+  is(events[0].type, "compositionend",
+     description + "commitComposition(enterKeyEvent) should cause compositionend");
+  is(events[0].data, "FOo",
+     description + "compositionend caused by commitComposition(enterKeyEvent) should have the committed string in its data");
+  is(events[1].type, "keyup",
+     description + "commitComposition(enterKeyEvent) should cause keyup");
+  is(input.value, "FOo",
+     description + "commitComposition(enterKeyEvent) shouldn't cause modifying the focused editor");
+
+  // Starting new composition without a call of startComposition()
+  TIP.setPendingCompositionString("bar");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+
+  reset();
+  TIP.flushPendingComposition(printableKeyEvent);
+  is(events.length, 3,
+     description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause both compositionstart and compositionupdate");
+  is(events[0].type, "keydown",
+     description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause keydown");
+  is(events[1].type, "compositionstart",
+     description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause compositionstart");
+  is(events[2].type, "compositionupdate",
+     description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause compositionupdate after compositionstart");
+  is(events[2].data, "bar",
+     description + "compositionupdate caused by flushPendingComposition(printableKeyEvent) without a call of startComposition() should have the composition string in its data");
+  is(input.value, "FOobar",
+     description + "new composition string should cause appending composition string to the focused editor");
+
+  // Canceling the composition
+  reset();
+  TIP.cancelComposition(escKeyEvent);
+  is(events.length, 3,
+     description + "cancelComposition(escKeyEvent) should cause both compositionupdate and compositionend");
+  is(events[0].type, "compositionupdate",
+     description + "cancelComposition(escKeyEvent) should cause compositionupdate");
+  is(events[0].data, "",
+     description + "compositionupdate caused by cancelComposition(escKeyEvent) should have empty string in its data");
+  is(events[1].type, "compositionend",
+     description + "cancelComposition(escKeyEvent) should cause compositionend after compositionupdate");
+  is(events[1].data, "",
+     description + "compositionend caused by cancelComposition(escKeyEvent) should have empty string in its data");
+  is(events[2].type, "keyup",
+     description + "cancelComposition(escKeyEvent) should cause keyup after compositionend");
+  is(input.value, "FOo",
+     description + "canceled composition string should be removed from the focused editor");
+
+  // Starting composition explicitly and canceling it
+  reset();
+  TIP.startComposition(printableKeyEvent);
+  TIP.cancelComposition(escKeyEvent);
+  is(events.length, 4,
+     description + "canceling composition immediately after startComposition() should cause keydown, compositionstart, compositionend and keyup");
+  is(events[0].type, "keydown",
+     description + "canceling composition immediately after startComposition() should cause keydown first");
+  is(events[1].type, "compositionstart",
+     description + "canceling composition immediately after startComposition() should cause compositionstart after keydown");
+  is(events[2].type, "compositionend",
+     description + "canceling composition immediately after startComposition() should cause compositionend after compositionstart");
+  is(events[2].data, "",
+     description + "compositionend caused by canceling composition should have empty string in its data");
+  is(events[3].type, "keyup",
+     description + "canceling composition immediately after startComposition() should cause keyup after compositionend");
+  is(input.value, "FOo",
+     description + "canceling composition shouldn't modify the focused editor");
+
+  // Create composition for next test.
+  TIP.setPendingCompositionString("bar");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.flushPendingComposition();
+  is(input.value, "FOobar",
+     description + "The focused editor should have new composition string \"bar\"");
+
+  // Allow to set empty composition string
+  reset();
+  TIP.flushPendingComposition(backspaceKeyEvent);
+  is(events.length, 1,
+     description + "making composition string empty should cause only compositionupdate");
+  is(events[0].type, "compositionupdate",
+     description + "making composition string empty should cause compositionupdate");
+  is(events[0].data, "",
+     description + "compositionupdate caused by making composition string empty should have empty string in its data");
+
+  // Allow to insert new composition string without compositionend/compositionstart
+  TIP.setPendingCompositionString("buzz");
+  TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+
+  reset();
+  TIP.flushPendingComposition(printableKeyEvent);
+  is(events.length, 1,
+     description + "modifying composition string from empty string should cause only compositionupdate");
+  is(events[0].type, "compositionupdate",
+     description + "modifying composition string from empty string should cause compositionupdate");
+  is(events[0].data, "buzz",
+     description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data");
+  is(input.value, "FOobuzz",
+     description + "new composition string should be appended to the focused editor");
+
+  // Committing with different string
+  reset();
+  TIP.commitComposition(printableKeyEvent, 0, "bar");
+  is(events.length, 3,
+     description + "committing with different string should cause compositionupdate and compositionend");
+  is(events[0].type, "compositionupdate",
+     description + "committing with different string should cause compositionupdate first");
+  is(events[0].data, "bar",
+     description + "compositionupdate caused by committing with different string should have the committing string in its data");
+  is(events[1].type, "compositionend",
+     description + "committing with different string should cause compositionend after compositionupdate");
+  is(events[1].data, "bar",
+     description + "compositionend caused by committing with different string should have the committing string in its data");
+  is(events[2].type, "keyup",
+     description + "committing with different string should cause keyup after compositionend");
+  is(input.value, "FOobar",
+     description + "new committed string should be appended to the focused editor");
+
+  // Appending new composition string
+  TIP.setPendingCompositionString("buzz");
+  TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+  TIP.flushPendingComposition();
+  is(input.value, "FOobarbuzz",
+     description + "new composition string should be appended to the focused editor");
+
+  // Committing with same string
+  reset();
+  TIP.commitComposition(enterKeyEvent, 0, "buzz");
+  is(events.length, 2,
+     description + "committing with same string should cause only compositionend");
+  is(events[0].type, "compositionend",
+     description + "committing with same string should cause compositionend");
+  is(events[0].data, "buzz",
+     description + "compositionend caused by committing with same string should have the committing string in its data");
+  is(events[1].type, "keyup",
+     description + "committing with same string should cause keyup after compositionend");
+  is(input.value, "FOobarbuzz",
+     description + "new committed string should be appended to the focused editor");
+
+  // Inserting commit string directly
+  reset();
+  TIP.commitComposition(printableKeyEvent, 0, "boo!");
+  is(events.length, 5,
+     description + "committing text directly should cause compositionstart, compositionupdate and compositionend");
+  is(events[0].type, "keydown",
+     description + "committing text directly should cause keydown first");
+  is(events[1].type, "compositionstart",
+     description + "committing text directly should cause compositionstart after keydown");
+  is(events[2].type, "compositionupdate",
+     description + "committing text directly should cause compositionupdate after compositionstart");
+  is(events[2].data, "boo!",
+     description + "compositionupdate caused by committing text directly should have the committing text in its data");
+  is(events[3].type, "compositionend",
+     description + "committing text directly should cause compositionend after compositionupdate");
+  is(events[3].data, "boo!",
+     description + "compositionend caused by committing text directly should have the committing text in its data");
+  is(events[4].type, "keyup",
+     description + "committing text directly should cause keyup after compositionend");
+  is(input.value, "FOobarbuzzboo!",
+     description + "committing text directly should append the committing text to the focused editor");
+
+  SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+
+  // Even if "dom.keyboardevent.dispatch_during_composition" is true, keypress event shouldn't be fired during composition
+  reset();
+  TIP.startComposition(printableKeyEvent);
+  is(events.length, 3,
+     description + "TIP.startComposition(printableKeyEvent) should cause keydown, compositionstart and keyup (keypress event shouldn't be fired during composition)");
+  is(events[0].type, "keydown",
+     description + "TIP.startComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+  is(events[1].type, "compositionstart",
+     description + "TIP.startComposition(printableKeyEvent) should cause compositionstart (keypress event shouldn't be fired during composition)");
+  is(events[2].type, "keyup",
+     description + "TIP.startComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+  // TIP.flushPendingComposition(printableKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+
+  reset();
+  TIP.flushPendingComposition(printableKeyEvent);
+  is(events.length, 3,
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown, compositionupdate and keyup (keypress event shouldn't be fired during composition)");
+  is(events[0].type, "keydown",
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+  is(events[1].type, "compositionupdate",
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate (keypress event shouldn't be fired during composition)");
+  is(events[2].type, "keyup",
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+  // TIP.commitComposition(enterKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true
+  reset();
+  TIP.commitComposition(enterKeyEvent);
+  is(events.length, 3,
+     description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)");
+  is(events[0].type, "keydown",
+     description + "TIP.commitComposition(enterKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+  is(events[1].type, "compositionend",
+     description + "TIP.commitComposition(enterKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)");
+  is(events[2].type, "keyup",
+     description + "TIP.commitComposition(enterKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+  // TIP.cancelComposition(escKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true
+  TIP.startComposition();
+  reset();
+  TIP.cancelComposition(escKeyEvent);
+  is(events.length, 3,
+     description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)");
+  is(events[0].type, "keydown",
+     description + "TIP.cancelComposition(escKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
+  is(events[1].type, "compositionend",
+     description + "TIP.cancelComposition(escKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)");
+  is(events[2].type, "keyup",
+     description + "TIP.cancelComposition(escKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)");
+
+  var printableKeydownEvent = new KeyboardEvent("keydown", { key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B });
+  var enterKeydownEvent = new KeyboardEvent("keydown", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+  var escKeydownEvent = new KeyboardEvent("keydown", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+
+  // TIP.startComposition(printableKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+  reset();
+  TIP.startComposition(printableKeydownEvent);
+  is(events.length, 2,
+     description + "TIP.startComposition(printableKeydownEvent) should cause keydown and compositionstart (keyup event shouldn't be fired)");
+  is(events[0].type, "keydown",
+     description + "TIP.startComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+  is(events[1].type, "compositionstart",
+     description + "TIP.startComposition(printableKeydownEvent) should cause compositionstart (keyup event shouldn't be fired)");
+
+  // TIP.flushPendingComposition(printableKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+
+  reset();
+  TIP.flushPendingComposition(printableKeydownEvent);
+  is(events.length, 2,
+     description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown and compositionupdate (keyup event shouldn't be fired)");
+  is(events[0].type, "keydown",
+     description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+  is(events[1].type, "compositionupdate",
+     description + "TIP.flushPendingComposition(printableKeydownEvent) should cause compositionupdate (keyup event shouldn't be fired)");
+
+  // TIP.commitComposition(enterKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+  reset();
+  TIP.commitComposition(enterKeydownEvent);
+  is(events.length, 2,
+     description + "TIP.commitComposition(enterKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)");
+  is(events[0].type, "keydown",
+     description + "TIP.commitComposition(enterKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+  is(events[1].type, "compositionend",
+     description + "TIP.commitComposition(enterKeydownEvent) should cause compositionend (keyup event shouldn't be fired)");
+
+  // TIP.cancelComposition(escKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true
+  TIP.startComposition();
+  reset();
+  TIP.cancelComposition(escKeydownEvent);
+  is(events.length, 2,
+     description + "TIP.cancelComposition(escKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)");
+  is(events[0].type, "keydown",
+     description + "TIP.cancelComposition(escKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
+  is(events[1].type, "compositionend",
+     description + "TIP.cancelComposition(escKeydownEvent) should cause compositionend (keyup event shouldn't be fired)");
+
+  SpecialPowers.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+  window.removeEventListener("compositionstart", handler, false);
+  window.removeEventListener("compositionupdate", handler, false);
+  window.removeEventListener("compositionend", handler, false);
+  window.removeEventListener("keydown", handler, false);
+  window.removeEventListener("keypress", handler, false);
+  window.removeEventListener("keyup", handler, false);
+}
+
+function runConsumingKeydownBeforeCompositionTests()
+{
+  var description = "runConsumingKeydownBeforeCompositionTests(): ";
+
+  var TIP = createTIP();
+  ok(TIP.beginInputTransactionForTests(window),
+     description + "TIP.beginInputTransactionForTests() should succeed");
+
+  var events;
+
+  function reset()
+  {
+    events = [];
+  }
+
+  function handler(aEvent)
+  {
+    events.push(aEvent);
+    if (aEvent.type == "keydown") {
+      aEvent.preventDefault();
+    }
+  }
+
+  window.addEventListener("compositionstart", handler, false);
+  window.addEventListener("compositionupdate", handler, false);
+  window.addEventListener("compositionend", handler, false);
+  window.addEventListener("keydown", handler, false);
+  window.addEventListener("keypress", handler, false);
+  window.addEventListener("keyup", handler, false);
+
+  input.value = "";
+  input.focus();
+
+  var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+  var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
+  var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
+
+  SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+
+  // If keydown before compositionstart is consumed, composition shouldn't be started.
+  reset();
+  ok(!TIP.startComposition(printableKeyEvent),
+     description + "TIP.startComposition(printableKeyEvent) should return false because it's keydown is consumed");
+  is(events.length, 2,
+     description + "TIP.startComposition(printableKeyEvent) should cause only keydown and keyup events");
+  is(events[0].type, "keydown",
+     description + "TIP.startComposition(printableKeyEvent) should cause keydown event first");
+  is(events[1].type, "keyup",
+     description + "TIP.startComposition(printableKeyEvent) should cause keyup event after keydown");
+  ok(!TIP.hasComposition,
+     description + "TIP.startComposition(printableKeyEvent) shouldn't cause composition");
+  is(input.value, "",
+     description + "TIP.startComposition(printableKeyEvent) shouldn't cause inserting text");
+
+  // If keydown before compositionstart caused by flushPendingComposition(printableKeyEvent) is consumed, composition shouldn't be started.
+  reset();
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  ok(!TIP.flushPendingComposition(printableKeyEvent),
+     description + "TIP.flushPendingComposition(printableKeyEvent) should return false because it's keydown is consumed");
+  is(events.length, 2,
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause only keydown and keyup events");
+  is(events[0].type, "keydown",
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown event first");
+  is(events[1].type, "keyup",
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup event after keydown");
+  ok(!TIP.hasComposition,
+     description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause composition");
+  is(input.value, "",
+     description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause inserting text");
+
+  // If keydown before compositionstart is consumed, composition shouldn't be started.
+  reset();
+  ok(!TIP.commitComposition(printableKeyEvent, 0, "foo"),
+     description + "TIP.commitComposition(printableKeyEvent, 0, \"foo\") should return false because it's keydown is consumed");
+  is(events.length, 2,
+     description + "TIP.commitComposition(printableKeyEvent, 0, \"foo\") should cause only keydown and keyup events");
+  is(events[0].type, "keydown",
+     description + "TIP.commitComposition(printableKeyEvent, 0, \"foo\") should cause keydown event first");
+  is(events[1].type, "keyup",
+     description + "TIP.commitComposition(printableKeyEvent, 0, \"foo\") should cause keyup event after keydown");
+  ok(!TIP.hasComposition,
+     description + "TIP.commitComposition(printableKeyEvent, 0, \"foo\") shouldn't cause composition");
+  is(input.value, "",
+     description + "TIP.commitComposition(printableKeyEvent, 0, \"foo\") shouldn't cause inserting text");
+
+  SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+
+  // If composition is already started, TIP.flushPendingComposition(printableKeyEvent) shouldn't be canceled.
+  TIP.startComposition();
+  ok(TIP.hasComposition,
+     description + "Before TIP.flushPendingComposition(printableKeyEvent), composition should've been created");
+  reset();
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  ok(TIP.flushPendingComposition(printableKeyEvent),
+     description + "TIP.flushPendingComposition(printableKeyEvent) should return true even if preceding keydown is consumed because there was a composition already");
+  is(events.length, 3,
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause only keydown and keyup events");
+  is(events[0].type, "keydown",
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown event first");
+  is(events[1].type, "compositionupdate",
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate event after keydown");
+  is(events[2].type, "keyup",
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup event after compositionupdate");
+  ok(TIP.hasComposition,
+     description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause canceling composition");
+  is(input.value, "foo",
+     description + "TIP.flushPendingComposition(printableKeyEvent) should cause inserting text even if preceding keydown is consumed because there was a composition already");
+
+  // If composition is already started, TIP.commitComposition(enterKeyEvent) shouldn't be canceled.
+  reset();
+  ok(TIP.commitComposition(enterKeyEvent),
+     description + "TIP.commitComposition(enterKeyEvent) should return true even if preceding keydown is consumed because there was a composition already");
+  is(events.length, 3,
+     description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup events");
+  is(events[0].type, "keydown",
+     description + "TIP.commitComposition(enterKeyEvent) should cause keydown event first");
+  is(events[1].type, "compositionend",
+     description + "TIP.commitComposition(enterKeyEvent) should cause compositionend event after keydown");
+  is(events[2].type, "keyup",
+     description + "TIP.commitComposition(enterKeyEvent) should cause keyup event after compositionend");
+  ok(!TIP.hasComposition,
+     description + "TIP.commitComposition(enterKeyEvent) should cause committing composition even if preceding keydown is consumed because there was a composition already");
+  is(input.value, "foo",
+     description + "TIP.commitComposition(enterKeyEvent) should commit composition even if preceding keydown is consumed because there was a composition already");
+
+  // cancelComposition() should work even if preceding keydown event is consumed.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  ok(TIP.hasComposition,
+     description + "Before TIP.cancelComposition(escKeyEvent), composition should've been created");
+  is(input.value, "foo",
+     description + "Before TIP.cancelComposition(escKeyEvent) should have composition string");
+  reset();
+  TIP.cancelComposition(escKeyEvent);
+  is(events.length, 4,
+     description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionupdate, compositionend and keyup events even if preceding keydown is consumed because there was a composition already");
+  is(events[0].type, "keydown",
+     description + "TIP.cancelComposition(escKeyEvent) should cause keydown event first");
+  is(events[1].type, "compositionupdate",
+     description + "TIP.cancelComposition(escKeyEvent) should cause compositionupdate event after keydown");
+  is(events[2].type, "compositionend",
+     description + "TIP.cancelComposition(escKeyEvent) should cause compositionend event after compositionupdate");
+  is(events[3].type, "keyup",
+     description + "TIP.cancelComposition(escKeyEvent) should cause keyup event after compositionend");
+  ok(!TIP.hasComposition,
+     description + "TIP.cancelComposition(escKeyEvent) should cause canceling composition even if preceding keydown is consumed because there was a composition already");
+  is(input.value, "",
+     description + "TIP.cancelComposition(escKeyEvent) should cancel composition even if preceding keydown is consumed because there was a composition already");
+
+  SpecialPowers.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+  window.removeEventListener("compositionstart", handler, false);
+  window.removeEventListener("compositionupdate", handler, false);
+  window.removeEventListener("compositionend", handler, false);
+  window.removeEventListener("keydown", handler, false);
+  window.removeEventListener("keypress", handler, false);
+  window.removeEventListener("keyup", handler, false);
+}
+
 function runKeyTests()
 {
   var description = "runKeyTests(): ";
   const kModifiers =
     [ "Alt", "AltGraph", "CapsLock", "Control", "Fn", "FnLock", "Meta", "NumLock",
       "ScrollLock", "Shift", "Symbol", "SymbolLock", "OS" ];
 
   var TIP = createTIP();
@@ -2129,24 +2679,24 @@ function runErrorTests()
     TIP.commitComposition();
     ok(false,
        description + "commitComposition() should fail if there is no composition");
   } catch (e) {
     ok(e.message.contains("NS_ERROR_FAILURE"),
        description + "commitComposition() should cause NS_ERROR_FAILURE if there is no composition");
   }
 
-  // commitComposition("") should throw an exception if there is no composition
+  // commitComposition(null, 0, "") should throw an exception if there is no composition
   try {
-    TIP.commitComposition("");
+    TIP.commitComposition(null, 0, "");
     ok(false,
-       description + "commitComposition(\"\") should fail if there is no composition");
+       description + "commitComposition(null, 0, \"\") should fail if there is no composition");
   } catch (e) {
     ok(e.message.contains("NS_ERROR_FAILURE"),
-       description + "commitComposition(\"\") should cause NS_ERROR_FAILURE if there is no composition");
+       description + "commitComposition(null, 0, \"\") should cause NS_ERROR_FAILURE if there is no composition");
   }
 
   // Pending composition string should allow to flush without clause information (for compatibility)
   try {
     TIP.setPendingCompositionString("foo");
     TIP.flushPendingComposition();
     ok(true,
        description + "flushPendingComposition() should succeed even if appendClauseToPendingComposition() has never been called");
@@ -2347,16 +2897,91 @@ function runErrorTests()
     var keyEvent = new KeyboardEvent("", { key: "Enter", code: "ShiftLeft" });
     TIP.keyup(keyEvent, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
     ok(false,
        description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should fail if the .key value isn't a modifier key");
   } catch (e) {
     ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
        description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should cause NS_ERROR_ILLEGAL_VALUE if the .key value isn't a modifier key");
   }
+
+  // The type of key events specified to composition methods should be "" or "keydown".
+  var kKeyEventTypes = [
+    { type: "keydown",   valid: true },
+    { type: "keypress",  valid: false },
+    { type: "keyup",     valid: false },
+    { type: "",          valid: true },
+    { type: "mousedown", valid: false },
+    { type: "foo",       valid: false },
+  ];
+  for (var i = 0; i < kKeyEventTypes.length; i++) {
+    var keyEvent =
+      new KeyboardEvent(kKeyEventTypes[i].type, { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+    var testDescription = description + "type=\"" + kKeyEventTypes[i].type + "\", ";
+    try {
+      TIP.startComposition(keyEvent);
+      ok(kKeyEventTypes[i].valid,
+         testDescription + "TIP.startComposition(keyEvent) should not accept the event type");
+      TIP.cancelComposition();
+    } catch (e) {
+      ok(!kKeyEventTypes[i].valid,
+         testDescription + "TIP.startComposition(keyEvent) should not throw an exception for the event type");
+      ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+         testDescription + "TIP.startComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+    }
+    try {
+      TIP.setPendingCompositionString("foo");
+      TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+      TIP.setCaretInPendingComposition(3);
+      TIP.flushPendingComposition(keyEvent);
+      ok(kKeyEventTypes[i].valid,
+         testDescription + "TIP.flushPendingComposition(keyEvent) should not accept the event type");
+      TIP.cancelComposition();
+    } catch (e) {
+      ok(!kKeyEventTypes[i].valid,
+         testDescription + "TIP.flushPendingComposition(keyEvent) should not throw an exception for the event type");
+      ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+         testDescription + "TIP.flushPendingComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+    }
+    try {
+      TIP.startComposition();
+      TIP.commitComposition(keyEvent);
+      ok(kKeyEventTypes[i].valid,
+         testDescription + "TIP.commitComposition(keyEvent) should not accept the event type");
+    } catch (e) {
+      ok(!kKeyEventTypes[i].valid,
+         testDescription + "TIP.commitComposition(keyEvent) should not throw an exception for the event type");
+      ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+         testDescription + "TIP.commitComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+      TIP.cancelComposition();
+    }
+    try {
+      TIP.commitComposition(keyEvent, 0, "foo");
+      ok(kKeyEventTypes[i].valid,
+         testDescription + "TIP.commitComposition(keyEvent, 0, \"foo\") should not accept the event type");
+    } catch (e) {
+      ok(!kKeyEventTypes[i].valid,
+         testDescription + "TIP.commitComposition(keyEvent, 0, \"foo\") should not throw an exception for the event type");
+      ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+         testDescription + "TIP.commitComposition(keyEvent, 0, \"foo\") should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+    }
+    try {
+      TIP.startComposition();
+      TIP.cancelComposition(keyEvent);
+      ok(kKeyEventTypes[i].valid,
+         testDescription + "TIP.cancelComposition(keyEvent) should not accept the event type");
+    } catch (e) {
+      ok(!kKeyEventTypes[i].valid,
+         testDescription + "TIP.cancelComposition(keyEvent) should not throw an exception for the event type");
+      ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+         testDescription + "TIP.cancelComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid");
+      TIP.cancelComposition();
+    }
+    input.value = "";
+  }
 }
 
 function runCommitCompositionTests()
 {
   var description = "runCommitCompositionTests(): ";
 
   var TIP = createTIP();
   ok(TIP.beginInputTransactionForTests(window),
@@ -2369,49 +2994,49 @@ function runCommitCompositionTests()
   TIP.setPendingCompositionString("foo");
   TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   TIP.setCaretInPendingComposition(3);
   TIP.flushPendingComposition();
   TIP.commitComposition();
   is(input.value, "foo",
      description + "commitComposition() should commit the composition with the last data");
 
-  // commitComposition("") should commit the composition with empty string.
+  // commitComposition(null, 0, "") should commit the composition with empty string.
   input.value = "";
   TIP.setPendingCompositionString("foo");
   TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   TIP.setCaretInPendingComposition(3);
   TIP.flushPendingComposition();
-  TIP.commitComposition("");
+  TIP.commitComposition(null, 0, "");
   is(input.value, "",
-     description + "commitComposition(\"\") should commit the composition with empty string");
+     description + "commitComposition(null, 0, \"\") should commit the composition with empty string");
 
-  // commitComposition(null) should commit the composition with empty string.
+  // commitComposition(null, 0, null) should commit the composition with empty string.
   input.value = "";
   TIP.setPendingCompositionString("foo");
   TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   TIP.setCaretInPendingComposition(3);
   TIP.flushPendingComposition();
-  TIP.commitComposition(null);
+  TIP.commitComposition(null, 0, null);
   is(input.value, "",
-     description + "commitComposition(null) should commit the composition with empty string");
+     description + "commitComposition(null, 0, null) should commit the composition with empty string");
 
-  // commitComposition(undefined) should commit the composition with the last data.
+  // commitComposition(null, 0, undefined) should commit the composition with the last data.
   input.value = "";
   TIP.setPendingCompositionString("foo");
   TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   TIP.setCaretInPendingComposition(3);
   TIP.flushPendingComposition();
-  TIP.commitComposition(undefined);
+  TIP.commitComposition(null, 0, undefined);
   todo_is(input.value, "foo",
-          description + "commitComposition(undefined) should commit the composition with the last data");
+          description + "commitComposition(null, 0, undefined) should commit the composition with the last data");
 
   function doCommit(aText)
   {
-    TIP.commitComposition(aText);
+    TIP.commitComposition(null, 0, aText);
   }
 
   // doCommit() should commit the composition with the last data.
   input.value = "";
   TIP.setPendingCompositionString("foo");
   TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   TIP.setCaretInPendingComposition(3);
   TIP.flushPendingComposition();
@@ -2446,17 +3071,17 @@ function runCommitCompositionTests()
   TIP.setCaretInPendingComposition(3);
   TIP.flushPendingComposition();
   doCommit(undefined);
   todo_is(input.value, "foo",
           description + "doCommit(undefined) should commit the composition with the last data");
 
   function doCommitWithNullCheck(aText)
   {
-    TIP.commitComposition(aText ? aText : "");
+    TIP.commitComposition(null, 0, aText ? aText : "");
   }
 
   // doCommitWithNullCheck() should commit the composition with the last data.
   input.value = "";
   TIP.setPendingCompositionString("foo");
   TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
   TIP.setCaretInPendingComposition(3);
   TIP.flushPendingComposition();
@@ -2707,16 +3332,18 @@ function runCallbackTests(aForTests)
 
 function runTests()
 {
   textareaInFrame = iframe.contentDocument.getElementById("textarea");
 
   runBeginInputTransactionMethodTests();
   runReleaseTests();
   runCompositionTests();
+  runCompositionWithKeyEventTests();
+  runConsumingKeydownBeforeCompositionTests();
   runKeyTests();
   runErrorTests();
   runCommitCompositionTests();
   runCallbackTests(false);
   runCallbackTests(true);
   runUnloadTests1(function () {
     runUnloadTests2(function () {
       finish();
--- a/dom/inputmethod/forms.js
+++ b/dom/inputmethod/forms.js
@@ -1302,17 +1302,17 @@ let CompositionManager =  {
     }
     this._isStarted = this._textInputProcessor.flushPendingComposition();
   },
 
   endComposition: function cm_endComposition(text) {
     if (!this._isStarted) {
       return;
     }
-    this._textInputProcessor.commitComposition(text ? text : "");
+    this._textInputProcessor.commitComposition(null, 0, text ? text : "");
     this._isStarted = false;
   },
 
   // Composition ends due to external actions.
   onCompositionEnd: function cm_onCompositionEnd() {
     if (!this._isStarted) {
       return;
     }
--- a/dom/interfaces/base/nsITextInputProcessor.idl
+++ b/dom/interfaces/base/nsITextInputProcessor.idl
@@ -23,92 +23,135 @@ interface nsITextInputProcessorCallback;
  *
  * Example #1 JS-IME can start composition like this:
  *
  *   var TIP = Components.classes["@mozilla.org/text-input-processor;1"].
  *               createInstance(Components.interfaces.nsITextInputProcessor);
  *   if (!TIP.beginInputTransaction(window, callback)) {
  *     return; // You failed to get the rights to make composition
  *   }
+ *   // Create a keyboard event if the following compositionc change is caused
+ *   // by a key event.
+ *   var keyEvent =
+ *     new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz });
  *   // Set new composition string first
  *   TIP.setPendingCompositionString("some-words-are-inputted");
  *   // Set clause information.
  *   TIP.appendClauseToPendingComposition(23, TIP.ATTR_RAW_CLAUSE);
  *   // Set caret position, this is optional.
  *   TIP.setCaretInPendingComposition(23);
  *   // Flush the pending composition
- *   if (!TIP.flushPendingComposition()) {
+ *   if (!TIP.flushPendingComposition(keyEvent)) {
  *     // If it returns false, it fails to start composition.
  *     return;
  *   }
  *
  * Example #2 JS-IME can separate composition string to two or more clauses:
  *
+ *   // Create a keyboard event if the following compositionc change is caused
+ *   // by a key event.
+ *   var keyEvent =
+ *     new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz });
  *   // First, set composition string again
  *   TIP.setPendingCompositionString("some-words-are-inputted");
  *   // Then, if "are" is selected to convert, there are 3 clauses:
  *   TIP.appendClauseToPendingComposition(11, TIP.ATTR_CONVERTED_CLAUSE);
  *   TIP.appendClauseToPendingComposition(3,  TIP.ATTR_SELECTED_CLAUSE);
  *   TIP.appendClauseToPendingComposition(9,  TIP.ATTR_CONVERTED_CLAUSE);
  *   // Show caret at the beginning of the selected clause
  *   TIP.setCaretInPendingComposition(11);
  *   // Flush the pending composition.  Note that if there is a composition,
  *   // flushPendingComposition() won't return false.
- *   TIP.flushPendingComposition();
+ *   TIP.flushPendingComposition(keyEvent);
  *
  * Example #3 JS-IME can commit composition with specific string with this:
  *
+ *   // Create a keyboard event if the following compositionc change is caused
+ *   // by a key event.
+ *   var keyEvent1 =
+ *     new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz });
  *   // First, there is a composition.
  *   TIP.setPendingCompositionString("some-words-directly-inputted");
  *   TIP.appendClauseToPendingComposition(28, TIP.ATTR_RAW_CLAUSE);
- *   TIP.flushPendingComposition();
+ *   TIP.flushPendingComposition(keyEvent1);
+ *   // Create a keyboard event if the following commit composition is caused
+ *   // by a key event.
+ *   var keyEvent2 =
+ *     new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz });
  *   // This is useful when user selects a commit string from candidate list UI
  *   // which is provided by JS-IME.
- *   TIP.commitComposition("selected-words-from-candidate-list");
+ *   TIP.commitComposition(keyEvent2, "selected-words-from-candidate-list");
  *
  * Example #4 JS-IME can commit composition with the last composition string
  *            without specifying commit string:
  *
+ *   // Create a keyboard event if the following compositionc change is caused
+ *   // by a key event.
+ *   var keyEvent1 =
+ *     new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz });
  *   // First, there is a composition.
  *   TIP.setPendingCompositionString("some-words-will-be-commited");
  *   TIP.appendClauseToPendingComposition(27, TIP.ATTR_RAW_CLAUSE);
- *   TIP.flushPendingComposition();
+ *   TIP.flushPendingComposition(keyEvent1);
+ *   // Create a keyboard event if the following commit is caused by a key
+ *   // event.
+ *   var keyEvent2 =
+ *     new KeyboardEvent("", { key: "Enter", code: "Enter",
+                               keyCode: KeyboardEvent.DOM_VK_RETURN });
  *   // This is useful when user just type Enter key.
- *   TIP.commitComposition();
+ *   TIP.commitComposition(keyEvent2);
  *
  * Example #5 JS-IME can cancel composition with this:
  *
+ *   // Create a keyboard event if the following composition change is caused
+ *   // by a key event.
+ *   var keyEvent1 =
+ *     new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz });
  *   // First, there is a composition.
  *   TIP.setPendingCompositionString("some-words-will-be-canceled");
  *   TIP.appendClauseToPendingComposition(27, TIP.ATTR_RAW_CLAUSE);
- *   TIP.flushPendingComposition();
+ *   TIP.flushPendingComposition(keyEvent1);
+ *   // Create a keyboard event if the following canceling composition is
+ *   // caused by a key event.
+ *   var keyEvent2 =
+ *     new KeyboardEvent("", { key: "Escape", code: "Escape",
+                               keyCode: KeyboardEvent.DOM_VK_ESCAPE });
  *   // This is useful when user doesn't want to commit the composition.
  *   // FYI: This is same as TIP.commitComposition("") for now.
- *   TIP.cancelComposition();
+ *   TIP.cancelComposition(keyEvent2);
  *
  * Example #6 JS-IME can insert text only with commitComposition():
  *
+ *   // Create a keyboard event if the following inserting text is caused by a
+ *   // key event.
+ *   var keyEvent1 =
+ *     new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz });
  *   if (!TIP.beginInputTransaction(window, callback)) {
  *     return; // You failed to get the rights to make composition
  *   }
- *   TIP.commitComposition("Some words");
+ *   TIP.commitComposition(keyEvent1, "Some words");
  *
  * Example #7 JS-IME can start composition explicitly:
  *
  *   if (!TIP.beginInputTransaction(window, callback)) {
  *     return; // You failed to get the rights to make composition
  *   }
+ *   // Create a keyboard event if the following starting composition is caused
+ *   // by a key event.
+ *   var keyEvent1 =
+ *     new KeyboardEvent("", { key: "foo", code: "bar", keyCode: buzz });
  *   // If JS-IME don't want to show composing string in the focused editor,
  *   // JS-IME can dispatch only compositionstart event with this.
- *   if (!TIP.startComposition()) {
+ *   if (!TIP.startComposition(keyEvent1)) {
  *     // Failed to start composition.
  *     return;
  *   }
  *   // And when user selects a result from UI of JS-IME, commit with it.
- *   TIP.commitComposition("selected-words");
+ *   // Then, the key event should be null.
+ *   TIP.commitComposition(null, "selected-words");
  *
  * Example #8 JS-IME or JS-Keyboard should dispatch key events even during
  *            composition (non-printable key case):
  *
  *   if (!TIP.beginInputTransaction(window, callback)) {
  *     return; // You failed to get the rights to dispatch key events
  *   }
  *
@@ -206,20 +249,26 @@ interface nsITextInputProcessorCallback;
  *
  *   // keyup of one of shift key doesn't cause inactivating "Shift" state.
  *   TIP.keyup(rightShift);
  *
  *   // This causes inactivating "Shift" state completely.
  *   TIP.keyup(leftShift);
  */
 
-[scriptable, builtinclass, uuid(bb19b843-dbe2-41c5-8485-794121351d3a)]
+[scriptable, builtinclass, uuid(9c3e5c81-52ca-4087-b708-cbfcbac2a055)]
 interface nsITextInputProcessor : nsISupports
 {
   /**
+   * Returns true if this instance was dispatched compositionstart but hasn't
+   * dispatched compositionend yet.
+   */
+  readonly attribute boolean hasComposition;
+
+  /**
    * When you create an instance, you must call beginInputTransaction() first
    * except when you created the instance for automated tests.
    *
    * @param aWindow         A DOM window.  The instance will look for a top
    *                        level widget from this.
    * @param aCallback       Callback interface which handles requests to
    *                        IME and notifications to IME.  This must not be
    *                        null.
@@ -247,21 +296,28 @@ interface nsITextInputProcessor : nsISup
   /**
    * startComposition() dispatches compositionstart event explicitly.
    * IME does NOT need to call this typically since compositionstart event
    * is automatically dispatched by sendPendingComposition() if
    * 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.
+   * @param aKeyFlags       See KEY_* constants.
    * @return                Returns true if composition starts normally.
    *                        Otherwise, returns false because it might be
    *                        canceled by the web application.
    */
-  boolean startComposition();
+  [optional_argc]
+    boolean startComposition([optional] in nsIDOMKeyEvent aKeyboardEvent,
+                             [optional] in unsigned long aKeyFlags);
 
   /**
    * Set new composition string.  Pending composition will be flushed by
    * a call of flushPendingComposition().  However, if the new composition
    * string isn't empty, you need to call appendClauseToPendingComposition() to
    * fill all characters of aString with one or more clauses before flushing.
    * Note that if you need to commit or cancel composition, use
    * commitComposition() or cancelComposition().
@@ -328,55 +384,78 @@ interface nsITextInputProcessor : nsISup
    *
    * Note that compositionstart will be automatically dispatched if this is
    * called when there is no composition.
    *
    * 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.
+   * @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.
    */
-  boolean flushPendingComposition();
+  [optional_argc]
+    boolean flushPendingComposition(
+      [optional] in nsIDOMKeyEvent aKeyboardEvent,
+      [optional] in unsigned long aKeyFlags);
 
   /**
    * commitComposition() will commit composition.
    * If there is a composition which is started by the instance, this commits
    * the composition.  Otherwise, throws an exception.
    *
    * Note that if you tries to commit composition without specifying commit
    * string when there is no composition, this throws 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.
+   * @param aKeyFlags       See KEY_* constants.
    * @param aCommitString   This is optional.  If this is not specified,
    *                        composition will be committed with the last
    *                        composing string.  Otherwise, committed with
    *                        the specified string.
    * @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]
-    boolean commitComposition([optional] in DOMString aCommitString);
+    boolean commitComposition([optional] in nsIDOMKeyEvent aKeyboardEvent,
+                              [optional] in unsigned long aKeyFlags,
+                              [optional] in DOMString aCommitString);
 
   /**
    * cancelComposition() will cancel composition.  This is for now the same as
    * calling commitComposition("").  However, in the future, this might work
    * better.  If your IME needs to cancel composition, use this instead of
    * commitComposition().
    *
    * 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.
+   * @param aKeyFlags       See KEY_* constants.
    */
-  void cancelComposition();
+  [optional_argc]
+    void cancelComposition([optional] in nsIDOMKeyEvent 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
   // won't be fired.
   const unsigned long KEY_DEFAULT_PREVENTED                        = 0x00000001;
   // If KEY_NON_PRINTABLE_KEY is specified and the .key value isn't valid
   // key name, the methods will throws an exception.  In other words, this
   // flag prevents to dispatch key events with wrong key values and to cause
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -920,17 +920,17 @@ function synthesizeComposition(aEvent, a
     return false;
   }
   switch (aEvent.type) {
     case "compositionstart":
       return TIP.startComposition();
     case "compositioncommitasis":
       return TIP.commitComposition();
     case "compositioncommit":
-      return TIP.commitComposition(aEvent.data);
+      return TIP.commitComposition(null, 0, aEvent.data);
     default:
       return false;
   }
 }
 /**
  * Synthesize a compositionchange event which causes a DOM text event and
  * compositionupdate event if it's necessary.
  *
--- a/widget/TextEventDispatcher.h
+++ b/widget/TextEventDispatcher.h
@@ -155,16 +155,24 @@ public:
    * SetCaretInPendingComposition().
    */
   nsresult FlushPendingComposition(nsEventStatus& aStatus)
   {
     return mPendingComposition.Flush(this, aStatus);
   }
 
   /**
+   * ClearPendingComposition() makes this instance forget pending composition.
+   */
+  void ClearPendingComposition()
+  {
+    mPendingComposition.Clear();
+  }
+
+  /**
    * @see nsIWidget::NotifyIME()
    */
   nsresult NotifyIME(const IMENotification& aIMENotification);
 
   /**
    * DispatchKeyboardEvent() maybe dispatches aKeyboardEvent.
    *
    * @param aMessage        Must be NS_KEY_DOWN or NS_KEY_UP.
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -183,16 +183,21 @@ public:
   {
     if (mCodeNameIndex == CODE_NAME_INDEX_USE_STRING) {
       aCodeName = mCodeValue;
       return;
     }
     GetDOMCodeName(mCodeNameIndex, aCodeName);
   }
 
+  bool IsModifierKeyEvent() const
+  {
+    return GetModifierForKeyName(mKeyNameIndex) != MODIFIER_NONE;
+  }
+
   static void Shutdown();
 
   /**
    * ComputeLocationFromCodeValue() returns one of .location value
    * (nsIDOMKeyEvent::DOM_KEY_LOCATION_*) which is the most preferred value
    * for the specified specified code value.
    */
   static uint32_t ComputeLocationFromCodeValue(CodeNameIndex aCodeNameIndex);