Bug 961704 part.2 Add an option to nsIMEUpdatePreference which prevents to be notified selection/text changes caused by composition r=smaug
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 26 Feb 2014 09:48:02 +0900
changeset 170578 5abffd2c5d9c71baf6272840f107f49bb59536e1
parent 170577 1289de3c12fec0d4150c7294b49eec9cdfeacec7
child 170579 7ea5a64f927986436119d018cc0caca54845df79
push id40254
push usermasayuki@d-toybox.com
push dateWed, 26 Feb 2014 00:47:55 +0000
treeherdermozilla-inbound@5abffd2c5d9c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs961704
milestone30.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 961704 part.2 Add an option to nsIMEUpdatePreference which prevents to be notified selection/text changes caused by composition r=smaug
dom/events/TextComposition.cpp
dom/events/TextComposition.h
dom/events/nsIMEStateManager.cpp
dom/ipc/PBrowser.ipdl
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
widget/android/nsWindow.cpp
widget/nsIWidget.h
widget/windows/nsTextStore.cpp
widget/xpwidgets/PuppetWidget.cpp
widget/xpwidgets/PuppetWidget.h
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -21,22 +21,25 @@ using namespace mozilla::widget;
 namespace mozilla {
 
 /******************************************************************************
  * TextComposition
  ******************************************************************************/
 
 TextComposition::TextComposition(nsPresContext* aPresContext,
                                  nsINode* aNode,
-                                 WidgetGUIEvent* aEvent) :
-  mPresContext(aPresContext), mNode(aNode),
-  mNativeContext(aEvent->widget->GetInputContext().mNativeIMEContext),
-  mCompositionStartOffset(0), mCompositionTargetOffset(0),
-  mIsSynthesizedForTests(aEvent->mFlags.mIsSynthesizedForTests),
-  mIsComposing(false)
+                                 WidgetGUIEvent* aEvent)
+  : mPresContext(aPresContext)
+  , mNode(aNode)
+  , mNativeContext(aEvent->widget->GetInputContext().mNativeIMEContext)
+  , mCompositionStartOffset(0)
+  , mCompositionTargetOffset(0)
+  , mIsSynthesizedForTests(aEvent->mFlags.mIsSynthesizedForTests)
+  , mIsComposing(false)
+  , mIsEditorHandlingEvent(false)
 {
 }
 
 void
 TextComposition::Destroy()
 {
   mPresContext = nullptr;
   mNode = nullptr;
@@ -153,26 +156,28 @@ TextComposition::NotifyIME(IMEMessage aM
   NS_ENSURE_TRUE(mPresContext, NS_ERROR_NOT_AVAILABLE);
   return nsIMEStateManager::NotifyIME(aMessage, mPresContext);
 }
 
 void
 TextComposition::EditorWillHandleTextEvent(const WidgetTextEvent* aTextEvent)
 {
   mIsComposing = aTextEvent->IsComposing();
+  mIsEditorHandlingEvent = true;
 
   MOZ_ASSERT(mLastData == aTextEvent->theText,
     "The text of a text event must be same as previous data attribute value "
     "of the latest compositionupdate event");
 }
 
 void
 TextComposition::EditorDidHandleTextEvent()
 {
   mString = mLastData;
+  mIsEditorHandlingEvent = false;
 }
 
 void
 TextComposition::StartHandlingComposition(nsIEditor* aEditor)
 {
   MOZ_ASSERT(!HasEditor(), "There is a handling editor already");
   mEditorWeak = do_GetWeakReference(aEditor);
 }
--- a/dom/events/TextComposition.h
+++ b/dom/events/TextComposition.h
@@ -89,16 +89,25 @@ public:
 
   /**
    * Returns true if there is non-empty composition string and it's not fixed.
    * Otherwise, false.
    */
   bool IsComposing() const { return mIsComposing; }
 
   /**
+   * Returns true while editor is handling an event which is modifying the
+   * composition string.
+   */
+  bool IsEditorHandlingEvent() const
+  {
+    return mIsEditorHandlingEvent;
+  }
+
+  /**
    * StartHandlingComposition() and EndHandlingComposition() are called by
    * editor when it holds a TextComposition instance and release it.
    */
   void StartHandlingComposition(nsIEditor* aEditor);
   void EndHandlingComposition(nsIEditor* aEditor);
 
   /**
    * TextEventHandlingMarker class should be created at starting to handle text
@@ -156,16 +165,20 @@ private:
   uint32_t mCompositionTargetOffset;
 
   // See the comment for IsSynthesizedForTests().
   bool mIsSynthesizedForTests;
 
   // See the comment for IsComposing().
   bool mIsComposing;
 
+  // mIsEditorHandlingEvent is true while editor is modifying the composition
+  // string.
+  bool mIsEditorHandlingEvent;
+
   // Hide the default constructor and copy constructor.
   TextComposition() {}
   TextComposition(const TextComposition& aOther);
 
   /**
    * GetEditor() returns nsIEditor pointer of mEditorWeak.
    */
   already_AddRefed<nsIEditor> GetEditor() const;
--- a/dom/events/nsIMEStateManager.cpp
+++ b/dom/events/nsIMEStateManager.cpp
@@ -62,16 +62,17 @@ public:
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
 
   void     Init(nsIWidget* aWidget,
                 nsPresContext* aPresContext,
                 nsIContent* aContent);
   void     Destroy(void);
   bool     IsManaging(nsPresContext* aPresContext, nsIContent* aContent);
+  bool     IsEditorHandlingEventForComposition() const;
   bool     KeepAliveDuringDeactive() const
   {
     return mUpdatePreference.WantDuringDeactive();
   }
 
   nsCOMPtr<nsIWidget>            mWidget;
   nsCOMPtr<nsISelection>         mSel;
   nsCOMPtr<nsIContent>           mRootContent;
@@ -813,125 +814,169 @@ nsTextStateManager::IsManaging(nsPresCon
   }
   if (!mRootContent->IsInDoc()) {
     return false; // the focused editor has already been reframed.
   }
   return mEditableNode == nsIMEStateManager::GetRootEditableNode(aPresContext,
                                                                  aContent);
 }
 
