Bug 1077345 part.4 Add NS_COMPOSITION_COMMIT_AS_IS event which automatically commits composition with the last data r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 25 Nov 2014 14:02:31 +0900
changeset 217354 4783e19c5feb9e3cd0d4ed43d07e0d0866e6babf
parent 217353 151c7c4b14c6f3b1b09cba293453e40a81376dde
child 217355 d2ca3f9615ac97aa74ebd3b6f7046b6fbb3300ec
push id10150
push usercbook@mozilla.com
push dateTue, 25 Nov 2014 13:25:44 +0000
treeherderfx-team@0094e3745ec2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1077345
milestone36.0a1
Bug 1077345 part.4 Add NS_COMPOSITION_COMMIT_AS_IS event which automatically commits composition with the last data r=smaug
dom/base/nsDOMWindowUtils.cpp
dom/events/EventStateManager.cpp
dom/events/IMEStateManager.cpp
dom/events/TextComposition.cpp
dom/events/TextComposition.h
dom/interfaces/base/nsIDOMWindowUtils.idl
editor/libeditor/nsEditor.cpp
testing/mochitest/tests/SimpleTest/EventUtils.js
widget/BasicEvents.h
widget/EventForwards.h
widget/TextEvents.h
widget/WidgetEventImpl.cpp
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2133,23 +2133,25 @@ nsDOMWindowUtils::SendCompositionEvent(c
     // Now we don't support manually dispatching composition update with this
     // API.  A compositionupdate is dispatched when a DOM text event modifies
     // composition string automatically.  For backward compatibility, this
     // shouldn't return error in this case.
     NS_WARNING("Don't call nsIDOMWindowUtils.sendCompositionEvent() for "
                "compositionupdate since it's ignored and the event is "
                "fired automatically when it's necessary");
     return NS_OK;
+  } else if (aType.EqualsLiteral("compositioncommitasis")) {
+    msg = NS_COMPOSITION_COMMIT_AS_IS;
   } else {
     return NS_ERROR_FAILURE;
   }
 
   WidgetCompositionEvent compositionEvent(true, msg, widget);
   InitEvent(compositionEvent);
-  if (msg != NS_COMPOSITION_START) {
+  if (msg != NS_COMPOSITION_START && msg != NS_COMPOSITION_COMMIT_AS_IS) {
     compositionEvent.mData = aData;
   }
 
   compositionEvent.mFlags.mIsSynthesizedForTests = true;
 
   nsEventStatus status;
   nsresult rv = widget->DispatchEvent(&compositionEvent, status);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -814,16 +814,17 @@ EventStateManager::PreHandleEvent(nsPres
                                            compositionEvent->widget);
       DoQuerySelectedText(&selectedText);
       NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
       compositionEvent->mData = selectedText.mReply.mString;
     }
     // through to compositionend handling
   case NS_COMPOSITION_END:
   case NS_COMPOSITION_CHANGE:
+  case NS_COMPOSITION_COMMIT_AS_IS:
     {
       WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
       if (IsTargetCrossProcess(compositionEvent)) {
         // Will not be handled locally, remote the event
         if (GetCrossProcessTarget()->SendCompositionEvent(*compositionEvent)) {
           // Cancel local dispatching
           aEvent->mFlags.mPropagationStopped = true;
         }
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -138,16 +138,18 @@ GetEventMessageName(uint32_t aMessage)
     case NS_COMPOSITION_START:
       return "NS_COMPOSITION_START";
     case NS_COMPOSITION_END:
       return "NS_COMPOSITION_END";
     case NS_COMPOSITION_UPDATE:
       return "NS_COMPOSITION_UPDATE";
     case NS_COMPOSITION_CHANGE:
       return "NS_COMPOSITION_CHANGE";
+    case NS_COMPOSITION_COMMIT_AS_IS:
+      return "NS_COMPOSITION_COMMIT_AS_IS";
     default:
       return "unacceptable event message";
   }
 }
 
 static const char*
 GetNotifyIMEMessageName(IMEMessage aMessage)
 {
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -79,38 +79,42 @@ TextComposition::MaybeDispatchCompositio
 
   if (mLastData == aCompositionEvent->mData) {
     return true;
   }
   CloneAndDispatchAs(aCompositionEvent, NS_COMPOSITION_UPDATE);
   return IsValidStateForComposition(aCompositionEvent->widget);
 }
 
