Bug 1574852 - part 20: Move `HTMLEditRules::GetPromotedPoint()` to `HTMLEditor` r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 23 Aug 2019 06:05:38 +0000
changeset 489617 c980a90fce4fa170fb7282ccebc32c18ccd3c979
parent 489616 779aff8ec1b28fefb0489085c492d0f037f04dd9
child 489618 6f4e923fed9054d860140bdf3bc44b77a933a356
push id93483
push usermasayuki@d-toybox.com
push dateFri, 23 Aug 2019 15:23:55 +0000
treeherderautoland@c980a90fce4f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1574852
milestone70.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 1574852 - part 20: Move `HTMLEditRules::GetPromotedPoint()` to `HTMLEditor` r=m_kato `HTMLEditRules::GetPromotedPoint()` does too many things. Therefore, this patch splits it to 3 methods. One is for specific `EditSubAction` values, that's the first block in `GetPromotedPoint()`. It's named as `GetWhiteSpaceEndPoint()`. Next one is for expanding start of the range to start of its line. It's named as `GetCurrentHardLineStartPoint()`. The last one is for expanding end of the range to end of its line. It's named as `GetCurrentHardLineEndPoint()`. Differential Revision: https://phabricator.services.mozilla.com/D42791
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditRules.h
editor/libeditor/HTMLEditor.h
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -6857,202 +6857,210 @@ nsresult HTMLEditRules::NormalizeSelecti
   if (NS_WARN_IF(!CanHandleEditAction())) {
     error.SuppressException();
     return NS_ERROR_EDITOR_DESTROYED;
   }
   NS_WARNING_ASSERTION(!error.Failed(), "Failed to set selection");
   return error.StealNSResult();
 }
 
