Bug 1639161 - part 2: Make `WSRunObject` use `HTMLEditor::ReplaceTextWithTransaction()` r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 21 May 2020 08:22:20 +0000
changeset 531402 5f86836c31e5c21db6ef51ada6893f5e7c65be5c
parent 531401 2ac7be4d82390baf45f3e50dd6693604a93df651
child 531403 7043d20f06b5fdea8b5f40da6051c7831f805d6a
push id37439
push userbtara@mozilla.com
push dateThu, 21 May 2020 21:49:34 +0000
treeherdermozilla-central@92c11f0bf14b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1639161
milestone78.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 1639161 - part 2: Make `WSRunObject` use `HTMLEditor::ReplaceTextWithTransaction()` r=m_kato Differential Revision: https://phabricator.services.mozilla.com/D76079
editor/libeditor/WSRunObject.cpp
editor/libeditor/WSRunObject.h
editor/libeditor/tests/test_bug1425997.html
testing/web-platform/meta/editing/run/inserttext.html.ini
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -231,22 +231,21 @@ already_AddRefed<Element> WSRunObject::I
           !atNextCharOfInsertionPoint.IsEndOfContainer() &&
           atNextCharOfInsertionPoint.IsCharASCIISpace()) {
         EditorDOMPointInText atPreviousCharOfNextCharOfInsertionPoint =
             GetPreviousEditableCharPoint(atNextCharOfInsertionPoint);
         if (!atPreviousCharOfNextCharOfInsertionPoint.IsSet() ||
             atPreviousCharOfNextCharOfInsertionPoint.IsEndOfContainer() ||
             !atPreviousCharOfNextCharOfInsertionPoint.IsCharASCIISpace()) {
           // We are at start of non-nbsps.  Convert to a single nbsp.
-          nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(
-              atNextCharOfInsertionPoint);
+          nsresult rv =
+              ReplaceASCIIWhitespacesWithOneNBSP(atNextCharOfInsertionPoint);
           if (NS_FAILED(rv)) {
             NS_WARNING(
-                "WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
-                "failed");
+                "WSRunObject::ReplaceASCIIWhitespacesWithOneNBSP() failed");
             return nullptr;
           }
         }
       }
     }
 
     // Handle any changes needed to ws run before inserted br
     if (!beforeRun || beforeRun->IsStartOfHardLine()) {
@@ -1182,23 +1181,21 @@ nsresult WSRunObject::PrepareToDeleteRan
             aEndObject->GetInclusiveNextEditableCharPoint(
                 aEndObject->mScanStartPoint);
         if (nextCharOfStartOfEnd.IsSet() &&
             !nextCharOfStartOfEnd.IsEndOfContainer() &&
             nextCharOfStartOfEnd.IsCharASCIISpace()) {
           // mScanStartPoint will be referred bellow so that we need to keep
           // it a valid point.
           AutoEditorDOMPointChildInvalidator forgetChild(mScanStartPoint);
-          nsresult rv =
-              aEndObject->InsertNBSPAndRemoveFollowingASCIIWhitespaces(
-                  nextCharOfStartOfEnd);
+          nsresult rv = aEndObject->ReplaceASCIIWhitespacesWithOneNBSP(
+              nextCharOfStartOfEnd);
           if (NS_FAILED(rv)) {
             NS_WARNING(
-                "WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
-                "failed");
+                "WSRunObject::ReplaceASCIIWhitespacesWithOneNBSP() failed");
             return rv;
           }
         }
       }
     }
   }
 
   if (!beforeRun) {
@@ -1229,21 +1226,20 @@ nsresult WSRunObject::PrepareToDeleteRan
         EditorDOMPointInText start, end;
         Tie(start, end) = GetASCIIWhitespacesBounds(eBoth, mScanStartPoint);
         NS_WARNING_ASSERTION(start.IsSet(),
                              "WSRunObject::GetASCIIWhitespacesBounds() didn't "
                              "return start point, but ignored");
         NS_WARNING_ASSERTION(end.IsSet(),
                              "WSRunObject::GetASCIIWhitespacesBounds() didn't "
                              "return end point, but ignored");
-        nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(start);
+        nsresult rv = ReplaceASCIIWhitespacesWithOneNBSP(start);
         if (NS_FAILED(rv)) {
           NS_WARNING(
-              "WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
-              "failed");
+              "WSRunObject::ReplaceASCIIWhitespacesWithOneNBSP() failed");
           return rv;
         }
       }
     }
   }
   return NS_OK;
 }
 
