Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/editor/libeditor/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 111 kB image not shown  

SSL HTMLEditUtils.h   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#ifndef HTMLEditUtils_h
#define HTMLEditUtils_h

/**
 * This header declares/defines static helper methods as members of
 * HTMLEditUtils.  If you want to create or look for helper trivial classes for
 * HTMLEditor, see HTMLEditHelpers.h.
 */


#include "EditorBase.h"
#include "EditorDOMPoint.h"
#include "EditorForwards.h"
#include "EditorLineBreak.h"
#include "EditorUtils.h"
#include "HTMLEditHelpers.h"

#include "mozilla/Attributes.h"
#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"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Text.h"

#include "nsContentUtils.h"
#include "nsCRT.h"
#include "nsGkAtoms.h"
#include "nsHTMLTags.h"
#include "nsTArray.h"

class nsAtom;
class nsPresContext;

namespace mozilla {

enum class CollectChildrenOption {
  // Ignore non-editable nodes
  IgnoreNonEditableChildren,
  // Ignore invisible text nodes
  IgnoreInvisibleTextNodes,
  // Collect list children too.
  CollectListChildren,
  // Collect table children too.
  CollectTableChildren,
};

class HTMLEditUtils final {
  using AbstractRange = dom::AbstractRange;
  using Element = dom::Element;
  using Selection = dom::Selection;
  using Text = dom::Text;

 public:
  static constexpr char16_t kNewLine = '\n';
  static constexpr char16_t kCarriageReturn = '\r';
  static constexpr char16_t kTab = '\t';
  static constexpr char16_t kSpace = ' ';
  static constexpr char16_t kNBSP = 0x00A0;
  static constexpr char16_t kGreaterThan = '>';

  /**
   * IsSimplyEditableNode() returns true when aNode is simply editable.
   * This does NOT means that aNode can be removed from current parent nor
   * aNode's data is editable.
   */

  static bool IsSimplyEditableNode(const nsINode& aNode) {
    return aNode.IsEditable();
  }

  /**
   * Return true if aElement is an editing host which is either:
   * - the root element
   * - parent is not editable
   * - the <body> element of the document
   */

  [[nodiscard]] static bool ElementIsEditableRoot(const Element& aElement);

  /**
   * Return true if inclusive flat tree ancestor has `inert` state.
   */

  static bool ContentIsInert(const nsIContent& aContent);

  /**
   * IsNeverContentEditableElementByUser() returns true if the element's content
   * is never editable by user.  E.g., the content is always replaced by
   * native anonymous node or something.
   */

  static bool IsNeverElementContentsEditableByUser(const nsIContent& aContent) {
    return aContent.IsElement() &&
           (!HTMLEditUtils::IsContainerNode(aContent) ||
            aContent.IsAnyOfHTMLElements(
                nsGkAtoms::applet, nsGkAtoms::colgroup, nsGkAtoms::frameset,
                nsGkAtoms::head, nsGkAtoms::html, nsGkAtoms::iframe,
                nsGkAtoms::meter, nsGkAtoms::progress, nsGkAtoms::select,
                nsGkAtoms::textarea));
  }

  /**
   * IsNonEditableReplacedContent() returns true when aContent is an inclusive
   * descendant of a replaced element whose content shouldn't be editable by
   * user's operation.
   */

  static bool IsNonEditableReplacedContent(const nsIContent& aContent) {
    for (Element* element : aContent.InclusiveAncestorsOfType<Element>()) {
      if (element->IsAnyOfHTMLElements(nsGkAtoms::select, nsGkAtoms::option,
                                       nsGkAtoms::optgroup)) {
        return true;
      }
    }
    return false;
  }

  /*
   * IsRemovalNode() returns true when parent of aContent is editable even
   * if aContent isn't editable.
   * This is a valid method to check it if you find the content from point
   * of view of siblings or parents of aContent.
   * Note that padding `<br>` element for empty editor and manual native
   * anonymous content should be deletable even after `HTMLEditor` is destroyed
   * because they are owned/managed by `HTMLEditor`.
   */

  static bool IsRemovableNode(const nsIContent& aContent) {
    return EditorUtils::IsPaddingBRElementForEmptyEditor(aContent) ||
           aContent.IsRootOfNativeAnonymousSubtree() ||
           (aContent.GetParentNode() &&
            aContent.GetParentNode()->IsEditable() &&
            &aContent != aContent.OwnerDoc()->GetBody() &&
            &aContent != aContent.OwnerDoc()->GetDocumentElement());
  }

  /**
   * IsRemovableFromParentNode() returns true when aContent is editable, has a
   * parent node and the parent node is also editable.
   * This is a valid method to check it if you find the content from point
   * of view of descendants of aContent.
   * Note that padding `<br>` element for empty editor and manual native
   * anonymous content should be deletable even after `HTMLEditor` is destroyed
   * because they are owned/managed by `HTMLEditor`.
   */

  static bool IsRemovableFromParentNode(const nsIContent& aContent) {
    return EditorUtils::IsPaddingBRElementForEmptyEditor(aContent) ||
           aContent.IsRootOfNativeAnonymousSubtree() ||
           (aContent.IsEditable() && aContent.GetParentNode() &&
            aContent.GetParentNode()->IsEditable() &&
            &aContent != aContent.OwnerDoc()->GetBody() &&
            &aContent != aContent.OwnerDoc()->GetDocumentElement());
  }

  /**
   * CanContentsBeJoined() returns true if aLeftContent and aRightContent can be
   * joined.
   */

  static bool CanContentsBeJoined(const nsIContent& aLeftContent,
                                  const nsIContent& aRightContent);

  /**
   * Returns true if aContent is an element and it should be treated as a block.
   *
   * @param aBlockInlineCheck
   *  - If UseHTMLDefaultStyle, this returns true only for HTML elements which
   * are defined as a block by the default style.  I.e., non-HTML elements are
   * always treated as inline.
   *  - If UseComputedDisplayOutsideStyle, this returns true for element nodes
   * whose display-outside is not inline nor ruby.  This is useful to get
   * inclusive ancestor block element.
   *  - If UseComputedDisplayStyle, this returns true for element nodes whose
   * display-outside is not inline or whose display-inside is flow-root and they
   * do not appear as a form control.  This is useful to check whether
   * collapsible white-spaces at the element edges are visible or invisible or
   * whether <br> element at end of the element is visible or invisible.
   */

  [[nodiscard]] static bool IsBlockElement(const nsIContent& aContent,
                                           BlockInlineCheck aBlockInlineCheck);

  /**
   * This is designed to check elements or non-element nodes which are layed out
   * as inline.  Therefore, inline-block etc and ruby are treated as inline.
   * Note that invisible non-element nodes like comment nodes are also treated
   * as inline.
   *
   * @param aBlockInlineCheck   UseComputedDisplayOutsideStyle and
   *                            UseComputedDisplayStyle return same result for
   *                            any elements.
   */

  [[nodiscard]] static bool IsInlineContent(const nsIContent& aContent,
                                            BlockInlineCheck aBlockInlineCheck);

  /**
   * IsVisibleElementEvenIfLeafNode() returns true if aContent is an empty block
   * element, a visible replaced element such as a form control.  This does not
   * check the layout information.
   */

  static bool IsVisibleElementEvenIfLeafNode(const nsIContent& aContent);