-EditorDOMPoint HTMLEditRules::GetPromotedPoint(
-    RulesEndpoint aWhere, nsINode& aNode, int32_t aOffset,
-    EditSubAction aEditSubAction) const {
-  MOZ_ASSERT(IsEditorDataAvailable());
-
-  // we do one thing for text actions, something else entirely for other
-  // actions
-  if (aEditSubAction == EditSubAction::eInsertText ||
-      aEditSubAction == EditSubAction::eInsertTextComingFromIME ||
-      aEditSubAction == EditSubAction::eInsertLineBreak ||
-      aEditSubAction == EditSubAction::eInsertParagraphSeparator ||
-      aEditSubAction == EditSubAction::eDeleteText) {
-    bool isSpace, isNBSP;
-    nsCOMPtr<nsIContent> content =
-        aNode.IsContent() ? aNode.AsContent() : nullptr;
-    nsCOMPtr<nsIContent> temp;
-    int32_t newOffset = aOffset;
-    // for text actions, we want to look backwards (or forwards, as
-    // appropriate) for additional whitespace or nbsp's.  We may have to act on
-    // these later even though they are outside of the initial selection.  Even
-    // if they are in another node!
-    while (content) {
-      int32_t offset;
-      if (aWhere == kStart) {
-        HTMLEditorRef().IsPrevCharInNodeWhitespace(
-            content, newOffset, &isSpace, &isNBSP, getter_AddRefs(temp),
-            &offset);
-      } else {
-        HTMLEditorRef().IsNextCharInNodeWhitespace(
-            content, newOffset, &isSpace, &isNBSP, getter_AddRefs(temp),
-            &offset);
-      }
-      if (isSpace || isNBSP) {
-        content = temp;
-        newOffset = offset;
-      } else {
-        break;
-      }
-    }
-
-    return EditorDOMPoint(content, newOffset);
-  }
-
-  EditorDOMPoint point(&aNode, aOffset);
-
-  // else not a text section.  In this case we want to see if we should grab
-  // any adjacent inline nodes and/or parents and other ancestors
-  if (aWhere == kStart) {
-    // some special casing for text nodes
-    if (point.IsInTextNode()) {
-      if (!point.GetContainer()->GetParentNode()) {
-        // Okay, can't promote any further
-        return point;
-      }
-      point.Set(point.GetContainer());
-    }
-
-    // look back through any further inline nodes that aren't across a <br>
-    // from us, and that are enclosed in the same block.
-    nsCOMPtr<nsINode> priorNode =
-        HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);
-
-    while (priorNode && priorNode->GetParentNode() &&
-           !HTMLEditorRef().IsVisibleBRElement(priorNode) &&
-           !HTMLEditor::NodeIsBlockStatic(*priorNode)) {
-      point.Set(priorNode);
-      priorNode = HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);
-    }
-
-    // finding the real start for this point.  look up the tree for as long as
-    // we are the first node in the container, and as long as we haven't hit
-    // the body node.
-    nsCOMPtr<nsIContent> nearNode =
-        HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);
-    while (!nearNode && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
-           point.GetContainer()->GetParentNode()) {
-      // some cutoffs are here: we don't need to also include them in the
-      // aWhere == kEnd case.  as long as they are in one or the other it will
-      // work.  special case for outdent: don't keep looking up if we have
-      // found a blockquote element to act on
-      if (aEditSubAction == EditSubAction::eOutdent &&
-          point.IsContainerHTMLElement(nsGkAtoms::blockquote)) {
-        break;
-      }
-
-      // Don't walk past the editable section. Note that we need to check
-      // before walking up to a parent because we need to return the parent
-      // object, so the parent itself might not be in the editable area, but
-      // it's OK if we're not performing a block-level action.
-      bool blockLevelAction =
-          aEditSubAction == EditSubAction::eIndent ||
-          aEditSubAction == EditSubAction::eOutdent ||
-          aEditSubAction == EditSubAction::eSetOrClearAlignment ||
-          aEditSubAction == EditSubAction::eCreateOrRemoveBlock;
-      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
-              point.GetContainer()->GetParentNode()) &&
-          (blockLevelAction ||
-           !HTMLEditorRef().IsDescendantOfEditorRoot(point.GetContainer()))) {
-        break;
-      }
-
-      point.Set(point.GetContainer());
-      nearNode = HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);
-    }
-    return point;
-  }
-
-  // aWhere == kEnd
-  // some special casing for text nodes
+// static
+EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(const RangeBoundary& aPoint,
+                                                 ScanDirection aScanDirection) {
+  if (NS_WARN_IF(!aPoint.IsSet()) ||
+      NS_WARN_IF(!aPoint.Container()->IsContent())) {
+    return EditorDOMPoint();
+  }
+
+  bool isSpace = false, isNBSP = false;
+  nsIContent* newContent = aPoint.Container()->AsContent();
+  int32_t newOffset = aPoint.Offset();
+  while (newContent) {
+    int32_t offset = -1;
+    nsCOMPtr<nsIContent> content;
+    if (aScanDirection == ScanDirection::Backward) {
+      HTMLEditor::IsPrevCharInNodeWhitespace(newContent, newOffset, &isSpace,
+                                             &isNBSP, getter_AddRefs(content),
+                                             &offset);
+    } else {
+      HTMLEditor::IsNextCharInNodeWhitespace(newContent, newOffset, &isSpace,
+                                             &isNBSP, getter_AddRefs(content),
+                                             &offset);
+    }
+    if (!isSpace && !isNBSP) {
+      break;
+    }
+    newContent = content;
+    newOffset = offset;
+  }
+  return EditorDOMPoint(newContent, newOffset);
+}
+
+EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
+    const RangeBoundary& aPoint, EditSubAction aEditSubAction) {
+  if (NS_WARN_IF(!aPoint.IsSet())) {
+    return EditorDOMPoint();
+  }
+
+  EditorDOMPoint point(aPoint);
+  // Start scanning from the container node if aPoint is in a text node.
+  // XXX Perhaps, IsInDataNode() must be expected.
   if (point.IsInTextNode()) {
     if (!point.GetContainer()->GetParentNode()) {
       // Okay, can't promote any further
+      // XXX Why don't we return start of the text node?
+      return point;
+    }
+    point.Set(point.GetContainer());
+  }
+
+  // Look back through any further inline nodes that aren't across a <br>
+  // from us, and that are enclosed in the same block.
+  // I.e., looking for start of current hard line.
+  for (nsIContent* previousEditableContent =
+           GetPreviousEditableHTMLNodeInBlock(point);
+       previousEditableContent && previousEditableContent->GetParentNode() &&
+       !IsVisibleBRElement(previousEditableContent) &&
+       !HTMLEditor::NodeIsBlockStatic(*previousEditableContent);
+       previousEditableContent = GetPreviousEditableHTMLNodeInBlock(point)) {
+    point.Set(previousEditableContent);
+    // XXX Why don't we check \n in pre-formated text node here?
+  }
+
+  // Finding the real start for this point unless current line starts after
+  // <br> element.  Look up the tree for as long as we are the first node in
+  // the container (typically, start of nearest block ancestor), and as long
+  // as we haven't hit the body node.
+  for (nsIContent* nearContent = GetPreviousEditableHTMLNodeInBlock(point);
+       !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
+       point.GetContainer()->GetParentNode();
+       nearContent = GetPreviousEditableHTMLNodeInBlock(point)) {
+    // Don't keep looking up if we have found a blockquote element to act on
+    // when we handle outdent.
+    // XXX Sounds like this is hacky.  If possible, it should be check in
+    //     outdent handler for consistency between edit sub-actions.
+    //     We should check Chromium's behavior of outdent when Selection
+    //     starts from `<blockquote>` and starts from first child of
+    //     `<blockquote>`.
+    if (aEditSubAction == EditSubAction::eOutdent &&
+        point.IsContainerHTMLElement(nsGkAtoms::blockquote)) {
+      break;
+    }
+
+    // Don't walk past the editable section. Note that we need to check
+    // before walking up to a parent because we need to return the parent
+    // object, so the parent itself might not be in the editable area, but
+    // it's OK if we're not performing a block-level action.
+    // XXX Here is too slow.  Let's cache active editing host first, then,
+    //     compair with container of the point when we climb up the tree.
+    bool blockLevelAction =
+        aEditSubAction == EditSubAction::eIndent ||
+        aEditSubAction == EditSubAction::eOutdent ||
+        aEditSubAction == EditSubAction::eSetOrClearAlignment ||
+        aEditSubAction == EditSubAction::eCreateOrRemoveBlock;
+    if (!IsDescendantOfEditorRoot(point.GetContainer()->GetParentNode()) &&
+        (blockLevelAction || !IsDescendantOfEditorRoot(point.GetContainer()))) {
+      break;
+    }
+
+    point.Set(point.GetContainer());
+  }
+  return point;
+}
+
+EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
+    const RangeBoundary& aPoint) {
+  if (NS_WARN_IF(!aPoint.IsSet())) {
+    return EditorDOMPoint();
+  }
+
+  EditorDOMPoint point(aPoint);
+  // Start scanning from the container node if aPoint is in a text node.
+  // XXX Perhaps, IsInDataNode() must be expected.
+  if (point.IsInTextNode()) {
+    if (!point.GetContainer()->GetParentNode()) {
+      // Okay, can't promote any further
+      // XXX Why don't we return end of the text node?
       return point;
     }
     // want to be after the text node
     point.Set(point.GetContainer());
     DebugOnly<bool> advanced = point.AdvanceOffset();
     NS_WARNING_ASSERTION(advanced,
                          "Failed to advance offset to after the text node");
   }
 
-  // look ahead through any further inline nodes that aren't across a <br> from
+  // Look ahead through any further inline nodes that aren't across a <br> from
   // us, and that are enclosed in the same block.
   // XXX Currently, we stop block-extending when finding visible <br> element.
   //     This might be different from "block-extend" of execCommand spec.
   //     However, the spec is really unclear.
   // XXX Probably, scanning only editable nodes is wrong for
   //     EditSubAction::eCreateOrRemoveBlock because it might be better to wrap
   //     existing inline elements even if it's non-editable.  For example,
   //     following examples with insertParagraph causes different result:
   //     * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
   //     * <div contenteditable>foo[]<b>bar</b></div>
   //     * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
   //     Only in the first case, after the caret position isn't wrapped with
   //     new <div> element.
-  nsCOMPtr<nsIContent> nextNode =
-      HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point);
-
-  while (nextNode && !HTMLEditor::NodeIsBlockStatic(*nextNode) &&
-         nextNode->GetParentNode()) {
-    point.Set(nextNode);
+  for (nsIContent* nextEditableContent = GetNextEditableHTMLNodeInBlock(point);
+       nextEditableContent &&
+       !HTMLEditor::NodeIsBlockStatic(*nextEditableContent) &&
+       nextEditableContent->GetParent();
+       nextEditableContent = GetNextEditableHTMLNodeInBlock(point)) {
+    point.Set(nextEditableContent);
     if (NS_WARN_IF(!point.AdvanceOffset())) {
       break;
     }
-    if (HTMLEditorRef().IsVisibleBRElement(nextNode)) {
+    if (IsVisibleBRElement(nextEditableContent)) {
       break;
     }
 
     // Check for newlines in pre-formatted text nodes.
-    if (EditorBase::IsPreformatted(nextNode) &&
-        EditorBase::IsTextNode(nextNode)) {
-      nsAutoString tempString;
-      nextNode->GetAsText()->GetData(tempString);
-      int32_t newlinePos = tempString.FindChar(nsCRT::LF);
+    if (EditorBase::IsPreformatted(nextEditableContent) &&
+        EditorBase::IsTextNode(nextEditableContent)) {
+      nsAutoString textContent;
+      nextEditableContent->GetAsText()->GetData(textContent);
+      int32_t newlinePos = textContent.FindChar(nsCRT::LF);
       if (newlinePos >= 0) {
-        if (static_cast<uint32_t>(newlinePos) + 1 == tempString.Length()) {
+        if (static_cast<uint32_t>(newlinePos) + 1 == textContent.Length()) {
           // No need for special processing if the newline is at the end.
           break;
         }
-        return EditorDOMPoint(nextNode, newlinePos + 1);
-      }
-    }
-    nextNode = HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point);
-  }
-
-  // finding the real end for this point.  look up the tree for as long as we
-  // are the last node in the container, and as long as we haven't hit the body
+        return EditorDOMPoint(nextEditableContent, newlinePos + 1);
+      }
+    }
+  }
+
+  // Finding the real end for this point unless current line ends with a <br>
+  // element.  Look up the tree for as long as we are the last node in the
+  // container (typically, block node), and as long as we haven't hit the body
   // node.
