Bug 1774704 - part 1: Make `HTMLEditor::EnsureCaretInBlockElement` only computes new caret point in the given element r=m_kato

Differential Revision: https://phabricator.services.mozilla.com/D152963
This commit is contained in:
Masayuki Nakano 2022-08-04 00:21:53 +00:00
Родитель 7f6f9cf71c
Коммит 14dd35ea1d
4 изменённых файлов: 114 добавлений и 89 удалений

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

@ -561,15 +561,30 @@ nsresult HTMLEditor::OnEndHandlingTopLevelEditSubActionInternal() {
// If we created a new block, make sure caret is in it.
if (TopLevelEditSubActionDataRef().mNewBlockElement &&
SelectionRef().IsCollapsed()) {
nsresult rv = EnsureCaretInBlockElement(
MOZ_KnownLive(*TopLevelEditSubActionDataRef().mNewBlockElement));
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
SelectionRef().IsCollapsed() && SelectionRef().RangeCount()) {
const auto firstRangeStartPoint =
GetFirstSelectionStartPoint<EditorRawDOMPoint>();
if (MOZ_LIKELY(firstRangeStartPoint.IsSet())) {
const Result<EditorRawDOMPoint, nsresult> pointToPutCaretOrError =
HTMLEditUtils::ComputePointToPutCaretInElementIfOutside<
EditorRawDOMPoint>(
*TopLevelEditSubActionDataRef().mNewBlockElement,
firstRangeStartPoint);
if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
NS_WARNING(
"HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() "
"failed, but ignored");
} else if (pointToPutCaretOrError.inspect().IsSet()) {
nsresult rv = CollapseSelectionTo(pointToPutCaretOrError.inspect());
if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING("EditorBase::CollapseSelectionTo() failed");
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed, but ignored");
}
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::EnsureSelectionInBlockElement() failed, but ignored");
}
// Adjust selection for insert text, html paste, and delete actions if
@ -8805,79 +8820,6 @@ nsresult HTMLEditor::InsertBRElementToEmptyListItemsAndTableCellsInRange(
return NS_OK;
}
nsresult HTMLEditor::EnsureCaretInBlockElement(Element& aElement) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(SelectionRef().IsCollapsed());
const auto atCaret = GetFirstSelectionStartPoint<EditorRawDOMPoint>();
if (NS_WARN_IF(!atCaret.IsSet())) {
return NS_ERROR_FAILURE;
}
// Use ranges and RangeUtils::CompareNodeToRange() to compare selection
// start to new block.
RefPtr<StaticRange> staticRange =
StaticRange::Create(atCaret.ToRawRangeBoundary(),
atCaret.ToRawRangeBoundary(), IgnoreErrors());
if (!staticRange) {
NS_WARNING("StaticRange::Create() failed");
return NS_ERROR_FAILURE;
}
bool nodeBefore, nodeAfter;
nsresult rv = RangeUtils::CompareNodeToRange(&aElement, staticRange,
&nodeBefore, &nodeAfter);
if (NS_FAILED(rv)) {
NS_WARNING("RangeUtils::CompareNodeToRange() failed");
return rv;
}
if (nodeBefore && nodeAfter) {
return NS_OK; // selection is inside block
}
if (nodeBefore) {
// selection is after block. put at end of block.
nsIContent* lastEditableContent = HTMLEditUtils::GetLastChild(
aElement, {WalkTreeOption::IgnoreNonEditableNode});
if (!lastEditableContent) {
lastEditableContent = &aElement;
}
EditorRawDOMPoint endPoint;
if (lastEditableContent->IsText() ||
HTMLEditUtils::IsContainerNode(*lastEditableContent)) {
endPoint.SetToEndOf(lastEditableContent);
} else {
endPoint.SetAfter(lastEditableContent);
if (NS_WARN_IF(!endPoint.IsSet())) {
return NS_ERROR_FAILURE;
}
}
nsresult rv = CollapseSelectionTo(endPoint);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed");
return rv;
}
// selection is before block. put at start of block.
nsIContent* firstEditableContent = HTMLEditUtils::GetFirstChild(
aElement, {WalkTreeOption::IgnoreNonEditableNode});
if (!firstEditableContent) {
firstEditableContent = &aElement;
}
EditorRawDOMPoint atStartOfBlock;
if (firstEditableContent->IsText() ||
HTMLEditUtils::IsContainerNode(*firstEditableContent)) {
atStartOfBlock.Set(firstEditableContent);
} else {
atStartOfBlock.Set(firstEditableContent, 0);
}
rv = CollapseSelectionTo(atStartOfBlock);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"EditorBase::CollapseSelectionTo() failed");
return rv;
}
void HTMLEditor::SetSelectionInterlinePosition() {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(SelectionRef().IsCollapsed());

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

@ -15,6 +15,7 @@
#include "mozilla/ArrayUtils.h" // for ArrayLength
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
#include "mozilla/RangeUtils.h" // for RangeUtils
#include "mozilla/dom/Element.h" // for Element, nsINode
#include "mozilla/dom/HTMLAnchorElement.h"
#include "mozilla/dom/HTMLInputElement.h"
@ -96,6 +97,19 @@ template EditorRawDOMPoint HTMLEditUtils::GetBetterInsertionPointFor(
const nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert,
const Element& aEditingHost);
template Result<EditorDOMPoint, nsresult>
HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
const Element& aElement, const EditorDOMPoint& aCurrentPoint);
template Result<EditorRawDOMPoint, nsresult>
HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
const Element& aElement, const EditorDOMPoint& aCurrentPoint);
template Result<EditorDOMPoint, nsresult>
HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
const Element& aElement, const EditorRawDOMPoint& aCurrentPoint);
template Result<EditorRawDOMPoint, nsresult>
HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
const Element& aElement, const EditorRawDOMPoint& aCurrentPoint);
bool HTMLEditUtils::CanContentsBeJoined(const nsIContent& aLeftContent,
const nsIContent& aRightContent,
StyleDifference aStyleDifference) {
@ -1794,6 +1808,69 @@ EditorDOMPointType HTMLEditUtils::GetBetterInsertionPointFor(
.template PointAfterContent<EditorDOMPointType>();
}
// static
template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
Result<EditorDOMPointType, nsresult>
HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
const Element& aElement, const EditorDOMPointTypeInput& aCurrentPoint) {
MOZ_ASSERT(aCurrentPoint.IsSet());
// FYI: This was moved from
// https://searchfox.org/mozilla-central/rev/d3c2f51d89c3ca008ff0cb5a057e77ccd973443e/editor/libeditor/HTMLEditSubActionHandler.cpp#9193
// Use ranges and RangeUtils::CompareNodeToRange() to compare selection
// start to new block.
RefPtr<StaticRange> staticRange =
StaticRange::Create(aCurrentPoint.ToRawRangeBoundary(),
aCurrentPoint.ToRawRangeBoundary(), IgnoreErrors());
if (MOZ_UNLIKELY(!staticRange)) {
NS_WARNING("StaticRange::Create() failed");
return Err(NS_ERROR_FAILURE);
}
bool nodeBefore, nodeAfter;
nsresult rv = RangeUtils::CompareNodeToRange(
const_cast<Element*>(&aElement), staticRange, &nodeBefore, &nodeAfter);
if (NS_FAILED(rv)) {
NS_WARNING("RangeUtils::CompareNodeToRange() failed");
return Err(rv);
}
if (nodeBefore && nodeAfter) {
return EditorDOMPointType(); // aCurrentPoint is in aElement
}
if (nodeBefore) {
// selection is after block. put at end of block.
const nsIContent* lastEditableContent = HTMLEditUtils::GetLastChild(
aElement, {WalkTreeOption::IgnoreNonEditableNode});
if (!lastEditableContent) {
lastEditableContent = &aElement;
}
if (lastEditableContent->IsText() ||
HTMLEditUtils::IsContainerNode(*lastEditableContent)) {
return EditorDOMPointType::AtEndOf(*lastEditableContent);
}
MOZ_ASSERT(lastEditableContent->GetParentNode());
return EditorDOMPointType::After(*lastEditableContent);
}
// selection is before block. put at start of block.
const nsIContent* firstEditableContent = HTMLEditUtils::GetFirstChild(
aElement, {WalkTreeOption::IgnoreNonEditableNode});
if (!firstEditableContent) {
firstEditableContent = &aElement;
}
if (firstEditableContent->IsText() ||
HTMLEditUtils::IsContainerNode(*firstEditableContent)) {
MOZ_ASSERT(firstEditableContent->GetParentNode());
// XXX Shouldn't this be EditorDOMPointType(firstEditableContent, 0u)?
return EditorDOMPointType(firstEditableContent);
}
// XXX And shouldn't this be EditorDOMPointType(firstEditableContent)?
return EditorDOMPointType(firstEditableContent, 0u);
}
// static
size_t HTMLEditUtils::CollectChildren(
nsINode& aNode, nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,

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

@ -20,6 +20,7 @@
#include "mozilla/EnumSet.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/Maybe.h"
#include "mozilla/Result.h"
#include "mozilla/dom/AbstractRange.h"
#include "mozilla/dom/AncestorIterator.h"
#include "mozilla/dom/Element.h"
@ -1949,6 +1950,19 @@ class HTMLEditUtils final {
const EditorDOMPointTypeInput& aPointToInsert,
const Element& aEditingHost);
/**
* ComputePointToPutCaretInElementIfOutside() returns a good point in aElement
* to put caret if aCurrentPoint is outside of aElement.
*
* @param aElement The result is a point in aElement.
* @param aCurrentPoint The current (candidate) caret point. Only if this
* is outside aElement, returns a point in aElement.
*/
template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
static Result<EditorDOMPointType, nsresult>
ComputePointToPutCaretInElementIfOutside(
const Element& aElement, const EditorDOMPointTypeInput& aCurrentPoint);
/**
* Content-based query returns true if <aProperty aAttribute=aValue> effects
* aNode. If <aProperty aAttribute=aValue> contains aNode, but

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

@ -2612,14 +2612,6 @@ class HTMLEditor final : public EditorBase,
*/
void SetSelectionInterlinePosition();
/**
* EnsureSelectionInBlockElement() may move caret into aElement or its
* parent block if caret is outside of them. Don't call this when
* `Selection` is not collapsed.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
EnsureCaretInBlockElement(dom::Element& aElement);
/**
* Called by `HTMLEditor::OnEndHandlingTopLevelEditSubAction()`. This may
* adjust Selection, remove unnecessary empty nodes, create `<br>` elements