-void
+BaseEventFlags
 TextComposition::CloneAndDispatchAs(
                    const WidgetCompositionEvent* aCompositionEvent,
-                   uint32_t aMessage)
+                   uint32_t aMessage,
+                   nsEventStatus* aStatus,
+                   EventDispatchingCallback* aCallBack)
 {
   MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->widget),
              "Should be called only when it's safe to dispatch an event");
 
   WidgetCompositionEvent compositionEvent(aCompositionEvent->mFlags.mIsTrusted,
                                           aMessage, aCompositionEvent->widget);
   compositionEvent.time = aCompositionEvent->time;
   compositionEvent.timeStamp = aCompositionEvent->timeStamp;
   compositionEvent.mData = aCompositionEvent->mData;
   compositionEvent.mFlags.mIsSynthesizedForTests =
     aCompositionEvent->mFlags.mIsSynthesizedForTests;
 
-  nsEventStatus status = nsEventStatus_eConsumeNoDefault;
+  nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault;
+  nsEventStatus* status = aStatus ? aStatus : &dummyStatus;
   if (aMessage == NS_COMPOSITION_UPDATE) {
     mLastData = compositionEvent.mData;
   }
   EventDispatcher::Dispatch(mNode, mPresContext,
-                            &compositionEvent, nullptr, &status, nullptr);
+                            &compositionEvent, nullptr, status, aCallBack);
+  return compositionEvent.mFlags;
 }
 
 void
 TextComposition::OnCompositionEventDiscarded(
                    const WidgetCompositionEvent* aCompositionEvent)
 {
   // Note that this method is never called for synthesized events for emulating
   // commit or cancel composition.
@@ -131,16 +135,32 @@ TextComposition::OnCompositionEventDisca
 
 void
 TextComposition::DispatchCompositionEvent(
                    WidgetCompositionEvent* aCompositionEvent,
                    nsEventStatus* aStatus,
                    EventDispatchingCallback* aCallBack,
                    bool aIsSynthesized)
 {
+  if (aCompositionEvent->message == NS_COMPOSITION_COMMIT_AS_IS) {
+    NS_ASSERTION(!aCompositionEvent->mRanges,
+                 "mRanges of NS_COMPOSITION_COMMIT_AS_IS should be null");
+    aCompositionEvent->mRanges = nullptr;
+    NS_ASSERTION(aCompositionEvent->mData.IsEmpty(),
+                 "mData of NS_COMPOSITION_COMMIT_AS_IS should be empty string");
+    if (mLastData == IDEOGRAPHIC_SPACE) {
+      // If the last data is an ideographic space (FullWidth space), it must be
+      // a placeholder character of some Chinese IME.  So, committing with
+      // this data must not be expected by users.  Let's use empty string.
+      aCompositionEvent->mData.Truncate();
+    } else {
+      aCompositionEvent->mData = mLastData;
+    }
+  }
+
   if (!IsValidStateForComposition(aCompositionEvent->widget)) {
     *aStatus = nsEventStatus_eConsumeNoDefault;
     return;
   }
 
   // If this instance has requested to commit or cancel composition but
   // is not synthesizing commit event, that means that the IME commits or
   // cancels the composition asynchronously.  Typically, iBus behaves so.
@@ -163,16 +183,17 @@ TextComposition::DispatchCompositionEven
   // 1. committing string is empty string at requesting commit but the last
   //    data isn't IDEOGRAPHIC SPACE.
   // 2. non-empty string is committed at requesting cancel.
   if (!aIsSynthesized && (mIsRequestingCommit || mIsRequestingCancel)) {
     nsString* committingData = nullptr;
     switch (aCompositionEvent->message) {
       case NS_COMPOSITION_END:
       case NS_COMPOSITION_CHANGE:
+      case NS_COMPOSITION_COMMIT_AS_IS:
         committingData = &aCompositionEvent->mData;
         break;
       default:
         NS_WARNING("Unexpected event comes during committing or "
                    "canceling composition");
         break;
     }
     if (committingData) {
@@ -180,42 +201,72 @@ TextComposition::DispatchCompositionEven
           mLastData != IDEOGRAPHIC_SPACE) {
         committingData->Assign(mLastData);
       } else if (mIsRequestingCancel && !committingData->IsEmpty()) {
         committingData->Truncate();
       }
     }
   }
 
-  if (aCompositionEvent->CausesDOMTextEvent()) {
+  bool dispatchEvent = true;
+  bool dispatchDOMTextEvent = aCompositionEvent->CausesDOMTextEvent();
+
+  // When mIsComposing is false but the committing string is different from
+  // the last data (E.g., previous NS_COMPOSITION_CHANGE event made the
+  // composition string empty or didn't have clause information), we don't
+  // need to dispatch redundant DOM text event.
+  if (dispatchDOMTextEvent &&
+      aCompositionEvent->message != NS_COMPOSITION_CHANGE &&
+      !mIsComposing && mLastData == aCompositionEvent->mData) {
+    dispatchEvent = dispatchDOMTextEvent = false;
+  }
+
+  if (dispatchDOMTextEvent) {
     if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) {
       return;
     }
   }
 
-  EventDispatcher::Dispatch(mNode, mPresContext,
-                            aCompositionEvent, nullptr, aStatus, aCallBack);
+  if (dispatchEvent) {
+    // If the composition event should cause a DOM text event, we should
+    // overwrite the event message as NS_COMPOSITION_CHANGE because due to
+    // the limitation of mapping between event messages and DOM event types,
+    // we cannot map multiple event messages to a DOM event type.
+    if (dispatchDOMTextEvent &&
+        aCompositionEvent->message != NS_COMPOSITION_CHANGE) {
+      aCompositionEvent->mFlags =
+        CloneAndDispatchAs(aCompositionEvent, NS_COMPOSITION_CHANGE,
+                           aStatus, aCallBack);
+    } else {
+      EventDispatcher::Dispatch(mNode, mPresContext,
+                                aCompositionEvent, nullptr, aStatus, aCallBack);
+    }
+  } else {
+    *aStatus = nsEventStatus_eConsumeNoDefault;
+  }
 
   if (!IsValidStateForComposition(aCompositionEvent->widget)) {
     return;
   }
 
   // Emulate editor behavior of compositionchange event (DOM text event) handler
   // if no editor handles composition events.
-  if (aCompositionEvent->CausesDOMTextEvent() && !HasEditor()) {
+  if (dispatchDOMTextEvent && !HasEditor()) {
     EditorWillHandleCompositionChangeEvent(aCompositionEvent);
     EditorDidHandleCompositionChangeEvent();
   }
 
-#ifdef DEBUG
-  else if (aCompositionEvent->CausesDOMCompositionEndEvent()) {
+  if (aCompositionEvent->CausesDOMCompositionEndEvent()) {
+    // Dispatch a compositionend event if it's necessary.
+    if (aCompositionEvent->message != NS_COMPOSITION_END) {
+      CloneAndDispatchAs(aCompositionEvent, NS_COMPOSITION_END);
+    }
     MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?");
     MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?");
   }
-#endif // #ifdef DEBUG
 
   // Notify composition update to widget if possible
   NotityUpdateComposition(aCompositionEvent);
 }
 
 void
 TextComposition::NotityUpdateComposition(
                    const WidgetCompositionEvent* aCompositionEvent)
@@ -298,52 +349,62 @@ TextComposition::RequestToCommit(nsIWidg
         return rv;
       }
     } else {
       // Emulates to commit or cancel the composition
       // FYI: These events may be discarded by PresShell if it's not safe to
       //      dispatch the event.
       nsCOMPtr<nsIWidget> widget(aWidget);
       nsAutoString commitData(aDiscard ? EmptyString() : lastData);
-      bool changingData = lastData != commitData;
+      if (commitData == mLastData) {
+        WidgetCompositionEvent commitEvent(true, NS_COMPOSITION_COMMIT_AS_IS,
+                                           widget);
+        commitEvent.mFlags.mIsSynthesizedForTests = true;
+        nsEventStatus status = nsEventStatus_eIgnore;
+        widget->DispatchEvent(&commitEvent, status);
+      } else {
+        WidgetCompositionEvent changeEvent(true, NS_COMPOSITION_CHANGE, widget);
+        changeEvent.mData = commitData;
+        changeEvent.mFlags.mIsSynthesizedForTests = true;
 
-      WidgetCompositionEvent changeEvent(true, NS_COMPOSITION_CHANGE, widget);
-      changeEvent.mData = commitData;
-      changeEvent.mFlags.mIsSynthesizedForTests = true;
-
-      MaybeDispatchCompositionUpdate(&changeEvent);
+        MaybeDispatchCompositionUpdate(&changeEvent);
 
-      // If changing the data or committing string isn't empty, we need to
-      // dispatch compositionchange event for setting the composition string
-      // without IME selection.
-      if (IsValidStateForComposition(widget) &&
-          (changingData || !commitData.IsEmpty())) {
-        nsEventStatus status = nsEventStatus_eIgnore;
-        widget->DispatchEvent(&changeEvent, status);
-      }
+        // If changing the data or committing string isn't empty, we need to
+        // dispatch compositionchange event for setting the composition string
+        // without IME selection.
+        if (IsValidStateForComposition(widget)) {
+          nsEventStatus status = nsEventStatus_eIgnore;
+          widget->DispatchEvent(&changeEvent, status);
+        }
 
-      if (IsValidStateForComposition(widget)) {
-        nsEventStatus status = nsEventStatus_eIgnore;
-        WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget);
-        endEvent.mData = commitData;
-        endEvent.mFlags.mIsSynthesizedForTests = true;
-        widget->DispatchEvent(&endEvent, status);
+        if (IsValidStateForComposition(widget)) {
+          nsEventStatus status = nsEventStatus_eIgnore;
+          WidgetCompositionEvent endEvent(true, NS_COMPOSITION_END, widget);
+          endEvent.mData = commitData;
+          endEvent.mFlags.mIsSynthesizedForTests = true;
+          widget->DispatchEvent(&endEvent, status);
+        }
       }
     }
   }
 
   mRequestedToCommitOrCancel = true;
 
   // If the request is performed synchronously, this must be already destroyed.
   if (Destroyed()) {
     return NS_OK;
   }
 
   // Otherwise, synthesize the commit in content.
   nsAutoString data(aDiscard ? EmptyString() : lastData);