@@ -1262,22 +1258,19 @@ nsresult WSRunObject::PrepareToSplitAcro
     // up
     EditorDOMPointInText atNextCharOfStart =
         GetInclusiveNextEditableCharPoint(mScanStartPoint);
     if (atNextCharOfStart.IsSet() && !atNextCharOfStart.IsEndOfContainer() &&
         atNextCharOfStart.IsCharASCIISpace()) {
       // mScanStartPoint will be referred bellow so that we need to keep
       // it a valid point.
       AutoEditorDOMPointChildInvalidator forgetChild(mScanStartPoint);
-      nsresult rv =
-          InsertNBSPAndRemoveFollowingASCIIWhitespaces(atNextCharOfStart);
+      nsresult rv = ReplaceASCIIWhitespacesWithOneNBSP(atNextCharOfStart);
       if (NS_FAILED(rv)) {
-        NS_WARNING(
-            "WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
-            "failed");
+        NS_WARNING("WSRunObject::ReplaceASCIIWhitespacesWithOneNBSP() failed");
         return rv;
       }
     }
   }
 
   // adjust normal ws in beforeRun if needed
   if (beforeRun && beforeRun->IsVisibleAndMiddleOfHardLine()) {
     // make sure trailing char of starting ws is an nbsp, so that it will show
@@ -1290,21 +1283,19 @@ nsresult WSRunObject::PrepareToSplitAcro
       EditorDOMPointInText start, end;
       Tie(start, end) = GetASCIIWhitespacesBounds(eBoth, mScanStartPoint);
       NS_WARNING_ASSERTION(start.IsSet(),
                            "WSRunObject::GetASCIIWhitespacesBounds() didn't "
                            "return start point, but ignored");
       NS_WARNING_ASSERTION(end.IsSet(),
                            "WSRunObject::GetASCIIWhitespacesBounds() didn't "
                            "return end point, but ignored");
-      nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(start);
+      nsresult rv = ReplaceASCIIWhitespacesWithOneNBSP(start);
       if (NS_FAILED(rv)) {
-        NS_WARNING(
-            "WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces() "
-            "failed");
+        NS_WARNING("WSRunObject::ReplaceASCIIWhitespacesWithOneNBSP() failed");
         return rv;
       }
     }
   }
   return NS_OK;
 }
 
 template <typename PT, typename CT>
@@ -1441,58 +1432,50 @@ EditorDOMPointInText WSRunScanner::GetPr
         previousContent->AsText(),
         previousContent->AsText()->TextLength()
             ? previousContent->AsText()->TextLength() - 1
             : 0);
   }
   return EditorDOMPointInText();
 }
 