+bool
+nsTextStateManager::IsEditorHandlingEventForComposition() const
+{
+  if (!mWidget) {
+    return false;
+  }
+  nsRefPtr<TextComposition> composition =
+    nsIMEStateManager::GetTextCompositionFor(mWidget);
+  if (!composition) {
+    return false;
+  }
+  return composition->IsEditorHandlingEvent();
+}
+
 NS_IMPL_ISUPPORTS2(nsTextStateManager,
                    nsIMutationObserver,
                    nsISelectionListener)
 
 // Helper class, used for selection change notification
 class SelectionChangeEvent : public nsRunnable {
 public:
-  SelectionChangeEvent(nsTextStateManager *aDispatcher)
+  SelectionChangeEvent(nsTextStateManager *aDispatcher,
+                       bool aCausedByComposition)
     : mDispatcher(aDispatcher)
+    , mCausedByComposition(aCausedByComposition)
   {
     MOZ_ASSERT(mDispatcher);
   }
 
   NS_IMETHOD Run() {
     if (mDispatcher->mWidget) {
-      mDispatcher->mWidget->NotifyIME(
-        IMENotification(NOTIFY_IME_OF_SELECTION_CHANGE));
+      IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
+      notification.mSelectionChangeData.mCausedByComposition =
+         mCausedByComposition;
+      mDispatcher->mWidget->NotifyIME(notification);
     }
     return NS_OK;
   }
 
 private:
   nsRefPtr<nsTextStateManager> mDispatcher;
+  bool mCausedByComposition;
 };
 
 nsresult
 nsTextStateManager::NotifySelectionChanged(nsIDOMDocument* aDoc,
                                            nsISelection* aSel,
                                            int16_t aReason)
 {
+  bool causedByComposition = IsEditorHandlingEventForComposition();
+  if (causedByComposition &&
+      !mUpdatePreference.WantChangesCausedByComposition()) {
+    return NS_OK;
+  }
+
   int32_t count = 0;
   nsresult rv = aSel->GetRangeCount(&count);
   NS_ENSURE_SUCCESS(rv, rv);
   if (count > 0 && mWidget) {
-    nsContentUtils::AddScriptRunner(new SelectionChangeEvent(this));
+    nsContentUtils::AddScriptRunner(
+      new SelectionChangeEvent(this, causedByComposition));
   }
   return NS_OK;
 }
 
 // Helper class, used for text change notification
 class TextChangeEvent : public nsRunnable {
 public:
   TextChangeEvent(nsTextStateManager* aDispatcher,
-                  uint32_t start, uint32_t oldEnd, uint32_t newEnd)
+                  uint32_t aStart, uint32_t aOldEnd, uint32_t aNewEnd,
+                  bool aCausedByComposition)
     : mDispatcher(aDispatcher)
