Bug 1723125 - Ignore normal selection when updating composition string r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 02 Aug 2021 08:23:50 +0000
changeset 587439 9a4c55524395f5278778f33365a4298b82db6ac7
parent 587438 3bdd88ac12977aa07cc442daaa1acf3db56dd5a3
child 587440 cd291d8753049f6a1e1c4b36f46a1c4abdd6bc73
push id38665
push usersmolnar@mozilla.com
push dateMon, 02 Aug 2021 15:51:02 +0000
treeherdermozilla-central@d3e0ca7e2145 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1723125
milestone92.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 1723125 - Ignore normal selection when updating composition string r=m_kato Web apps can modify normal selection even during IME composition and no browsers stop composition by it. However, our editor tries to delete non-collapsed selected range before updating composition. Therefore, we need additional state at handling inserting text whether selection should be deleted or ignored. Depends on D121371 Differential Revision: https://phabricator.services.mozilla.com/D121372
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
editor/libeditor/HTMLEditSubActionHandler.cpp
editor/libeditor/HTMLEditor.h
editor/libeditor/HTMLEditorDataTransfer.cpp
editor/libeditor/TextEditSubActionHandler.cpp
editor/libeditor/TextEditor.cpp
editor/libeditor/TextEditor.h
editor/libeditor/TextEditorDataTransfer.cpp
widget/tests/test_composition_text_querycontent.xhtml
widget/tests/window_composition_text_querycontent.xhtml
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -1780,17 +1780,17 @@ nsresult EditorBase::InsertTextAt(const 
   MOZ_ASSERT(aPointToInsert.IsSet());
 
   nsresult rv = PrepareToInsertContent(aPointToInsert, aDoDeleteSelection);
   if (NS_FAILED(rv)) {
     NS_WARNING("EditorBase::PrepareToInsertContent() failed");
     return rv;
   }
 
-  rv = InsertTextAsSubAction(aStringToInsert);
+  rv = InsertTextAsSubAction(aStringToInsert, SelectionHandling::Delete);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "EditorBase::InsertTextAsSubAction() failed");
   return rv;
 }
 
 bool EditorBase::IsSafeToInsertData(const Document* aSourceDoc) const {
   // Try to determine whether we should use a sanitizing fragment sink
   bool isSafe = false;
@@ -3645,16 +3645,17 @@ nsresult EditorBase::OnCompositionChange
   //       need to ignore selection changes caused by composition.  Therefore,
   //       CompositionChangeEventHandlingMarker must be destroyed after a call
   //       of NotifiyEditorObservers(eNotifyEditorObserversOfEnd) or
   //       NotifiyEditorObservers(eNotifyEditorObserversOfCancel) which notifies
   //       TextComposition of a selection change.
   MOZ_ASSERT(
       !mPlaceholderBatch,
       "UpdateIMEComposition() must be called without place holder batch");
+  bool wasComposing = mComposition->IsComposing();
   TextComposition::CompositionChangeEventHandlingMarker
       compositionChangeEventHandlingMarker(mComposition,
                                            &aCompositionChangeEvent);
 
   RefPtr<nsCaret> caret = GetCaret();
 
   {
     AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::IMETxnName,
@@ -3662,17 +3663,20 @@ nsresult EditorBase::OnCompositionChange
 
     MOZ_ASSERT(
         mIsInEditSubAction,
         "AutoPlaceholderBatch should've notified the observes of before-edit");
     nsString data(aCompositionChangeEvent.mData);
     if (IsHTMLEditor()) {
       nsContentUtils::PlatformToDOMLineBreaks(data);
     }
-    rv = InsertTextAsSubAction(data);
+    // If we're updating composition, we need to ignore normal selection
+    // which may be updated by the web content.
+    rv = InsertTextAsSubAction(data, wasComposing ? SelectionHandling::Ignore
+                                                  : SelectionHandling::Delete);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                          "EditorBase::InsertTextAsSubAction() failed");
 
     if (caret) {
       caret->SetSelection(&SelectionRef());
     }
   }
 