-nsresult WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces(
-    const EditorDOMPointInText& aPoint) {
-  // MOOSE: this routine needs to be modified to preserve the integrity of the
-  // wsFragment info.
-  if (NS_WARN_IF(!aPoint.IsSet())) {
-    return NS_ERROR_NULL_POINTER;
-  }
+nsresult WSRunObject::ReplaceASCIIWhitespacesWithOneNBSP(
+    const EditorDOMPointInText& aPointAtASCIIWhitespace) {
+  MOZ_ASSERT(aPointAtASCIIWhitespace.IsSet());
+  MOZ_ASSERT(!aPointAtASCIIWhitespace.IsEndOfContainer());
+  MOZ_ASSERT(aPointAtASCIIWhitespace.IsCharASCIISpace());
 
-  // First, insert an NBSP.
+  EditorDOMPointInText start, end;
+  Tie(start, end) = GetASCIIWhitespacesBounds(eAfter, aPointAtASCIIWhitespace);
+  if (NS_WARN_IF(!start.IsSet()) || NS_WARN_IF(!end.IsSet())) {
+    return NS_OK;
+  }
+  MOZ_ASSERT(start.GetContainer() != end.GetContainer() ||
+             end.Offset() > start.Offset());
+
   AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
-  nsresult rv = MOZ_KnownLive(mHTMLEditor)
-                    .InsertTextIntoTextNodeWithTransaction(
-                        nsDependentSubstring(&kNBSP, 1), aPoint, true);
+  nsresult rv =
+      MOZ_KnownLive(mHTMLEditor)
+          .ReplaceTextWithTransaction(
+              MOZ_KnownLive(*start.ContainerAsText()), start.Offset(),
+              end.Offset() - start.Offset(), nsDependentSubstring(&kNBSP, 1));
   if (NS_FAILED(rv)) {
-    NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
+    NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
     return rv;
   }
 
-  // Now, the text node may have been modified by mutation observer.
-  // So, the NBSP may have gone.
-  if (!aPoint.IsSetAndValid() || aPoint.IsEndOfContainer() ||
-      !aPoint.IsCharNBSP()) {
-    // This is just preparation of an edit action.  Let's return NS_OK.
-    // XXX Perhaps, we should return another success code which indicates
-    //     mutation observer touched the DOM tree.  However, that should
-    //     be returned from each transaction's DoTransaction.
+  if (start.GetContainer() == end.GetContainer()) {
     return NS_OK;
   }
 
-  // Next, find range of whitespaces it will be replaced.
-  EditorDOMPointInText start, end;
-  Tie(start, end) = GetASCIIWhitespacesBounds(eAfter, aPoint.NextPoint());
-  if (!start.IsSet()) {
-    return NS_OK;
-  }
-  NS_WARNING_ASSERTION(end.IsSet(),
-                       "WSRunObject::GetASCIIWhitespacesBounds() didn't return "
-                       "end point, but ignored");
-
-  // Finally, delete that replaced ws, if any
+  // We need to remove the following unnecessary ASCII whitespaces because we
+  // collapsed them into the start node.
   rv = MOZ_KnownLive(mHTMLEditor)
-           .DeleteTextAndTextNodesWithTransaction(start, end);
+           .DeleteTextAndTextNodesWithTransaction(
+               EditorDOMPointInText::AtEndOf(*start.ContainerAsText()), end);
   NS_WARNING_ASSERTION(
       NS_SUCCEEDED(rv),
       "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
   return rv;
 }
 
 template <typename PT, typename CT>
 Tuple<EditorDOMPointInText, EditorDOMPointInText>
@@ -1518,16 +1501,18 @@ WSRunObject::GetASCIIWhitespacesBounds(
            atNextChar = GetInclusiveNextEditableCharPoint(atNextChar)) {
         // End of the range should be after the whitespace.
         end = atNextChar = atNextChar.NextPoint();
       }
     }
   }
 
   if (aDir & eBefore) {
+    // XXX Different from eAfter case, this may cross element boundaries with
+    //     this call.  I think that it's not expected case.
     EditorDOMPointInText atPreviousChar = GetPreviousEditableCharPoint(aPoint);
     if (atPreviousChar.IsSet()) {
       // We found a text node, at least.
       start = atPreviousChar.NextPoint();
       if (!end.IsSet()) {
         end = start;
       }
       // Scan back to start of ASCII whitespaces.
@@ -1690,111 +1675,93 @@ nsresult WSRunObject::NormalizeWhitespac
         followedByVisibleContentOrBRElement = true;
       }
     }
 
     // Next, replace the NBSP with an ASCII whitespace if it's surrounded
     // by visible contents (or immediately before a <br> element).
     if (maybeNBSPFollowingVisibleContent &&
         followedByVisibleContentOrBRElement) {
-      // Now replace nbsp with space.  First, insert a space
       AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
       nsresult rv =
           MOZ_KnownLive(mHTMLEditor)
-              .InsertTextIntoTextNodeWithTransaction(
-                  NS_LITERAL_STRING(" "), atPreviousCharOfEndOfRun, true);
-      if (NS_WARN_IF(mHTMLEditor.Destroyed())) {
-        return NS_ERROR_EDITOR_DESTROYED;
-      }
-      if (NS_FAILED(rv)) {
-        NS_WARNING(
-            "EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
-        return rv;
-      }
-
-      if (atPreviousCharOfEndOfRun.IsEndOfContainer() ||
-          atPreviousCharOfEndOfRun.IsAtLastContent()) {
-        NS_WARNING("The text node was modified by mutation event listener");
-        return NS_OK;
-      }
-
-      // Finally, delete that nbsp
-      NS_ASSERTION(atPreviousCharOfEndOfRun.IsNextCharNBSP(),
-                   "Trying to remove an NBSP, but it's gone from the "
-                   "expected position");
-      EditorDOMPointInText atNextCharOfPreviousCharOfEndOfRun =
-          atPreviousCharOfEndOfRun.NextPoint();
-      rv = MOZ_KnownLive(mHTMLEditor)
-               .DeleteTextAndTextNodesWithTransaction(
-                   atNextCharOfPreviousCharOfEndOfRun,
-                   atNextCharOfPreviousCharOfEndOfRun.NextPoint());
-      if (NS_FAILED(rv)) {
-        NS_WARNING(
-            "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
-        return rv;
-      }
-      return NS_OK;
+              .ReplaceTextWithTransaction(
+                  MOZ_KnownLive(*atPreviousCharOfEndOfRun.ContainerAsText()),
+                  atPreviousCharOfEndOfRun.Offset(), 1, NS_LITERAL_STRING(" "));
+      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                           "HTMLEditor::ReplaceTextWithTransaction() failed");
+      return rv;
     }
   }
 
   // If the text node is not preformatted, and the NBSP is followed by a <br>
   // element and following (maybe multiple) ASCII spaces, remove the NBSP,
   // but inserts a NBSP before the spaces.  This makes a line break opportunity
   // to wrap the line.
   // XXX This is different behavior from Blink.  Blink generates pairs of
   //     an NBSP and an ASCII whitespace, but put NBSP at the end of the
   //     sequence.  We should follow the behavior for web-compat.
   if (mPRE || maybeNBSPFollowingVisibleContent ||
       !isPreviousCharASCIIWhitespace || !followedByVisibleContentOrBRElement) {
     return NS_OK;
   }
 
+  // Currently, we're at an NBSP following an ASCII space (in theory,
+  // GetASCIIWhitespacesBounds() may return earlier text node's ASCII
+  // whitespace point to `start` though).  Then, we need to replace it with
+  // `"&nbsp; "` for avoiding collapsing whitespaces.
   MOZ_ASSERT(!atPreviousCharOfPreviousCharOfEndOfRun.IsEndOfContainer());
+  // XXX `eBoth` is required even though the following code does not refer
+  //     `end` because GetASCIIWhitespacesBounds() may not set `start` nor
+  //     `end` if `eBefore`.
   EditorDOMPointInText start, end;
-  // XXX end won't be used, whey `eBoth`?
-  Tie(start, end) = GetASCIIWhitespacesBounds(
-      eBoth, atPreviousCharOfPreviousCharOfEndOfRun.NextPoint());
-  NS_WARNING_ASSERTION(
+  Tie(start, end) = GetASCIIWhitespacesBounds(eBoth, atPreviousCharOfEndOfRun);
+  MOZ_ASSERT(
       start.IsSet(),
       "WSRunObject::GetASCIIWhitespacesBounds() didn't return start point");
-  NS_WARNING_ASSERTION(end.IsSet(),
-                       "WSRunObject::GetASCIIWhitespacesBounds() didn't "
-                       "return end point, but ignored");
+  MOZ_ASSERT(start.GetContainer() != atPreviousCharOfEndOfRun.GetContainer() ||
+                 start.Offset() < atPreviousCharOfEndOfRun.Offset(),
+             "There must be at least one ASCII whitespace");
 
-  // Delete that nbsp
-  NS_ASSERTION(!atPreviousCharOfEndOfRun.IsEndOfContainer(),
-               "The text node was modified by mutation event listener");
-  if (!atPreviousCharOfEndOfRun.IsEndOfContainer()) {
-    NS_ASSERTION(atPreviousCharOfEndOfRun.IsCharNBSP(),
-                 "Trying to remove an NBSP, but it's gone from the "
-                 "expected position");
-    nsresult rv =
-        MOZ_KnownLive(mHTMLEditor)
-            .DeleteTextAndTextNodesWithTransaction(
-                atPreviousCharOfEndOfRun, atPreviousCharOfEndOfRun.NextPoint());
-    if (NS_FAILED(rv)) {
-      NS_WARNING("HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
-      return rv;
-    }
+  AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
+  uint32_t numberOfASCIIWhitespacesInStartNode =
+      start.ContainerAsText() == atPreviousCharOfEndOfRun.ContainerAsText()
+          ? atPreviousCharOfEndOfRun.Offset() - start.Offset()
+          : start.ContainerAsText()->Length() - start.Offset();
+  // Replace all preceding ASCII whitespaces **and** the NBSP.
+  uint32_t replaceLengthInStartNode =
+      numberOfASCIIWhitespacesInStartNode +
+      (start.ContainerAsText() == atPreviousCharOfEndOfRun.ContainerAsText()
+           ? 1
+           : 0);
+  nsresult rv =
+      MOZ_KnownLive(mHTMLEditor)
+          .ReplaceTextWithTransaction(MOZ_KnownLive(*start.ContainerAsText()),
+                                      start.Offset(), replaceLengthInStartNode,
+                                      NS_LITERAL_STRING(u"\x00A0 "));
+  if (NS_FAILED(rv)) {
+    NS_WARNING("HTMLEditor::ReplaceTextWithTransaction() failed");
+    return rv;
   }
 
-  // Finally, insert that nbsp before the ASCII ws run
-  NS_ASSERTION(start.IsSetAndValid(),
-               "The text node was modified by mutation event listener");
-  if (start.IsSetAndValid()) {
-    AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
-    nsresult rv = MOZ_KnownLive(mHTMLEditor)
-                      .InsertTextIntoTextNodeWithTransaction(
-                          nsDependentSubstring(&kNBSP, 1), start, true);
-    if (NS_FAILED(rv)) {
-      NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
-      return rv;
-    }
+  if (start.GetContainer() == atPreviousCharOfEndOfRun.GetContainer()) {
+    return NS_OK;
   }
-  return NS_OK;
+
+  // We need to remove the following unnecessary ASCII whitespaces and
+  // NBSP at atPreviousCharOfEndOfRun because we collapsed them into
+  // the start node.
+  rv = MOZ_KnownLive(mHTMLEditor)
+           .DeleteTextAndTextNodesWithTransaction(
+               EditorDOMPointInText::AtEndOf(*start.ContainerAsText()),
+               atPreviousCharOfEndOfRun.NextPoint());
+  NS_WARNING_ASSERTION(
+      NS_SUCCEEDED(rv),
+      "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
+  return rv;
 }
 
 nsresult WSRunObject::MaybeReplacePreviousNBSPWithASCIIWhitespace(
     const WSFragment& aRun, const EditorDOMPoint& aPoint) {
   MOZ_ASSERT(aPoint.IsSetAndValid());
 
   // Try to change an NBSP to a space, if possible, just to prevent NBSP
   // proliferation.  This routine is called when we are about to make this
@@ -1818,46 +1785,23 @@ nsresult WSRunObject::MaybeReplacePrevio
     }
   }
   // If previous content of the NBSP is block boundary, we cannot replace the
   // NBSP with an ASCII whitespace to keep it rendered.
   else if (!aRun.StartsFromNormalText() && !aRun.StartsFromSpecialContent()) {
     return NS_OK;
   }
 
-  // First, insert a space before the previous NBSP.
   AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
   nsresult rv = MOZ_KnownLive(mHTMLEditor)
-                    .InsertTextIntoTextNodeWithTransaction(
-                        NS_LITERAL_STRING(" "), atPreviousChar, true);
-  if (NS_WARN_IF(mHTMLEditor.Destroyed())) {
-    return NS_ERROR_EDITOR_DESTROYED;
-  }
-  if (NS_FAILED(rv)) {
-    NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
-    return rv;
-  }
-
-  // If the insertion causes unexpected DOM tree, we should abort it.
-  if (atPreviousChar.IsEndOfContainer() || atPreviousChar.IsAtLastContent()) {
-    NS_WARNING("The text node was modified by mutation event listener");
-    return NS_OK;
-  }
-
-  // Finally, delete the previous NBSP.
-  NS_ASSERTION(
-      atPreviousChar.IsNextCharNBSP(),
-      "Trying to remove an NBSP, but it's gone from the expected position");
-  EditorDOMPointInText atNextCharOfPreviousChar = atPreviousChar.NextPoint();
-  rv = MOZ_KnownLive(mHTMLEditor)
-           .DeleteTextAndTextNodesWithTransaction(
-               atNextCharOfPreviousChar, atNextCharOfPreviousChar.NextPoint());
-  NS_WARNING_ASSERTION(
-      NS_SUCCEEDED(rv),
-      "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
+                    .ReplaceTextWithTransaction(
+                        MOZ_KnownLive(*atPreviousChar.ContainerAsText()),
+                        atPreviousChar.Offset(), 1, NS_LITERAL_STRING(" "));
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "HTMLEditor::ReplaceTextWithTransaction() failed");
   return rv;
 }
 
 nsresult WSRunObject::MaybeReplaceInclusiveNextNBSPWithASCIIWhitespace(
     const WSFragment& aRun, const EditorDOMPoint& aPoint) {
   MOZ_ASSERT(aPoint.IsSetAndValid());
 
   // Try to change an nbsp to a space, if possible, just to prevent nbsp
@@ -1882,46 +1826,23 @@ nsresult WSRunObject::MaybeReplaceInclus
   }
   // If the NBSP is last character in the hard line, we don't need to
   // replace it because it's required to render multiple whitespaces.
   else if (!aRun.EndsByNormalText() && !aRun.EndsBySpecialContent() &&
            !aRun.EndsByBRElement()) {
     return NS_OK;
   }
 
-  // First, insert an ASCII whitespace.
   AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
   nsresult rv = MOZ_KnownLive(mHTMLEditor)
-                    .InsertTextIntoTextNodeWithTransaction(
-                        NS_LITERAL_STRING(" "), atNextChar, true);
-  if (NS_WARN_IF(mHTMLEditor.Destroyed())) {
-    return NS_ERROR_EDITOR_DESTROYED;
-  }
-  if (NS_FAILED(rv)) {
-    NS_WARNING("EditorBase::InsertTextIntoTextNodeWithTransaction() failed");
-    return rv;
-  }
-
-  // If the insertion causes unexpected DOM tree, we should abort it.
-  if (atNextChar.IsEndOfContainer() || atNextChar.IsAtLastContent()) {
-    NS_WARNING("The text node was modified by mutation event listener");
-    return NS_OK;
-  }
-
-  // Finally, delete that nbsp
-  NS_ASSERTION(
-      atNextChar.IsNextCharNBSP(),
-      "Trying to remove an NBSP, but it's gone from the expected position");
-  EditorDOMPointInText atNextCharOfNextChar = atNextChar.NextPoint();
-  rv = MOZ_KnownLive(mHTMLEditor)
-           .DeleteTextAndTextNodesWithTransaction(
-               atNextCharOfNextChar, atNextCharOfNextChar.NextPoint());
-  NS_WARNING_ASSERTION(
-      NS_SUCCEEDED(rv),
-      "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
+                    .ReplaceTextWithTransaction(
+                        MOZ_KnownLive(*atNextChar.ContainerAsText()),
+                        atNextChar.Offset(), 1, NS_LITERAL_STRING(" "));
+  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+                       "HTMLEditor::ReplaceTextWithTransaction() failed");
   return rv;
 }
 
 nsresult WSRunObject::Scrub() {
   for (WSFragment* run = mStartRun; run; run = run->mRight) {
     if (run->IsMiddleOfHardLine()) {
       continue;
     }
--- a/editor/libeditor/WSRunObject.h
+++ b/editor/libeditor/WSRunObject.h
@@ -783,21 +783,25 @@ class MOZ_STACK_CLASS WSRunObject final 
   // be safely converted to regular ascii space and converts them.
   MOZ_CAN_RUN_SCRIPT nsresult AdjustWhitespace();
 
  protected:
   MOZ_CAN_RUN_SCRIPT nsresult PrepareToDeleteRangePriv(WSRunObject* aEndObject);
   MOZ_CAN_RUN_SCRIPT nsresult PrepareToSplitAcrossBlocksPriv();
 
   /**
-   * InsertNBSPAndRemoveFollowingASCIIWhitespaces() inserts an NBSP first.
-   * Then, if following characters are ASCII whitespaces, will remove them.
+   * ReplaceASCIIWhitespacesWithOneNBSP() replaces all adjuscent ASCII
+   * whitespaces which includes aPointAtASCIIWitespace with an NBSP. Then, if
+   * Note that this may remove ASCII whitespaces which are not in container of
+   * aPointAtASCIIWhitespace.
+   *
+   * @param aPointAtASCIIWhitespace     Point of an ASCII whitespace.
    */
-  MOZ_CAN_RUN_SCRIPT nsresult InsertNBSPAndRemoveFollowingASCIIWhitespaces(
-      const EditorDOMPointInText& aPoint);
+  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ReplaceASCIIWhitespacesWithOneNBSP(
+      const EditorDOMPointInText& aPointAtASCIIWhitespace);
 
   /**
    * GetASCIIWhitespacesBounds() returns a range from start of whitespaces
    * and end of whitespaces if the character at aPoint is an ASCII whitespace.
    * Note that the end is next character of the last whitespace.
    *
    * @param aDir            Specify eBefore if you want to scan text backward.
    *                        Specify eAfter if you want to scan text forward.
--- a/editor/libeditor/tests/test_bug1425997.html
+++ b/editor/libeditor/tests/test_bug1425997.html
@@ -19,27 +19,27 @@ https://bugzilla.mozilla.org/show_bug.cg
 <!-- -->
 <span id="inline">foo</span>
 </div>
 
 <pre id="test">
 
 <script class="testbody" type="application/javascript">
 SimpleTest.waitForExplicitFinish();
-// 3 assertions are recorded due to nested execCommand() but not a problem.
+// 2 assertions are recorded due to nested execCommand() but not a problem.
 // They are necessary to detect invalid method call without mutation event listers.
-SimpleTest.expectAssertions(3, 3);
+SimpleTest.expectAssertions(2, 2);
 SimpleTest.waitForFocus(async function() {
   await SpecialPowers.pushPrefEnv({set: [["dom.document.exec_command.nested_calls_allowed", true]]});
   let selection = window.getSelection();
   let editor = document.getElementById("editor");
   function onCharacterDataModified() {
     // Until removing all NBSPs which were inserted by the editor,
     // emulates Backspace key with "delete" command.
-    // When this test is created, the behavior is:
+    // When this test is created, the behavior was:
     //   after 1st delete: "\n<!-- -->&nbsp;&nbsp;\n"
     //   after 2nd delete: "\n<!-- -->&nbsp;\n"
     //   after 3rd delete: "\n<!-- -->\n"
     while (editor.innerHTML.includes("&nbsp;")) {
       if (!document.execCommand("delete", false)) {
         break;
       }
     }
--- a/testing/web-platform/meta/editing/run/inserttext.html.ini
+++ b/testing/web-platform/meta/editing/run/inserttext.html.ini
@@ -86,19 +86,16 @@
     expected: FAIL
 
   [[["inserttext"," "\]\] "foo[\]&nbsp;&nbsp;bar" compare innerHTML]
     expected: FAIL
 
   [[["inserttext"," "\]\] "foo [\]&nbsp;        bar" compare innerHTML]
     expected: FAIL
 
-  [[["inserttext"," "\]\] "foo  [\]bar" compare innerHTML]
-    expected: FAIL
-
   [[["inserttext"," "\]\] "foo[\]" compare innerHTML]
     expected: FAIL
 
   [[["inserttext"," "\]\] "foo{}" compare innerHTML]
     expected: FAIL
 
   [[["inserttext"," "\]\] "foo&nbsp;[\]" compare innerHTML]
     expected: FAIL