mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-19 16:25:38 +00:00
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:
parent
a0375c98e1
commit
93ff6864bb
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user