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 256912 88c746188a40b1380c9e5eadf5a568eca0d307cb
parent 256911 9a45db9e1e2830993834196e23e87b85c2baa000
child 256913 bde0d9575b8dc2e1966a6402c5de8fae093aa578
push id721
push userjlund@mozilla.com
push dateTue, 21 Apr 2015 23:03:33 +0000
treeherdermozilla-release@d27c9211ebb3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1125934
milestone38.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 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();
     });
   });
 }