Bug 1770877 - part 27: Make `HTMLEditor::SplitParagraph` stop touching `Selection` directly r=m_kato

Differential Revision: https://phabricator.services.mozilla.com/D149091
This commit is contained in:
Masayuki Nakano 2022-06-21 08:49:51 +00:00
Родитель 324edb90ae
Коммит c77d8d358f
4 изменённых файлов: 126 добавлений и 98 удалений

Просмотреть файл

@ -539,6 +539,18 @@ class MOZ_STACK_CLASS SplitNodeResult final {
SplitNodeResult(SplitNodeResult&&) = default;
SplitNodeResult& operator=(SplitNodeResult&&) = default;
/**
* This constructor should be used for setting specific caret point instead of
* aSplitResult's one.
*/
SplitNodeResult(SplitNodeResult&& aSplitResult,
const EditorDOMPoint& aNewCaretPoint)
: SplitNodeResult(std::move(aSplitResult)) {
MOZ_ASSERT(isOk());
mCaretPoint = aNewCaretPoint;
mHandledCaretPoint = false;
}
/**
* This constructor shouldn't be used by anybody except methods which
* use this as result when it succeeds.

Просмотреть файл

@ -7226,43 +7226,45 @@ EditActionResult HTMLEditor::HandleInsertParagraphInParagraph(
bool doesCRCreateNewP = GetReturnInParagraphCreatesNewParagraph();
bool splitAfterNewBR = false;
nsCOMPtr<nsIContent> brContent;
RefPtr<HTMLBRElement> brElement;
EditorDOMPoint pointToSplitParentDivOrP(atStartOfSelection);
EditorDOMPoint pointToInsertBR;
if (doesCRCreateNewP && atStartOfSelection.GetContainer() == &aParentDivOrP) {
// We are at the edges of the block, so, we don't need to create new <br>.
brContent = nullptr;
brElement = nullptr;
} else if (atStartOfSelection.IsInTextNode()) {
// at beginning of text node?
if (atStartOfSelection.IsStartOfContainer()) {
// is there a BR prior to it?
brContent = atStartOfSelection.IsInContentNode()
? HTMLEditUtils::GetPreviousSibling(
*atStartOfSelection.ContainerAsContent(),
{WalkTreeOption::IgnoreNonEditableNode})
: nullptr;
if (!brContent || !HTMLEditUtils::IsVisibleBRElement(*brContent) ||
EditorUtils::IsPaddingBRElementForEmptyLastLine(*brContent)) {
brElement = HTMLBRElement::FromNodeOrNull(
atStartOfSelection.IsInContentNode()
? HTMLEditUtils::GetPreviousSibling(
*atStartOfSelection.ContainerAsContent(),
{WalkTreeOption::IgnoreNonEditableNode})
: nullptr);
if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
pointToInsertBR.Set(atStartOfSelection.GetContainer());
brContent = nullptr;
brElement = nullptr;
}
} else if (atStartOfSelection.IsEndOfContainer()) {
// we're at the end of text node...
// is there a BR after to it?
brContent = atStartOfSelection.IsInContentNode()
? HTMLEditUtils::GetNextSibling(
*atStartOfSelection.ContainerAsContent(),
{WalkTreeOption::IgnoreNonEditableNode})
: nullptr;
if (!brContent || !HTMLEditUtils::IsVisibleBRElement(*brContent) ||
EditorUtils::IsPaddingBRElementForEmptyLastLine(*brContent)) {
brElement = HTMLBRElement::FromNodeOrNull(
atStartOfSelection.IsInContentNode()
? HTMLEditUtils::GetNextSibling(
*atStartOfSelection.ContainerAsContent(),
{WalkTreeOption::IgnoreNonEditableNode})
: nullptr);
if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
pointToInsertBR.SetAfter(atStartOfSelection.GetContainer());
NS_WARNING_ASSERTION(
pointToInsertBR.IsSet(),
"Failed to set to after the container of selection start");
brContent = nullptr;
brElement = nullptr;
}
} else {
if (doesCRCreateNewP) {
@ -7312,28 +7314,27 @@ EditActionResult HTMLEditor::HandleInsertParagraphInParagraph(
// not in a text node.
// is there a BR prior to it?
Element* editingHost = ComputeEditingHost();
nsIContent* nearContent =
brElement = HTMLBRElement::FromNodeOrNull(
editingHost ? HTMLEditUtils::GetPreviousContent(
atStartOfSelection,
{WalkTreeOption::IgnoreNonEditableNode}, editingHost)
: nullptr;
if (!nearContent || !HTMLEditUtils::IsVisibleBRElement(*nearContent) ||
EditorUtils::IsPaddingBRElementForEmptyLastLine(*nearContent)) {
: nullptr);
if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
// is there a BR after it?
nearContent = editingHost ? HTMLEditUtils::GetNextContent(
atStartOfSelection,
{WalkTreeOption::IgnoreNonEditableNode},
editingHost)
: nullptr;
if (!nearContent || !HTMLEditUtils::IsVisibleBRElement(*nearContent) ||
EditorUtils::IsPaddingBRElementForEmptyLastLine(*nearContent)) {
brElement = HTMLBRElement::FromNodeOrNull(
editingHost
? HTMLEditUtils::GetNextContent(
atStartOfSelection, {WalkTreeOption::IgnoreNonEditableNode},
editingHost)
: nullptr);
if (!brElement || HTMLEditUtils::IsInvisibleBRElement(*brElement) ||
EditorUtils::IsPaddingBRElementForEmptyLastLine(*brElement)) {
pointToInsertBR = atStartOfSelection;
splitAfterNewBR = true;
brElement = nullptr;
}
}
if (!pointToInsertBR.IsSet() && nearContent->IsHTMLElement(nsGkAtoms::br)) {
brContent = nearContent;
}
}
if (pointToInsertBR.IsSet()) {
// if CR does not create a new P, default to BR creation
@ -7365,19 +7366,37 @@ EditActionResult HTMLEditor::HandleInsertParagraphInParagraph(
NS_WARNING_ASSERTION(pointToSplitParentDivOrP.IsSet(),
"Failed to set after the new <br>");
}
brContent = insertBRElementResult.UnwrapNewNode();
brElement =
HTMLBRElement::FromNodeOrNull(insertBRElementResult.GetNewNode());
}
EditActionResult result(
SplitParagraph(aParentDivOrP, pointToSplitParentDivOrP, brContent));
NS_WARNING_ASSERTION(result.Succeeded(),
"HTMLEditor::SplitParagraph() failed");
result.MarkAsHandled();
return result;
const SplitNodeResult splitParagraphResult = SplitParagraphWithTransaction(
aParentDivOrP, pointToSplitParentDivOrP, brElement);
if (splitParagraphResult.isErr()) {
NS_WARNING("HTMLEditor::SplitParagraphWithTransaction() failed");
return EditActionResult(splitParagraphResult.inspectErr());
}
if (MOZ_UNLIKELY(!splitParagraphResult.DidSplit())) {
NS_WARNING(
"HTMLEditor::SplitParagraphWithTransaction() didn't split the "
"paragraph");
splitParagraphResult.IgnoreCaretPointSuggestion();
return EditActionResult(NS_ERROR_FAILURE);
}
nsresult rv = splitParagraphResult.SuggestCaretPointTo(
*this, {SuggestCaret::AndIgnoreTrivialError});
if (NS_FAILED(rv)) {
NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
return EditActionResult(rv);
}
NS_WARNING_ASSERTION(
rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR,
"SplitNodeResult::SuggestCaretPointTo() failed, but ignored");
return EditActionHandled();
}
nsresult HTMLEditor::SplitParagraph(Element& aParentDivOrP,
const EditorDOMPoint& aStartOfRightNode,
nsIContent* aNextBRNode) {
SplitNodeResult HTMLEditor::SplitParagraphWithTransaction(
Element& aParentDivOrP, const EditorDOMPoint& aStartOfRightNode,
HTMLBRElement* aMayBecomeVisibleBRElement) {
MOZ_ASSERT(IsEditActionDataAvailable());
Result<EditorDOMPoint, nsresult> preparationResult =
@ -7386,31 +7405,27 @@ nsresult HTMLEditor::SplitParagraph(Element& aParentDivOrP,
if (MOZ_UNLIKELY(preparationResult.isErr())) {
NS_WARNING(
"WhiteSpaceVisibilityKeeper::PrepareToSplitBlockElement() failed");
return preparationResult.unwrapErr();
return SplitNodeResult(preparationResult.unwrapErr());
}
EditorDOMPoint pointToSplit = preparationResult.unwrap();
MOZ_ASSERT(pointToSplit.IsInContentNode());
// Split the paragraph.
const SplitNodeResult splitDivOrPResult = SplitNodeDeepWithTransaction(
SplitNodeResult splitDivOrPResult = SplitNodeDeepWithTransaction(
aParentDivOrP, pointToSplit, SplitAtEdges::eAllowToCreateEmptyContainer);
if (splitDivOrPResult.isErr()) {
NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
return splitDivOrPResult.unwrapErr();
}
nsresult rv = splitDivOrPResult.SuggestCaretPointTo(
*this, {SuggestCaret::OnlyIfHasSuggestion,
SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
if (NS_FAILED(rv)) {
NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed");
return rv;
return splitDivOrPResult;
}
if (MOZ_UNLIKELY(!splitDivOrPResult.DidSplit())) {
NS_WARNING(
"HTMLEditor::SplitNodeDeepWithTransaction() didn't split any nodes");
return NS_ERROR_FAILURE;
return splitDivOrPResult;
}
// We'll compute caret suggestion later. So the simple result is not needed.
splitDivOrPResult.IgnoreCaretPointSuggestion();
Element* leftDivOrParagraphElement =
Element::FromNode(splitDivOrPResult.GetPreviousContent());
MOZ_ASSERT(leftDivOrParagraphElement,
@ -7424,26 +7439,27 @@ nsresult HTMLEditor::SplitParagraph(Element& aParentDivOrP,
// Get rid of the break, if it is visible (otherwise it may be needed to
// prevent an empty p).
if (aNextBRNode && HTMLEditUtils::IsVisibleBRElement(*aNextBRNode)) {
nsresult rv = DeleteNodeWithTransaction(*aNextBRNode);
if (aMayBecomeVisibleBRElement &&
HTMLEditUtils::IsVisibleBRElement(*aMayBecomeVisibleBRElement)) {
nsresult rv = DeleteNodeWithTransaction(*aMayBecomeVisibleBRElement);
if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
return rv;
return SplitNodeResult(rv);
}
}
// Remove ID attribute on the paragraph from the right node.
// MOZ_KnownLive(rightDivOrParagraphElement) because it's grabbed by
// splitDivOrPResult.
rv = RemoveAttributeWithTransaction(
nsresult rv = RemoveAttributeWithTransaction(
MOZ_KnownLive(*rightDivOrParagraphElement), *nsGkAtoms::id);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
return SplitNodeResult(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_FAILED(rv)) {
NS_WARNING(
"EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::id) failed");
return rv;
return SplitNodeResult(rv);
}
// We need to ensure to both paragraphs visible even if they are empty.
@ -7470,9 +7486,8 @@ nsresult HTMLEditor::SplitParagraph(Element& aParentDivOrP,
"HTMLEditor::InsertBRElement(WithTransaction::Yes) failed");
return insertBRElementResult.unwrapErr();
}
// After this is called twice, selection will be updated by
// SplitParagraph itself. Therefore, we don't need to update selection
// here.
// After this is called twice, we'll compute new caret position.
// Therefore, we don't need to update selection here.
insertBRElementResult.IgnoreCaretPointSuggestion();
return NS_OK;
};
@ -7484,7 +7499,7 @@ nsresult HTMLEditor::SplitParagraph(Element& aParentDivOrP,
if (NS_FAILED(rv)) {
NS_WARNING(
"InsertBRElementIfEmptyBlockElement(leftDivOrParagraphElement) failed");
return rv;
return SplitNodeResult(rv);
}
// MOZ_KnownLive(rightDivOrParagraphElement) because it's grabbed by
// splitDivOrResult.
@ -7494,35 +7509,22 @@ nsresult HTMLEditor::SplitParagraph(Element& aParentDivOrP,
NS_WARNING(
"InsertBRElementIfEmptyBlockElement(rightDivOrParagraphElement) "
"failed");
return rv;
return SplitNodeResult(rv);
}
// selection to beginning of right hand para;
// look inside any containers that are up front.
nsCOMPtr<nsIContent> child = HTMLEditUtils::GetFirstLeafContent(
nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
*rightDivOrParagraphElement, {LeafNodeType::LeafNodeOrChildBlock});
if (child && (child->IsText() || HTMLEditUtils::IsContainerNode(*child))) {
nsresult rv = CollapseSelectionToStartOf(*child);
if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING(
"EditorBase::CollapseSelectionTo() caused destroying the editor");
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionToStartOf() failed, but ignored");
} else {
nsresult rv = CollapseSelectionTo(EditorRawDOMPoint(child));
if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING(
"EditorBase::CollapseSelectionTo() caused destroying the editor");
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed, but ignored");
if (MOZ_UNLIKELY(!child)) {
return SplitNodeResult(std::move(splitDivOrPResult),
EditorDOMPoint(rightDivOrParagraphElement, 0u));
}
return NS_OK;
return child->IsText() || HTMLEditUtils::IsContainerNode(*child)
? SplitNodeResult(std::move(splitDivOrPResult),
EditorDOMPoint(child, 0u))
: SplitNodeResult(std::move(splitDivOrPResult),
EditorDOMPoint(child));
}
Result<EditorDOMPoint, nsresult>

Просмотреть файл

@ -23,6 +23,7 @@
#include "mozilla/dom/AbstractRange.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Text.h"
#include "nsContentUtils.h"
@ -332,17 +333,27 @@ class HTMLEditUtils final {
* last line in a block element visible, or an invisible <br> element.
*/
static bool IsVisibleBRElement(const nsIContent& aContent) {
if (!aContent.IsHTMLElement(nsGkAtoms::br)) {
return false;
if (const dom::HTMLBRElement* brElement =
dom::HTMLBRElement::FromNode(&aContent)) {
return IsVisibleBRElement(*brElement);
}
return false;
}
static bool IsVisibleBRElement(const dom::HTMLBRElement& aBRElement) {
// If followed by a block boundary without visible content, it's invisible
// <br> element.
return !HTMLEditUtils::GetElementOfImmediateBlockBoundary(
aContent, WalkTreeDirection::Forward);
aBRElement, WalkTreeDirection::Forward);
}
static bool IsInvisibleBRElement(const nsIContent& aContent) {
return aContent.IsHTMLElement(nsGkAtoms::br) &&
!HTMLEditUtils::IsVisibleBRElement(aContent);
if (const dom::HTMLBRElement* brElement =
dom::HTMLBRElement::FromNode(&aContent)) {
return IsInvisibleBRElement(*brElement);
}
return false;
}
static bool IsInvisibleBRElement(const dom::HTMLBRElement& aBRElement) {
return !HTMLEditUtils::IsVisibleBRElement(aBRElement);
}
/**

Просмотреть файл

@ -1473,7 +1473,7 @@ class HTMLEditor final : public EditorBase,
MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
/**
* SplitParagraph() splits the parent block, aParentDivOrP, at
* SplitParagraphWithTransaction() splits the parent block, aParentDivOrP, at
* aStartOfRightNode.
*
* @param aParentDivOrP The parent block to be split. This must be <p>
@ -1481,13 +1481,16 @@ class HTMLEditor final : public EditorBase,
* @param aStartOfRightNode The point to be start of right node after
* split. This must be descendant of
* aParentDivOrP.
* @param aNextBRNode Next <br> node if there is. Otherwise, nullptr.
* If this is not nullptr, the <br> node may be
* removed.
* @param aMayBecomeVisibleBRElement
* Next <br> element of the split point if there
* is. Otherwise, nullptr. If this is not nullptr,
* the <br> element may be removed if it becomes
* visible.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
SplitParagraph(Element& aParentDivOrP,
const EditorDOMPoint& aStartOfRightNode, nsIContent* aBRNode);
[[nodiscard]] MOZ_CAN_RUN_SCRIPT SplitNodeResult
SplitParagraphWithTransaction(Element& aParentDivOrP,
const EditorDOMPoint& aStartOfRightNode,
dom::HTMLBRElement* aMayBecomeVisibleBRElement);
/**
* HandleInsertParagraphInParagraph() does the right thing for Enter key