Bug 1275906 part.2 TextComposition should use IMEContentObserver for sending NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED if the editor which has the composition is in the active IMEContentObserver r?smaug draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 01 Jun 2016 22:14:41 +0900
changeset 374276 40690ce53ad83b463ee3eef35f13b56b428c67bd
parent 374275 d3cd26e907c314899bec6367f9d2e863a8861840
child 522593 f7c10bf0ac230c6e8662e677e5bd5cf2f40bcaa5
push id19973
push usermasayuki@d-toybox.com
push dateThu, 02 Jun 2016 04:17:43 +0000
reviewerssmaug
bugs1275906
milestone49.0a1
Bug 1275906 part.2 TextComposition should use IMEContentObserver for sending NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED if the editor which has the composition is in the active IMEContentObserver r?smaug For sending NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED after the other change notifications which was caused by the user input, we need to use IMEContentObserver::IMENotificationSender because it sends the notifications when it's safe to do it. This patch makes TextComposition use IMEContentObserver to send the notification. However, if there is no active IMEContentObserver, e.g., composition events are fired on unfocused window, TextComposition sends it by itself (same as current implementation). If IMEContentObserver stops observing when it has pending NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED, it cannot send the notification (i.e., it is discarded completely in such case). However, in such case, IMEContentObserver sends NOTIFY_IME_OF_BLUR. So, anyway, native IME handler should treat the blur notification as it including NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED. On the other hand, we're buggy if composition events are fired in non-active window. Even in such case, IMEContentObserver should be created for active editor in each document and it notifies IME of the changes. But this is out of the scope of this bug. MozReview-Commit-ID: 7Q0ZsJTh4hX
dom/events/IMEContentObserver.cpp
dom/events/IMEContentObserver.h
dom/events/IMEStateManager.cpp
dom/events/IMEStateManager.h
dom/events/TextComposition.cpp
dom/events/TextComposition.h
widget/IMEData.h
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -173,16 +173,17 @@ IMEContentObserver::IMEContentObserver()
   , mPreCharacterDataChangeLength(-1)
   , mSendingNotification(NOTIFY_IME_OF_NOTHING)
   , mIsObserving(false)
   , mIMEHasFocus(false)
   , mNeedsToNotifyIMEOfFocusSet(false)
   , mNeedsToNotifyIMEOfTextChange(false)
   , mNeedsToNotifyIMEOfSelectionChange(false)
   , mNeedsToNotifyIMEOfPositionChange(false)
+  , mNeedsToNotifyIMEOfCompositionEventHandled(false)
   , mIsHandlingQueryContentEvent(false)
 {
 #ifdef DEBUG
   mTextChangeData.Test();
 #endif
 }
 
 void
@@ -556,22 +557,41 @@ IMEContentObserver::MaybeReinitialize(ns
   if (GetState() == eState_StoppedObserving) {
     Init(aWidget, aPresContext, aContent, aEditor);
   }
   return IsManaging(aPresContext, aContent);
 }
 
 bool
 IMEContentObserver::IsManaging(nsPresContext* aPresContext,
-                               nsIContent* aContent)
+                               nsIContent* aContent) const
 {
   return GetState() == eState_Observing &&
          IsObservingContent(aPresContext, aContent);
 }
 
+bool
+IMEContentObserver::IsManaging(const TextComposition* aComposition) const
+{
+  if (GetState() != eState_Observing) {
+    return false;
+  }
+  nsPresContext* presContext = aComposition->GetPresContext();
+  if (NS_WARN_IF(!presContext)) {
+    return false;
+  }
+  if (presContext != GetPresContext()) {
+    return false; // observing different document
+  }
+  nsINode* targetNode = aComposition->GetEventTargetNode();
+  nsIContent* targetContent =
+    targetNode && targetNode->IsContent() ? targetNode->AsContent() : nullptr;
+  return IsObservingContent(presContext, targetContent);
+}
+
 IMEContentObserver::State
 IMEContentObserver::GetState() const
 {
   if (!mSelection || !mRootContent || !mEditableNode) {
     return eState_NotObserving; // failed to initialize or finalized.
   }
   if (!mRootContent->IsInComposedDoc()) {
     // the focused editor has already been reframed.
@@ -1214,16 +1234,27 @@ IMEContentObserver::MaybeNotifyIMEOfPosi
        "ignored since caused by ContentEventHandler during sending "
        "NOTIY_IME_OF_POSITION_CHANGE", this));
     return;
   }
   PostPositionChangeNotification();
   FlushMergeableNotifications();
 }
 
