Bug 1574852 - part 21: Move `HTMLEditRules::PromoteRange()` to `HTMLEditor` r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Fri, 23 Aug 2019 06:32:27 +0000
changeset 489686 cd856fd9fc39d5016d698d3a6f84a19eb6da7dc9
parent 489685 610592f70d7423ba8bf402dcd98a028c526975ac
child 489687 da6952acbbcfadeb19cdb998098a3a7048425d34
push id93531
push usermasayuki@d-toybox.com
push dateFri, 23 Aug 2019 23:10:57 +0000
treeherderautoland@cd856fd9fc39 [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 21: Move `HTMLEditRules::PromoteRange()` to `HTMLEditor` r=m_kato The method name is really unclear what's done. The method does 3 things. One is try to select a `<br>` element in empty block if given range is collapsed in the block. This is moved as `HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock()`. Next, it extends the given range to include adjuscent whitespaces only when edit sub-action is inserting or deleting text. This is moved as `HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces()`. Finally, when handling the other edit sub-actions, extends the given range to start/end of line at both edges. This is moved as `HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd()`. And also this patch makes each caller of `PromoteRange()` to check edit sub-action by themselves. Unfortunately, this duplicates same logic to multiple places, but makes what they do clearer. I think that the duplication issue should be fixed later if necessary. Instead, we should shine the blackbox of `HTMLEditRules` right now. Differential Revision: https://phabricator.services.mozilla.com/D43017
editor/libeditor/HTMLEditRules.cpp
editor/libeditor/HTMLEditRules.h
editor/libeditor/HTMLEditor.h
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -181,16 +181,64 @@ class MOZ_RAII AutoSetTemporaryAncestorL
 
  private:
   RefPtr<Selection> mSelection;
 };
 
 /********************************************************
  * mozilla::HTMLEditRules
  ********************************************************/