  static bool IsInlineStyle(nsINode* aNode);

  /**
   * IsDisplayOutsideInline() returns true if display-outside value is
   * "inside".  This does NOT flush the layout.
   */

  [[nodiscard]] static bool IsDisplayOutsideInline(const Element& aElement);

  /**
   * IsDisplayInsideFlowRoot() returns true if display-inline value of aElement
   * is "flow-root".  This does NOT flush the layout.
   */

  [[nodiscard]] static bool IsDisplayInsideFlowRoot(const Element& aElement);

  /**
   * Return true if aElement is a flex item or a grid item.  This works only
   * when aElement has a primary frame.
   */

  [[nodiscard]] static bool IsFlexOrGridItem(const Element& aElement);

  /**
   * IsRemovableInlineStyleElement() returns true if aElement is an inline
   * element and can be removed or split to in order to modifying inline
   * styles.
   */

  static bool IsRemovableInlineStyleElement(Element& aElement);

  /**
   * Return true if aTagName is one of the format element name of
   * Document.execCommand("formatBlock").
   */

  [[nodiscard]] static bool IsFormatTagForFormatBlockCommand(
      const nsStaticAtom& aTagName) {
    return
        // clang-format off
        &aTagName == nsGkAtoms::address ||
        &aTagName == nsGkAtoms::article ||
        &aTagName == nsGkAtoms::aside ||
        &aTagName == nsGkAtoms::blockquote ||
        &aTagName == nsGkAtoms::dd ||
        &aTagName == nsGkAtoms::div ||
        &aTagName == nsGkAtoms::dl ||
        &aTagName == nsGkAtoms::dt ||
        &aTagName == nsGkAtoms::footer ||
        &aTagName == nsGkAtoms::h1 ||
        &aTagName == nsGkAtoms::h2 ||
        &aTagName == nsGkAtoms::h3 ||
        &aTagName == nsGkAtoms::h4 ||
        &aTagName == nsGkAtoms::h5 ||
        &aTagName == nsGkAtoms::h6 ||
        &aTagName == nsGkAtoms::header ||
        &aTagName == nsGkAtoms::hgroup ||
        &aTagName == nsGkAtoms::main ||
        &aTagName == nsGkAtoms::nav ||
        &aTagName == nsGkAtoms::p ||
        &aTagName == nsGkAtoms::pre ||
        &aTagName == nsGkAtoms::section;
    // clang-format on
  }

  /**
   * Return true if aContent is a format element of
   * Document.execCommand("formatBlock").
   */

  [[nodiscard]] static bool IsFormatElementForFormatBlockCommand(
      const nsIContent& aContent) {
    if (!aContent.IsHTMLElement() ||
        !aContent.NodeInfo()->NameAtom()->IsStatic()) {
      return false;
    }
    const nsStaticAtom* tagName = aContent.NodeInfo()->NameAtom()->AsStatic();
    return IsFormatTagForFormatBlockCommand(*tagName);
  }

  /**
   * Return true if aTagName is one of the format element name of
   * cmd_paragraphState.
   */

  [[nodiscard]] static bool IsFormatTagForParagraphStateCommand(
      const nsStaticAtom& aTagName) {
    return
        // clang-format off
        &aTagName == nsGkAtoms::address ||
        &aTagName == nsGkAtoms::dd ||
        &aTagName == nsGkAtoms::dl ||
        &aTagName == nsGkAtoms::dt ||
        &aTagName == nsGkAtoms::h1 ||
        &aTagName == nsGkAtoms::h2 ||
        &aTagName == nsGkAtoms::h3 ||
        &aTagName == nsGkAtoms::h4 ||
        &aTagName == nsGkAtoms::h5 ||
        &aTagName == nsGkAtoms::h6 ||
        &aTagName == nsGkAtoms::p ||
        &aTagName == nsGkAtoms::pre;
    // clang-format on
  }

  /**
   * Return true if aContent is a format element of cmd_paragraphState.
   */

  [[nodiscard]] static bool IsFormatElementForParagraphStateCommand(
      const nsIContent& aContent) {
    if (!aContent.IsHTMLElement() ||
        !aContent.NodeInfo()->NameAtom()->IsStatic()) {
      return false;
    }
    const nsStaticAtom* tagName = aContent.NodeInfo()->NameAtom()->AsStatic();
    return IsFormatTagForParagraphStateCommand(*tagName);
  }

  static bool IsNodeThatCanOutdent(nsINode* aNode);
  static bool IsHeader(nsINode& aNode);
  static bool IsListItem(const nsINode* aNode);
  static bool IsTable(nsINode* aNode);
  static bool IsTableRow(nsINode* aNode);
  static bool IsAnyTableElement(const nsINode* aNode);
  static bool IsAnyTableElementButNotTable(nsINode* aNode);
  static bool IsTableCell(const nsINode* aNode);
  static bool IsTableCellOrCaption(nsINode& aNode);
  static bool IsAnyListElement(const nsINode* aNode);
  static bool IsPre(const nsINode* aNode);
  static bool IsImage(nsINode* aNode);
  static bool IsLink(const nsINode* aNode);
  static bool IsNamedAnchor(const nsINode* aNode);
  static bool IsMozDiv(nsINode* aNode);
  static bool IsMailCite(const Element& aElement);
  static bool IsFormWidget(const nsINode* aNode);
  static bool SupportsAlignAttr(nsINode& aNode);

  static bool CanNodeContain(const nsINode& aParent, const nsIContent& aChild) {
    switch (aParent.NodeType()) {
      case nsINode::ELEMENT_NODE:
      case nsINode::DOCUMENT_FRAGMENT_NODE:
        return HTMLEditUtils::CanNodeContain(*aParent.NodeInfo()->NameAtom(),
                                             aChild);
    }
    return false;
  }

  static bool CanNodeContain(const nsINode& aParent,
                             const nsAtom& aChildNodeName) {
    switch (aParent.NodeType()) {
      case nsINode::ELEMENT_NODE:
      case nsINode::DOCUMENT_FRAGMENT_NODE:
        return HTMLEditUtils::CanNodeContain(*aParent.NodeInfo()->NameAtom(),
                                             aChildNodeName);
    }
    return false;
  }

  static bool CanNodeContain(const nsAtom& aParentNodeName,
                             const nsIContent& aChild) {
    switch (aChild.NodeType()) {
      case nsINode::TEXT_NODE:
      case nsINode::COMMENT_NODE:
      case nsINode::CDATA_SECTION_NODE:
      case nsINode::ELEMENT_NODE:
      case nsINode::DOCUMENT_FRAGMENT_NODE:
        return HTMLEditUtils::CanNodeContain(aParentNodeName,
                                             *aChild.NodeInfo()->NameAtom());
    }
    return false;
  }

  // XXX Only this overload does not check the node type.  Therefore, only this
  //     handle Document and ProcessingInstructionTagName.
  static bool CanNodeContain(const nsAtom& aParentNodeName,
                             const nsAtom& aChildNodeName) {
    nsHTMLTag childTagEnum;
    if (&aChildNodeName == nsGkAtoms::textTagName) {
      childTagEnum = eHTMLTag_text;
    } else if (&aChildNodeName == nsGkAtoms::commentTagName ||
               &aChildNodeName == nsGkAtoms::cdataTagName) {
      childTagEnum = eHTMLTag_comment;
    } else {
      childTagEnum =
          nsHTMLTags::AtomTagToId(const_cast<nsAtom*>(&aChildNodeName));
    }

    nsHTMLTag parentTagEnum =
        nsHTMLTags::AtomTagToId(const_cast<nsAtom*>(&aParentNodeName));
    return HTMLEditUtils::CanNodeContain(parentTagEnum, childTagEnum);
  }

