Bug 1125934 - Discard redundant NS_COMPOSITION_CHANGE event which is send just before NS_COMPOSITION_END on TSF. r=masayuki
☠☠ backed out by 1bd47ee5fde9 ☠ ☠
authorTooru Fujisawa <arai_a@mac.com>
Mon, 09 Feb 2015 21:19:43 +0900
changeset 241651 88c746188a40b1380c9e5eadf5a568eca0d307cb
parent 241650 9a45db9e1e2830993834196e23e87b85c2baa000
child 241652 bde0d9575b8dc2e1966a6402c5de8fae093aa578
push id611
push userdburns@mozilla.com
push dateMon, 09 Feb 2015 13:50:10 +0000
reviewersmasayuki
bugs1125934
milestone38.0a1
Bug 1125934 - Discard redundant NS_COMPOSITION_CHANGE event which is send just before NS_COMPOSITION_END on TSF. r=masayuki
dom/events/TextComposition.cpp
widget/TextRange.h
widget/tests/window_composition_text_querycontent.xul
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -219,16 +219,28 @@ TextComposition::DispatchCompositionEven
   // 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;
   }
 
+  // widget may dispatch redundant NS_COMPOSITION_CHANGE event
+  // which modifies neither composition string, clauses nor caret
+  // position.  In such case, we shouldn't dispatch DOM events.
+  if (dispatchDOMTextEvent &&
+      aCompositionEvent->message == NS_COMPOSITION_CHANGE &&
+      mLastData == aCompositionEvent->mData &&
+      ((!mRanges && !aCompositionEvent->mRanges) ||
+       (mRanges && aCompositionEvent->mRanges &&
+        mRanges->Equals(*aCompositionEvent->mRanges)))) {
+    dispatchEvent = dispatchDOMTextEvent = false;
+  }
+
   if (dispatchDOMTextEvent) {
     if (!MaybeDispatchCompositionUpdate(aCompositionEvent)) {
       return;
     }
   }
 
   if (dispatchEvent) {
     // If the composition event should cause a DOM text event, we should
--- a/widget/TextRange.h
+++ b/widget/TextRange.h
@@ -79,17 +79,17 @@ struct TextRangeStyle
   }
 
   bool IsNoChangeStyle() const
   {
     return !IsForegroundColorDefined() && !IsBackgroundColorDefined() &&
            IsLineStyleDefined() && mLineStyle == LINESTYLE_NONE;
   }
 
-  bool Equals(const TextRangeStyle& aOther)
+  bool Equals(const TextRangeStyle& aOther) const
   {
     if (mDefinedStyles != aOther.mDefinedStyles)
       return false;
     if (IsLineStyleDefined() && (mLineStyle != aOther.mLineStyle ||
                                  !mIsBoldLine != !aOther.mIsBoldLine))
       return false;
     if (IsForegroundColorDefined() &&
         (mForegroundColor != aOther.mForegroundColor))
@@ -98,22 +98,22 @@ struct TextRangeStyle
         (mBackgroundColor != aOther.mBackgroundColor))
       return false;
     if (IsUnderlineColorDefined() &&
         (mUnderlineColor != aOther.mUnderlineColor))
       return false;
     return true;
   }
 
-  bool operator !=(const TextRangeStyle &aOther)
+  bool operator !=(const TextRangeStyle &aOther) const
   {
     return !Equals(aOther);
   }
 
-  bool operator ==(const TextRangeStyle &aOther)
+  bool operator ==(const TextRangeStyle &aOther) const
   {
     return Equals(aOther);
   }
 
   uint8_t mDefinedStyles;
   uint8_t mLineStyle;        // DEFINED_LINESTYLE
 
   bool mIsBoldLine;  // DEFINED_LINESTYLE
@@ -161,16 +161,24 @@ struct TextRange
 
   bool IsClause() const
   {
     MOZ_ASSERT(mRangeType >= NS_TEXTRANGE_CARETPOSITION &&
                  mRangeType <= NS_TEXTRANGE_SELECTEDCONVERTEDTEXT,
                "Invalid range type");
     return mRangeType != NS_TEXTRANGE_CARETPOSITION;
   }
+
+  bool Equals(const TextRange& aOther) const
+  {
+    return mStartOffset == aOther.mStartOffset &&
+           mEndOffset == aOther.mEndOffset &&
+           mRangeType == aOther.mRangeType &&
+           mRangeStyle == aOther.mRangeStyle;
+  }
 };
 
 /******************************************************************************
  * mozilla::TextRangeArray
  ******************************************************************************/
 class TextRangeArray MOZ_FINAL : public nsAutoTArray<TextRange, 10>
 {
   ~TextRangeArray() {}
@@ -196,13 +204,27 @@ public:
       const TextRange& range = ElementAt(i);
       if (range.mRangeType == NS_TEXTRANGE_SELECTEDRAWTEXT ||
           range.mRangeType == NS_TEXTRANGE_SELECTEDCONVERTEDTEXT) {
         return range.mStartOffset;
       }
     }
     return 0;
   }