+template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+    RangeBoundary& aStartRef, RangeBoundary& aEndRef);
+template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+    RawRangeBoundary& aStartRef, RangeBoundary& aEndRef);
+template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+    RangeBoundary& aStartRef, RawRangeBoundary& aEndRef);
+template void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+    RawRangeBoundary& aStartRef, RawRangeBoundary& aEndRef);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+    const RangeBoundary& aStartRef, const RangeBoundary& aEndRef);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+    const RawRangeBoundary& aStartRef, const RangeBoundary& aEndRef);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+    const RangeBoundary& aStartRef, const RawRangeBoundary& aEndRef);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+    const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+    const RangeBoundary& aStartRef, const RangeBoundary& aEndRef,
+    EditSubAction aEditSubAction);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+    const RawRangeBoundary& aStartRef, const RangeBoundary& aEndRef,
+    EditSubAction aEditSubAction);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+    const RangeBoundary& aStartRef, const RawRangeBoundary& aEndRef,
+    EditSubAction aEditSubAction);
+template already_AddRefed<nsRange>
+HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+    const RawRangeBoundary& aStartRef, const RawRangeBoundary& aEndRef,
+    EditSubAction aEditSubAction);
+template EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(
+    const RangeBoundary& aPoint, ScanDirection aScanDirection);
+template EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(
+    const RawRangeBoundary& aPoint, ScanDirection aScanDirection);
+template EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
+    const RangeBoundary& aPoint, EditSubAction aEditSubAction);
+template EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
+    const RawRangeBoundary& aPoint, EditSubAction aEditSubAction);
+template EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
+    const RangeBoundary& aPoint);
+template EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
+    const RawRangeBoundary& aPoint);
 
 HTMLEditRules::HTMLEditRules() : mHTMLEditor(nullptr), mInitialized(false) {
   mIsHTMLEditRules = true;
 }
 
 nsresult HTMLEditRules::Init(TextEditor* aTextEditor) {
   if (NS_WARN_IF(!aTextEditor) || NS_WARN_IF(!aTextEditor->AsHTMLEditor())) {
     return NS_ERROR_INVALID_ARG;
@@ -395,19 +443,47 @@ nsresult HTMLEditRules::AfterEditInner()
           .TopLevelEditSubActionDataRef()
           .mChangedRange->IsPositioned() &&
       HTMLEditorRef().GetTopLevelEditSubAction() != EditSubAction::eUndo &&
       HTMLEditorRef().GetTopLevelEditSubAction() != EditSubAction::eRedo) {
     // don't let any txns in here move the selection around behind our back.
     // Note that this won't prevent explicit selection setting from working.
     AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef());
 
-    // expand the "changed doc range" as needed
-    PromoteRange(*HTMLEditorRef().TopLevelEditSubActionDataRef().mChangedRange,
-                 HTMLEditorRef().GetTopLevelEditSubAction());
+    switch (HTMLEditorRef().GetTopLevelEditSubAction()) {
+      case EditSubAction::eInsertText:
+      case EditSubAction::eInsertTextComingFromIME:
+      case EditSubAction::eInsertLineBreak:
+      case EditSubAction::eInsertParagraphSeparator:
+      case EditSubAction::eDeleteText: {
+        // XXX We should investigate whether this is really needed because it
+        //     seems that the following code does not handle the whitespaces.
+        RefPtr<nsRange> extendedChangedRange =
+            HTMLEditorRef().CreateRangeIncludingAdjuscentWhiteSpaces(
+                *HTMLEditorRef().TopLevelEditSubActionDataRef().mChangedRange);
+        if (extendedChangedRange) {
+          // Use extended range temporarily.
+          HTMLEditorRef().TopLevelEditSubActionDataRef().mChangedRange =
+              std::move(extendedChangedRange);
+        }
+        break;
+      }
+      default: {
+        RefPtr<nsRange> extendedChangedRange =
+            HTMLEditorRef().CreateRangeExtendedToHardLineStartAndEnd(
+                *HTMLEditorRef().TopLevelEditSubActionDataRef().mChangedRange,
+                HTMLEditorRef().GetTopLevelEditSubAction());
+        if (extendedChangedRange) {
+          // Use extended range temporarily.
+          HTMLEditorRef().TopLevelEditSubActionDataRef().mChangedRange =
+              std::move(extendedChangedRange);
+        }
+        break;
+      }
+    }
 
     // if we did a ranged deletion or handling backspace key, make sure we have
     // a place to put caret.
     // Note we only want to do this if the overall operation was deletion,
     // not if deletion was done along the way for
     // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc.
     // That's why this is here rather than DidDeleteSelection().
     // However, we shouldn't insert <br> elements if we've already removed
@@ -6858,18 +6934,19 @@ nsresult HTMLEditRules::NormalizeSelecti
     error.SuppressException();
     return NS_ERROR_EDITOR_DESTROYED;
   }
   NS_WARNING_ASSERTION(!error.Failed(), "Failed to set selection");
   return error.StealNSResult();
 }
 
 // static
-EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(const RangeBoundary& aPoint,
-                                                 ScanDirection aScanDirection) {
+template <typename PT, typename RT>
+EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(
+    const RangeBoundaryBase<PT, RT>& 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();
@@ -6889,18 +6966,19 @@ EditorDOMPoint HTMLEditor::GetWhiteSpace
       break;
     }
     newContent = content;
     newOffset = offset;
   }
   return EditorDOMPoint(newContent, newOffset);
 }
 
+template <typename PT, typename RT>
 EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
-    const RangeBoundary& aPoint, EditSubAction aEditSubAction) {
+    const RangeBoundaryBase<PT, RT>& 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()) {
@@ -6961,18 +7039,19 @@ EditorDOMPoint HTMLEditor::GetCurrentHar
       break;
     }
 
     point.Set(point.GetContainer());
   }
   return point;
 }
 
+template <typename PT, typename RT>
 EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