-    , mStart(start)
-    , mOldEnd(oldEnd)
-    , mNewEnd(newEnd)
+    , mStart(aStart)
+    , mOldEnd(aOldEnd)
+    , mNewEnd(aNewEnd)
+    , mCausedByComposition(aCausedByComposition)
   {
     MOZ_ASSERT(mDispatcher);
   }
 
   NS_IMETHOD Run() {
     if (mDispatcher->mWidget) {
       IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
       notification.mTextChangeData.mStartOffset = mStart;
       notification.mTextChangeData.mOldEndOffset = mOldEnd;
       notification.mTextChangeData.mNewEndOffset = mNewEnd;
+      notification.mTextChangeData.mCausedByComposition = mCausedByComposition;
       mDispatcher->mWidget->NotifyIME(notification);
     }
     return NS_OK;
   }
 
 private:
   nsRefPtr<nsTextStateManager> mDispatcher;
   uint32_t mStart, mOldEnd, mNewEnd;
+  bool mCausedByComposition;
 };
 
 void
 nsTextStateManager::CharacterDataChanged(nsIDocument* aDocument,
                                          nsIContent* aContent,
                                          CharacterDataChangeInfo* aInfo)
 {
   NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT),
                "character data changed for non-text node");
 
+  bool causedByComposition = IsEditorHandlingEventForComposition();
+  if (causedByComposition &&
+      !mUpdatePreference.WantChangesCausedByComposition()) {
+    return;
+  }
+
   uint32_t offset = 0;
   // get offsets of change and fire notification
   if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
                     mRootContent, aContent, aInfo->mChangeStart, &offset)))
     return;
 
   uint32_t oldEnd = offset + aInfo->mChangeEnd - aInfo->mChangeStart;
   uint32_t newEnd = offset + aInfo->mReplaceLength;
 
   nsContentUtils::AddScriptRunner(
-      new TextChangeEvent(this, offset, oldEnd, newEnd));
+    new TextChangeEvent(this, offset, oldEnd, newEnd, causedByComposition));
 }
 
 void
 nsTextStateManager::NotifyContentAdded(nsINode* aContainer,
                                        int32_t aStartIndex,
                                        int32_t aEndIndex)
 {
+  bool causedByComposition = IsEditorHandlingEventForComposition();
+  if (causedByComposition &&
+      !mUpdatePreference.WantChangesCausedByComposition()) {
+    return;
+  }
+
   uint32_t offset = 0, newOffset = 0;
   if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
                     mRootContent, aContainer, aStartIndex, &offset)))
     return;
 
   // get offset at the end of the last added node
   if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
                     aContainer->GetChildAt(aStartIndex),
                     aContainer, aEndIndex, &newOffset)))
     return;
 
   // fire notification
-  if (newOffset)
+  if (newOffset) {
     nsContentUtils::AddScriptRunner(
-        new TextChangeEvent(this, offset, offset, offset + newOffset));
+      new TextChangeEvent(this, offset, offset, offset + newOffset,
+                          causedByComposition));
+  }
 }
 
 void
 nsTextStateManager::ContentAppended(nsIDocument* aDocument,
                                     nsIContent* aContainer,
                                     nsIContent* aFirstNewContent,
                                     int32_t aNewIndexInContainer)
 {
@@ -951,16 +996,22 @@ nsTextStateManager::ContentInserted(nsID
 
 void
 nsTextStateManager::ContentRemoved(nsIDocument* aDocument,
                                    nsIContent* aContainer,
                                    nsIContent* aChild,
                                    int32_t aIndexInContainer,
                                    nsIContent* aPreviousSibling)
 {
+  bool causedByComposition = IsEditorHandlingEventForComposition();
+  if (causedByComposition &&
+      !mUpdatePreference.WantChangesCausedByComposition()) {
+    return;
+  }
+
   uint32_t offset = 0, childOffset = 1;
   if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
                     mRootContent, NODE_FROM(aContainer, aDocument),
                     aIndexInContainer, &offset)))
     return;
 
   // get offset at the end of the deleted node
   if (aChild->IsNodeOfType(nsINode::eTEXT))