+void
+IMEContentObserver::MaybeNotifyCompositionEventHandled()
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::MaybeNotifyCompositionEventHandled()",
+     this));
+
+  PostCompositionEventHandledNotification();
+  FlushMergeableNotifications();
+}
+
 bool
 IMEContentObserver::UpdateSelectionCache()
 {
   MOZ_ASSERT(IsSafeToNotifyIME());
 
   if (!mUpdatePreference.WantSelectionChange()) {
     return false;
   }
@@ -1260,16 +1291,26 @@ void
 IMEContentObserver::PostPositionChangeNotification()
 {
   MOZ_LOG(sIMECOLog, LogLevel::Debug,
     ("IMECO: 0x%p IMEContentObserver::PostPositionChangeNotification()", this));
 
   mNeedsToNotifyIMEOfPositionChange = true;
 }
 
+void
+IMEContentObserver::PostCompositionEventHandledNotification()
+{
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::"
+     "PostCompositionEventHandledNotification()", this));
+
+  mNeedsToNotifyIMEOfCompositionEventHandled = true;
+}
+
 bool
 IMEContentObserver::IsReflowLocked() const
 {
   nsPresContext* presContext = GetPresContext();
   if (NS_WARN_IF(!presContext)) {
     return false;
   }
   nsIPresShell* presShell = presContext->GetPresShell();
@@ -1387,16 +1428,19 @@ IMEContentObserver::TryToFlushPendingNot
 
 bool
 IMEContentObserver::AChangeEvent::CanNotifyIME(
                                     ChangeEventType aChangeEventType) const
 {
   if (NS_WARN_IF(!mIMEContentObserver)) {
     return false;
   }
+  if (aChangeEventType == eChangeEventType_CompositionEventHandled) {
+    return mIMEContentObserver->mWidget != nullptr;
+  }
   State state = mIMEContentObserver->GetState();
   // If it's not initialized, we should do nothing.
   if (state == eState_NotObserving) {
     return false;
   }
   // If setting focus, just check the state.
   if (aChangeEventType == eChangeEventType_Focus) {
     return !NS_WARN_IF(mIMEContentObserver->mIMEHasFocus);
@@ -1429,16 +1473,18 @@ IMEContentObserver::AChangeEvent::IsSafe
        this, ToChar(mIMEContentObserver->mSendingNotification)));
     return false;
   }
   State state = mIMEContentObserver->GetState();
   if (aChangeEventType == eChangeEventType_Focus) {
     if (NS_WARN_IF(state != eState_Initializing && state != eState_Observing)) {
       return false;
     }
+  } else if (aChangeEventType == eChangeEventType_CompositionEventHandled) {
+    // It doesn't need to check the observing status.
   } else if (state != eState_Observing) {
     return false;
   }
   return mIMEContentObserver->IsSafeToNotifyIME();
 }
 
 /******************************************************************************
  * mozilla::IMEContentObserver::IMENotificationSender
@@ -1514,16 +1560,28 @@ IMEContentObserver::IMENotificationSende
   if (!mIMEContentObserver->mNeedsToNotifyIMEOfTextChange &&
       !mIMEContentObserver->mNeedsToNotifyIMEOfSelectionChange) {
     if (mIMEContentObserver->mNeedsToNotifyIMEOfPositionChange) {
       mIMEContentObserver->mNeedsToNotifyIMEOfPositionChange = false;
       SendPositionChange();
     }
   }
 
+  // Composition event handled notification should be sent after all the
+  // other notifications because this notifies widget of finishing all pending
+  // events are handled completely.
+  if (!mIMEContentObserver->mNeedsToNotifyIMEOfTextChange &&
+      !mIMEContentObserver->mNeedsToNotifyIMEOfSelectionChange &&
+      !mIMEContentObserver->mNeedsToNotifyIMEOfPositionChange) {
+    if (mIMEContentObserver->mNeedsToNotifyIMEOfCompositionEventHandled) {
+      mIMEContentObserver->mNeedsToNotifyIMEOfCompositionEventHandled = false;
+      SendCompositionEventHandled();
+    }
+  }
+
   mIMEContentObserver->mQueuedSender = nullptr;
 
   // If notifications caused some new change, we should notify them now.
   if (mIMEContentObserver->NeedsToNotifyIMEOfSomething()) {
     if (mIMEContentObserver->GetState() == eState_StoppedObserving) {
       MOZ_LOG(sIMECOLog, LogLevel::Debug,
         ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::Run(), "
          "waiting IMENotificationSender to be reinitialized", this));
@@ -1740,9 +1798,49 @@ IMEContentObserver::IMENotificationSende
                              mIMEContentObserver->mWidget);
   mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING;
 
   MOZ_LOG(sIMECOLog, LogLevel::Debug,
     ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
      "SendPositionChange(), sent NOTIFY_IME_OF_POSITION_CHANGE", this));
 }
 
+void
+IMEContentObserver::IMENotificationSender::SendCompositionEventHandled()
+{
+  if (!CanNotifyIME(eChangeEventType_CompositionEventHandled)) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
+       "SendCompositionEventHandled(), FAILED, due to impossible to notify "
+       "IME of composition event handled", this));
+    return;
+  }
+
+  if (!IsSafeToNotifyIME(eChangeEventType_CompositionEventHandled)) {
+    MOZ_LOG(sIMECOLog, LogLevel::Debug,
+      ("IMECO: 0x%p   IMEContentObserver::IMENotificationSender::"
+       "SendCompositionEventHandled(), retrying to send "
+       "NOTIFY_IME_OF_POSITION_CHANGE...", this));
+    mIMEContentObserver->PostCompositionEventHandledNotification();
+    return;
+  }
+
+  MOZ_LOG(sIMECOLog, LogLevel::Info,
+    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
+     "SendCompositionEventHandled(), sending "
+     "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED...", this));
+
+  MOZ_RELEASE_ASSERT(mIMEContentObserver->mSendingNotification ==
+                       NOTIFY_IME_OF_NOTHING);
+  mIMEContentObserver->mSendingNotification =
+                         NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED;
+  IMEStateManager::NotifyIME(
+                     IMENotification(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED),
+                     mIMEContentObserver->mWidget);
+  mIMEContentObserver->mSendingNotification = NOTIFY_IME_OF_NOTHING;
+
+  MOZ_LOG(sIMECOLog, LogLevel::Debug,
+    ("IMECO: 0x%p IMEContentObserver::IMENotificationSender::"
+     "SendCompositionEventHandled(), sent "
+     "NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED", this));
+}
+
 } // namespace mozilla
--- a/dom/events/IMEContentObserver.h
+++ b/dom/events/IMEContentObserver.h
@@ -24,16 +24,17 @@
 class nsIContent;
 class nsINode;
 class nsISelection;
 class nsPresContext;
 
 namespace mozilla {
 
 class EventStateManager;
+class TextComposition;
 
 // IMEContentObserver notifies widget of any text and selection changes
 // in the currently focused editor
 class IMEContentObserver final : public nsISelectionListener
                                , public nsStubMutationObserver
                                , public nsIReflowObserver
                                , public nsIScrollObserver
                                , public nsSupportsWeakReference
@@ -86,17 +87,18 @@ public:
    * with new node instances.
    * @return            Returns true if the instance is managing the content.
    *                    Otherwise, false.
    */
   bool MaybeReinitialize(nsIWidget* aWidget,
                          nsPresContext* aPresContext,
                          nsIContent* aContent,
                          nsIEditor* aEditor);