+
+  bool Equals(const TextRangeArray& aOther) const
+  {
+    size_t len = Length();
+    if (len != aOther.Length()) {
+      return false;
+    }
+    for (size_t i = 0; i < len; i++) {
+      if (!ElementAt(i).Equals(aOther.ElementAt(i))) {
+        return false;
+      }
+    }
+    return true;
+  }
 };
 
 } // namespace mozilla
 
 #endif // mozilla_TextRage_h_
--- a/widget/tests/window_composition_text_querycontent.xul
+++ b/widget/tests/window_composition_text_querycontent.xul
@@ -2973,16 +2973,103 @@ function runIsComposingTest()
   textarea.removeEventListener("keyup", eventHandler, true);
   textarea.removeEventListener("input", eventHandler, true);
   textarea.removeEventListener("compositionstart", onComposition, true);
   textarea.removeEventListener("compositionend", onComposition, true);
 
   textarea.value = "";
 }
 
+function runRedundantChangeTest()
+{
+  textarea.focus();
+
+  var result = {};
+  function clearResult()
+  {
+    result = { compositionupdate: false, compositionend: false, text: false, input: false };
+  }
+
+  function handler(aEvent)
+  {
+    result[aEvent.type] = true;
+  }
+
+  textarea.addEventListener("compositionupdate", handler, true);
+  textarea.addEventListener("compositionend", handler, true);
+  textarea.addEventListener("input", handler, true);
+  textarea.addEventListener("text", handler, true);
+
+  textarea.value = "";
+
+  // synthesize change event
+  clearResult();
+  synthesizeCompositionChange(
+    { "composition":
+      { "string": "\u3042",
+        "clauses":
+        [
+          { "length": 1, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+        ]
+      },
+      "caret": { "start": 1, "length": 0 }
+    });
+
+  is(result.compositionupdate, true, "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #1");
+  is(result.compositionend, false, "runRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change #1");
+  is(result.text, true, "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #1");
+  is(result.input, true, "runRedundantChangeTest: input should be fired after synthesizing composition change #1");
+  is(textarea.value, "\u3042", "runRedundantChangeTest: textarea has uncommitted string #1");
+
+  // synthesize another change event
+  clearResult();
+  synthesizeCompositionChange(
+    { "composition":
+      { "string": "\u3042\u3044",
+        "clauses":
+        [
+          { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+        ]
+      },
+      "caret": { "start": 2, "length": 0 }
+    });
+
+  is(result.compositionupdate, true, "runRedundantChangeTest: compositionupdate should be fired after synthesizing composition change #2");
+  is(result.compositionend, false, "runRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change #2");
+  is(result.text, true, "runRedundantChangeTest: text should be fired after synthesizing composition change because it's dispatched when there is composing string #2");
+  is(result.input, true, "runRedundantChangeTest: input should be fired after synthesizing composition change #2");
+  is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #2");
+
+  // synthesize same change event again
+  clearResult();
+  synthesizeCompositionChange(
+    { "composition":
+      { "string": "\u3042\u3044",
+        "clauses":
+        [
+          { "length": 2, "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
+        ]
+      },
+      "caret": { "start": 2, "length": 0 }
+    });
+
+  is(result.compositionupdate, false, "runRedundantChangeTest: compositionupdate shouldn't be fired after synthesizing composition change again");
+  is(result.compositionend, false, "runRedundantChangeTest: compositionend shouldn't be fired after synthesizing composition change again");
+  is(result.text, false, "runRedundantChangeTest: text shouldn't be fired after synthesizing composition change again because it's dispatched when there is composing string");
+  is(result.input, false, "runRedundantChangeTest: input shouldn't be fired after synthesizing composition change again");
+  is(textarea.value, "\u3042\u3044", "runRedundantChangeTest: textarea has uncommitted string #3");
+
+  synthesizeComposition({ type: "compositioncommit" });
+
+  textarea.removeEventListener("compositionupdate", handler, true);
+  textarea.removeEventListener("compositionend", handler, true);
+  textarea.removeEventListener("input", handler, true);
+  textarea.removeEventListener("text", handler, true);
+}
+
 function runRemoveContentTest(aCallback)
 {
   var events = [];
   function eventHandler(aEvent)
   {
     events.push(aEvent);
   }
   textarea.addEventListener("compositionstart", eventHandler, true);
@@ -3545,16 +3632,17 @@ function runTest()
   runCompositionTest();
   runCompositionEventTest();
   runCharAtPointTest(textarea, "textarea in the document");
   runCharAtPointAtOutsideTest();
   runBug722639Test();
   runForceCommitTest();
   runBug811755Test();
   runIsComposingTest();
+  runRedundantChangeTest();
   runAsyncForceCommitTest(function () {
     runRemoveContentTest(function () {
       runFrameTest();
       runPanelTest();
       runMaxLengthTest();
     });
   });
 }