@@ -968,19 +1019,21 @@ nsTextStateManager::ContentRemoved(nsIDo
   else if (0 < aChild->GetChildCount())
     childOffset = aChild->GetChildCount();
 
   if (NS_FAILED(nsContentEventHandler::GetFlatTextOffsetOfRange(
                     aChild, aChild, childOffset, &childOffset)))
     return;
 
   // fire notification
-  if (childOffset)
+  if (childOffset) {
     nsContentUtils::AddScriptRunner(
-        new TextChangeEvent(this, offset, offset + childOffset, offset));
+      new TextChangeEvent(this, offset, offset + childOffset, offset,
+                          causedByComposition));
+  }
 }
 
 static nsIContent*
 GetContentBR(mozilla::dom::Element *aElement) {
   if (!aElement->IsNodeOfType(nsINode::eCONTENT)) {
     return nullptr;
   }
   nsIContent *content = static_cast<nsIContent*>(aElement);
@@ -1001,28 +1054,35 @@ nsTextStateManager::AttributeWillChange(
 
 void
 nsTextStateManager::AttributeChanged(nsIDocument* aDocument,
                                      mozilla::dom::Element* aElement,
                                      int32_t aNameSpaceID,
                                      nsIAtom* aAttribute,
                                      int32_t aModType)
 {
+  bool causedByComposition = IsEditorHandlingEventForComposition();
+  if (causedByComposition &&
+      !mUpdatePreference.WantChangesCausedByComposition()) {
+    return;
+  }
+
   nsIContent *content = GetContentBR(aElement);
   if (!content) {
     return;
   }
   uint32_t postAttrChangeLength =
     nsContentEventHandler::GetNativeTextLength(content);
   if (postAttrChangeLength != mPreAttrChangeLength) {
     uint32_t start;
     if (NS_SUCCEEDED(nsContentEventHandler::GetFlatTextOffsetOfRange(
         mRootContent, content, 0, &start))) {
       nsContentUtils::AddScriptRunner(new TextChangeEvent(this, start,
-        start + mPreAttrChangeLength, start + postAttrChangeLength));
+        start + mPreAttrChangeLength, start + postAttrChangeLength,
+        causedByComposition));
     }
   }
 }
 
 bool
 nsIMEStateManager::IsEditable(nsINode* node)
 {
   if (node->IsEditable()) {
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -125,21 +125,23 @@ parent:
     /**
      * Notifies chrome that there has been a change in text content
      * One call can encompass both a delete and an insert operation
      * Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates
      *
      *  offset       Starting offset of the change
      *  end          Ending offset of the range deleted
      *  newEnd       New ending offset after insertion
+     *  causedByComposition true if the change is caused by composition
      *
      *  for insertion, offset == end
      *  for deletion, offset == newEnd
      */
-    NotifyIMETextChange(uint32_t offset, uint32_t end, uint32_t newEnd);
+    NotifyIMETextChange(uint32_t offset, uint32_t end, uint32_t newEnd,
+                        bool causedByComposition);
 
     /**
      * Notifies chrome that there is a IME compostion rect updated
      *
      *  offset       The starting offset of this rect
      *  rect         The rect of first character of selected IME composition
      *  caretRect    The rect of IME caret
      */
@@ -147,18 +149,20 @@ parent:
 
     /**
      * Notifies chrome that there has been a change in selection
      * Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates
      *
      *  seqno        Current seqno value on the content side
      *  anchor       Offset where the selection started
      *  focus        Offset where the caret is
+     *  causedByComposition true if the change is caused by composition
      */
-    NotifyIMESelection(uint32_t seqno, uint32_t anchor, uint32_t focus);
+    NotifyIMESelection(uint32_t seqno, uint32_t anchor, uint32_t focus,
+                       bool causedByComposition);
 
     /**
      * Notifies chrome to refresh its text cache 
      *
      *  text         The entire content of the text field
      */
     NotifyIMETextHint(nsString text);
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1046,17 +1046,17 @@ TabParent::RecvHideTooltip()
 
 bool
 TabParent::RecvNotifyIMEFocus(const bool& aFocus,
                               nsIMEUpdatePreference* aPreference,
                               uint32_t* aSeqno)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
-    aPreference->mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING;
+    *aPreference = nsIMEUpdatePreference();
     return true;
   }
 
   *aSeqno = mIMESeqno;
   mIMETabParent = aFocus ? this : nullptr;
   mIMESelectionAnchor = 0;
   mIMESelectionFocus = 0;
   widget->NotifyIME(IMENotification(aFocus ? NOTIFY_IME_OF_FOCUS :
@@ -1068,29 +1068,37 @@ TabParent::RecvNotifyIMEFocus(const bool
     mIMECacheText.Truncate(0);
   }
   return true;
 }
 
 bool
 TabParent::RecvNotifyIMETextChange(const uint32_t& aStart,
                                    const uint32_t& aEnd,
-                                   const uint32_t& aNewEnd)
+                                   const uint32_t& aNewEnd,
+                                   const bool& aCausedByComposition)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget)
     return true;
 