  /**
   * CanElementContainParagraph() returns true if aElement can have a <p>
   * element as its child or its descendant.
   */

  static bool CanElementContainParagraph(const Element& aElement) {
    if (HTMLEditUtils::CanNodeContain(aElement, *nsGkAtoms::p)) {
      return true;
    }

    // Even if the element cannot have a <p> element as a child, it can contain
    // <p> element as a descendant if it's one of the following elements.
    if (aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul,
                                     nsGkAtoms::dl, nsGkAtoms::table,
                                     nsGkAtoms::thead, nsGkAtoms::tbody,
                                     nsGkAtoms::tfoot, nsGkAtoms::tr)) {
      return true;
    }

    // XXX Otherwise, Chromium checks the CSS box is a block, but we don't do it
    //     for now.
    return false;
  }

  /**
   * Return a point which can insert a node whose name is aTagName scanning
   * from aPoint to its ancestor points.
   */

  template <typename EditorDOMPointType>
  static EditorDOMPoint GetInsertionPointInInclusiveAncestor(
      const nsAtom& aTagName, const EditorDOMPointType& aPoint,
      const Element* aAncestorLimit = nullptr) {
    if (MOZ_UNLIKELY(!aPoint.IsInContentNode())) {
      return EditorDOMPoint();
    }
    Element* lastChild = nullptr;
    for (Element* containerElement :
         aPoint.template ContainerAs<nsIContent>()
             ->template InclusiveAncestorsOfType<Element>()) {
      if (!HTMLEditUtils::IsSimplyEditableNode(*containerElement)) {
        return EditorDOMPoint();
      }
      if (HTMLEditUtils::CanNodeContain(*containerElement, aTagName)) {
        return lastChild ? EditorDOMPoint(lastChild)
                         : aPoint.template To<EditorDOMPoint>();
      }
      if (containerElement == aAncestorLimit) {
        return EditorDOMPoint();
      }
      lastChild = containerElement;
    }
    return EditorDOMPoint();
  }

  /**
   * IsContainerNode() returns true if aContent is a container node.
   */

  static bool IsContainerNode(const nsIContent& aContent) {
    nsHTMLTag tagEnum;
    // XXX Should this handle #cdata-section too?
    if (aContent.IsText()) {
      tagEnum = eHTMLTag_text;
    } else {
      // XXX Why don't we use nsHTMLTags::AtomTagToId?  Are there some
      //     difference?
      tagEnum = nsHTMLTags::StringTagToId(aContent.NodeName());
    }
    return HTMLEditUtils::IsContainerNode(tagEnum);
  }

  /**
   * IsSplittableNode() returns true if aContent can split.
   */