@@ -4995,17 +4999,17 @@ nsresult EditorBase::OnInputText(const n
   if (NS_FAILED(rv)) {
     NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
                          "CanHandleAndMaybeDispatchBeforeInputEvent() failed");
     return EditorBase::ToGenericNSResult(rv);
   }
 
   AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
                                              ScrollSelectionIntoView::Yes);
-  rv = InsertTextAsSubAction(aStringToInsert);
+  rv = InsertTextAsSubAction(aStringToInsert, SelectionHandling::Delete);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "EditorBase::InsertTextAsSubAction() failed");
   return EditorBase::ToGenericNSResult(rv);
 }
 
 nsresult EditorBase::ReplaceTextAsAction(
     const nsAString& aString, nsRange* aReplaceRange,
     AllowBeforeInputEventCancelable aAllowBeforeInputEventCancelable,
@@ -5124,17 +5128,17 @@ nsresult EditorBase::ReplaceSelectionAsS
         nsIEditor::eNone,
         IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip);
     NS_WARNING_ASSERTION(
         NS_SUCCEEDED(rv),
         "EditorBase::DeleteSelectionAsSubAction(eNone) failed");
     return rv;
   }
 
-  nsresult rv = InsertTextAsSubAction(aString);
+  nsresult rv = InsertTextAsSubAction(aString, SelectionHandling::Delete);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "EditorBase::InsertTextAsSubAction() failed");
   return rv;
 }
 
 nsresult EditorBase::HandleInlineSpellCheck(
     const EditorDOMPoint& aPreviouslySelectedStart,
     const AbstractRange* aRange) {
@@ -5958,47 +5962,54 @@ nsresult EditorBase::InsertTextAsAction(
   }
 
   nsString stringToInsert(aStringToInsert);
   if (IsTextEditor()) {
     nsContentUtils::PlatformToDOMLineBreaks(stringToInsert);
   }
   AutoPlaceholderBatch treatAsOneTransaction(*this,
                                              ScrollSelectionIntoView::Yes);
-  rv = InsertTextAsSubAction(stringToInsert);
+  rv = InsertTextAsSubAction(stringToInsert, SelectionHandling::Delete);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "EditorBase::InsertTextAsSubAction() failed");
   return EditorBase::ToGenericNSResult(rv);
 }
 
