Commit bddc344c authored by Masayuki Nakano's avatar Masayuki Nakano
Browse files

Bug 1658702 - part 14: Implement a path to compute target ranges of...

Bug 1658702 - part 14: Implement a path to compute target ranges of `EditorBase::DeleteRangesWithTransaction()` r=m_kato

`HTMLEditor` falls back to `EditorBase::DeleteRangesWithTransaction()` in
some cases, especially when handling non-collapsed ranges.  Therefore, we
need to implement it for the following patches.

The code corresponds to:
* https://searchfox.org/mozilla-central/rev/62c443a7c801ba9672de34c2867ec1665a4bbe67/editor/libeditor/EditorBase.cpp#3848-3862
* https://searchfox.org/mozilla-central/rev/62c443a7c801ba9672de34c2867ec1665a4bbe67/editor/libeditor/EditorBase.cpp#3355,3371,3374,3376,3386-3387,3392
* https://searchfox.org/mozilla-central/rev/62c443a7c801ba9672de34c2867ec1665a4bbe67/editor/libeditor/EditorBase.cpp#3421,3431-3432,3435-3436,3444,3453-3455,3464-3465,3473-3474,3477-3478,3486,3495-3497,3506-3507,3515-3520,3526-3528,3535-3538,3543-3544,3546-3549,3558-3562,3570-3572,3579-3580
* https://searchfox.org/mozilla-central/rev/62c443a7c801ba9672de34c2867ec1665a4bbe67/editor/libeditor/DeleteTextTransaction.cpp#34,49-51,59,69-71

Basically, we don't need to touch the ranges, but if `aDirectionAndAmount` is
`nsIEditor::eNext` or `nsIEditor::ePrevious`, each collapsed range is extened
for:
* previous character (treating only surrogate pair)
* next character (treating only surrogate pair)
* selecting another content node

This logic is much rougher than what `AutoDeleteRangesHandler` and its nested
classes do.  So, `HTMLEditor` should stop using it in the future, but we need
to keep using it for now.

Depends on D90210