-    const RangeBoundary& aPoint) {
+    const RangeBoundaryBase<PT, RT>& 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()) {
@@ -7065,145 +7144,198 @@ void HTMLEditRules::GetPromotedRanges(
     EditSubAction aEditSubAction) const {
   MOZ_ASSERT(IsEditorDataAvailable());
 
   uint32_t rangeCount = SelectionRefPtr()->RangeCount();
   for (uint32_t i = 0; i < rangeCount; i++) {
     RefPtr<nsRange> selectionRange = SelectionRefPtr()->GetRangeAt(i);
     MOZ_ASSERT(selectionRange);
 
-    // Clone range so we don't muck with actual selection ranges
-    RefPtr<nsRange> opRange = selectionRange->CloneRange();
-
     // Make a new adjusted range to represent the appropriate block content.
     // The basic idea is to push out the range endpoints to truly enclose the
     // blocks that we will affect.  This call alters opRange.
-    PromoteRange(*opRange, aEditSubAction);
+
+    RefPtr<nsRange> opRange;
+    switch (aEditSubAction) {
+      case EditSubAction::eInsertText:
+      case EditSubAction::eInsertTextComingFromIME:
+      case EditSubAction::eInsertLineBreak:
+      case EditSubAction::eInsertParagraphSeparator:
+      case EditSubAction::eDeleteText:
+        opRange = HTMLEditorRef().CreateRangeIncludingAdjuscentWhiteSpaces(
+            *selectionRange);
+        break;
+      default:
+        opRange = HTMLEditorRef().CreateRangeExtendedToHardLineStartAndEnd(
+            *selectionRange, aEditSubAction);
+        break;
+    }
+    if (!opRange) {
+      opRange = selectionRange->CloneRange();
+    }
 
     // Stuff new opRange into array
     outArrayOfRanges.AppendElement(opRange);
   }
 }
 
-void HTMLEditRules::PromoteRange(nsRange& aRange,
-                                 EditSubAction aEditSubAction) const {
-  MOZ_ASSERT(IsEditorDataAvailable());
-  MOZ_ASSERT(!aRange.IsInSelection());
-
-  if (!aRange.IsPositioned()) {
-    return;
-  }
-
-  nsCOMPtr<nsINode> startNode = aRange.GetStartContainer();
-  nsCOMPtr<nsINode> endNode = aRange.GetEndContainer();
-  int32_t startOffset = aRange.StartOffset();
-  int32_t endOffset = aRange.EndOffset();
-
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+void HTMLEditor::SelectBRElementIfCollapsedInEmptyBlock(
+    RangeBoundaryBase<SPT, SRT>& aStartRef,
+    RangeBoundaryBase<EPT, ERT>& aEndRef) {
   // MOOSE major hack:
-  // 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 them.  :-(
+  // The GetWhiteSpaceEndPoint(), GetCurrentHardLineStartPoint() and
+  // GetCurrentHardLineEndPoint() 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 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;
-      }
-      // Make sure we don't go higher than our root element in the content tree
-      if (!host->IsInclusiveDescendantOf(block)) {
-        HTMLEditorRef().IsEmptyNode(block, &bIsEmptyNode, true, false);
-      }
-      if (bIsEmptyNode) {
-        startNode = block;
-        endNode = block;
-        startOffset = 0;
-        endOffset = block->Length();
-      }
-    }
-  }
-
-  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;
-    }
-  }
+  if (aStartRef != aEndRef) {
+    return;
+  }
+
+  Element* block = GetBlock(*aStartRef.Container());
+  if (!block) {
+    return;
+  }
+
+  Element* editingHost = GetActiveEditingHost();
+  if (NS_WARN_IF(!editingHost)) {
+    return;
+  }
+
+  // Make sure we don't go higher than our root element in the content tree
+  if (editingHost->IsInclusiveDescendantOf(block)) {
+    return;
+  }
+
+  bool isEmptyNode = false;
+  IsEmptyNode(block, &isEmptyNode, true, false);
+  if (isEmptyNode) {
+    aStartRef.Set(block, 0);
+    aEndRef.Set(block, block->Length());
+  }
+}
+
+already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+    const AbstractRange& aAbstractRange) {
+  if (!aAbstractRange.IsPositioned()) {
+    return nullptr;
+  }
+  return CreateRangeIncludingAdjuscentWhiteSpaces(aAbstractRange.StartRef(),
+                                                  aAbstractRange.EndRef());
+}
+
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+already_AddRefed<nsRange> HTMLEditor::CreateRangeIncludingAdjuscentWhiteSpaces(
+    const RangeBoundaryBase<SPT, SRT>& aStartRef,
+    const RangeBoundaryBase<EPT, ERT>& aEndRef) {
+  if (NS_WARN_IF(!aStartRef.IsSet()) || NS_WARN_IF(!aEndRef.IsSet())) {
+    return nullptr;
+  }
+
+  if (!aStartRef.Container()->IsContent() ||
+      !aEndRef.Container()->IsContent()) {
+    return nullptr;
+  }
+
+  RangeBoundaryBase<SPT, SRT> startRef(aStartRef);
+  RangeBoundaryBase<EPT, ERT> endRef(aEndRef);
+  SelectBRElementIfCollapsedInEmptyBlock(startRef, endRef);
+
+  // 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(
+      startRef, HTMLEditor::ScanDirection::Backward);
+  if (!IsDescendantOfEditorRoot(
+          EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
+    return nullptr;
+  }
+  EditorDOMPoint endPoint = HTMLEditor::GetWhiteSpaceEndPoint(
+      endRef, HTMLEditor::ScanDirection::Forward);
+  EditorRawDOMPoint lastRawPoint(endPoint);
+  lastRawPoint.RewindOffset();
+  if (!IsDescendantOfEditorRoot(
+          EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
+    return nullptr;
+  }
+
+  RefPtr<nsRange> range = new nsRange(GetDocument());
+  nsresult rv = range->SetStartAndEnd(startPoint.ToRawRangeBoundary(),
+                                      endPoint.ToRawRangeBoundary());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+  return range.forget();
+}
+
+already_AddRefed<nsRange> HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+    const AbstractRange& aAbstractRange, EditSubAction aEditSubAction) {
+  if (!aAbstractRange.IsPositioned()) {
+    return nullptr;
+  }
+  return CreateRangeExtendedToHardLineStartAndEnd(
+      aAbstractRange.StartRef(), aAbstractRange.EndRef(), aEditSubAction);
+}
+
+template <typename SPT, typename SRT, typename EPT, typename ERT>
+already_AddRefed<nsRange> HTMLEditor::CreateRangeExtendedToHardLineStartAndEnd(
+    const RangeBoundaryBase<SPT, SRT>& aStartRef,
+    const RangeBoundaryBase<EPT, ERT>& aEndRef, EditSubAction aEditSubAction) {
+  if (NS_WARN_IF(!aStartRef.IsSet()) || NS_WARN_IF(!aEndRef.IsSet())) {
+    return nullptr;
+  }
+
+  RangeBoundaryBase<SPT, SRT> startRef(aStartRef);
+  RangeBoundaryBase<EPT, ERT> endRef(aEndRef);
+  SelectBRElementIfCollapsedInEmptyBlock(startRef, endRef);
+
+  // 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 =
+      GetCurrentHardLineStartPoint(startRef, 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 (!IsDescendantOfEditorRoot(
+          EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
+    return nullptr;
+  }
+  EditorDOMPoint endPoint = GetCurrentHardLineEndPoint(endRef);
+  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 (!IsDescendantOfEditorRoot(
+          EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
+    return nullptr;
+  }
+
+  RefPtr<nsRange> range = new nsRange(GetDocument());
+  nsresult rv = range->SetStartAndEnd(startPoint.ToRawRangeBoundary(),
+                                      endPoint.ToRawRangeBoundary());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+  return range.forget();
 }
 
 class UniqueFunctor final : public BoolDomIterFunctor {
  public:
   explicit UniqueFunctor(nsTArray<OwningNonNull<nsINode>>& aArray)
       : mArray(aArray) {}
 
   // Used to build list of all nodes iterator covers.
@@ -7747,25 +7879,40 @@ nsIContent* HTMLEditor::GetMostAncestorI
 
 nsresult HTMLEditRules::GetNodesFromPoint(
     const EditorDOMPoint& aPoint, EditSubAction aEditSubAction,
     nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
     TouchContent aTouchContent) const {
   if (NS_WARN_IF(!aPoint.IsSet())) {
     return NS_ERROR_INVALID_ARG;
   }
-  RefPtr<nsRange> range = new nsRange(aPoint.GetContainer());
-  IgnoredErrorResult ignoredError;
-  range->SetStart(aPoint, ignoredError);
-  // error will assert on failure, because we are not cleaning it up,
-  // but we're asserting in that case anyway.
-  MOZ_ASSERT(!ignoredError.Failed());
-
-  // Expand the range to include adjacent inlines
-  PromoteRange(*range, aEditSubAction);
+  RefPtr<nsRange> range;
+  switch (aEditSubAction) {
+    case EditSubAction::eInsertText:
+    case EditSubAction::eInsertTextComingFromIME:
+    case EditSubAction::eInsertLineBreak:
+    case EditSubAction::eInsertParagraphSeparator:
+    case EditSubAction::eDeleteText:
+      range = HTMLEditorRef().CreateRangeIncludingAdjuscentWhiteSpaces(
+          aPoint.ToRawRangeBoundary(), aPoint.ToRawRangeBoundary());
+      break;
+    default:
+      range = HTMLEditorRef().CreateRangeExtendedToHardLineStartAndEnd(
+          aPoint.ToRawRangeBoundary(), aPoint.ToRawRangeBoundary(),
+          aEditSubAction);
+      break;
+  }
+  if (!range) {
+    ErrorResult error;
+    range = nsRange::Create(aPoint.ToRawRangeBoundary(),
+                            aPoint.ToRawRangeBoundary(), error);
+    if (NS_WARN_IF(error.Failed())) {
+      return error.StealNSResult();
+    }
+  }
 
   // Make array of ranges
   nsTArray<RefPtr<nsRange>> arrayOfRanges;
 
   // Stuff new opRange into array
   arrayOfRanges.AppendElement(range);
 
   if (aTouchContent == TouchContent::yes) {
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -788,22 +788,16 @@ class HTMLEditRules : public TextEditRul
 
   /**
    * 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
-   * editable children are already in range.
-   */
-  void PromoteRange(nsRange& aRange, EditSubAction aEditSubAction) const;
-
   void GetChildNodesForOperation(
       nsINode& aNode, nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes);
 
   enum class TouchContent { no, yes };
 
   /**
    * GetNodesFromPoint() constructs a list of nodes from a point that will be
    * operated on.
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -1322,41 +1322,81 @@ class HTMLEditor final : public TextEdit
 
   /**
    * 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);
+  template <typename PT, typename RT>
+  static EditorDOMPoint GetWhiteSpaceEndPoint(
+      const RangeBoundaryBase<PT, RT>& 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);
+  template <typename PT, typename RT>
+  EditorDOMPoint GetCurrentHardLineStartPoint(
+      const RangeBoundaryBase<PT, RT>& 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);
+  template <typename PT, typename RT>
+  EditorDOMPoint GetCurrentHardLineEndPoint(
+      const RangeBoundaryBase<PT, RT>& aPoint);
+
+  /**
+   * CreateRangeIncludingAdjuscentWhiteSpaces() creates an nsRange instance
+   * which may be expanded from the given range to include adjuscent
+   * whitespaces.  If this fails handling something, returns nullptr.
+   */
+  already_AddRefed<nsRange> CreateRangeIncludingAdjuscentWhiteSpaces(
+      const dom::AbstractRange& aAbstractRange);
+  template <typename SPT, typename SRT, typename EPT, typename ERT>
+  already_AddRefed<nsRange> CreateRangeIncludingAdjuscentWhiteSpaces(
+      const RangeBoundaryBase<SPT, SRT>& aStartRef,
+      const RangeBoundaryBase<EPT, ERT>& aEndRef);
+
+  /**
+   * CreateRangeExtendedToHardLineStartAndEnd() creates an nsRange instance
+   * which may be expanded to start/end of hard line at both edges of the given
+   * range.  If this fails handling something, returns nullptr.
+   */
+  already_AddRefed<nsRange> CreateRangeExtendedToHardLineStartAndEnd(
+      const dom::AbstractRange& aAbstractRange, EditSubAction aEditSubAction);
+  template <typename SPT, typename SRT, typename EPT, typename ERT>
+  already_AddRefed<nsRange> CreateRangeExtendedToHardLineStartAndEnd(
+      const RangeBoundaryBase<SPT, SRT>& aStartRef,
+      const RangeBoundaryBase<EPT, ERT>& aEndRef, EditSubAction aEditSubAction);
+
+  /**
+   * SelectBRElementIfCollapsedInEmptyBlock() helper method for
+   * CreateRangeIncludingAdjuscentWhiteSpaces() and
+   * CreateRangeExtendedToLineStartAndEnd().  If the given range is collapsed
+   * in a block and the block has only one `<br>` element, this makes
+   * aStartRef and aEndRef select the `<br>` element.
+   */
+  template <typename SPT, typename SRT, typename EPT, typename ERT>
+  void SelectBRElementIfCollapsedInEmptyBlock(
+      RangeBoundaryBase<SPT, SRT>& aStartRef,
+      RangeBoundaryBase<EPT, ERT>& aEndRef);
 
  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