Bug 1393337 - Don't try to remove whitespaces in WSRunObject::ConvertToNBSP() when the text node is changed by mutation observer r?m_kato draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 21 Dec 2017 19:27:31 +0900
changeset 713923 38187f99667e3eb7d73c2654081dbec9ccabd853
parent 713922 b824cc33c24ac743b703e3625cdcfb91ea029e5c
child 744476 bf77e5fc02892f83cd22d8fe639ebda1697c7139
push id93799
push usermasayuki@d-toybox.com
push dateThu, 21 Dec 2017 11:03:11 +0000
reviewersm_kato
bugs1393337
milestone59.0a1
Bug 1393337 - Don't try to remove whitespaces in WSRunObject::ConvertToNBSP() when the text node is changed by mutation observer r?m_kato WSRunObject::ConvertToNBSP() inserts an NBSP, then, removes following ASCII whitespaces. When inserting an NBSP, mutation observer may change the text node. In this case, it shouldn't keep working on removing ASCII whitespaces because it may causes unexpected result. This patch also renames ConvertToNBSP() and GetAsciiWSBounds() to InsertNBSPAndRemoveFollowingASCIIWhitespaces() and GetASCIIWhitespacesBounds() for making their jobs clearer. MozReview-Commit-ID: TVy9fEKL6p
editor/libeditor/WSRunObject.cpp
editor/libeditor/WSRunObject.h
editor/libeditor/tests/mochitest.ini
editor/libeditor/tests/test_bug1425997.html
--- a/editor/libeditor/WSRunObject.cpp
+++ b/editor/libeditor/WSRunObject.cpp
@@ -28,17 +28,17 @@
 #include "nsRange.h"
 #include "nsString.h"
 #include "nsTextFragment.h"
 
 namespace mozilla {
 
 using namespace dom;
 
-const char16_t nbsp = 160;
+const char16_t kNBSP = 160;
 
 WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
                          nsINode* aNode,
                          int32_t aOffset)
   : mNode(aNode)
   , mOffset(aOffset)
   , mPRE(false)
   , mStartOffset(0)