-nsresult EditorBase::InsertTextAsSubAction(const nsAString& aStringToInsert) {
+nsresult EditorBase::InsertTextAsSubAction(
+    const nsAString& aStringToInsert, SelectionHandling aSelectionHandling) {
   MOZ_ASSERT(IsEditActionDataAvailable());
   MOZ_ASSERT(mPlaceholderBatch);
   MOZ_ASSERT(IsHTMLEditor() ||
              aStringToInsert.FindChar(nsCRT::CR) == kNotFound);
+  MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore, mComposition);
 
   if (NS_WARN_IF(!mInitSucceeded)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
+  if (NS_WARN_IF(Destroyed())) {
+    return NS_ERROR_EDITOR_DESTROYED;
+  }
+
   EditSubAction editSubAction = ShouldHandleIMEComposition()
                                     ? EditSubAction::eInsertTextComingFromIME
                                     : EditSubAction::eInsertText;
 
   IgnoredErrorResult ignoredError;
   AutoEditSubActionNotifier startToHandleEditSubAction(
       *this, editSubAction, nsIEditor::eNext, ignoredError);
   if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
     return ignoredError.StealNSResult();
   }
   NS_WARNING_ASSERTION(
       !ignoredError.Failed(),
       "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 
-  EditActionResult result = HandleInsertText(editSubAction, aStringToInsert);
+  EditActionResult result =
+      HandleInsertText(editSubAction, aStringToInsert, aSelectionHandling);
   NS_WARNING_ASSERTION(result.Succeeded(),
                        "EditorBase::HandleInsertText() failed");
   return result.Rv();
 }
 
 NS_IMETHODIMP EditorBase::InsertLineBreak() {
   AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak);
   nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -1659,19 +1659,22 @@ class EditorBase : public nsIEditor,
   [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
   OnInputText(const nsAString& aStringToInsert);
 
   /**
    * InsertTextAsSubAction() inserts aStringToInsert at selection.  This
    * should be used for handling it as an edit sub-action.
    *
    * @param aStringToInsert     The string to insert.
+   * @param aSelectionHandling  Specify whether selected content should be
+   *                            deleted or ignored.
    */
-  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
-  InsertTextAsSubAction(const nsAString& aStringToInsert);
+  enum class SelectionHandling { Ignore, Delete };
+  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertTextAsSubAction(
+      const nsAString& aStringToInsert, SelectionHandling aSelectionHandling);
 
   /**
    * InsertTextWithTransaction() inserts aStringToInsert to aPointToInsert or
    * better insertion point around it.  If aPointToInsert isn't in a text node,
    * this method looks for the nearest point in a text node with
    * FindBetterInsertionPoint().  If there is no text node, this creates
    * new text node and put aStringToInsert to it.
    *
@@ -2045,19 +2048,22 @@ class EditorBase : public nsIEditor,
   ReplaceSelectionAsSubAction(const nsAString& aString);
 
   /**
    * HandleInsertText() handles inserting text at selection.
    *
    * @param aEditSubAction      Must be EditSubAction::eInsertText or
    *                            EditSubAction::eInsertTextComingFromIME.
    * @param aInsertionString    String to be inserted at selection.
+   * @param aSelectionHandling  Specify whether selected content should be
+   *                            deleted or ignored.
    */
   [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual EditActionResult HandleInsertText(
-      EditSubAction aEditSubAction, const nsAString& aInsertionString) = 0;
+      EditSubAction aEditSubAction, const nsAString& aInsertionString,
+      SelectionHandling aSelectionHandling) = 0;
 
   /**
    * InsertWithQuotationsAsSubAction() inserts aQuotedText with appending ">"
    * to start of every line.
    *
    * @param aQuotedText         String to insert.  This will be quoted by ">"
    *                            automatically.
    */
--- a/editor/libeditor/HTMLEditSubActionHandler.cpp
+++ b/editor/libeditor/HTMLEditSubActionHandler.cpp
@@ -921,33 +921,37 @@ nsresult HTMLEditor::PrepareInlineStyles
   // exceptions
   if (!IsStyleCachePreservingSubAction(GetTopLevelEditSubAction())) {
     TopLevelEditSubActionDataRef().mCachedInlineStyles->Clear();
   }
   return NS_OK;
 }
 
 EditActionResult HTMLEditor::HandleInsertText(
-    EditSubAction aEditSubAction, const nsAString& aInsertionString) {
+    EditSubAction aEditSubAction, const nsAString& aInsertionString,
+    SelectionHandling aSelectionHandling) {
   MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
   MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText ||
              aEditSubAction == EditSubAction::eInsertTextComingFromIME);
+  MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore,
+                aEditSubAction == EditSubAction::eInsertTextComingFromIME);
 
   EditActionResult result = CanHandleHTMLEditSubAction();
   if (result.Failed() || result.Canceled()) {
     NS_WARNING_ASSERTION(result.Succeeded(),
                          "HTMLEditor::CanHandleHTMLEditSubAction() failed");
     return result;
   }
 
   UndefineCaretBidiLevel();
 
   // If the selection isn't collapsed, delete it.  Don't delete existing inline
   // tags, because we're hopefully going to insert text (bug 787432).
