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 217317 4783e19c5feb9e3cd0d4ed43d07e0d0866e6babf
parent 217316 151c7c4b14c6f3b1b09cba293453e40a81376dde
child 217318 d2ca3f9615ac97aa74ebd3b6f7046b6fbb3300ec
push id27877
push usercbook@mozilla.com
push dateTue, 25 Nov 2014 11:48:25 +0000
treeherdermozilla-central@4631a7474d8a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1077345
milestone36.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 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