-  nsCOMPtr<nsIContent> nearNode =
-      HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point);
-  while (!nearNode && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
-         point.GetContainer()->GetParentNode()) {
+  for (nsIContent* nearContent = GetNextEditableHTMLNodeInBlock(point);
+       !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
+       point.GetContainer()->GetParentNode();
+       nearContent = GetNextEditableHTMLNodeInBlock(point)) {
     // Don't walk past the editable section. Note that we need to check before
     // walking up to a parent because we need to return the parent object, so
     // the parent itself might not be in the editable area, but it's OK.
-    if (!HTMLEditorRef().IsDescendantOfEditorRoot(point.GetContainer()) &&
-        !HTMLEditorRef().IsDescendantOfEditorRoot(
-            point.GetContainer()->GetParentNode())) {
+    // XXX Maybe returning parent of editing host is really error prone since
+    //     everybody need to check whether the end point is in editing host
+    //     when they touch there.
+    // XXX Here is too slow.  Let's cache active editing host first, then,
+    //     compair with container of the point when we climb up the tree.
+    if (!IsDescendantOfEditorRoot(point.GetContainer()) &&
+        !IsDescendantOfEditorRoot(point.GetContainer()->GetParentNode())) {
       break;
     }
 
     point.Set(point.GetContainer());
     if (NS_WARN_IF(!point.AdvanceOffset())) {
       break;
     }
-    nearNode = HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point);
   }
   return point;
 }
 
 void HTMLEditRules::GetPromotedRanges(
     nsTArray<RefPtr<nsRange>>& outArrayOfRanges,
     EditSubAction aEditSubAction) const {
   MOZ_ASSERT(IsEditorDataAvailable());
@@ -7085,19 +7093,21 @@ void HTMLEditRules::PromoteRange(nsRange
   }
 
   nsCOMPtr<nsINode> startNode = aRange.GetStartContainer();
   nsCOMPtr<nsINode> endNode = aRange.GetEndContainer();
   int32_t startOffset = aRange.StartOffset();
   int32_t endOffset = aRange.EndOffset();
 
   // MOOSE major hack:
-  // GetPromotedPoint doesn't really do the right thing for collapsed ranges
+  // The following methods don't really do the right thing for collapsed ranges
   // inside block elements that contain nothing but a solo <br>.  It's easier
-  // to put a workaround here than to revamp GetPromotedPoint.  :-(
+  // to put a workaround here than to revamp them.  :-(
+  // XXX This sounds odd in the cases using `GetWhiteSpaceEndPoint()`.  Don't
+  //     we hack something in the edit sub-action handlers?
   if (startNode == endNode && startOffset == endOffset) {
     RefPtr<Element> block = HTMLEditorRef().GetBlock(*startNode);
     if (block) {
       bool bIsEmptyNode = false;
       nsIContent* host = HTMLEditorRef().GetActiveEditingHost();
       if (NS_WARN_IF(!host)) {
         return;
       }
@@ -7109,54 +7119,91 @@ void HTMLEditRules::PromoteRange(nsRange
         startNode = block;
         endNode = block;
         startOffset = 0;
         endOffset = block->Length();
       }
     }
   }
 
-  if (aEditSubAction == EditSubAction::eInsertText ||
-      aEditSubAction == EditSubAction::eInsertTextComingFromIME ||
-      aEditSubAction == EditSubAction::eInsertLineBreak ||
-      aEditSubAction == EditSubAction::eInsertParagraphSeparator ||
-      aEditSubAction == EditSubAction::eDeleteText) {
-    if (!startNode->IsContent() || !endNode->IsContent()) {
-      // GetPromotedPoint cannot promote node when action type is text
-      // operation and selected node isn't content node.
-      return;
-    }
-  }
-
-  // Make a new adjusted range to represent the appropriate block content.
-  // This is tricky.  The basic idea is to push out the range endpoints to
-  // truly enclose the blocks that we will affect.
-
-  // Make sure that the new range ends up to be in the editable section.
-  // XXX Looks like that this check wastes the time.  Perhaps, we should
-  //     implement a method which checks both two DOM points in the editor
-  //     root.
-  EditorDOMPoint startPoint =
-      GetPromotedPoint(kStart, *startNode, startOffset, aEditSubAction);
-  if (!HTMLEditorRef().IsDescendantOfEditorRoot(
-          EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
-    return;
-  }
-  EditorDOMPoint endPoint =
-      GetPromotedPoint(kEnd, *endNode, endOffset, aEditSubAction);
-  EditorRawDOMPoint lastRawPoint(endPoint);
-  lastRawPoint.RewindOffset();
-  if (!HTMLEditorRef().IsDescendantOfEditorRoot(
-          EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
-    return;
-  }
-
-  DebugOnly<nsresult> rv = aRange.SetStartAndEnd(
-      startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
-  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  switch (aEditSubAction) {
+    case EditSubAction::eInsertText:
+    case EditSubAction::eInsertTextComingFromIME:
+    case EditSubAction::eInsertLineBreak:
+    case EditSubAction::eInsertParagraphSeparator:
+    case EditSubAction::eDeleteText: {
+      if (!startNode->IsContent() || !endNode->IsContent()) {
+        return;
+      }
+      // For text actions, we want to look backwards (or forwards, as
+      // appropriate) for additional whitespace or nbsp's.  We may have to act
+      // on these later even though they are outside of the initial selection.
+      // Even if they are in another node!
+      // XXX Although the comment mentioned that this may scan other text nodes,
+      //     GetWhiteSpaceEndPoint() scans only in given container node.
+      // XXX Looks like that we should make GetWhiteSpaceEndPoint() be a
+      //     instance method and stop scanning whitespaces when it reaches
+      //     active editing host.
+      EditorDOMPoint startPoint = HTMLEditor::GetWhiteSpaceEndPoint(
+          RangeBoundary(startNode, startOffset),
+          HTMLEditor::ScanDirection::Backward);
+      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
+              EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
+        return;
+      }
+      EditorDOMPoint endPoint =
+          HTMLEditor::GetWhiteSpaceEndPoint(RangeBoundary(endNode, endOffset),
+                                            HTMLEditor::ScanDirection::Forward);
+      EditorRawDOMPoint lastRawPoint(endPoint);
+      lastRawPoint.RewindOffset();
+      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
+              EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
+        return;
+      }
+      DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
+          startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
+      MOZ_ASSERT(NS_SUCCEEDED(rvIgnored));
+      break;
+    }
+    default: {
+      // Make a new adjusted range to represent the appropriate block content.
+      // This is tricky.  The basic idea is to push out the range endpoints to
+      // truly enclose the blocks that we will affect.
+
+      // Make sure that the new range ends up to be in the editable section.
+      // XXX Looks like that this check wastes the time.  Perhaps, we should
+      //     implement a method which checks both two DOM points in the editor
+      //     root.
+
+      EditorDOMPoint startPoint = HTMLEditorRef().GetCurrentHardLineStartPoint(
+          RangeBoundary(startNode, startOffset), aEditSubAction);
+      // XXX GetCurrentHardLineStartPoint() may return point of editing host.
+      //     Perhaps, we should change it and stop checking it here since
+      //     this check may be expensive.
+      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
+              EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
+        return;
+      }
+      EditorDOMPoint endPoint = HTMLEditorRef().GetCurrentHardLineEndPoint(
+          RangeBoundary(endNode, endOffset));
+      EditorRawDOMPoint lastRawPoint(endPoint);
+      lastRawPoint.RewindOffset();
+      // XXX GetCurrentHardLineEndPoint() may return point of editing host.
+      //     Perhaps, we should change it and stop checking it here since this
+      //     check may be expensive.
+      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
+              EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
+        return;
+      }
+      DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
+          startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
+      MOZ_ASSERT(NS_SUCCEEDED(rvIgnored));
+      break;
+    }
+  }
 }
 
 class UniqueFunctor final : public BoolDomIterFunctor {
  public:
   explicit UniqueFunctor(nsTArray<OwningNonNull<nsINode>>& aArray)
       : mArray(aArray) {}
 
   // Used to build list of all nodes iterator covers.
@@ -7191,19 +7238,18 @@ nsresult HTMLEditor::SplitInlinesAndColl
   rv = MaybeSplitElementsAtEveryBRElement(aOutArrayOfNodes, aEditSubAction);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "MaybeSplitElementsAtEveryBRElement() failed");
   return rv;
 }
 
 nsresult HTMLEditor::SplitTextNodesAtRangeEnd(
     nsTArray<RefPtr<nsRange>>& aArrayOfRanges) {
-  // Split text nodes. This is necessary, since GetPromotedPoint() may return
-  // a range ending in a text node in case where part of a pre-formatted
-  // elements needs to be moved.
+  // Split text nodes. This is necessary, since given ranges may end in text
+  // nodes in case where part of a pre-formatted elements needs to be moved.
   for (RefPtr<nsRange>& range : aArrayOfRanges) {
     EditorDOMPoint atEnd(range->EndRef());
     if (NS_WARN_IF(!atEnd.IsSet()) || !atEnd.IsInTextNode()) {
       continue;
     }
 
     if (!atEnd.IsStartOfContainer() && !atEnd.IsEndOfContainer()) {
       // Split the text node.
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -113,18 +113,16 @@ class HTMLEditRules : public TextEditRul
  protected:
   virtual ~HTMLEditRules() = default;
 
   HTMLEditor& HTMLEditorRef() const {
     MOZ_ASSERT(mData);
     return mData->HTMLEditorRef();
   }
 
-  enum RulesEndpoint { kStart, kEnd };
-
   /**
    * WillInsertParagraphSeparator() is called when insertParagraph command is
    * executed or something equivalent.  This method actually tries to insert
    * new paragraph or <br> element, etc.
    */
   MOZ_CAN_RUN_SCRIPT
   MOZ_MUST_USE EditActionResult WillInsertParagraphSeparator();
 
@@ -784,24 +782,16 @@ class HTMLEditRules : public TextEditRul
    * only one range.  If range start and/or end point is <br> node or something
    * non-editable point, they should be moved to nearest text node or something
    * where the other methods easier to handle edit action.
    */
   MOZ_CAN_RUN_SCRIPT
   MOZ_MUST_USE nsresult NormalizeSelection();
 
   /**
-   * GetPromotedPoint() figures out where a start or end point for a block
-   * operation really is.
-   */
-  EditorDOMPoint GetPromotedPoint(RulesEndpoint aWhere, nsINode& aNode,
-                                  int32_t aOffset,
-                                  EditSubAction aEditSubAction) const;
-
-  /**
    * GetPromotedRanges() runs all the selection range endpoint through
    * GetPromotedPoint().
    */
   void GetPromotedRanges(nsTArray<RefPtr<nsRange>>& outArrayOfRanges,
                          EditSubAction aEditSubAction) const;
 
   /**
    * PromoteRange() expands a range to include any parents for which all
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -1315,16 +1315,49 @@ class HTMLEditor final : public TextEdit
    * First, this collects all nodes in given ranges, then, modifies the
    * result for specific edit sub-actions.
    */
   nsresult CollectEditTargetNodes(
       nsTArray<RefPtr<nsRange>>& aArrayOfRanges,
       nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
       EditSubAction aEditSubAction) const;
 
+  /**
+   * GetWhiteSpaceEndPoint() returns point at first or last ASCII whitespace
+   * or non-breakable space starting from aPoint.  I.e., this returns next or
+   * previous point whether the character is neither ASCII whitespace nor
+   * non-brekable space.
+   */
+  enum class ScanDirection { Backward, Forward };
+  static EditorDOMPoint GetWhiteSpaceEndPoint(const RangeBoundary& aPoint,
+                                              ScanDirection aScanDirection);
+
+  /**
+   * GetCurrentHardLineStartPoint() returns start point of hard line
+   * including aPoint.  If the line starts after a `<br>` element, returns
+   * next sibling of the `<br>` element.  If the line is first line of a block,
+   * returns point of the block.
+   * NOTE: The result may be point of editing host.  I.e., the container may
+   *       be outside of editing host.
+   */
+  EditorDOMPoint GetCurrentHardLineStartPoint(const RangeBoundary& aPoint,
+                                              EditSubAction aEditSubAction);
+
+  /**
+   * GetCurrentHardLineEndPoint() returns end point of hard line including
+   * aPoint.  If the line ends with a `<br>` element, returns the `<br>`
+   * element unless it's the last node of a block.  If the line is last line
+   * of a block, returns next sibling of the block.  Additionally, if the
+   * line ends with a linefeed in pre-formated text node, returns point of
+   * the linefeed.
+   * NOTE: This result may be point of editing host.  I.e., the container
+   *       may be outside of editing host.
+   */
+  EditorDOMPoint GetCurrentHardLineEndPoint(const RangeBoundary& aPoint);
+
  protected:  // Called by helper classes.
   virtual void OnStartToHandleTopLevelEditSubAction(
       EditSubAction aEditSubAction, nsIEditor::EDirection aDirection) override;
   MOZ_CAN_RUN_SCRIPT
   virtual void OnEndHandlingTopLevelEditSubAction() override;
 
  protected:  // Shouldn't be used by friend classes
   virtual ~HTMLEditor();