-  if (!SelectionRef().IsCollapsed()) {
+  if (!SelectionRef().IsCollapsed() &&
+      aSelectionHandling == SelectionHandling::Delete) {
     nsresult rv =
         DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
     if (NS_FAILED(rv)) {
       NS_WARNING(
           "EditorBase::DeleteSelectionAsSubAction(nsIEditor::eNone, "
           "nsIEditor::eNoStrip) failed");
       return EditActionHandled(rv);
     }
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -1059,17 +1059,18 @@ class HTMLEditor final : public EditorBa
    * PrepareInlineStylesForCaret() consider inline styles from top level edit
    * sub-action and setting it to `mTypeInState` and clear inline style cache
    * if necessary.
    * NOTE: This method should be called only when `Selection` is collapsed.
    */
   [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult PrepareInlineStylesForCaret();
 
   [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult HandleInsertText(
-      EditSubAction aEditSubAction, const nsAString& aInsertionString) final;
+      EditSubAction aEditSubAction, const nsAString& aInsertionString,
+      SelectionHandling aSelectionHandling) final;
 
   [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertDroppedDataTransferAsAction(
       AutoEditActionDataSetter& aEditActionData,
       dom::DataTransfer& aDataTransfer, const EditorDOMPoint& aDroppedAt,
       dom::Document* aSrcDocument) final;
 
   /**
    * GetInlineStyles() retrieves the style of aNode and modifies each item of
--- a/editor/libeditor/HTMLEditorDataTransfer.cpp
+++ b/editor/libeditor/HTMLEditorDataTransfer.cpp
@@ -1835,17 +1835,18 @@ nsresult HTMLEditor::InsertFromTransfera
           nsresult rv = DoInsertHTMLWithContext(
               stuffToPaste, aContextStr, aInfoStr, flavor, aSourceDoc,
               EditorDOMPoint(), aDoDeleteSelection, isSafe);
           if (NS_FAILED(rv)) {
             NS_WARNING("HTMLEditor::DoInsertHTMLWithContext() failed");
             return rv;
           }
         } else {
-          nsresult rv = InsertTextAsSubAction(stuffToPaste);
+          nsresult rv =
+              InsertTextAsSubAction(stuffToPaste, SelectionHandling::Delete);
           if (NS_FAILED(rv)) {
             NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
             return rv;
           }
         }
       }
     }
   }
@@ -2620,17 +2621,17 @@ nsresult HTMLEditor::InsertWithQuotation
         return NS_ERROR_EDITOR_DESTROYED;
       }
       NS_WARNING_ASSERTION(
           NS_SUCCEEDED(rv),
           "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
     }
   }
 
-  rv = InsertTextAsSubAction(quotedStuff);
+  rv = InsertTextAsSubAction(quotedStuff, SelectionHandling::Delete);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "EditorBase::InsertTextAsSubAction() failed");
   return rv;
 }
 
 nsresult HTMLEditor::InsertTextWithQuotations(
     const nsAString& aStringToInsert) {
   AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText);
@@ -2735,17 +2736,17 @@ nsresult HTMLEditor::InsertTextWithQuota
           InsertAsPlaintextQuotation(curHunk, false, getter_AddRefs(dummyNode));
       if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
         return NS_ERROR_EDITOR_DESTROYED;
       }
       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                            "HTMLEditor::InsertAsPlaintextQuotation() failed, "
                            "but might be ignored");
     } else {
-      rv = InsertTextAsSubAction(curHunk);
+      rv = InsertTextAsSubAction(curHunk, SelectionHandling::Delete);
       NS_WARNING_ASSERTION(
           NS_SUCCEEDED(rv),
           "EditorBase::InsertTextAsSubAction() failed, but might be ignored");
     }
     if (!found) {
       break;
     }
     curHunkIsQuoted = quoted;
@@ -2914,17 +2915,17 @@ nsresult HTMLEditor::InsertAsPlaintextQu
 
   if (aAddCites) {
     rv = InsertWithQuotationsAsSubAction(aQuotedText);
     if (NS_FAILED(rv)) {
       NS_WARNING("HTMLEditor::InsertWithQuotationsAsSubAction() failed");
       return rv;
     }
   } else {
-    rv = InsertTextAsSubAction(aQuotedText);
+    rv = InsertTextAsSubAction(aQuotedText, SelectionHandling::Delete);
     if (NS_FAILED(rv)) {
       NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
       return rv;
     }
   }
 
   // XXX Why don't we check this before inserting the quoted text?
   if (!newSpanElement) {
@@ -3166,17 +3167,18 @@ nsresult HTMLEditor::InsertAsCitedQuotat
     if (NS_WARN_IF(Destroyed())) {
       return NS_ERROR_EDITOR_DESTROYED;
     }
     if (NS_FAILED(rv)) {
       NS_WARNING("HTMLEditor::LoadHTML() failed");
       return rv;
     }
   } else {
-    rv = InsertTextAsSubAction(aQuotedText);  // XXX ignore charset
+    rv = InsertTextAsSubAction(
+        aQuotedText, SelectionHandling::Delete);  // XXX ignore charset
     if (NS_WARN_IF(Destroyed())) {
       return NS_ERROR_EDITOR_DESTROYED;
     }
     if (NS_FAILED(rv)) {
       NS_WARNING("HTMLEditor::LoadHTML() failed");
       return rv;
     }
   }
--- a/editor/libeditor/TextEditSubActionHandler.cpp
+++ b/editor/libeditor/TextEditSubActionHandler.cpp
@@ -333,20 +333,23 @@ void TextEditor::HandleNewLinesInStringF
     case nsIEditor::eNewlinesPasteIntact:
       // even if we're pasting newlines, don't paste leading/trailing ones
       aString.Trim(LFSTR, true, true);
       break;
   }
 }
 
 EditActionResult TextEditor::HandleInsertText(
-    EditSubAction aEditSubAction, const nsAString& aInsertionString) {
+    EditSubAction aEditSubAction, const nsAString& aInsertionString,
+    SelectionHandling aSelectionHandling) {
   MOZ_ASSERT(IsEditActionDataAvailable());
   MOZ_ASSERT(aEditSubAction == EditSubAction::eInsertText ||
              aEditSubAction == EditSubAction::eInsertTextComingFromIME);
+  MOZ_ASSERT_IF(aSelectionHandling == SelectionHandling::Ignore,
+                aEditSubAction == EditSubAction::eInsertTextComingFromIME);
 
   UndefineCaretBidiLevel();
 
   if (aInsertionString.IsEmpty() &&
       aEditSubAction != EditSubAction::eInsertTextComingFromIME) {
     // HACK: this is a fix for bug 19395
     // I can't outlaw all empty insertions
     // because IME transaction depend on them
@@ -379,17 +382,18 @@ EditActionResult TextEditor::HandleInser
     } else {
       uint32_t end = 0;
       nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
                                                 start, end);
     }
   }
 
   // if the selection isn't collapsed, delete it.
-  if (!SelectionRef().IsCollapsed()) {
+  if (!SelectionRef().IsCollapsed() &&
+      aSelectionHandling == SelectionHandling::Delete) {
     nsresult rv =
         DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip);
     if (NS_FAILED(rv)) {
       NS_WARNING(
           "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
       return EditActionHandled(rv);
     }
   }
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -578,17 +578,17 @@ nsresult TextEditor::InsertWithQuotation
   NS_WARNING_ASSERTION(
       !ignoredError.Failed(),
       "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
 
   // XXX Do we need to support paste-as-quotation in password editor (and
   //     also in single line editor)?
   MaybeDoAutoPasswordMasking();
 
-  rv = InsertTextAsSubAction(quotedStuff);
+  rv = InsertTextAsSubAction(quotedStuff, SelectionHandling::Delete);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "EditorBase::InsertTextAsSubAction() failed");
   return rv;
 }
 
 nsresult TextEditor::SelectEntireDocument() {
   MOZ_ASSERT(IsEditActionDataAvailable());
 
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -412,17 +412,18 @@ class TextEditor final : public EditorBa
    *   nsIEditor::eNewlinesPasteToFirst (1) or any other value:
    *     remove the first newline and all characters following it.
    *
    * @param aString the string to be modified in place.
    */
   void HandleNewLinesInStringForSingleLineEditor(nsString& aString) const;
 
   [[nodiscard]] MOZ_CAN_RUN_SCRIPT EditActionResult HandleInsertText(
-      EditSubAction aEditSubAction, const nsAString& aInsertionString) final;
+      EditSubAction aEditSubAction, const nsAString& aInsertionString,
+      SelectionHandling aSelectionHandling) final;
 
   [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertDroppedDataTransferAsAction(
       AutoEditActionDataSetter& aEditActionData,
       dom::DataTransfer& aDataTransfer, const EditorDOMPoint& aDroppedAt,
       dom::Document* aSrcDocument) final;
 
   /**
    * HandleDeleteSelectionInternal() is a helper method of
--- a/editor/libeditor/TextEditorDataTransfer.cpp
+++ b/editor/libeditor/TextEditorDataTransfer.cpp
@@ -73,17 +73,18 @@ nsresult TextEditor::InsertTextFromTrans
     }
 
     if (!stuffToPaste.IsEmpty()) {
       // Sanitize possible carriage returns in the string to be inserted
       nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
 
       AutoPlaceholderBatch treatAsOneTransaction(*this,
                                                  ScrollSelectionIntoView::Yes);
-      nsresult rv = InsertTextAsSubAction(stuffToPaste);
+      nsresult rv =
+          InsertTextAsSubAction(stuffToPaste, SelectionHandling::Delete);
       if (NS_FAILED(rv)) {
         NS_WARNING("EditorBase::InsertTextAsSubAction() failed");
         return rv;
       }
     }
   }
 
   // Try to scroll the selection into view if the paste/drop succeeded
--- a/widget/tests/test_composition_text_querycontent.xhtml
+++ b/widget/tests/test_composition_text_querycontent.xhtml
@@ -19,17 +19,17 @@
 <![CDATA[
 
 // 3 assertions are: If setting selection with eSetSelection event whose range
 // is larger than the actual range, hits "Can only call this on frames that have
 // been reflowed:
 // '!(GetStateBits() & NS_FRAME_FIRST_REFLOW) || (GetParent()->GetStateBits() &
 // NS_FRAME_TOO_DEEP_IN_FRAME_TREE)'" in nsTextFrame.cpp.
 // Strangely, this doesn't occur with RDP on Windows.
-// 12 assertions are: assertions in WSRunScanner::TextFragmentData::GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace()
-SimpleTest.expectAssertions(0, 3 + 12);
+// 16 assertions are: assertions in WSRunScanner::TextFragmentData::GetInclusiveNextNBSPPointIfNeedToReplaceWithASCIIWhiteSpace()
+SimpleTest.expectAssertions(0, 3 + 16);
 SimpleTest.waitForExplicitFinish();
 window.openDialog("window_composition_text_querycontent.xhtml", "_blank", 
                   "chrome,width=600,height=600,noopener", window);
 
 ]]>
 </script>
 </window>
--- a/widget/tests/window_composition_text_querycontent.xhtml
+++ b/widget/tests/window_composition_text_querycontent.xhtml
@@ -5703,16 +5703,125 @@ function runBug722639Test()
           !checkRectArray(currentLineLFAsArray, [currentLineLF], "runBug722639Test: " + i + "th line's rect as array should match with text rect result")) {
         return;
       }
     }
     previousTop = currentLine.top;
   }
 }
 
+function runCompositionWithSelectionChange() {
+  function doTest(aEditor, aDescription) {
+    aEditor.focus();
+    const isHTMLEditor =
+      aEditor.nodeName.toLowerCase() != "input" && aEditor.nodeName.toLowerCase() != "textarea";
+    const win = isHTMLEditor ? windowOfContenteditable : window;
+    function getValue() {
+      return isHTMLEditor ? aEditor.innerHTML : aEditor.value;
+    }
+    function setSelection(aStart, aLength) {
+      if (isHTMLEditor) {
+        win.getSelection().setBaseAndExtent(aEditor.firstChild, aStart, aEditor.firstChild, aStart + aLength);
+      } else {
+        aEditor.setSelectionRange(aStart, aStart + aLength);
+      }
+    }
+
+    if (isHTMLEditor) {
+      aEditor.innerHTML = "abcxyz";
+    } else {
+      aEditor.value = "abcxyz";
+    }
+    setSelection("abc".length, 0);
+
+    synthesizeCompositionChange({
+      composition: {
+        string: "1",
+        clauses: [{ length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+        caret: { start: 1, length: 0 },
+      }
+    });
+
+    is(getValue(), "abc1xyz",
+      `${aDescription}: First composing character should be inserted middle of the text`);
+
+    aEditor.addEventListener("compositionupdate", () => {
+      setSelection("abc".length, "1".length);
+    }, {once: true});
+
+    synthesizeCompositionChange({
+      composition: {
+        string: "12",
+        clauses: [{ length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+        caret: { start: 2, length: 0 },
+      }
+    });
+
+    is(getValue(), "abc12xyz",
+      `${aDescription}: Only composition string should be updated even if selection range is updated by "compositionupdate" event listener`);
+
+    aEditor.addEventListener("compositionupdate", () => {
+      setSelection("abc1".length, "2d".length);
+    }, {once: true});
+
+    synthesizeCompositionChange({
+      composition: {
+        string: "123",
+        clauses: [{ length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+        caret: { start: 3, length: 0 },
+      }
+    });
+
+    is(getValue(), "abc123xyz",
+      `${aDescription}: Only composition string should be updated even if selection range wider than composition string is updated by "compositionupdate" event listener`);
+
+    aEditor.addEventListener("compositionupdate", () => {
+      setSelection("ab".length, "c123d".length);
+    }, {once: true});
+
+    synthesizeCompositionChange({
+      composition: {
+        string: "456",
+        clauses: [{ length: 3, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+        caret: { start: 3, length: 0 },
+      }
+    });
+
+    is(getValue(), "abc456xyz",
+      `${aDescription}: Only composition string should be updated even if selection range which covers all over the composition string is updated by "compositionupdate" event listener`);
+
+    aEditor.addEventListener("beforeinput", () => {
+      setSelection("abc456d".length, 0);
+    }, {once: true});
+
+    synthesizeComposition({ type: "compositioncommitasis" });
+
+    is(getValue(), "abc456xyz",
+      `${aDescription}: Only composition string should be updated when committing composition but selection is updated by "beforeinput" event listener`);
+    if (isHTMLEditor) {
+      is(win.getSelection().focusNode, aEditor.firstChild,
+        `${aDescription}: The focus node after composition should be the text node`);
+      is(win.getSelection().focusOffset, "abc456".length,
+        `${aDescription}: The focus offset after composition should be end of the composition string`);
+      is(win.getSelection().anchorNode, aEditor.firstChild,
+        `${aDescription}: The anchor node after composition should be the text node`);
+      is(win.getSelection().anchorOffset, "abc456".length,
+        `${aDescription}: The anchor offset after composition should be end of the composition string`);
+    } else {
+      is(aEditor.selectionStart, "abc456".length,
+        `${aDescription}: The selectionStart after composition should be end of the composition string`);
+      is(aEditor.selectionEnd, "abc456".length,
+        `${aDescription}: The selectionEnd after composition should be end of the composition string`);
+    }
+  }
+  doTest(textarea, "runCompositionWithSelectionChange(textarea)");
+  doTest(input, "runCompositionWithSelectionChange(input)");
+  doTest(contenteditable, "runCompositionWithSelectionChange(contenteditable)");
+}
+
 function runForceCommitTest()
 {
   let events;
   function eventHandler(aEvent)
   {
     events.push(aEvent);
   }
   window.addEventListener("compositionstart", eventHandler, true);
@@ -9598,16 +9707,17 @@ async function runTest()
   runQueryPasswordTest();
   runCSSTransformTest();
   runBug722639Test();
   runBug1375825Test();
   runBug1530649Test();
   runBug1571375Test();
   runBug1675313Test();
   runCommitCompositionWithSpaceKey();
+  runCompositionWithSelectionChange();
   runForceCommitTest();
   runNestedSettingValue();
   runBug811755Test();
   runIsComposingTest();
   runRedundantChangeTest();
   runNotRedundantChangeTest();
   runNativeLineBreakerTest();
   runControlCharTest();