tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

HTMLEditUtils.h (139832B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #ifndef HTMLEditUtils_h
      7 #define HTMLEditUtils_h
      8 
      9 /**
     10 * This header declares/defines static helper methods as members of
     11 * HTMLEditUtils.  If you want to create or look for helper trivial classes for
     12 * HTMLEditor, see HTMLEditHelpers.h.
     13 */
     14 
     15 #include "EditorBase.h"
     16 #include "EditorDOMPoint.h"
     17 #include "EditorForwards.h"
     18 #include "EditorLineBreak.h"
     19 #include "EditorUtils.h"
     20 #include "HTMLEditHelpers.h"
     21 
     22 #include "mozilla/Attributes.h"
     23 #include "mozilla/EnumSet.h"
     24 #include "mozilla/IntegerRange.h"
     25 #include "mozilla/Maybe.h"
     26 #include "mozilla/Result.h"
     27 #include "mozilla/dom/AbstractRange.h"
     28 #include "mozilla/dom/AncestorIterator.h"
     29 #include "mozilla/dom/CharacterDataBuffer.h"
     30 #include "mozilla/dom/Element.h"
     31 #include "mozilla/dom/HTMLBRElement.h"
     32 #include "mozilla/dom/Selection.h"
     33 #include "mozilla/dom/Text.h"
     34 
     35 #include "nsContentUtils.h"
     36 #include "nsCRT.h"
     37 #include "nsGkAtoms.h"
     38 #include "nsHTMLTags.h"
     39 #include "nsTArray.h"
     40 
     41 class nsAtom;
     42 class nsPresContext;
     43 
     44 namespace mozilla {
     45 
     46 enum class CollectChildrenOption {
     47  // Ignore non-editable nodes
     48  IgnoreNonEditableChildren,
     49  // Ignore invisible text nodes
     50  IgnoreInvisibleTextNodes,
     51  // Collect list children too.
     52  CollectListChildren,
     53  // Collect table children too.
     54  CollectTableChildren,
     55 };
     56 
     57 class HTMLEditUtils final {
     58  using AbstractRange = dom::AbstractRange;
     59  using Element = dom::Element;
     60  using Selection = dom::Selection;
     61  using Text = dom::Text;
     62  using WhitespaceOption = dom::CharacterDataBuffer::WhitespaceOption;
     63  using WhitespaceOptions = dom::CharacterDataBuffer::WhitespaceOptions;
     64 
     65 public:
     66  static constexpr char16_t kNewLine = '\n';
     67  static constexpr char16_t kCarriageReturn = '\r';
     68  static constexpr char16_t kTab = '\t';
     69  static constexpr char16_t kSpace = ' ';
     70  static constexpr char16_t kNBSP = 0x00A0;
     71  static constexpr char16_t kGreaterThan = '>';
     72 
     73  /**
     74   * IsSimplyEditableNode() returns true when aNode is simply editable.
     75   * This does NOT means that aNode can be removed from current parent nor
     76   * aNode's data is editable.
     77   */
     78  static bool IsSimplyEditableNode(const nsINode& aNode) {
     79    return aNode.IsEditable();
     80  }
     81 
     82  /**
     83   * Return true if aNode is editable or not in a composed doc.  This is useful
     84   * if the caller may modify document fragment before inserting it into a
     85   * Document.
     86   */
     87  static bool NodeIsEditableOrNotInComposedDoc(const nsINode& aNode) {
     88    return MOZ_UNLIKELY(!aNode.IsInComposedDoc()) || aNode.IsEditable();
     89  }
     90 
     91  /**
     92   * Return true if aElement is an editing host which is either:
     93   * - the root element
     94   * - parent is not editable
     95   * - the <body> element of the document
     96   */
     97  [[nodiscard]] static bool ElementIsEditableRoot(const Element& aElement);
     98 
     99  /**
    100   * Return true if inclusive flat tree ancestor has `inert` state.
    101   */
    102  static bool ContentIsInert(const nsIContent& aContent);
    103 
    104  /**
    105   * IsNeverContentEditableElementByUser() returns true if the element's content
    106   * is never editable by user.  E.g., the content is always replaced by
    107   * native anonymous node or something.
    108   */
    109  static bool IsNeverElementContentsEditableByUser(const nsIContent& aContent) {
    110    return aContent.IsElement() &&
    111           // XXX I think we should not treat <button> contents as editable
    112           !aContent.IsHTMLElement(nsGkAtoms::button) &&
    113           (!HTMLEditUtils::IsContainerNode(aContent) ||
    114            HTMLEditUtils::IsReplacedElement(*aContent.AsElement()) ||
    115            aContent.IsAnyOfHTMLElements(nsGkAtoms::applet, nsGkAtoms::colgroup,
    116                                         nsGkAtoms::frameset, nsGkAtoms::head,
    117                                         nsGkAtoms::html));
    118  }
    119 
    120  enum class ReplaceOrVoidElementOption {
    121    LookForOnlyVoidElement,
    122    LookForOnlyReplaceElement,
    123    LookForOnlyNonVoidReplacedElement,
    124    LookForReplacedOrVoidElement,
    125  };
    126 
    127  /**
    128   * Return an inclusive ancestor replaced element or void element of aContent.
    129   * I.e., if this returns non-nullptr, aContent is in a replaced element or a
    130   * void element.
    131   */
    132  [[nodiscard]] static Element* GetInclusiveAncestorReplacedOrVoidElement(
    133      const nsIContent& aContent, ReplaceOrVoidElementOption aOption) {
    134    const bool lookForAnyReplaceElement =
    135        aOption == ReplaceOrVoidElementOption::LookForOnlyReplaceElement ||
    136        aOption == ReplaceOrVoidElementOption::LookForReplacedOrVoidElement;
    137    const bool lookForNonVoidReplacedElement =
    138        aOption ==
    139        ReplaceOrVoidElementOption::LookForOnlyNonVoidReplacedElement;
    140    const bool lookForVoidElement =
    141        aOption == ReplaceOrVoidElementOption::LookForOnlyVoidElement ||
    142        aOption == ReplaceOrVoidElementOption::LookForReplacedOrVoidElement;
    143    Element* lastReplacedOrVoidElement = nullptr;
    144    for (Element* const element :
    145         aContent.InclusiveAncestorsOfType<Element>()) {
    146      // XXX I think we should not treat <button> contents as editable
    147      if (lookForAnyReplaceElement &&
    148          !element->IsHTMLElement(nsGkAtoms::button) &&
    149          HTMLEditUtils::IsReplacedElement(*element)) {
    150        lastReplacedOrVoidElement = element;
    151      } else if (lookForNonVoidReplacedElement &&
    152                 !element->IsHTMLElement(nsGkAtoms::button) &&
    153                 HTMLEditUtils::IsNonVoidReplacedElement(*element)) {
    154        lastReplacedOrVoidElement = element;
    155      } else if (lookForVoidElement &&
    156                 !HTMLEditUtils::IsContainerNode(*element)) {
    157        lastReplacedOrVoidElement = element;
    158      }
    159    }
    160    return lastReplacedOrVoidElement;
    161  }
    162 
    163  /*
    164   * IsRemovalNode() returns true when parent of aContent is editable even
    165   * if aContent isn't editable.
    166   * This is a valid method to check it if you find the content from point
    167   * of view of siblings or parents of aContent.
    168   * Note that padding `<br>` element for empty editor and manual native
    169   * anonymous content should be deletable even after `HTMLEditor` is destroyed
    170   * because they are owned/managed by `HTMLEditor`.
    171   */
    172  static bool IsRemovableNode(const nsIContent& aContent) {
    173    return EditorUtils::IsPaddingBRElementForEmptyEditor(aContent) ||
    174           aContent.IsRootOfNativeAnonymousSubtree() ||
    175           (aContent.GetParentNode() &&
    176            aContent.GetParentNode()->IsEditable() &&
    177            &aContent != aContent.OwnerDoc()->GetBody() &&
    178            &aContent != aContent.OwnerDoc()->GetDocumentElement());
    179  }
    180 
    181  /**
    182   * IsRemovableFromParentNode() returns true when aContent is editable, has a
    183   * parent node and the parent node is also editable.
    184   * This is a valid method to check it if you find the content from point
    185   * of view of descendants of aContent.
    186   * Note that padding `<br>` element for empty editor and manual native
    187   * anonymous content should be deletable even after `HTMLEditor` is destroyed
    188   * because they are owned/managed by `HTMLEditor`.
    189   */
    190  static bool IsRemovableFromParentNode(const nsIContent& aContent) {
    191    return EditorUtils::IsPaddingBRElementForEmptyEditor(aContent) ||
    192           aContent.IsRootOfNativeAnonymousSubtree() ||
    193           (aContent.IsEditable() && aContent.GetParentNode() &&
    194            aContent.GetParentNode()->IsEditable() &&
    195            &aContent != aContent.OwnerDoc()->GetBody() &&
    196            &aContent != aContent.OwnerDoc()->GetDocumentElement());
    197  }
    198 
    199  /**
    200   * CanContentsBeJoined() returns true if aLeftContent and aRightContent can be
    201   * joined.
    202   */
    203  static bool CanContentsBeJoined(const nsIContent& aLeftContent,
    204                                  const nsIContent& aRightContent);
    205 
    206  /**
    207   * Returns true if aContent is an element and it should be treated as a block.
    208   *
    209   * @param aBlockInlineCheck
    210   *  - If UseHTMLDefaultStyle, this returns true only for HTML elements which
    211   * are defined as a block by the default style.  I.e., non-HTML elements are
    212   * always treated as inline.
    213   *  - If UseComputedDisplayOutsideStyle, this returns true for element nodes
    214   * whose display-outside is not inline nor ruby.  This is useful to get
    215   * inclusive ancestor block element.
    216   *  - If UseComputedDisplayStyle, this returns true for element nodes whose
    217   * display-outside is not inline or whose display-inside is flow-root and they
    218   * do not appear as a form control.  This is useful to check whether
    219   * collapsible white-spaces at the element edges are visible or invisible or
    220   * whether <br> element at end of the element is visible or invisible.
    221   */
    222  [[nodiscard]] static bool IsBlockElement(const nsIContent& aContent,
    223                                           BlockInlineCheck aBlockInlineCheck);
    224 
    225  /**
    226   * This is designed to check elements or non-element nodes which are laid out
    227   * as inline.  Therefore, inline-block etc and ruby are treated as inline.
    228   * Note that invisible non-element nodes like comment nodes are also treated
    229   * as inline.
    230   *
    231   * @param aBlockInlineCheck   UseComputedDisplayOutsideStyle and
    232   *                            UseComputedDisplayStyle return same result for
    233   *                            any elements.
    234   */
    235  [[nodiscard]] static bool IsInlineContent(const nsIContent& aContent,
    236                                            BlockInlineCheck aBlockInlineCheck);
    237 
    238  /**
    239   * IsVisibleElementEvenIfLeafNode() returns true if aContent is an empty block
    240   * element, a visible replaced element such as a form control.  This does not
    241   * check the layout information.
    242   */
    243  static bool IsVisibleElementEvenIfLeafNode(const nsIContent& aContent);
    244 
    245  /**
    246   * Return true if aContent is an inline element which formats the content
    247   * without giving any meanings.  E.g., <b>, <big>, <sub>, <sup>, etc, but
    248   * not <em>, <strong>, etc.
    249   */
    250  [[nodiscard]] static bool IsInlineStyleElement(const nsIContent& aContent);
    251 
    252  /**
    253   * IsDisplayOutsideInline() returns true if display-outside value is
    254   * "inside".  This does NOT flush the layout.
    255   */
    256  [[nodiscard]] static bool IsDisplayOutsideInline(const Element& aElement);
    257 
    258  /**
    259   * IsDisplayInsideFlowRoot() returns true if display-inline value of aElement
    260   * is "flow-root".  This does NOT flush the layout.
    261   */
    262  [[nodiscard]] static bool IsDisplayInsideFlowRoot(const Element& aElement);
    263 
    264  /**
    265   * Return true if aContent is a flex item or a grid item.  Note that if
    266   * aContent is the `Text` node in the following case, this returns `true`.
    267   * <div style="display:flex"><span style="display:contents">text</span></div>
    268   */
    269  [[nodiscard]] static bool IsFlexOrGridItem(const nsIContent& aContent);
    270 
    271  /**
    272   * IsRemovableInlineStyleElement() returns true if aElement is an inline
    273   * element and can be removed or split to in order to modifying inline
    274   * styles.
    275   */
    276  static bool IsRemovableInlineStyleElement(Element& aElement);
    277 
    278  /**
    279   * Return true if aTagName is one of the format element name of
    280   * Document.execCommand("formatBlock").
    281   */
    282  [[nodiscard]] static bool IsFormatTagForFormatBlockCommand(
    283      const nsStaticAtom& aTagName) {
    284    return
    285        // clang-format off
    286        &aTagName == nsGkAtoms::address ||
    287        &aTagName == nsGkAtoms::article ||
    288        &aTagName == nsGkAtoms::aside ||
    289        &aTagName == nsGkAtoms::blockquote ||
    290        &aTagName == nsGkAtoms::dd ||
    291        &aTagName == nsGkAtoms::div ||
    292        &aTagName == nsGkAtoms::dl ||
    293        &aTagName == nsGkAtoms::dt ||
    294        &aTagName == nsGkAtoms::footer ||
    295        &aTagName == nsGkAtoms::h1 ||
    296        &aTagName == nsGkAtoms::h2 ||
    297        &aTagName == nsGkAtoms::h3 ||
    298        &aTagName == nsGkAtoms::h4 ||
    299        &aTagName == nsGkAtoms::h5 ||
    300        &aTagName == nsGkAtoms::h6 ||
    301        &aTagName == nsGkAtoms::header ||
    302        &aTagName == nsGkAtoms::hgroup ||
    303        &aTagName == nsGkAtoms::main ||
    304        &aTagName == nsGkAtoms::nav ||
    305        &aTagName == nsGkAtoms::p ||
    306        &aTagName == nsGkAtoms::pre ||
    307        &aTagName == nsGkAtoms::section;
    308    // clang-format on
    309  }
    310 
    311  /**
    312   * Return true if aContent is a format element of
    313   * Document.execCommand("formatBlock").
    314   */
    315  [[nodiscard]] static bool IsFormatElementForFormatBlockCommand(
    316      const nsIContent& aContent) {
    317    if (!aContent.IsHTMLElement() ||
    318        !aContent.NodeInfo()->NameAtom()->IsStatic()) {
    319      return false;
    320    }
    321    const nsStaticAtom* tagName = aContent.NodeInfo()->NameAtom()->AsStatic();
    322    return IsFormatTagForFormatBlockCommand(*tagName);
    323  }
    324 
    325  /**
    326   * Return true if aTagName is one of the format element name of
    327   * cmd_paragraphState.
    328   */
    329  [[nodiscard]] static bool IsFormatTagForParagraphStateCommand(
    330      const nsStaticAtom& aTagName) {
    331    return
    332        // clang-format off
    333        &aTagName == nsGkAtoms::address ||
    334        &aTagName == nsGkAtoms::dd ||
    335        &aTagName == nsGkAtoms::dl ||
    336        &aTagName == nsGkAtoms::dt ||
    337        &aTagName == nsGkAtoms::h1 ||
    338        &aTagName == nsGkAtoms::h2 ||
    339        &aTagName == nsGkAtoms::h3 ||
    340        &aTagName == nsGkAtoms::h4 ||
    341        &aTagName == nsGkAtoms::h5 ||
    342        &aTagName == nsGkAtoms::h6 ||
    343        &aTagName == nsGkAtoms::p ||
    344        &aTagName == nsGkAtoms::pre;
    345    // clang-format on
    346  }
    347 
    348  /**
    349   * Return true if aContent is a format element of cmd_paragraphState.
    350   */
    351  [[nodiscard]] static bool IsFormatElementForParagraphStateCommand(
    352      const nsIContent& aContent) {
    353    if (!aContent.IsHTMLElement() ||
    354        !aContent.NodeInfo()->NameAtom()->IsStatic()) {
    355      return false;
    356    }
    357    const nsStaticAtom* tagName = aContent.NodeInfo()->NameAtom()->AsStatic();
    358    return IsFormatTagForParagraphStateCommand(*tagName);
    359  }
    360 
    361  /**
    362   * Return true if aContent is an element which can be outdented such as
    363   * a list element, a list-item element or a <blockquote>.
    364   */
    365  [[nodiscard]] static bool IsOutdentable(const nsIContent& aContent);
    366 
    367  /**
    368   * Return true if aContent is one of <h1>, <h2>, <h3>, <h4>, <h5> or <h6>.
    369   */
    370  [[nodiscard]] static bool IsHeadingElement(const nsIContent& aContent);
    371 
    372  /**
    373   * Return true if aContent is a list item element such as <li>, <dt> or <dd>.
    374   */
    375  [[nodiscard]] static bool IsListItemElement(const nsIContent& aContent);
    376  [[nodiscard]] static bool IsListItemElement(const nsIContent* aContent) {
    377    return aContent && IsListItemElement(*aContent);
    378  }
    379 
    380  /**
    381   * Return true if aContent is a <tr>.
    382   */
    383  [[nodiscard]] static bool IsTableRowElement(const nsIContent& aContent);
    384  [[nodiscard]] static bool IsTableRowElement(const nsIContent* aContent) {
    385    return aContent && IsTableRowElement(*aContent);
    386  }
    387 
    388  /**
    389   * Return true if aContent is an element which makes a table and is not a
    390   * <col> nor a <colgroup>.  So, <table>, <caption>, <tbody>, <tr>, <td>, etc.
    391   */
    392  [[nodiscard]] static bool IsAnyTableElementExceptColumnElement(
    393      const nsIContent& aContent);
    394 
    395  /**
    396   * Return true if aContent is an element which makes a table and nis not a
    397   * <col>, <colgroup> nor <table>.
    398   */
    399  [[nodiscard]] static bool IsAnyTableElementExceptTableElementAndColumElement(
    400      const nsIContent& aContent);
    401 
    402  /**
    403   * Return true if aContent is a table cell such as <td> or <th>.
    404   */
    405  [[nodiscard]] static bool IsTableCellElement(const nsIContent& aContent);
    406  [[nodiscard]] static bool IsTableCellElement(const nsIContent* aContent) {
    407    return aContent && IsTableCellElement(*aContent);
    408  }
    409 
    410  /**
    411   * Return true if aContent is a table cell or a caption, i.e., that may
    412   * contain visible content.
    413   */
    414  [[nodiscard]] static bool IsTableCellOrCaptionElement(
    415      const nsIContent& aContent);
    416 
    417  /**
    418   * Return true if aContent is a list element such as <ul>, <ol> or <dl>.
    419   */
    420  [[nodiscard]] static bool IsListElement(const nsIContent& aContent);
    421  [[nodiscard]] static bool IsListElement(const nsIContent* aContent) {
    422    return aContent && IsListElement(*aContent);
    423  }
    424 
    425  /**
    426   * Return true if aContent is an <img>.
    427   * XXX Should this return true for other elements which is replaced with an
    428   * image like <object>?
    429   */
    430  [[nodiscard]] static bool IsImageElement(const nsIContent& aContent);
    431 
    432  /**
    433   * Return true if aContent is an <a> which has non-empty `href` attribute
    434   * value.
    435   */
    436  [[nodiscard]] static bool IsHyperlinkElement(const nsIContent& aContent);
    437 
    438  /**
    439   * Return true if aContent is an <a> which has non-empty `name` attribute
    440   * value.
    441   */
    442  [[nodiscard]] static bool IsNamedAnchorElement(const nsIContent& aContent);
    443 
    444  /**
    445   * Return true if aContent is a <div type="_moz">.
    446   */
    447  [[nodiscard]] static bool IsMozDivElement(const nsIContent& aContent);
    448 
    449  /**
    450   * Return true if aElement is a mailcite element in the mail editor.
    451   */
    452  [[nodiscard]] static bool IsMailCiteElement(const Element& aElement);
    453 
    454  /**
    455   * Return true if aElement is a replaced element.
    456   */
    457  [[nodiscard]] static bool IsReplacedElement(const Element& aElement);
    458 
    459  /**
    460   * Return true if aElement is a non-void replaced element such as <iframe>,
    461   * <embed>, <audio>, <video>, <select>, etc.
    462   */
    463  [[nodiscard]] static bool IsNonVoidReplacedElement(const Element& aElement) {
    464    return IsReplacedElement(aElement) && IsContainerNode(aElement);
    465  }
    466 
    467  /**
    468   * Return true if aElement is a form widget, i.e., a replaced element for the
    469   * <form>.
    470   */
    471  [[nodiscard]] static bool IsFormWidgetElement(const nsIContent& aContent);
    472 
    473  /**
    474   * Return true if aContent is an element which can ahve `align` attribute.
    475   */
    476  [[nodiscard]] static bool IsAlignAttrSupported(const nsIContent& aContent);
    477 
    478  static bool CanNodeContain(const nsINode& aParent, const nsIContent& aChild) {
    479    switch (aParent.NodeType()) {
    480      case nsINode::ELEMENT_NODE:
    481      case nsINode::DOCUMENT_FRAGMENT_NODE:
    482        return HTMLEditUtils::CanNodeContain(*aParent.NodeInfo()->NameAtom(),
    483                                             aChild);
    484    }
    485    return false;
    486  }
    487 
    488  static bool CanNodeContain(const nsINode& aParent,
    489                             const nsAtom& aChildNodeName) {
    490    switch (aParent.NodeType()) {
    491      case nsINode::ELEMENT_NODE:
    492      case nsINode::DOCUMENT_FRAGMENT_NODE:
    493        return HTMLEditUtils::CanNodeContain(*aParent.NodeInfo()->NameAtom(),
    494                                             aChildNodeName);
    495    }
    496    return false;
    497  }
    498 
    499  static bool CanNodeContain(const nsAtom& aParentNodeName,
    500                             const nsIContent& aChild) {
    501    switch (aChild.NodeType()) {
    502      case nsINode::TEXT_NODE:
    503      case nsINode::COMMENT_NODE:
    504      case nsINode::CDATA_SECTION_NODE:
    505      case nsINode::ELEMENT_NODE:
    506      case nsINode::DOCUMENT_FRAGMENT_NODE:
    507        return HTMLEditUtils::CanNodeContain(aParentNodeName,
    508                                             *aChild.NodeInfo()->NameAtom());
    509    }
    510    return false;
    511  }
    512 
    513  // XXX Only this overload does not check the node type.  Therefore, only this
    514  //     handle Document and ProcessingInstructionTagName.
    515  static bool CanNodeContain(const nsAtom& aParentNodeName,
    516                             const nsAtom& aChildNodeName) {
    517    nsHTMLTag childTagEnum;
    518    if (&aChildNodeName == nsGkAtoms::textTagName) {
    519      childTagEnum = eHTMLTag_text;
    520    } else if (&aChildNodeName == nsGkAtoms::commentTagName ||
    521               &aChildNodeName == nsGkAtoms::cdataTagName) {
    522      childTagEnum = eHTMLTag_comment;
    523    } else {
    524      childTagEnum =
    525          nsHTMLTags::AtomTagToId(const_cast<nsAtom*>(&aChildNodeName));
    526    }
    527 
    528    nsHTMLTag parentTagEnum =
    529        nsHTMLTags::AtomTagToId(const_cast<nsAtom*>(&aParentNodeName));
    530    return HTMLEditUtils::CanNodeContain(parentTagEnum, childTagEnum);
    531  }
    532 
    533  /**
    534   * Return a point where can insert a node whose name is aInsertNodeName.
    535   * Note that if the container of aPointToInsert is not an element, this check
    536   * whether aInsertNodeName can be inserted into the element.  Therefore, the
    537   * caller may need to split the container when actually inserting a node.
    538   */
    539  [[nodiscard]] static EditorDOMPoint GetPossiblePointToInsert(
    540      const EditorDOMPoint& aPointToInsert, const nsAtom& aInsertNodeName,
    541      const Element& aEditingHost) {
    542    if (MOZ_UNLIKELY(!aPointToInsert.IsInContentNode())) {
    543      return EditorDOMPoint();
    544    }
    545    EditorDOMPoint pointToInsert(aPointToInsert);
    546    // We shouldn't modify the subtree in a replaced element so that we need to
    547    // test whether aInsertNodeName is inserted with inclusive ancestors
    548    // starting from the most distant replaced element ancestor.
    549    if (Element* const replacedOrVoidElement =
    550            HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement(
    551                *aPointToInsert.GetContainer()->AsContent(),
    552                ReplaceOrVoidElementOption::LookForReplacedOrVoidElement)) {
    553      if (MOZ_UNLIKELY(replacedOrVoidElement == &aEditingHost) ||
    554          MOZ_UNLIKELY(
    555              !replacedOrVoidElement->IsInclusiveDescendantOf(&aEditingHost))) {
    556        return EditorDOMPoint();
    557      }
    558      pointToInsert.Set(replacedOrVoidElement);
    559    }
    560    if ((pointToInsert.IsInTextNode() &&
    561         &aInsertNodeName == nsGkAtoms::textTagName) ||
    562        HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(),
    563                                      aInsertNodeName)) {
    564      return pointToInsert;
    565    }
    566    if (pointToInsert.IsInTextNode()) {
    567      Element* const parentElement =
    568          pointToInsert.GetContainerParentAs<Element>();
    569      if (NS_WARN_IF(!parentElement)) {
    570        return EditorDOMPoint();
    571      }
    572      if (HTMLEditUtils::CanNodeContain(*parentElement, aInsertNodeName)) {
    573        // Okay, the insertion point should be fine even though the caller needs
    574        // to split the `Text`.
    575        return pointToInsert;
    576      }
    577    }
    578    nsIContent* lastContent = pointToInsert.GetContainer()->AsContent();
    579    for (Element* const element : lastContent->AncestorsOfType<Element>()) {
    580      if (HTMLEditUtils::CanNodeContain(*element, aInsertNodeName)) {
    581        return EditorDOMPoint(lastContent);
    582      }
    583      if (MOZ_UNLIKELY(element == &aEditingHost)) {
    584        return EditorDOMPoint();
    585      }
    586      lastContent = element;
    587    }
    588    return pointToInsert;
    589  }
    590 
    591  /**
    592   * CanElementContainParagraph() returns true if aElement can have a <p>
    593   * element as its child or its descendant.
    594   */
    595  static bool CanElementContainParagraph(const Element& aElement) {
    596    if (HTMLEditUtils::CanNodeContain(aElement, *nsGkAtoms::p)) {
    597      return true;
    598    }
    599 
    600    // Even if the element cannot have a <p> element as a child, it can contain
    601    // <p> element as a descendant if it's one of the following elements.
    602    if (aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul,
    603                                     nsGkAtoms::dl, nsGkAtoms::table,
    604                                     nsGkAtoms::thead, nsGkAtoms::tbody,
    605                                     nsGkAtoms::tfoot, nsGkAtoms::tr)) {
    606      return true;
    607    }
    608 
    609    // XXX Otherwise, Chromium checks the CSS box is a block, but we don't do it
    610    //     for now.
    611    return false;
    612  }
    613 
    614  /**
    615   * Return a point which can insert a node whose name is aTagName scanning
    616   * from aPoint to its ancestor points.
    617   */
    618  template <typename EditorDOMPointType>
    619  static EditorDOMPoint GetInsertionPointInInclusiveAncestor(
    620      const nsAtom& aTagName, const EditorDOMPointType& aPoint,
    621      const Element* aAncestorLimit = nullptr) {
    622    if (MOZ_UNLIKELY(!aPoint.IsInContentNode())) {
    623      return EditorDOMPoint();
    624    }
    625    Element* lastChild = nullptr;
    626    for (Element* containerElement :
    627         aPoint.template ContainerAs<nsIContent>()
    628             ->template InclusiveAncestorsOfType<Element>()) {
    629      if (!HTMLEditUtils::IsSimplyEditableNode(*containerElement)) {
    630        return EditorDOMPoint();
    631      }
    632      if (HTMLEditUtils::CanNodeContain(*containerElement, aTagName)) {
    633        return lastChild ? EditorDOMPoint(lastChild)
    634                         : aPoint.template To<EditorDOMPoint>();
    635      }
    636      if (containerElement == aAncestorLimit) {
    637        return EditorDOMPoint();
    638      }
    639      lastChild = containerElement;
    640    }
    641    return EditorDOMPoint();
    642  }
    643 
    644  /**
    645   * IsContainerNode() returns true if aContent is a container node.
    646   */
    647  [[nodiscard]] static bool IsContainerNode(const nsIContent& aContent) {
    648    if (aContent.IsCharacterData()) {
    649      return false;
    650    }
    651    return HTMLEditUtils::IsContainerNode(
    652        // XXX Why don't we use nsHTMLTags::AtomTagToId?  Are there some
    653        //     difference?
    654        nsHTMLTags::StringTagToId(aContent.NodeName()));
    655  }
    656 
    657  /**
    658   * IsSplittableNode() returns true if aContent can split.
    659   */
    660  static bool IsSplittableNode(const nsIContent& aContent) {
    661    if (!EditorUtils::IsEditableContent(aContent,
    662                                        EditorUtils::EditorType::HTML) ||
    663        !HTMLEditUtils::IsRemovableFromParentNode(aContent)) {
    664      return false;
    665    }
    666    if (aContent.IsElement()) {
    667      // XXX Perhaps, instead of using container, we should have "splittable"
    668      //     information in the DB.  E.g., `<template>`, `<script>` elements
    669      //     can have children, but shouldn't be split.
    670      return HTMLEditUtils::IsContainerNode(aContent) &&
    671             !aContent.IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::button,
    672                                           nsGkAtoms::caption, nsGkAtoms::table,
    673                                           nsGkAtoms::tbody, nsGkAtoms::tfoot,
    674                                           nsGkAtoms::thead, nsGkAtoms::tr) &&
    675             !HTMLEditUtils::IsNeverElementContentsEditableByUser(aContent) &&
    676             !HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement(
    677                 aContent,
    678                 ReplaceOrVoidElementOption::LookForReplacedOrVoidElement);
    679    }
    680    return aContent.IsText() && aContent.Length() > 0;
    681  }
    682 
    683  /**
    684   * See execCommand spec:
    685   * https://w3c.github.io/editing/execCommand.html#non-list-single-line-container
    686   * https://w3c.github.io/editing/execCommand.html#single-line-container
    687   */
    688  [[nodiscard]] static bool IsNonListSingleLineContainer(
    689      const nsIContent& aContent);
    690  [[nodiscard]] static bool IsSingleLineContainer(const nsIContent& aContent);
    691 
    692  /**
    693   * Return true if aText has only a linefeed and it's preformatted.
    694   */
    695  [[nodiscard]] static bool TextHasOnlyOnePreformattedLinefeed(
    696      const Text& aText) {
    697    return aText.TextDataLength() == 1u &&
    698           aText.DataBuffer().CharAt(0u) == kNewLine &&
    699           EditorUtils::IsNewLinePreformatted(aText);
    700  }
    701 
    702  /**
    703   * IsVisibleTextNode() returns true if aText has visible text.  If it has
    704   * only white-spaces and they are collapsed, returns false.
    705   */
    706  [[nodiscard]] static bool IsVisibleTextNode(const Text& aText);
    707 
    708  /**
    709   * IsInVisibleTextFrames() returns true if any text in aText is in visible
    710   * text frames.  Callers have to guarantee that there is no pending reflow.
    711   */
    712  static bool IsInVisibleTextFrames(nsPresContext* aPresContext,
    713                                    const Text& aText);
    714 
    715  /**
    716   * IsVisibleBRElement() and IsInvisibleBRElement() return true if aContent is
    717   * a visible HTML <br> element, i.e., not a padding <br> element for making
    718   * last line in a block element visible, or an invisible <br> element.
    719   */
    720  static bool IsVisibleBRElement(const nsIContent& aContent) {
    721    if (const dom::HTMLBRElement* brElement =
    722            dom::HTMLBRElement::FromNode(&aContent)) {
    723      return IsVisibleBRElement(*brElement);
    724    }
    725    return false;
    726  }
    727  static bool IsVisibleBRElement(const dom::HTMLBRElement& aBRElement) {
    728    // If followed by a block boundary without visible content, it's invisible
    729    // <br> element.
    730    return !HTMLEditUtils::GetElementOfImmediateBlockBoundary(
    731        aBRElement, WalkTreeDirection::Forward);
    732  }
    733  static bool IsInvisibleBRElement(const nsIContent& aContent) {
    734    if (const dom::HTMLBRElement* brElement =
    735            dom::HTMLBRElement::FromNode(&aContent)) {
    736      return IsInvisibleBRElement(*brElement);
    737    }
    738    return false;
    739  }
    740  static bool IsInvisibleBRElement(const dom::HTMLBRElement& aBRElement) {
    741    return !HTMLEditUtils::IsVisibleBRElement(aBRElement);
    742  }
    743 
    744  enum class IgnoreInvisibleLineBreak { No, Yes };
    745 
    746  /**
    747   * Return true if aPoint is immediately before current block boundary.  If
    748   * aIgnoreInvisibleLineBreak is "Yes", this returns true if aPoint is before
    749   * invisible line break before a block boundary.
    750   */
    751  template <typename PT, typename CT>
    752  [[nodiscard]] static bool PointIsImmediatelyBeforeCurrentBlockBoundary(
    753      const EditorDOMPointBase<PT, CT>& aPoint,
    754      IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak);
    755 
    756  /**
    757   * Return true if aRange crosses the inclusive ancestor block element at
    758   * start boundary, in other words, if aRange ends outside of the inclusive
    759   * ancestor block of the start boundary.
    760   */
    761  template <typename EditorDOMPointType>
    762  [[nodiscard]] static bool RangeIsAcrossStartBlockBoundary(
    763      const EditorDOMRangeBase<EditorDOMPointType>& aRange,
    764      BlockInlineCheck aBlockInlineCheck) {
    765    MOZ_ASSERT(aRange.IsPositionedAndValid());
    766    if (MOZ_UNLIKELY(!aRange.StartRef().IsInContentNode())) {
    767      return false;
    768    }
    769    const Element* const startBlockElement =
    770        HTMLEditUtils::GetInclusiveAncestorElement(
    771            *aRange.StartRef().template ContainerAs<nsIContent>(),
    772            ClosestBlockElement,
    773            UseComputedDisplayStyleIfAuto(aBlockInlineCheck));
    774    if (MOZ_UNLIKELY(!startBlockElement)) {
    775      return false;
    776    }
    777    return EditorRawDOMPoint::After(*startBlockElement)
    778        .EqualsOrIsBefore(aRange.EndRef());
    779  }
    780 
    781  /**
    782   * Return true if `display` of inclusive ancestor of aContent is `none`.
    783   */
    784  static bool IsInclusiveAncestorCSSDisplayNone(const nsIContent& aContent);
    785 
    786  /**
    787   * IsVisiblePreformattedNewLine() and IsInvisiblePreformattedNewLine() return
    788   * true if the point is preformatted linefeed and it's visible or invisible.
    789   * If linefeed is immediately before a block boundary, it's invisible.
    790   *
    791   * @param aFollowingBlockElement  [out] If the node is followed by a block
    792   *                                boundary, this is set to the element
    793   *                                creating the block boundary.
    794   */
    795  template <typename EditorDOMPointType>
    796  [[nodiscard]] static bool IsVisiblePreformattedNewLine(
    797      const EditorDOMPointType& aPoint,
    798      Element** aFollowingBlockElement = nullptr) {
    799    if (aFollowingBlockElement) {
    800      *aFollowingBlockElement = nullptr;
    801    }
    802    if (!aPoint.IsInTextNode() || aPoint.IsEndOfContainer() ||
    803        !aPoint.IsCharPreformattedNewLine()) {
    804      return false;
    805    }
    806    // If there are some other characters in the text node, it's a visible
    807    // linefeed.
    808    if (!aPoint.IsAtLastContent()) {
    809      if (EditorUtils::IsWhiteSpacePreformatted(
    810              *aPoint.template ContainerAs<Text>())) {
    811        return true;
    812      }
    813      const dom::CharacterDataBuffer& characterDataBuffer =
    814          aPoint.template ContainerAs<Text>()->DataBuffer();
    815      const uint32_t nextVisibleCharOffset =
    816          characterDataBuffer.FindNonWhitespaceChar(
    817              EditorUtils::IsNewLinePreformatted(
    818                  *aPoint.template ContainerAs<Text>())
    819                  ? WhitespaceOptions{WhitespaceOption::FormFeedIsSignificant,
    820                                      WhitespaceOption::NewLineIsSignificant}
    821                  : WhitespaceOptions{WhitespaceOption::FormFeedIsSignificant},
    822              aPoint.Offset() + 1);
    823      if (nextVisibleCharOffset != dom::CharacterDataBuffer::kNotFound) {
    824        return true;  // There is a visible character after the point.
    825      }
    826    }
    827    // If followed by a block boundary without visible content, it's invisible
    828    // linefeed.
    829    Element* followingBlockElement =
    830        HTMLEditUtils::GetElementOfImmediateBlockBoundary(
    831            *aPoint.template ContainerAs<Text>(), WalkTreeDirection::Forward);
    832    if (aFollowingBlockElement) {
    833      *aFollowingBlockElement = followingBlockElement;
    834    }
    835    return !followingBlockElement;
    836  }
    837  template <typename EditorDOMPointType>
    838  static bool IsInvisiblePreformattedNewLine(
    839      const EditorDOMPointType& aPoint,
    840      Element** aFollowingBlockElement = nullptr) {
    841    if (!aPoint.IsInTextNode() || aPoint.IsEndOfContainer() ||
    842        !aPoint.IsCharPreformattedNewLine()) {
    843      if (aFollowingBlockElement) {
    844        *aFollowingBlockElement = nullptr;
    845      }
    846      return false;
    847    }
    848    return !IsVisiblePreformattedNewLine(aPoint, aFollowingBlockElement);
    849  }
    850 
    851  /**
    852   * Return a point to insert a padding line break if aPoint is following a
    853   * block boundary and the line containing aPoint requires a following padding
    854   * line break to make the line visible.
    855   */
    856  template <typename PT, typename CT>
    857  static EditorDOMPoint LineRequiresPaddingLineBreakToBeVisible(
    858      const EditorDOMPointBase<PT, CT>& aPoint, const Element& aEditingHost);
    859 
    860  /**
    861   * ShouldInsertLinefeedCharacter() returns true if the caller should insert
    862   * a linefeed character instead of <br> element.
    863   */
    864  static bool ShouldInsertLinefeedCharacter(
    865      const EditorDOMPoint& aPointToInsert, const Element& aEditingHost);
    866 
    867  enum class EmptyCheckOption {
    868    TreatSingleBRElementAsVisible,
    869    TreatBlockAsVisible,
    870    TreatListItemAsVisible,
    871    TreatTableCellAsVisible,
    872    TreatNonEditableContentAsInvisible,
    873    TreatCommentAsVisible,
    874    SafeToAskLayout,
    875  };
    876  using EmptyCheckOptions = EnumSet<EmptyCheckOption, uint32_t>;
    877 
    878  friend std::ostream& operator<<(std::ostream& aStream,
    879                                  const EmptyCheckOption& aOption);
    880  friend std::ostream& operator<<(std::ostream& aStream,
    881                                  const EmptyCheckOptions& aOptions);
    882 
    883  /**
    884   * Return false if aNode has some visible content nodes, list elements or
    885   * table elements.
    886   *
    887   * @param aPresContext    Must not be nullptr if
    888   *                        EmptyCheckOption::SafeToAskLayout is set.
    889   * @param aNode           The node to check whether it's empty.
    890   * @param aOptions        You can specify which type of elements are visible
    891   *                        and/or whether this can access layout information.
    892   * @param aSeenBR         [Out] Set to true if this meets an <br> element
    893   *                        before meeting visible things.
    894   */
    895  static bool IsEmptyNode(nsPresContext* aPresContext, const nsINode& aNode,
    896                          const EmptyCheckOptions& aOptions = {},
    897                          bool* aSeenBR = nullptr);
    898 
    899  /**
    900   * Return false if aNode has some visible content nodes, list elements or
    901   * table elements.
    902   *
    903   * @param aNode           The node to check whether it's empty.
    904   * @param aOptions        You can specify which type of elements are visible
    905   *                        and/or whether this can access layout information.
    906   *                        Must not contain EmptyCheckOption::SafeToAskLayout.
    907   * @param aSeenBR         [Out] Set to true if this meets an <br> element
    908   *                        before meeting visible things.
    909   */
    910  static bool IsEmptyNode(const nsINode& aNode,
    911                          const EmptyCheckOptions& aOptions = {},
    912                          bool* aSeenBR = nullptr) {
    913    MOZ_ASSERT(!aOptions.contains(EmptyCheckOption::SafeToAskLayout));
    914    return IsEmptyNode(nullptr, aNode, aOptions, aSeenBR);
    915  }
    916 
    917  /**
    918   * IsEmptyInlineContainer() returns true if aContent is an inline element
    919   * which can have children and does not have meaningful content.
    920   */
    921  static bool IsEmptyInlineContainer(const nsIContent& aContent,
    922                                     const EmptyCheckOptions& aOptions,
    923                                     BlockInlineCheck aBlockInlineCheck) {
    924    return HTMLEditUtils::IsInlineContent(aContent, aBlockInlineCheck) &&
    925           HTMLEditUtils::IsContainerNode(aContent) &&
    926           HTMLEditUtils::IsEmptyNode(aContent, aOptions);
    927  }
    928 
    929  /**
    930   * IsEmptyBlockElement() returns true if aElement is a block level element
    931   * and it doesn't have any visible content.
    932   */
    933  static bool IsEmptyBlockElement(const Element& aElement,
    934                                  const EmptyCheckOptions& aOptions,
    935                                  BlockInlineCheck aBlockInlineCheck) {
    936    return HTMLEditUtils::IsBlockElement(aElement, aBlockInlineCheck) &&
    937           HTMLEditUtils::IsEmptyNode(aElement, aOptions);
    938  }
    939 
    940  /**
    941   * Return true if aListElement is completely empty or it has only one list
    942   * item element which is empty.
    943   */
    944  [[nodiscard]] static bool IsEmptyAnyListElement(const Element& aListElement) {
    945    MOZ_ASSERT(HTMLEditUtils::IsListElement(aListElement));
    946    bool foundListItem = false;
    947    for (nsIContent* child = aListElement.GetFirstChild(); child;
    948         child = child->GetNextSibling()) {
    949      if (HTMLEditUtils::IsListItemElement(*child)) {
    950        if (foundListItem) {
    951          return false;  // 2 list items found.
    952        }
    953        if (!IsEmptyNode(*child, {})) {
    954          return false;  // found non-empty list item.
    955        }
    956        foundListItem = true;
    957        continue;
    958      }
    959      if (child->IsElement()) {
    960        return false;  // found sublist or illegal child.
    961      }
    962      if (child->IsText() &&
    963          HTMLEditUtils::IsVisibleTextNode(*child->AsText())) {
    964        return false;  // found illegal visible text node.
    965      }
    966    }
    967    return true;
    968  }
    969 
    970  /**
    971   * Return true if aListElement does not have invalid child.
    972   */
    973  enum class TreatSubListElementAs { Invalid, Valid };
    974  [[nodiscard]] static bool IsValidListElement(
    975      const Element& aListElement,
    976      TreatSubListElementAs aTreatSubListElementAs) {
    977    MOZ_ASSERT(HTMLEditUtils::IsListElement(aListElement));
    978    for (nsIContent* child = aListElement.GetFirstChild(); child;
    979         child = child->GetNextSibling()) {
    980      if (HTMLEditUtils::IsListElement(*child)) {
    981        if (aTreatSubListElementAs == TreatSubListElementAs::Invalid) {
    982          return false;
    983        }
    984        continue;
    985      }
    986      if (child->IsHTMLElement(nsGkAtoms::li)) {
    987        if (MOZ_UNLIKELY(!aListElement.IsAnyOfHTMLElements(nsGkAtoms::ol,
    988                                                           nsGkAtoms::ul))) {
    989          return false;
    990        }
    991        continue;
    992      }
    993      if (child->IsAnyOfHTMLElements(nsGkAtoms::dt, nsGkAtoms::dd)) {
    994        if (MOZ_UNLIKELY(!aListElement.IsAnyOfHTMLElements(nsGkAtoms::dl))) {
    995          return false;
    996        }
    997        continue;
    998      }
    999      if (MOZ_UNLIKELY(child->IsElement())) {
   1000        return false;
   1001      }
   1002      if (MOZ_LIKELY(child->IsText())) {
   1003        if (MOZ_UNLIKELY(HTMLEditUtils::IsVisibleTextNode(*child->AsText()))) {
   1004          return false;
   1005        }
   1006      }
   1007    }
   1008    return true;
   1009  }
   1010 
   1011  /**
   1012   * IsEmptyOneHardLine() returns true if aArrayOfContents does not represent
   1013   * 2 or more lines and have meaningful content.
   1014   */
   1015  static bool IsEmptyOneHardLine(
   1016      nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
   1017      BlockInlineCheck aBlockInlineCheck) {
   1018    if (NS_WARN_IF(aArrayOfContents.IsEmpty())) {
   1019      return true;
   1020    }
   1021 
   1022    bool brElementHasFound = false;
   1023    for (OwningNonNull<nsIContent>& content : aArrayOfContents) {
   1024      if (!EditorUtils::IsEditableContent(content,
   1025                                          EditorUtils::EditorType::HTML)) {
   1026        continue;
   1027      }
   1028      if (content->IsHTMLElement(nsGkAtoms::br)) {
   1029        // If there are 2 or more `<br>` elements, it's not empty line since
   1030        // there may be only one `<br>` element in a hard line.
   1031        if (brElementHasFound) {
   1032          return false;
   1033        }
   1034        brElementHasFound = true;
   1035        continue;
   1036      }
   1037      if (!HTMLEditUtils::IsEmptyInlineContainer(
   1038              content,
   1039              {EmptyCheckOption::TreatSingleBRElementAsVisible,
   1040               EmptyCheckOption::TreatNonEditableContentAsInvisible},
   1041              aBlockInlineCheck)) {
   1042        return false;
   1043      }
   1044    }
   1045    return true;
   1046  }
   1047 
   1048  /**
   1049   * IsPointAtEdgeOfLink() returns true if aPoint is at start or end of a
   1050   * link.
   1051   */
   1052  template <typename PT, typename CT>
   1053  static bool IsPointAtEdgeOfLink(const EditorDOMPointBase<PT, CT>& aPoint,
   1054                                  Element** aFoundLinkElement = nullptr) {
   1055    if (aFoundLinkElement) {
   1056      *aFoundLinkElement = nullptr;
   1057    }
   1058    if (!aPoint.IsInContentNode()) {
   1059      return false;
   1060    }
   1061    if (!aPoint.IsStartOfContainer() && !aPoint.IsEndOfContainer()) {
   1062      return false;
   1063    }
   1064    // XXX Assuming it's not in an empty text node because it's unrealistic edge
   1065    //     case.
   1066    bool maybeStartOfAnchor = aPoint.IsStartOfContainer();
   1067    for (EditorRawDOMPoint point(aPoint.template ContainerAs<nsIContent>());
   1068         point.IsInContentNode() &&
   1069         (maybeStartOfAnchor ? point.IsStartOfContainer()
   1070                             : point.IsAtLastContent());
   1071         point = point.ParentPoint()) {
   1072      if (HTMLEditUtils::IsHyperlinkElement(*point.ContainerAs<nsIContent>())) {
   1073        // Now, we're at start or end of <a href>.
   1074        if (aFoundLinkElement) {
   1075          *aFoundLinkElement =
   1076              do_AddRef(point.template ContainerAs<Element>()).take();
   1077        }
   1078        return true;
   1079      }
   1080    }
   1081    return false;
   1082  }
   1083 
   1084  /**
   1085   * IsContentInclusiveDescendantOfLink() returns true if aContent is a
   1086   * descendant of a link element.
   1087   * Note that this returns true even if editing host of aContent is in a link
   1088   * element.
   1089   */
   1090  static bool IsContentInclusiveDescendantOfLink(
   1091      nsIContent& aContent, Element** aFoundLinkElement = nullptr) {
   1092    if (aFoundLinkElement) {
   1093      *aFoundLinkElement = nullptr;
   1094    }
   1095    for (Element* element : aContent.InclusiveAncestorsOfType<Element>()) {
   1096      if (HTMLEditUtils::IsHyperlinkElement(*element)) {
   1097        if (aFoundLinkElement) {
   1098          *aFoundLinkElement = do_AddRef(element).take();
   1099        }
   1100        return true;
   1101      }
   1102    }
   1103    return false;
   1104  }
   1105 
   1106  /**
   1107   * IsRangeEntirelyInLink() returns true if aRange is entirely in a link
   1108   * element.
   1109   * Note that this returns true even if editing host of the range is in a link
   1110   * element.
   1111   */
   1112  template <typename EditorDOMRangeType>
   1113  static bool IsRangeEntirelyInLink(const EditorDOMRangeType& aRange,
   1114                                    Element** aFoundLinkElement = nullptr) {
   1115    MOZ_ASSERT(aRange.IsPositionedAndValid());
   1116    if (aFoundLinkElement) {
   1117      *aFoundLinkElement = nullptr;
   1118    }
   1119    nsINode* commonAncestorNode =
   1120        nsContentUtils::GetClosestCommonInclusiveAncestor(
   1121            aRange.StartRef().GetContainer(), aRange.EndRef().GetContainer());
   1122    if (NS_WARN_IF(!commonAncestorNode) || !commonAncestorNode->IsContent()) {
   1123      return false;
   1124    }
   1125    return IsContentInclusiveDescendantOfLink(*commonAncestorNode->AsContent(),
   1126                                              aFoundLinkElement);
   1127  }
   1128 
   1129  /**
   1130   * Get adjacent content node of aNode if there is (even if one is in different
   1131   * parent element).
   1132   *
   1133   * @param aNode               The node from which we start to walk the DOM
   1134   *                            tree.
   1135   * @param aOptions            See WalkTreeOption for the detail.
   1136   * @param aBlockInlineCheck   Whether considering block vs. inline with the
   1137   *                            computed style or the HTML default style.
   1138   * @param aAncestorLimiter    Ancestor limiter element which these methods
   1139   *                            never cross its boundary.  This is typically
   1140   *                            the editing host.
   1141   */
   1142  enum class WalkTreeOption {
   1143    IgnoreNonEditableNode,     // Ignore non-editable nodes and their children.
   1144    IgnoreDataNodeExceptText,  // Ignore data nodes which are not text node.
   1145    IgnoreWhiteSpaceOnlyText,  // Ignore text nodes having only white-spaces.
   1146    StopAtBlockBoundary,       // Stop waking the tree at a block boundary.
   1147  };
   1148  using WalkTreeOptions = EnumSet<WalkTreeOption>;
   1149  static nsIContent* GetPreviousContent(
   1150      const nsINode& aNode, const WalkTreeOptions& aOptions,
   1151      BlockInlineCheck aBlockInlineCheck,
   1152      const Element* aAncestorLimiter = nullptr) {
   1153    if (&aNode == aAncestorLimiter ||
   1154        (aAncestorLimiter &&
   1155         !aNode.IsInclusiveDescendantOf(aAncestorLimiter))) {
   1156      return nullptr;
   1157    }
   1158    return HTMLEditUtils::GetAdjacentContent(aNode, WalkTreeDirection::Backward,
   1159                                             aOptions, aBlockInlineCheck,
   1160                                             aAncestorLimiter);
   1161  }
   1162  static nsIContent* GetNextContent(const nsINode& aNode,
   1163                                    const WalkTreeOptions& aOptions,
   1164                                    BlockInlineCheck aBlockInlineCheck,
   1165                                    const Element* aAncestorLimiter = nullptr) {
   1166    if (&aNode == aAncestorLimiter ||
   1167        (aAncestorLimiter &&
   1168         !aNode.IsInclusiveDescendantOf(aAncestorLimiter))) {
   1169      return nullptr;
   1170    }
   1171    return HTMLEditUtils::GetAdjacentContent(aNode, WalkTreeDirection::Forward,
   1172                                             aOptions, aBlockInlineCheck,
   1173                                             aAncestorLimiter);
   1174  }
   1175 
   1176  /**
   1177   * And another version that takes a point in DOM tree rather than a node.
   1178   */
   1179  template <typename PT, typename CT>
   1180  static nsIContent* GetPreviousContent(
   1181      const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions,
   1182      BlockInlineCheck aBlockInlineCheck,
   1183      const Element* aAncestorLimiter = nullptr);
   1184 
   1185  /**
   1186   * And another version that takes a point in DOM tree rather than a node.
   1187   *
   1188   * Note that this may return the child at the offset.  E.g., following code
   1189   * causes infinite loop.
   1190   *
   1191   * EditorRawDOMPoint point(aEditableNode);
   1192   * while (nsIContent* content =
   1193   *          GetNextContent(point, {WalkTreeOption::IgnoreNonEditableNode})) {
   1194   *   // Do something...
   1195   *   point.Set(content);
   1196   * }
   1197   *
   1198   * Following code must be you expected:
   1199   *
   1200   * while (nsIContent* content =
   1201   *          GetNextContent(point, {WalkTreeOption::IgnoreNonEditableNode}) {
   1202   *   // Do something...
   1203   *   DebugOnly<bool> advanced = point.Advanced();
   1204   *   MOZ_ASSERT(advanced);
   1205   *   point.Set(point.GetChild());
   1206   * }
   1207   */
   1208  template <typename PT, typename CT>
   1209  static nsIContent* GetNextContent(const EditorDOMPointBase<PT, CT>& aPoint,
   1210                                    const WalkTreeOptions& aOptions,
   1211                                    BlockInlineCheck aBlockInlineCheck,
   1212                                    const Element* aAncestorLimiter = nullptr);
   1213 
   1214  /**
   1215   * GetPreviousSibling() return the preceding sibling of aContent which matches
   1216   * with aOption.
   1217   *
   1218   * @param aBlockInlineCheck   Can be Unused if aOptions does not contain
   1219   *                            StopAtBlockBoundary.
   1220   */
   1221  static nsIContent* GetPreviousSibling(
   1222      const nsIContent& aContent, const WalkTreeOptions& aOptions,
   1223      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
   1224    aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck);
   1225    for (nsIContent* sibling = aContent.GetPreviousSibling(); sibling;
   1226         sibling = sibling->GetPreviousSibling()) {
   1227      if (HTMLEditUtils::IsContentIgnored(*sibling, aOptions)) {
   1228        continue;
   1229      }
   1230      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   1231          HTMLEditUtils::IsBlockElement(*sibling, aBlockInlineCheck)) {
   1232        return nullptr;
   1233      }
   1234      return sibling;
   1235    }
   1236    return nullptr;
   1237  }
   1238 
   1239  /**
   1240   * GetNextSibling() return the following sibling of aContent which matches
   1241   * with aOption.
   1242   *
   1243   * @param aBlockInlineCheck   Can be Unused if aOptions does not contain
   1244   *                            StopAtBlockBoundary.
   1245   */
   1246  static nsIContent* GetNextSibling(
   1247      const nsIContent& aContent, const WalkTreeOptions& aOptions,
   1248      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
   1249    aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck);
   1250    for (nsIContent* sibling = aContent.GetNextSibling(); sibling;
   1251         sibling = sibling->GetNextSibling()) {
   1252      if (HTMLEditUtils::IsContentIgnored(*sibling, aOptions)) {
   1253        continue;
   1254      }
   1255      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   1256          HTMLEditUtils::IsBlockElement(*sibling, aBlockInlineCheck)) {
   1257        return nullptr;
   1258      }
   1259      return sibling;
   1260    }
   1261    return nullptr;
   1262  }
   1263 
   1264  /**
   1265   * Return the last child of aNode which matches with aOption.
   1266   *
   1267   * @param aBlockInlineCheck   Can be unused if aOptions does not contain
   1268   *                            StopAtBlockBoundary.
   1269   */
   1270  static nsIContent* GetLastChild(
   1271      const nsINode& aNode, const WalkTreeOptions& aOptions,
   1272      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
   1273    aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck);
   1274    for (nsIContent* child = aNode.GetLastChild(); child;
   1275         child = child->GetPreviousSibling()) {
   1276      if (HTMLEditUtils::IsContentIgnored(*child, aOptions)) {
   1277        continue;
   1278      }
   1279      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   1280          HTMLEditUtils::IsBlockElement(*child, aBlockInlineCheck)) {
   1281        return nullptr;
   1282      }
   1283      return child;
   1284    }
   1285    return nullptr;
   1286  }
   1287 
   1288  /**
   1289   * Return the first child of aNode which matches with aOption.
   1290   *
   1291   * @param aBlockInlineCheck   Can be unused if aOptions does not contain
   1292   *                            StopAtBlockBoundary.
   1293   */
   1294  static nsIContent* GetFirstChild(
   1295      const nsINode& aNode, const WalkTreeOptions& aOptions,
   1296      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
   1297    aBlockInlineCheck = UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck);
   1298    for (nsIContent* child = aNode.GetFirstChild(); child;
   1299         child = child->GetNextSibling()) {
   1300      if (HTMLEditUtils::IsContentIgnored(*child, aOptions)) {
   1301        continue;
   1302      }
   1303      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   1304          HTMLEditUtils::IsBlockElement(*child, aBlockInlineCheck)) {
   1305        return nullptr;
   1306      }
   1307      return child;
   1308    }
   1309    return nullptr;
   1310  }
   1311 
   1312  /**
   1313   * Return true if aContent is the last child of aNode with ignoring all
   1314   * children which do not match with aOption.
   1315   *
   1316   * @param aBlockInlineCheck   Can be unused if aOptions does not contain
   1317   *                            StopAtBlockBoundary.
   1318   */
   1319  static bool IsLastChild(
   1320      const nsIContent& aContent, const WalkTreeOptions& aOptions,
   1321      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
   1322    nsINode* parentNode = aContent.GetParentNode();
   1323    if (!parentNode) {
   1324      return false;
   1325    }
   1326    return HTMLEditUtils::GetLastChild(*parentNode, aOptions,
   1327                                       aBlockInlineCheck) == &aContent;
   1328  }
   1329 
   1330  /**
   1331   * Return true if aContent is the first child of aNode with ignoring all
   1332   * children which do not match with aOption.
   1333   *
   1334   * @param aBlockInlineCheck   Can be unused if aOptions does not contain
   1335   *                            StopAtBlockBoundary.
   1336   */
   1337  static bool IsFirstChild(
   1338      const nsIContent& aContent, const WalkTreeOptions& aOptions,
   1339      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused) {
   1340    nsINode* parentNode = aContent.GetParentNode();
   1341    if (!parentNode) {
   1342      return false;
   1343    }
   1344    return HTMLEditUtils::GetFirstChild(*parentNode, aOptions,
   1345                                        aBlockInlineCheck) == &aContent;
   1346  }
   1347 
   1348  /**
   1349   * GetAdjacentContentToPutCaret() walks the DOM tree to find an editable node
   1350   * near aPoint where may be a good point to put caret and keep typing or
   1351   * deleting.
   1352   *
   1353   * @param aPoint      The DOM point where to start to search from.
   1354   * @return            If found, returns non-nullptr.  Otherwise, nullptr.
   1355   *                    Note that if found node is in different table structure
   1356   *                    element, this returns nullptr.
   1357   */
   1358  enum class WalkTreeDirection { Forward, Backward };
   1359  template <typename PT, typename CT>
   1360  static nsIContent* GetAdjacentContentToPutCaret(
   1361      const EditorDOMPointBase<PT, CT>& aPoint,
   1362      WalkTreeDirection aWalkTreeDirection, const Element& aEditingHost) {
   1363    MOZ_ASSERT(aPoint.IsSetAndValid());
   1364 
   1365    nsIContent* editableContent = nullptr;
   1366    if (aWalkTreeDirection == WalkTreeDirection::Backward) {
   1367      editableContent = HTMLEditUtils::GetPreviousContent(
   1368          aPoint, {WalkTreeOption::IgnoreNonEditableNode},
   1369          BlockInlineCheck::Auto, &aEditingHost);
   1370      if (!editableContent) {
   1371        return nullptr;  // Not illegal.
   1372      }
   1373    } else {
   1374      editableContent = HTMLEditUtils::GetNextContent(
   1375          aPoint, {WalkTreeOption::IgnoreNonEditableNode},
   1376          BlockInlineCheck::Auto, &aEditingHost);
   1377      if (NS_WARN_IF(!editableContent)) {
   1378        // Perhaps, illegal because the node pointed by aPoint isn't editable
   1379        // and nobody of previous nodes is editable.
   1380        return nullptr;
   1381      }
   1382    }
   1383 
   1384    // scan in the right direction until we find an eligible text node,
   1385    // but don't cross any breaks, images, or table elements.
   1386    // XXX This comment sounds odd.  editableContent may have already crossed
   1387    //     breaks and/or images if they are non-editable.
   1388    while (editableContent && !editableContent->IsText() &&
   1389           !editableContent->IsHTMLElement(nsGkAtoms::br) &&
   1390           !HTMLEditUtils::IsImageElement(*editableContent)) {
   1391      if (aWalkTreeDirection == WalkTreeDirection::Backward) {
   1392        editableContent = HTMLEditUtils::GetPreviousContent(
   1393            *editableContent, {WalkTreeOption::IgnoreNonEditableNode},
   1394            BlockInlineCheck::Auto, &aEditingHost);
   1395        if (NS_WARN_IF(!editableContent)) {
   1396          return nullptr;
   1397        }
   1398      } else {
   1399        editableContent = HTMLEditUtils::GetNextContent(
   1400            *editableContent, {WalkTreeOption::IgnoreNonEditableNode},
   1401            BlockInlineCheck::Auto, &aEditingHost);
   1402        if (NS_WARN_IF(!editableContent)) {
   1403          return nullptr;
   1404        }
   1405      }
   1406    }
   1407 
   1408    // don't cross any table elements
   1409    if ((!aPoint.IsInContentNode() &&
   1410         !!HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
   1411             *editableContent)) ||
   1412        (HTMLEditUtils::GetInclusiveAncestorAnyTableElement(*editableContent) !=
   1413         HTMLEditUtils::GetInclusiveAncestorAnyTableElement(
   1414             *aPoint.template ContainerAs<nsIContent>()))) {
   1415      return nullptr;
   1416    }
   1417 
   1418    // otherwise, ok, we have found a good spot to put the selection
   1419    return editableContent;
   1420  }
   1421 
   1422  enum class LeafNodeType {
   1423    // Even if there is a child block, keep scanning a leaf content in it.
   1424    OnlyLeafNode,
   1425    // If there is a child block, return it too.  Note that this does not
   1426    // mean that block siblings are not treated as leaf nodes.
   1427    LeafNodeOrChildBlock,
   1428    // If there is a non-editable element if and only if scanning from editable
   1429    // node, return it too.
   1430    LeafNodeOrNonEditableNode,
   1431    // Ignore non-editable content at walking the tree.
   1432    OnlyEditableLeafNode,
   1433    // Treat `Comment` nodes are empty leaf nodes.
   1434    TreatCommentAsLeafNode,
   1435  };
   1436  using LeafNodeTypes = EnumSet<LeafNodeType>;
   1437 
   1438  friend std::ostream& operator<<(std::ostream& aStream,
   1439                                  const LeafNodeType& aLeafNodeType);
   1440  friend std::ostream& operator<<(std::ostream& aStream,
   1441                                  const LeafNodeTypes& aLeafNodeTypes);
   1442 
   1443  /**
   1444   * GetLastLeafContent() returns rightmost leaf content in aNode.  It depends
   1445   * on aLeafNodeTypes whether this which types of nodes are treated as leaf
   1446   * nodes.
   1447   *
   1448   * @param aBlockInlineCheck   Can be Unused if aLeafNodeTypes does not contain
   1449   *                            LeafNodeOrCHildBlock.
   1450   */
   1451  static nsIContent* GetLastLeafContent(
   1452      const nsINode& aNode, const LeafNodeTypes& aLeafNodeTypes,
   1453      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused,
   1454      const Element* aAncestorLimiter = nullptr) {
   1455    MOZ_ASSERT_IF(
   1456        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
   1457        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
   1458    // editor shouldn't touch child nodes which are replaced with native
   1459    // anonymous nodes.
   1460    if (aNode.IsElement() &&
   1461        HTMLEditUtils::IsNeverElementContentsEditableByUser(
   1462            *aNode.AsElement())) {
   1463      return nullptr;
   1464    }
   1465    for (nsIContent* content = aNode.GetLastChild(); content;) {
   1466      if (aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode) &&
   1467          !EditorUtils::IsEditableContent(*content,
   1468                                          EditorUtils::EditorType::HTML)) {
   1469        content = HTMLEditUtils::GetPreviousContent(
   1470            *content, {WalkTreeOption::IgnoreNonEditableNode},
   1471            aBlockInlineCheck, aAncestorLimiter);
   1472        continue;
   1473      }
   1474      if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) &&
   1475          content->IsComment()) {
   1476        content = content->GetPreviousSibling();
   1477        continue;
   1478      }
   1479      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) &&
   1480          HTMLEditUtils::IsBlockElement(
   1481              *content,
   1482              UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) {
   1483        return content;
   1484      }
   1485      if (!content->HasChildren() ||
   1486          HTMLEditUtils::IsNeverElementContentsEditableByUser(*content)) {
   1487        return content;
   1488      }
   1489      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
   1490          !HTMLEditUtils::IsSimplyEditableNode(*content)) {
   1491        return content;
   1492      }
   1493      content = content->GetLastChild();
   1494    }
   1495    return nullptr;
   1496  }
   1497 
   1498  /**
   1499   * GetFirstLeafContent() returns leftmost leaf content in aNode.  It depends
   1500   * on aLeafNodeTypes whether this scans into a block child or treat block as a
   1501   * leaf.
   1502   *
   1503   * @param aBlockInlineCheck   Can be Unused if aLeafNodeTypes does not contain
   1504   *                            LeafNodeOrCHildBlock.
   1505   */
   1506  static nsIContent* GetFirstLeafContent(
   1507      const nsINode& aNode, const LeafNodeTypes& aLeafNodeTypes,
   1508      BlockInlineCheck aBlockInlineCheck = BlockInlineCheck::Unused,
   1509      const Element* aAncestorLimiter = nullptr) {
   1510    MOZ_ASSERT_IF(
   1511        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
   1512        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
   1513    // editor shouldn't touch child nodes which are replaced with native
   1514    // anonymous nodes.
   1515    if (aNode.IsElement() &&
   1516        HTMLEditUtils::IsNeverElementContentsEditableByUser(
   1517            *aNode.AsElement())) {
   1518      return nullptr;
   1519    }
   1520    for (nsIContent* content = aNode.GetFirstChild(); content;) {
   1521      if (aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode) &&
   1522          !EditorUtils::IsEditableContent(*content,
   1523                                          EditorUtils::EditorType::HTML)) {
   1524        content = HTMLEditUtils::GetNextContent(
   1525            *content, {WalkTreeOption::IgnoreNonEditableNode},
   1526            aBlockInlineCheck, aAncestorLimiter);
   1527        continue;
   1528      }
   1529      if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) &&
   1530          content->IsComment()) {
   1531        content = content->GetNextSibling();
   1532        continue;
   1533      }
   1534      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) &&
   1535          HTMLEditUtils::IsBlockElement(
   1536              *content,
   1537              UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) {
   1538        return content;
   1539      }
   1540      if (!content->HasChildren() ||
   1541          HTMLEditUtils::IsNeverElementContentsEditableByUser(*content)) {
   1542        return content;
   1543      }
   1544      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
   1545          !HTMLEditUtils::IsSimplyEditableNode(*content)) {
   1546        return content;
   1547      }
   1548      content = content->GetFirstChild();
   1549    }
   1550    return nullptr;
   1551  }
   1552 
   1553  /**
   1554   * GetNextLeafContentOrNextBlockElement() returns next leaf content or
   1555   * next block element of aStartContent inside aAncestorLimiter.
   1556   *
   1557   * @param aStartContent       The start content to scan next content.
   1558   * @param aLeafNodeTypes      See LeafNodeType.
   1559   * @param aAncestorLimiter    Optional, if you set this, it must be an
   1560   *                            inclusive ancestor of aStartContent.
   1561   */
   1562  static nsIContent* GetNextLeafContentOrNextBlockElement(
   1563      const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes,
   1564      BlockInlineCheck aBlockInlineCheck,
   1565      const Element* aAncestorLimiter = nullptr) {
   1566    MOZ_ASSERT_IF(
   1567        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
   1568        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
   1569 
   1570    if (&aStartContent == aAncestorLimiter) {
   1571      return nullptr;
   1572    }
   1573 
   1574    Element* container = aStartContent.GetParentElement();
   1575    for (nsIContent* nextContent = aStartContent.GetNextSibling();;) {
   1576      if (!nextContent) {
   1577        if (!container) {
   1578          NS_WARNING("Reached orphan node while climbing up the DOM tree");
   1579          return nullptr;
   1580        }
   1581        for (Element* parentElement :
   1582             container->InclusiveAncestorsOfType<Element>()) {
   1583          if (parentElement == aAncestorLimiter ||
   1584              HTMLEditUtils::IsBlockElement(
   1585                  *parentElement,
   1586                  UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) {
   1587            return nullptr;
   1588          }
   1589          if (aLeafNodeTypes.contains(
   1590                  LeafNodeType::LeafNodeOrNonEditableNode) &&
   1591              !parentElement->IsEditable()) {
   1592            return nullptr;
   1593          }
   1594          nextContent = parentElement->GetNextSibling();
   1595          if (nextContent) {
   1596            container = nextContent->GetParentElement();
   1597            break;
   1598          }
   1599          if (!parentElement->GetParentElement()) {
   1600            NS_WARNING("Reached orphan node while climbing up the DOM tree");
   1601            return nullptr;
   1602          }
   1603        }
   1604        MOZ_ASSERT(nextContent);
   1605      }
   1606 
   1607      if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) &&
   1608          nextContent->IsComment()) {
   1609        nextContent = nextContent->GetNextSibling();
   1610        continue;
   1611      }
   1612 
   1613      // We have a next content.  If it's a block, return it.
   1614      if (HTMLEditUtils::IsBlockElement(
   1615              *nextContent,
   1616              PreferDisplayOutsideIfUsingDisplay(
   1617                  UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) {
   1618        return nextContent;
   1619      }
   1620      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
   1621          !nextContent->IsEditable()) {
   1622        return nextContent;
   1623      }
   1624      if (HTMLEditUtils::IsContainerNode(*nextContent)) {
   1625        // Else if it's a container, get deep leftmost child
   1626        if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
   1627                *nextContent, aLeafNodeTypes, aBlockInlineCheck)) {
   1628          return child;
   1629        }
   1630      }
   1631      // Else return the next content itself.
   1632      return nextContent;
   1633    }
   1634    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
   1635        "Must return from the preceding for-loop");
   1636  }
   1637 
   1638  /**
   1639   * Similar to the above method, but take a DOM point to specify scan start
   1640   * point.
   1641   */
   1642  template <typename PT, typename CT>
   1643  static nsIContent* GetNextLeafContentOrNextBlockElement(
   1644      const EditorDOMPointBase<PT, CT>& aStartPoint,
   1645      const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
   1646      const Element* aAncestorLimiter = nullptr) {
   1647    MOZ_ASSERT(aStartPoint.IsSet());
   1648    MOZ_ASSERT_IF(
   1649        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
   1650        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
   1651    NS_ASSERTION(!aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
   1652                 "Not implemented yet");
   1653 
   1654    if (!aStartPoint.IsInContentNode()) {
   1655      return nullptr;
   1656    }
   1657    if (aStartPoint.IsInTextNode()) {
   1658      return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1659          *aStartPoint.template ContainerAs<Text>(), aLeafNodeTypes,
   1660          aBlockInlineCheck, aAncestorLimiter);
   1661    }
   1662    if (!HTMLEditUtils::IsContainerNode(
   1663            *aStartPoint.template ContainerAs<nsIContent>())) {
   1664      return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1665          *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
   1666          aBlockInlineCheck, aAncestorLimiter);
   1667    }
   1668 
   1669    for (nsIContent* nextContent = aStartPoint.GetChild();;) {
   1670      if (!nextContent) {
   1671        if (aStartPoint.GetContainer() == aAncestorLimiter ||
   1672            HTMLEditUtils::IsBlockElement(
   1673                *aStartPoint.template ContainerAs<nsIContent>(),
   1674                UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) {
   1675          // We are at end of the block.
   1676          return nullptr;
   1677        }
   1678 
   1679        // We are at end of non-block container
   1680        return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
   1681            *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
   1682            PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck),
   1683            aAncestorLimiter);
   1684      }
   1685 
   1686      if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) &&
   1687          nextContent->IsComment()) {
   1688        nextContent = nextContent->GetNextSibling();
   1689        continue;
   1690      }
   1691 
   1692      // We have a next node.  If it's a block, return it.
   1693      if (HTMLEditUtils::IsBlockElement(
   1694              *nextContent,
   1695              UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) {
   1696        return nextContent;
   1697      }
   1698      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
   1699          !HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
   1700        return nextContent;
   1701      }
   1702      if (HTMLEditUtils::IsContainerNode(*nextContent)) {
   1703        // else if it's a container, get deep leftmost child
   1704        if (nsIContent* child = HTMLEditUtils::GetFirstLeafContent(
   1705                *nextContent, aLeafNodeTypes,
   1706                PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) {
   1707          return child;
   1708        }
   1709      }
   1710      // Else return the node itself
   1711      return nextContent;
   1712    }
   1713    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
   1714        "Must return from the preceding for-loop");
   1715  }
   1716 
   1717  /**
   1718   * GetPreviousLeafContentOrPreviousBlockElement() returns previous leaf
   1719   * content or previous block element of aStartContent inside
   1720   * aAncestorLimiter.
   1721   *
   1722   * @param aStartContent       The start content to scan previous content.
   1723   * @param aLeafNodeTypes      See LeafNodeType.
   1724   * @param aAncestorLimiter    Optional, if you set this, it must be an
   1725   *                            inclusive ancestor of aStartContent.
   1726   */
   1727  static nsIContent* GetPreviousLeafContentOrPreviousBlockElement(
   1728      const nsIContent& aStartContent, const LeafNodeTypes& aLeafNodeTypes,
   1729      BlockInlineCheck aBlockInlineCheck,
   1730      const Element* aAncestorLimiter = nullptr) {
   1731    MOZ_ASSERT_IF(
   1732        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
   1733        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
   1734    NS_ASSERTION(!aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
   1735                 "Not implemented yet");
   1736 
   1737    if (&aStartContent == aAncestorLimiter) {
   1738      return nullptr;
   1739    }
   1740 
   1741    Element* container = aStartContent.GetParentElement();
   1742    for (nsIContent* previousContent = aStartContent.GetPreviousSibling();;) {
   1743      if (!previousContent) {
   1744        if (!container) {
   1745          NS_WARNING("Reached orphan node while climbing up the DOM tree");
   1746          return nullptr;
   1747        }
   1748        for (Element* parentElement :
   1749             container->InclusiveAncestorsOfType<Element>()) {
   1750          if (parentElement == aAncestorLimiter ||
   1751              HTMLEditUtils::IsBlockElement(
   1752                  *parentElement,
   1753                  UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) {
   1754            return nullptr;
   1755          }
   1756          if (aLeafNodeTypes.contains(
   1757                  LeafNodeType::LeafNodeOrNonEditableNode) &&
   1758              !parentElement->IsEditable()) {
   1759            return nullptr;
   1760          }
   1761          previousContent = parentElement->GetPreviousSibling();
   1762          if (previousContent) {
   1763            container = previousContent->GetParentElement();
   1764            break;
   1765          }
   1766          if (!parentElement->GetParentElement()) {
   1767            NS_WARNING("Reached orphan node while climbing up the DOM tree");
   1768            return nullptr;
   1769          }
   1770        }
   1771        MOZ_ASSERT(previousContent);
   1772      }
   1773 
   1774      if (!aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) &&
   1775          previousContent->IsComment()) {
   1776        previousContent = previousContent->GetPreviousSibling();
   1777        continue;
   1778      }
   1779 
   1780      // We have a next content.  If it's a block, return it.
   1781      if (HTMLEditUtils::IsBlockElement(
   1782              *previousContent,
   1783              PreferDisplayOutsideIfUsingDisplay(
   1784                  UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck)))) {
   1785        return previousContent;
   1786      }
   1787      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
   1788          !HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
   1789        return previousContent;
   1790      }
   1791      if (HTMLEditUtils::IsContainerNode(*previousContent)) {
   1792        // Else if it's a container, get deep rightmost child
   1793        if (nsIContent* child = HTMLEditUtils::GetLastLeafContent(
   1794                *previousContent, aLeafNodeTypes,
   1795                PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) {
   1796          return child;
   1797        }
   1798      }
   1799      // Else return the next content itself.
   1800      return previousContent;
   1801    }
   1802    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
   1803        "Must return from the preceding for-loop");
   1804  }
   1805 
   1806  /**
   1807   * Similar to the above method, but take a DOM point to specify scan start
   1808   * point.
   1809   */
   1810  template <typename PT, typename CT>
   1811  static nsIContent* GetPreviousLeafContentOrPreviousBlockElement(
   1812      const EditorDOMPointBase<PT, CT>& aStartPoint,
   1813      const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
   1814      const Element* aAncestorLimiter = nullptr) {
   1815    MOZ_ASSERT(aStartPoint.IsSet());
   1816    MOZ_ASSERT_IF(
   1817        aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
   1818        !aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode));
   1819    NS_ASSERTION(!aLeafNodeTypes.contains(LeafNodeType::OnlyEditableLeafNode),
   1820                 "Not implemented yet");
   1821 
   1822    if (!aStartPoint.IsInContentNode()) {
   1823      return nullptr;
   1824    }
   1825    if (aStartPoint.IsInTextNode()) {
   1826      return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1827          *aStartPoint.template ContainerAs<Text>(), aLeafNodeTypes,
   1828          aBlockInlineCheck, aAncestorLimiter);
   1829    }
   1830    if (!HTMLEditUtils::IsContainerNode(
   1831            *aStartPoint.template ContainerAs<nsIContent>())) {
   1832      return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1833          *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
   1834          aBlockInlineCheck, aAncestorLimiter);
   1835    }
   1836 
   1837    if (aStartPoint.IsStartOfContainer()) {
   1838      if (aStartPoint.GetContainer() == aAncestorLimiter ||
   1839          HTMLEditUtils::IsBlockElement(
   1840              *aStartPoint.template ContainerAs<nsIContent>(),
   1841              UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) {
   1842        // We are at start of the block.
   1843        return nullptr;
   1844      }
   1845 
   1846      // We are at start of non-block container
   1847      return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1848          *aStartPoint.template ContainerAs<nsIContent>(), aLeafNodeTypes,
   1849          PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck),
   1850          aAncestorLimiter);
   1851    }
   1852 
   1853    for (nsIContent* previousContent = aStartPoint.GetPreviousSiblingOfChild();
   1854         previousContent &&
   1855         (aLeafNodeTypes.contains(LeafNodeType::TreatCommentAsLeafNode) ||
   1856          !previousContent->IsComment());
   1857         previousContent = previousContent->GetPreviousSibling()) {
   1858      // We have a prior node.  If it's a block, return it.
   1859      if (HTMLEditUtils::IsBlockElement(
   1860              *previousContent,
   1861              UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) {
   1862        return previousContent;
   1863      }
   1864      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
   1865          !HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
   1866        return previousContent;
   1867      }
   1868      if (HTMLEditUtils::IsContainerNode(*previousContent)) {
   1869        // Else if it's a container, get deep rightmost child
   1870        if (nsIContent* child = HTMLEditUtils::GetLastLeafContent(
   1871                *previousContent, aLeafNodeTypes,
   1872                PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck))) {
   1873          return child;
   1874        }
   1875      }
   1876      // Else return the node itself
   1877      return previousContent;
   1878    }
   1879    return nullptr;
   1880  }
   1881 
   1882  /**
   1883   * Return previous non-empty leaf content or child block or non-editable
   1884   * content (depending on aLeafNodeTypes).  This ignores invisible inline leaf
   1885   * element like `<b></b>` and empty `Text` nodes.  So, this may return
   1886   * invisible `Text` node, but it may be useful to consider whether we need to
   1887   * insert a padding <br> element.
   1888   */
   1889  [[nodiscard]] static nsIContent*
   1890  GetPreviousNonEmptyLeafContentOrPreviousBlockElement(
   1891      const nsIContent& aContent, const LeafNodeTypes& aLeafNodeTypes,
   1892      BlockInlineCheck aBlockInlineCheck,
   1893      const Element* aAncestorLimiter = nullptr) {
   1894    for (nsIContent* previousContent =
   1895             HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1896                 aContent, aLeafNodeTypes, aBlockInlineCheck, aAncestorLimiter);
   1897         previousContent;
   1898         previousContent =
   1899             HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1900                 *previousContent, aLeafNodeTypes, aBlockInlineCheck,
   1901                 aAncestorLimiter)) {
   1902      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) &&
   1903          HTMLEditUtils::IsBlockElement(
   1904              *previousContent,
   1905              PreferDisplayOutsideIfUsingDisplay(
   1906                  UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck)))) {
   1907        return previousContent;  // Reached block element
   1908      }
   1909      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
   1910          HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
   1911        return previousContent;  // Reached non-editable content
   1912      }
   1913      Text* const previousText = Text::FromNode(previousContent);
   1914      if (!previousText) {
   1915        if (!HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) {
   1916          continue;  // Ignore invisible inline elements
   1917        }
   1918        return previousContent;  // Reached visible inline element
   1919      }
   1920      if (!previousText->TextDataLength()) {
   1921        continue;  // Ignore empty Text nodes.
   1922      }
   1923      return previousText;  // Reached non-empty text
   1924    }
   1925    return nullptr;
   1926  }
   1927 
   1928  /**
   1929   * Return previous visible leaf content or child block or non-editable content
   1930   * (depending on aLeafNodeTypes).  This ignores invisible inline leaf element
   1931   * like `<b></b>` and empty `Text` nodes.  So, this may return invisible
   1932   * `Text` node, but it may be useful to consider whether we need to insert a
   1933   * padding <br> element.
   1934   */
   1935  template <typename PT, typename CT>
   1936  [[nodiscard]] static nsIContent*
   1937  GetPreviousNonEmptyLeafContentOrPreviousBlockElement(
   1938      const EditorDOMPointBase<PT, CT>& aPoint,
   1939      const LeafNodeTypes& aLeafNodeTypes, BlockInlineCheck aBlockInlineCheck,
   1940      const Element* aAncestorLimiter = nullptr) {
   1941    for (nsIContent* previousContent =
   1942             HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1943                 aPoint, aLeafNodeTypes, aBlockInlineCheck, aAncestorLimiter);
   1944         previousContent;
   1945         previousContent =
   1946             HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1947                 *previousContent, aLeafNodeTypes, aBlockInlineCheck,
   1948                 aAncestorLimiter)) {
   1949      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrChildBlock) &&
   1950          HTMLEditUtils::IsBlockElement(
   1951              *previousContent,
   1952              PreferDisplayOutsideIfUsingDisplay(
   1953                  UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck)))) {
   1954        return previousContent;  // Reached block element
   1955      }
   1956      if (aLeafNodeTypes.contains(LeafNodeType::LeafNodeOrNonEditableNode) &&
   1957          HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
   1958        return previousContent;  // Reached non-editable content
   1959      }
   1960      Text* const previousText = Text::FromNode(previousContent);
   1961      if (!previousText) {
   1962        if (!HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*previousContent)) {
   1963          continue;  // Ignore invisible inline elements
   1964        }
   1965        return previousContent;  // Reached visible inline element
   1966      }
   1967      if (!previousText->TextDataLength()) {
   1968        continue;  // Ignore empty Text nodes.
   1969      }
   1970      return previousText;  // Reached non-empty text
   1971    }
   1972    return nullptr;
   1973  }
   1974  /**
   1975   * Returns a content node whose inline styles should be preserved after
   1976   * deleting content in a range.  Typically, you should set aPoint to start
   1977   * boundary of the range to delete.
   1978   */
   1979  template <typename EditorDOMPointType>
   1980  static nsIContent* GetContentToPreserveInlineStyles(
   1981      const EditorDOMPointType& aPoint, const Element& aEditingHost);
   1982 
   1983  /**
   1984   * Get previous/next editable point from start or end of aContent.
   1985   */
   1986  enum class InvisibleWhiteSpaces {
   1987    Ignore,    // Ignore invisible white-spaces, i.e., don't return middle of
   1988               // them.
   1989    Preserve,  // Preserve invisible white-spaces, i.e., result may be start or
   1990               // end of a text node even if it begins or ends with invisible
   1991               // white-spaces.
   1992  };
   1993  enum class TableBoundary {
   1994    Ignore,                  // May cross any table element boundary.
   1995    NoCrossTableElement,     // Won't cross `<table>` element boundary.
   1996    NoCrossAnyTableElement,  // Won't cross any table element boundary.
   1997  };
   1998  template <typename EditorDOMPointType>
   1999  static EditorDOMPointType GetPreviousEditablePoint(
   2000      nsIContent& aContent, const Element* aAncestorLimiter,
   2001      InvisibleWhiteSpaces aInvisibleWhiteSpaces,
   2002      TableBoundary aHowToTreatTableBoundary);
   2003  template <typename EditorDOMPointType>
   2004  static EditorDOMPointType GetNextEditablePoint(
   2005      nsIContent& aContent, const Element* aAncestorLimiter,
   2006      InvisibleWhiteSpaces aInvisibleWhiteSpaces,
   2007      TableBoundary aHowToTreatTableBoundary);
   2008 
   2009  /**
   2010   * GetAncestorElement() and GetInclusiveAncestorElement() return
   2011   * (inclusive) block ancestor element of aContent whose time matches
   2012   * aAncestorTypes.
   2013   */
   2014  enum class AncestorType {
   2015    // Return the closest block element.
   2016    ClosestBlockElement,
   2017    // Return the closest container element, either block or inline.  I.e., this
   2018    // ignores void elements like <br>, <hr>, etc.
   2019    ClosestContainerElement,
   2020    // Return a child element of the closest block element.
   2021    // NOTE: If the scanning start node is a block element, the methods return
   2022    // nullptr or the ancestor limiter if AllowRootOrAncestorLimiterElement is
   2023    // set.
   2024    // NOTE: If ClosestButtonElement is set, the methods may return a <button>.
   2025    // NOTE: If StopAtClosestButtonElement is set, the methods return a child of
   2026    // <button>.
   2027    MostDistantInlineElementInBlock,
   2028    // Ignore ancestor <hr> elements to check whether a block.
   2029    IgnoreHRElement,
   2030    // Return the closest button element.
   2031    ClosestButtonElement,
   2032    // Stop scanning at <button> element.  The methods do not return the
   2033    // <button> element if MostDistantInlineElementInBlock is set.
   2034    StopAtClosestButtonElement,
   2035    // When there is no ancestor which matches with the other flags and reached
   2036    // the ancestor limiter (or an editing host, the editable <body> or the
   2037    // editable root document element if and only if EditableElement is
   2038    // specified), return the ancestor limiter, editable <body> or editable root
   2039    // document element instead.
   2040    ReturnAncestorLimiterIfNoProperAncestor,
   2041 
   2042    // Limit to editable elements.  If it reaches an non-editable element,
   2043    // return the last ancestor element which matches with the other types.
   2044    EditableElement,
   2045  };
   2046  using AncestorTypes = EnumSet<AncestorType>;
   2047 
   2048  friend std::ostream& operator<<(std::ostream& aStream,
   2049                                  const AncestorType& aType);
   2050  friend std::ostream& operator<<(std::ostream& aStream,
   2051                                  const AncestorTypes& aTypes);
   2052 
   2053  constexpr static AncestorTypes
   2054      ClosestEditableBlockElementOrInlineEditingHost = {
   2055          AncestorType::ClosestBlockElement, AncestorType::EditableElement,
   2056          AncestorType::ReturnAncestorLimiterIfNoProperAncestor};
   2057  constexpr static AncestorTypes ClosestBlockElement = {
   2058      AncestorType::ClosestBlockElement};
   2059  constexpr static AncestorTypes ClosestEditableBlockElement = {
   2060      AncestorType::ClosestBlockElement, AncestorType::EditableElement};
   2061  constexpr static AncestorTypes ClosestBlockElementExceptHRElement = {
   2062      AncestorType::ClosestBlockElement, AncestorType::IgnoreHRElement};
   2063  constexpr static AncestorTypes ClosestEditableBlockElementExceptHRElement = {
   2064      AncestorType::ClosestBlockElement, AncestorType::IgnoreHRElement,
   2065      AncestorType::EditableElement};
   2066  constexpr static AncestorTypes ClosestEditableBlockElementOrButtonElement = {
   2067      AncestorType::ClosestBlockElement, AncestorType::EditableElement,
   2068      AncestorType::ClosestButtonElement};
   2069  // Return the most distant ancestor element in current block or <button>.
   2070  constexpr static AncestorTypes
   2071      MostDistantEditableInlineElementInBlockOrButton = {
   2072          AncestorType::MostDistantInlineElementInBlock,
   2073          AncestorType::StopAtClosestButtonElement};
   2074  // Return the most distant ancestor element in current block or the closest
   2075  // <button> if starting to scan within a <button>.  If you want a child in the
   2076  // <button> in the latter case, use
   2077  // MostDistantEditableInlineElementInBlockOrButton.
   2078  constexpr static AncestorTypes
   2079      MostDistantEditableInlineElementInBlockOrClosestButton = {
   2080          AncestorType::MostDistantInlineElementInBlock,
   2081          AncestorType::ClosestButtonElement};
   2082  constexpr static AncestorTypes ClosestContainerElementOrVoidAncestorLimiter =
   2083      {AncestorType::ClosestContainerElement,
   2084       AncestorType::ReturnAncestorLimiterIfNoProperAncestor};
   2085  static Element* GetAncestorElement(const nsIContent& aContent,
   2086                                     const AncestorTypes& aAncestorTypes,
   2087                                     BlockInlineCheck aBlockInlineCheck,
   2088                                     const Element* aAncestorLimiter = nullptr);
   2089  static Element* GetInclusiveAncestorElement(
   2090      const nsIContent& aContent, const AncestorTypes& aAncestorTypes,
   2091      BlockInlineCheck aBlockInlineCheck,
   2092      const Element* aAncestorLimiter = nullptr);
   2093 
   2094  /**
   2095   * GetClosestAncestorTableElement() returns the nearest inclusive ancestor
   2096   * <table> element of aContent.
   2097   */
   2098  static Element* GetClosestAncestorTableElement(const nsIContent& aContent) {
   2099    // TODO: the method name and its documentation clash with the
   2100    // implementation. Split this method into
   2101    // `GetClosestAncestorTableElement` and
   2102    // `GetClosestInclusiveAncestorTableElement`.
   2103    if (!aContent.GetParent()) {
   2104      return nullptr;
   2105    }
   2106    for (Element* element : aContent.InclusiveAncestorsOfType<Element>()) {
   2107      if (element->IsHTMLElement(nsGkAtoms::table)) {
   2108        return element;
   2109      }
   2110    }
   2111    return nullptr;
   2112  }
   2113 
   2114  static Element* GetInclusiveAncestorAnyTableElement(
   2115      const nsIContent& aContent) {
   2116    for (Element* parent : aContent.InclusiveAncestorsOfType<Element>()) {
   2117      if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(*parent)) {
   2118        return parent;
   2119      }
   2120    }
   2121    return nullptr;
   2122  }
   2123 
   2124  [[nodiscard]] static Element* GetClosestAncestorAnyListElement(
   2125      const nsIContent& aContent);
   2126  [[nodiscard]] static Element* GetClosestInclusiveAncestorAnyListElement(
   2127      const nsIContent& aContent);
   2128 
   2129  /**
   2130   * Return a list item element if aContent or its ancestor in editing host is
   2131   * one.  However, this won't cross table related element.
   2132   */
   2133  static Element* GetClosestInclusiveAncestorListItemElement(
   2134      const nsIContent& aContent, const Element* aAncestorLimit = nullptr) {
   2135    MOZ_ASSERT_IF(aAncestorLimit,
   2136                  aContent.IsInclusiveDescendantOf(aAncestorLimit));
   2137 
   2138    if (HTMLEditUtils::IsListItemElement(aContent)) {
   2139      return const_cast<Element*>(aContent.AsElement());
   2140    }
   2141 
   2142    for (Element* parentElement : aContent.AncestorsOfType<Element>()) {
   2143      if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(*parentElement)) {
   2144        return nullptr;
   2145      }
   2146      if (HTMLEditUtils::IsListItemElement(*parentElement)) {
   2147        return parentElement;
   2148      }
   2149      if (parentElement == aAncestorLimit) {
   2150        return nullptr;
   2151      }
   2152    }
   2153    return nullptr;
   2154  }
   2155 
   2156  /**
   2157   * GetRangeSelectingAllContentInAllListItems() returns a range which selects
   2158   * from start of the first list item to end of the last list item of
   2159   * aListElement.  Note that the result may be in different list element if
   2160   * aListElement has child list element(s) directly.
   2161   */
   2162  template <typename EditorDOMRangeType>
   2163  static EditorDOMRangeType GetRangeSelectingAllContentInAllListItems(
   2164      const Element& aListElement) {
   2165    MOZ_ASSERT(HTMLEditUtils::IsListElement(aListElement));
   2166    Element* firstListItem =
   2167        HTMLEditUtils::GetFirstListItemElement(aListElement);
   2168    Element* lastListItem = HTMLEditUtils::GetLastListItemElement(aListElement);
   2169    MOZ_ASSERT_IF(firstListItem, lastListItem);
   2170    MOZ_ASSERT_IF(!firstListItem, !lastListItem);
   2171    if (!firstListItem || !lastListItem) {
   2172      return EditorDOMRangeType();
   2173    }
   2174    return EditorDOMRangeType(
   2175        typename EditorDOMRangeType::PointType(firstListItem, 0u),
   2176        EditorDOMRangeType::PointType::AtEndOf(*lastListItem));
   2177  }
   2178 
   2179  /**
   2180   * GetFirstListItemElement() returns the first list item element in the
   2181   * pre-order tree traversal of the DOM.
   2182   */
   2183  static Element* GetFirstListItemElement(const Element& aListElement) {
   2184    MOZ_ASSERT(HTMLEditUtils::IsListElement(aListElement));
   2185    for (nsIContent* maybeFirstListItem = aListElement.GetFirstChild();
   2186         maybeFirstListItem;
   2187         maybeFirstListItem = maybeFirstListItem->GetNextNode(&aListElement)) {
   2188      if (HTMLEditUtils::IsListItemElement(*maybeFirstListItem)) {
   2189        return maybeFirstListItem->AsElement();
   2190      }
   2191    }
   2192    return nullptr;
   2193  }
   2194 
   2195  /**
   2196   * GetLastListItemElement() returns the last list item element in the
   2197   * post-order tree traversal of the DOM.  I.e., returns the last list
   2198   * element whose close tag appears at last.
   2199   */
   2200  static Element* GetLastListItemElement(const Element& aListElement) {
   2201    MOZ_ASSERT(HTMLEditUtils::IsListElement(aListElement));
   2202    for (nsIContent* maybeLastListItem = aListElement.GetLastChild();
   2203         maybeLastListItem;) {
   2204      if (HTMLEditUtils::IsListItemElement(*maybeLastListItem)) {
   2205        return maybeLastListItem->AsElement();
   2206      }
   2207      if (maybeLastListItem->HasChildren()) {
   2208        maybeLastListItem = maybeLastListItem->GetLastChild();
   2209        continue;
   2210      }
   2211      if (maybeLastListItem->GetPreviousSibling()) {
   2212        maybeLastListItem = maybeLastListItem->GetPreviousSibling();
   2213        continue;
   2214      }
   2215      for (Element* parent = maybeLastListItem->GetParentElement(); parent;
   2216           parent = parent->GetParentElement()) {
   2217        maybeLastListItem = nullptr;
   2218        if (parent == &aListElement) {
   2219          return nullptr;
   2220        }
   2221        if (parent->GetPreviousSibling()) {
   2222          maybeLastListItem = parent->GetPreviousSibling();
   2223          break;
   2224        }
   2225      }
   2226    }
   2227    return nullptr;
   2228  }
   2229 
   2230  /**
   2231   * GetFirstTableCellElementChild() and GetLastTableCellElementChild()
   2232   * return the first/last element child of <tr> element if it's a table
   2233   * cell element.
   2234   */
   2235  static Element* GetFirstTableCellElementChild(
   2236      const Element& aTableRowElement) {
   2237    MOZ_ASSERT(aTableRowElement.IsHTMLElement(nsGkAtoms::tr));
   2238    Element* const firstElementChild = aTableRowElement.GetFirstElementChild();
   2239    return HTMLEditUtils::IsTableCellElement(firstElementChild)
   2240               ? firstElementChild
   2241               : nullptr;
   2242  }
   2243  static Element* GetLastTableCellElementChild(
   2244      const Element& aTableRowElement) {
   2245    MOZ_ASSERT(aTableRowElement.IsHTMLElement(nsGkAtoms::tr));
   2246    Element* const lastElementChild = aTableRowElement.GetLastElementChild();
   2247    return HTMLEditUtils::IsTableCellElement(lastElementChild)
   2248               ? lastElementChild
   2249               : nullptr;
   2250  }
   2251 
   2252  /**
   2253   * GetPreviousTableCellElementSibling() and GetNextTableCellElementSibling()
   2254   * return a table cell element of previous/next element sibling of given
   2255   * content node if and only if the element sibling is a table cell element.
   2256   */
   2257  static Element* GetPreviousTableCellElementSibling(
   2258      const nsIContent& aChildOfTableRow) {
   2259    MOZ_ASSERT(aChildOfTableRow.GetParentNode());
   2260    MOZ_ASSERT(aChildOfTableRow.GetParentNode()->IsHTMLElement(nsGkAtoms::tr));
   2261    Element* const previousElementSibling =
   2262        aChildOfTableRow.GetPreviousElementSibling();
   2263    return HTMLEditUtils::IsTableCellElement(previousElementSibling)
   2264               ? previousElementSibling
   2265               : nullptr;
   2266  }
   2267  static Element* GetNextTableCellElementSibling(
   2268      const nsIContent& aChildOfTableRow) {
   2269    MOZ_ASSERT(aChildOfTableRow.GetParentNode());
   2270    MOZ_ASSERT(aChildOfTableRow.GetParentNode()->IsHTMLElement(nsGkAtoms::tr));
   2271    Element* const nextElementSibling =
   2272        aChildOfTableRow.GetNextElementSibling();
   2273    return HTMLEditUtils::IsTableCellElement(nextElementSibling)
   2274               ? nextElementSibling
   2275               : nullptr;
   2276  }
   2277 
   2278  /**
   2279   * GetMostDistantAncestorInlineElement() returns the most distant ancestor
   2280   * inline element between aContent and the aEditingHost.  Even if aEditingHost
   2281   * is an inline element, this method never returns aEditingHost as the result.
   2282   * Optionally, you can specify ancestor limiter content node.  This guarantees
   2283   * that the result is a descendant of aAncestorLimiter if aContent is a
   2284   * descendant of aAncestorLimiter.
   2285   */
   2286  static nsIContent* GetMostDistantAncestorInlineElement(
   2287      const nsIContent& aContent, BlockInlineCheck aBlockInlineCheck,
   2288      const Element* aEditingHost = nullptr,
   2289      const nsIContent* aAncestorLimiter = nullptr) {
   2290    aBlockInlineCheck = UseComputedDisplayStyleIfAuto(aBlockInlineCheck);
   2291    if (HTMLEditUtils::IsBlockElement(aContent, aBlockInlineCheck)) {
   2292      return nullptr;
   2293    }
   2294 
   2295    // If aNode is the editing host itself, there is no modifiable inline
   2296    // parent.
   2297    if (&aContent == aEditingHost || &aContent == aAncestorLimiter) {
   2298      return nullptr;
   2299    }
   2300 
   2301    // If aNode is outside of the <body> element, we don't support to edit
   2302    // such elements for now.
   2303    // XXX This should be MOZ_ASSERT after fixing bug 1413131 for avoiding
   2304    //     calling this expensive method.
   2305    if (aEditingHost && !aContent.IsInclusiveDescendantOf(aEditingHost)) {
   2306      return nullptr;
   2307    }
   2308 
   2309    if (!aContent.GetParent()) {
   2310      return const_cast<nsIContent*>(&aContent);
   2311    }
   2312 
   2313    // Looks for the highest inline parent in the editing host.
   2314    nsIContent* topMostInlineContent = const_cast<nsIContent*>(&aContent);
   2315    for (Element* element : aContent.AncestorsOfType<Element>()) {
   2316      if (element == aEditingHost || element == aAncestorLimiter ||
   2317          HTMLEditUtils::IsBlockElement(*element, aBlockInlineCheck)) {
   2318        break;
   2319      }
   2320      topMostInlineContent = element;
   2321    }
   2322    return topMostInlineContent;
   2323  }
   2324 
   2325  /**
   2326   * GetMostDistantAncestorEditableEmptyInlineElement() returns most distant
   2327   * ancestor which only has aEmptyContent or its ancestor, editable and
   2328   * inline element.
   2329   */
   2330  static Element* GetMostDistantAncestorEditableEmptyInlineElement(
   2331      const nsIContent& aEmptyContent, BlockInlineCheck aBlockInlineCheck,
   2332      const Element* aEditingHost = nullptr,
   2333      const nsIContent* aAncestorLimiter = nullptr) {
   2334    if (&aEmptyContent == aEditingHost || &aEmptyContent == aAncestorLimiter) {
   2335      return nullptr;
   2336    }
   2337    aBlockInlineCheck = UseComputedDisplayStyleIfAuto(aBlockInlineCheck);
   2338    nsIContent* lastEmptyContent = const_cast<nsIContent*>(&aEmptyContent);
   2339    for (Element* element : aEmptyContent.AncestorsOfType<Element>()) {
   2340      if (element == aEditingHost || element == aAncestorLimiter) {
   2341        break;
   2342      }
   2343      if (!HTMLEditUtils::IsInlineContent(*element, aBlockInlineCheck) ||
   2344          !HTMLEditUtils::IsSimplyEditableNode(*element)) {
   2345        break;
   2346      }
   2347      if (element->GetChildCount() > 1) {
   2348        for (const nsIContent* child = element->GetFirstChild(); child;
   2349             child = child->GetNextSibling()) {
   2350          if (child == lastEmptyContent || child->IsComment()) {
   2351            continue;
   2352          }
   2353          return lastEmptyContent != &aEmptyContent
   2354                     ? Element::FromNode(lastEmptyContent)
   2355                     : nullptr;
   2356        }
   2357      }
   2358      lastEmptyContent = element;
   2359    }
   2360    return lastEmptyContent != &aEmptyContent
   2361               ? Element::FromNode(lastEmptyContent)
   2362               : nullptr;
   2363  }
   2364 
   2365  /**
   2366   * GetElementIfOnlyOneSelected() returns an element if aRange selects only
   2367   * the element node (and its descendants).
   2368   */
   2369  static Element* GetElementIfOnlyOneSelected(const AbstractRange& aRange) {
   2370    return GetElementIfOnlyOneSelected(EditorRawDOMRange(aRange));
   2371  }
   2372  template <typename EditorDOMPointType>
   2373  static Element* GetElementIfOnlyOneSelected(
   2374      const EditorDOMRangeBase<EditorDOMPointType>& aRange) {
   2375    if (!aRange.IsPositioned() || aRange.Collapsed()) {
   2376      return nullptr;
   2377    }
   2378    const auto& start = aRange.StartRef();
   2379    const auto& end = aRange.EndRef();
   2380    if (NS_WARN_IF(!start.IsSetAndValid()) ||
   2381        NS_WARN_IF(!end.IsSetAndValid()) ||
   2382        start.GetContainer() != end.GetContainer()) {
   2383      return nullptr;
   2384    }
   2385    nsIContent* childAtStart = start.GetChild();
   2386    if (!childAtStart || !childAtStart->IsElement()) {
   2387      return nullptr;
   2388    }
   2389    // If start child is not the last sibling and only if end child is its
   2390    // next sibling, the start child is selected.
   2391    if (childAtStart->GetNextSibling()) {
   2392      return childAtStart->GetNextSibling() == end.GetChild()
   2393                 ? childAtStart->AsElement()
   2394                 : nullptr;
   2395    }
   2396    // If start child is the last sibling and only if no child at the end,
   2397    // the start child is selected.
   2398    return !end.GetChild() ? childAtStart->AsElement() : nullptr;
   2399  }
   2400 
   2401  static Element* GetTableCellElementIfOnlyOneSelected(
   2402      const AbstractRange& aRange) {
   2403    Element* element = HTMLEditUtils::GetElementIfOnlyOneSelected(aRange);
   2404    return HTMLEditUtils::IsTableCellElement(element) ? element : nullptr;
   2405  }
   2406 
   2407  /**
   2408   * GetFirstSelectedTableCellElement() returns a table cell element (i.e.,
   2409   * `<td>` or `<th>` if and only if first selection range selects only a
   2410   * table cell element.
   2411   */
   2412  static Element* GetFirstSelectedTableCellElement(
   2413      const Selection& aSelection) {
   2414    if (!aSelection.RangeCount()) {
   2415      return nullptr;
   2416    }
   2417    const nsRange* firstRange = aSelection.GetRangeAt(0);
   2418    if (NS_WARN_IF(!firstRange) || NS_WARN_IF(!firstRange->IsPositioned())) {
   2419      return nullptr;
   2420    }
   2421    return GetTableCellElementIfOnlyOneSelected(*firstRange);
   2422  }
   2423 
   2424  /**
   2425   * GetInclusiveFirstChildWhichHasOneChild() returns the deepest element whose
   2426   * tag name is one of `aFirstElementName` and `aOtherElementNames...` if and
   2427   * only if the elements have only one child node.   In other words, when
   2428   * this method meets an element which does not matches any of the tag name
   2429   * or it has no children or 2+ children.
   2430   *
   2431   * XXX This method must be implemented without treating edge cases.  So, the
   2432   *     behavior is odd.  E.g., why can we ignore non-editable node at counting
   2433   *     each children?  Why do we dig non-editable aNode or first child of its
   2434   *     descendants?
   2435   */
   2436  template <typename FirstElementName, typename... OtherElementNames>
   2437  static Element* GetInclusiveDeepestFirstChildWhichHasOneChild(
   2438      const nsINode& aNode, const WalkTreeOptions& aOptions,
   2439      BlockInlineCheck aBlockInlineCheck, FirstElementName aFirstElementName,
   2440      OtherElementNames... aOtherElementNames) {
   2441    if (!aNode.IsElement()) {
   2442      return nullptr;
   2443    }
   2444    Element* parentElement = nullptr;
   2445    for (nsIContent* content = const_cast<nsIContent*>(aNode.AsContent());
   2446         content && content->IsElement() &&
   2447         content->IsAnyOfHTMLElements(aFirstElementName, aOtherElementNames...);
   2448         // XXX Why do we scan only the first child of every element?  If it's
   2449         //     not editable, why do we ignore it when aOptions specifies so.
   2450         content = content->GetFirstChild()) {
   2451      if (HTMLEditUtils::CountChildren(*content, aOptions, aBlockInlineCheck) !=
   2452          1) {
   2453        return content->AsElement();
   2454      }
   2455      parentElement = content->AsElement();
   2456    }
   2457    return parentElement;
   2458  }
   2459 
   2460  /**
   2461   * Get the first line break in aElement.  This scans only leaf nodes so
   2462   * if a <br> element has children illegally, it'll be ignored.
   2463   *
   2464   * @param aElement    The element which may have a <br> element or a
   2465   *                    preformatted linefeed.
   2466   */
   2467  template <typename EditorLineBreakType>
   2468  static Maybe<EditorLineBreakType> GetFirstLineBreak(
   2469      const dom::Element& aElement) {
   2470    for (nsIContent* content = HTMLEditUtils::GetFirstLeafContent(
   2471             aElement, {LeafNodeType::OnlyLeafNode});
   2472         content; content = HTMLEditUtils::GetNextContent(
   2473                      *content,
   2474                      {WalkTreeOption::IgnoreDataNodeExceptText,
   2475                       WalkTreeOption::IgnoreWhiteSpaceOnlyText},
   2476                      BlockInlineCheck::Unused, &aElement)) {
   2477      if (auto* brElement = dom::HTMLBRElement::FromNode(*content)) {
   2478        return Some(EditorLineBreakType(*brElement));
   2479      }
   2480      if (auto* textNode = Text::FromNode(*content)) {
   2481        if (EditorUtils::IsNewLinePreformatted(*textNode)) {
   2482          uint32_t offset = textNode->DataBuffer().FindChar(kNewLine);
   2483          if (offset != dom::CharacterDataBuffer::kNotFound) {
   2484            return Some(EditorLineBreakType(*textNode, offset));
   2485          }
   2486        }
   2487      }
   2488    }
   2489    return Nothing();
   2490  }
   2491 
   2492  enum class ScanLineBreak {
   2493    AtEndOfBlock,
   2494    BeforeBlock,
   2495  };
   2496  /**
   2497   * Return last <br> element or last text node ending with a preserved line
   2498   * break of/before aBlockElement.
   2499   * Note that the result may be non-editable and/or non-removable.
   2500   */
   2501  template <typename EditorLineBreakType>
   2502  static Maybe<EditorLineBreakType> GetUnnecessaryLineBreak(
   2503      const Element& aBlockElement, ScanLineBreak aScanLineBreak);
   2504 
   2505  /**
   2506   * Return following <br> element from aPoint if and only if it's immediately
   2507   * before a block boundary but it's not necessary to make the preceding
   2508   * empty line of the block boundary visible anymore.
   2509   * Note that the result may be non-editable and/or non-removable linebreak.
   2510   */
   2511  template <typename EditorLineBreakType, typename EditorDOMPointType>
   2512  [[nodiscard]] static Maybe<EditorLineBreakType>
   2513  GetFollowingUnnecessaryLineBreak(const EditorDOMPointType& aPoint);
   2514 
   2515  /**
   2516   * IsInTableCellSelectionMode() returns true when Gecko's editor thinks that
   2517   * selection is in a table cell selection mode.
   2518   * Note that Gecko's editor traditionally treats selection as in table cell
   2519   * selection mode when first range selects a table cell element.  I.e., even
   2520   * if `nsFrameSelection` is not in table cell selection mode, this may return
   2521   * true.
   2522   */
   2523  static bool IsInTableCellSelectionMode(const Selection& aSelection) {
   2524    return GetFirstSelectedTableCellElement(aSelection) != nullptr;
   2525  }
   2526 
   2527  static EditAction GetEditActionForInsert(const nsAtom& aTagName);
   2528  static EditAction GetEditActionForRemoveList(const nsAtom& aTagName);
   2529  static EditAction GetEditActionForInsert(const Element& aElement);
   2530  static EditAction GetEditActionForFormatText(const nsAtom& aProperty,
   2531                                               const nsAtom* aAttribute,
   2532                                               bool aToSetStyle);
   2533  static EditAction GetEditActionForAlignment(const nsAString& aAlignType);
   2534 
   2535  /**
   2536   * GetPreviousNonCollapsibleCharOffset() returns offset of previous
   2537   * character which is not collapsible white-space characters.
   2538   */
   2539  enum class WalkTextOption {
   2540    TreatNBSPsCollapsible,
   2541  };
   2542  using WalkTextOptions = EnumSet<WalkTextOption>;
   2543  static Maybe<uint32_t> GetPreviousNonCollapsibleCharOffset(
   2544      const EditorDOMPointInText& aPoint,
   2545      const WalkTextOptions& aWalkTextOptions = {}) {
   2546    MOZ_ASSERT(aPoint.IsSetAndValid());
   2547    return GetPreviousNonCollapsibleCharOffset(
   2548        *aPoint.ContainerAs<Text>(), aPoint.Offset(), aWalkTextOptions);
   2549  }
   2550  static Maybe<uint32_t> GetPreviousNonCollapsibleCharOffset(
   2551      const Text& aTextNode, uint32_t aOffset,
   2552      const WalkTextOptions& aWalkTextOptions = {}) {
   2553    if (MOZ_UNLIKELY(!aOffset)) {
   2554      return Nothing{};
   2555    }
   2556    MOZ_ASSERT(aOffset <= aTextNode.TextDataLength());
   2557    if (EditorUtils::IsWhiteSpacePreformatted(aTextNode)) {
   2558      return Some(aOffset - 1);
   2559    }
   2560    WhitespaceOptions whitespaceOptions{
   2561        WhitespaceOption::FormFeedIsSignificant};
   2562    if (EditorUtils::IsNewLinePreformatted(aTextNode)) {
   2563      whitespaceOptions += WhitespaceOption::NewLineIsSignificant;
   2564    }
   2565    if (aWalkTextOptions.contains(WalkTextOption::TreatNBSPsCollapsible)) {
   2566      whitespaceOptions += WhitespaceOption::TreatNBSPAsCollapsible;
   2567    }
   2568    const uint32_t prevVisibleCharOffset =
   2569        aTextNode.DataBuffer().RFindNonWhitespaceChar(whitespaceOptions,
   2570                                                      aOffset - 1);
   2571    return prevVisibleCharOffset != dom::CharacterDataBuffer::kNotFound
   2572               ? Some(prevVisibleCharOffset)
   2573               : Nothing();
   2574  }
   2575 
   2576  /**
   2577   * GetNextNonCollapsibleCharOffset() returns offset of next character which is
   2578   * not collapsible white-space characters.
   2579   */
   2580  static Maybe<uint32_t> GetNextNonCollapsibleCharOffset(
   2581      const EditorDOMPointInText& aPoint,
   2582      const WalkTextOptions& aWalkTextOptions = {}) {
   2583    MOZ_ASSERT(aPoint.IsSetAndValid());
   2584    return GetNextNonCollapsibleCharOffset(*aPoint.ContainerAs<Text>(),
   2585                                           aPoint.Offset(), aWalkTextOptions);
   2586  }
   2587  static Maybe<uint32_t> GetNextNonCollapsibleCharOffset(
   2588      const Text& aTextNode, uint32_t aOffset,
   2589      const WalkTextOptions& aWalkTextOptions = {}) {
   2590    return GetInclusiveNextNonCollapsibleCharOffset(aTextNode, aOffset + 1,
   2591                                                    aWalkTextOptions);
   2592  }
   2593 
   2594  /**
   2595   * GetInclusiveNextNonCollapsibleCharOffset() returns offset of inclusive next
   2596   * character which is not collapsible white-space characters.
   2597   */
   2598  template <typename PT, typename CT>
   2599  static Maybe<uint32_t> GetInclusiveNextNonCollapsibleCharOffset(
   2600      const EditorDOMPointBase<PT, CT>& aPoint,
   2601      const WalkTextOptions& aWalkTextOptions = {}) {
   2602    static_assert(std::is_same<PT, RefPtr<Text>>::value ||
   2603                  std::is_same<PT, Text*>::value);
   2604    MOZ_ASSERT(aPoint.IsSetAndValid());
   2605    return GetInclusiveNextNonCollapsibleCharOffset(
   2606        *aPoint.template ContainerAs<Text>(), aPoint.Offset(),
   2607        aWalkTextOptions);
   2608  }
   2609  static Maybe<uint32_t> GetInclusiveNextNonCollapsibleCharOffset(
   2610      const Text& aTextNode, uint32_t aOffset,
   2611      const WalkTextOptions& aWalkTextOptions = {}) {
   2612    if (MOZ_UNLIKELY(aOffset >= aTextNode.TextDataLength())) {
   2613      return Nothing();
   2614    }
   2615    MOZ_ASSERT(aOffset <= aTextNode.TextDataLength());
   2616    if (EditorUtils::IsWhiteSpacePreformatted(aTextNode)) {
   2617      return Some(aOffset);
   2618    }
   2619    WhitespaceOptions whitespaceOptions{
   2620        WhitespaceOption::FormFeedIsSignificant};
   2621    if (EditorUtils::IsNewLinePreformatted(aTextNode)) {
   2622      whitespaceOptions += WhitespaceOption::NewLineIsSignificant;
   2623    }
   2624    if (aWalkTextOptions.contains(WalkTextOption::TreatNBSPsCollapsible)) {
   2625      whitespaceOptions += WhitespaceOption::TreatNBSPAsCollapsible;
   2626    }
   2627    const uint32_t inclusiveNextVisibleCharOffset =
   2628        aTextNode.DataBuffer().FindNonWhitespaceChar(whitespaceOptions,
   2629                                                     aOffset);
   2630    if (inclusiveNextVisibleCharOffset != dom::CharacterDataBuffer::kNotFound) {
   2631      return Some(inclusiveNextVisibleCharOffset);
   2632    }
   2633    return Nothing();
   2634  }
   2635 
   2636  /**
   2637   * GetFirstWhiteSpaceOffsetCollapsedWith() returns first collapsible
   2638   * white-space offset which is collapsed with a white-space at the given
   2639   * position.  I.e., the character at the position must be a collapsible
   2640   * white-space.
   2641   */
   2642  template <typename PT, typename CT>
   2643  static uint32_t GetFirstWhiteSpaceOffsetCollapsedWith(
   2644      const EditorDOMPointBase<PT, CT>& aPoint,
   2645      const WalkTextOptions& aWalkTextOptions = {}) {
   2646    static_assert(std::is_same<PT, RefPtr<Text>>::value ||
   2647                  std::is_same<PT, Text*>::value);
   2648    MOZ_ASSERT(aPoint.IsSetAndValid());
   2649    MOZ_ASSERT(!aPoint.IsEndOfContainer());
   2650    MOZ_ASSERT_IF(
   2651        aWalkTextOptions.contains(WalkTextOption::TreatNBSPsCollapsible),
   2652        aPoint.IsCharCollapsibleASCIISpaceOrNBSP());
   2653    MOZ_ASSERT_IF(
   2654        !aWalkTextOptions.contains(WalkTextOption::TreatNBSPsCollapsible),
   2655        aPoint.IsCharCollapsibleASCIISpace());
   2656    return GetFirstWhiteSpaceOffsetCollapsedWith(
   2657        *aPoint.template ContainerAs<Text>(), aPoint.Offset(),
   2658        aWalkTextOptions);
   2659  }
   2660  static uint32_t GetFirstWhiteSpaceOffsetCollapsedWith(
   2661      const Text& aTextNode, uint32_t aOffset,
   2662      const WalkTextOptions& aWalkTextOptions = {}) {
   2663    MOZ_ASSERT(aOffset < aTextNode.TextLength());
   2664    MOZ_ASSERT_IF(
   2665        aWalkTextOptions.contains(WalkTextOption::TreatNBSPsCollapsible),
   2666        EditorRawDOMPoint(&aTextNode, aOffset)
   2667            .IsCharCollapsibleASCIISpaceOrNBSP());
   2668    MOZ_ASSERT_IF(
   2669        !aWalkTextOptions.contains(WalkTextOption::TreatNBSPsCollapsible),
   2670        EditorRawDOMPoint(&aTextNode, aOffset).IsCharCollapsibleASCIISpace());
   2671    if (!aOffset) {
   2672      return 0;
   2673    }
   2674    Maybe<uint32_t> previousVisibleCharOffset =
   2675        GetPreviousNonCollapsibleCharOffset(aTextNode, aOffset,
   2676                                            aWalkTextOptions);
   2677    return previousVisibleCharOffset.isSome()
   2678               ? previousVisibleCharOffset.value() + 1
   2679               : 0;
   2680  }
   2681 
   2682  /**
   2683   * GetPreviousPreformattedNewLineInTextNode() returns a point which points
   2684   * previous preformatted linefeed if there is and aPoint is in a text node.
   2685   * If the node's linefeed characters are not preformatted or aPoint is not
   2686   * in a text node, this returns unset DOM point.
   2687   */
   2688  template <typename EditorDOMPointType, typename ArgEditorDOMPointType>
   2689  static EditorDOMPointType GetPreviousPreformattedNewLineInTextNode(
   2690      const ArgEditorDOMPointType& aPoint) {
   2691    if (!aPoint.IsInTextNode() || aPoint.IsStartOfContainer() ||
   2692        !EditorUtils::IsNewLinePreformatted(
   2693            *aPoint.template ContainerAs<Text>())) {
   2694      return EditorDOMPointType();
   2695    }
   2696    const Text& textNode = *aPoint.template ContainerAs<Text>();
   2697    MOZ_ASSERT(aPoint.Offset() <= textNode.DataBuffer().GetLength());
   2698    const uint32_t previousLineBreakOffset =
   2699        textNode.DataBuffer().RFindChar('\n', aPoint.Offset() - 1u);
   2700    return previousLineBreakOffset != dom::CharacterDataBuffer::kNotFound
   2701               ? EditorDOMPointType(&textNode, previousLineBreakOffset)
   2702               : EditorDOMPointType();
   2703  }
   2704 
   2705  /**
   2706   * GetInclusiveNextPreformattedNewLineInTextNode() returns a point which
   2707   * points inclusive next preformatted linefeed if there is and aPoint is in a
   2708   * text node. If the node's linefeed characters are not preformatted or aPoint
   2709   * is not in a text node, this returns unset DOM point.
   2710   */
   2711  template <typename EditorDOMPointType, typename ArgEditorDOMPointType>
   2712  static EditorDOMPointType GetInclusiveNextPreformattedNewLineInTextNode(
   2713      const ArgEditorDOMPointType& aPoint) {
   2714    if (!aPoint.IsInTextNode() || aPoint.IsEndOfContainer() ||
   2715        !EditorUtils::IsNewLinePreformatted(
   2716            *aPoint.template ContainerAs<Text>())) {
   2717      return EditorDOMPointType();
   2718    }
   2719    const Text& textNode = *aPoint.template ContainerAs<Text>();
   2720    MOZ_ASSERT(aPoint.Offset() <= textNode.DataBuffer().GetLength());
   2721    const uint32_t inclusiveNextVisibleCharOffset =
   2722        textNode.DataBuffer().FindChar('\n', aPoint.Offset());
   2723    return inclusiveNextVisibleCharOffset != dom::CharacterDataBuffer::kNotFound
   2724               ? EditorDOMPointType(&textNode, inclusiveNextVisibleCharOffset)
   2725               : EditorDOMPointType();
   2726  }
   2727 
   2728  /**
   2729   * Get the first visible char offset in aText.  I.e., this returns invisible
   2730   * white-space length at start of aText.  If there is no visible char in
   2731   * aText, this returns the text data length.
   2732   * Note that WSRunScanner::GetFirstVisiblePoint() may return different `Text`
   2733   * node point, but this does not scan following `Text` nodes even if aText
   2734   * is completely invisible.
   2735   */
   2736  [[nodiscard]] static uint32_t GetFirstVisibleCharOffset(const Text& aText);
   2737 
   2738  /**
   2739   * Get next offset of the last visible char in aText.  I.e., this returns
   2740   * the first offset of invisible trailing white-spaces.  If there is no
   2741   * invisible trailing white-spaces in aText, this returns 0.
   2742   * Note that WSRunScanner::GetAfterLastVisiblePoint() may return different
   2743   * `Text` node point, but this does not scan preceding `Text` nodes even if
   2744   * aText is completely invisible.
   2745   */
   2746  [[nodiscard]] static uint32_t GetOffsetAfterLastVisibleChar(
   2747      const Text& aText);
   2748 
   2749  /**
   2750   * Get the number of invisible white-spaces in the white-space sequence.  Note
   2751   * that some invisible white-spaces may be after the first visible character.
   2752   * E.g., "SP SP NBSP SP SP NBSP".  If this Text follows a block boundary, the
   2753   * first SPs are the leading invisible white-spaces, and the first NBSP is the
   2754   * first visible character.  However, following 2 SPs are collapsed to one.
   2755   * Therefore, one of them is counted as an invisible white-space.
   2756   *
   2757   * Note that this assumes that all white-spaces starting from aOffset and
   2758   * ending by aOffset + aLength are collapsible white-spaces including NBSPs.
   2759   */
   2760  [[nodiscard]] static uint32_t GetInvisibleWhiteSpaceCount(
   2761      const Text& aText, uint32_t aOffset = 0u, uint32_t aLength = UINT32_MAX);
   2762 
   2763  /**
   2764   * GetGoodCaretPointFor() returns a good point to collapse `Selection`
   2765   * after handling edit action with aDirectionAndAmount.
   2766   *
   2767   * @param aContent            The content where you want to put caret
   2768   *                            around.
   2769   * @param aDirectionAndAmount Muse be one of eNext, eNextWord, eToEndOfLine,
   2770   *                            ePrevious, ePreviousWord and eToBeggingOfLine.
   2771   *                            Set the direction of handled edit action.
   2772   */
   2773  template <typename EditorDOMPointType>
   2774  static EditorDOMPointType GetGoodCaretPointFor(
   2775      nsIContent& aContent, nsIEditor::EDirection aDirectionAndAmount) {
   2776    MOZ_ASSERT(nsIEditor::EDirectionIsValidExceptNone(aDirectionAndAmount));
   2777 
   2778    // XXX Why don't we check whether the candidate position is enable or not?
   2779    //     When the result is not editable point, caret will be enclosed in
   2780    //     the non-editable content.
   2781 
   2782    // If we can put caret in aContent, return start or end in it.
   2783    if (aContent.IsText() || HTMLEditUtils::IsContainerNode(aContent) ||
   2784        NS_WARN_IF(!aContent.GetParentNode())) {
   2785      return EditorDOMPointType(
   2786          &aContent, nsIEditor::DirectionIsDelete(aDirectionAndAmount)
   2787                         ? 0
   2788                         : aContent.Length());
   2789    }
   2790 
   2791    // If we are going forward, put caret at aContent itself.
   2792    if (nsIEditor::DirectionIsDelete(aDirectionAndAmount)) {
   2793      return EditorDOMPointType(&aContent);
   2794    }
   2795 
   2796    // If we are going backward, put caret to next node unless aContent is an
   2797    // invisible `<br>` element.
   2798    // XXX Shouldn't we put caret to first leaf of the next node?
   2799    if (!HTMLEditUtils::IsInvisibleBRElement(aContent)) {
   2800      EditorDOMPointType ret(EditorDOMPointType::After(aContent));
   2801      NS_WARNING_ASSERTION(ret.IsSet(), "Failed to set after aContent");
   2802      return ret;
   2803    }
   2804 
   2805    // Otherwise, we should put caret at the invisible `<br>` element.
   2806    return EditorDOMPointType(&aContent);
   2807  }
   2808 
   2809  /**
   2810   * GetBetterInsertionPointFor() returns better insertion point to insert
   2811   * aContentToInsert.
   2812   *
   2813   * @param aContentToInsert    The content to insert.
   2814   * @param aPointToInsert      A candidate point to insert the node.
   2815   * @return                    Better insertion point if next visible node
   2816   *                            is a <br> element and previous visible node
   2817   *                            is neither none, another <br> element nor
   2818   *                            different block level element.
   2819   */
   2820  template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
   2821  static EditorDOMPointType GetBetterInsertionPointFor(
   2822      const nsIContent& aContentToInsert,
   2823      const EditorDOMPointTypeInput& aPointToInsert);
   2824 
   2825  /**
   2826   * GetBetterCaretPositionToInsertText() returns better point to put caret
   2827   * if aPoint is near a text node or in non-container node.
   2828   */
   2829  template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
   2830  static EditorDOMPointType GetBetterCaretPositionToInsertText(
   2831      const EditorDOMPointTypeInput& aPoint);
   2832 
   2833  /**
   2834   * ComputePointToPutCaretInElementIfOutside() returns a good point in aElement
   2835   * to put caret if aCurrentPoint is outside of aElement.
   2836   *
   2837   * @param aElement        The result is a point in aElement.
   2838   * @param aCurrentPoint   The current (candidate) caret point.  Only if this
   2839   *                        is outside aElement, returns a point in aElement.
   2840   */
   2841  template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
   2842  static Result<EditorDOMPointType, nsresult>
   2843  ComputePointToPutCaretInElementIfOutside(
   2844      const Element& aElement, const EditorDOMPointTypeInput& aCurrentPoint);
   2845 
   2846  /**
   2847   * Return a line break if aPoint is after a line break which is immediately
   2848   * before a block boundary.
   2849   */
   2850  template <typename EditorLineBreakType, typename EditorDOMPointType>
   2851  static Maybe<EditorLineBreakType>
   2852  GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
   2853      const EditorDOMPointType& aPoint, const Element& aEditingHost);
   2854 
   2855  /**
   2856   * Content-based query returns true if
   2857   * <mHTMLProperty mAttribute=mAttributeValue> effects aContent.  If there is
   2858   * such a element, but another element whose attribute value does not match
   2859   * with mAttributeValue is closer ancestor of aContent, then the distant
   2860   * ancestor does not effect aContent.
   2861   *
   2862   * @param aContent    The target of the query
   2863   * @param aStyle      The style which queries a representing element.
   2864   * @param aValue      Optional, the value of aStyle.mAttribute, example: blue
   2865   *                    in <font color="blue"> May be null.  Ignored if
   2866   *                    aStyle.mAttribute is null.
   2867   * @param aOutValue   [OUT] the value of the attribute, if returns true
   2868   * @return            true if <mHTMLProperty mAttribute=mAttributeValue>
   2869   *                    effects aContent.
   2870   */
   2871  [[nodiscard]] static bool IsInlineStyleSetByElement(
   2872      const nsIContent& aContent, const EditorInlineStyle& aStyle,
   2873      const nsAString* aValue, nsAString* aOutValue = nullptr);
   2874 
   2875  /**
   2876   * CollectAllChildren() collects all child nodes of aParentNode.
   2877   */
   2878  static void CollectAllChildren(
   2879      const nsINode& aParentNode,
   2880      nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) {
   2881    MOZ_ASSERT(aOutArrayOfContents.IsEmpty());
   2882    aOutArrayOfContents.SetCapacity(aParentNode.GetChildCount());
   2883    for (nsIContent* childContent = aParentNode.GetFirstChild(); childContent;
   2884         childContent = childContent->GetNextSibling()) {
   2885      aOutArrayOfContents.AppendElement(*childContent);
   2886    }
   2887  }
   2888 
   2889  /**
   2890   * CollectChildren() collects child nodes of aNode (starting from
   2891   * first editable child, but may return non-editable children after it).
   2892   *
   2893   * @param aNode               Parent node of retrieving children.
   2894   * @param aOutArrayOfContents [out] This method will inserts found children
   2895   *                            into this array.
   2896   * @param aIndexToInsertChildren      Starting from this index, found
   2897   *                                    children will be inserted to the array.
   2898   * @param aOptions            Options to scan the children.
   2899   * @return                    Number of found children.
   2900   */
   2901  static size_t CollectChildren(
   2902      const nsINode& aNode,
   2903      nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
   2904      const CollectChildrenOptions& aOptions) {
   2905    return HTMLEditUtils::CollectChildren(aNode, aOutArrayOfContents, 0u,
   2906                                          aOptions);
   2907  }
   2908  static size_t CollectChildren(
   2909      const nsINode& aNode,
   2910      nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
   2911      size_t aIndexToInsertChildren, const CollectChildrenOptions& aOptions);
   2912 
   2913  /**
   2914   * CollectEmptyInlineContainerDescendants() appends empty inline elements in
   2915   * aNode to aOutArrayOfContents.  Although it's array of nsIContent, the
   2916   * instance will be elements.
   2917   *
   2918   * @param aNode               The node whose descendants may have empty inline
   2919   *                            elements.
   2920   * @param aOutArrayOfContents [out] This method will append found descendants
   2921   *                            into this array.
   2922   * @param aOptions            The option which element should be treated as
   2923   *                            empty.
   2924   * @param aBlockInlineCheck   Whether use computed style or HTML default style
   2925   *                            when consider block vs. inline.
   2926   * @return                    Number of found elements.
   2927   */
   2928  static size_t CollectEmptyInlineContainerDescendants(
   2929      const nsINode& aNode,
   2930      nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
   2931      const EmptyCheckOptions& aOptions, BlockInlineCheck aBlockInlineCheck);
   2932 
   2933  /**
   2934   * Check whether aElement has attributes except the name aAttribute and
   2935   * "_moz_*" attributes.
   2936   */
   2937  [[nodiscard]] static bool ElementHasAttribute(const Element& aElement) {
   2938    return ElementHasAttributeExcept(aElement, *nsGkAtoms::_empty,
   2939                                     *nsGkAtoms::empty, *nsGkAtoms::_empty);
   2940  }
   2941  [[nodiscard]] static bool ElementHasAttributeExcept(
   2942      const Element& aElement, const nsAtom& aAttribute) {
   2943    return ElementHasAttributeExcept(aElement, aAttribute, *nsGkAtoms::_empty,
   2944                                     *nsGkAtoms::empty);
   2945  }
   2946  [[nodiscard]] static bool ElementHasAttributeExcept(
   2947      const Element& aElement, const nsAtom& aAttribute1,
   2948      const nsAtom& aAttribute2) {
   2949    return ElementHasAttributeExcept(aElement, aAttribute1, aAttribute2,
   2950                                     *nsGkAtoms::empty);
   2951  }
   2952  [[nodiscard]] static bool ElementHasAttributeExcept(
   2953      const Element& aElement, const nsAtom& aAttribute1,
   2954      const nsAtom& aAttribute2, const nsAtom& aAttribute3);
   2955 
   2956  enum class EditablePointOption {
   2957    // Do not ignore invisible collapsible white-spaces which are next to a
   2958    // block boundary.
   2959    RecognizeInvisibleWhiteSpaces,
   2960    // Stop at Comment node.
   2961    StopAtComment,
   2962    // Stop at List element.
   2963    StopAtListElement,
   2964    // Stop at ListItem element.
   2965    StopAtListItemElement,
   2966    // Stop at Table element.
   2967    StopAtTableElement,
   2968    // Stop at any table element.
   2969    StopAtAnyTableElement,
   2970  };
   2971  using EditablePointOptions = EnumSet<EditablePointOption>;
   2972 
   2973  friend std::ostream& operator<<(std::ostream& aStream,
   2974                                  const EditablePointOption& aOption);
   2975  friend std::ostream& operator<<(std::ostream& aStream,
   2976                                  const EditablePointOptions& aOptions);
   2977 
   2978 private:
   2979  class MOZ_STACK_CLASS AutoEditablePointChecker final {
   2980   public:
   2981    explicit AutoEditablePointChecker(const EditablePointOptions& aOptions)
   2982        : mIgnoreInvisibleText(!aOptions.contains(
   2983              EditablePointOption::RecognizeInvisibleWhiteSpaces)),
   2984          mIgnoreComment(
   2985              !aOptions.contains(EditablePointOption::StopAtComment)),
   2986          mStopAtListElement(
   2987              aOptions.contains(EditablePointOption::StopAtListElement)),
   2988          mStopAtListItemElement(
   2989              aOptions.contains(EditablePointOption::StopAtListItemElement)),
   2990          mStopAtTableElement(
   2991              aOptions.contains(EditablePointOption::StopAtTableElement)),
   2992          mStopAtAnyTableElement(
   2993              aOptions.contains(EditablePointOption::StopAtAnyTableElement)) {}
   2994 
   2995    [[nodiscard]] bool IgnoreInvisibleWhiteSpaces() const {
   2996      return mIgnoreInvisibleText;
   2997    }
   2998 
   2999    [[nodiscard]] bool NodeShouldBeIgnored(const nsIContent& aContent) const {
   3000      if (mIgnoreInvisibleText && aContent.IsText() &&
   3001          HTMLEditUtils::IsSimplyEditableNode(aContent) &&
   3002          !HTMLEditUtils::IsVisibleTextNode(*aContent.AsText())) {
   3003        return true;
   3004      }
   3005      if (mIgnoreComment && aContent.IsComment()) {
   3006        return true;
   3007      }
   3008      return false;
   3009    }
   3010 
   3011    [[nodiscard]] bool ShouldStopScanningAt(const nsIContent& aContent) const {
   3012      if (HTMLEditUtils::IsListElement(aContent)) {
   3013        return mStopAtListElement;
   3014      }
   3015      if (HTMLEditUtils::IsListItemElement(aContent)) {
   3016        return mStopAtListItemElement;
   3017      }
   3018      if (HTMLEditUtils::IsAnyTableElementExceptColumnElement(aContent)) {
   3019        return mStopAtAnyTableElement ||
   3020               (mStopAtTableElement &&
   3021                aContent.IsHTMLElement(nsGkAtoms::table));
   3022      }
   3023      return false;
   3024    }
   3025 
   3026   private:
   3027    const bool mIgnoreInvisibleText;
   3028    const bool mIgnoreComment;
   3029    const bool mStopAtListElement;
   3030    const bool mStopAtListItemElement;
   3031    const bool mStopAtTableElement;
   3032    const bool mStopAtAnyTableElement;
   3033  };
   3034 
   3035 public:
   3036  /**
   3037   * Return a point which points deepest editable start point of aContent.  This
   3038   * walks the DOM tree in aContent to search meaningful first descendant.  If
   3039   * EditablePointOption::IgnoreInvisibleText is specified, this returns first
   3040   * visible char offset if this reaches a visible `Text` first.  If there is an
   3041   * empty inline element such as <span>, this returns start of the inline
   3042   * element.  If this reaches non-editable element or non-container element
   3043   * like <img>, this returns the position.
   3044   */
   3045  template <typename EditorDOMPointType>
   3046  [[nodiscard]] static EditorDOMPointType GetDeepestEditableStartPointOf(
   3047      const nsIContent& aContent, const EditablePointOptions& aOptions) {
   3048    if (NS_WARN_IF(!EditorUtils::IsEditableContent(
   3049            aContent, EditorBase::EditorType::HTML))) {
   3050      return EditorDOMPointType();
   3051    }
   3052    const AutoEditablePointChecker checker(aOptions);
   3053    EditorRawDOMPoint result(&aContent, 0u);
   3054    while (true) {
   3055      nsIContent* firstChild = result.GetContainer()->GetFirstChild();
   3056      if (!firstChild) {
   3057        break;
   3058      }
   3059      // If the caller wants to skip invisible white-spaces, we should skip
   3060      // invisible text nodes.
   3061      nsIContent* meaningfulFirstChild = nullptr;
   3062      if (checker.NodeShouldBeIgnored(*firstChild)) {
   3063        // If we ignored a non-empty `Text`, it means that we're next to a block
   3064        // boundary.
   3065        for (nsIContent* nextSibling = firstChild->GetNextSibling();
   3066             nextSibling; nextSibling = nextSibling->GetNextSibling()) {
   3067          if (!checker.NodeShouldBeIgnored(*nextSibling) ||
   3068              checker.ShouldStopScanningAt(*nextSibling)) {
   3069            meaningfulFirstChild = nextSibling;
   3070            break;
   3071          }
   3072        }
   3073        if (!meaningfulFirstChild) {
   3074          break;
   3075        }
   3076      } else {
   3077        meaningfulFirstChild = firstChild;
   3078      }
   3079      if (meaningfulFirstChild->IsText()) {
   3080        if (checker.IgnoreInvisibleWhiteSpaces()) {
   3081          result.Set(meaningfulFirstChild,
   3082                     HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
   3083                         *meaningfulFirstChild->AsText(), 0u)
   3084                         .valueOr(0u));
   3085        } else {
   3086          result.Set(meaningfulFirstChild, 0u);
   3087        }
   3088        break;
   3089      }
   3090      if (checker.ShouldStopScanningAt(*meaningfulFirstChild) ||
   3091          !HTMLEditUtils::IsContainerNode(*meaningfulFirstChild) ||
   3092          !EditorUtils::IsEditableContent(*meaningfulFirstChild,
   3093                                          EditorBase::EditorType::HTML)) {
   3094        // FIXME: If the node is at middle of invisible white-spaces, we should
   3095        // ignore the node.
   3096        result.Set(meaningfulFirstChild);
   3097        break;
   3098      }
   3099      result.Set(meaningfulFirstChild, 0u);
   3100    }
   3101    return result.To<EditorDOMPointType>();
   3102  }
   3103 
   3104  /**
   3105   * Return a point which points deepest editable last point of aContent.  This
   3106   * walks the DOM tree in aContent to search meaningful last descendant.  If
   3107   * EditablePointOption::IgnoreInvisibleText is specified, this returns next
   3108   * offset of the last visible char if this reaches a visible `Text` first.  If
   3109   * there is an empty inline element such as <span>, this returns end of the
   3110   * inline element.  If this reaches non-editable element or non-container
   3111   * element like <img>, this returns the position after that.
   3112   */
   3113  template <typename EditorDOMPointType>
   3114  [[nodiscard]] static EditorDOMPointType GetDeepestEditableEndPointOf(
   3115      const nsIContent& aContent, const EditablePointOptions& aOptions) {
   3116    if (NS_WARN_IF(!EditorUtils::IsEditableContent(
   3117            aContent, EditorBase::EditorType::HTML))) {
   3118      return EditorDOMPointType();
   3119    }
   3120    const AutoEditablePointChecker checker(aOptions);
   3121    auto result = EditorRawDOMPoint::AtEndOf(aContent);
   3122    while (true) {
   3123      nsIContent* lastChild = result.GetContainer()->GetLastChild();
   3124      if (!lastChild) {
   3125        break;
   3126      }
   3127      // If the caller wants to skip invisible white-spaces, we should skip
   3128      // invisible text nodes.
   3129      nsIContent* meaningfulLastChild = nullptr;
   3130      // XXX Should we skip the lastChild if it's an invisible line break?
   3131      if (checker.NodeShouldBeIgnored(*lastChild)) {
   3132        for (nsIContent* nextSibling = lastChild->GetPreviousSibling();
   3133             nextSibling; nextSibling = nextSibling->GetPreviousSibling()) {
   3134          if (!checker.NodeShouldBeIgnored(*nextSibling) ||
   3135              checker.ShouldStopScanningAt(*nextSibling)) {
   3136            meaningfulLastChild = nextSibling;
   3137            break;
   3138          }
   3139        }
   3140        if (!meaningfulLastChild) {
   3141          break;
   3142        }
   3143      } else {
   3144        meaningfulLastChild = lastChild;
   3145      }
   3146      if (meaningfulLastChild->IsText()) {
   3147        if (checker.IgnoreInvisibleWhiteSpaces()) {
   3148          const Maybe<uint32_t> visibleCharOffset =
   3149              HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
   3150                  *meaningfulLastChild->AsText(),
   3151                  meaningfulLastChild->AsText()->TextDataLength());
   3152          if (visibleCharOffset.isNothing()) {
   3153            result = EditorRawDOMPoint::AtEndOf(*meaningfulLastChild);
   3154          } else {
   3155            result.Set(meaningfulLastChild, visibleCharOffset.value() + 1u);
   3156          }
   3157        } else {
   3158          result = EditorRawDOMPoint::AtEndOf(*meaningfulLastChild);
   3159        }
   3160        break;
   3161      }
   3162      if (checker.ShouldStopScanningAt(*meaningfulLastChild) ||
   3163          !HTMLEditUtils::IsContainerNode(*meaningfulLastChild) ||
   3164          !EditorUtils::IsEditableContent(*meaningfulLastChild,
   3165                                          EditorBase::EditorType::HTML)) {
   3166        // FIXME: If the node is at middle of invisible white-spaces, we should
   3167        // ignore the node.
   3168        result.SetAfter(meaningfulLastChild);
   3169        break;
   3170      }
   3171      result = EditorRawDOMPoint::AtEndOf(*lastChild);
   3172    }
   3173    return result.To<EditorDOMPointType>();
   3174  }
   3175 
   3176  /**
   3177   * Get `#[0-9a-f]{6}` style HTML color value if aColorValue is valid value
   3178   * for color-specifying attribute. The result is useful to set attributes
   3179   * of HTML elements which take a color value.
   3180   *
   3181   * @param aColorValue         [in] Should be one of `#[0-9a-fA-Z]{3}`,
   3182   *                            `#[0-9a-fA-Z]{3}` or a color name.
   3183   * @param aNormalizedValue    [out] Set to `#[0-9a-f]{6}` style color code
   3184   *                            if this returns true.  Otherwise, returns
   3185   *                            aColorValue as-is.
   3186   * @return                    true if aColorValue is valid.  Otherwise, false.
   3187   */
   3188  static bool GetNormalizedHTMLColorValue(const nsAString& aColorValue,
   3189                                          nsAString& aNormalizedValue);
   3190 
   3191  /**
   3192   * Return true if aColorValue may be a CSS specific color value or general
   3193   * keywords of CSS.
   3194   */
   3195  [[nodiscard]] static bool MaybeCSSSpecificColorValue(
   3196      const nsAString& aColorValue);
   3197 
   3198  /**
   3199   * Return true if aColorValue can be specified to `color` value of <font>.
   3200   */
   3201  [[nodiscard]] static bool CanConvertToHTMLColorValue(
   3202      const nsAString& aColorValue);
   3203 
   3204  /**
   3205   * Convert aColorValue to `#[0-9a-f]{6}` style HTML color value.
   3206   */
   3207  static bool ConvertToNormalizedHTMLColorValue(const nsAString& aColorValue,
   3208                                                nsAString& aNormalizedValue);
   3209 
   3210  /**
   3211   * Get serialized color value (`rgb(...)` or `rgba(...)`) or "currentcolor"
   3212   * if aColorValue is valid. The result is useful to set CSS color property.
   3213   *
   3214   * @param aColorValue         [in] Should be valid CSS color value.
   3215   * @param aZeroAlphaColor     [in] If TransparentKeyword, aNormalizedValue is
   3216   *                            set to "transparent" if the alpha value is 0.
   3217   *                            Otherwise, `rgba(...)` value is set.
   3218   * @param aNormalizedValue    [out] Serialized color value or "currentcolor".
   3219   * @return                    true if aColorValue is valid.  Otherwise, false.
   3220   */
   3221  enum class ZeroAlphaColor { RGBAValue, TransparentKeyword };
   3222  static bool GetNormalizedCSSColorValue(const nsAString& aColorValue,
   3223                                         ZeroAlphaColor aZeroAlphaColor,
   3224                                         nsAString& aNormalizedValue);
   3225 
   3226  /**
   3227   * Check whether aColorA and aColorB are same color.
   3228   *
   3229   * @param aTransparentKeyword Whether allow to treat "transparent" keyword
   3230   *                            as a valid value or an invalid value.
   3231   * @return                    If aColorA and aColorB are valid values and
   3232   *                            mean same color, returns true.
   3233   */
   3234  enum class TransparentKeyword { Invalid, Allowed };
   3235  static bool IsSameHTMLColorValue(const nsAString& aColorA,
   3236                                   const nsAString& aColorB,
   3237                                   TransparentKeyword aTransparentKeyword);
   3238 
   3239  /**
   3240   * Check whether aColorA and aColorB are same color.
   3241   *
   3242   * @return                    If aColorA and aColorB are valid values and
   3243   *                            mean same color, returns true.
   3244   */
   3245  template <typename CharType>
   3246  static bool IsSameCSSColorValue(const nsTSubstring<CharType>& aColorA,
   3247                                  const nsTSubstring<CharType>& aColorB);
   3248 
   3249  /**
   3250   * Return true if aColor is completely transparent.
   3251   */
   3252  [[nodiscard]] static bool IsTransparentCSSColor(const nsAString& aColor);
   3253 
   3254 private:
   3255  static bool CanNodeContain(nsHTMLTag aParentTagId, nsHTMLTag aChildTagId);
   3256  static bool IsContainerNode(nsHTMLTag aTagId);
   3257 
   3258  static bool CanCrossContentBoundary(nsIContent& aContent,
   3259                                      TableBoundary aHowToTreatTableBoundary) {
   3260    const bool cannotCrossBoundary =
   3261        (aHowToTreatTableBoundary == TableBoundary::NoCrossAnyTableElement &&
   3262         HTMLEditUtils::IsAnyTableElementExceptColumnElement(aContent)) ||
   3263        (aHowToTreatTableBoundary == TableBoundary::NoCrossTableElement &&
   3264         aContent.IsHTMLElement(nsGkAtoms::table));
   3265    return !cannotCrossBoundary;
   3266  }
   3267 
   3268  static bool IsContentIgnored(const nsIContent& aContent,
   3269                               const WalkTreeOptions& aOptions) {
   3270    if (aOptions.contains(WalkTreeOption::IgnoreNonEditableNode) &&
   3271        !EditorUtils::IsEditableContent(aContent,
   3272                                        EditorUtils::EditorType::HTML)) {
   3273      return true;
   3274    }
   3275    if (aOptions.contains(WalkTreeOption::IgnoreDataNodeExceptText) &&
   3276        !EditorUtils::IsElementOrText(aContent)) {
   3277      return true;
   3278    }
   3279    if (aOptions.contains(WalkTreeOption::IgnoreWhiteSpaceOnlyText) &&
   3280        aContent.IsText() &&
   3281        const_cast<Text*>(aContent.AsText())->TextIsOnlyWhitespace()) {
   3282      return true;
   3283    }
   3284    return false;
   3285  }
   3286 
   3287  static uint32_t CountChildren(const nsINode& aNode,
   3288                                const WalkTreeOptions& aOptions,
   3289                                BlockInlineCheck aBlockInlineCheck) {
   3290    uint32_t count = 0;
   3291    for (nsIContent* child = aNode.GetFirstChild(); child;
   3292         child = child->GetNextSibling()) {
   3293      if (HTMLEditUtils::IsContentIgnored(*child, aOptions)) {
   3294        continue;
   3295      }
   3296      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   3297          HTMLEditUtils::IsBlockElement(
   3298              *child,
   3299              UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) {
   3300        break;
   3301      }
   3302      ++count;
   3303    }
   3304    return count;
   3305  }
   3306 
   3307  /**
   3308   * Helper for GetPreviousContent() and GetNextContent().
   3309   */
   3310  static nsIContent* GetAdjacentLeafContent(
   3311      const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
   3312      const WalkTreeOptions& aOptions, BlockInlineCheck aBlockInlineCheck,
   3313      const Element* aAncestorLimiter = nullptr);
   3314  static nsIContent* GetAdjacentContent(
   3315      const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
   3316      const WalkTreeOptions& aOptions, BlockInlineCheck aBlockInlineCheck,
   3317      const Element* aAncestorLimiter = nullptr);
   3318 
   3319  /**
   3320   * GetElementOfImmediateBlockBoundary() returns a block element if its
   3321   * block boundary and aContent may be first visible thing before/after the
   3322   * boundary.  And it may return a <br> element only when aContent is a
   3323   * text node and follows a <br> element because only in this case, the
   3324   * start white-spaces are invisible.  So the <br> element works same as
   3325   * a block boundary.
   3326   */
   3327  static Element* GetElementOfImmediateBlockBoundary(
   3328      const nsIContent& aContent, const WalkTreeDirection aDirection);
   3329 
   3330  /**
   3331   * Return true if parent element is a grid or flex container.
   3332   * Note that even if the parent is a grid/flex container, the
   3333   * <display-outside> of aMaybeFlexOrGridItemContent may be "inline" if the
   3334   * parent is also a grid/flex item but has `display:contents`.
   3335   */
   3336  [[nodiscard]] static bool ParentElementIsGridOrFlexContainer(
   3337      const nsIContent& aMaybeFlexOrGridItemContent);
   3338 };
   3339 
   3340 /**
   3341 * DefinitionListItemScanner() scans given `<dl>` element's children.
   3342 * Then, you can check whether `<dt>` and/or `<dd>` elements are in it.
   3343 */
   3344 class MOZ_STACK_CLASS DefinitionListItemScanner final {
   3345  using Element = dom::Element;
   3346 
   3347 public:
   3348  DefinitionListItemScanner() = delete;
   3349  explicit DefinitionListItemScanner(Element& aDLElement) {
   3350    MOZ_ASSERT(aDLElement.IsHTMLElement(nsGkAtoms::dl));
   3351    for (nsIContent* child = aDLElement.GetFirstChild(); child;
   3352         child = child->GetNextSibling()) {
   3353      if (child->IsHTMLElement(nsGkAtoms::dt)) {
   3354        mDTFound = true;
   3355        if (mDDFound) {
   3356          break;
   3357        }
   3358        continue;
   3359      }
   3360      if (child->IsHTMLElement(nsGkAtoms::dd)) {
   3361        mDDFound = true;
   3362        if (mDTFound) {
   3363          break;
   3364        }
   3365        continue;
   3366      }
   3367    }
   3368  }
   3369 
   3370  bool DTElementFound() const { return mDTFound; }
   3371  bool DDElementFound() const { return mDDFound; }
   3372 
   3373 private:
   3374  bool mDTFound = false;
   3375  bool mDDFound = false;
   3376 };
   3377 
   3378 /**
   3379 * SelectedTableCellScanner() scans all table cell elements which are selected
   3380 * by each selection range.  Note that if 2nd or later ranges do not select
   3381 * only one table cell element, the ranges are just ignored.
   3382 */
   3383 class MOZ_STACK_CLASS SelectedTableCellScanner final {
   3384  using Element = dom::Element;
   3385  using Selection = dom::Selection;
   3386 
   3387 public:
   3388  SelectedTableCellScanner() = delete;
   3389  explicit SelectedTableCellScanner(const Selection& aSelection) {
   3390    Element* firstSelectedCellElement =
   3391        HTMLEditUtils::GetFirstSelectedTableCellElement(aSelection);
   3392    if (!firstSelectedCellElement) {
   3393      return;  // We're not in table cell selection mode.
   3394    }
   3395    mSelectedCellElements.SetCapacity(aSelection.RangeCount());
   3396    mSelectedCellElements.AppendElement(*firstSelectedCellElement);
   3397    const uint32_t rangeCount = aSelection.RangeCount();
   3398    for (const uint32_t i : IntegerRange(1u, rangeCount)) {
   3399      MOZ_ASSERT(aSelection.RangeCount() == rangeCount);
   3400      nsRange* range = aSelection.GetRangeAt(i);
   3401      if (MOZ_UNLIKELY(NS_WARN_IF(!range)) ||
   3402          MOZ_UNLIKELY(NS_WARN_IF(!range->IsPositioned()))) {
   3403        continue;  // Shouldn't occur in normal conditions.
   3404      }
   3405      // Just ignore selection ranges which do not select only one table
   3406      // cell element.  This is possible case if web apps sets multiple
   3407      // selections and first range selects a table cell element.
   3408      if (Element* selectedCellElement =
   3409              HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(*range)) {
   3410        mSelectedCellElements.AppendElement(*selectedCellElement);
   3411      }
   3412    }
   3413  }
   3414 
   3415  explicit SelectedTableCellScanner(const AutoClonedRangeArray& aRanges);
   3416 
   3417  bool IsInTableCellSelectionMode() const {
   3418    return !mSelectedCellElements.IsEmpty();
   3419  }
   3420 
   3421  const nsTArray<OwningNonNull<Element>>& ElementsRef() const {
   3422    return mSelectedCellElements;
   3423  }
   3424 
   3425  /**
   3426   * GetFirstElement() and GetNextElement() are stateful iterator methods.
   3427   * This is useful to port legacy code which used old `nsITableEditor` API.
   3428   */
   3429  Element* GetFirstElement() const {
   3430    MOZ_ASSERT(!mSelectedCellElements.IsEmpty());
   3431    mIndex = 0;
   3432    return !mSelectedCellElements.IsEmpty() ? mSelectedCellElements[0].get()
   3433                                            : nullptr;
   3434  }
   3435  Element* GetNextElement() const {
   3436    MOZ_ASSERT(mIndex < mSelectedCellElements.Length());
   3437    return ++mIndex < mSelectedCellElements.Length()
   3438               ? mSelectedCellElements[mIndex].get()
   3439               : nullptr;
   3440  }
   3441 
   3442 private:
   3443  AutoTArray<OwningNonNull<Element>, 16> mSelectedCellElements;
   3444  mutable size_t mIndex = 0;
   3445 };
   3446 
   3447 }  // namespace mozilla
   3448 
   3449 #endif  // #ifndef HTMLEditUtils_h