@@ -204,18 +204,20 @@ WSRunObject::InsertBreak(Selection& aSel
       // Need to determine if break at front of non-nbsp run.  If so, convert
       // run to nbsp.
       WSPoint thePoint = GetNextCharPoint(pointToInsert.AsRaw());
       if (thePoint.mTextNode && nsCRT::IsAsciiSpace(thePoint.mChar)) {
         WSPoint prevPoint = GetPreviousCharPoint(thePoint);
         if (!prevPoint.mTextNode ||
             (prevPoint.mTextNode && !nsCRT::IsAsciiSpace(prevPoint.mChar))) {
           // We are at start of non-nbsps.  Convert to a single nbsp.
-          nsresult rv = ConvertToNBSP(thePoint);
-          NS_ENSURE_SUCCESS(rv, nullptr);
+          nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(thePoint);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return nullptr;
+          }
         }
       }
     }
 
     // Handle any changes needed to ws run before inserted br
     if (!beforeRun || (beforeRun->mType & WSType::leadingWS)) {
       // Don't need to do anything.  Just insert break.  ws won't change.
     } else if (beforeRun->mType & WSType::trailingWS) {
@@ -324,57 +326,57 @@ WSRunObject::InsertText(nsIDocument& aDo
   // Next up, tweak head and tail of string as needed.  First the head: there
   // are a variety of circumstances that would require us to convert a leading
   // ws char into an nbsp:
 
   if (nsCRT::IsAsciiSpace(theString[0])) {
     // We have a leading space
     if (beforeRun) {
       if (beforeRun->mType & WSType::leadingWS) {
-        theString.SetCharAt(nbsp, 0);
+        theString.SetCharAt(kNBSP, 0);
       } else if (beforeRun->mType & WSType::normalWS) {
         WSPoint wspoint = GetPreviousCharPoint(pointToInsert.AsRaw());
         if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
-          theString.SetCharAt(nbsp, 0);
+          theString.SetCharAt(kNBSP, 0);
         }
       }
     } else if (mStartReason & WSType::block || mStartReason == WSType::br) {
-      theString.SetCharAt(nbsp, 0);
+      theString.SetCharAt(kNBSP, 0);
     }
   }
 
   // Then the tail
   uint32_t lastCharIndex = theString.Length() - 1;
 
   if (nsCRT::IsAsciiSpace(theString[lastCharIndex])) {
     // We have a leading space
     if (afterRun) {
       if (afterRun->mType & WSType::trailingWS) {
-        theString.SetCharAt(nbsp, lastCharIndex);
+        theString.SetCharAt(kNBSP, lastCharIndex);
       } else if (afterRun->mType & WSType::normalWS) {
         WSPoint wspoint = GetNextCharPoint(pointToInsert.AsRaw());
         if (wspoint.mTextNode && nsCRT::IsAsciiSpace(wspoint.mChar)) {
-          theString.SetCharAt(nbsp, lastCharIndex);
+          theString.SetCharAt(kNBSP, lastCharIndex);
         }
       }
     } else if (mEndReason & WSType::block) {
-      theString.SetCharAt(nbsp, lastCharIndex);
+      theString.SetCharAt(kNBSP, lastCharIndex);
     }
   }
 
   // Next, scan string for adjacent ws and convert to nbsp/space combos
   // MOOSE: don't need to convert tabs here since that is done by
   // WillInsertText() before we are called.  Eventually, all that logic will be
   // pushed down into here and made more efficient.
   bool prevWS = false;
   for (uint32_t i = 0; i <= lastCharIndex; i++) {
     if (nsCRT::IsAsciiSpace(theString[i])) {
       if (prevWS) {
         // i - 1 can't be negative because prevWS starts out false
-        theString.SetCharAt(nbsp, i - 1);
+        theString.SetCharAt(kNBSP, i - 1);
       } else {
         prevWS = true;
       }
     } else {
       prevWS = false;
     }
   }
 
@@ -390,34 +392,34 @@ WSRunObject::InsertText(nsIDocument& aDo
 
 nsresult
 WSRunObject::DeleteWSBackward()
 {
   WSPoint point = GetPreviousCharPoint(Point());
   NS_ENSURE_TRUE(point.mTextNode, NS_OK);  // nothing to delete
 
   // Easy case, preformatted ws.
-  if (mPRE &&  (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp)) {
+  if (mPRE &&  (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP)) {
     nsresult rv =
       DeleteRange(EditorRawDOMPoint(point.mTextNode, point.mOffset),
                   EditorRawDOMPoint(point.mTextNode, point.mOffset + 1));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
   // Caller's job to ensure that previous char is really ws.  If it is normal
   // ws, we need to delete the whole run.
   if (nsCRT::IsAsciiSpace(point.mChar)) {
     RefPtr<Text> startNodeText, endNodeText;
     int32_t startOffset, endOffset;
-    GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
-                     getter_AddRefs(startNodeText), &startOffset,
-                     getter_AddRefs(endNodeText), &endOffset);
+    GetASCIIWhitespacesBounds(eBoth, point.mTextNode, point.mOffset + 1,
+                              getter_AddRefs(startNodeText), &startOffset,
+                              getter_AddRefs(endNodeText), &endOffset);
 
     // adjust surrounding ws
     nsCOMPtr<nsINode> startNode = startNodeText.get();
     nsCOMPtr<nsINode> endNode = endNodeText.get();
     nsresult rv =
       WSRunObject::PrepareToDeleteRange(mHTMLEditor,
                                         address_of(startNode), &startOffset,
                                         address_of(endNode), &endOffset);
@@ -427,17 +429,17 @@ WSRunObject::DeleteWSBackward()
     rv = DeleteRange(EditorRawDOMPoint(startNode, startOffset),
                      EditorRawDOMPoint(endNode, endOffset));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
-  if (point.mChar == nbsp) {
+  if (point.mChar == kNBSP) {
     nsCOMPtr<nsINode> node(point.mTextNode);
     // adjust surrounding ws
     int32_t startOffset = point.mOffset;
     int32_t endOffset = point.mOffset + 1;
     nsresult rv =
       WSRunObject::PrepareToDeleteRange(mHTMLEditor,
                                         address_of(node), &startOffset,
                                         address_of(node), &endOffset);
@@ -457,34 +459,34 @@ WSRunObject::DeleteWSBackward()
 
 nsresult
 WSRunObject::DeleteWSForward()
 {
   WSPoint point = GetNextCharPoint(Point());
   NS_ENSURE_TRUE(point.mTextNode, NS_OK); // nothing to delete
 
   // Easy case, preformatted ws.
-  if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp)) {
+  if (mPRE && (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP)) {
     nsresult rv =
       DeleteRange(EditorRawDOMPoint(point.mTextNode, point.mOffset),
                   EditorRawDOMPoint(point.mTextNode, point.mOffset + 1));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
   // Caller's job to ensure that next char is really ws.  If it is normal ws,
   // we need to delete the whole run.
   if (nsCRT::IsAsciiSpace(point.mChar)) {
     RefPtr<Text> startNodeText, endNodeText;
     int32_t startOffset, endOffset;
-    GetAsciiWSBounds(eBoth, point.mTextNode, point.mOffset + 1,
-                     getter_AddRefs(startNodeText), &startOffset,
-                     getter_AddRefs(endNodeText), &endOffset);
+    GetASCIIWhitespacesBounds(eBoth, point.mTextNode, point.mOffset + 1,
+                              getter_AddRefs(startNodeText), &startOffset,
+                              getter_AddRefs(endNodeText), &endOffset);
 
     // Adjust surrounding ws
     nsCOMPtr<nsINode> startNode(startNodeText), endNode(endNodeText);
     nsresult rv =
       WSRunObject::PrepareToDeleteRange(mHTMLEditor,
                                         address_of(startNode), &startOffset,
                                         address_of(endNode), &endOffset);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -493,17 +495,17 @@ WSRunObject::DeleteWSForward()
     rv = DeleteRange(EditorRawDOMPoint(startNode, startOffset),
                      EditorRawDOMPoint(endNode, endOffset));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
-  if (point.mChar == nbsp) {
+  if (point.mChar == kNBSP) {
     nsCOMPtr<nsINode> node(point.mTextNode);
     // Adjust surrounding ws
     int32_t startOffset = point.mOffset;
     int32_t endOffset = point.mOffset+1;
     nsresult rv =
       WSRunObject::PrepareToDeleteRange(mHTMLEditor,
                                         address_of(node), &startOffset,
                                         address_of(node), &endOffset);
@@ -538,17 +540,17 @@ WSRunObject::PriorVisibleNode(nsINode* a
   // Is there a visible run there or earlier?
   for (; run; run = run->mLeft) {
     if (run->mType == WSType::normalWS) {
       WSPoint point = GetPreviousCharPoint(EditorRawDOMPoint(aNode, aOffset));
       // When it's a non-empty text node, return it.
       if (point.mTextNode && point.mTextNode->Length()) {
         *outVisNode = point.mTextNode;
         *outVisOffset = point.mOffset + 1;
-        if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
+        if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
           *outType = WSType::normalWS;
         } else {
           *outType = WSType::text;
         }
         return;
       }
       // If no text node, keep looking.  We should eventually fall out of loop
     }
@@ -579,17 +581,17 @@ WSRunObject::NextVisibleNode(nsINode* aN
   // Is there a visible run there or later?
   for (; run; run = run->mRight) {
     if (run->mType == WSType::normalWS) {
       WSPoint point = GetNextCharPoint(EditorRawDOMPoint(aNode, aOffset));
       // When it's a non-empty text node, return it.
       if (point.mTextNode && point.mTextNode->Length()) {
         *outVisNode = point.mTextNode;
         *outVisOffset = point.mOffset;
-        if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == nbsp) {
+        if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
           *outType = WSType::normalWS;
         } else {
           *outType = WSType::text;
         }
         return;
       }
       // If no text node, keep looking.  We should eventually fall out of loop
     }
@@ -664,17 +666,17 @@ WSRunObject::GetWSNodes()
       for (int32_t pos = mOffset - 1; pos >= 0; pos--) {
         // sanity bounds check the char position.  bug 136165
         if (uint32_t(pos) >= textFrag->GetLength()) {
           NS_NOTREACHED("looking beyond end of text fragment");
           continue;
         }
         char16_t theChar = textFrag->CharAt(pos);
         if (!nsCRT::IsAsciiSpace(theChar)) {
-          if (theChar != nbsp) {
+          if (theChar != kNBSP) {
             mStartNode = textNode;
             mStartOffset = pos + 1;
             mStartReason = WSType::text;
             mStartReasonNode = textNode;
             break;
           }
           // as we look backwards update our earliest found nbsp
           mFirstNBSPNode = textNode;
@@ -717,17 +719,17 @@ WSRunObject::GetWSNodes()
           for (int32_t pos = len - 1; pos >= 0; pos--) {
             // sanity bounds check the char position.  bug 136165
             if (uint32_t(pos) >= textFrag->GetLength()) {
               NS_NOTREACHED("looking beyond end of text fragment");
               continue;
             }
             char16_t theChar = textFrag->CharAt(pos);
             if (!nsCRT::IsAsciiSpace(theChar)) {
-              if (theChar != nbsp) {
+              if (theChar != kNBSP) {
                 mStartNode = textNode;
                 mStartOffset = pos + 1;
                 mStartReason = WSType::text;
                 mStartReasonNode = textNode;
                 break;
               }
               // as we look backwards update our earliest found nbsp
               mFirstNBSPNode = textNode;
@@ -772,17 +774,17 @@ WSRunObject::GetWSNodes()
       for (uint32_t pos = mOffset; pos < len; pos++) {
         // sanity bounds check the char position.  bug 136165
         if (pos >= textFrag->GetLength()) {
           NS_NOTREACHED("looking beyond end of text fragment");
           continue;
         }
         char16_t theChar = textFrag->CharAt(pos);
         if (!nsCRT::IsAsciiSpace(theChar)) {
-          if (theChar != nbsp) {
+          if (theChar != kNBSP) {
             mEndNode = textNode;
             mEndOffset = pos;
             mEndReason = WSType::text;
             mEndReasonNode = textNode;
             break;
           }
           // as we look forwards update our latest found nbsp
           mLastNBSPNode = textNode;
@@ -826,17 +828,17 @@ WSRunObject::GetWSNodes()
           for (uint32_t pos = 0; pos < len; pos++) {
             // sanity bounds check the char position.  bug 136165
             if (pos >= textFrag->GetLength()) {
               NS_NOTREACHED("looking beyond end of text fragment");
               continue;
             }
             char16_t theChar = textFrag->CharAt(pos);
             if (!nsCRT::IsAsciiSpace(theChar)) {
-              if (theChar != nbsp) {
+              if (theChar != kNBSP) {
                 mEndNode = textNode;
                 mEndOffset = pos;
                 mEndReason = WSType::text;
                 mEndReasonNode = textNode;
                 break;
               }
               // as we look forwards update our latest found nbsp
               mLastNBSPNode = textNode;
@@ -1228,18 +1230,21 @@ WSRunObject::PrepareToDeleteRangePriv(WS
   // adjust normal ws in afterRun if needed
   if (afterRun && afterRun->mType == WSType::normalWS && !aEndObject->mPRE) {
     if ((beforeRun && (beforeRun->mType & WSType::leadingWS)) ||
         (!beforeRun && ((mStartReason & WSType::block) ||
                         mStartReason == WSType::br))) {
       // make sure leading char of following ws is an nbsp, so that it will show up
       WSPoint point = aEndObject->GetNextCharPoint(aEndObject->Point());
       if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
-        nsresult rv = aEndObject->ConvertToNBSP(point);
-        NS_ENSURE_SUCCESS(rv, rv);
+        nsresult rv =
+          aEndObject->InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
       }
     }
   }
   // trim before run of any trailing ws
   if (beforeRun && (beforeRun->mType & WSType::trailingWS)) {
     nsresult rv = DeleteRange(beforeRun->StartPoint(), Point());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -1248,23 +1253,25 @@ WSRunObject::PrepareToDeleteRangePriv(WS
     if ((afterRun && (afterRun->mType & WSType::trailingWS)) ||
         (afterRun && afterRun->mType == WSType::normalWS) ||
         (!afterRun && (aEndObject->mEndReason & WSType::block))) {
       // make sure trailing char of starting ws is an nbsp, so that it will show up
       WSPoint point = GetPreviousCharPoint(Point());
       if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
         RefPtr<Text> wsStartNode, wsEndNode;
         int32_t wsStartOffset, wsEndOffset;
-        GetAsciiWSBounds(eBoth, mNode, mOffset,
-                         getter_AddRefs(wsStartNode), &wsStartOffset,
-                         getter_AddRefs(wsEndNode), &wsEndOffset);
+        GetASCIIWhitespacesBounds(eBoth, mNode, mOffset,
+                                  getter_AddRefs(wsStartNode), &wsStartOffset,
+                                  getter_AddRefs(wsEndNode), &wsEndOffset);
         point.mTextNode = wsStartNode;
         point.mOffset = wsStartOffset;
-        nsresult rv = ConvertToNBSP(point);
-        NS_ENSURE_SUCCESS(rv, rv);
+        nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
       }
     }
   }
   return NS_OK;
 }
 
 nsresult
 WSRunObject::PrepareToSplitAcrossBlocksPriv()
@@ -1277,35 +1284,39 @@ WSRunObject::PrepareToSplitAcrossBlocksP
   WSFragment* beforeRun = FindNearestRun(Point(), false);
   WSFragment* afterRun = FindNearestRun(Point(), true);
 
   // adjust normal ws in afterRun if needed
   if (afterRun && afterRun->mType == WSType::normalWS) {
     // make sure leading char of following ws is an nbsp, so that it will show up
     WSPoint point = GetNextCharPoint(Point());
     if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
-      nsresult rv = ConvertToNBSP(point);
-      NS_ENSURE_SUCCESS(rv, rv);
+      nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     }
   }
 
   // adjust normal ws in beforeRun if needed
   if (beforeRun && beforeRun->mType == WSType::normalWS) {
     // make sure trailing char of starting ws is an nbsp, so that it will show up
     WSPoint point = GetPreviousCharPoint(Point());
     if (point.mTextNode && nsCRT::IsAsciiSpace(point.mChar)) {
       RefPtr<Text> wsStartNode, wsEndNode;
       int32_t wsStartOffset, wsEndOffset;
-      GetAsciiWSBounds(eBoth, mNode, mOffset,
-                       getter_AddRefs(wsStartNode), &wsStartOffset,
-                       getter_AddRefs(wsEndNode), &wsEndOffset);
+      GetASCIIWhitespacesBounds(eBoth, mNode, mOffset,
+                                getter_AddRefs(wsStartNode), &wsStartOffset,
+                                getter_AddRefs(wsEndNode), &wsEndOffset);
       point.mTextNode = wsStartNode;
       point.mOffset = wsStartOffset;
-      nsresult rv = ConvertToNBSP(point);
-      NS_ENSURE_SUCCESS(rv, rv);
+      nsresult rv = InsertNBSPAndRemoveFollowingASCIIWhitespaces(point);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
     }
   }
   return NS_OK;
 }
 
 nsresult
 WSRunObject::DeleteRange(const EditorRawDOMPoint& aStartPoint,
                          const EditorRawDOMPoint& aEndPoint)
@@ -1487,58 +1498,73 @@ WSRunObject::GetPreviousCharPoint(const 
       outPoint.mOffset = len - 1;
       outPoint.mChar = GetCharAt(outPoint.mTextNode, len - 1);
     }
   }
   return outPoint;
 }
 
 nsresult
-WSRunObject::ConvertToNBSP(WSPoint aPoint)
+WSRunObject::InsertNBSPAndRemoveFollowingASCIIWhitespaces(WSPoint aPoint)
 {
   // MOOSE: this routine needs to be modified to preserve the integrity of the
   // wsFragment info.
-  NS_ENSURE_TRUE(aPoint.mTextNode, NS_ERROR_NULL_POINTER);
+  if (NS_WARN_IF(!aPoint.mTextNode)) {
+    return NS_ERROR_NULL_POINTER;
+  }
 
-  // First, insert an nbsp
+ // First, insert an NBSP.
   AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
-  nsAutoString nbspStr(nbsp);
   nsresult rv =
-    mHTMLEditor->InsertTextIntoTextNodeImpl(nbspStr, *aPoint.mTextNode,
-                                            aPoint.mOffset, true);
-  NS_ENSURE_SUCCESS(rv, rv);
+    mHTMLEditor->InsertTextIntoTextNodeImpl(nsDependentString(&kNBSP, 1),
+                                            *aPoint.mTextNode, aPoint.mOffset,
+                                            true);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
-  // Next, find range of ws it will replace
+  // Now, the text node may have been modified by mutation observer.
+  // So, the NBSP may have gone.
+  if (aPoint.mTextNode->TextDataLength() <= aPoint.mOffset ||
+      aPoint.mTextNode->GetText()->CharAt(aPoint.mOffset) != kNBSP) {
+    // 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.
+    return NS_OK;
+  }
+
+  // Next, find range of whitespaces it will be replaced.
   RefPtr<Text> startNode, endNode;
   int32_t startOffset = 0, endOffset = 0;
 
-  GetAsciiWSBounds(eAfter, aPoint.mTextNode, aPoint.mOffset + 1,
-                   getter_AddRefs(startNode), &startOffset,
-                   getter_AddRefs(endNode), &endOffset);
+  GetASCIIWhitespacesBounds(eAfter, aPoint.mTextNode, aPoint.mOffset + 1,
+                            getter_AddRefs(startNode), &startOffset,
+                            getter_AddRefs(endNode), &endOffset);
 
   // Finally, delete that replaced ws, if any
   if (startNode) {
     rv = DeleteRange(EditorRawDOMPoint(startNode, startOffset),
                      EditorRawDOMPoint(endNode, endOffset));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
 void
-WSRunObject::GetAsciiWSBounds(int16_t aDir,
-                              nsINode* aNode,
-                              int32_t aOffset,
-                              Text** outStartNode,
-                              int32_t* outStartOffset,
-                              Text** outEndNode,
-                              int32_t* outEndOffset)
+WSRunObject::GetASCIIWhitespacesBounds(int16_t aDir,
+                                       nsINode* aNode,
+                                       int32_t aOffset,
+                                       Text** outStartNode,
+                                       int32_t* outStartOffset,
+                                       Text** outEndNode,
+                                       int32_t* outEndOffset)
 {
   MOZ_ASSERT(aNode && outStartNode && outStartOffset && outEndNode &&
              outEndOffset);
 
   RefPtr<Text> startNode, endNode;
   int32_t startOffset = 0, endOffset = 0;
 
   if (aDir & eAfter) {
@@ -1756,17 +1782,17 @@ WSRunObject::CheckTrailingNBSPOfRun(WSFr
 
   if (NS_WARN_IF(!mHTMLEditor)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
 
   // first check for trailing nbsp
   WSPoint thePoint = GetPreviousCharPoint(aRun->EndPoint());
-  if (thePoint.mTextNode && thePoint.mChar == nbsp) {
+  if (thePoint.mTextNode && thePoint.mChar == kNBSP) {
     // now check that what is to the left of it is compatible with replacing nbsp with space
     WSPoint prevPoint = GetPreviousCharPoint(thePoint);
     if (prevPoint.mTextNode) {
       if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
         leftCheck = true;
       } else {
         spaceNBSP = true;
       }
@@ -1838,34 +1864,35 @@ WSRunObject::CheckTrailingNBSPOfRun(WSFr
       // whitespace (which will render as one space) followed by an nbsp (which
       // is at the end of the whitespace run).  Let's switch their order.  This
       // will ensure that if someone types two spaces after a sentence, and the
       // editor softwraps at this point, the spaces won't be split across lines,
       // which looks ugly and is bad for the moose.
 
       RefPtr<Text> startNode, endNode;
       int32_t startOffset, endOffset;
-      GetAsciiWSBounds(eBoth, prevPoint.mTextNode, prevPoint.mOffset + 1,
-                       getter_AddRefs(startNode), &startOffset,
-                       getter_AddRefs(endNode), &endOffset);
+      GetASCIIWhitespacesBounds(eBoth, prevPoint.mTextNode,
+                                prevPoint.mOffset + 1,
+                                getter_AddRefs(startNode), &startOffset,
+                                getter_AddRefs(endNode), &endOffset);
 
       // Delete that nbsp
       nsresult rv = DeleteRange(EditorRawDOMPoint(thePoint.mTextNode,
                                                   thePoint.mOffset),
                                 EditorRawDOMPoint(thePoint.mTextNode,
                                                   thePoint.mOffset + 1));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       // Finally, insert that nbsp before the ASCII ws run
       AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);
-      nsAutoString nbspStr(nbsp);
-      rv = htmlEditor->InsertTextIntoTextNodeImpl(nbspStr, *startNode,
-                                                  startOffset, true);
+      rv = htmlEditor->InsertTextIntoTextNodeImpl(nsDependentString(&kNBSP, 1),
+                                                  *startNode, startOffset,
+                                                  true);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
   return NS_OK;
 }
 
 nsresult
 WSRunObject::ReplacePreviousNBSPIfUnncessary(WSFragment* aRun,
@@ -1879,17 +1906,17 @@ WSRunObject::ReplacePreviousNBSPIfUnnces
 
   // 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
   // point in the ws abut an inserted break or text, so we don't have to worry
   // about what is after it.  What is after it now will end up after the
   // inserted object.
   bool canConvert = false;
   WSPoint thePoint = GetPreviousCharPoint(aPoint);
-  if (thePoint.mTextNode && thePoint.mChar == nbsp) {
+  if (thePoint.mTextNode && thePoint.mChar == kNBSP) {
     WSPoint prevPoint = GetPreviousCharPoint(thePoint);
     if (prevPoint.mTextNode) {
       if (!nsCRT::IsAsciiSpace(prevPoint.mChar)) {
         // If previous character is a NBSP and its previous character isn't
         // ASCII space, we can replace the NBSP with ASCII space.
         canConvert = true;
       }
     } else if (aRun->mLeftType == WSType::text ||
@@ -1933,17 +1960,17 @@ WSRunObject::CheckLeadingNBSP(WSFragment
                               int32_t aOffset)
 {
   // 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 point
   // in the ws abut an inserted text, so we don't have to worry about what is
   // before it.  What is before it now will end up before the inserted text.
   bool canConvert = false;
   WSPoint thePoint = GetNextCharPoint(EditorRawDOMPoint(aNode, aOffset));
-  if (thePoint.mChar == nbsp) {
+  if (thePoint.mChar == kNBSP) {
     WSPoint tmp = thePoint;
     // we want to be after thePoint
     tmp.mOffset++;
     WSPoint nextPoint = GetNextCharPoint(tmp);
     if (nextPoint.mTextNode) {
       if (!nsCRT::IsAsciiSpace(nextPoint.mChar)) {
         canConvert = true;
       }
--- a/editor/libeditor/WSRunObject.h
+++ b/editor/libeditor/WSRunObject.h
@@ -405,20 +405,50 @@ protected:
    * isn't in mNodeArray, they call one of these methods.  Then, these
    * methods look for nearest text node in mNodeArray from aPoint.
    * Then, will call GetNextCharPoint(const WSPoint&) or
    * GetPreviousCharPoint(const WSPoint&) and returns its result.
    */
   WSPoint GetNextCharPointInternal(const EditorRawDOMPoint& aPoint);
   WSPoint GetPreviousCharPointInternal(const EditorRawDOMPoint& aPoint);
 
-  nsresult ConvertToNBSP(WSPoint aPoint);
-  void GetAsciiWSBounds(int16_t aDir, nsINode* aNode, int32_t aOffset,
-                        dom::Text** outStartNode, int32_t* outStartOffset,
-                        dom::Text** outEndNode, int32_t* outEndOffset);
+  /**
+   * InsertNBSPAndRemoveFollowingASCIIWhitespaces() inserts an NBSP first.
+   * Then, if following characters are ASCII whitespaces, will remove them.
+   */
+  nsresult InsertNBSPAndRemoveFollowingASCIIWhitespaces(WSPoint aPoint);
+
+  /**
+   * GetASCIIWhitespacesBounds() retrieves whitespaces before and/or after the
+   * point specified by aNode and aOffset.
+   *
+   * @param aDir            Specify eBefore if you want to scan text backward.
+   *                        Specify eAfter if you want to scan text forward.
+   *                        Specify eBoth if you want to scan text to both
+   *                        direction.
+   * @param aNode           The container node where you want to start to scan
+   *                        whitespaces from.
+   * @param aOffset         The offset in aNode where you want to start to scan
+   *                        whitespaces from.
+   * @param outStartNode    [out] The container of first ASCII whitespace.
+   *                              If there is no whitespaces, returns nullptr.
+   * @param outStartOffset  [out] The offset of first ASCII whitespace in
+   *                              outStartNode.
+   * @param outEndNode      [out] The container of last ASCII whitespace.
+   *                              If there is no whitespaces, returns nullptr.
+   * @param outEndOffset    [out] The offset of last ASCII whitespace in
+   *                              outEndNode.
+   */
+  void GetASCIIWhitespacesBounds(int16_t aDir,
+                                 nsINode* aNode,
+                                 int32_t aOffset,
+                                 dom::Text** outStartNode,
+                                 int32_t* outStartOffset,
+                                 dom::Text** outEndNode,
+                                 int32_t* outEndOffset);
 
   /**
    * FindNearestRun() looks for a WSFragment which is closest to specified
    * direction from aPoint.
    *
    * @param aPoint      The point to start to look for.
    * @param aForward    true if caller needs to look for a WSFragment after the
    *                    point in the DOM tree.  Otherwise, i.e., before the
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -251,16 +251,17 @@ skip-if = toolkit == 'android' # bug 131
 [test_bug1358025.html]
 [test_bug1361008.html]
 [test_bug1368544.html]
 [test_bug1385905.html]
 [test_bug1390562.html]
 [test_bug1394758.html]
 [test_bug1399722.html]
 [test_bug1409520.html]
+[test_bug1425997.html]
 
 [test_CF_HTML_clipboard.html]
 subsuite = clipboard
 [test_composition_event_created_in_chrome.html]
 [test_contenteditable_focus.html]
 [test_documentCharacterSet.html]
 [test_dom_input_event_on_htmleditor.html]
 skip-if = toolkit == 'android' # bug 1054087
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1425997.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1425997
+-->
+<html>
+<head>
+  <title>Test for Bug 1425997</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1425997">Mozilla Bug 1425997</a>
+<p id="display"></p>
+<div id="content" style="display: none;">
+
+</div>
+
+<div id="editor" contenteditable>
+<!-- -->
+<span id="inline">foo</span>
+</div>
+
+<pre id="test">
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  let selection = window.getSelection();
+  let editor = document.getElementById("editor");
+  let originalContent = editor.innerHTML;
+  let count = 0;
+  function onCharacterDataModified() {
+    document.execCommand("delete", false);
+    if (++count == 3) {
+      editor.removeEventListener("DOMCharacterDataModified", onCharacterDataModified);
+    }
+  }
+  editor.addEventListener("DOMCharacterDataModified", onCharacterDataModified);
+  editor.focus();
+  selection.selectAllChildren(document.getElementById("inline"));
+  document.execCommand('insertHTML', false, 'text');
+  // This expected result is just same as the result of Chrome.
+  todo_is(editor.innerHTML, "\n<!-- --><span id=\"inline\">text</span>",
+          "The 'foo' should be replaced with 'text' and whitespaces before the span element should be removed");
+  SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>