  static bool IsSplittableNode(const nsIContent& aContent) {
    if (!EditorUtils::IsEditableContent(aContent,
                                        EditorUtils::EditorType::HTML) ||
        !HTMLEditUtils::IsRemovableFromParentNode(aContent)) {
      return false;
    }
    if (aContent.IsElement()) {
      // XXX Perhaps, instead of using container, we should have "splittable"
      //     information in the DB.  E.g., `<template>`, `<script>` elements
      //     can have children, but shouldn't be split.
      return HTMLEditUtils::IsContainerNode(aContent) &&
             !aContent.IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::button,
                                           nsGkAtoms::caption, nsGkAtoms::table,
                                           nsGkAtoms::tbody, nsGkAtoms::tfoot,
                                           nsGkAtoms::thead, nsGkAtoms::tr) &&
             !HTMLEditUtils::IsNeverElementContentsEditableByUser(aContent) &&
             !HTMLEditUtils::IsNonEditableReplacedContent(aContent);
    }
    return aContent.IsText() && aContent.Length() > 0;
  }

  /**
   * See execCommand spec:
   * https://w3c.github.io/editing/execCommand.html#non-list-single-line-container
   * https://w3c.github.io/editing/execCommand.html#single-line-container
   */

  static bool IsNonListSingleLineContainer(const nsINode& aNode);
  static bool IsSingleLineContainer(const nsINode& aNode);

  /**
   * Return true if aText has only a linefeed and it's preformatted.
   */

  [[nodiscard]] static bool TextHasOnlyOnePreformattedLinefeed(
      const Text& aText) {
    return aText.TextDataLength() == 1u &&
           aText.TextFragment().CharAt(0u) == kNewLine &&
           EditorUtils::IsNewLinePreformatted(aText);
  }

  /**
   * IsVisibleTextNode() returns true if aText has visible text.  If it has
   * only white-spaces and they are collapsed, returns false.
   */

  [[nodiscard]] static bool IsVisibleTextNode(const Text& aText);

  /**
   * IsInVisibleTextFrames() returns true if any text in aText is in visible
   * text frames.  Callers have to guarantee that there is no pending reflow.
   */

  static bool IsInVisibleTextFrames(nsPresContext* aPresContext,
                                    const Text& aText);

  /**
   * IsVisibleBRElement() and IsInvisibleBRElement() return true if aContent is
   * a visible HTML <br> element, i.e., not a padding <br> element for making
   * last line in a block element visible, or an invisible <br> element.
   */

  static bool IsVisibleBRElement(const nsIContent& aContent) {
    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(
        aBRElement, WalkTreeDirection::Forward);
  }
  static bool IsInvisibleBRElement(const nsIContent& 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);
  }

  enum class IgnoreInvisibleLineBreak { No, Yes };

  /**
   * Return true if aPoint is immediately before current block boundary.  If
   * aIgnoreInvisibleLineBreak is "Yes", this returns true if aPoint is before
   * invisible line break before a block boundary.
   */

  template <typename PT, typename CT>
  [[nodiscard]] static bool PointIsImmediatelyBeforeCurrentBlockBoundary(
      const EditorDOMPointBase<PT, CT>& aPoint,
      IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak);

  /**
   * Return true if aRange crosses the inclusive ancestor block element at
   * start boundary, in other words, if aRange ends outside of the inclusive
   * ancestor block of the start boundary.
   */

  template <typename EditorDOMPointType>
  [[nodiscard]] static bool RangeIsAcrossStartBlockBoundary(
      const EditorDOMRangeBase<EditorDOMPointType>& aRange) {
    MOZ_ASSERT(aRange.IsPositionedAndValid());
    if (MOZ_UNLIKELY(!aRange.StartRef().IsInContentNode())) {
      return false;
    }
    const Element* const startBlockElement =
        HTMLEditUtils::GetInclusiveAncestorElement(
            *aRange.StartRef().template ContainerAs<nsIContent>(),
            ClosestBlockElement,
            BlockInlineCheck::UseComputedDisplayOutsideStyle);
    if (MOZ_UNLIKELY(!startBlockElement)) {
      return false;
    }
    return EditorRawDOMPoint::After(*startBlockElement)
        .EqualsOrIsBefore(aRange.EndRef());
  }

  /**
   * Return true if `display` of inclusive ancestor of aContent is `none`.
   */

  static bool IsInclusiveAncestorCSSDisplayNone(const nsIContent& aContent);

  /**
   * IsVisiblePreformattedNewLine() and IsInvisiblePreformattedNewLine() return
   * true if the point is preformatted linefeed and it's visible or invisible.
   * If linefeed is immediately before a block boundary, it's invisible.
   *
   * @param aFollowingBlockElement  [out] If the node is followed by a block
   *                                boundary, this is set to the element
   *                                creating the block boundary.
   */

  template <typename EditorDOMPointType>
  static bool IsVisiblePreformattedNewLine(
      const EditorDOMPointType& aPoint,
      Element** aFollowingBlockElement = nullptr) {
    if (aFollowingBlockElement) {
      *aFollowingBlockElement = nullptr;
    }
    if (!aPoint.IsInTextNode() || aPoint.IsEndOfContainer() ||
        !aPoint.IsCharPreformattedNewLine()) {
      return false;
    }
    // If there are some other characters in the text node, it's a visible
    // linefeed.
    if (!aPoint.IsAtLastContent()) {
      if (EditorUtils::IsWhiteSpacePreformatted(
              *aPoint.template ContainerAs<Text>())) {
        return true;
      }
      const nsTextFragment& textFragment =
          aPoint.template ContainerAs<Text>()->TextFragment();
      for (uint32_t offset = aPoint.Offset() + 1;
           offset < textFragment.GetLength(); ++offset) {
        char16_t ch = textFragment.CharAt(AssertedCast<int32_t>(offset));
        if (nsCRT::IsAsciiSpace(ch) && ch != HTMLEditUtils::kNewLine) {
          continue;  // ASCII white-space which is collapsed into the linefeed.
        }
        return true;  // There is a visible character after it.
      }
    }
    // If followed by a block boundary without visible content, it's invisible
    // linefeed.
    Element* followingBlockElement =
        HTMLEditUtils::GetElementOfImmediateBlockBoundary(
            *aPoint.template ContainerAs<Text>(), WalkTreeDirection::Forward);
    if (aFollowingBlockElement) {
      *aFollowingBlockElement = followingBlockElement;
    }
    return !followingBlockElement;
  }
  template <typename EditorDOMPointType>
  static bool IsInvisiblePreformattedNewLine(
      const EditorDOMPointType& aPoint,
      Element** aFollowingBlockElement = nullptr) {
    if (!aPoint.IsInTextNode() || aPoint.IsEndOfContainer() ||
        !aPoint.IsCharPreformattedNewLine()) {
      if (aFollowingBlockElement) {
        *aFollowingBlockElement = nullptr;
      }
      return false;
    }
    return !IsVisiblePreformattedNewLine(aPoint, aFollowingBlockElement);
  }

  /**
   * Return a point to insert a padding line break if aPoint is following a
   * collapsible ASCII white-space or a block boundary and the line containing
   * aPoint requires a following padding line break which there is not.
   */

  template <typename PT, typename CT>
  static EditorDOMPoint LineRequiresPaddingLineBreakToBeVisible(
      const EditorDOMPointBase<PT, CT>& aPoint, const Element& aEditingHost);

  /**
   * ShouldInsertLinefeedCharacter() returns true if the caller should insert
   * a linefeed character instead of <br> element.
   */

  static bool ShouldInsertLinefeedCharacter(
      const EditorDOMPoint& aPointToInsert, const Element& aEditingHost);

  /**
   * IsEmptyNode() returns false if aNode has some visible content nodes,
   * list elements or table elements.
   *
   * @param aPresContext    Must not be nullptr if
   *                        EmptyCheckOption::SafeToAskLayout is set.
   * @param aNode           The node to check whether it's empty.
   * @param aOptions        You can specify which type of elements are visible
   *                        and/or whether this can access layout information.
   * @param aSeenBR         [Out] Set to true if this meets an <br> element
   *                        before meeting visible things.
   */

  enum class EmptyCheckOption {
    TreatSingleBRElementAsVisible,
    TreatBlockAsVisible,
    TreatListItemAsVisible,
    TreatTableCellAsVisible,
    TreatNonEditableContentAsInvisible,
    SafeToAskLayout,
  };
  using EmptyCheckOptions = EnumSet<EmptyCheckOption, uint32_t>;
  static bool IsEmptyNode(nsPresContext* aPresContext, const nsINode& aNode,
                          const EmptyCheckOptions& aOptions = {},
                          bool* aSeenBR = nullptr);
  static bool IsEmptyNode(const nsINode& aNode,
                          const EmptyCheckOptions& aOptions = {},
                          bool* aSeenBR = nullptr) {
    MOZ_ASSERT(!aOptions.contains(EmptyCheckOption::SafeToAskLayout));
    return IsEmptyNode(nullptr, aNode, aOptions, aSeenBR);
  }

  /**
   * IsEmptyInlineContainer() returns true if aContent is an inline element
   * which can have children and does not have meaningful content.
   */

  static bool IsEmptyInlineContainer(const nsIContent& aContent,
                                     const EmptyCheckOptions& aOptions,
                                     BlockInlineCheck aBlockInlineCheck) {
    return HTMLEditUtils::IsInlineContent(aContent, aBlockInlineCheck) &&
           HTMLEditUtils::IsContainerNode(aContent) &&
           HTMLEditUtils::IsEmptyNode(aContent, aOptions);
  }

  /**
   * IsEmptyBlockElement() returns true if aElement is a block level element
   * and it doesn't have any visible content.
   */

  static bool IsEmptyBlockElement(const Element& aElement,
                                  const EmptyCheckOptions& aOptions,
                                  BlockInlineCheck aBlockInlineCheck) {
    return HTMLEditUtils::IsBlockElement(aElement, aBlockInlineCheck) &&
           HTMLEditUtils::IsEmptyNode(aElement, aOptions);
  }

  /**
   * Return true if aListElement is completely empty or it has only one list
   * item element which is empty.
   */

  [[nodiscard]] static bool IsEmptyAnyListElement(const Element& aListElement) {
    MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement));
    bool foundListItem = false;
    for (nsIContent* child = aListElement.GetFirstChild(); child;
         child = child->GetNextSibling()) {
      if (HTMLEditUtils::IsListItem(child)) {
        if (foundListItem) {
          return false;  // 2 list items found.
        }
        if (!IsEmptyNode(*child, {})) {
          return false;  // found non-empty list item.
        }
        foundListItem = true;
        continue;
      }
      if (child->IsElement()) {
        return false;  // found sublist or illegal child.
      }
      if (child->IsText() &&
          HTMLEditUtils::IsVisibleTextNode(*child->AsText())) {
        return false;  // found illegal visible text node.
      }
    }
    return true;
  }

  /**
   * Return true if aListElement does not have invalid child.
   */

  enum class TreatSubListElementAs { Invalid, Valid };
  [[nodiscard]] static bool IsValidListElement(
      const Element& aListElement,
      TreatSubListElementAs aTreatSubListElementAs) {
    MOZ_ASSERT(HTMLEditUtils::IsAnyListElement(&aListElement));
    for (nsIContent* child = aListElement.GetFirstChild(); child;
         child = child->GetNextSibling()) {
      if (HTMLEditUtils::IsAnyListElement(child)) {
        if (aTreatSubListElementAs == TreatSubListElementAs::Invalid) {
          return false;
        }
        continue;
      }
      if (child->IsHTMLElement(nsGkAtoms::li)) {
        if (MOZ_UNLIKELY(!aListElement.IsAnyOfHTMLElements(nsGkAtoms::ol,
                                                           nsGkAtoms::ul))) {
          return false;
        }
        continue;
      }
      if (child->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd)) {
        if (MOZ_UNLIKELY(!aListElement.IsAnyOfHTMLElements(nsGkAtoms::dl))) {
          return false;
        }
        continue;
      }
      if (MOZ_UNLIKELY(child->IsElement())) {
        return false;
      }
      if (MOZ_LIKELY(child->IsText())) {
        if (MOZ_UNLIKELY(HTMLEditUtils::IsVisibleTextNode(*child->AsText()))) {
          return false;
        }
      }
    }
    return true;
  }

  /**
   * IsEmptyOneHardLine() returns true if aArrayOfContents does not represent
   * 2 or more lines and have meaningful content.
   */

  static bool IsEmptyOneHardLine(
      nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
      BlockInlineCheck aBlockInlineCheck) {
    if (NS_WARN_IF(aArrayOfContents.IsEmpty())) {
      return true;
    }

    bool brElementHasFound = false;
    for (OwningNonNull<nsIContent>& content : aArrayOfContents) {
      if (!EditorUtils::IsEditableContent(content,
                                          EditorUtils::EditorType::HTML)) {
        continue;
      }
      if (content->IsHTMLElement(nsGkAtoms::br)) {
        // If there are 2 or more `<br>` elements, it's not empty line since
        // there may be only one `<br>` element in a hard line.
        if (brElementHasFound) {
          return false;
        }
        brElementHasFound = true;
        continue;
      }
      if (!HTMLEditUtils::IsEmptyInlineContainer(
              content,
              {EmptyCheckOption::TreatSingleBRElementAsVisible,
               EmptyCheckOption::TreatNonEditableContentAsInvisible},
              aBlockInlineCheck)) {
        return false;
      }
    }
    return true;
  }

  /**
   * IsPointAtEdgeOfLink() returns true if aPoint is at start or end of a
   * link.
   */

  template <typename PT, typename CT>
  static bool IsPointAtEdgeOfLink(const EditorDOMPointBase<PT, CT>& aPoint,
                                  Element** aFoundLinkElement = nullptr) {
    if (aFoundLinkElement) {
      *aFoundLinkElement = nullptr;
    }
    if (!aPoint.IsInContentNode()) {
      return false;
    }
    if (!aPoint.IsStartOfContainer() && !aPoint.IsEndOfContainer()) {
      return false;
    }
    // XXX Assuming it's not in an empty text node because it's unrealistic edge
    //     case.
    bool maybeStartOfAnchor = aPoint.IsStartOfContainer();
    for (EditorRawDOMPoint point(aPoint.template ContainerAs<nsIContent>());
         point.IsSet() && (maybeStartOfAnchor ? point.IsStartOfContainer()
                                              : point.IsAtLastContent());
         point = point.ParentPoint()) {
      if (HTMLEditUtils::IsLink(point.GetContainer())) {
        // Now, we're at start or end of <a href>.
        if (aFoundLinkElement) {
          *aFoundLinkElement =
              do_AddRef(point.template ContainerAs<Element>()).take();
        }
        return true;
      }
    }
    return false;
  }

  /**
   * IsContentInclusiveDescendantOfLink() returns true if aContent is a
   * descendant of a link element.
   * Note that this returns true even if editing host of aContent is in a link
   * element.
   */

  static bool IsContentInclusiveDescendantOfLink(
      nsIContent& aContent, Element** aFoundLinkElement = nullptr) {
    if (aFoundLinkElement) {
      *aFoundLinkElement = nullptr;
    }
    for (Element* element : aContent.InclusiveAncestorsOfType<Element>()) {
      if (HTMLEditUtils::IsLink(element)) {
        if (aFoundLinkElement) {
          *aFoundLinkElement = do_AddRef(element).take();
        }
        return true;
      }
    }
    return false;
  }

  /**
   * IsRangeEntirelyInLink() returns true if aRange is entirely in a link
   * element.
   * Note that this returns true even if editing host of the range is in a link
   * element.
   */

  template <typename EditorDOMRangeType>
  static bool IsRangeEntirelyInLink(const EditorDOMRangeType& aRange,
                                    Element** aFoundLinkElement = nullptr) {
    MOZ_ASSERT(aRange.IsPositionedAndValid());
    if (aFoundLinkElement) {
      *aFoundLinkElement = nullptr;
    }
    nsINode* commonAncestorNode =
        nsContentUtils::GetClosestCommonInclusiveAncestor(
            aRange.StartRef().GetContainer(), aRange.EndRef().GetContainer());
    if (NS_WARN_IF(!commonAncestorNode) || !commonAncestorNode->IsContent()) {
      return false;
    }
    return IsContentInclusiveDescendantOfLink(*commonAncestorNode->AsContent(),
                                              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 aBlockInlineCheck   Whether considering block vs. inline with the
   *                            computed style or the HTML default style.
   * @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.
    IgnoreWhiteSpaceOnlyText,  // Ignore text nodes having only white-spaces.
    StopAtBlockBoundary,       // Stop waking the tree at a block boundary.
  };
  using WalkTreeOptions = EnumSet<WalkTreeOption>;
  static nsIContent* GetPreviousContent(
      const nsINode& aNode, const WalkTreeOptions& aOptions,
      BlockInlineCheck aBlockInlineCheck,
      const Element* aAncestorLimiter = nullptr) {
    if (&aNode == aAncestorLimiter ||
        (aAncestorLimiter &&
         !aNode.IsInclusiveDescendantOf(aAncestorLimiter))) {
      return nullptr;
    }
    return HTMLEditUtils::GetAdjacentContent(aNode, WalkTreeDirection::Backward,
                                             aOptions, aBlockInlineCheck,
                                             aAncestorLimiter);
  }
  static nsIContent* GetNextContent(const nsINode& aNode,
                                    const WalkTreeOptions& aOptions,
                                    BlockInlineCheck aBlockInlineCheck,
                                    const Element* aAncestorLimiter = nullptr) {
    if (&aNode == aAncestorLimiter ||
        (aAncestorLimiter &&
         !aNode.IsInclusiveDescendantOf(aAncestorLimiter))) {
      return nullptr;
    }
    return HTMLEditUtils::GetAdjacentContent(aNode, WalkTreeDirection::Forward,
                                             aOptions, aBlockInlineCheck,
                                             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,
      BlockInlineCheck aBlockInlineCheck,
      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,
                                    BlockInlineCheck aBlockInlineCheck,
                                    const Element* aAncestorLimiter = nullptr);

  /**
   * GetPreviousSibling() return the preceding sibling of aContent which matches
   * with aOption.
   *
   * @param aBlockInlineCheck   Can be Unused if aOptions does not contain
   *                            StopAtBlockBoundary.
   */

  static nsIContent* GetPreviousSibling(
      const nsIContent& aContent, const WalkTreeOptions& aOptions,
      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
    for (nsIContent* sibling = aContent.GetPreviousSibling(); sibling;
         sibling = sibling->GetPreviousSibling()) {
      if (HTMLEditUtils::IsContentIgnored(*sibling, aOptions)) {
        continue;
      }
      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
          HTMLEditUtils::IsBlockElement(*sibling, aBlockInlineCheck)) {
        return nullptr;
      }
      return sibling;
    }
    return nullptr;
  }

  /**
   * GetNextSibling() return the following sibling of aContent which matches
   * with aOption.
   *
   * @param aBlockInlineCheck   Can be Unused if aOptions does not contain
   *                            StopAtBlockBoundary.
   */

  static nsIContent* GetNextSibling(
      const nsIContent& aContent, const WalkTreeOptions& aOptions,
      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
    for (nsIContent* sibling = aContent.GetNextSibling(); sibling;
         sibling = sibling->GetNextSibling()) {
      if (HTMLEditUtils::IsContentIgnored(*sibling, aOptions)) {
        continue;
      }
      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
          HTMLEditUtils::IsBlockElement(*sibling, aBlockInlineCheck)) {
        return nullptr;
      }
      return sibling;
    }
    return nullptr;
  }

  /**
   * Return the last child of aNode which matches with aOption.
   *
   * @param aBlockInlineCheck   Can be unused if aOptions does not contain
   *                            StopAtBlockBoundary.
   */

  static nsIContent* GetLastChild(
      const nsINode& aNode, const WalkTreeOptions& aOptions,
      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
    for (nsIContent* child = aNode.GetLastChild(); child;
         child = child->GetPreviousSibling()) {
      if (HTMLEditUtils::IsContentIgnored(*child, aOptions)) {
        continue;
      }
      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
          HTMLEditUtils::IsBlockElement(*child, aBlockInlineCheck)) {
        return nullptr;
      }
      return child;
    }
    return nullptr;
  }

  /**
   * Return the first child of aNode which matches with aOption.
   *
   * @param aBlockInlineCheck   Can be unused if aOptions does not contain
   *                            StopAtBlockBoundary.
   */

  static nsIContent* GetFirstChild(
      const nsINode& aNode, const WalkTreeOptions& aOptions,
      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
    for (nsIContent* child = aNode.GetFirstChild(); child;
         child = child->GetNextSibling()) {
      if (HTMLEditUtils::IsContentIgnored(*child, aOptions)) {
        continue;
      }
      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
          HTMLEditUtils::IsBlockElement(*child, aBlockInlineCheck)) {
        return nullptr;
      }
      return child;
    }
    return nullptr;
  }

  /**
   * Return true if aContent is the last child of aNode with ignoring all
   * children which do not match with aOption.
   *
   * @param aBlockInlineCheck   Can be unused if aOptions does not contain
   *                            StopAtBlockBoundary.
   */

  static bool IsLastChild(
      const nsIContent& aContent, const WalkTreeOptions& aOptions,
      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
    nsINode* parentNode = aContent.GetParentNode();
    if (!parentNode) {
      return false;
    }
    return HTMLEditUtils::GetLastChild(*parentNode, aOptions,
                                       aBlockInlineCheck) == &aContent;
  }

  /**
   * Return true if aContent is the first child of aNode with ignoring all
   * children which do not match with aOption.
   *
   * @param aBlockInlineCheck   Can be unused if aOptions does not contain
   *                            StopAtBlockBoundary.
   */

  static bool IsFirstChild(
      const nsIContent& aContent, const WalkTreeOptions& aOptions,
      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
    nsINode* parentNode = aContent.GetParentNode();
    if (!parentNode) {
      return false;
    }
    return HTMLEditUtils::GetFirstChild(*parentNode, aOptions,
                                        aBlockInlineCheck) == &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},
          BlockInlineCheck::UseComputedDisplayStyle, &aEditingHost);
      if (!editableContent) {
        return nullptr;  // Not illegal.
      }
    } else {
      editableContent = HTMLEditUtils::GetNextContent(
          aPoint, {WalkTreeOption::IgnoreNonEditableNode},
          BlockInlineCheck::UseComputedDisplayStyle, &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},
            BlockInlineCheck::UseComputedDisplayStyle, &aEditingHost);
        if (NS_WARN_IF(!editableContent)) {
          return nullptr;
        }
      } else {
        editableContent = HTMLEditUtils::GetNextContent(
            *editableContent, {WalkTreeOption::IgnoreNonEditableNode},
            BlockInlineCheck::UseComputedDisplayStyle, &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.template ContainerAs<nsIContent>()))) {
      return nullptr;
    }

    // otherwise, ok, we have found a good spot to put the selection
    return editableContent;
  }

  enum class LeafNodeType {
    // Even if there is a child block, keep scanning a leaf content in it.
    OnlyLeafNode,
    // If there is a child block, return it too.  Note that this does not
    // mean that block siblings are not treated as leaf nodes.
    LeafNodeOrChildBlock,
    // If there is a non-editable element if and only if scanning from editable
    // node, return it too.
    LeafNodeOrNonEditableNode,
    // Ignore non-editable content at walking the tree.
    OnlyEditableLeafNode,
  };
  using LeafNodeTypes = EnumSet<LeafNodeType>;

  /**
   * GetLastLeafContent() returns rightmost leaf content in aNode.  It depends
   * on aLeafNodeTypes whether this which types of nodes are treated as leaf
   * nodes.
   *
   * @param aBlockInlineCheck   Can be Unused if aLeafNodeTypes does not contain
   *                            LeafNodeOrCHildBlock.
   */

  static nsIContent* GetLastLeafContent(
      const nsINode& aNode, const LeafNodeTypes& aLeafNodeTypes,
      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused,
      const Element* aAncestorLimiter = nullptr) {
    MOZ_ASSERT_IF(
        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
    // editor shouldn't touch child nodes which are replaced with native
    // anonymous nodes.
    if (aNode.IsElement() &&
        HTMLEditUtils::IsNeverElementContentsEditableByUser(
            *aNode.AsElement())) {
      return nullptr;
    }
    for (nsIContent* content = aNode.GetLastChild(); content;) {
      if (aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode) &&
          !EditorUtils::IsEditableContent(*content,
                                          EditorUtils::EditorType::HTML)) {
        content = HTMLEditUtils::GetPreviousContent(
            *content, {WalkTreeOption::IgnoreNonEditableNode},
            aBlockInlineCheck, aAncestorLimiter);
        continue;
      }
      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) &&
          HTMLEditUtils::IsBlockElement(*content, aBlockInlineCheck)) {
        return content;
      }
      if (!content->HasChildren() ||
          HTMLEditUtils::IsNeverElementContentsEditableByUser(*content)) {
        return content;
      }
      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
          !HTMLEditUtils::IsSimplyEditableNode(*content)) {
        return content;
      }
      content = content->GetLastChild();
    }
    return nullptr;
  }

  /**
   * GetFirstLeafContent() returns leftmost leaf content in aNode.  It depends
   * on aLeafNodeTypes whether this scans into a block child or treat block as a
   * leaf.
   *
   * @param aBlockInlineCheck   Can be Unused if aLeafNodeTypes does not contain
   *                            LeafNodeOrCHildBlock.
   */

  static nsIContent* GetFirstLeafContent(
      const nsINode& aNode, const LeafNodeTypes& aLeafNodeTypes,
      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused,
      const Element* aAncestorLimiter = nullptr) {
    MOZ_ASSERT_IF(
        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
    // editor shouldn't touch child nodes which are replaced with native
    // anonymous nodes.
    if (aNode.IsElement() &&
        HTMLEditUtils::IsNeverElementContentsEditableByUser(
            *aNode.AsElement())) {
      return nullptr;
    }
    for (nsIContent* content = aNode.GetFirstChild(); content;) {
      if (aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode) &&
          !EditorUtils::IsEditableContent(*content,
                                          EditorUtils::EditorType::HTML)) {
        content = HTMLEditUtils::GetNextContent(
            *content, {WalkTreeOption::IgnoreNonEditableNode},
            aBlockInlineCheck, aAncestorLimiter);
        continue;
      }
      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) &&
          HTMLEditUtils::IsBlockElement(*content, aBlockInlineCheck)) {
        return content;
      }
      if (!content->HasChildren() ||
          HTMLEditUtils::IsNeverElementContentsEditableByUser(*content)) {
        return content;
      }
      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
          !HTMLEditUtils::IsSimplyEditableNode(*content)) {
        return content;
      }
      content = content->GetFirstChild();
    }
    return nullptr;
  }

  /**
   * GetNextLeafContentOrNextBlockElement() returns next leaf content or
   * next block element of aStartContent inside aAncestorLimiter.
   *
   * @param aStartContent       The start content to scan next content.
   * @param aLeafNodeTypes      See LeafNodeType.
   * @param aAncestorLimiter    Optional, if you set this, it must be an
   *                            inclusive ancestor of aStartContent.
   */

  static nsIContent* GetNextLeafContentOrNextBlockElement(
      const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes,
      BlockInlineCheck aBlockInlineCheck,
      const Element* aAncestorLimiter = nullptr) {
    MOZ_ASSERT_IF(
        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));

    if (&aStartContent == aAncestorLimiter) {
      return nullptr;
    }

    nsIContent* nextContent = aStartContent.GetNextSibling();
    if (!nextContent) {
      if (!aStartContent.GetParentElement()) {
        NS_WARNING("Reached orphan node while climbing up the DOM tree");
        return nullptr;
      }
      for (Element* parentElement : aStartContent.AncestorsOfType<Element>()) {
        if (parentElement == aAncestorLimiter ||
            HTMLEditUtils::IsBlockElement(*parentElement, aBlockInlineCheck)) {
          return nullptr;
        }
        if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
            !parentElement->IsEditable()) {
          return nullptr;
        }
        nextContent = parentElement->GetNextSibling();
        if (nextContent) {
          break;
        }
        if (!parentElement->GetParentElement()) {
          NS_WARNING("Reached orphan node while climbing up the DOM tree");
          return nullptr;
        }
      }
      MOZ_ASSERT(nextContent);
      aBlockInlineCheck = IgnoreInsideBlockBoundary(aBlockInlineCheck);
    }

    // We have a next content.  If it's a block, return it.
    if (HTMLEditUtils::IsBlockElement(*nextContent, aBlockInlineCheck)) {
      return nextContent;
    }
    if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
        !nextContent->IsEditable()) {
      return nextContent;
    }
    if (HTMLEditUtils::IsContainerNode(*nextContent)) {
      // Else if it's a container, get deep leftmost child
      if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
              *nextContent, aLeafNodeTypes, aBlockInlineCheck)) {
        return child;
      }
    }
    // Else return the next content itself.
    return nextContent;
  }

  /**
   * Similar to the above method, but take a DOM point to specify scan start
   * point.
   */

  template <typename PT, typename CT>
  static nsIContent* GetNextLeafContentOrNextBlockElement(
      const EditorDOMPointBase<PT, CT>& aStartPoint,
      const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
      const Element* aAncestorLimiter = nullptr) {
    MOZ_ASSERT(aStartPoint.IsSet());
    MOZ_ASSERT_IF(
        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
    NS_ASSERTION(!aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
                 "Not implemented yet");

    if (!aStartPoint.IsInContentNode()) {
      return nullptr;
    }
    if (aStartPoint.IsInTextNode()) {
      return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
          *aStartPoint.template ContainerAs<Text>(), aLeafNodeTypes,
          aBlockInlineCheck, aAncestorLimiter);
    }
    if (!HTMLEditUtils::IsContainerNode(
            *aStartPoint.template ContainerAs<nsIContent>())) {
      return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
          *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
          aBlockInlineCheck, aAncestorLimiter);
    }

    nsCOMPtr<nsIContent> nextContent = aStartPoint.GetChild();
    if (!nextContent) {
      if (aStartPoint.GetContainer() == aAncestorLimiter ||
          HTMLEditUtils::IsBlockElement(
              *aStartPoint.template ContainerAs<nsIContent>(),
              aBlockInlineCheck)) {
        // We are at end of the block.
        return nullptr;
      }

      // We are at end of non-block container
      return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
          *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
          IgnoreInsideBlockBoundary(aBlockInlineCheck), aAncestorLimiter);
    }

    // We have a next node.  If it's a block, return it.
    if (HTMLEditUtils::IsBlockElement(*nextContent, aBlockInlineCheck)) {
      return nextContent;
    }
    if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
        !HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
      return nextContent;
    }
    if (HTMLEditUtils::IsContainerNode(*nextContent)) {
      // else if it's a container, get deep leftmost child
      if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
              *nextContent, aLeafNodeTypes,
              IgnoreInsideBlockBoundary(aBlockInlineCheck))) {
        return child;
      }
    }
    // Else return the node itself
    return nextContent;
  }

  /**
   * GetPreviousLeafContentOrPreviousBlockElement() returns previous leaf
   * content or previous block element of aStartContent inside
   * aAncestorLimiter.
   *
   * @param aStartContent       The start content to scan previous content.
   * @param aLeafNodeTypes      See LeafNodeType.
   * @param aAncestorLimiter    Optional, if you set this, it must be an
   *                            inclusive ancestor of aStartContent.
   */

  static nsIContent* GetPreviousLeafContentOrPreviousBlockElement(
      const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes,
      BlockInlineCheck aBlockInlineCheck,
      const Element* aAncestorLimiter = nullptr) {
    MOZ_ASSERT_IF(
        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
    NS_ASSERTION(!aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
                 "Not implemented yet");

    if (&aStartContent == aAncestorLimiter) {
      return nullptr;
    }

    nsIContent* previousContent = aStartContent.GetPreviousSibling();
    if (!previousContent) {
      if (!aStartContent.GetParentElement()) {
        NS_WARNING("Reached orphan node while climbing up the DOM tree");
        return nullptr;
      }
      for (Element* parentElement : aStartContent.AncestorsOfType<Element>()) {
        if (parentElement == aAncestorLimiter ||
            HTMLEditUtils::IsBlockElement(*parentElement, aBlockInlineCheck)) {
          return nullptr;
        }
        if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
            !parentElement->IsEditable()) {
          return nullptr;
        }
        previousContent = parentElement->GetPreviousSibling();
        if (previousContent) {
          break;
        }
        if (!parentElement->GetParentElement()) {
          NS_WARNING("Reached orphan node while climbing up the DOM tree");
          return nullptr;
        }
      }
      MOZ_ASSERT(previousContent);
      aBlockInlineCheck = IgnoreInsideBlockBoundary(aBlockInlineCheck);
    }

    // We have a next content.  If it's a block, return it.
    if (HTMLEditUtils::IsBlockElement(*previousContent, aBlockInlineCheck)) {
      return previousContent;
    }
    if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
        !HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
      return previousContent;
    }
    if (HTMLEditUtils::IsContainerNode(*previousContent)) {
      // Else if it's a container, get deep rightmost child
      if (nsIContent* child = HTMLEditUtils::GetLastLeafContent(
              *previousContent, aLeafNodeTypes, aBlockInlineCheck)) {
        return child;
      }
    }
    // Else return the next content itself.
    return previousContent;
  }

  /**
   * Similar to the above method, but take a DOM point to specify scan start
   * point.
   */

  template <typename PT, typename CT>
  static nsIContent* GetPreviousLeafContentOrPreviousBlockElement(
      const EditorDOMPointBase<PT, CT>& aStartPoint,
      const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
      const Element* aAncestorLimiter = nullptr) {
    MOZ_ASSERT(aStartPoint.IsSet());
    MOZ_ASSERT_IF(
        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
    NS_ASSERTION(!aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
                 "Not implemented yet");

    if (!aStartPoint.IsInContentNode()) {
      return nullptr;
    }
    if (aStartPoint.IsInTextNode()) {
      return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
          *aStartPoint.template ContainerAs<Text>(), aLeafNodeTypes,
          aBlockInlineCheck, aAncestorLimiter);
    }
    if (!HTMLEditUtils::IsContainerNode(
            *aStartPoint.template ContainerAs<nsIContent>())) {
      return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
          *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
          aBlockInlineCheck, aAncestorLimiter);
    }

    if (aStartPoint.IsStartOfContainer()) {
      if (aStartPoint.GetContainer() == aAncestorLimiter ||
          HTMLEditUtils::IsBlockElement(
              *aStartPoint.template ContainerAs<nsIContent>(),
              aBlockInlineCheck)) {
        // We are at start of the block.
        return nullptr;
      }

      // We are at start of non-block container
      return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
          *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
          IgnoreInsideBlockBoundary(aBlockInlineCheck), aAncestorLimiter);
    }

    nsCOMPtr<nsIContent> previousContent =
        aStartPoint.GetPreviousSiblingOfChild();
    if (NS_WARN_IF(!previousContent)) {
      return nullptr;
    }

    // We have a prior node.  If it's a block, return it.
    if (HTMLEditUtils::IsBlockElement(*previousContent, aBlockInlineCheck)) {
      return previousContent;
    }
    if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
        !HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
      return previousContent;
    }
    if (HTMLEditUtils::IsContainerNode(*previousContent)) {
      // Else if it's a container, get deep rightmost child
      if (nsIContent* child = HTMLEditUtils::GetLastLeafContent(
              *previousContent, aLeafNodeTypes,
              IgnoreInsideBlockBoundary(aBlockInlineCheck))) {
        return child;
      }
    }
    // Else return the node itself
    return previousContent;
  }

  /**
   * Returns a content node whose inline styles should be preserved after
   * deleting content in a range.  Typically, you should set aPoint to start
   * boundary of the range to delete.
   */

  template <typename EditorDOMPointType>
  static nsIContent* GetContentToPreserveInlineStyles(
      const EditorDOMPointType& aPoint, const Element& aEditingHost);

  /**
   * Get previous/next editable point from start or end of aContent.
   */

  enum class InvisibleWhiteSpaces {
    Ignore,    // Ignore invisible white-spaces, i.e., don't return middle of
               // them.
    Preserve,  // Preserve invisible white-spaces, i.e., result may be start or
               // end of a text node even if it begins or ends with invisible
               // white-spaces.
  };
  enum class TableBoundary {
    Ignore,                  // May cross any table element boundary.
    NoCrossTableElement,     // Won't cross `<table>` element boundary.
    NoCrossAnyTableElement,  // Won't cross any table element boundary.
  };
  template <typename EditorDOMPointType>
  static EditorDOMPointType GetPreviousEditablePoint(
      nsIContent& aContent, const Element* aAncestorLimiter,
      InvisibleWhiteSpaces aInvisibleWhiteSpaces,
      TableBoundary aHowToTreatTableBoundary);
  template <typename EditorDOMPointType>
  static EditorDOMPointType GetNextEditablePoint(
      nsIContent& aContent, const Element* aAncestorLimiter,
      InvisibleWhiteSpaces aInvisibleWhiteSpaces,
      TableBoundary aHowToTreatTableBoundary);

  /**
   * GetAncestorElement() and GetInclusiveAncestorElement() return
   * (inclusive) block ancestor element of aContent whose time matches
   * aAncestorTypes.
   */

  enum class AncestorType {
    // If there is an ancestor block, it's a limiter of the scan.
    ClosestBlockElement,
    // If there is no ancestor block in the range, the topmost inline element is
    // a limiter of the scan.
    MostDistantInlineElementInBlock,
    // Ignore ancestor <hr> elements to check whether a block.
    IgnoreHRElement,
    // If there is an ancestor <button> element, it's also a limiter of the
    // scan.
    ButtonElement,
    // The root element of the scan start node or the ancestor limiter may be
    // return if there is no proper element.
    AllowRootOrAncestorLimiterElement,

    // Limit to editable elements.  If it reaches an non-editable element,
    // return its child element.
    EditableElement,
  };
  using AncestorTypes = EnumSet<AncestorType>;
  constexpr static AncestorTypes
      ClosestEditableBlockElementOrInlineEditingHost = {
          AncestorType::ClosestBlockElement,
          AncestorType::MostDistantInlineElementInBlock,
          AncestorType::EditableElement};
  constexpr static AncestorTypes ClosestBlockElement = {
      AncestorType::ClosestBlockElement};
  constexpr static AncestorTypes ClosestEditableBlockElement = {
      AncestorType::ClosestBlockElement, AncestorType::EditableElement};
  constexpr static AncestorTypes ClosestBlockElementExceptHRElement = {
      AncestorType::ClosestBlockElement, AncestorType::IgnoreHRElement};
  constexpr static AncestorTypes ClosestEditableBlockElementExceptHRElement = {
      AncestorType::ClosestBlockElement, AncestorType::IgnoreHRElement,
      AncestorType::EditableElement};
  constexpr static AncestorTypes ClosestEditableBlockElementOrButtonElement = {
      AncestorType::ClosestBlockElement, AncestorType::EditableElement,
      AncestorType::ButtonElement};
  static Element* GetAncestorElement(const nsIContent& aContent,
                                     const AncestorTypes& aAncestorTypes,
                                     BlockInlineCheck aBlockInlineCheck,
                                     const Element* aAncestorLimiter = nullptr);
  static Element* GetInclusiveAncestorElement(
      const nsIContent& aContent, const AncestorTypes& aAncestorTypes,
      BlockInlineCheck aBlockInlineCheck,
      const Element* aAncestorLimiter = nullptr);

  /**
   * GetClosestAncestorTableElement() returns the nearest inclusive ancestor
   * <table> element of aContent.
   */

  static Element* GetClosestAncestorTableElement(const nsIContent& aContent) {
    // TODO: the method name and its documentation clash with the
    // implementation. Split this method into
    // `GetClosestAncestorTableElement` and
    // `GetClosestInclusiveAncestorTableElement`.
    if (!aContent.GetParent()) {
      return nullptr;
    }
--> --------------------

--> maximum size reached

--> --------------------

96%


¤ Dauer der Verarbeitung: 0.64 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.