Bug 1077345 part.6 Add NS_COMPOSITION event which automatically commits composition with its mData r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Tue, 25 Nov 2014 14:02:32 +0900
changeset 217319 d571e279fac484ed3e8e4275ef4f32214d8221de
parent 217318 d2ca3f9615ac97aa74ebd3b6f7046b6fbb3300ec
child 217320 fad93bdcf42e9b85d4d1c13f69beddebf61768d8
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs1077345
milestone36.0a1
Bug 1077345 part.6 Add NS_COMPOSITION event which automatically commits composition with its mData r=smaug
dom/base/nsDOMWindowUtils.cpp
dom/events/EventStateManager.cpp
dom/events/TextComposition.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
testing/mochitest/tests/SimpleTest/EventUtils.js
widget/BasicEvents.h
widget/TextEvents.h
widget/WidgetEventImpl.cpp
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2123,28 +2123,38 @@ nsDOMWindowUtils::SendCompositionEvent(c
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
   uint32_t msg;
   if (aType.EqualsLiteral("compositionstart")) {
     msg = NS_COMPOSITION_START;
   } else if (aType.EqualsLiteral("compositionend")) {
-    msg = NS_COMPOSITION_END;
+    // Now we don't support manually dispatching composition end with this
+    // API.  A compositionend is dispatched when this is called with
+    // compositioncommitasis or compositioncommit automatically.  For backward
+    // compatibility, this shouldn't return error in this case.
+    NS_WARNING("Don't call nsIDOMWindowUtils.sendCompositionEvent() for "
+               "compositionend.  Instead, use it with compositioncommitasis or "
+               "compositioncommit.  Then, compositionend will be automatically "
+               "dispatched.");
+    return NS_OK;
   } else if (aType.EqualsLiteral("compositionupdate")) {
     // 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 if (aType.EqualsLiteral("compositioncommit")) {
+    msg = NS_COMPOSITION_COMMIT;
   } else {
     return NS_ERROR_FAILURE;
   }
 
   WidgetCompositionEvent compositionEvent(true, msg, widget);
   InitEvent(compositionEvent);
   if (msg != NS_COMPOSITION_START && msg != NS_COMPOSITION_COMMIT_AS_IS) {
     compositionEvent.mData = aData;
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -815,16 +815,17 @@ EventStateManager::PreHandleEvent(nsPres
       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:
+  case NS_COMPOSITION_COMMIT:
     {
       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/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -149,16 +149,20 @@ TextComposition::DispatchCompositionEven
     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;
     }
+  } else if (aCompositionEvent->message == NS_COMPOSITION_COMMIT) {
+    NS_ASSERTION(!aCompositionEvent->mRanges,
+                 "mRanges of NS_COMPOSITION_COMMIT should be null");
+    aCompositionEvent->mRanges = nullptr;
   }
 
   if (!IsValidStateForComposition(aCompositionEvent->widget)) {
     *aStatus = nsEventStatus_eConsumeNoDefault;
     return;
   }
 
   // If this instance has requested to commit or cancel composition but
@@ -184,16 +188,17 @@ TextComposition::DispatchCompositionEven
   //    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:
+      case NS_COMPOSITION_COMMIT:
         committingData = &aCompositionEvent->mData;
         break;
       default:
         NS_WARNING("Unexpected event comes during committing or "
                    "canceling composition");
         break;
     }
     if (committingData) {
@@ -349,71 +354,44 @@ 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);
-      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;
-
-        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)) {
-          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);
-        }
+      bool isChanging = commitData != mLastData;
+      uint32_t message =
+        isChanging ? NS_COMPOSITION_COMMIT : NS_COMPOSITION_COMMIT_AS_IS;
+      WidgetCompositionEvent commitEvent(true, message, widget);
+      if (commitEvent.message == NS_COMPOSITION_COMMIT) {
+        commitEvent.mData = commitData;
       }
+      commitEvent.mFlags.mIsSynthesizedForTests = true;
+      nsEventStatus status = nsEventStatus_eIgnore;
+      widget->DispatchEvent(&commitEvent, 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;
+  } else {
+    DispatchCompositionEventRunnable(NS_COMPOSITION_COMMIT, data, true);
   }
-  // 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);
-
   return NS_OK;
 }
 
 nsresult
 TextComposition::NotifyIME(IMEMessage aMessage)
 {
   NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
   return IMEStateManager::NotifyIME(aMessage, mPresContext);
@@ -514,19 +492,19 @@ TextComposition::CompositionEventDispatc
       compStart.mData = selectedText.mReply.mString;
       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_COMMIT_AS_IS: {
+    case NS_COMPOSITION_COMMIT_AS_IS:
+    case NS_COMPOSITION_COMMIT: {
       WidgetCompositionEvent compEvent(true, mEventMessage, widget);
       if (mEventMessage != NS_COMPOSITION_COMMIT_AS_IS) {
         compEvent.mData = mData;
       }
       compEvent.mFlags.mIsSynthesizedForTests =
         mTextComposition->IsSynthesizedForTests();
       IMEStateManager::DispatchCompositionEvent(mEventTarget, presContext,
                                                 &compEvent, &status, nullptr,
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1031,18 +1031,18 @@ interface nsIDOMWindowUtils : nsISupport
                                [optional] in nsITransferable aTransferable);
 
   /**
    * 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
-   *                  "compositioncommitasis".
+   * @param aType     The event type: "compositionstart",
+   *                  "compositioncommitasis", or "compositioncommit".
    * @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. 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,
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -866,18 +866,18 @@ 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", "compositionend" or
- *                             "compositioncommitasis".
+ *                             "compositionstart", "compositionend",
+ *                             "compositioncommitasis" or "compositioncommit".
  *                             And also this may have |data| and |locale| which
  *                             would be used for the value of each property of
  *                             the composition event.  Note that the |data| is
  *                             ignored if the event type is "compositionstart"
  *                             or "compositioncommitasis".
  * @param aWindow              Optional (If null, current |window| will be used)
  */
 function synthesizeComposition(aEvent, aWindow)
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -192,16 +192,22 @@
 #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)
+// NS_COMPOSITION_COMMIT is the message for representing a commit of
+// composition string with its mData value.  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 mRanges should be nullptr.
+#define NS_COMPOSITION_COMMIT         (NS_COMPOSITION_EVENT_START + 5)
 
 // 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/TextEvents.h
+++ b/widget/TextEvents.h
@@ -341,22 +341,24 @@ public:
   uint32_t RangeCount() const
   {
     return mRanges ? mRanges->Length() : 0;
   }
 
   bool CausesDOMTextEvent() const
   {
     return message == NS_COMPOSITION_CHANGE ||
+           message == NS_COMPOSITION_COMMIT ||
            message == NS_COMPOSITION_COMMIT_AS_IS;
   }
 
   bool CausesDOMCompositionEndEvent() const
   {
     return message == NS_COMPOSITION_END ||
+           message == NS_COMPOSITION_COMMIT ||
            message == NS_COMPOSITION_COMMIT_AS_IS;
   }
 };
 
 /******************************************************************************
  * mozilla::WidgetQueryContentEvent
  ******************************************************************************/
 
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -136,16 +136,17 @@ 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:
+    case NS_COMPOSITION_COMMIT:
       return true;
     default:
       return false;
   }
 }
 
 bool
 WidgetEvent::HasPluginActivationEventMessage() const