+  if (data == mLastData) {
+    DispatchCompositionEventRunnable(NS_COMPOSITION_COMMIT_AS_IS, EmptyString(),
+                                     true);
+    return NS_OK;
+  }
   // If the last composition string and new data are different, we need to
   // dispatch compositionchange event for removing IME selection.  However, if
   // the commit string is empty string and it's not changed from the last data,
   // we don't need to dispatch compositionchange event.
   if (lastData != data || !data.IsEmpty()) {
     DispatchCompositionEventRunnable(NS_COMPOSITION_CHANGE, data, true);
   }
   DispatchCompositionEventRunnable(NS_COMPOSITION_END, data, true);
@@ -454,19 +515,22 @@ TextComposition::CompositionEventDispatc
       compStart.mFlags.mIsSynthesizedForTests =
         mTextComposition->IsSynthesizedForTests();
       IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext,
                                                 &compStart, &status, nullptr,
                                                 mIsSynthesizedEvent);
       break;
     }
     case NS_COMPOSITION_END:
-    case NS_COMPOSITION_CHANGE: {
+    case NS_COMPOSITION_CHANGE:
+    case NS_COMPOSITION_COMMIT_AS_IS: {
       WidgetCompositionEvent compEvent(true, mEventMessage, widget);
-      compEvent.mData = mData;
+      if (mEventMessage != NS_COMPOSITION_COMMIT_AS_IS) {
+        compEvent.mData = mData;
+      }
       compEvent.mFlags.mIsSynthesizedForTests =
         mTextComposition->IsSynthesizedForTests();
       IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext,
                                                 &compEvent, &status, nullptr,
                                                 mIsSynthesizedEvent);
       break;
     }
     default:
--- a/dom/events/TextComposition.h
+++ b/dom/events/TextComposition.h
@@ -273,20 +273,24 @@ private:
    *         destroying this composition.
    */
   bool MaybeDispatchCompositionUpdate(
          const WidgetCompositionEvent* aCompositionEvent);
 
   /**
    * CloneAndDispatchAs() dispatches a composition event which is
    * duplicateed from aCompositionEvent and set the aMessage.
+   *
+   * @return Returns BaseEventFlags which is the result of dispatched event.
    */
-  void CloneAndDispatchAs(
-         const WidgetCompositionEvent* aCompositionEvent,
-         uint32_t aMessage);
+  BaseEventFlags CloneAndDispatchAs(
+                   const WidgetCompositionEvent* aCompositionEvent,
+                   uint32_t aMessage,
+                   nsEventStatus* aStatus = nullptr,
+                   EventDispatchingCallback* aCallBack = nullptr);
 
   /**
    * If IME has already dispatched compositionend event but it was discarded
    * by PresShell due to not safe to dispatch, this returns true.
    */
   bool WasNativeCompositionEndEventDiscarded() const
   {
     return mWasNativeCompositionEndEventDiscarded;
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1032,20 +1032,22 @@ interface nsIDOMWindowUtils : nsISupport
 
   /**
    * Synthesize a composition event to the window.
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without chrome privileges.
    *
    * @param aType     The event type: "compositionstart", "compositionend" or
-   *                  "compositionupdate".
+   *                  "compositioncommitasis".
    * @param aData     The data property value.  Note that this isn't applied
    *                  for compositionstart event because its value is the
-   *                  selected text which is automatically computed.
+   *                  selected text which is automatically computed. And also
+   *                  this isn't applied for compositioncommitasis because
+   *                  the last data will be set automatically.
    * @param aLocale   The locale property value.
    */
   void sendCompositionEvent(in AString aType,
                             in AString aData,
                             in AString aLocale);
 
   /**
    * Creating synthesizer of composition string on the window.
--- a/editor/libeditor/nsEditor.cpp
+++ b/editor/libeditor/nsEditor.cpp
@@ -4910,16 +4910,17 @@ nsEditor::IsAcceptableInputEvent(nsIDOME
     case NS_USER_DEFINED_EVENT:
       // If events are not created with proper event interface, their message
       // are initialized with NS_USER_DEFINED_EVENT.  Let's ignore such event.
       return false;
     case NS_COMPOSITION_START:
     case NS_COMPOSITION_END:
     case NS_COMPOSITION_UPDATE:
     case NS_COMPOSITION_CHANGE:
+    case NS_COMPOSITION_COMMIT_AS_IS:
       // Don't allow composition events whose internal event are not
       // WidgetCompositionEvent.
       widgetGUIEvent = aEvent->GetInternalNSEvent()->AsCompositionEvent();
       needsWidget = true;
       break;
     default:
       break;
   }
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -866,22 +866,23 @@ const COMPOSITION_ATTR_SELECTEDRAWTEXT  
 const COMPOSITION_ATTR_CONVERTEDTEXT         = 0x04;
 const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
 
 /**
  * Synthesize a composition event.
  *
  * @param aEvent               The composition event information.  This must
  *                             have |type| member.  The value must be
- *                             "compositionstart" or "compositionend".
+ *                             "compositionstart", "compositionend" or
+ *                             "compositioncommitasis".
  *                             And also this may have |data| and |locale| which
  *                             would be used for the value of each property of
- *                             the composition event.  Note that the data would
- *                             be ignored if the event type were
- *                             "compositionstart".
+ *                             the composition event.  Note that the |data| is
+ *                             ignored if the event type is "compositionstart"
+ *                             or "compositioncommitasis".
  * @param aWindow              Optional (If null, current |window| will be used)
  */
 function synthesizeComposition(aEvent, aWindow)
 {
   var utils = _getDOMWindowUtils(aWindow);
   if (!utils) {
     return;
   }
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -171,27 +171,37 @@
 #define NS_MUTATION_CHARACTERDATAMODIFIED    (NS_MUTATION_START+6)
 #define NS_MUTATION_END                      (NS_MUTATION_START+6)
 
 #define NS_USER_DEFINED_EVENT         2000
  
 // composition events
 #define NS_COMPOSITION_EVENT_START    2200
 #define NS_COMPOSITION_START          (NS_COMPOSITION_EVENT_START)
+// NS_COMPOSITION_END is the message for DOM compositionend event.
+// This event should NOT be dispatched from widget if NS_COMPOSITION_COMMIT
+// is available.
 #define NS_COMPOSITION_END            (NS_COMPOSITION_EVENT_START + 1)
 // NS_COMPOSITION_UPDATE is the message for DOM compositionupdate event.
 // This event should NOT be dispatched from widget since it will be dispatched
 // by mozilla::TextComposition automatically if NS_COMPOSITION_CHANGE event
 // will change composition string.
 #define NS_COMPOSITION_UPDATE         (NS_COMPOSITION_EVENT_START + 2)
 // NS_COMPOSITION_CHANGE is the message for representing a change of
 // composition string.  This should be dispatched from widget even if
 // composition string isn't changed but the ranges are changed.  This causes
 // a DOM "text" event which is a non-standard DOM event.
 #define NS_COMPOSITION_CHANGE         (NS_COMPOSITION_EVENT_START + 3)
+// NS_COMPOSITION_COMMIT_AS_IS is the message for representing a commit of
+// composition string.  TextComposition will commit composition with the
+// last data.  TextComposition will dispatch this event to the DOM tree as
+// NS_COMPOSITION_CHANGE without clause information.  After that,
+// NS_COMPOSITION_END will be dispatched automatically.
+// Its mData and mRanges should be empty and nullptr.
+#define NS_COMPOSITION_COMMIT_AS_IS   (NS_COMPOSITION_EVENT_START + 4)
 
 // UI events
 #define NS_UI_EVENT_START          2500
 // this is not to be confused with NS_ACTIVATE!
 #define NS_UI_ACTIVATE             (NS_UI_EVENT_START)
 #define NS_UI_FOCUSIN              (NS_UI_EVENT_START + 1)
 #define NS_UI_FOCUSOUT             (NS_UI_EVENT_START + 2)
 
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -97,16 +97,17 @@ namespace mozilla {
 #define NS_ROOT_EVENT_CLASS(aPrefix, aName) NS_EVENT_CLASS(aPrefix, aName)
 
 #include "mozilla/EventClassList.h"
 
 #undef NS_EVENT_CLASS
 #undef NS_ROOT_EVENT_CLASS
 
 // BasicEvents.h
+struct BaseEventFlags;
 struct EventFlags;
 
 // TextEvents.h
 struct AlternativeCharCode;
 
 // TextRange.h
 struct TextRangeStyle;
 struct TextRange;
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -340,22 +340,24 @@ public:
 
   uint32_t RangeCount() const
   {
     return mRanges ? mRanges->Length() : 0;
   }
 
   bool CausesDOMTextEvent() const
   {
-    return message == NS_COMPOSITION_CHANGE;
+    return message == NS_COMPOSITION_CHANGE ||
+           message == NS_COMPOSITION_COMMIT_AS_IS;
   }
 
   bool CausesDOMCompositionEndEvent() const
   {
-    return message == NS_COMPOSITION_END;
+    return message == NS_COMPOSITION_END ||
+           message == NS_COMPOSITION_COMMIT_AS_IS;
   }
 };
 
 /******************************************************************************
  * mozilla::WidgetQueryContentEvent
  ******************************************************************************/
 
 class WidgetQueryContentEvent : public WidgetGUIEvent
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -135,16 +135,17 @@ WidgetEvent::HasKeyEventMessage() const
 bool
 WidgetEvent::HasIMEEventMessage() const
 {
   switch (message) {
     case NS_COMPOSITION_START:
     case NS_COMPOSITION_END:
     case NS_COMPOSITION_UPDATE:
     case NS_COMPOSITION_CHANGE:
+    case NS_COMPOSITION_COMMIT_AS_IS:
       return true;
     default:
       return false;
   }
 }
 
 bool
 WidgetEvent::HasPluginActivationEventMessage() const