-  NS_ASSERTION(widget->GetIMEUpdatePreference().WantTextChange(),
+#ifdef DEBUG
+  nsIMEUpdatePreference updatePreference = widget->GetIMEUpdatePreference();
+  NS_ASSERTION(updatePreference.WantTextChange(),
                "Don't call Send/RecvNotifyIMETextChange without NOTIFY_TEXT_CHANGE");
+  MOZ_ASSERT(!aCausedByComposition ||
+               updatePreference.WantChangesCausedByComposition(),
+    "The widget doesn't want text change notification caused by composition");
+#endif
 
   IMENotification notification(NOTIFY_IME_OF_TEXT_CHANGE);
   notification.mTextChangeData.mStartOffset = aStart;
   notification.mTextChangeData.mOldEndOffset = aEnd;
   notification.mTextChangeData.mNewEndOffset = aNewEnd;
+  notification.mTextChangeData.mCausedByComposition = aCausedByComposition;
   widget->NotifyIME(notification);
   return true;
 }
 
 bool
 TabParent::RecvNotifyIMESelectedCompositionRect(const uint32_t& aOffset,
                                                 const nsIntRect& aRect,
                                                 const nsIntRect& aCaretRect)
@@ -1106,27 +1114,35 @@ TabParent::RecvNotifyIMESelectedComposit
   }
   widget->NotifyIME(IMENotification(NOTIFY_IME_OF_COMPOSITION_UPDATE));
   return true;
 }
 
 bool
 TabParent::RecvNotifyIMESelection(const uint32_t& aSeqno,
                                   const uint32_t& aAnchor,
-                                  const uint32_t& aFocus)
+                                  const uint32_t& aFocus,
+                                  const bool& aCausedByComposition)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget)
     return true;
 
   if (aSeqno == mIMESeqno) {
     mIMESelectionAnchor = aAnchor;
     mIMESelectionFocus = aFocus;
-    if (widget->GetIMEUpdatePreference().WantSelectionChange()) {
-      widget->NotifyIME(IMENotification(NOTIFY_IME_OF_SELECTION_CHANGE));
+    const nsIMEUpdatePreference updatePreference =
+      widget->GetIMEUpdatePreference();
+    if (updatePreference.WantSelectionChange() &&
+        (updatePreference.WantChangesCausedByComposition() ||
+         !aCausedByComposition)) {
+      IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
+      notification.mSelectionChangeData.mCausedByComposition =
+        aCausedByComposition;
+      widget->NotifyIME(notification);
     }
   }
   return true;
 }
 
 bool
 TabParent::RecvNotifyIMETextHint(const nsString& aText)
 {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -144,23 +144,25 @@ public:
                                   const ClonedMessageData& aData,
                                   const InfallibleTArray<CpowEntry>& aCpows,
                                   const IPC::Principal& aPrincipal) MOZ_OVERRIDE;
     virtual bool RecvNotifyIMEFocus(const bool& aFocus,
                                     nsIMEUpdatePreference* aPreference,
                                     uint32_t* aSeqno) MOZ_OVERRIDE;
     virtual bool RecvNotifyIMETextChange(const uint32_t& aStart,
                                          const uint32_t& aEnd,
-                                         const uint32_t& aNewEnd) MOZ_OVERRIDE;
+                                         const uint32_t& aNewEnd,
+                                         const bool& aCausedByComposition) MOZ_OVERRIDE;
     virtual bool RecvNotifyIMESelectedCompositionRect(const uint32_t& aOffset,
                                                       const nsIntRect& aRect,
                                                       const nsIntRect& aCaretRect) MOZ_OVERRIDE;
     virtual bool RecvNotifyIMESelection(const uint32_t& aSeqno,
                                         const uint32_t& aAnchor,
-                                        const uint32_t& aFocus) MOZ_OVERRIDE;
+                                        const uint32_t& aFocus,
+                                        const bool& aCausedByComposition) MOZ_OVERRIDE;
     virtual bool RecvNotifyIMETextHint(const nsString& aText) MOZ_OVERRIDE;
     virtual bool RecvEndIMEComposition(const bool& aCancel,
                                        nsString* aComposition) MOZ_OVERRIDE;
     virtual bool RecvGetInputContext(int32_t* aIMEEnabled,
                                      int32_t* aIMEOpen,
                                      intptr_t* aNativeIMEContext) MOZ_OVERRIDE;
     virtual bool RecvSetInputContext(const int32_t& aIMEEnabled,
                                      const int32_t& aIMEOpen,
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -2374,19 +2374,19 @@ nsWindow::NotifyIMEOfTextChange(const IM
         srcIndex = dstIndex;
     }
     return NS_OK;
 }
 
 nsIMEUpdatePreference
 nsWindow::GetIMEUpdatePreference()
 {
-    int8_t notifications = (nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
-                            nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
-    return nsIMEUpdatePreference(notifications);
+    return nsIMEUpdatePreference(
+        nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
+        nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
 }
 
 void
 nsWindow::DrawWindowUnderlay(LayerManagerComposite* aManager, nsIntRect aRect)
 {
     JNIEnv *env = GetJNIForThread();
 
     AutoLocalJNIFrame jniFrame(env);
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -216,43 +216,70 @@ enum nsTopLevelWidgetZPlacement { // for
  * they should set mWantUpdates to NOTIFY_NOTHING to avoid the cost.
  * If the IME implementation needs notifications even while our process is
  * deactive, it should also set NOTIFY_DURING_DEACTIVE.
  */
 struct nsIMEUpdatePreference {
 
   typedef uint8_t Notifications;
 
-  enum
+  enum MOZ_ENUM_TYPE(Notifications)
   {
-    NOTIFY_NOTHING           = 0x00,
-    NOTIFY_SELECTION_CHANGE  = 0x01,
-    NOTIFY_TEXT_CHANGE       = 0x02,
-    NOTIFY_DURING_DEACTIVE   = 0x80
+    NOTIFY_NOTHING                       = 0,
+    NOTIFY_SELECTION_CHANGE              = 1 << 0,
+    NOTIFY_TEXT_CHANGE                   = 1 << 1,
+    // Following values indicate when widget needs or doesn't need notification.
+    NOTIFY_CHANGES_CAUSED_BY_COMPOSITION = 1 << 6,
+    // NOTE: NOTIFY_DURING_DEACTIVE isn't supported in environments where two
+    //       or more compositions are possible.  E.g., Mac and Linux (GTK).
+    NOTIFY_DURING_DEACTIVE               = 1 << 7,
+    // Changes are notified in following conditions if the instance is
+    // just constructed.  If some platforms don't need change notifications
+    // in some of following conditions, the platform should remove following
+    // flags before returing the instance from nsIWidget::GetUpdatePreference().
+    DEFAULT_CONDITIONS_OF_NOTIFYING_CHANGES =
+      NOTIFY_CHANGES_CAUSED_BY_COMPOSITION
   };
 
   nsIMEUpdatePreference()
-    : mWantUpdates(NOTIFY_NOTHING)
+    : mWantUpdates(DEFAULT_CONDITIONS_OF_NOTIFYING_CHANGES)
   {
   }
+
   nsIMEUpdatePreference(Notifications aWantUpdates)
-    : mWantUpdates(aWantUpdates)
+    : mWantUpdates(aWantUpdates | DEFAULT_CONDITIONS_OF_NOTIFYING_CHANGES)
   {
   }
 
+  void DontNotifyChangesCausedByComposition()
+  {
+    mWantUpdates &= ~DEFAULT_CONDITIONS_OF_NOTIFYING_CHANGES;
+  }
+
   bool WantSelectionChange() const
   {
     return !!(mWantUpdates & NOTIFY_SELECTION_CHANGE);
   }
 
   bool WantTextChange() const
   {
     return !!(mWantUpdates & NOTIFY_TEXT_CHANGE);
   }
 
+  bool WantChanges() const
+  {
+    return WantSelectionChange() || WantTextChange();
+  }
+
+  bool WantChangesCausedByComposition() const
+  {
+    return WantChanges() &&
+             !!(mWantUpdates & NOTIFY_CHANGES_CAUSED_BY_COMPOSITION);
+  }
+
   bool WantDuringDeactive() const
   {
     return !!(mWantUpdates & NOTIFY_DURING_DEACTIVE);
   }
 
   Notifications mWantUpdates;
 };
 
@@ -474,52 +501,76 @@ enum IMEMessage MOZ_ENUM_TYPE(int8_t)
 };
 
 struct IMENotification
 {
   IMENotification(IMEMessage aMessage)
     : mMessage(aMessage)
   {
     switch (aMessage) {
+      case NOTIFY_IME_OF_SELECTION_CHANGE:
+        mSelectionChangeData.mCausedByComposition = false;
+        break;
       case NOTIFY_IME_OF_TEXT_CHANGE:
         mTextChangeData.mStartOffset = 0;
         mTextChangeData.mOldEndOffset = 0;
         mTextChangeData.mNewEndOffset = 0;
+        mTextChangeData.mCausedByComposition = false;
         break;
       default:
         break;
     }
   }
 
   IMEMessage mMessage;
 
   union
   {
+    // NOTIFY_IME_OF_SELECTION_CHANGE specific data
+    struct
+    {
+      bool mCausedByComposition;
+    } mSelectionChangeData;
+
     // NOTIFY_IME_OF_TEXT_CHANGE specific data
     struct
     {
       uint32_t mStartOffset;
       uint32_t mOldEndOffset;
       uint32_t mNewEndOffset;
 
+      bool mCausedByComposition;
+
       uint32_t OldLength() const { return mOldEndOffset - mStartOffset; }
       uint32_t NewLength() const { return mNewEndOffset - mStartOffset; }
       int32_t AdditionalLength() const
       {
         return static_cast<int32_t>(mNewEndOffset - mOldEndOffset);
       }
       bool IsInInt32Range() const
       {
         return mStartOffset <= INT32_MAX &&
                mOldEndOffset <= INT32_MAX &&
                mNewEndOffset <= INT32_MAX;
       }
     } mTextChangeData;
   };
 
+  bool IsCausedByComposition() const
+  {
+    switch (mMessage) {
+      case NOTIFY_IME_OF_SELECTION_CHANGE:
+        return mSelectionChangeData.mCausedByComposition;
+      case NOTIFY_IME_OF_TEXT_CHANGE:
+        return mTextChangeData.mCausedByComposition;
+      default:
+        return false;
+    }
+  }
+
 private:
   IMENotification();
 };
 
 } // namespace widget
 } // namespace mozilla
 
 /**
--- a/widget/windows/nsTextStore.cpp
+++ b/widget/windows/nsTextStore.cpp
@@ -3166,28 +3166,31 @@ nsTextStore::OnFocusChange(bool aGotFocu
   }
   return NS_OK;
 }
 
 // static
 nsIMEUpdatePreference
 nsTextStore::GetIMEUpdatePreference()
 {
-  nsIMEUpdatePreference::Notifications notifications =
-    nsIMEUpdatePreference::NOTIFY_NOTHING;
   if (sTsfThreadMgr && sTsfTextStore && sTsfTextStore->mDocumentMgr) {
     nsRefPtr<ITfDocumentMgr> docMgr;
     sTsfThreadMgr->GetFocus(getter_AddRefs(docMgr));
     if (docMgr == sTsfTextStore->mDocumentMgr) {
-      notifications = (nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
-                       nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE |
-                       nsIMEUpdatePreference::NOTIFY_DURING_DEACTIVE);
+      nsIMEUpdatePreference updatePreference(
+        nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
+        nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE |
+        nsIMEUpdatePreference::NOTIFY_DURING_DEACTIVE);
+      // nsTextStore shouldn't notify TSF of selection change and text change
+      // which are caused by composition.
+      updatePreference.DontNotifyChangesCausedByComposition();
+      return updatePreference;
     }
   }
-  return nsIMEUpdatePreference(notifications);
+  return nsIMEUpdatePreference();
 }
 
 nsresult
 nsTextStore::OnTextChangeInternal(const IMENotification& aIMENotification)
 {
   PR_LOG(sTextStoreLog, PR_LOG_DEBUG,
          ("TSF: 0x%p   nsTextStore::OnTextChangeInternal(aIMENotification={ "
           "mMessage=0x%08X, mTextChangeData={ mStartOffset=%lu, "
--- a/widget/xpwidgets/PuppetWidget.cpp
+++ b/widget/xpwidgets/PuppetWidget.cpp
@@ -385,17 +385,17 @@ PuppetWidget::NotifyIME(const IMENotific
       return IMEEndComposition(false);
     case REQUEST_TO_CANCEL_COMPOSITION:
       return IMEEndComposition(true);
     case NOTIFY_IME_OF_FOCUS:
       return NotifyIMEOfFocusChange(true);
     case NOTIFY_IME_OF_BLUR:
       return NotifyIMEOfFocusChange(false);
     case NOTIFY_IME_OF_SELECTION_CHANGE:
-      return NotifyIMEOfSelectionChange();
+      return NotifyIMEOfSelectionChange(aIMENotification);
     case NOTIFY_IME_OF_TEXT_CHANGE:
       return NotifyIMEOfTextChange(aIMENotification);
     case NOTIFY_IME_OF_COMPOSITION_UPDATE:
       return NotifyIMEOfUpdateComposition();
     default:
       return NS_ERROR_NOT_IMPLEMENTED;
   }
 }
@@ -462,24 +462,26 @@ PuppetWidget::NotifyIMEOfFocusChange(boo
       mTabChild->SendNotifyIMETextHint(queryEvent.mReply.mString);
     }
   } else {
     // Might not have been committed composition yet
     IMEEndComposition(false);
   }
 
   uint32_t chromeSeqno;
-  mIMEPreferenceOfParent.mWantUpdates = nsIMEUpdatePreference::NOTIFY_NOTHING;
+  mIMEPreferenceOfParent = nsIMEUpdatePreference();
   if (!mTabChild->SendNotifyIMEFocus(aFocus, &mIMEPreferenceOfParent,
                                      &chromeSeqno)) {
     return NS_ERROR_FAILURE;
   }
 
   if (aFocus) {
-    NotifyIMEOfSelectionChange(); // Update selection
+    IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
+    notification.mSelectionChangeData.mCausedByComposition = false;
+    NotifyIMEOfSelectionChange(notification); // Update selection
   } else {
     mIMELastBlurSeqno = chromeSeqno;
   }
   return NS_OK;
 }
 
 nsresult
 PuppetWidget::NotifyIMEOfUpdateComposition()
@@ -526,16 +528,19 @@ PuppetWidget::GetIMEUpdatePreference()
   // B2G doesn't handle IME as widget-level.
   return nsIMEUpdatePreference();
 #endif
 }
 
 nsresult
 PuppetWidget::NotifyIMEOfTextChange(const IMENotification& aIMENotification)
 {
+  MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_TEXT_CHANGE,
+             "Passed wrong notification");
+
 #ifndef MOZ_CROSS_PROCESS_IME
   return NS_OK;
 #endif
 
   if (!mTabChild)
     return NS_ERROR_FAILURE;
 
   nsEventStatus status;
@@ -545,44 +550,53 @@ PuppetWidget::NotifyIMEOfTextChange(cons
   DispatchEvent(&queryEvent, status);
 
   if (queryEvent.mSucceeded) {
     mTabChild->SendNotifyIMETextHint(queryEvent.mReply.mString);
   }
 
   // TabParent doesn't this this to cache.  we don't send the notification
   // if parent process doesn't request NOTIFY_TEXT_CHANGE.
-  if (mIMEPreferenceOfParent.WantTextChange()) {
+  if (mIMEPreferenceOfParent.WantTextChange() &&
+      (mIMEPreferenceOfParent.WantChangesCausedByComposition() ||
+       !aIMENotification.mTextChangeData.mCausedByComposition)) {
     mTabChild->SendNotifyIMETextChange(
       aIMENotification.mTextChangeData.mStartOffset,
       aIMENotification.mTextChangeData.mOldEndOffset,
-      aIMENotification.mTextChangeData.mNewEndOffset);
+      aIMENotification.mTextChangeData.mNewEndOffset,
+      aIMENotification.mTextChangeData.mCausedByComposition);
   }
   return NS_OK;
 }
 
 nsresult
-PuppetWidget::NotifyIMEOfSelectionChange()
+PuppetWidget::NotifyIMEOfSelectionChange(
+                const IMENotification& aIMENotification)
 {
+  MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE,
+             "Passed wrong notification");
+
 #ifndef MOZ_CROSS_PROCESS_IME
   return NS_OK;
 #endif
 
   if (!mTabChild)
     return NS_ERROR_FAILURE;
 
   nsEventStatus status;
   WidgetQueryContentEvent queryEvent(true, NS_QUERY_SELECTED_TEXT, this);
   InitEvent(queryEvent, nullptr);
   DispatchEvent(&queryEvent, status);
 
   if (queryEvent.mSucceeded) {
-    mTabChild->SendNotifyIMESelection(mIMELastReceivedSeqno,
-                                      queryEvent.GetSelectionStart(),
-                                      queryEvent.GetSelectionEnd());
+    mTabChild->SendNotifyIMESelection(
+      mIMELastReceivedSeqno,
+      queryEvent.GetSelectionStart(),
+      queryEvent.GetSelectionEnd(),
+      aIMENotification.mSelectionChangeData.mCausedByComposition);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PuppetWidget::SetCursor(nsCursor aCursor)
 {
   if (mCursor == aCursor) {
--- a/widget/xpwidgets/PuppetWidget.h
+++ b/widget/xpwidgets/PuppetWidget.h
@@ -175,17 +175,17 @@ public:
 
 private:
   nsresult Paint();
 
   void SetChild(PuppetWidget* aChild);
 
   nsresult IMEEndComposition(bool aCancel);
   nsresult NotifyIMEOfFocusChange(bool aFocus);
-  nsresult NotifyIMEOfSelectionChange();
+  nsresult NotifyIMEOfSelectionChange(const IMENotification& aIMENotification);
   nsresult NotifyIMEOfUpdateComposition();
   nsresult NotifyIMEOfTextChange(const IMENotification& aIMENotification);
 
   class PaintTask : public nsRunnable {
   public:
     NS_DECL_NSIRUNNABLE
     PaintTask(PuppetWidget* widget) : mWidget(widget) {}
     void Revoke() { mWidget = nullptr; }