Differential Revision: https://phabricator.services.mozilla.com/D90211
parent 3177f74d
Loading
Loading
Loading
Loading
+220 −7
Original line number Diff line number Diff line
@@ -2651,6 +2651,30 @@ class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
                                   // fall it back again.
  }
  /**
   * ComputeRangesToDeleteRangesWithTransaction() computes target ranges
   * which will be called by `EditorBase::DeleteRangesWithTransaction()`.
   * TODO: We should not use it for consistency with each deletion handler
   *       in this and nested classes.
   */
  nsresult ComputeRangesToDeleteRangesWithTransaction(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsIEditor::EStripWrappers aStripWrappers,
      AutoRangeArray& aRangesToDelete) const;
  nsresult FallbackToComputeRangesToDeleteRangesWithTransaction(
      const HTMLEditor& aHTMLEditor, AutoRangeArray& aRangesToDelete) const {
    MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
    MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete));
    nsresult rv = ComputeRangesToDeleteRangesWithTransaction(
        aHTMLEditor, mOriginalDirectionAndAmount, mOriginalStripWrappers,
        aRangesToDelete);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "AutoDeleteRangesHandler::"
                         "ComputeRangesToDeleteRangesWithTransaction() failed");
    return rv;
  }
  class MOZ_STACK_CLASS AutoBlockElementsJoiner final {
   public:
    AutoBlockElementsJoiner() = delete;
@@ -3435,8 +3459,13 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDelete(
        // XXX In this case, do we need to modify the range again?
        return NS_SUCCESS_DOM_NO_OPERATION;
      }
      // aRangesToDelete must select some haracters, a word or the line.
      return NS_OK;
      nsresult rv = FallbackToComputeRangesToDeleteRangesWithTransaction(
          aHTMLEditor, aRangesToDelete);
      NS_WARNING_ASSERTION(
          NS_SUCCEEDED(rv),
          "AutoDeleteRangesHandler::"
          "FallbackToComputeRangesToDeleteRangesWithTransaction() failed");
      return rv;
    }
    if (aRangesToDelete.IsCollapsed()) {
@@ -4551,17 +4580,21 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
  if (HTMLEditor::NodesInDifferentTableElements(*mLeftContent,
                                                *mRightContent)) {
    if (!mDeleteRangesHandler->CanFallbackToDeleteRangesWithTransaction(
    if (!mDeleteRangesHandlerConst.CanFallbackToDeleteRangesWithTransaction(
            aRangesToDelete)) {
      nsresult rv = aRangesToDelete.Collapse(aCaretPoint);
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                           "AutoRangeArray::Collapse() failed");
      return rv;
    }
    // TODO: In this case, HandleDeleteAtOtherBlockBoundary() may fall it
    //       back to `DeleteRangesWithTransaction()` so that we need
    //       `ComputTargetRangesToDelete()` for it.
    return NS_ERROR_NOT_IMPLEMENTED;
    nsresult rv = mDeleteRangesHandlerConst
                      .FallbackToComputeRangesToDeleteRangesWithTransaction(
                          aHTMLEditor, aRangesToDelete);
    NS_WARNING_ASSERTION(
        NS_SUCCEEDED(rv),
        "AutoDeleteRangesHandler::"
        "FallbackToComputeRangesToDeleteRangesWithTransaction() failed");
    return rv;
  }
  AutoInclusiveAncestorBlockElementsJoiner joiner(*mLeftContent,
@@ -5723,6 +5756,186 @@ HTMLEditor::AutoDeleteRangesHandler::DeleteParentBlocksWithTransactionIfEmpty(
  return rv;
}
nsresult
HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction(
    const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    nsIEditor::EStripWrappers aStripWrappers,
    AutoRangeArray& aRangesToDelete) const {
  MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
  MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
  MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty());
  EditorBase::HowToHandleCollapsedRange howToHandleCollapsedRange =
      EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount);
  if (NS_WARN_IF(aRangesToDelete.IsCollapsed() &&
                 howToHandleCollapsedRange ==
                     EditorBase::HowToHandleCollapsedRange::Ignore)) {
    return NS_ERROR_FAILURE;
  }
  auto extendRangeToSelectCharacterForward =
      [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void {
    const nsTextFragment& textFragment =
        aCaretPoint.ContainerAsText()->TextFragment();
    if (!textFragment.GetLength()) {
      return;
    }
    if (textFragment.IsHighSurrogateFollowedByLowSurrogateAt(
            aCaretPoint.Offset())) {
      DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
          aCaretPoint.ContainerAsText(), aCaretPoint.Offset(),
          aCaretPoint.ContainerAsText(), aCaretPoint.Offset() + 2);
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                           "nsRange::SetStartAndEnd() failed");
      return;
    }
    DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
        aCaretPoint.ContainerAsText(), aCaretPoint.Offset(),
        aCaretPoint.ContainerAsText(), aCaretPoint.Offset() + 1);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                         "nsRange::SetStartAndEnd() failed");
  };
  auto extendRangeToSelectCharacterBackward =
      [](nsRange& aRange, const EditorRawDOMPointInText& aCaretPoint) -> void {
    if (aCaretPoint.IsStartOfContainer()) {
      return;
    }
    const nsTextFragment& textFragment =
        aCaretPoint.ContainerAsText()->TextFragment();
    if (!textFragment.GetLength()) {
      return;
    }
    if (textFragment.IsLowSurrogateFollowingHighSurrogateAt(
            aCaretPoint.Offset() - 1)) {
      DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
          aCaretPoint.ContainerAsText(), aCaretPoint.Offset() - 2,
          aCaretPoint.ContainerAsText(), aCaretPoint.Offset());
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                           "nsRange::SetStartAndEnd() failed");
      return;
    }
    DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
        aCaretPoint.ContainerAsText(), aCaretPoint.Offset() - 1,
        aCaretPoint.ContainerAsText(), aCaretPoint.Offset());
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                         "nsRange::SetStartAndEnd() failed");
  };
  for (OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) {
    // If it's not collapsed, `DeleteRangeTransaction::Create()` will be called
    // with it and `DeleteRangeTransaction` won't modify the range.
    if (!range->Collapsed()) {
      continue;
    }
    if (howToHandleCollapsedRange ==
        EditorBase::HowToHandleCollapsedRange::Ignore) {
      continue;
    }
    // In the other cases, `EditorBase::CreateTransactionForCollapsedRange()`
    // will handle the collapsed range.
    EditorRawDOMPoint caretPoint(range->StartRef());
    if (howToHandleCollapsedRange ==
            EditorBase::HowToHandleCollapsedRange::ExtendBackward &&
        caretPoint.IsStartOfContainer()) {
      nsIContent* previousEditableContent =
          aHTMLEditor.GetPreviousEditableNode(*caretPoint.GetContainer());
      if (!previousEditableContent) {
        continue;
      }
      if (!previousEditableContent->IsText()) {
        IgnoredErrorResult ignoredError;
        range->SelectNode(*previousEditableContent, ignoredError);
        NS_WARNING_ASSERTION(!ignoredError.Failed(),
                             "nsRange::SelectNode() failed");
        continue;
      }
      extendRangeToSelectCharacterBackward(
          range,
          EditorRawDOMPointInText::AtEndOf(*previousEditableContent->AsText()));
      continue;
    }
    if (howToHandleCollapsedRange ==
            EditorBase::HowToHandleCollapsedRange::ExtendForward &&
        caretPoint.IsEndOfContainer()) {
      nsIContent* nextEditableContent =
          aHTMLEditor.GetNextEditableNode(*caretPoint.GetContainer());
      if (!nextEditableContent) {
        continue;
      }
      if (!nextEditableContent->IsText()) {
        IgnoredErrorResult ignoredError;
        range->SelectNode(*nextEditableContent, ignoredError);
        NS_WARNING_ASSERTION(!ignoredError.Failed(),
                             "nsRange::SelectNode() failed");
        continue;
      }
      extendRangeToSelectCharacterForward(
          range, EditorRawDOMPointInText(nextEditableContent->AsText(), 0));
      continue;
    }
    if (caretPoint.IsInTextNode()) {
      if (howToHandleCollapsedRange ==
          EditorBase::HowToHandleCollapsedRange::ExtendBackward) {
        extendRangeToSelectCharacterBackward(
            range, EditorRawDOMPointInText(caretPoint.ContainerAsText(),
                                           caretPoint.Offset()));
        continue;
      }
      extendRangeToSelectCharacterForward(
          range, EditorRawDOMPointInText(caretPoint.ContainerAsText(),
                                         caretPoint.Offset()));
      continue;
    }
    nsIContent* editableContent =
        howToHandleCollapsedRange ==
                EditorBase::HowToHandleCollapsedRange::ExtendBackward
            ? aHTMLEditor.GetPreviousEditableNode(caretPoint)
            : aHTMLEditor.GetNextEditableNode(caretPoint);
    if (!editableContent) {
      continue;
    }
    while (editableContent && editableContent->IsCharacterData() &&
           !editableContent->Length()) {
      editableContent =
          howToHandleCollapsedRange ==
                  EditorBase::HowToHandleCollapsedRange::ExtendBackward
              ? aHTMLEditor.GetPreviousEditableNode(*editableContent)
              : aHTMLEditor.GetNextEditableNode(*editableContent);
    }
    if (!editableContent) {
      continue;
    }
    if (!editableContent->IsText()) {
      IgnoredErrorResult ignoredError;
      range->SelectNode(*editableContent, ignoredError);
      NS_WARNING_ASSERTION(!ignoredError.Failed(),
                           "nsRange::SelectNode() failed");
      continue;
    }
    if (howToHandleCollapsedRange ==
        EditorBase::HowToHandleCollapsedRange::ExtendBackward) {
      extendRangeToSelectCharacterBackward(
          range, EditorRawDOMPointInText::AtEndOf(*editableContent->AsText()));
      continue;
    }
    extendRangeToSelectCharacterForward(
        range, EditorRawDOMPointInText(editableContent->AsText(), 0));
    continue;
  }
  return NS_OK;
}
template <typename EditorDOMPointType>
nsresult HTMLEditor::DeleteTextAndTextNodesWithTransaction(
    const EditorDOMPointType& aStartPoint, const EditorDOMPointType& aEndPoint,