Bug 1757492 - part 1: Clean up HTMLEditor::DoJoinNodes r=m_kato

This patch just cleans up the method code for making the changes for bug 1735608
simpler.  This does not change any behavior.

Differential Revision: https://phabricator.services.mozilla.com/D140010
This commit is contained in:
Masayuki Nakano 2022-03-02 11:55:05 +00:00
parent 3339cabe9d
commit 5bbb9ea55a
2 changed files with 88 additions and 91 deletions

View File

@ -4783,13 +4783,10 @@ void HTMLEditor::DidJoinNodesTransaction(
}
nsresult HTMLEditor::DoJoinNodes(nsIContent& aContentToKeep,
nsIContent& aContentToJoin) {
nsIContent& aContentToRemove) {
MOZ_ASSERT(IsEditActionDataAvailable());
uint32_t firstNodeLength = aContentToJoin.Length();
EditorRawDOMPoint atNodeToJoin(&aContentToJoin);
EditorRawDOMPoint atNodeToKeep(&aContentToKeep);
const uint32_t removingNodeLength = aContentToRemove.Length();
// Remember all selection points.
// XXX Do we need to restore all types of selections by ourselves? Normal
@ -4798,58 +4795,62 @@ nsresult HTMLEditor::DoJoinNodes(nsIContent& aContentToKeep,
// selections should be recreated with newer text. URL selections
// shouldn't be there because of used only by the URL bar.
AutoTArray<SavedRange, 10> savedRanges;
for (SelectionType selectionType : kPresentSelectionTypes) {
SavedRange range;
range.mSelection = GetSelection(selectionType);
if (selectionType == SelectionType::eNormal) {
if (NS_WARN_IF(!range.mSelection)) {
return NS_ERROR_FAILURE;
}
} else if (!range.mSelection) {
// For non-normal selections, skip over the non-existing ones.
continue;
}
const uint32_t rangeCount = range.mSelection->RangeCount();
for (const uint32_t j : IntegerRange(rangeCount)) {
MOZ_ASSERT(range.mSelection->RangeCount() == rangeCount);
const RefPtr<nsRange> r = range.mSelection->GetRangeAt(j);
MOZ_ASSERT(r);
MOZ_ASSERT(r->IsPositioned());
range.mStartContainer = r->GetStartContainer();
range.mStartOffset = r->StartOffset();
range.mEndContainer = r->GetEndContainer();
range.mEndOffset = r->EndOffset();
// If selection endpoint is between the nodes, remember it as being
// in the one that is going away instead. This simplifies later selection
// adjustment logic at end of this method.
if (range.mStartContainer) {
if (range.mStartContainer == atNodeToKeep.GetContainer() &&
atNodeToJoin.Offset() < range.mStartOffset &&
range.mStartOffset <= atNodeToKeep.Offset()) {
range.mStartContainer = &aContentToJoin;
range.mStartOffset = firstNodeLength;
}
if (range.mEndContainer == atNodeToKeep.GetContainer() &&
atNodeToJoin.Offset() < range.mEndOffset &&
range.mEndOffset <= atNodeToKeep.Offset()) {
range.mEndContainer = &aContentToJoin;
range.mEndOffset = firstNodeLength;
{
EditorRawDOMPoint atRemovingNode(&aContentToRemove);
EditorRawDOMPoint atNodeToKeep(&aContentToKeep);
for (SelectionType selectionType : kPresentSelectionTypes) {
SavedRange savingRange;
savingRange.mSelection = GetSelection(selectionType);
if (selectionType == SelectionType::eNormal) {
if (NS_WARN_IF(!savingRange.mSelection)) {
return NS_ERROR_FAILURE;
}
} else if (!savingRange.mSelection) {
// For non-normal selections, skip over the non-existing ones.
continue;
}
savedRanges.AppendElement(range);
const uint32_t rangeCount = savingRange.mSelection->RangeCount();
for (const uint32_t j : IntegerRange(rangeCount)) {
MOZ_ASSERT(savingRange.mSelection->RangeCount() == rangeCount);
const RefPtr<nsRange> r = savingRange.mSelection->GetRangeAt(j);
MOZ_ASSERT(r);
MOZ_ASSERT(r->IsPositioned());
savingRange.mStartContainer = r->GetStartContainer();
savingRange.mStartOffset = r->StartOffset();
savingRange.mEndContainer = r->GetEndContainer();
savingRange.mEndOffset = r->EndOffset();
// If selection endpoint is between the nodes, remember it as being
// in the one that is going away instead. This simplifies later
// selection adjustment logic at end of this method.
if (savingRange.mStartContainer) {
if (savingRange.mStartContainer == atNodeToKeep.GetContainer() &&
atRemovingNode.Offset() < savingRange.mStartOffset &&
savingRange.mStartOffset <= atNodeToKeep.Offset()) {
savingRange.mStartContainer = &aContentToRemove;
savingRange.mStartOffset = removingNodeLength;
}
if (savingRange.mEndContainer == atNodeToKeep.GetContainer() &&
atRemovingNode.Offset() < savingRange.mEndOffset &&
savingRange.mEndOffset <= atNodeToKeep.Offset()) {
savingRange.mEndContainer = &aContentToRemove;
savingRange.mEndOffset = removingNodeLength;
}
}
savedRanges.AppendElement(savingRange);
}
}
}
// OK, ready to do join now.
// If it's a text node, just shuffle around some text.
if (aContentToKeep.IsText() && aContentToJoin.IsText()) {
if (aContentToKeep.IsText() && aContentToRemove.IsText()) {
nsAutoString rightText;
nsAutoString leftText;
aContentToKeep.AsText()->GetData(rightText);
aContentToJoin.AsText()->GetData(leftText);
aContentToRemove.AsText()->GetData(leftText);
leftText += rightText;
IgnoredErrorResult ignoredError;
DoSetText(MOZ_KnownLive(*aContentToKeep.AsText()), leftText, ignoredError);
@ -4860,101 +4861,96 @@ nsresult HTMLEditor::DoJoinNodes(nsIContent& aContentToKeep,
"EditorBase::DoSetText() failed, but ignored");
} else {
// Otherwise it's an interior node, so shuffle around the children.
nsCOMPtr<nsINodeList> childNodes = aContentToJoin.ChildNodes();
nsCOMPtr<nsINodeList> childNodes = aContentToRemove.ChildNodes();
MOZ_ASSERT(childNodes);
// Remember the first child in aContentToKeep, we'll insert all the children
// of aContentToJoin in front of it GetFirstChild returns nullptr firstNode
// if aContentToKeep has no children, that's OK.
nsCOMPtr<nsIContent> firstNode = aContentToKeep.GetFirstChild();
// of aContentToRemove in front of it GetFirstChild returns nullptr
// firstChild if aContentToKeep has no children, that's OK.
nsCOMPtr<nsIContent> firstChild = aContentToKeep.GetFirstChild();
// Have to go through the list backwards to keep deletes from interfering
// with iteration.
for (uint32_t i = childNodes->Length(); i; --i) {
nsCOMPtr<nsIContent> childNode = childNodes->Item(i - 1);
if (childNode) {
// prepend children of aContentToJoin
ErrorResult error;
aContentToKeep.InsertBefore(*childNode, firstNode, error);
// prepend children of aContentToRemove
IgnoredErrorResult error;
aContentToKeep.InsertBefore(*childNode, firstChild, error);
if (NS_WARN_IF(Destroyed())) {
error.SuppressException();
return NS_ERROR_EDITOR_DESTROYED;
}
if (error.Failed()) {
NS_WARNING("nsINode::InsertBefore() failed");
return error.StealNSResult();
}
firstNode = std::move(childNode);
firstChild = std::move(childNode);
}
}
}
// Delete the extra node.
aContentToJoin.Remove();
aContentToRemove.Remove();
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
bool allowedTransactionsToChangeSelection =
const bool allowedTransactionsToChangeSelection =
AllowsTransactionsToChangeSelection();
// And adjust the selection if needed.
RefPtr<Selection> previousSelection;
for (size_t i = 0; i < savedRanges.Length(); ++i) {
// And adjust the selection if needed.
SavedRange& range = savedRanges[i];
for (SavedRange& savedRange : savedRanges) {
// If we have not seen the selection yet, clear all of its ranges.
if (range.mSelection != previousSelection) {
ErrorResult error;
MOZ_KnownLive(range.mSelection)->RemoveAllRanges(error);
if (savedRange.mSelection != previousSelection) {
IgnoredErrorResult error;
MOZ_KnownLive(savedRange.mSelection)->RemoveAllRanges(error);
if (NS_WARN_IF(Destroyed())) {
error.SuppressException();
return NS_ERROR_EDITOR_DESTROYED;
}
if (error.Failed()) {
NS_WARNING("Selection::RemoveAllRanges() failed");
return error.StealNSResult();
}
previousSelection = range.mSelection;
previousSelection = savedRange.mSelection;
}
if (allowedTransactionsToChangeSelection &&
range.mSelection->Type() == SelectionType::eNormal) {
savedRange.mSelection->Type() == SelectionType::eNormal) {
// If the editor should adjust the selection, don't bother restoring
// the ranges for the normal selection here.
continue;
}
// Check to see if we joined nodes where selection starts.
if (range.mStartContainer == &aContentToJoin) {
range.mStartContainer = &aContentToKeep;
} else if (range.mStartContainer == &aContentToKeep) {
range.mStartOffset += firstNodeLength;
if (savedRange.mStartContainer == &aContentToRemove) {
savedRange.mStartContainer = &aContentToKeep;
} else if (savedRange.mStartContainer == &aContentToKeep) {
savedRange.mStartOffset += removingNodeLength;
}
// Check to see if we joined nodes where selection ends.
if (range.mEndContainer == &aContentToJoin) {
range.mEndContainer = &aContentToKeep;
} else if (range.mEndContainer == &aContentToKeep) {
range.mEndOffset += firstNodeLength;
if (savedRange.mEndContainer == &aContentToRemove) {
savedRange.mEndContainer = &aContentToKeep;
} else if (savedRange.mEndContainer == &aContentToKeep) {
savedRange.mEndOffset += removingNodeLength;
}
RefPtr<nsRange> newRange =
nsRange::Create(range.mStartContainer, range.mStartOffset,
range.mEndContainer, range.mEndOffset, IgnoreErrors());
const RefPtr<nsRange> newRange = nsRange::Create(
savedRange.mStartContainer, savedRange.mStartOffset,
savedRange.mEndContainer, savedRange.mEndOffset, IgnoreErrors());
if (!newRange) {
NS_WARNING("nsRange::Create() failed");
return NS_ERROR_FAILURE;
}
ErrorResult error;
IgnoredErrorResult error;
// The `MOZ_KnownLive` annotation is only necessary because of a bug
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253) in the
// static analyzer.
MOZ_KnownLive(range.mSelection)
MOZ_KnownLive(savedRange.mSelection)
->AddRangeAndSelectFramesAndNotifyListeners(*newRange, error);
if (NS_WARN_IF(Destroyed())) {
error.SuppressException();
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_WARN_IF(error.Failed())) {
@ -4965,7 +4961,7 @@ nsresult HTMLEditor::DoJoinNodes(nsIContent& aContentToKeep,
if (allowedTransactionsToChangeSelection) {
// Editor wants us to set selection at join point.
DebugOnly<nsresult> rvIgnored =
SelectionRef().CollapseInLimiter(&aContentToKeep, firstNodeLength);
SelectionRef().CollapseInLimiter(&aContentToKeep, removingNodeLength);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}

View File

@ -978,18 +978,19 @@ class HTMLEditor final : public EditorBase,
const EditorDOMPoint& aStartOfRightNode, nsIContent& aNewLeftNode);
/**
* DoJoinNodes() merges contents in aContentToJoin to aContentToKeep and
* remove aContentToJoin from the DOM tree. aContentToJoin and aContentToKeep
* must have same parent, aParent. Additionally, if one of aContentToJoin or
* aContentToKeep is a text node, the other must be a text node.
* DoJoinNodes() merges contents in aContentToRemove to aContentToKeep and
* remove aContentToRemove from the DOM tree. aContentToRemove and
* aContentToKeep must have same parent. Additionally, if one of
* aContentToRemove or aContentToKeep is a text node, the other must be a
* text node.
*
* @param aContentToKeep The node that will remain after the join.
* @param aContentToJoin The node that will be joined with aContentToKeep.
* There is no requirement that the two nodes be of the
* same type.
* @param aContentToKeep The node that will remain after the join.
* @param aContentToRemove The node that will be joined with aContentToKeep.
* There is no requirement that the two nodes be of
* the same type.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
DoJoinNodes(nsIContent& aContentToKeep, nsIContent& aContentToJoin);
DoJoinNodes(nsIContent& aContentToKeep, nsIContent& aContentToRemove);
/**
* Routines for managing the preservation of selection across