Bug 1627175 - part 67: Move `HTMLEditor::FindNearEditableContent()` to `HTMLEditUtils` r=m_kato

Differential Revision: https://phabricator.services.mozilla.com/D115179
This commit is contained in:
Masayuki Nakano 2021-05-18 08:28:32 +00:00
Родитель 7d05e7cab0
Коммит af77fca66e
3 изменённых файлов: 166 добавлений и 175 удалений

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

@ -76,6 +76,7 @@ using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using LeafNodeType = HTMLEditUtils::LeafNodeType;
using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
using StyleDifference = HTMLEditUtils::StyleDifference;
using WalkTreeDirection = HTMLEditUtils::WalkTreeDirection;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
/********************************************************
@ -148,10 +149,6 @@ template EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
const RangeBoundary& aPoint) const;
template EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
const RawRangeBoundary& aPoint) const;
template nsIContent* HTMLEditor::FindNearEditableContent(
const EditorDOMPoint& aPoint, nsIEditor::EDirection aDirection);
template nsIContent* HTMLEditor::FindNearEditableContent(
const EditorRawDOMPoint& aPoint, nsIEditor::EDirection aDirection);
nsresult HTMLEditor::InitEditorContentAndSelection() {
MOZ_ASSERT(IsEditActionDataAvailable());
@ -8093,109 +8090,116 @@ nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement(
// 1) prior node is in same block where selection is AND
// 2) prior node is a br AND
// 3) that br is not visible
if (RefPtr<Element> editingHost = GetActiveEditingHost()) {
if (nsCOMPtr<nsIContent> previousEditableContent =
HTMLEditUtils::GetPreviousContent(
point, {WalkTreeOption::IgnoreNonEditableNode}, editingHost)) {
RefPtr<Element> blockElementAtCaret =
HTMLEditUtils::GetInclusiveAncestorBlockElement(
*point.ContainerAsContent());
RefPtr<Element> blockElementParentAtPreviousEditableContent =
HTMLEditUtils::GetAncestorBlockElement(*previousEditableContent);
// If previous editable content of caret is in same block and a `<br>`
// element, we need to adjust interline position.
if (blockElementAtCaret &&
blockElementAtCaret == blockElementParentAtPreviousEditableContent &&
previousEditableContent &&
previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
// If it's an invisible `<br>` element, we need to insert a padding
// `<br>` element for making empty line have one-line height.
if (HTMLEditUtils::IsInvisibleBRElement(*previousEditableContent,
editingHost)) {
CreateElementResult createPaddingBRResult =
InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
if (createPaddingBRResult.Failed()) {
NS_WARNING(
"HTMLEditor::"
"InsertPaddingBRElementForEmptyLastLineWithTransaction() "
"failed");
return createPaddingBRResult.Rv();
}
point.Set(createPaddingBRResult.GetNewNode());
// Selection stays *before* padding `<br>` element for empty last
// line, sticking to it.
RefPtr<Element> editingHost = GetActiveEditingHost();
if (!editingHost) {
return NS_OK;
}
if (nsCOMPtr<nsIContent> previousEditableContent =
HTMLEditUtils::GetPreviousContent(
point, {WalkTreeOption::IgnoreNonEditableNode}, editingHost)) {
RefPtr<Element> blockElementAtCaret =
HTMLEditUtils::GetInclusiveAncestorBlockElement(
*point.ContainerAsContent());
RefPtr<Element> blockElementParentAtPreviousEditableContent =
HTMLEditUtils::GetAncestorBlockElement(*previousEditableContent);
// If previous editable content of caret is in same block and a `<br>`
// element, we need to adjust interline position.
if (blockElementAtCaret &&
blockElementAtCaret == blockElementParentAtPreviousEditableContent &&
previousEditableContent &&
previousEditableContent->IsHTMLElement(nsGkAtoms::br)) {
// If it's an invisible `<br>` element, we need to insert a padding
// `<br>` element for making empty line have one-line height.
if (HTMLEditUtils::IsInvisibleBRElement(*previousEditableContent,
editingHost)) {
CreateElementResult createPaddingBRResult =
InsertPaddingBRElementForEmptyLastLineWithTransaction(point);
if (createPaddingBRResult.Failed()) {
NS_WARNING(
"HTMLEditor::"
"InsertPaddingBRElementForEmptyLastLineWithTransaction() "
"failed");
return createPaddingBRResult.Rv();
}
point.Set(createPaddingBRResult.GetNewNode());
// Selection stays *before* padding `<br>` element for empty last
// line, sticking to it.
IgnoredErrorResult ignoredError;
SelectionRef().SetInterlinePosition(true, ignoredError);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
!ignoredError.Failed(),
"Selection::SetInterlinePosition(true) failed, but ignored");
nsresult rv = CollapseSelectionTo(point);
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::CollapseSelectionTo() failed");
return rv;
}
}
// If it's a visible `<br>` element and next editable content is a
// padding `<br>` element, we need to set interline position.
else if (nsIContent* nextEditableContentInBlock =
HTMLEditUtils::GetNextContent(
*previousEditableContent,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)) {
if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
*nextEditableContentInBlock)) {
// Make it stick to the padding `<br>` element so that it will be
// on blank line.
IgnoredErrorResult ignoredError;
SelectionRef().SetInterlinePosition(true, ignoredError);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
!ignoredError.Failed(),
"Selection::SetInterlinePosition(true) failed, but ignored");
nsresult rv = CollapseSelectionTo(point);
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::CollapseSelectionTo() failed");
return rv;
}
}
// If it's a visible `<br>` element and next editable content is a
// padding `<br>` element, we need to set interline position.
else if (nsIContent* nextEditableContentInBlock =
HTMLEditUtils::GetNextContent(
*previousEditableContent,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)) {
if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
*nextEditableContentInBlock)) {
// Make it stick to the padding `<br>` element so that it will be
// on blank line.
IgnoredErrorResult ignoredError;
SelectionRef().SetInterlinePosition(true, ignoredError);
NS_WARNING_ASSERTION(
!ignoredError.Failed(),
"Selection::SetInterlinePosition(true) failed, but ignored");
}
}
}
}
}
// If previous editable content in same block is `<br>`, text node, `<img>`
// or `<hr>`, current caret position is fine.
if (nsIContent* previousEditableContentInBlock =
HTMLEditUtils::GetPreviousContent(
point,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)) {
if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) ||
previousEditableContentInBlock->IsText() ||
HTMLEditUtils::IsImage(previousEditableContentInBlock) ||
previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) {
return NS_OK;
}
// If previous editable content in same block is `<br>`, text node, `<img>`
// or `<hr>`, current caret position is fine.
if (nsIContent* previousEditableContentInBlock =
HTMLEditUtils::GetPreviousContent(
point,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)) {
if (previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::br) ||
previousEditableContentInBlock->IsText() ||
HTMLEditUtils::IsImage(previousEditableContentInBlock) ||
previousEditableContentInBlock->IsHTMLElement(nsGkAtoms::hr)) {
return NS_OK;
}
}
// If next editable content in same block is `<br>`, text node, `<img>` or
// `<hr>`, current caret position is fine.
if (nsIContent* nextEditableContentInBlock = HTMLEditUtils::GetNextContent(
point,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)) {
if (nextEditableContentInBlock->IsText() ||
nextEditableContentInBlock->IsAnyOfHTMLElements(
nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) {
return NS_OK;
}
// If next editable content in same block is `<br>`, text node, `<img>` or
// `<hr>`, current caret position is fine.
if (nsIContent* nextEditableContentInBlock =
HTMLEditUtils::GetNextContent(point,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)) {
if (nextEditableContentInBlock->IsText() ||
nextEditableContentInBlock->IsAnyOfHTMLElements(
nsGkAtoms::br, nsGkAtoms::img, nsGkAtoms::hr)) {
return NS_OK;
}
}
// Otherwise, look for a near editable content towards edit action direction.
// If there is no editable content, keep current caret position.
nsIContent* nearEditableContent =
FindNearEditableContent(point, aDirectionAndAmount);
// XXX Why do we treat `nsIEditor::ePreviousWord` etc as forward direction?
nsIContent* nearEditableContent = HTMLEditUtils::GetAdjacentContentToPutCaret(
point,
aDirectionAndAmount == nsIEditor::ePrevious ? WalkTreeDirection::Backward
: WalkTreeDirection::Forward,
*editingHost);
if (!nearEditableContent) {
return NS_OK;
}
@ -8213,73 +8217,6 @@ nsresult HTMLEditor::AdjustCaretPositionAndEnsurePaddingBRElement(
return rv;
}
template <typename PT, typename CT>
nsIContent* HTMLEditor::FindNearEditableContent(
const EditorDOMPointBase<PT, CT>& aPoint,
nsIEditor::EDirection aDirection) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aPoint.IsSetAndValid());
Element* editingHost = GetActiveEditingHost();
if (NS_WARN_IF(!editingHost)) {
return nullptr;
}
nsIContent* editableContent = nullptr;
if (aDirection == nsIEditor::ePrevious) {
editableContent = HTMLEditUtils::GetPreviousContent(
aPoint, {WalkTreeOption::IgnoreNonEditableNode}, editingHost);
if (!editableContent) {
return nullptr; // Not illegal.
}
} else {
editableContent = HTMLEditUtils::GetNextContent(
aPoint, {WalkTreeOption::IgnoreNonEditableNode}, editingHost);
if (NS_WARN_IF(!editableContent)) {
// Perhaps, illegal because the node pointed by aPoint isn't editable
// and nobody of previous nodes is editable.
return nullptr;
}
}
// scan in the right direction until we find an eligible text node,
// but don't cross any breaks, images, or table elements.
// XXX This comment sounds odd. editableContent may have already crossed
// breaks and/or images if they are non-editable.
while (editableContent && !editableContent->IsText() &&
!editableContent->IsHTMLElement(nsGkAtoms::br) &&
!HTMLEditUtils::IsImage(editableContent)) {
if (aDirection == nsIEditor::ePrevious) {
editableContent = HTMLEditUtils::GetPreviousContent(
*editableContent, {WalkTreeOption::IgnoreNonEditableNode},
editingHost);
if (NS_WARN_IF(!editableContent)) {
return nullptr;
}
} else {
editableContent = HTMLEditUtils::GetNextContent(
*editableContent, {WalkTreeOption::IgnoreNonEditableNode},
editingHost);
if (NS_WARN_IF(!editableContent)) {
return nullptr;
}
}
}
// don't cross any table elements
if ((!aPoint.IsInContentNode() &&
!!HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
*editableContent)) ||
(HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*editableContent) !=
HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
*aPoint.ContainerAsContent()))) {
return nullptr;
}
// otherwise, ok, we have found a good spot to put the selection
return editableContent;
}
nsresult HTMLEditor::RemoveEmptyNodesIn(nsRange& aRange) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aRange.IsPositioned());

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

@ -607,6 +607,78 @@ class HTMLEditUtils final {
return HTMLEditUtils::GetFirstChild(*parentNode, aOptions) == &aContent;
}
/**
* GetAdjacentContentToPutCaret() walks the DOM tree to find an editable node
* near aPoint where may be a good point to put caret and keep typing or
* deleting.
*
* @param aPoint The DOM point where to start to search from.
* @return If found, returns non-nullptr. Otherwise, nullptr.
* Note that if found node is in different table structure
* element, this returns nullptr.
*/
enum class WalkTreeDirection { Forward, Backward };
template <typename PT, typename CT>
static nsIContent* GetAdjacentContentToPutCaret(
const EditorDOMPointBase<PT, CT>& aPoint,
WalkTreeDirection aWalkTreeDirection, const Element& aEditingHost) {
MOZ_ASSERT(aPoint.IsSetAndValid());
nsIContent* editableContent = nullptr;
if (aWalkTreeDirection == WalkTreeDirection::Backward) {
editableContent = HTMLEditUtils::GetPreviousContent(
aPoint, {WalkTreeOption::IgnoreNonEditableNode}, &aEditingHost);
if (!editableContent) {
return nullptr; // Not illegal.
}
} else {
editableContent = HTMLEditUtils::GetNextContent(
aPoint, {WalkTreeOption::IgnoreNonEditableNode}, &aEditingHost);
if (NS_WARN_IF(!editableContent)) {
// Perhaps, illegal because the node pointed by aPoint isn't editable
// and nobody of previous nodes is editable.
return nullptr;
}
}
// scan in the right direction until we find an eligible text node,
// but don't cross any breaks, images, or table elements.
// XXX This comment sounds odd. editableContent may have already crossed
// breaks and/or images if they are non-editable.
while (editableContent && !editableContent->IsText() &&
!editableContent->IsHTMLElement(nsGkAtoms::br) &&
!HTMLEditUtils::IsImage(editableContent)) {
if (aWalkTreeDirection == WalkTreeDirection::Backward) {
editableContent = HTMLEditUtils::GetPreviousContent(
*editableContent, {WalkTreeOption::IgnoreNonEditableNode},
&aEditingHost);
if (NS_WARN_IF(!editableContent)) {
return nullptr;
}
} else {
editableContent = HTMLEditUtils::GetNextContent(
*editableContent, {WalkTreeOption::IgnoreNonEditableNode},
&aEditingHost);
if (NS_WARN_IF(!editableContent)) {
return nullptr;
}
}
}
// don't cross any table elements
if ((!aPoint.IsInContentNode() &&
!!HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
*editableContent)) ||
(HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*editableContent) !=
HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
*aPoint.ContainerAsContent()))) {
return nullptr;
}
// otherwise, ok, we have found a good spot to put the selection
return editableContent;
}
/**
* GetLastLeafContent() returns rightmost leaf content in aNode. It depends
* on aLeafNodeTypes whether this which types of nodes are treated as leaf
@ -1609,7 +1681,6 @@ class HTMLEditUtils final {
/**
* Helper for GetPreviousContent() and GetNextContent().
*/
enum class WalkTreeDirection { Forward, Backward };
static nsIContent* GetAdjacentLeafContent(
const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
const WalkTreeOptions& aOptions,

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

@ -2557,23 +2557,6 @@ class HTMLEditor final : public TextEditor,
*/
bool StartOrEndOfSelectionRangesIsIn(nsIContent& aContent) const;
/**
* FindNearEditableContent() tries to find an editable node near aPoint.
*
* @param aPoint The DOM point where to start to search from.
* @param aDirection If nsIEditor::ePrevious is set, this searches an
* editable node from next nodes. Otherwise, from
* previous nodes.
* @return If found, returns non-nullptr. Otherwise, nullptr.
* Note that if found node is in different table element,
* this returns nullptr.
* And also if aDirection is not nsIEditor::ePrevious,
* the result may be the node pointed by aPoint.
*/
template <typename PT, typename CT>
nsIContent* FindNearEditableContent(const EditorDOMPointBase<PT, CT>& aPoint,
nsIEditor::EDirection aDirection);
/**
* AdjustCaretPositionAndEnsurePaddingBRElement() may adjust caret
* position to nearest editable content and if padding `<br>` element is