Bug 1627175 - part 38: Move `EditorBase::GetNextContent()` and `EditorBase::GetPreviousContent()` to `HTMLEditUtils` r=m_kato

Differential Revision: https://phabricator.services.mozilla.com/D113242
This commit is contained in:
Masayuki Nakano 2021-04-27 11:17:44 +00:00
Родитель 7da0836039
Коммит 3aef835f6c
8 изменённых файлов: 365 добавлений и 353 удалений

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

@ -128,6 +128,7 @@ using namespace widget;
using LeafNodeType = HTMLEditUtils::LeafNodeType;
using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
/*****************************************************************************
* mozilla::EditorBase
@ -2802,231 +2803,6 @@ nsresult EditorBase::DeleteTextWithTransaction(Text& aTextNode,
return rv;
}
// static
nsIContent* EditorBase::GetPreviousContent(
const nsINode& aNode, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
if (&aNode == aAncestorLimiter ||
(aAncestorLimiter && !aNode.IsInclusiveDescendantOf(aAncestorLimiter))) {
return nullptr;
}
return EditorBase::GetAdjacentContent(aNode, WalkTreeDirection::Backward,
aOptions, aAncestorLimiter);
}
// static
nsIContent* EditorBase::GetPreviousContent(
const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
MOZ_ASSERT(aPoint.IsSetAndValid());
NS_WARNING_ASSERTION(
!aPoint.IsInDataNode() || aPoint.IsInTextNode(),
"GetPreviousContent() doesn't assume that the start point is a "
"data node except text node");
// If we are at the beginning of the node, or it is a text node, then just
// look before it.
if (aPoint.IsStartOfContainer() || aPoint.IsInTextNode()) {
if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
aPoint.IsInContentNode() &&
HTMLEditUtils::IsBlockElement(*aPoint.ContainerAsContent())) {
// If we aren't allowed to cross blocks, don't look before this block.
return nullptr;
}
return EditorBase::GetPreviousContent(*aPoint.GetContainer(), aOptions,
aAncestorLimiter);
}
// else look before the child at 'aOffset'
if (aPoint.GetChild()) {
return EditorBase::GetPreviousContent(*aPoint.GetChild(), aOptions,
aAncestorLimiter);
}
// unless there isn't one, in which case we are at the end of the node
// and want the deep-right child.
nsIContent* lastLeafContent = HTMLEditUtils::GetLastLeafChild(
*aPoint.GetContainer(),
{aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
? LeafNodeType::LeafNodeOrChildBlock
: LeafNodeType::OnlyLeafNode});
if (!lastLeafContent) {
return nullptr;
}
if ((!aOptions.contains(WalkTreeOption::IgnoreNonEditableNode) ||
EditorUtils::IsEditableContent(*lastLeafContent, EditorType::HTML)) &&
(!aOptions.contains(WalkTreeOption::IgnoreDataNodeExceptText) ||
EditorUtils::IsElementOrText(*lastLeafContent))) {
return lastLeafContent;
}
// restart the search from the non-editable node we just found
return EditorBase::GetPreviousContent(*lastLeafContent, aOptions,
aAncestorLimiter);
}
// static
nsIContent* EditorBase::GetNextContent(
const nsINode& aNode, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
if (&aNode == aAncestorLimiter ||
(aAncestorLimiter && !aNode.IsInclusiveDescendantOf(aAncestorLimiter))) {
return nullptr;
}
return EditorBase::GetAdjacentContent(aNode, WalkTreeDirection::Forward,
aOptions, aAncestorLimiter);
}
// static
nsIContent* EditorBase::GetNextContent(
const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
MOZ_ASSERT(aPoint.IsSetAndValid());
NS_WARNING_ASSERTION(
!aPoint.IsInDataNode() || aPoint.IsInTextNode(),
"GetNextContent() doesn't assume that the start point is a "
"data node except text node");
EditorRawDOMPoint point(aPoint);
// if the container is a text node, use its location instead
if (point.IsInTextNode()) {
point.SetAfter(point.GetContainer());
if (NS_WARN_IF(!point.IsSet())) {
return nullptr;
}
}
if (point.GetChild()) {
if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
HTMLEditUtils::IsBlockElement(*point.GetChild())) {
return point.GetChild();
}
nsIContent* firstLeafContent = HTMLEditUtils::GetFirstLeafChild(
*point.GetChild(),
{aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
? LeafNodeType::LeafNodeOrChildBlock
: LeafNodeType::OnlyLeafNode});
if (!firstLeafContent) {
return point.GetChild();
}
// XXX Why do we need to do this check? The leaf node must be a descendant
// of `point.GetChild()`.
if (aAncestorLimiter &&
(firstLeafContent == aAncestorLimiter ||
!firstLeafContent->IsInclusiveDescendantOf(aAncestorLimiter))) {
return nullptr;
}
if ((!aOptions.contains(WalkTreeOption::IgnoreNonEditableNode) ||
EditorUtils::IsEditableContent(*firstLeafContent, EditorType::HTML)) &&
(!aOptions.contains(WalkTreeOption::IgnoreDataNodeExceptText) ||
EditorUtils::IsElementOrText(*firstLeafContent))) {
return firstLeafContent;
}
// restart the search from the non-editable node we just found
return EditorBase::GetNextContent(*firstLeafContent, aOptions,
aAncestorLimiter);
}
// unless there isn't one, in which case we are at the end of the node
// and want the next one.
if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
point.IsInContentNode() &&
HTMLEditUtils::IsBlockElement(*point.ContainerAsContent())) {
// don't cross out of parent block
return nullptr;
}
return EditorBase::GetNextContent(*point.GetContainer(), aOptions,
aAncestorLimiter);
}
// static
nsIContent* EditorBase::GetAdjacentLeafContent(
const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
// called only by GetPriorNode so we don't need to check params.
MOZ_ASSERT(&aNode != aAncestorLimiter);
MOZ_ASSERT_IF(aAncestorLimiter,
aAncestorLimiter->IsInclusiveDescendantOf(aAncestorLimiter));
const nsINode* node = &aNode;
for (;;) {
// if aNode has a sibling in the right direction, return
// that sibling's closest child (or itself if it has no children)
nsIContent* sibling = aWalkTreeDirection == WalkTreeDirection::Forward
? node->GetNextSibling()
: node->GetPreviousSibling();
if (sibling) {
if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
HTMLEditUtils::IsBlockElement(*sibling)) {
// don't look inside prevsib, since it is a block
return sibling;
}
const LeafNodeTypes leafNodeTypes = {
aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
? LeafNodeType::LeafNodeOrChildBlock
: LeafNodeType::OnlyLeafNode};
nsIContent* leafContent =
aWalkTreeDirection == WalkTreeDirection::Forward
? HTMLEditUtils::GetFirstLeafChild(*sibling, leafNodeTypes)
: HTMLEditUtils::GetLastLeafChild(*sibling, leafNodeTypes);
return leafContent ? leafContent : sibling;
}
nsIContent* parent = node->GetParent();
if (!parent) {
return nullptr;
}
if (parent == aAncestorLimiter ||
(aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
HTMLEditUtils::IsBlockElement(*parent))) {
return nullptr;
}
node = parent;
}
MOZ_ASSERT_UNREACHABLE("What part of for(;;) do you not understand?");
return nullptr;
}
// static
nsIContent* EditorBase::GetAdjacentContent(
const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
if (&aNode == aAncestorLimiter) {
// Don't allow traversal above the root node! This helps
// prevent us from accidentally editing browser content
// when the editor is in a text widget.
return nullptr;
}
nsIContent* leafContent = EditorBase::GetAdjacentLeafContent(
aNode, aWalkTreeDirection, aOptions, aAncestorLimiter);
if (!leafContent) {
return nullptr;
}
if ((!aOptions.contains(WalkTreeOption::IgnoreNonEditableNode) ||
EditorUtils::IsEditableContent(*leafContent, EditorType::HTML)) &&
(!aOptions.contains(WalkTreeOption::IgnoreDataNodeExceptText) ||
EditorUtils::IsElementOrText(*leafContent))) {
return leafContent;
}
return EditorBase::GetAdjacentContent(*leafContent, aWalkTreeDirection,
aOptions, aAncestorLimiter);
}
bool EditorBase::IsRoot(const nsINode* inNode) const {
if (NS_WARN_IF(!inNode)) {
return false;
@ -3509,7 +3285,7 @@ EditorBase::CreateTransactionForCollapsedRange(
MOZ_ASSERT(IsHTMLEditor());
// We're backspacing from the beginning of a node. Delete the last thing
// of previous editable content.
nsIContent* previousEditableContent = EditorBase::GetPreviousContent(
nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent(
*point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
GetEditorRoot());
if (!previousEditableContent) {
@ -3553,7 +3329,7 @@ EditorBase::CreateTransactionForCollapsedRange(
MOZ_ASSERT(IsHTMLEditor());
// We're deleting from the end of a node. Delete the first thing of
// next editable content.
nsIContent* nextEditableContent = EditorBase::GetNextContent(
nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent(
*point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
GetEditorRoot());
if (!nextEditableContent) {
@ -3616,10 +3392,10 @@ EditorBase::CreateTransactionForCollapsedRange(
if (IsHTMLEditor()) {
editableContent =
aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward
? EditorBase::GetPreviousContent(
? HTMLEditUtils::GetPreviousContent(
point, {WalkTreeOption::IgnoreNonEditableNode},
GetEditorRoot())
: EditorBase::GetNextContent(
: HTMLEditUtils::GetNextContent(
point, {WalkTreeOption::IgnoreNonEditableNode},
GetEditorRoot());
if (!editableContent) {
@ -3632,10 +3408,10 @@ EditorBase::CreateTransactionForCollapsedRange(
editableContent =
aHowToHandleCollapsedRange ==
HowToHandleCollapsedRange::ExtendBackward
? EditorBase::GetPreviousContent(
? HTMLEditUtils::GetPreviousContent(
*editableContent, {WalkTreeOption::IgnoreNonEditableNode},
GetEditorRoot())
: EditorBase::GetNextContent(
: HTMLEditUtils::GetNextContent(
*editableContent, {WalkTreeOption::IgnoreNonEditableNode},
GetEditorRoot());
}

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

@ -9,7 +9,6 @@
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
#include "mozilla/EditAction.h" // for EditAction and EditSubAction
#include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
#include "mozilla/EnumSet.h" // for EnumSet
#include "mozilla/EventForwards.h" // for InputEventTargetRanges
#include "mozilla/Maybe.h" // for Maybe
#include "mozilla/OwningNonNull.h" // for OwningNonNull
@ -1723,73 +1722,6 @@ class EditorBase : public nsIEditor,
MOZ_CAN_RUN_SCRIPT nsresult
DoTransactionInternal(nsITransaction* aTransaction);
/**
* Get the next node.
*
*
* On the other hand, the methods taking nsINode behavior must be what
* you want. They start to search the result from next node of the given
* node.
*/
/**
* Get previous content node of aNode if there is.
*
* @param aNode The node from which we start to walk the DOM tree.
*/
enum class WalkTreeOption {
IgnoreNonEditableNode, // Ignore non-editable nodes and their children.
IgnoreDataNodeExceptText, // Ignore data nodes which are not text node.
StopAtBlockBoundary, // Stop waking the tree at a block boundary.
};
using WalkTreeOptions = EnumSet<WalkTreeOption>;
static nsIContent* GetPreviousContent(
const nsINode& aNode, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
/**
* And another version that takes a point in DOM tree rather than a node.
*/
static nsIContent* GetPreviousContent(
const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
/**
* Get next content node of aNode if there is.
*
* @param aNode The node from which we start to walk the DOM tree.
*/
static nsIContent* GetNextContent(const nsINode& aNode,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
/**
* And another version that takes a point in DOM tree rather than a node.
*
* Note that this may return the child at the offset. E.g., following code
* causes infinite loop.
*
* EditorRawDOMPoint point(aEditableNode);
* while (nsIContent* content =
* GetNextContent(point, {WalkTreeOption::IgnoreNonEditableNode})) {
* // Do something...
* point.Set(content);
* }
*
* Following code must be you expected:
*
* while (nsIContent* content =
* GetNextContent(point, {WalkTreeOption::IgnoreNonEditableNode}) {
* // Do something...
* DebugOnly<bool> advanced = point.Advanced();
* MOZ_ASSERT(advanced);
* point.Set(point.GetChild());
* }
*/
static nsIContent* GetNextContent(const EditorRawDOMPoint& aPoint,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
/**
* Returns true if aNode is our root node.
*/
@ -2154,19 +2086,6 @@ class EditorBase : public nsIEditor,
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ScrollSelectionFocusIntoView();
/**
* Helper for GetPreviousContent() and GetNextContent().
*/
enum class WalkTreeDirection { Forward, Backward };
static nsIContent* GetAdjacentLeafContent(
const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
static nsIContent* GetAdjacentContent(
const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
virtual nsresult InstallEventListeners();
virtual void CreateEventListeners();
virtual void RemoveEventListeners();

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

@ -38,6 +38,31 @@ namespace mozilla {
using namespace dom;
using EditorType = EditorBase::EditorType;
template nsIContent* HTMLEditUtils::GetPreviousContent(
const EditorDOMPoint& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter);
template nsIContent* HTMLEditUtils::GetPreviousContent(
const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter);
template nsIContent* HTMLEditUtils::GetPreviousContent(
const EditorDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter);
template nsIContent* HTMLEditUtils::GetPreviousContent(
const EditorRawDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter);
template nsIContent* HTMLEditUtils::GetNextContent(
const EditorDOMPoint& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter);
template nsIContent* HTMLEditUtils::GetNextContent(
const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter);
template nsIContent* HTMLEditUtils::GetNextContent(
const EditorDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter);
template nsIContent* HTMLEditUtils::GetNextContent(
const EditorRawDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter);
template EditorDOMPoint HTMLEditUtils::GetPreviousEditablePoint(
nsIContent& aContent, const Element* aAncestorLimiter,
InvisibleWhiteSpaces aInvisibleWhiteSpaces,
@ -865,6 +890,209 @@ bool HTMLEditUtils::IsSingleLineContainer(nsINode& aNode) {
aNode.IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::dt, nsGkAtoms::dd);
}
// static
template <typename PT, typename CT>
nsIContent* HTMLEditUtils::GetPreviousContent(
const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
MOZ_ASSERT(aPoint.IsSetAndValid());
NS_WARNING_ASSERTION(
!aPoint.IsInDataNode() || aPoint.IsInTextNode(),
"GetPreviousContent() doesn't assume that the start point is a "
"data node except text node");
// If we are at the beginning of the node, or it is a text node, then just
// look before it.
if (aPoint.IsStartOfContainer() || aPoint.IsInTextNode()) {
if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
aPoint.IsInContentNode() &&
HTMLEditUtils::IsBlockElement(*aPoint.ContainerAsContent())) {
// If we aren't allowed to cross blocks, don't look before this block.
return nullptr;
}
return HTMLEditUtils::GetPreviousContent(*aPoint.GetContainer(), aOptions,
aAncestorLimiter);
}
// else look before the child at 'aOffset'
if (aPoint.GetChild()) {
return HTMLEditUtils::GetPreviousContent(*aPoint.GetChild(), aOptions,
aAncestorLimiter);
}
// unless there isn't one, in which case we are at the end of the node
// and want the deep-right child.
nsIContent* lastLeafContent = HTMLEditUtils::GetLastLeafChild(
*aPoint.GetContainer(),
{aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
? LeafNodeType::LeafNodeOrChildBlock
: LeafNodeType::OnlyLeafNode});
if (!lastLeafContent) {
return nullptr;
}
if ((!aOptions.contains(WalkTreeOption::IgnoreNonEditableNode) ||
EditorUtils::IsEditableContent(*lastLeafContent, EditorType::HTML)) &&
(!aOptions.contains(WalkTreeOption::IgnoreDataNodeExceptText) ||
EditorUtils::IsElementOrText(*lastLeafContent))) {
return lastLeafContent;
}
// restart the search from the non-editable node we just found
return HTMLEditUtils::GetPreviousContent(*lastLeafContent, aOptions,
aAncestorLimiter);
}
// static
template <typename PT, typename CT>
nsIContent* HTMLEditUtils::GetNextContent(
const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
MOZ_ASSERT(aPoint.IsSetAndValid());
NS_WARNING_ASSERTION(
!aPoint.IsInDataNode() || aPoint.IsInTextNode(),
"GetNextContent() doesn't assume that the start point is a "
"data node except text node");
EditorRawDOMPoint point(aPoint);
// if the container is a text node, use its location instead
if (point.IsInTextNode()) {
point.SetAfter(point.GetContainer());
if (NS_WARN_IF(!point.IsSet())) {
return nullptr;
}
}
if (point.GetChild()) {
if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
HTMLEditUtils::IsBlockElement(*point.GetChild())) {
return point.GetChild();
}
nsIContent* firstLeafContent = HTMLEditUtils::GetFirstLeafChild(
*point.GetChild(),
{aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
? LeafNodeType::LeafNodeOrChildBlock
: LeafNodeType::OnlyLeafNode});
if (!firstLeafContent) {
return point.GetChild();
}
// XXX Why do we need to do this check? The leaf node must be a descendant
// of `point.GetChild()`.
if (aAncestorLimiter &&
(firstLeafContent == aAncestorLimiter ||
!firstLeafContent->IsInclusiveDescendantOf(aAncestorLimiter))) {
return nullptr;
}
if ((!aOptions.contains(WalkTreeOption::IgnoreNonEditableNode) ||
EditorUtils::IsEditableContent(*firstLeafContent, EditorType::HTML)) &&
(!aOptions.contains(WalkTreeOption::IgnoreDataNodeExceptText) ||
EditorUtils::IsElementOrText(*firstLeafContent))) {
return firstLeafContent;
}
// restart the search from the non-editable node we just found
return HTMLEditUtils::GetNextContent(*firstLeafContent, aOptions,
aAncestorLimiter);
}
// unless there isn't one, in which case we are at the end of the node
// and want the next one.
if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
point.IsInContentNode() &&
HTMLEditUtils::IsBlockElement(*point.ContainerAsContent())) {
// don't cross out of parent block
return nullptr;
}
return HTMLEditUtils::GetNextContent(*point.GetContainer(), aOptions,
aAncestorLimiter);
}
// static
nsIContent* HTMLEditUtils::GetAdjacentLeafContent(
const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
// called only by GetPriorNode so we don't need to check params.
MOZ_ASSERT(&aNode != aAncestorLimiter);
MOZ_ASSERT_IF(aAncestorLimiter,
aAncestorLimiter->IsInclusiveDescendantOf(aAncestorLimiter));
const nsINode* node = &aNode;
for (;;) {
// if aNode has a sibling in the right direction, return
// that sibling's closest child (or itself if it has no children)
nsIContent* sibling = aWalkTreeDirection == WalkTreeDirection::Forward
? node->GetNextSibling()
: node->GetPreviousSibling();
if (sibling) {
if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
HTMLEditUtils::IsBlockElement(*sibling)) {
// don't look inside prevsib, since it is a block
return sibling;
}
const LeafNodeTypes leafNodeTypes = {
aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
? LeafNodeType::LeafNodeOrChildBlock
: LeafNodeType::OnlyLeafNode};
nsIContent* leafContent =
aWalkTreeDirection == WalkTreeDirection::Forward
? HTMLEditUtils::GetFirstLeafChild(*sibling, leafNodeTypes)
: HTMLEditUtils::GetLastLeafChild(*sibling, leafNodeTypes);
return leafContent ? leafContent : sibling;
}
nsIContent* parent = node->GetParent();
if (!parent) {
return nullptr;
}
if (parent == aAncestorLimiter ||
(aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
HTMLEditUtils::IsBlockElement(*parent))) {
return nullptr;
}
node = parent;
}
MOZ_ASSERT_UNREACHABLE("What part of for(;;) do you not understand?");
return nullptr;
}
// static
nsIContent* HTMLEditUtils::GetAdjacentContent(
const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter /* = nullptr */) {
if (&aNode == aAncestorLimiter) {
// Don't allow traversal above the root node! This helps
// prevent us from accidentally editing browser content
// when the editor is in a text widget.
return nullptr;
}
nsIContent* leafContent = HTMLEditUtils::GetAdjacentLeafContent(
aNode, aWalkTreeDirection, aOptions, aAncestorLimiter);
if (!leafContent) {
return nullptr;
}
if ((!aOptions.contains(WalkTreeOption::IgnoreNonEditableNode) ||
EditorUtils::IsEditableContent(*leafContent, EditorType::HTML)) &&
(!aOptions.contains(WalkTreeOption::IgnoreDataNodeExceptText) ||
EditorUtils::IsElementOrText(*leafContent))) {
return leafContent;
}
return HTMLEditUtils::GetAdjacentContent(*leafContent, aWalkTreeDirection,
aOptions, aAncestorLimiter);
}
// static
template <typename EditorDOMPointType>
EditorDOMPointType HTMLEditUtils::GetPreviousEditablePoint(

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

@ -370,6 +370,82 @@ class HTMLEditUtils final {
aFoundLinkElement);
}
/**
* Get adjacent content node of aNode if there is (even if one is in different
* parent element).
*
* @param aNode The node from which we start to walk the DOM
* tree.
* @param aOptions See WalkTreeOption for the detail.
* @param aAncestorLimiter Ancestor limiter element which these methods
* never cross its boundary. This is typically
* the editing host.
*/
enum class WalkTreeOption {
IgnoreNonEditableNode, // Ignore non-editable nodes and their children.
IgnoreDataNodeExceptText, // Ignore data nodes which are not text node.
StopAtBlockBoundary, // Stop waking the tree at a block boundary.
};
using WalkTreeOptions = EnumSet<WalkTreeOption>;
static nsIContent* GetPreviousContent(
const nsINode& aNode, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr) {
if (&aNode == aAncestorLimiter ||
(aAncestorLimiter &&
!aNode.IsInclusiveDescendantOf(aAncestorLimiter))) {
return nullptr;
}
return HTMLEditUtils::GetAdjacentContent(aNode, WalkTreeDirection::Backward,
aOptions, aAncestorLimiter);
}
static nsIContent* GetNextContent(const nsINode& aNode,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr) {
if (&aNode == aAncestorLimiter ||
(aAncestorLimiter &&
!aNode.IsInclusiveDescendantOf(aAncestorLimiter))) {
return nullptr;
}
return HTMLEditUtils::GetAdjacentContent(aNode, WalkTreeDirection::Forward,
aOptions, aAncestorLimiter);
}
/**
* And another version that takes a point in DOM tree rather than a node.
*/
template <typename PT, typename CT>
static nsIContent* GetPreviousContent(
const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
/**
* And another version that takes a point in DOM tree rather than a node.
*
* Note that this may return the child at the offset. E.g., following code
* causes infinite loop.
*
* EditorRawDOMPoint point(aEditableNode);
* while (nsIContent* content =
* GetNextContent(point, {WalkTreeOption::IgnoreNonEditableNode})) {
* // Do something...
* point.Set(content);
* }
*
* Following code must be you expected:
*
* while (nsIContent* content =
* GetNextContent(point, {WalkTreeOption::IgnoreNonEditableNode}) {
* // Do something...
* DebugOnly<bool> advanced = point.Advanced();
* MOZ_ASSERT(advanced);
* point.Set(point.GetChild());
* }
*/
template <typename PT, typename CT>
static nsIContent* GetNextContent(const EditorDOMPointBase<PT, CT>& aPoint,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
/**
* GetLastLeafChild() returns rightmost leaf content in aNode. It depends on
* aLeafNodeTypes whether this which types of nodes are treated as leaf nodes.
@ -696,9 +772,8 @@ class HTMLEditUtils final {
aRootElement, {LeafNodeType::OnlyLeafNode});
if (leafContent && !EditorUtils::IsEditableContent(
*leafContent, EditorBase::EditorType::HTML)) {
leafContent = EditorBase::GetNextContent(
*leafContent, {EditorBase::WalkTreeOption::IgnoreNonEditableNode},
&aRootElement);
leafContent = HTMLEditUtils::GetNextContent(
*leafContent, {WalkTreeOption::IgnoreNonEditableNode}, &aRootElement);
}
MOZ_ASSERT(leafContent != &aRootElement);
return leafContent;
@ -1068,6 +1143,19 @@ class HTMLEditUtils final {
aContent.IsHTMLElement(nsGkAtoms::table));
return !cannotCrossBoundary;
}
/**
* Helper for GetPreviousContent() and GetNextContent().
*/
enum class WalkTreeDirection { Forward, Backward };
static nsIContent* GetAdjacentLeafContent(
const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
static nsIContent* GetAdjacentContent(
const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
const WalkTreeOptions& aOptions,
const Element* aAncestorLimiter = nullptr);
};
/**

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

@ -72,6 +72,7 @@ using namespace widget;
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using LeafNodeType = HTMLEditUtils::LeafNodeType;
using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
const char16_t kNBSP = 160;
@ -5006,12 +5007,12 @@ nsIContent* HTMLEditor::GetPreviousHTMLElementOrTextInternal(
return nullptr;
}
return aNoBlockCrossing
? EditorBase::GetPreviousContent(
? HTMLEditUtils::GetPreviousContent(
aNode,
{WalkTreeOption::IgnoreDataNodeExceptText,
WalkTreeOption::StopAtBlockBoundary},
editingHost)
: EditorBase::GetPreviousContent(
: HTMLEditUtils::GetPreviousContent(
aNode, {WalkTreeOption::IgnoreDataNodeExceptText},
editingHost);
}
@ -5024,12 +5025,12 @@ nsIContent* HTMLEditor::GetPreviousHTMLElementOrTextInternal(
return nullptr;
}
return aNoBlockCrossing
? EditorBase::GetPreviousContent(
? HTMLEditUtils::GetPreviousContent(
aPoint,
{WalkTreeOption::IgnoreDataNodeExceptText,
WalkTreeOption::StopAtBlockBoundary},
editingHost)
: EditorBase::GetPreviousContent(
: HTMLEditUtils::GetPreviousContent(
aPoint, {WalkTreeOption::IgnoreDataNodeExceptText},
editingHost);
}
@ -5041,12 +5042,12 @@ nsIContent* HTMLEditor::GetPreviousEditableHTMLNodeInternal(
return nullptr;
}
return aNoBlockCrossing
? EditorBase::GetPreviousContent(
? HTMLEditUtils::GetPreviousContent(
aNode,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)
: EditorBase::GetPreviousContent(
: HTMLEditUtils::GetPreviousContent(
aNode, {WalkTreeOption::IgnoreNonEditableNode}, editingHost);
}
@ -5057,12 +5058,12 @@ nsIContent* HTMLEditor::GetPreviousEditableHTMLNodeInternal(
if (NS_WARN_IF(!editingHost)) {
return nullptr;
}
return aNoBlockCrossing ? EditorBase::GetPreviousContent(
return aNoBlockCrossing ? HTMLEditUtils::GetPreviousContent(
aPoint,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)
: EditorBase::GetPreviousContent(
: HTMLEditUtils::GetPreviousContent(
aPoint, {WalkTreeOption::IgnoreNonEditableNode},
editingHost);
}
@ -5074,12 +5075,12 @@ nsIContent* HTMLEditor::GetNextHTMLElementOrTextInternal(
return nullptr;
}
return aNoBlockCrossing
? EditorBase::GetNextContent(
? HTMLEditUtils::GetNextContent(
aNode,
{WalkTreeOption::IgnoreDataNodeExceptText,
WalkTreeOption::StopAtBlockBoundary},
editingHost)
: EditorBase::GetNextContent(
: HTMLEditUtils::GetNextContent(
aNode, {WalkTreeOption::IgnoreDataNodeExceptText},
editingHost);
}
@ -5092,12 +5093,12 @@ nsIContent* HTMLEditor::GetNextHTMLElementOrTextInternal(
return nullptr;
}
return aNoBlockCrossing
? EditorBase::GetNextContent(
? HTMLEditUtils::GetNextContent(
aPoint,
{WalkTreeOption::IgnoreDataNodeExceptText,
WalkTreeOption::StopAtBlockBoundary},
editingHost)
: EditorBase::GetNextContent(
: HTMLEditUtils::GetNextContent(
aPoint, {WalkTreeOption::IgnoreDataNodeExceptText},
editingHost);
}
@ -5109,12 +5110,12 @@ nsIContent* HTMLEditor::GetNextEditableHTMLNodeInternal(
return nullptr;
}
return aNoBlockCrossing
? EditorBase::GetNextContent(
? HTMLEditUtils::GetNextContent(
aNode,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)
: EditorBase::GetNextContent(
: HTMLEditUtils::GetNextContent(
aNode, {WalkTreeOption::IgnoreNonEditableNode}, editingHost);
}
@ -5125,12 +5126,12 @@ nsIContent* HTMLEditor::GetNextEditableHTMLNodeInternal(
if (NS_WARN_IF(!editingHost)) {
return nullptr;
}
return aNoBlockCrossing ? EditorBase::GetNextContent(
return aNoBlockCrossing ? HTMLEditUtils::GetNextContent(
aPoint,
{WalkTreeOption::IgnoreNonEditableNode,
WalkTreeOption::StopAtBlockBoundary},
editingHost)
: EditorBase::GetNextContent(
: HTMLEditUtils::GetNextContent(
aPoint, {WalkTreeOption::IgnoreNonEditableNode},
editingHost);
}

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

@ -978,8 +978,8 @@ class HTMLEditor final : public TextEditor,
/**
* GetPreviousEditableHTMLNode*() methods are similar to
* EditorBase::GetPreviousContent({WalkTreeOption::IgnoreNonEditableNode}) but
* this won't return nodes outside active editing host.
* HTMLEditUtils::GetPreviousContent({WalkTreeOption::IgnoreNonEditableNode})
* but this won't return nodes outside active editing host.
*/
nsIContent* GetPreviousEditableHTMLNode(nsINode& aNode) const {
return GetPreviousEditableHTMLNodeInternal(aNode, false);
@ -1047,7 +1047,7 @@ class HTMLEditor final : public TextEditor,
/**
* GetNextEditableHTMLNode*() methods are similar to
* EditorBase::GetNextContent({WalkTreeOption::IgnoreNonEditableNode}) but
* HTMLEditUtils::GetNextContent({WalkTreeOption::IgnoreNonEditableNode}) but
* this won't return nodes outside active editing host.
*
* Note that same as EditorBase::GetTextEditableNode(), methods which take

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

@ -50,6 +50,7 @@ using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
using InvisibleWhiteSpaces = HTMLEditUtils::InvisibleWhiteSpaces;
using StyleDifference = HTMLEditUtils::StyleDifference;
using TableBoundary = HTMLEditUtils::TableBoundary;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
template nsresult HTMLEditor::DeleteTextAndTextNodesWithTransaction(
const EditorDOMPoint& aStartPoint, const EditorDOMPoint& aEndPoint,
@ -3897,9 +3898,9 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction(
if (howToHandleCollapsedRange ==
EditorBase::HowToHandleCollapsedRange::ExtendBackward &&
caretPoint.IsStartOfContainer()) {
nsIContent* previousEditableContent = EditorBase::GetPreviousContent(
*caretPoint.GetContainer(),
{EditorBase::WalkTreeOption::IgnoreNonEditableNode}, editingHost);
nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent(
*caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
editingHost);
if (!previousEditableContent) {
continue;
}
@ -3920,9 +3921,9 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction(
if (howToHandleCollapsedRange ==
EditorBase::HowToHandleCollapsedRange::ExtendForward &&
caretPoint.IsEndOfContainer()) {
nsIContent* nextEditableContent = EditorBase::GetNextContent(
*caretPoint.GetContainer(),
{EditorBase::WalkTreeOption::IgnoreNonEditableNode}, editingHost);
nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent(
*caretPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode},
editingHost);
if (!nextEditableContent) {
continue;
}
@ -3957,13 +3958,11 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction(
nsIContent* editableContent =
howToHandleCollapsedRange ==
EditorBase::HowToHandleCollapsedRange::ExtendBackward
? EditorBase::GetPreviousContent(
caretPoint,
{EditorBase::WalkTreeOption::IgnoreNonEditableNode},
? HTMLEditUtils::GetPreviousContent(
caretPoint, {WalkTreeOption::IgnoreNonEditableNode},
editingHost)
: EditorBase::GetNextContent(
caretPoint,
{EditorBase::WalkTreeOption::IgnoreNonEditableNode},
: HTMLEditUtils::GetNextContent(
caretPoint, {WalkTreeOption::IgnoreNonEditableNode},
editingHost);
if (!editableContent) {
continue;
@ -3973,10 +3972,10 @@ HTMLEditor::AutoDeleteRangesHandler::ComputeRangesToDeleteRangesWithTransaction(
editableContent =
howToHandleCollapsedRange ==
EditorBase::HowToHandleCollapsedRange::ExtendBackward
? EditorBase::GetPreviousContent(
? HTMLEditUtils::GetPreviousContent(
*editableContent, {WalkTreeOption::IgnoreNonEditableNode},
editingHost)
: EditorBase::GetNextContent(
: HTMLEditUtils::GetNextContent(
*editableContent, {WalkTreeOption::IgnoreNonEditableNode},
editingHost);
}
@ -4428,7 +4427,7 @@ nsresult HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner::
atStart.IsEndOfContainer() && range.StartRef().GetChild() &&
range.StartRef().GetChild()->IsHTMLElement(nsGkAtoms::br) &&
!aHTMLEditor.IsVisibleBRElement(range.StartRef().GetChild())
? EditorBase::GetNextContent(
? HTMLEditUtils::GetNextContent(
*atStart.ContainerAsContent(),
{WalkTreeOption::IgnoreDataNodeExceptText,
WalkTreeOption::StopAtBlockBoundary},
@ -5177,7 +5176,7 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
EditorDOMPoint afterEmptyBlock(
EditorRawDOMPoint::After(mEmptyInclusiveAncestorBlockElement));
MOZ_ASSERT(afterEmptyBlock.IsSet());
if (nsIContent* nextContentOfEmptyBlock = EditorBase::GetNextContent(
if (nsIContent* nextContentOfEmptyBlock = HTMLEditUtils::GetNextContent(
afterEmptyBlock, {}, aHTMLEditor.GetActiveEditingHost())) {
EditorDOMPoint pt = aHTMLEditor.GetGoodCaretPointFor(
*nextContentOfEmptyBlock, aDirectionAndAmount);
@ -5199,7 +5198,7 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::AutoDeleteRangesHandler::
// if there is. Otherwise, to after the empty block.
EditorRawDOMPoint atEmptyBlock(mEmptyInclusiveAncestorBlockElement);
if (nsIContent* previousContentOfEmptyBlock =
EditorBase::GetPreviousContent(
HTMLEditUtils::GetPreviousContent(
atEmptyBlock, {WalkTreeOption::IgnoreNonEditableNode},
aHTMLEditor.GetActiveEditingHost())) {
EditorDOMPoint pt = aHTMLEditor.GetGoodCaretPointFor(

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

@ -39,6 +39,7 @@
namespace mozilla {
using EditorType = EditorUtils::EditorType;
using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
/*****************************************************************************
* ListElementSelectionState
@ -247,8 +248,8 @@ AlignStateAtSelection::AlignStateAtSelection(HTMLEditor& aHTMLEditor,
else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) &&
atBodyOrDocumentElement.IsSet() &&
atStartOfSelection.Offset() == atBodyOrDocumentElement.Offset()) {
editTargetContent = EditorBase::GetNextContent(
atStartOfSelection, {EditorBase::WalkTreeOption::IgnoreNonEditableNode},
editTargetContent = HTMLEditUtils::GetNextContent(
atStartOfSelection, {WalkTreeOption::IgnoreNonEditableNode},
aHTMLEditor.GetActiveEditingHost());
if (NS_WARN_IF(!editTargetContent)) {
aRv.Throw(NS_ERROR_FAILURE);