-  bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent);
+  bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent) const;
+  bool IsManaging(const TextComposition* aTextComposition) const;
   bool IsEditorHandlingEventForComposition() const;
   bool KeepAliveDuringDeactive() const
   {
     return mUpdatePreference.WantDuringDeactive();
   }
   nsIWidget* GetWidget() const { return mWidget; }
   nsIEditor* GetEditor() const { return mEditor; }
   void SuppressNotifyingIME();
@@ -106,16 +108,22 @@ public:
                                nsIContent** aRoot) const;
 
   /**
    * TryToFlushPendingNotifications() should be called when pending events
    * should be flushed.  This tries to run the queued IMENotificationSender.
    */
   void TryToFlushPendingNotifications();
 
+  /**
+   * MaybeNotifyCompositionEventHandled() posts composition event handled
+   * notification into the pseudo queue.
+   */
+  void MaybeNotifyCompositionEventHandled();
+
 private:
   ~IMEContentObserver() {}
 
   enum State {
     eState_NotObserving,
     eState_Initializing,
     eState_StoppedObserving,
     eState_Observing
@@ -138,16 +146,17 @@ private:
   void PostTextChangeNotification();
   void MaybeNotifyIMEOfTextChange(const TextChangeDataBase& aTextChangeData);
   void PostSelectionChangeNotification();
   void MaybeNotifyIMEOfSelectionChange(bool aCausedByComposition,
                                        bool aCausedBySelectionEvent,
                                        bool aOccurredDuringComposition);
   void PostPositionChangeNotification();
   void MaybeNotifyIMEOfPositionChange();
+  void PostCompositionEventHandledNotification();
 
   void NotifyContentAdded(nsINode* aContainer, int32_t aStart, int32_t aEnd);
   void ObserveEditableNode();
   /**
    *  NotifyIMEOfBlur() notifies IME of blur.
    */
   void NotifyIMEOfBlur();
   /**
@@ -156,24 +165,26 @@ private:
   void UnregisterObservers();
   void FlushMergeableNotifications();
   void ClearPendingNotifications()
   {
     mNeedsToNotifyIMEOfFocusSet = false;
     mNeedsToNotifyIMEOfTextChange = false;
     mNeedsToNotifyIMEOfSelectionChange = false;
     mNeedsToNotifyIMEOfPositionChange = false;
+    mNeedsToNotifyIMEOfCompositionEventHandled = false;
     mTextChangeData.Clear();
   }
   bool NeedsToNotifyIMEOfSomething() const
   {
     return mNeedsToNotifyIMEOfFocusSet ||
            mNeedsToNotifyIMEOfTextChange ||
            mNeedsToNotifyIMEOfSelectionChange ||
-           mNeedsToNotifyIMEOfPositionChange;
+           mNeedsToNotifyIMEOfPositionChange ||
+           mNeedsToNotifyIMEOfCompositionEventHandled;
   }
 
   /**
    * UpdateSelectionCache() updates mSelectionData with the latest selection.
    * This should be called only when IsSafeToNotifyIME() returns true.
    *
    * Note that this does nothing if mUpdatePreference.WantSelectionChange()
    * returns false.
@@ -199,17 +210,17 @@ private:
   {
   protected:
     enum ChangeEventType
     {
       eChangeEventType_Focus,
       eChangeEventType_Selection,
       eChangeEventType_Text,
       eChangeEventType_Position,
-      eChangeEventType_FlushPendingEvents
+      eChangeEventType_CompositionEventHandled
     };
 
     explicit AChangeEvent(IMEContentObserver* aIMEContentObserver)
       : mIMEContentObserver(aIMEContentObserver)
     {
       MOZ_ASSERT(mIMEContentObserver);
     }
 
@@ -236,16 +247,17 @@ private:
     }
     NS_IMETHOD Run() override;
 
   private:
     void SendFocusSet();
     void SendSelectionChange();
     void SendTextChange();
     void SendPositionChange();
+    void SendCompositionEventHandled();
 
     bool mIsRunning;
   };
 
   // mQueuedSender is, it was put into the event queue but not run yet.
   RefPtr<IMENotificationSender> mQueuedSender;
 
   /**
@@ -322,16 +334,17 @@ private:
   IMEMessage mSendingNotification;
 
   bool mIsObserving;
   bool mIMEHasFocus;
   bool mNeedsToNotifyIMEOfFocusSet;
   bool mNeedsToNotifyIMEOfTextChange;
   bool mNeedsToNotifyIMEOfSelectionChange;
   bool mNeedsToNotifyIMEOfPositionChange;
+  bool mNeedsToNotifyIMEOfCompositionEventHandled;
   // mIsHandlingQueryContentEvent is true when IMEContentObserver is handling
   // WidgetQueryContentEvent with ContentEventHandler.
   bool mIsHandlingQueryContentEvent;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_IMEContentObserver_h_
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -1166,16 +1166,23 @@ IMEStateManager::DispatchCompositionEven
          "was dispatched"));
       sTextCompositions->ElementAt(i)->Destroy();
       sTextCompositions->RemoveElementAt(i);
     }
   }
 }
 
 // static
+IMEContentObserver*
+IMEStateManager::GetActiveContentObserver()
+{
+  return sActiveIMEContentObserver;
+}
+
+// static
 nsIContent*
 IMEStateManager::GetRootContent(nsPresContext* aPresContext)
 {
   nsIDocument* doc = aPresContext->Document();
   if (NS_WARN_IF(!doc)) {
     return nullptr;
   }
   return doc->GetRootElement();
--- a/dom/events/IMEStateManager.h
+++ b/dom/events/IMEStateManager.h
@@ -219,16 +219,22 @@ public:
                             bool aOriginIsRemote = false);
   static nsresult NotifyIME(IMEMessage aMessage,
                             nsPresContext* aPresContext,
                             bool aOriginIsRemote = false);
 
   static nsINode* GetRootEditableNode(nsPresContext* aPresContext,
                                       nsIContent* aContent);
 
+  /**
+   * Returns active IMEContentObserver but may be nullptr if focused content
+   * isn't editable or focus in a remote process.
+   */
+  static IMEContentObserver* GetActiveContentObserver();
+
 protected:
   static nsresult OnChangeFocusInternal(nsPresContext* aPresContext,
                                         nsIContent* aContent,
                                         InputContextAction aAction);
   static void SetIMEState(const IMEState &aState,
                           nsIContent* aContent,
                           nsIWidget* aWidget,
                           InputContextAction aAction);
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -1,15 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "ContentEventHandler.h"
+#include "IMEContentObserver.h"
+#include "IMEStateManager.h"
 #include "nsContentUtils.h"
 #include "nsIContent.h"
 #include "nsIEditor.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/IMEStateManager.h"
@@ -392,18 +394,17 @@ TextComposition::DispatchCompositionEven
     // Dispatch a compositionend event if it's necessary.
     if (aCompositionEvent->mMessage != eCompositionEnd) {
       CloneAndDispatchAs(aCompositionEvent, eCompositionEnd);
     }
     MOZ_ASSERT(!mIsComposing, "Why is the editor still composing?");
     MOZ_ASSERT(!HasEditor(), "Why does the editor still keep to hold this?");
   }
 
