Bug 1656799 - part 1: Make HTMLEditor::HandleDeleteNonCollapsedSelection() store selection ranges with AutoRangeArray first r=m_kato

Depends on D85686

Differential Revision: https://phabricator.services.mozilla.com/D85845
This commit is contained in:
Masayuki Nakano 2020-08-06 07:03:55 +00:00
parent a0375c98e1
commit 93ff6864bb
3 changed files with 92 additions and 75 deletions

View File

@ -752,6 +752,8 @@ class MOZ_STACK_CLASS AutoRangeArray final {
auto& Ranges() { return mRanges; }
const auto& Ranges() const { return mRanges; }
auto& FirstRangeRef() { return mRanges[0]; }
const auto& FirstRangeRef() const { return mRanges[0]; }
template <template <typename> typename StrongPtrType>
AutoTArray<StrongPtrType<nsRange>, 8> CloneRanges() const {

View File

@ -43,6 +43,7 @@
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsFrameSelection.h"
#include "nsGkAtoms.h"
#include "nsHTMLDocument.h"
#include "nsIContent.h"
@ -3278,55 +3279,54 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection(
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
MOZ_ASSERT(!SelectionRefPtr()->IsCollapsed());
AutoRangeArray rangesToDelete(*SelectionRefPtr());
MOZ_ASSERT(!rangesToDelete.IsCollapsed());
if (NS_WARN_IF(!rangesToDelete.FirstRangeRef()->StartRef().IsSet()) ||
NS_WARN_IF(!rangesToDelete.FirstRangeRef()->EndRef().IsSet())) {
return EditActionResult(NS_ERROR_FAILURE);
}
// Else we have a non-collapsed selection. First adjust the selection.
// XXX Why do we extend selection only when there is only one range?
if (SelectionRefPtr()->RangeCount() == 1) {
if (const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0)) {
RefPtr<StaticRange> extendedRange =
GetRangeExtendedToIncludeInvisibleNodes(*firstRange);
if (!extendedRange) {
NS_WARNING(
"HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes() failed");
return EditActionResult(NS_ERROR_FAILURE);
}
ErrorResult error;
MOZ_KnownLive(SelectionRefPtr())
->SetStartAndEndInLimiter(extendedRange->StartRef().AsRaw(),
extendedRange->EndRef().AsRaw(), error);
if (NS_WARN_IF(Destroyed())) {
error = NS_ERROR_EDITOR_DESTROYED;
}
if (error.Failed()) {
NS_WARNING_ASSERTION(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED),
"Selection::SetStartAndEndInLimiter() failed");
return EditActionResult(error.StealNSResult());
}
if (rangesToDelete.Ranges().Length() == 1) {
RefPtr<nsRange> extendedRange =
GetRangeExtendedToIncludeInvisibleNodes(rangesToDelete.FirstRangeRef());
if (!extendedRange || !extendedRange->IsPositioned() ||
!extendedRange->StartRef().IsSet() ||
!extendedRange->EndRef().IsSet()) {
NS_WARNING(
"HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes() failed");
return EditActionResult(NS_ERROR_FAILURE);
}
nsFrameSelection* frameSelection = SelectionRefPtr()->GetFrameSelection();
if (NS_WARN_IF(!frameSelection)) {
return EditActionResult(NS_ERROR_FAILURE);
}
if (!frameSelection->IsValidSelectionPoint(
extendedRange->GetStartContainer()) ||
!frameSelection->IsValidSelectionPoint(
extendedRange->GetEndContainer())) {
NS_WARNING(
"HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes() returned a "
"range outer limit");
return EditActionResult(NS_ERROR_FAILURE);
}
rangesToDelete.FirstRangeRef() = *extendedRange;
}
// Remember that we did a ranged delete for the benefit of AfterEditInner().
TopLevelEditSubActionDataRef().mDidDeleteNonCollapsedRange = true;
const nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
if (NS_WARN_IF(!firstRange)) {
return EditActionResult(NS_ERROR_FAILURE);
}
EditorDOMPoint firstRangeStart(firstRange->StartRef());
EditorDOMPoint firstRangeEnd(firstRange->EndRef());
if (NS_WARN_IF(!firstRangeStart.IsSet()) ||
NS_WARN_IF(!firstRangeEnd.IsSet())) {
return EditActionResult(NS_ERROR_FAILURE);
}
// Figure out if the endpoints are in nodes that can be merged. Adjust
// surrounding white-space in preparation to delete selection.
if (!IsPlaintextEditor()) {
AutoTransactionsConserveSelection dontChangeMySelection(*this);
EditorDOMPoint firstRangeStart(rangesToDelete.FirstRangeRef()->StartRef());
EditorDOMPoint firstRangeEnd(rangesToDelete.FirstRangeRef()->EndRef());
nsresult rv = WhiteSpaceVisibilityKeeper::PrepareToDeleteRange(
*this, &firstRangeStart, &firstRangeEnd);
if (NS_WARN_IF(Destroyed())) {
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_FAILED(rv)) {
NS_WARNING("WhiteSpaceVisibilityKeeper::PrepareToDeleteRange() failed");
return EditActionResult(rv);
@ -3341,22 +3341,32 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection(
NS_WARNING("Mutation event listener changed the DOM tree");
return EditActionHandled(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
rv = rangesToDelete.FirstRangeRef()->SetStartAndEnd(
firstRangeStart.ToRawRangeBoundary(),
firstRangeEnd.ToRawRangeBoundary());
if (NS_FAILED(rv)) {
NS_WARNING("Failed to modify first range to delete");
return EditActionResult(NS_ERROR_FAILURE);
}
}
// XXX This is odd. We do we simply use `DeleteSelectionWithTransaction()`
// XXX This is odd. We do we simply use `DeleteRangesWithTransaction()`
// only when **first** range is in same container?
if (firstRangeStart.GetContainer() == firstRangeEnd.GetContainer()) {
if (rangesToDelete.FirstRangeRef()->GetStartContainer() ==
rangesToDelete.FirstRangeRef()->GetEndContainer()) {
// Because of previous DOM tree changes, the range may be collapsed.
// If we've already removed all contents in the range, we shouldn't
// delete anything around the caret.
if (firstRangeStart != firstRangeEnd) {
EditorDOMPoint firstRangeStart(rangesToDelete.FirstRangeRef()->StartRef());
EditorDOMPoint firstRangeEnd(rangesToDelete.FirstRangeRef()->EndRef());
if (!rangesToDelete.FirstRangeRef()->Collapsed()) {
AutoTrackDOMPoint startTracker(RangeUpdaterRef(), &firstRangeStart);
AutoTrackDOMPoint endTracker(RangeUpdaterRef(), &firstRangeEnd);
nsresult rv =
DeleteSelectionWithTransaction(aDirectionAndAmount, aStripWrappers);
nsresult rv = DeleteRangesWithTransaction(aDirectionAndAmount,
aStripWrappers, rangesToDelete);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::DeleteSelectionWithTransaction() failed");
NS_WARNING("EditorBase::DeleteRangesWithTransaction() failed");
return EditActionHandled(rv);
}
}
@ -3370,11 +3380,18 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection(
return EditActionHandled(rv);
}
if (NS_WARN_IF(
!rangesToDelete.FirstRangeRef()->GetStartContainer()->IsContent()) ||
NS_WARN_IF(
!rangesToDelete.FirstRangeRef()->GetEndContainer()->IsContent())) {
return EditActionHandled(NS_ERROR_FAILURE); // XXX "handled"?
}
// Figure out mailcite ancestors
RefPtr<Element> startCiteNode =
GetMostAncestorMailCiteElement(*firstRangeStart.GetContainer());
RefPtr<Element> endCiteNode =
GetMostAncestorMailCiteElement(*firstRangeEnd.GetContainer());
RefPtr<Element> startCiteNode = GetMostAncestorMailCiteElement(
*rangesToDelete.FirstRangeRef()->GetStartContainer());
RefPtr<Element> endCiteNode = GetMostAncestorMailCiteElement(
*rangesToDelete.FirstRangeRef()->GetEndContainer());
// If we only have a mailcite at one of the two endpoints, set the
// directionality of the deletion so that the selection will end up
@ -3387,38 +3404,36 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection(
// Figure out block parents
RefPtr<Element> leftBlockElement =
firstRangeStart.IsInContentNode()
? HTMLEditUtils::GetInclusiveAncestorBlockElement(
*firstRangeStart.ContainerAsContent())
: nullptr;
HTMLEditUtils::GetInclusiveAncestorBlockElement(
*rangesToDelete.FirstRangeRef()->GetStartContainer()->AsContent());
RefPtr<Element> rightBlockElement =
firstRangeEnd.IsInContentNode()
? HTMLEditUtils::GetInclusiveAncestorBlockElement(
*firstRangeEnd.ContainerAsContent())
: nullptr;
HTMLEditUtils::GetInclusiveAncestorBlockElement(
*rangesToDelete.FirstRangeRef()->GetEndContainer()->AsContent());
if (NS_WARN_IF(!leftBlockElement) || NS_WARN_IF(!rightBlockElement)) {
return EditActionHandled(NS_ERROR_FAILURE); // XXX "handled"??
}
// XXX This is also odd. We do we simply use
// `DeleteSelectionWithTransaction()` only when **first** range is in
// `DeleteRangesWithTransaction()` only when **first** range is in
// same block?
if (leftBlockElement && leftBlockElement == rightBlockElement) {
EditorDOMPoint firstRangeStart(rangesToDelete.FirstRangeRef()->StartRef());
EditorDOMPoint firstRangeEnd(rangesToDelete.FirstRangeRef()->EndRef());
{
AutoTrackDOMPoint startTracker(RangeUpdaterRef(), &firstRangeStart);
AutoTrackDOMPoint endTracker(RangeUpdaterRef(), &firstRangeEnd);
nsresult rv =
DeleteSelectionWithTransaction(aDirectionAndAmount, aStripWrappers);
nsresult rv = DeleteRangesWithTransaction(aDirectionAndAmount,
aStripWrappers, rangesToDelete);
if (rv == NS_ERROR_EDITOR_DESTROYED) {
NS_WARNING(
"EditorBase::DeleteSelectionWithTransaction() caused destroying "
"EditorBase::DeleteRangesWithTransaction() caused destroying "
"the editor");
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"EditorBase::DeleteSelectionWithTransaction() failed, but ignored");
"EditorBase::DeleteRangesWithTransaction() failed, but ignored");
}
nsresult rv = DeleteUnnecessaryNodesAndCollapseSelection(
aDirectionAndAmount, firstRangeStart, firstRangeEnd);
@ -3443,10 +3458,10 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection(
HTMLEditUtils::IsListItem(leftBlockElement) ||
HTMLEditUtils::IsHeader(*leftBlockElement))) {
// First delete the selection
nsresult rv =
DeleteSelectionWithTransaction(aDirectionAndAmount, aStripWrappers);
nsresult rv = DeleteRangesWithTransaction(aDirectionAndAmount,
aStripWrappers, rangesToDelete);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::DeleteSelectionWithTransaction() failed");
NS_WARNING("EditorBase::DeleteRangesWithTransaction() failed");
return EditActionHandled(rv);
}
// Join blocks
@ -3468,6 +3483,8 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection(
// Otherwise, delete every nodes in all ranges, then, clean up something.
EditActionResult result(NS_OK);
result.MarkAsHandled();
EditorDOMPoint firstRangeStart(rangesToDelete.FirstRangeRef()->StartRef());
EditorDOMPoint firstRangeEnd(rangesToDelete.FirstRangeRef()->EndRef());
{
AutoTrackDOMPoint startTracker(RangeUpdaterRef(), &firstRangeStart);
AutoTrackDOMPoint endTracker(RangeUpdaterRef(), &firstRangeEnd);
@ -3476,12 +3493,11 @@ EditActionResult HTMLEditor::HandleDeleteNonCollapsedSelection(
// except table elements.
bool join = true;
AutoSelectionRangeArray arrayOfRanges(SelectionRefPtr());
for (auto& range : arrayOfRanges.mRanges) {
for (auto& range : rangesToDelete.Ranges()) {
// Build a list of direct child nodes in the range
AutoTArray<OwningNonNull<nsIContent>, 10> arrayOfTopChildren;
DOMSubtreeIterator iter;
nsresult rv = iter.Init(*range);
nsresult rv = iter.Init(range);
if (NS_FAILED(rv)) {
NS_WARNING("DOMSubtreeIterator::Init() failed");
return result.SetResult(rv);
@ -8053,8 +8069,7 @@ size_t HTMLEditor::CollectChildren(
return numberOfFoundChildren;
}
already_AddRefed<StaticRange>
HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes(
already_AddRefed<nsRange> HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes(
const AbstractRange& aAbstractRange) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(!aAbstractRange.Collapsed());
@ -8160,10 +8175,10 @@ HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes(
HTMLEditUtils::GetInclusiveAncestorBlockElement(
*atFirstInvisibleBRElement.ContainerAsContent())) {
// Create a range that represents extended selection.
RefPtr<StaticRange> staticRange =
StaticRange::Create(atStart.ToRawRangeBoundary(),
atEnd.ToRawRangeBoundary(), IgnoreErrors());
if (!staticRange) {
RefPtr<nsRange> range =
nsRange::Create(atStart.ToRawRangeBoundary(),
atEnd.ToRawRangeBoundary(), IgnoreErrors());
if (!range) {
NS_WARNING("StaticRange::Create() failed");
return nullptr;
}
@ -8171,12 +8186,12 @@ HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes(
// Check if block is entirely inside range
bool nodeBefore = false, nodeAfter = false;
DebugOnly<nsresult> rvIgnored = RangeUtils::CompareNodeToRange(
brElementParent, staticRange, &nodeBefore, &nodeAfter);
brElementParent, range, &nodeBefore, &nodeAfter);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"RangeUtils::CompareNodeToRange() failed");
// If block is contained in the range, include the invisible `<br>`.
if (!nodeBefore && !nodeAfter) {
return staticRange.forget();
return range.forget();
}
// Otherwise, the new range should end at the invisible `<br>`.
atEnd = atFirstInvisibleBRElement;
@ -8186,8 +8201,8 @@ HTMLEditor::GetRangeExtendedToIncludeInvisibleNodes(
// XXX This is unnecessary creation cost for us since we just want to return
// the start point and the end point.
return StaticRange::Create(atStart.ToRawRangeBoundary(),
atEnd.ToRawRangeBoundary(), IgnoreErrors());
return nsRange::Create(atStart.ToRawRangeBoundary(),
atEnd.ToRawRangeBoundary(), IgnoreErrors());
}
nsresult HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() {

View File

@ -2457,7 +2457,7 @@ class HTMLEditor final : public TextEditor,
* and must be positioned.
* @return Extended range.
*/
already_AddRefed<dom::StaticRange> GetRangeExtendedToIncludeInvisibleNodes(
already_AddRefed<nsRange> GetRangeExtendedToIncludeInvisibleNodes(
const dom::AbstractRange& aAbstractRange);
/**