-  // Notify composition update to widget if possible
-  NotityUpdateComposition(aCompositionEvent);
+  OnCompositionEventHandled(aCompositionEvent);
 }
 
 // static
 void
 TextComposition::HandleSelectionEvent(nsPresContext* aPresContext,
                                       TabParent* aTabParent,
                                       WidgetSelectionEvent* aSelectionEvent)
 {
@@ -421,17 +422,17 @@ TextComposition::HandleSelectionEvent(ns
   // XXX During setting selection, a selection listener may change selection
   //     again.  In such case, sHandlingSelectionEvent doesn't indicate if
   //     the selection change is caused by a selection event.  However, it
   //     must be non-realistic scenario.
   handler.OnSelectionEvent(aSelectionEvent);
 }
 
 void
-TextComposition::NotityUpdateComposition(
+TextComposition::OnCompositionEventHandled(
                    const WidgetCompositionEvent* aCompositionEvent)
 {
   MOZ_RELEASE_ASSERT(!mTabParent);
 
   nsEventStatus status;
 
   // When compositon start, notify the rect of first offset character.
   // When not compositon start, notify the rect of selected composition
@@ -451,16 +452,32 @@ TextComposition::NotityUpdateComposition
     mCompositionTargetOffset = mCompositionStartOffset;
   } else if (aCompositionEvent->CausesDOMTextEvent()) {
     mCompositionTargetOffset =
       mCompositionStartOffset + aCompositionEvent->TargetClauseOffset();
   } else {
     return;
   }
 
+  RefPtr<IMEContentObserver> contentObserver =
+    IMEStateManager::GetActiveContentObserver();
+  // When IMEContentObserver is managing the editor which has this composition,
+  // composition event handled notification should be sent after the observer
+  // notifies all pending notifications.  Therefore, we should use it.
+  // XXX If IMEContentObserver suddenly loses focus after here and notifying
+  //     widget of pending notifications, we won't notify widget of composition
+  //     event handled.  Although, this is a bug but it should be okay since
+  //     destroying IMEContentObserver notifies IME of blur.  So, native IME
+  //     handler can treat it as this notification too.
+  if (contentObserver && contentObserver->IsManaging(this)) {
+    contentObserver->MaybeNotifyCompositionEventHandled();
+    return;
+  }
+  // Otherwise, e.g., this composition is in non-active window, we should
+  // notify widget directly.
   NotifyIME(NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED);
 }
 
 void
 TextComposition::DispatchCompositionEventRunnable(EventMessage aEventMessage,
                                                   const nsAString& aData,
                                                   bool aIsSynthesizingCommit)
 {
--- a/dom/events/TextComposition.h
+++ b/dom/events/TextComposition.h
@@ -369,17 +369,18 @@ private:
    * compositionupdate, compositionend or compositionchange event due to not
    * safe to dispatch event.
    */
   void OnCompositionEventDiscarded(WidgetCompositionEvent* aCompositionEvent);
 
   /**
    * Calculate composition offset then notify composition update to widget
    */
-  void NotityUpdateComposition(const WidgetCompositionEvent* aCompositionEvent);
+  void OnCompositionEventHandled(
+         const WidgetCompositionEvent* aCompositionEvent);
 
   /**
    * CompositionEventDispatcher dispatches the specified composition (or text)
    * event.
    */
   class CompositionEventDispatcher : public Runnable
   {
   public:
--- a/widget/IMEData.h
+++ b/widget/IMEData.h
@@ -442,16 +442,21 @@ enum IMEMessage : IMEMessageType
   // Text in the focused editable content is changed
   NOTIFY_IME_OF_TEXT_CHANGE,
   // Notified when a dispatched composition event is handled by the
   // contents.  This must be notified after the other notifications.
   // Note that if a remote process has focus, this is notified only once when
   // all dispatched events are handled completely.  So, the receiver shouldn't
   // count number of received this notification for comparing with the number
   // of dispatched events.
+  // NOTE: If a composition event causes moving focus from the focused editor,
+  //       this notification may not be notified as usual.  Even in such case,
+  //       NOTIFY_IME_OF_BLUR is always sent.  So, notification listeners
+  //       should tread the blur notification as including this if there is
+  //       pending composition events.
   NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED,
   // Position or size of focused element may be changed.
   NOTIFY_IME_OF_POSITION_CHANGE,
   // Mouse button event is fired on a character in focused editor
   NOTIFY_IME_OF_MOUSE_BUTTON_EVENT,
   // Request to commit current composition to IME
   // (some platforms may not support)
   REQUEST_TO_COMMIT_COMPOSITION,