tor-browser

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

HTMLEditUtils.cpp (142996B)


      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 #include "HTMLEditUtils.h"
      7 
      8 #include "AutoClonedRangeArray.h"  // for AutoClonedRangeArray
      9 #include "CSSEditUtils.h"          // for CSSEditUtils
     10 #include "EditAction.h"            // for EditAction
     11 #include "EditorBase.h"            // for EditorBase, EditorType
     12 #include "EditorDOMPoint.h"        // for EditorDOMPoint, etc.
     13 #include "EditorForwards.h"        // for CollectChildrenOptions
     14 #include "EditorUtils.h"           // for EditorUtils
     15 #include "HTMLEditHelpers.h"       // for EditorInlineStyle
     16 #include "WSRunScanner.h"          // for WSRunScanner
     17 
     18 #include "mozilla/Assertions.h"  // for MOZ_ASSERT, etc.
     19 #include "mozilla/Attributes.h"
     20 #include "mozilla/StaticPrefs_editor.h"       // for StaticPrefs::editor_
     21 #include "mozilla/RangeUtils.h"               // for RangeUtils
     22 #include "mozilla/dom/CharacterDataBuffer.h"  // for CharacterDataBuffer
     23 #include "mozilla/dom/DocumentInlines.h"      // for GetBodyElement()
     24 #include "mozilla/dom/Element.h"              // for Element, nsINode
     25 #include "mozilla/dom/ElementInlines.h"  // for IsContentEditablePlainTextOnly()
     26 #include "mozilla/dom/HTMLAnchorElement.h"
     27 #include "mozilla/dom/HTMLBodyElement.h"
     28 #include "mozilla/dom/HTMLInputElement.h"
     29 #include "mozilla/ServoCSSParser.h"  // for ServoCSSParser
     30 #include "mozilla/dom/StaticRange.h"
     31 #include "mozilla/dom/Text.h"  // for Text
     32 
     33 #include "nsAString.h"    // for nsAString::IsEmpty
     34 #include "nsAtom.h"       // for nsAtom
     35 #include "nsAttrValue.h"  // nsAttrValue
     36 #include "nsCaseTreatment.h"
     37 #include "nsCOMPtr.h"            // for nsCOMPtr, operator==, etc.
     38 #include "nsComputedDOMStyle.h"  // for nsComputedDOMStyle
     39 #include "nsDebug.h"             // for NS_ASSERTION, etc.
     40 #include "nsElementTable.h"      // for nsHTMLElement
     41 #include "nsError.h"             // for NS_SUCCEEDED
     42 #include "nsGkAtoms.h"           // for nsGkAtoms, nsGkAtoms::a, etc.
     43 #include "nsHTMLTags.h"
     44 #include "nsIContentInlines.h"  // for nsIContent::IsInDesignMode(), etc.
     45 #include "nsIObjectLoadingContent.h"
     46 #include "nsLiteralString.h"     // for NS_LITERAL_STRING
     47 #include "nsNameSpaceManager.h"  // for kNameSpaceID_None
     48 #include "nsPrintfCString.h"     // nsPringfCString
     49 #include "nsString.h"            // for nsAutoString
     50 #include "nsStyledElement.h"
     51 #include "nsStyleStruct.h"  // for StyleDisplay
     52 #include "nsStyleUtil.h"    // for nsStyleUtil
     53 #include "nsTextFrame.h"    // for nsTextFrame
     54 
     55 namespace mozilla {
     56 
     57 using namespace dom;
     58 using EditorType = EditorBase::EditorType;
     59 
     60 template nsIContent* HTMLEditUtils::GetPreviousContent(
     61    const EditorDOMPoint& aPoint, const WalkTreeOptions& aOptions,
     62    BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter);
     63 template nsIContent* HTMLEditUtils::GetPreviousContent(
     64    const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions,
     65    BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter);
     66 template nsIContent* HTMLEditUtils::GetPreviousContent(
     67    const EditorDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
     68    BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter);
     69 template nsIContent* HTMLEditUtils::GetPreviousContent(
     70    const EditorRawDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
     71    BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter);
     72 template nsIContent* HTMLEditUtils::GetNextContent(
     73    const EditorDOMPoint& aPoint, const WalkTreeOptions& aOptions,
     74    BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter);
     75 template nsIContent* HTMLEditUtils::GetNextContent(
     76    const EditorRawDOMPoint& aPoint, const WalkTreeOptions& aOptions,
     77    BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter);
     78 template nsIContent* HTMLEditUtils::GetNextContent(
     79    const EditorDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
     80    BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter);
     81 template nsIContent* HTMLEditUtils::GetNextContent(
     82    const EditorRawDOMPointInText& aPoint, const WalkTreeOptions& aOptions,
     83    BlockInlineCheck aBlockInlineCheck, const Element* aAncestorLimiter);
     84 
     85 template EditorDOMPoint HTMLEditUtils::GetPreviousEditablePoint(
     86    nsIContent& aContent, const Element* aAncestorLimiter,
     87    InvisibleWhiteSpaces aInvisibleWhiteSpaces,
     88    TableBoundary aHowToTreatTableBoundary);
     89 template EditorRawDOMPoint HTMLEditUtils::GetPreviousEditablePoint(
     90    nsIContent& aContent, const Element* aAncestorLimiter,
     91    InvisibleWhiteSpaces aInvisibleWhiteSpaces,
     92    TableBoundary aHowToTreatTableBoundary);
     93 template EditorDOMPoint HTMLEditUtils::GetNextEditablePoint(
     94    nsIContent& aContent, const Element* aAncestorLimiter,
     95    InvisibleWhiteSpaces aInvisibleWhiteSpaces,
     96    TableBoundary aHowToTreatTableBoundary);
     97 template EditorRawDOMPoint HTMLEditUtils::GetNextEditablePoint(
     98    nsIContent& aContent, const Element* aAncestorLimiter,
     99    InvisibleWhiteSpaces aInvisibleWhiteSpaces,
    100    TableBoundary aHowToTreatTableBoundary);
    101 
    102 template EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible(
    103    const EditorDOMPoint& aPoint, const Element& aEditingHost);
    104 template EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible(
    105    const EditorRawDOMPoint& aPoint, const Element& aEditingHost);
    106 template EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible(
    107    const EditorDOMPointInText& aPoint, const Element& aEditingHost);
    108 template EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible(
    109    const EditorRawDOMPointInText& aPoint, const Element& aEditingHost);
    110 
    111 template nsIContent* HTMLEditUtils::GetContentToPreserveInlineStyles(
    112    const EditorDOMPoint& aPoint, const Element& aEditingHost);
    113 template nsIContent* HTMLEditUtils::GetContentToPreserveInlineStyles(
    114    const EditorRawDOMPoint& aPoint, const Element& aEditingHost);
    115 
    116 template EditorDOMPoint HTMLEditUtils::GetBetterInsertionPointFor(
    117    const nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert);
    118 template EditorRawDOMPoint HTMLEditUtils::GetBetterInsertionPointFor(
    119    const nsIContent& aContentToInsert,
    120    const EditorRawDOMPoint& aPointToInsert);
    121 template EditorDOMPoint HTMLEditUtils::GetBetterInsertionPointFor(
    122    const nsIContent& aContentToInsert,
    123    const EditorRawDOMPoint& aPointToInsert);
    124 template EditorRawDOMPoint HTMLEditUtils::GetBetterInsertionPointFor(
    125    const nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert);
    126 
    127 template EditorDOMPoint HTMLEditUtils::GetBetterCaretPositionToInsertText(
    128    const EditorDOMPoint& aPoint);
    129 template EditorDOMPoint HTMLEditUtils::GetBetterCaretPositionToInsertText(
    130    const EditorRawDOMPoint& aPoint);
    131 template EditorRawDOMPoint HTMLEditUtils::GetBetterCaretPositionToInsertText(
    132    const EditorDOMPoint& aPoint);
    133 template EditorRawDOMPoint HTMLEditUtils::GetBetterCaretPositionToInsertText(
    134    const EditorRawDOMPoint& aPoint);
    135 
    136 template Result<EditorDOMPoint, nsresult>
    137 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
    138    const Element& aElement, const EditorDOMPoint& aCurrentPoint);
    139 template Result<EditorRawDOMPoint, nsresult>
    140 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
    141    const Element& aElement, const EditorDOMPoint& aCurrentPoint);
    142 template Result<EditorDOMPoint, nsresult>
    143 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
    144    const Element& aElement, const EditorRawDOMPoint& aCurrentPoint);
    145 template Result<EditorRawDOMPoint, nsresult>
    146 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
    147    const Element& aElement, const EditorRawDOMPoint& aCurrentPoint);
    148 
    149 template Maybe<EditorLineBreak>
    150 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
    151    const EditorDOMPoint&, const Element&);
    152 template Maybe<EditorRawLineBreak>
    153 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
    154    const EditorDOMPoint&, const Element&);
    155 template Maybe<EditorLineBreak>
    156 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
    157    const EditorRawDOMPoint&, const Element&);
    158 template Maybe<EditorRawLineBreak>
    159 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
    160    const EditorRawDOMPoint&, const Element&);
    161 template Maybe<EditorLineBreak>
    162 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
    163    const EditorDOMPointInText&, const Element&);
    164 template Maybe<EditorRawLineBreak>
    165 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
    166    const EditorDOMPointInText&, const Element&);
    167 template Maybe<EditorLineBreak>
    168 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
    169    const EditorRawDOMPointInText&, const Element&);
    170 template Maybe<EditorRawLineBreak>
    171 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
    172    const EditorRawDOMPointInText&, const Element&);
    173 
    174 template bool HTMLEditUtils::IsSameCSSColorValue(const nsAString& aColorA,
    175                                                 const nsAString& aColorB);
    176 template bool HTMLEditUtils::IsSameCSSColorValue(const nsACString& aColorA,
    177                                                 const nsACString& aColorB);
    178 
    179 template Maybe<EditorLineBreak> HTMLEditUtils::GetFollowingUnnecessaryLineBreak(
    180    const EditorDOMPoint& aPoint);
    181 template Maybe<EditorLineBreak> HTMLEditUtils::GetFollowingUnnecessaryLineBreak(
    182    const EditorRawDOMPoint& aPoint);
    183 template Maybe<EditorLineBreak> HTMLEditUtils::GetFollowingUnnecessaryLineBreak(
    184    const EditorDOMPointInText& aPoint);
    185 template Maybe<EditorLineBreak> HTMLEditUtils::GetFollowingUnnecessaryLineBreak(
    186    const EditorRawDOMPointInText& aPoint);
    187 template Maybe<EditorRawLineBreak>
    188 HTMLEditUtils::GetFollowingUnnecessaryLineBreak(const EditorDOMPoint& aPoint);
    189 template Maybe<EditorRawLineBreak>
    190 HTMLEditUtils::GetFollowingUnnecessaryLineBreak(
    191    const EditorRawDOMPoint& aPoint);
    192 template Maybe<EditorRawLineBreak>
    193 HTMLEditUtils::GetFollowingUnnecessaryLineBreak(
    194    const EditorDOMPointInText& aPoint);
    195 template Maybe<EditorRawLineBreak>
    196 HTMLEditUtils::GetFollowingUnnecessaryLineBreak(
    197    const EditorRawDOMPointInText& aPoint);
    198 
    199 template bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary(
    200    const EditorDOMPoint& aPoint,
    201    IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak);
    202 template bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary(
    203    const EditorRawDOMPoint& aPoint,
    204    IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak);
    205 template bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary(
    206    const EditorDOMPointInText& aPoint,
    207    IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak);
    208 template bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary(
    209    const EditorRawDOMPointInText& aPoint,
    210    IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak);
    211 
    212 template Maybe<EditorLineBreak> HTMLEditUtils::GetUnnecessaryLineBreak(
    213    const Element& aBlockElement, ScanLineBreak aScanLineBreak);
    214 template Maybe<EditorRawLineBreak> HTMLEditUtils::GetUnnecessaryLineBreak(
    215    const Element& aBlockElement, ScanLineBreak aScanLineBreak);
    216 
    217 bool HTMLEditUtils::ElementIsEditableRoot(const Element& aElement) {
    218  MOZ_ASSERT(!aElement.IsInNativeAnonymousSubtree());
    219  if (NS_WARN_IF(!aElement.IsEditable()) ||
    220      NS_WARN_IF(!aElement.IsInComposedDoc())) {
    221    return false;
    222  }
    223  return !aElement.GetParent() ||                      // root element
    224         !aElement.GetParent()->IsEditable() ||        // editing host
    225         aElement.OwnerDoc()->GetBody() == &aElement;  // the <body>
    226 }
    227 
    228 bool HTMLEditUtils::CanContentsBeJoined(const nsIContent& aLeftContent,
    229                                        const nsIContent& aRightContent) {
    230  if (aLeftContent.NodeInfo()->NameAtom() !=
    231      aRightContent.NodeInfo()->NameAtom()) {
    232    return false;
    233  }
    234 
    235  if (!aLeftContent.IsElement()) {
    236    return true;  // can join text nodes, etc
    237  }
    238  MOZ_ASSERT(aRightContent.IsElement());
    239 
    240  if (aLeftContent.NodeInfo()->NameAtom() == nsGkAtoms::font) {
    241    const nsAttrValue* const leftSize =
    242        aLeftContent.AsElement()->GetParsedAttr(nsGkAtoms::size);
    243    const nsAttrValue* const rightSize =
    244        aRightContent.AsElement()->GetParsedAttr(nsGkAtoms::size);
    245    if (!leftSize ^ !rightSize || (leftSize && !leftSize->Equals(*rightSize))) {
    246      return false;
    247    }
    248 
    249    const nsAttrValue* const leftColor =
    250        aLeftContent.AsElement()->GetParsedAttr(nsGkAtoms::color);
    251    const nsAttrValue* const rightColor =
    252        aRightContent.AsElement()->GetParsedAttr(nsGkAtoms::color);
    253    if (!leftColor ^ !rightColor ||
    254        (leftColor && !leftColor->Equals(*rightColor))) {
    255      return false;
    256    }
    257 
    258    const nsAttrValue* const leftFace =
    259        aLeftContent.AsElement()->GetParsedAttr(nsGkAtoms::face);
    260    const nsAttrValue* const rightFace =
    261        aRightContent.AsElement()->GetParsedAttr(nsGkAtoms::face);
    262    if (!leftFace ^ !rightFace || (leftFace && !leftFace->Equals(*rightFace))) {
    263      return false;
    264    }
    265  }
    266  nsStyledElement* leftStyledElement =
    267      nsStyledElement::FromNode(const_cast<nsIContent*>(&aLeftContent));
    268  if (!leftStyledElement) {
    269    return false;
    270  }
    271  nsStyledElement* rightStyledElement =
    272      nsStyledElement::FromNode(const_cast<nsIContent*>(&aRightContent));
    273  if (!rightStyledElement) {
    274    return false;
    275  }
    276  return CSSEditUtils::DoStyledElementsHaveSameStyle(*leftStyledElement,
    277                                                     *rightStyledElement);
    278 }
    279 
    280 static bool IsHTMLBlockElementByDefault(const nsIContent& aContent) {
    281  if (!aContent.IsHTMLElement()) {
    282    return false;
    283  }
    284  if (aContent.IsHTMLElement(nsGkAtoms::br)) {  // shortcut for TextEditor
    285    MOZ_ASSERT(!nsHTMLElement::IsBlock(
    286        nsHTMLTags::CaseSensitiveAtomTagToId(nsGkAtoms::br)));
    287    return false;
    288  }
    289  // We want to treat these as block nodes even though nsHTMLElement says
    290  // they're not.
    291  if (aContent.IsAnyOfHTMLElements(
    292          nsGkAtoms::body, nsGkAtoms::head, nsGkAtoms::tbody, nsGkAtoms::thead,
    293          nsGkAtoms::tfoot, nsGkAtoms::tr, nsGkAtoms::th, nsGkAtoms::td,
    294          nsGkAtoms::dt, nsGkAtoms::dd)) {
    295    return true;
    296  }
    297 
    298  return nsHTMLElement::IsBlock(
    299      nsHTMLTags::CaseSensitiveAtomTagToId(aContent.NodeInfo()->NameAtom()));
    300 }
    301 
    302 bool HTMLEditUtils::IsBlockElement(const nsIContent& aContent,
    303                                   BlockInlineCheck aBlockInlineCheck) {
    304  MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Unused);
    305  MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Auto);
    306 
    307  if (MOZ_UNLIKELY(!aContent.IsElement())) {
    308    // FIXME: If aContent is a visible `Text` and a flex/grid item, we should
    309    // treat it as block.
    310    return false;
    311  }
    312  // If it's a <br>, we should always treat it as an inline element because
    313  // its preceding collapse white-spaces and another <br> works same as usual
    314  // even if you set its style to `display:block`.
    315  if (aContent.IsHTMLElement(nsGkAtoms::br)) {
    316    return false;
    317  }
    318  if (aBlockInlineCheck == BlockInlineCheck::UseHTMLDefaultStyle) {
    319    return IsHTMLBlockElementByDefault(aContent);
    320  }
    321  // Let's treat the document element and the body element is a block to avoid
    322  // complicated things which may be detected by fuzzing.
    323  if (aContent.OwnerDoc()->GetDocumentElement() == &aContent ||
    324      (aContent.IsHTMLElement(nsGkAtoms::body) &&
    325       aContent.OwnerDoc()->GetBodyElement() == &aContent)) {
    326    return true;
    327  }
    328  RefPtr<const ComputedStyle> elementStyle =
    329      nsComputedDOMStyle::GetComputedStyleNoFlush(aContent.AsElement());
    330  if (MOZ_UNLIKELY(!elementStyle)) {  // If aContent is not in the composed tree
    331    return IsHTMLBlockElementByDefault(aContent);
    332  }
    333  const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay();
    334  if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) {
    335    // Typically, we should not keep handling editing in invisible nodes, but if
    336    // we reach here, let's fallback to the default style for protecting the
    337    // structure as far as possible.
    338    return IsHTMLBlockElementByDefault(aContent);
    339  }
    340  // If the outside is not inline, treat it as block.
    341  if (!styleDisplay->IsInlineOutsideStyle()) {
    342    return true;
    343  }
    344  // Special case.  If aContent is a grid or flex item, we want to treat it as a
    345  // block to handle it with the general paths.
    346  if (HTMLEditUtils::ParentElementIsGridOrFlexContainer(aContent)) {
    347    return true;
    348  }
    349  // If we're checking display-inside, inline-block, etc should be a block too.
    350  return aBlockInlineCheck == BlockInlineCheck::UseComputedDisplayStyle &&
    351         styleDisplay->DisplayInside() == StyleDisplayInside::FlowRoot &&
    352         // Treat widgets as inline since they won't hide collapsible
    353         // white-spaces around them.
    354         styleDisplay->EffectiveAppearance() == StyleAppearance::None;
    355 }
    356 
    357 bool HTMLEditUtils::IsInlineContent(const nsIContent& aContent,
    358                                    BlockInlineCheck aBlockInlineCheck) {
    359  MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Unused);
    360  MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Auto);
    361 
    362  if (!aContent.IsElement()) {
    363    // FIXME: If aContent is a visible `Text` and a flex/grid item, we should
    364    // treat it as block.
    365    return true;
    366  }
    367  // If it's a <br>, we should always treat it as an inline element because
    368  // its preceding collapse white-spaces and another <br> works same as usual
    369  // even if you set its style to `display:block`.
    370  if (aContent.IsHTMLElement(nsGkAtoms::br)) {
    371    return true;
    372  }
    373  if (aBlockInlineCheck == BlockInlineCheck::UseHTMLDefaultStyle) {
    374    return !IsHTMLBlockElementByDefault(aContent);
    375  }
    376  // Let's treat the document element and the body element is a block to avoid
    377  // complicated things which may be detected by fuzzing.
    378  if (aContent.OwnerDoc()->GetDocumentElement() == &aContent ||
    379      (aContent.IsHTMLElement(nsGkAtoms::body) &&
    380       aContent.OwnerDoc()->GetBodyElement() == &aContent)) {
    381    return false;
    382  }
    383  RefPtr<const ComputedStyle> elementStyle =
    384      nsComputedDOMStyle::GetComputedStyleNoFlush(aContent.AsElement());
    385  if (MOZ_UNLIKELY(!elementStyle)) {  // If aContent is not in the composed tree
    386    return !IsHTMLBlockElementByDefault(aContent);
    387  }
    388  const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay();
    389  if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) {
    390    // Similar to IsBlockElement, let's fallback to refer the default style.
    391    // Note that if you change here, you may need to check the parent element
    392    // style if aContent.
    393    return !IsHTMLBlockElementByDefault(aContent);
    394  }
    395  // Special case.  If aContent is a grid or flex item, we want to treat it as a
    396  // block to handle it with the general paths.
    397  if (HTMLEditUtils::ParentElementIsGridOrFlexContainer(aContent)) {
    398    return false;
    399  }
    400  // Different block IsBlockElement, when the display-outside is inline, it's
    401  // simply an inline element.
    402  return styleDisplay->IsInlineOutsideStyle();
    403 }
    404 
    405 bool HTMLEditUtils::ParentElementIsGridOrFlexContainer(
    406    const nsIContent& aMaybeFlexOrGridItemContent) {
    407  if (!aMaybeFlexOrGridItemContent.IsElement()) {
    408    if (!aMaybeFlexOrGridItemContent.IsText() ||
    409        !aMaybeFlexOrGridItemContent.AsText()->TextDataLength()) {
    410      return false;
    411    }
    412    // FIXME: If aMaybeFlexOrGridItemContent has only collapsible white-spaces
    413    // and next to a block boundary, it's invisible and shouldn't be a flex/grid
    414    // item.  However, scanning block boundary requires to call this method.
    415  }
    416  Element* const parentElement = aMaybeFlexOrGridItemContent.GetParentElement();
    417  // Editable state does not affect to elements across shadow DOM boundaries.
    418  // Therefore, we don't need to treat aElement as an flex item nor a grid item
    419  // as so if aElement is a root element of a shadow DOM unless we'll support
    420  // inline editing host support better (all browsers do not handle surrounding
    421  // content of the inline editing host strictly, e.g., when inserting a
    422  // collapsible white-space at start or end of it).
    423  if (MOZ_UNLIKELY(!parentElement)) {
    424    return false;
    425  }
    426  // We should consider whether the element is a flex item or a grid item
    427  // without nsIFrame since we don't want to refresh the layout while
    428  // `HTMLEditor` handles an action to avoid to run script.
    429  const RefPtr<const ComputedStyle> elementStyle =
    430      nsComputedDOMStyle::GetComputedStyleNoFlush(
    431          aMaybeFlexOrGridItemContent.IsElement()
    432              ? aMaybeFlexOrGridItemContent.AsElement()
    433              : parentElement);
    434  if (MOZ_UNLIKELY(!elementStyle)) {
    435    return false;
    436  }
    437  const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay();
    438  if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) {
    439    return false;
    440  }
    441  const RefPtr<const ComputedStyle> parentElementStyle =
    442      aMaybeFlexOrGridItemContent.IsElement()
    443          ? nsComputedDOMStyle::GetComputedStyleNoFlush(parentElement)
    444          : elementStyle;
    445  if (MOZ_UNLIKELY(!parentElementStyle)) {
    446    return false;
    447  }
    448  const auto parentDisplayInside =
    449      parentElementStyle->StyleDisplay()->DisplayInside();
    450  return parentDisplayInside == StyleDisplayInside::Flex ||
    451         parentDisplayInside == StyleDisplayInside::Grid;
    452 }
    453 
    454 bool HTMLEditUtils::IsFlexOrGridItem(const nsIContent& aContent) {
    455  if (!HTMLEditUtils::ParentElementIsGridOrFlexContainer(aContent)) {
    456    return false;
    457  }
    458  // Note that if parent element's `display` is `contents`, the
    459  // `display-outside` style of aElement may not be block.  However, even in
    460  // such case, HTMLEditUtils::IsBlockElement() should return true.
    461  MOZ_ASSERT_IF(aContent.IsElement(),
    462                HTMLEditUtils::IsBlockElement(
    463                    *aContent.AsElement(),
    464                    BlockInlineCheck::UseComputedDisplayOutsideStyle));
    465  return true;
    466 }
    467 
    468 bool HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone(
    469    const nsIContent& aContent) {
    470  if (NS_WARN_IF(!aContent.IsInComposedDoc())) {
    471    return true;
    472  }
    473  for (const Element* element :
    474       aContent.InclusiveFlatTreeAncestorsOfType<Element>()) {
    475    RefPtr<const ComputedStyle> elementStyle =
    476        nsComputedDOMStyle::GetComputedStyleNoFlush(element);
    477    if (NS_WARN_IF(!elementStyle)) {
    478      continue;
    479    }
    480    const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay();
    481    if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) {
    482      return true;
    483    }
    484  }
    485  return false;
    486 }
    487 
    488 bool HTMLEditUtils::IsVisibleElementEvenIfLeafNode(const nsIContent& aContent) {
    489  if (!aContent.IsElement()) {
    490    return false;
    491  }
    492  // Assume non-HTML element is visible.
    493  if (!aContent.IsHTMLElement()) {
    494    return true;
    495  }
    496  // XXX Should we return false if the element is display:none?
    497  if (HTMLEditUtils::IsBlockElement(
    498          aContent, BlockInlineCheck::UseComputedDisplayStyle)) {
    499    return true;
    500  }
    501  // <br> element may not have a frame, but it always affects surrounding
    502  // content.  Therefore, it should be treated as visible.  The others which are
    503  // checked here are replace elements which provide something visible content.
    504  if (aContent.IsAnyOfHTMLElements(nsGkAtoms::applet, nsGkAtoms::br,
    505                                   nsGkAtoms::iframe, nsGkAtoms::img,
    506                                   nsGkAtoms::meter, nsGkAtoms::progress,
    507                                   nsGkAtoms::select, nsGkAtoms::textarea)) {
    508    return true;
    509  }
    510  if (const HTMLInputElement* inputElement =
    511          HTMLInputElement::FromNode(&aContent)) {
    512    return inputElement->ControlType() != FormControlType::InputHidden;
    513  }
    514  // If the element has a primary frame and it's not empty, the element is
    515  // visible.
    516  // XXX This method does not guarantee that the layout has already been
    517  // updated.  Therefore, this check might be wrong in the edge cases.
    518  // However, basically, editor apps should not depend on this path, this
    519  // is required if last <br> before a block boundary becomes visible because
    520  // of followed by empty but styled frame like <span style=padding:1px></span>.
    521  if (aContent.GetPrimaryFrame() &&
    522      !aContent.GetPrimaryFrame()->GetSize().IsEmpty()) {
    523    return true;
    524  }
    525  // Maybe, empty inline element such as <span>.
    526  return false;
    527 }
    528 
    529 bool HTMLEditUtils::IsInlineStyleElement(const nsIContent& aContent) {
    530  return aContent.IsAnyOfHTMLElements(
    531      nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::tt, nsGkAtoms::s,
    532      nsGkAtoms::strike, nsGkAtoms::big, nsGkAtoms::small, nsGkAtoms::sub,
    533      nsGkAtoms::sup, nsGkAtoms::font);
    534 }
    535 
    536 bool HTMLEditUtils::IsDisplayOutsideInline(const Element& aElement) {
    537  RefPtr<const ComputedStyle> elementStyle =
    538      nsComputedDOMStyle::GetComputedStyleNoFlush(&aElement);
    539  if (!elementStyle) {
    540    return false;
    541  }
    542  return elementStyle->StyleDisplay()->DisplayOutside() ==
    543         StyleDisplayOutside::Inline;
    544 }
    545 
    546 bool HTMLEditUtils::IsDisplayInsideFlowRoot(const Element& aElement) {
    547  RefPtr<const ComputedStyle> elementStyle =
    548      nsComputedDOMStyle::GetComputedStyleNoFlush(&aElement);
    549  if (!elementStyle) {
    550    return false;
    551  }
    552  return elementStyle->StyleDisplay()->DisplayInside() ==
    553         StyleDisplayInside::FlowRoot;
    554 }
    555 
    556 bool HTMLEditUtils::IsRemovableInlineStyleElement(Element& aElement) {
    557  if (!aElement.IsHTMLElement()) {
    558    return false;
    559  }
    560  // https://w3c.github.io/editing/execCommand.html#removeformat-candidate
    561  if (aElement.IsAnyOfHTMLElements(
    562          nsGkAtoms::abbr,  // Chrome ignores, but does not make sense.
    563          nsGkAtoms::acronym, nsGkAtoms::b,
    564          nsGkAtoms::bdi,  // Chrome ignores, but does not make sense.
    565          nsGkAtoms::bdo, nsGkAtoms::big, nsGkAtoms::cite, nsGkAtoms::code,
    566          // nsGkAtoms::del, Chrome ignores, but does not make sense but
    567          // execCommand unofficial draft excludes this.  Spec issue:
    568          // https://github.com/w3c/editing/issues/192
    569          nsGkAtoms::dfn, nsGkAtoms::em, nsGkAtoms::font, nsGkAtoms::i,
    570          nsGkAtoms::ins, nsGkAtoms::kbd,
    571          nsGkAtoms::mark,  // Chrome ignores, but does not make sense.
    572          nsGkAtoms::nobr, nsGkAtoms::q, nsGkAtoms::s, nsGkAtoms::samp,
    573          nsGkAtoms::small, nsGkAtoms::span, nsGkAtoms::strike,
    574          nsGkAtoms::strong, nsGkAtoms::sub, nsGkAtoms::sup, nsGkAtoms::tt,
    575          nsGkAtoms::u, nsGkAtoms::var)) {
    576    return true;
    577  }
    578  // If it's a <blink> element, we can remove it.
    579  nsAutoString tagName;
    580  aElement.GetTagName(tagName);
    581  return tagName.LowerCaseEqualsASCII("blink");
    582 }
    583 
    584 bool HTMLEditUtils::IsOutdentable(const nsIContent& aContent) {
    585  return aContent.IsAnyOfHTMLElements(
    586      nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl, nsGkAtoms::li, nsGkAtoms::dd,
    587      nsGkAtoms::dt, nsGkAtoms::blockquote);
    588 }
    589 
    590 bool HTMLEditUtils::IsHeadingElement(const nsIContent& aContent) {
    591  return aContent.IsAnyOfHTMLElements(nsGkAtoms::h1, nsGkAtoms::h2,
    592                                      nsGkAtoms::h3, nsGkAtoms::h4,
    593                                      nsGkAtoms::h5, nsGkAtoms::h6);
    594 }
    595 
    596 bool HTMLEditUtils::IsListItemElement(const nsIContent& aContent) {
    597  return aContent.IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::dd,
    598                                      nsGkAtoms::dt);
    599 }
    600 
    601 bool HTMLEditUtils::IsAnyTableElementExceptColumnElement(
    602    const nsIContent& aContent) {
    603  return aContent.IsAnyOfHTMLElements(
    604      nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::th,
    605      nsGkAtoms::thead, nsGkAtoms::tfoot, nsGkAtoms::tbody, nsGkAtoms::caption);
    606 }
    607 
    608 bool HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement(
    609    const nsIContent& aContent) {
    610  return aContent.IsAnyOfHTMLElements(
    611      nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::th, nsGkAtoms::thead,
    612      nsGkAtoms::tfoot, nsGkAtoms::tbody, nsGkAtoms::caption);
    613 }
    614 
    615 bool HTMLEditUtils::IsTableRowElement(const nsIContent& aContent) {
    616  return aContent.IsHTMLElement(nsGkAtoms::tr);
    617 }
    618 
    619 bool HTMLEditUtils::IsTableCellElement(const nsIContent& aContent) {
    620  return aContent.IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th);
    621 }
    622 
    623 bool HTMLEditUtils::IsTableCellOrCaptionElement(const nsIContent& aContent) {
    624  return aContent.IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th,
    625                                      nsGkAtoms::caption);
    626 }
    627 
    628 bool HTMLEditUtils::IsListElement(const nsIContent& aContent) {
    629  return aContent.IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol,
    630                                      nsGkAtoms::dl);
    631 }
    632 
    633 bool HTMLEditUtils::IsImageElement(const nsIContent& aContent) {
    634  // XXX How about <object> and <picture>?
    635  return aContent.IsHTMLElement(nsGkAtoms::img);
    636 }
    637 
    638 bool HTMLEditUtils::IsHyperlinkElement(const nsIContent& aContent) {
    639  const dom::HTMLAnchorElement* const anchor =
    640      dom::HTMLAnchorElement::FromNode(aContent);
    641  if (!anchor) {
    642    return false;
    643  }
    644  // XXX Isn't it enough to check whether the `href` value is empty?
    645  nsAutoCString tmpText;
    646  anchor->GetHref(tmpText);
    647  return !tmpText.IsEmpty();
    648 }
    649 
    650 bool HTMLEditUtils::IsNamedAnchorElement(const nsIContent& aContent) {
    651  const dom::HTMLAnchorElement* const anchor =
    652      dom::HTMLAnchorElement::FromNode(aContent);
    653  if (!anchor) {
    654    return false;
    655  }
    656  return anchor->HasName();
    657 }
    658 
    659 bool HTMLEditUtils::IsMozDivElement(const nsIContent& aContent) {
    660  return aContent.IsHTMLElement(nsGkAtoms::div) &&
    661         aContent.AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
    662                                           u"_moz"_ns, eIgnoreCase);
    663 }
    664 
    665 bool HTMLEditUtils::IsMailCiteElement(const Element& aElement) {
    666  // don't ask me why, but our html mailcites are id'd by "type=cite"...
    667  if (aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns,
    668                           eIgnoreCase)) {
    669    return true;
    670  }
    671 
    672  // ... but our plaintext mailcites by "_moz_quote=true".  go figure.
    673  if (aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozquote, u"true"_ns,
    674                           eIgnoreCase)) {
    675    return true;
    676  }
    677 
    678  return false;
    679 }
    680 
    681 bool HTMLEditUtils::IsReplacedElement(const Element& aElement) {
    682  if (!aElement.IsHTMLElement()) {
    683    // FIXME: Well known SVG, MathML elements should be tested here.
    684    return false;
    685  }
    686  if (aElement.IsHTMLElement(nsGkAtoms::input)) {
    687    return !aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
    688                                 nsGkAtoms::hidden, eIgnoreCase);
    689  }
    690  if (HTMLEditUtils::IsFormWidgetElement(aElement)) {
    691    return true;
    692  }
    693  // <object> is a special element, it shows its subtree when it does not load
    694  // its content.
    695  if (aElement.IsHTMLElement(nsGkAtoms::object)) {
    696    const nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent =
    697        do_QueryInterface(const_cast<Element*>(&aElement));
    698    uint32_t displayedType = nsIObjectLoadingContent::TYPE_FALLBACK;
    699    if (MOZ_LIKELY(objectLoadingContent)) {
    700      objectLoadingContent->GetDisplayedType(&displayedType);
    701    }
    702    return displayedType != nsIObjectLoadingContent::TYPE_FALLBACK;
    703  }
    704  return aElement.IsAnyOfHTMLElements(
    705      nsGkAtoms::audio,
    706      // In strictly speaking, <br> is not a replaced element, but treating it
    707      // as a replaced element makes HTMLEditor and its peers simpler.
    708      nsGkAtoms::br, nsGkAtoms::canvas, nsGkAtoms::embed, nsGkAtoms::iframe,
    709      nsGkAtoms::img,
    710      // <optgroup> and <option> are not replaced element actually but they
    711      // are treated as so for the compatibility with Chrome.
    712      // XXX I wonder if we can treat them as so only when they are in
    713      // <select>.
    714      nsGkAtoms::optgroup, nsGkAtoms::option, nsGkAtoms::video);
    715 }
    716 
    717 bool HTMLEditUtils::IsFormWidgetElement(const nsIContent& aContent) {
    718  return aContent.IsAnyOfHTMLElements(nsGkAtoms::textarea, nsGkAtoms::select,
    719                                      nsGkAtoms::button, nsGkAtoms::output,
    720                                      nsGkAtoms::progress, nsGkAtoms::meter,
    721                                      nsGkAtoms::input);
    722 }
    723 
    724 bool HTMLEditUtils::IsAlignAttrSupported(const nsIContent& aContent) {
    725  return aContent.IsAnyOfHTMLElements(
    726      nsGkAtoms::hr, nsGkAtoms::table, nsGkAtoms::tbody, nsGkAtoms::tfoot,
    727      nsGkAtoms::thead, nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::th,
    728      nsGkAtoms::div, nsGkAtoms::p, nsGkAtoms::h1, nsGkAtoms::h2, nsGkAtoms::h3,
    729      nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6);
    730 }
    731 
    732 bool HTMLEditUtils::IsVisibleTextNode(const Text& aText) {
    733  if (!aText.TextDataLength()) {
    734    return false;
    735  }
    736 
    737  Maybe<uint32_t> visibleCharOffset =
    738      HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(
    739          EditorDOMPointInText(&aText, 0));
    740  if (visibleCharOffset.isSome()) {
    741    return true;
    742  }
    743 
    744  // Now, all characters in aText is collapsible white-spaces.  The node is
    745  // invisible if next to block boundary.
    746  return !HTMLEditUtils::GetElementOfImmediateBlockBoundary(
    747             aText, WalkTreeDirection::Forward) &&
    748         !HTMLEditUtils::GetElementOfImmediateBlockBoundary(
    749             aText, WalkTreeDirection::Backward);
    750 }
    751 
    752 bool HTMLEditUtils::IsInVisibleTextFrames(nsPresContext* aPresContext,
    753                                          const Text& aText) {
    754  // TODO(dholbert): aPresContext is now unused; maybe we can remove it, here
    755  // and in IsEmptyNode?  We do use it as a signal (implicitly here,
    756  // more-explicitly in IsEmptyNode) that we are in a "SafeToAskLayout" case...
    757  // If/when we remove it, we should be sure we're not losing that signal of
    758  // strictness, since this function here does absolutely need to query layout.
    759  MOZ_ASSERT(aPresContext);
    760 
    761  if (!aText.TextDataLength()) {
    762    return false;
    763  }
    764 
    765  nsTextFrame* textFrame = do_QueryFrame(aText.GetPrimaryFrame());
    766  if (!textFrame) {
    767    return false;
    768  }
    769 
    770  return textFrame->HasVisibleText();
    771 }
    772 
    773 template <typename PT, typename CT>
    774 EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible(
    775    const EditorDOMPointBase<PT, CT>& aPoint, const Element& aEditingHost) {
    776  if (MOZ_UNLIKELY(!aPoint.IsInContentNode())) {
    777    return EditorDOMPoint();
    778  }
    779  MOZ_ASSERT(HTMLEditUtils::NodeIsEditableOrNotInComposedDoc(
    780      *aPoint.template ContainerAs<nsIContent>()));
    781  // First, if the container is an element node, get the next deepest point.
    782  EditorRawDOMPoint point = aPoint.template To<EditorRawDOMPoint>();
    783  if (point.IsContainerElement()) {
    784    for (nsIContent* child = point.GetChild(); child;
    785         child = child->GetFirstChild()) {
    786      if (child->IsHTMLElement(nsGkAtoms::br)) {
    787        return EditorDOMPoint();
    788      }
    789      if (!HTMLEditUtils::NodeIsEditableOrNotInComposedDoc(*child) ||
    790          HTMLEditUtils::IsBlockElement(
    791              *child, BlockInlineCheck::UseComputedDisplayOutsideStyle) ||
    792          (child->IsElement() && !HTMLEditUtils::IsContainerNode(*child))) {
    793        break;
    794      }
    795      point.Set(child, 0);
    796    }
    797  }
    798  // If the point is in a Text, check the next character in it to skip the
    799  // expensive check below.
    800  if (point.IsInTextNode() && !point.IsContainerEmpty()) {
    801    if (!point.IsStartOfContainer() &&
    802        !point.IsPreviousCharCollapsibleASCIISpace()) {
    803      return EditorDOMPoint();  // following a visible character.
    804    }
    805    if (!point.IsEndOfContainer()) {
    806      if (EditorUtils::IsWhiteSpacePreformatted(*point.ContainerAs<Text>())) {
    807        return EditorDOMPoint();  // followed by a visible character.
    808      }
    809      // NOTE: In the worst case, the fragment has a lot of collapsible
    810      // white-spaces after the point.  However, it won't occur with usual web
    811      // apps.  Instead, we should optimize the response time when user typing
    812      // keys in the usual web apps.
    813      const CharacterDataBuffer& characterDataBuffer =
    814          point.template ContainerAs<Text>()->DataBuffer();
    815      const uint32_t inclusiveNextVisibleCharOffset =
    816          characterDataBuffer.FindNonWhitespaceChar(
    817              EditorUtils::IsNewLinePreformatted(*point.ContainerAs<Text>())
    818                  ? WhitespaceOptions{WhitespaceOption::FormFeedIsSignificant,
    819                                      WhitespaceOption::NewLineIsSignificant}
    820                  : WhitespaceOptions{WhitespaceOption::FormFeedIsSignificant},
    821              point.Offset());
    822      if (inclusiveNextVisibleCharOffset != CharacterDataBuffer::kNotFound) {
    823        return EditorDOMPoint();  // followed by a visible character.
    824      }
    825      // Followed by only collapsible white-spaces, let's check the next visible
    826      // thing.
    827    }
    828  }
    829 
    830  const auto AdjustPointToInsertPaddingLineBreak =
    831      [](EditorDOMPoint& aPointToInsertLineBreak,
    832         const Element* aParentBlockElement, const Element& aEditingHost) {
    833        if (MOZ_UNLIKELY(!aPointToInsertLineBreak.IsInContentNode())) {
    834          aPointToInsertLineBreak.Clear();
    835          return;
    836        }
    837        while (MOZ_UNLIKELY(
    838            !HTMLEditUtils::CanNodeContain(
    839                *aPointToInsertLineBreak.GetContainer(), *nsGkAtoms::br) ||
    840            !HTMLEditUtils::NodeIsEditableOrNotInComposedDoc(
    841                *aPointToInsertLineBreak.GetContainer()))) {
    842          if (MOZ_UNLIKELY(aPointToInsertLineBreak.GetContainer() ==
    843                               aParentBlockElement ||
    844                           aPointToInsertLineBreak.GetContainer() ==
    845                               &aEditingHost)) {
    846            aPointToInsertLineBreak.Clear();
    847            return;
    848          }
    849          aPointToInsertLineBreak.SetAfterContainer();
    850          if (MOZ_UNLIKELY(!aPointToInsertLineBreak.IsInContentNode())) {
    851            aPointToInsertLineBreak.Clear();
    852            return;
    853          }
    854        }
    855      };
    856 
    857  // If the point is in an empty block, we can skip the expensive check below
    858  // too.
    859  const Element* maybeNonEditableBlock =
    860      HTMLEditUtils::GetInclusiveAncestorElement(
    861          *point.ContainerAs<nsIContent>(), ClosestBlockElement,
    862          BlockInlineCheck::UseComputedDisplayStyle);
    863  if (maybeNonEditableBlock &&
    864      HTMLEditUtils::IsEmptyNode(
    865          *maybeNonEditableBlock,
    866          {EmptyCheckOption::TreatSingleBRElementAsVisible})) {
    867    EditorDOMPoint pointToInsertLineBreak =
    868        HTMLEditUtils::GetDeepestEditableEndPointOf<EditorDOMPoint>(
    869            *maybeNonEditableBlock,
    870            {EditablePointOption::RecognizeInvisibleWhiteSpaces,
    871             EditablePointOption::StopAtComment});
    872    if (pointToInsertLineBreak.IsInTextNode()) {
    873      pointToInsertLineBreak.SetAfterContainer();
    874    }
    875    AdjustPointToInsertPaddingLineBreak(pointToInsertLineBreak,
    876                                        maybeNonEditableBlock, aEditingHost);
    877    return pointToInsertLineBreak;
    878  }
    879 
    880  EditorDOMPoint preferredPaddingLineBreakPoint;
    881  const bool followedByBlockBoundary = [&]() {
    882    if (point.GetContainer() == maybeNonEditableBlock &&
    883        point.IsEndOfContainer()) {
    884      preferredPaddingLineBreakPoint = point.To<EditorDOMPoint>();
    885      return true;
    886    }
    887    if (point.GetContainer() == &aEditingHost && point.IsEndOfContainer()) {
    888      return false;
    889    }
    890    const WSScanResult nextThing =
    891        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary({}, point);
    892    if (nextThing.ReachedBlockBoundary()) {
    893      if (nextThing.ReachedCurrentBlockBoundary()) {
    894        preferredPaddingLineBreakPoint = point.AfterContainer<EditorDOMPoint>();
    895      } else {
    896        preferredPaddingLineBreakPoint = point.To<EditorDOMPoint>();
    897      }
    898      // FIXME: Scan an editable point to put a padding <br>.
    899      if (NS_WARN_IF(!HTMLEditUtils::NodeIsEditableOrNotInComposedDoc(
    900              *preferredPaddingLineBreakPoint.GetContainer()))) {
    901        return false;
    902      }
    903      return true;
    904    }
    905    return false;
    906  }();
    907  if (!followedByBlockBoundary) {
    908    return EditorDOMPoint();
    909  }
    910  const bool isFollowingBlockBoundary = [&]() {
    911    if (point.GetContainer() == maybeNonEditableBlock &&
    912        point.IsStartOfContainer()) {
    913      return true;
    914    }
    915    // We need to scan previous `Text` which may ends with invisible white-space
    916    // because we want to make it visible.  Therefore, we cannot use
    917    // WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary() here.
    918    nsIContent* const previousVisibleLeafOrChildBlock =
    919        HTMLEditUtils::GetPreviousNonEmptyLeafContentOrPreviousBlockElement(
    920            preferredPaddingLineBreakPoint,
    921            {LeafNodeType::LeafNodeOrChildBlock}, BlockInlineCheck::Auto);
    922    if (!previousVisibleLeafOrChildBlock) {
    923      // Reached current block.
    924      return true;
    925    }
    926    return HTMLEditUtils::IsBlockElement(
    927        *previousVisibleLeafOrChildBlock,
    928        BlockInlineCheck::UseComputedDisplayOutsideStyle);
    929  }();
    930  if (!isFollowingBlockBoundary) {
    931    return EditorDOMPoint();
    932  }
    933  AdjustPointToInsertPaddingLineBreak(preferredPaddingLineBreakPoint,
    934                                      maybeNonEditableBlock, aEditingHost);
    935  return preferredPaddingLineBreakPoint;
    936 }
    937 
    938 Element* HTMLEditUtils::GetElementOfImmediateBlockBoundary(
    939    const nsIContent& aContent, const WalkTreeDirection aDirection) {
    940  MOZ_ASSERT(aContent.IsHTMLElement(nsGkAtoms::br) || aContent.IsText());
    941 
    942  // First, we get a block container.  This is not designed for reaching
    943  // no block boundaries in the tree.
    944  Element* maybeNonEditableAncestorBlock = HTMLEditUtils::GetAncestorElement(
    945      aContent, HTMLEditUtils::ClosestBlockElement,
    946      BlockInlineCheck::UseComputedDisplayStyle);
    947  if (NS_WARN_IF(!maybeNonEditableAncestorBlock)) {
    948    return nullptr;
    949  }
    950 
    951  auto getNextContent = [&aDirection, &maybeNonEditableAncestorBlock](
    952                            const nsIContent& aContent) -> nsIContent* {
    953    return aDirection == WalkTreeDirection::Forward
    954               ? HTMLEditUtils::GetNextContent(
    955                     aContent,
    956                     {WalkTreeOption::IgnoreDataNodeExceptText,
    957                      WalkTreeOption::StopAtBlockBoundary},
    958                     BlockInlineCheck::UseComputedDisplayStyle,
    959                     maybeNonEditableAncestorBlock)
    960               : HTMLEditUtils::GetPreviousContent(
    961                     aContent,
    962                     {WalkTreeOption::IgnoreDataNodeExceptText,
    963                      WalkTreeOption::StopAtBlockBoundary},
    964                     BlockInlineCheck::UseComputedDisplayStyle,
    965                     maybeNonEditableAncestorBlock);
    966  };
    967 
    968  // Then, scan block element boundary while we don't see visible things.
    969  const bool isBRElement = aContent.IsHTMLElement(nsGkAtoms::br);
    970  for (nsIContent* nextContent = getNextContent(aContent); nextContent;
    971       nextContent = getNextContent(*nextContent)) {
    972    if (nextContent->IsElement()) {
    973      // Break is right before a child block, it's not visible
    974      if (HTMLEditUtils::IsBlockElement(
    975              *nextContent, BlockInlineCheck::UseComputedDisplayStyle)) {
    976        return nextContent->AsElement();
    977      }
    978 
    979      // XXX How about other non-HTML elements?  Assume they are styled as
    980      //     blocks for now.
    981      if (!nextContent->IsHTMLElement()) {
    982        return nextContent->AsElement();
    983      }
    984 
    985      if (nextContent->IsHTMLElement(nsGkAtoms::br)) {
    986        // If aContent is a <br> element, another <br> element prevents the
    987        // block boundary special handling.
    988        if (isBRElement) {
    989          return nullptr;
    990        }
    991 
    992        MOZ_ASSERT(aContent.IsText());
    993        // Following <br> element always hides its following block boundary.
    994        // I.e., white-spaces is at end of the text node is visible.
    995        if (aDirection == WalkTreeDirection::Forward) {
    996          return nullptr;
    997        }
    998        // Otherwise, if text node follows <br> element, its white-spaces at
    999        // start of the text node are invisible.  In this case, we return
   1000        // the found <br> element.
   1001        return nextContent->AsElement();
   1002      }
   1003 
   1004      // If there is a visible content which generates something visible,
   1005      // stop scanning.
   1006      if (HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*nextContent)) {
   1007        return nullptr;
   1008      }
   1009 
   1010      continue;
   1011    }
   1012 
   1013    switch (nextContent->NodeType()) {
   1014      case nsINode::TEXT_NODE:
   1015      case nsINode::CDATA_SECTION_NODE:
   1016        break;
   1017      default:
   1018        continue;
   1019    }
   1020 
   1021    Text* textNode = Text::FromNode(nextContent);
   1022    MOZ_ASSERT(textNode);
   1023    if (!textNode->TextDataLength()) {
   1024      continue;  // empty invisible text node, keep scanning next one.
   1025    }
   1026    if (HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone(*textNode)) {
   1027      continue;  // Styled as invisible.
   1028    }
   1029    if (EditorUtils::IsWhiteSpacePreformatted(*textNode)) {
   1030      return nullptr;  // found a visible text node.
   1031    }
   1032    const uint32_t nonWhiteSpaceOffset =
   1033        textNode->DataBuffer().FindNonWhitespaceChar(
   1034            EditorUtils::IsNewLinePreformatted(*textNode)
   1035                ? WhitespaceOptions{WhitespaceOption::FormFeedIsSignificant,
   1036                                    WhitespaceOption::NewLineIsSignificant}
   1037                : WhitespaceOptions{WhitespaceOption::FormFeedIsSignificant});
   1038    if (nonWhiteSpaceOffset != CharacterDataBuffer::kNotFound) {
   1039      return nullptr;  // found a visible text node.
   1040    }
   1041    // All white-spaces in the text node are invisible, keep scanning next one.
   1042  }
   1043 
   1044  // There is no visible content and reached current block boundary.  Then,
   1045  // the <br> element is the last content in the block and invisible.
   1046  // XXX Should we treat it visible if it's the only child of a block?
   1047  return maybeNonEditableAncestorBlock;
   1048 }
   1049 
   1050 template <typename PT, typename CT>
   1051 bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary(
   1052    const EditorDOMPointBase<PT, CT>& aPoint,
   1053    IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak) {
   1054  MOZ_ASSERT(aPoint.IsSetAndValidInComposedDoc());
   1055 
   1056  if (MOZ_UNLIKELY(!aPoint.IsInContentNode())) {
   1057    return false;
   1058  }
   1059  const WSScanResult nextThing =
   1060      WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1061          {WSRunScanner::Option::OnlyEditableNodes}, aPoint);
   1062  if (nextThing.ReachedCurrentBlockBoundary()) {
   1063    return true;
   1064  }
   1065  if (nextThing.ReachedInvisibleBRElement()) {
   1066    if (aIgnoreInvisibleLineBreak == IgnoreInvisibleLineBreak::No) {
   1067      return false;
   1068    }
   1069    const WSScanResult afterInvisibleBRThing =
   1070        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1071            {WSRunScanner::Option::OnlyEditableNodes},
   1072            nextThing.PointAfterReachedContent<EditorRawDOMPoint>());
   1073    return afterInvisibleBRThing.ReachedCurrentBlockBoundary();
   1074  }
   1075  if (nextThing.ReachedPreformattedLineBreak()) {
   1076    if (aIgnoreInvisibleLineBreak == IgnoreInvisibleLineBreak::No) {
   1077      return false;
   1078    }
   1079    const WSScanResult afterPreformattedLineBreakThing =
   1080        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1081            {WSRunScanner::Option::OnlyEditableNodes},
   1082            nextThing.PointAfterReachedContent<EditorRawDOMPoint>());
   1083    return afterPreformattedLineBreakThing.ReachedCurrentBlockBoundary();
   1084  }
   1085  return false;
   1086 }
   1087 
   1088 template <typename EditorLineBreakType>
   1089 Maybe<EditorLineBreakType> HTMLEditUtils::GetUnnecessaryLineBreak(
   1090    const Element& aBlockElement, ScanLineBreak aScanLineBreak) {
   1091  auto* lastLineBreakContent = [&]() -> nsIContent* {
   1092    const WalkTreeOptions onlyPrecedingLine{
   1093        WalkTreeOption::StopAtBlockBoundary};
   1094    for (nsIContent* content =
   1095             aScanLineBreak == ScanLineBreak::AtEndOfBlock
   1096                 ? HTMLEditUtils::GetLastLeafContent(
   1097                       aBlockElement, {LeafNodeType::OnlyLeafNode})
   1098                 : HTMLEditUtils::GetPreviousContent(
   1099                       aBlockElement, onlyPrecedingLine,
   1100                       BlockInlineCheck::UseComputedDisplayStyle,
   1101                       aBlockElement.GetParentElement());
   1102         content;
   1103         content =
   1104             aScanLineBreak == ScanLineBreak::AtEndOfBlock
   1105                 ? HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1106                       *content, {LeafNodeType::OnlyLeafNode},
   1107                       BlockInlineCheck::UseComputedDisplayStyle,
   1108                       &aBlockElement)
   1109                 : HTMLEditUtils::GetPreviousContent(
   1110                       *content, onlyPrecedingLine,
   1111                       BlockInlineCheck::UseComputedDisplayStyle,
   1112                       aBlockElement.GetParentElement())) {
   1113      // If we're scanning preceding <br> element of aBlockElement, we don't
   1114      // need to look for a line break in another block because the caller
   1115      // needs to handle only preceding <br> element of aBlockElement.
   1116      if (aScanLineBreak == ScanLineBreak::BeforeBlock &&
   1117          HTMLEditUtils::IsBlockElement(
   1118              *content, BlockInlineCheck::UseComputedDisplayStyle)) {
   1119        return nullptr;
   1120      }
   1121      if (Text* textNode = Text::FromNode(content)) {
   1122        if (!textNode->TextLength()) {
   1123          continue;  // ignore empty text node
   1124        }
   1125        const CharacterDataBuffer& characterDataBuffer = textNode->DataBuffer();
   1126        if (EditorUtils::IsNewLinePreformatted(*textNode) &&
   1127            characterDataBuffer.CharAt(characterDataBuffer.GetLength() - 1u) ==
   1128                HTMLEditUtils::kNewLine) {
   1129          // If the text node ends with a preserved line break, it's unnecessary
   1130          // unless it follows another preformatted line break.
   1131          if (characterDataBuffer.GetLength() == 1u) {
   1132            return textNode;  // Need to scan previous leaf.
   1133          }
   1134          return characterDataBuffer.CharAt(characterDataBuffer.GetLength() -
   1135                                            2u) == HTMLEditUtils::kNewLine
   1136                     ? nullptr
   1137                     : textNode;
   1138        }
   1139        if (HTMLEditUtils::IsVisibleTextNode(*textNode)) {
   1140          return nullptr;
   1141        }
   1142        continue;
   1143      }
   1144      if (content->IsCharacterData()) {
   1145        continue;  // ignore hidden character data nodes like comment
   1146      }
   1147      if (content->IsHTMLElement(nsGkAtoms::br)) {
   1148        return content;
   1149      }
   1150      if (HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*content)) {
   1151        return nullptr;
   1152      }
   1153      // Otherwise, e.g., empty <b>, we should keep scanning.
   1154    }
   1155    return nullptr;
   1156  }();
   1157  if (!lastLineBreakContent) {
   1158    return Nothing();
   1159  }
   1160 
   1161  // If the found node is a text node and contains only one preformatted new
   1162  // line break, we need to keep scanning previous one, but if it has 2 or more
   1163  // characters, we know it has redundant line break.
   1164  Text* const lastLineBreakText = Text::FromNode(lastLineBreakContent);
   1165  if (lastLineBreakText && lastLineBreakText->TextDataLength() != 1u) {
   1166    return Some(EditorLineBreakType::AtLastChar(*lastLineBreakText));
   1167  }
   1168  HTMLBRElement* const lastBRElement =
   1169      lastLineBreakText ? nullptr
   1170                        : HTMLBRElement::FromNode(lastLineBreakContent);
   1171  MOZ_ASSERT_IF(!lastLineBreakText, lastBRElement);
   1172 
   1173  // Scan previous leaf content, but now, we can stop at child block boundary.
   1174  const Element* blockElement = HTMLEditUtils::GetAncestorElement(
   1175      *lastLineBreakContent, HTMLEditUtils::ClosestBlockElement,
   1176      BlockInlineCheck::UseComputedDisplayStyle);
   1177  for (nsIContent* content =
   1178           HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1179               *lastLineBreakContent, {LeafNodeType::LeafNodeOrChildBlock},
   1180               BlockInlineCheck::UseComputedDisplayStyle, blockElement);
   1181       content;
   1182       content = HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
   1183           *content, {LeafNodeType::LeafNodeOrChildBlock},
   1184           BlockInlineCheck::UseComputedDisplayStyle, blockElement)) {
   1185    if (HTMLEditUtils::IsBlockElement(
   1186            *content, BlockInlineCheck::UseComputedDisplayStyle) ||
   1187        (content->IsElement() && !content->IsHTMLElement())) {
   1188      // Now, must found <div>...<div>...</div><br></div>
   1189      //                                       ^^^^
   1190      // In this case, the <br> element is necessary to make a following empty
   1191      // line of the inner <div> visible.
   1192      return Nothing();
   1193    }
   1194    if (Text* textNode = Text::FromNode(content)) {
   1195      if (!textNode->TextDataLength()) {
   1196        continue;  // ignore empty text node
   1197      }
   1198      const CharacterDataBuffer& characterDataBuffer = textNode->DataBuffer();
   1199      if (EditorUtils::IsNewLinePreformatted(*textNode) &&
   1200          characterDataBuffer.CharAt(characterDataBuffer.GetLength() - 1u) ==
   1201              HTMLEditUtils::kNewLine) {
   1202        // So, we are here because the preformatted line break is followed by
   1203        // lastLineBreakContent which is <br> or a text node containing only
   1204        // one.  In this case, even if their parents are different,
   1205        // lastLineBreakContent is necessary to make the last line visible.
   1206        return Nothing();
   1207      }
   1208      if (!HTMLEditUtils::IsVisibleTextNode(*textNode)) {
   1209        continue;
   1210      }
   1211      if (EditorUtils::IsWhiteSpacePreformatted(*textNode)) {
   1212        // If the white-space is preserved, neither following <br> nor a
   1213        // preformatted line break is not necessary.
   1214        return Some(lastLineBreakText
   1215                        ? EditorLineBreakType::AtLastChar(*lastLineBreakText)
   1216                        : EditorLineBreakType(*lastBRElement));
   1217      }
   1218      // Otherwise, only if the last character is a collapsible white-space,
   1219      // we need lastLineBreakContent to make the trailing white-space visible.
   1220      switch (characterDataBuffer.LastChar()) {
   1221        case HTMLEditUtils::kSpace:
   1222        case HTMLEditUtils::kNewLine:
   1223        case HTMLEditUtils::kCarriageReturn:
   1224        case HTMLEditUtils::kTab:
   1225          return Nothing();
   1226        default:
   1227          return Some(lastLineBreakText
   1228                          ? EditorLineBreakType::AtLastChar(*lastLineBreakText)
   1229                          : EditorLineBreakType(*lastBRElement));
   1230      }
   1231    }
   1232    if (content->IsCharacterData()) {
   1233      continue;  // ignore hidden character data nodes like comment
   1234    }
   1235    // If lastLineBreakContent follows a <br> element in same block, it's
   1236    // necessary to make the empty last line visible.
   1237    if (content->IsHTMLElement(nsGkAtoms::br)) {
   1238      return Nothing();
   1239    }
   1240    if (HTMLEditUtils::IsVisibleElementEvenIfLeafNode(*content)) {
   1241      return Some(lastLineBreakText
   1242                      ? EditorLineBreakType::AtLastChar(*lastLineBreakText)
   1243                      : EditorLineBreakType(*lastBRElement));
   1244    }
   1245    // Otherwise, ignore empty inline elements such as <b>.
   1246  }
   1247  // If the block is empty except invisible data nodes and lastLineBreakContent,
   1248  // lastLineBreakContent is necessary to make the block visible.
   1249  return Nothing();
   1250 }
   1251 
   1252 template <typename EditorLineBreakType, typename EditorDOMPointType>
   1253 Maybe<EditorLineBreakType> HTMLEditUtils::GetFollowingUnnecessaryLineBreak(
   1254    const EditorDOMPointType& aPoint) {
   1255  MOZ_ASSERT(aPoint.IsSetAndValid());
   1256  MOZ_ASSERT(aPoint.IsInContentNode());
   1257 
   1258  const WSScanResult nextThing =
   1259      WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary({}, aPoint);
   1260  if (!nextThing.ReachedBRElement() &&
   1261      !(nextThing.ReachedPreformattedLineBreak() &&
   1262        nextThing.PointAtReachedContent<EditorRawDOMPoint>()
   1263            .IsAtLastContent())) {
   1264    return Nothing();  // no line break next to aPoint
   1265  }
   1266  const WSScanResult nextThingOfLineBreak =
   1267      WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1268          {}, nextThing.PointAfterReachedContent<EditorRawDOMPoint>());
   1269  const Element* const blockElement =
   1270      nextThingOfLineBreak.ReachedBlockBoundary()
   1271          ? nextThingOfLineBreak.ElementPtr()
   1272          : HTMLEditUtils::GetAncestorElement(
   1273                *nextThing.GetContent(), {AncestorType::ClosestBlockElement},
   1274                BlockInlineCheck::UseComputedDisplayStyle);
   1275  if (MOZ_UNLIKELY(!blockElement)) {
   1276    return Nothing();
   1277  }
   1278  Maybe<EditorLineBreakType> unnecessaryLineBreak =
   1279      GetUnnecessaryLineBreak<EditorLineBreakType>(
   1280          *blockElement, nextThingOfLineBreak.ReachedOtherBlockElement()
   1281                             ? ScanLineBreak::BeforeBlock
   1282                             : ScanLineBreak::AtEndOfBlock);
   1283  // If the line break content is different from the found line break
   1284  // immediately after aPoint, it's too far. So, the caller should not touch it.
   1285  if (unnecessaryLineBreak.isSome() &&
   1286      &unnecessaryLineBreak->ContentRef() != nextThing.GetContent()) {
   1287    unnecessaryLineBreak.reset();
   1288  }
   1289  return unnecessaryLineBreak;
   1290 }
   1291 
   1292 uint32_t HTMLEditUtils::GetFirstVisibleCharOffset(const Text& aText) {
   1293  const CharacterDataBuffer& characterDataBuffer = aText.DataBuffer();
   1294  if (!characterDataBuffer.GetLength() ||
   1295      !EditorRawDOMPointInText(&aText, 0u)
   1296           .IsCharCollapsibleASCIISpaceOrNBSP()) {
   1297    return 0u;
   1298  }
   1299  const WSScanResult previousThingOfText =
   1300      WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   1301          {}, EditorRawDOMPoint(&aText));
   1302  if (!previousThingOfText.ReachedLineBoundary()) {
   1303    return 0u;
   1304  }
   1305  return HTMLEditUtils::GetInclusiveNextNonCollapsibleCharOffset(aText, 0u)
   1306      .valueOr(characterDataBuffer.GetLength());
   1307 }
   1308 
   1309 uint32_t HTMLEditUtils::GetOffsetAfterLastVisibleChar(const Text& aText) {
   1310  const CharacterDataBuffer& characterDataBuffer = aText.DataBuffer();
   1311  if (!characterDataBuffer.GetLength()) {
   1312    return 0u;
   1313  }
   1314  if (!EditorRawDOMPointInText::AtLastContentOf(aText)
   1315           .IsCharCollapsibleASCIISpaceOrNBSP()) {
   1316    return characterDataBuffer.GetLength();
   1317  }
   1318  const WSScanResult nextThingOfText =
   1319      WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   1320          {}, EditorRawDOMPoint::After(aText));
   1321  if (!nextThingOfText.ReachedLineBoundary()) {
   1322    return characterDataBuffer.GetLength();
   1323  }
   1324  const Maybe<uint32_t> lastNonCollapsibleCharOffset =
   1325      HTMLEditUtils::GetPreviousNonCollapsibleCharOffset(
   1326          aText, characterDataBuffer.GetLength());
   1327  if (lastNonCollapsibleCharOffset.isNothing()) {
   1328    return 0u;
   1329  }
   1330  if (*lastNonCollapsibleCharOffset == characterDataBuffer.GetLength() - 1u) {
   1331    return characterDataBuffer.GetLength();
   1332  }
   1333  const uint32_t firstTrailingWhiteSpaceOffset =
   1334      *lastNonCollapsibleCharOffset + 1u;
   1335  MOZ_ASSERT(firstTrailingWhiteSpaceOffset < characterDataBuffer.GetLength());
   1336  if (nextThingOfText.ReachedBlockBoundary()) {
   1337    return firstTrailingWhiteSpaceOffset;
   1338  }
   1339  // If followed by <br> or preformatted line break, one white-space is
   1340  // rendered.
   1341  return firstTrailingWhiteSpaceOffset + 1u;
   1342 }
   1343 
   1344 uint32_t HTMLEditUtils::GetInvisibleWhiteSpaceCount(
   1345    const Text& aText, uint32_t aOffset /* = 0u */,
   1346    uint32_t aLength /* = UINT32_MAX */) {
   1347  const CharacterDataBuffer& characterDataBuffer = aText.DataBuffer();
   1348  if (!aLength || characterDataBuffer.GetLength() <= aOffset) {
   1349    return 0u;
   1350  }
   1351  const uint32_t endOffset = static_cast<uint32_t>(
   1352      std::min(static_cast<uint64_t>(aOffset) + aLength,
   1353               static_cast<uint64_t>(characterDataBuffer.GetLength())));
   1354  const auto firstVisibleOffset = [&]() -> uint32_t {
   1355    // If the white-space sequence follows a preformatted linebreak, ASCII
   1356    // spaces at start are invisible.
   1357    if (aOffset &&
   1358        characterDataBuffer.CharAt(aOffset - 1u) == HTMLEditUtils::kNewLine &&
   1359        EditorUtils::IsNewLinePreformatted(aText)) {
   1360      for (const uint32_t offset : IntegerRange(aOffset, endOffset)) {
   1361        if (characterDataBuffer.CharAt(offset) == HTMLEditUtils::kNBSP) {
   1362          return offset;
   1363        }
   1364      }
   1365      return endOffset;  // all white-spaces are invisible.
   1366    }
   1367    if (aOffset) {
   1368      return aOffset - 1u;
   1369    }
   1370    return HTMLEditUtils::GetFirstVisibleCharOffset(aText);
   1371  }();
   1372  if (firstVisibleOffset >= endOffset) {
   1373    return endOffset - aOffset;  // All white-spaces are invisible.
   1374  }
   1375  const auto afterLastVisibleOffset = [&]() -> uint32_t {
   1376    // If the white-spaces are followed by a preformatted line break, ASCII
   1377    // spaces at end are invisible.
   1378    if (endOffset < characterDataBuffer.GetLength() &&
   1379        characterDataBuffer.CharAt(endOffset) == HTMLEditUtils::kNewLine &&
   1380        EditorUtils::IsNewLinePreformatted(aText)) {
   1381      for (const uint32_t offset : Reversed(IntegerRange(aOffset, endOffset))) {
   1382        if (characterDataBuffer.CharAt(offset) == HTMLEditUtils::kNBSP) {
   1383          return offset + 1u;
   1384        }
   1385      }
   1386      return aOffset;  // all white-spaces are invisible.
   1387    }
   1388    if (endOffset < characterDataBuffer.GetLength() - 1u) {
   1389      return endOffset;
   1390    }
   1391    return HTMLEditUtils::GetOffsetAfterLastVisibleChar(aText);
   1392  }();
   1393  if (aOffset >= afterLastVisibleOffset) {
   1394    return endOffset - aOffset;  // All white-spaces are invisible.
   1395  }
   1396  enum class PrevChar { NotChar, Space, NBSP };
   1397  PrevChar prevChar = PrevChar::NotChar;
   1398  uint32_t invisibleChars = 0u;
   1399  for (const uint32_t offset : IntegerRange(aOffset, endOffset)) {
   1400    if (characterDataBuffer.CharAt(offset) == HTMLEditUtils::kNBSP) {
   1401      prevChar = PrevChar::NBSP;
   1402      continue;
   1403    }
   1404    MOZ_ASSERT(
   1405        EditorRawDOMPointInText(&aText, offset).IsCharCollapsibleASCIISpace());
   1406    if (offset < firstVisibleOffset || offset >= afterLastVisibleOffset ||
   1407        // white-space after another white-space is invisible
   1408        prevChar == PrevChar::Space) {
   1409      invisibleChars++;
   1410    }
   1411    prevChar = PrevChar::Space;
   1412  }
   1413  return invisibleChars;
   1414 }
   1415 
   1416 bool HTMLEditUtils::IsEmptyNode(nsPresContext* aPresContext,
   1417                                const nsINode& aNode,
   1418                                const EmptyCheckOptions& aOptions /* = {} */,
   1419                                bool* aSeenBR /* = nullptr */) {
   1420  MOZ_ASSERT_IF(aOptions.contains(EmptyCheckOption::SafeToAskLayout),
   1421                aPresContext);
   1422 
   1423  if (aSeenBR) {
   1424    *aSeenBR = false;
   1425  }
   1426 
   1427  if (const Text* text = Text::FromNode(&aNode)) {
   1428    return aOptions.contains(EmptyCheckOption::SafeToAskLayout)
   1429               ? !IsInVisibleTextFrames(aPresContext, *text)
   1430               : !IsVisibleTextNode(*text);
   1431  }
   1432 
   1433  const bool treatCommentAsVisible =
   1434      aOptions.contains(EmptyCheckOption::TreatCommentAsVisible);
   1435  if (aNode.IsComment()) {
   1436    return !treatCommentAsVisible;
   1437  }
   1438 
   1439  if (!aNode.IsElement()) {
   1440    return false;
   1441  }
   1442 
   1443  if (
   1444      // If it's not a container such as an <hr> or <br>, etc, it should be
   1445      // treated as not empty.
   1446      // XXX I think <input type="hidden"> should not be treated as a special
   1447      // element since it's invisible. Treating invisible elements as special
   1448      // ones causes changing the behavior with the invisible thing so that the
   1449      // users may report the different behavior as a bug.
   1450      !IsContainerNode(*aNode.AsContent()) ||
   1451      // If it's a named anchor, we shouldn't treat it as empty because it
   1452      // has special meaning even if invisible.
   1453      IsNamedAnchorElement(*aNode.AsContent()) ||
   1454      // Replaced elements should be treated as not empty because they have
   1455      // visible content.
   1456      IsReplacedElement(*aNode.AsElement())) {
   1457    return false;
   1458  }
   1459 
   1460  const auto [isListItem, isTableCell, hasAppearance] =
   1461      [&]() MOZ_NEVER_INLINE_DEBUG -> std::tuple<bool, bool, bool> {
   1462    // Let's stop treating the document element and the <body> as a list item
   1463    // nor a table cell to avoid tricky cases.
   1464    if (aNode.OwnerDoc()->GetDocumentElement() == &aNode ||
   1465        (aNode.IsHTMLElement(nsGkAtoms::body) &&
   1466         aNode.OwnerDoc()->GetBodyElement() == &aNode)) {
   1467      return {false, false, false};
   1468    }
   1469 
   1470    RefPtr<const ComputedStyle> elementStyle =
   1471        nsComputedDOMStyle::GetComputedStyleNoFlush(aNode.AsElement());
   1472    // If there is no style information like in a document fragment, let's refer
   1473    // the default style.
   1474    if (MOZ_UNLIKELY(!elementStyle)) {
   1475      return {IsListItemElement(*aNode.AsContent()),
   1476              IsTableCellElement(*aNode.AsContent()), false};
   1477    }
   1478    const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay();
   1479    if (NS_WARN_IF(!styleDisplay)) {
   1480      return {IsListItemElement(*aNode.AsContent()),
   1481              IsTableCellElement(*aNode.AsContent()), false};
   1482    }
   1483    if (styleDisplay->mDisplay != StyleDisplay::None &&
   1484        styleDisplay->HasAppearance()) {
   1485      return {false, false, true};
   1486    }
   1487    if (styleDisplay->IsListItem()) {
   1488      return {true, false, false};
   1489    }
   1490    if (styleDisplay->mDisplay == StyleDisplay::TableCell) {
   1491      return {false, true, false};
   1492    }
   1493    // The default display of <dt> and <dd> is block.  Therefore, we need
   1494    // special handling for them.
   1495    return {styleDisplay->mDisplay == StyleDisplay::Block &&
   1496                aNode.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt),
   1497            false, false};
   1498  }();
   1499 
   1500  // The web author created native widget without form control elements.  Let's
   1501  // treat it as visible.
   1502  if (hasAppearance) {
   1503    return false;
   1504  }
   1505 
   1506  if (isListItem &&
   1507      aOptions.contains(EmptyCheckOption::TreatListItemAsVisible)) {
   1508    return false;
   1509  }
   1510  if (isTableCell &&
   1511      aOptions.contains(EmptyCheckOption::TreatTableCellAsVisible)) {
   1512    return false;
   1513  }
   1514 
   1515  const bool treatNonEditableContentAsInvisible =
   1516      aOptions.contains(EmptyCheckOption::TreatNonEditableContentAsInvisible);
   1517  bool seenBR = aSeenBR && *aSeenBR;
   1518  for (nsIContent* childContent = aNode.GetFirstChild(); childContent;
   1519       childContent = childContent->GetNextSibling()) {
   1520    if (childContent->IsComment()) {
   1521      if (treatCommentAsVisible) {
   1522        return false;
   1523      }
   1524      continue;
   1525    }
   1526    if (treatNonEditableContentAsInvisible &&
   1527        !HTMLEditUtils::IsSimplyEditableNode(*childContent)) {
   1528      continue;
   1529    }
   1530    if (Text* text = Text::FromNode(childContent)) {
   1531      // break out if we find we aren't empty
   1532      if (aOptions.contains(EmptyCheckOption::SafeToAskLayout)
   1533              ? IsInVisibleTextFrames(aPresContext, *text)
   1534              : IsVisibleTextNode(*text)) {
   1535        return false;
   1536      }
   1537      continue;
   1538    }
   1539 
   1540    MOZ_ASSERT(childContent != &aNode);
   1541 
   1542    if (!aOptions.contains(EmptyCheckOption::TreatSingleBRElementAsVisible) &&
   1543        !seenBR && childContent->IsHTMLElement(nsGkAtoms::br)) {
   1544      // Ignore first <br> element in it if caller wants so because it's
   1545      // typically a padding <br> element of for a parent block.
   1546      seenBR = true;
   1547      if (aSeenBR) {
   1548        *aSeenBR = true;
   1549      }
   1550      continue;
   1551    }
   1552 
   1553    if (aOptions.contains(EmptyCheckOption::TreatBlockAsVisible) &&
   1554        HTMLEditUtils::IsBlockElement(
   1555            *childContent, BlockInlineCheck::UseComputedDisplayOutsideStyle)) {
   1556      return false;
   1557    }
   1558 
   1559    // Note: list items or table cells are not considered empty
   1560    // if they contain other lists or tables
   1561    EmptyCheckOptions options(aOptions);
   1562    if (childContent->IsElement() && (isListItem || isTableCell)) {
   1563      options += {EmptyCheckOption::TreatListItemAsVisible,
   1564                  EmptyCheckOption::TreatTableCellAsVisible};
   1565    }
   1566    if (!IsEmptyNode(aPresContext, *childContent, options, &seenBR)) {
   1567      if (aSeenBR) {
   1568        *aSeenBR = seenBR;
   1569      }
   1570      return false;
   1571    }
   1572  }
   1573 
   1574  if (aSeenBR) {
   1575    *aSeenBR = seenBR;
   1576  }
   1577  return true;
   1578 }
   1579 
   1580 bool HTMLEditUtils::ShouldInsertLinefeedCharacter(
   1581    const EditorDOMPoint& aPointToInsert, const Element& aEditingHost) {
   1582  MOZ_ASSERT(aPointToInsert.IsSetAndValid());
   1583 
   1584  if (!aPointToInsert.IsInContentNode()) {
   1585    return false;
   1586  }
   1587 
   1588  // If in contenteditable=plaintext-only, we should use linefeed when it's
   1589  // preformatted.
   1590  if (aEditingHost.IsContentEditablePlainTextOnly()) {
   1591    return EditorUtils::IsNewLinePreformatted(
   1592        *aPointToInsert.ContainerAs<nsIContent>());
   1593  }
   1594 
   1595  // closestEditableBlockElement can be nullptr if aEditingHost is an inline
   1596  // element.
   1597  Element* closestEditableBlockElement =
   1598      HTMLEditUtils::GetInclusiveAncestorElement(
   1599          *aPointToInsert.ContainerAs<nsIContent>(),
   1600          HTMLEditUtils::ClosestEditableBlockElement,
   1601          BlockInlineCheck::UseComputedDisplayOutsideStyle);
   1602 
   1603  // If and only if the nearest block is the editing host or its parent,
   1604  // and new line character is preformatted, we should insert a linefeed.
   1605  return (!closestEditableBlockElement ||
   1606          closestEditableBlockElement == &aEditingHost) &&
   1607         EditorUtils::IsNewLinePreformatted(
   1608             *aPointToInsert.ContainerAs<nsIContent>());
   1609 }
   1610 
   1611 // We use bitmasks to test containment of elements. Elements are marked to be
   1612 // in certain groups by setting the mGroup member of the `ElementInfo` struct
   1613 // to the corresponding GROUP_ values (OR'ed together). Similarly, elements are
   1614 // marked to allow containment of certain groups by setting the
   1615 // mCanContainGroups member of the `ElementInfo` struct to the corresponding
   1616 // GROUP_ values (OR'ed together).
   1617 // Testing containment then simply consists of checking whether the
   1618 // mCanContainGroups bitmask of an element and the mGroup bitmask of a
   1619 // potential child overlap.
   1620 
   1621 #define GROUP_NONE 0
   1622 
   1623 // body, head, html
   1624 #define GROUP_TOPLEVEL (1 << 1)
   1625 
   1626 // base, link, meta, script, style, title
   1627 #define GROUP_HEAD_CONTENT (1 << 2)
   1628 
   1629 // b, big, i, s, small, strike, tt, u
   1630 #define GROUP_FONTSTYLE (1 << 3)
   1631 
   1632 // abbr, acronym, cite, code, datalist, del, dfn, em, ins, kbd, mark, rb, rp
   1633 // rt, rtc, ruby, samp, strong, var
   1634 #define GROUP_PHRASE (1 << 4)
   1635 
   1636 // a, applet, basefont, bdi, bdo, br, font, iframe, img, map, meter, object,
   1637 // output, picture, progress, q, script, span, sub, sup
   1638 #define GROUP_SPECIAL (1 << 5)
   1639 
   1640 // button, form, input, label, select, textarea
   1641 #define GROUP_FORMCONTROL (1 << 6)
   1642 
   1643 // address, applet, article, aside, blockquote, button, center, del, details,
   1644 // dialog, dir, div, dl, fieldset, figure, footer, form, h1, h2, h3, h4, h5,
   1645 // h6, header, hgroup, hr, iframe, ins, main, map, menu, nav, noframes,
   1646 // noscript, object, ol, p, pre, table, search, section, summary, ul
   1647 #define GROUP_BLOCK (1 << 7)
   1648 
   1649 // frame, frameset
   1650 #define GROUP_FRAME (1 << 8)
   1651 
   1652 // col, tbody
   1653 #define GROUP_TABLE_CONTENT (1 << 9)
   1654 
   1655 // tr
   1656 #define GROUP_TBODY_CONTENT (1 << 10)
   1657 
   1658 // td, th
   1659 #define GROUP_TR_CONTENT (1 << 11)
   1660 
   1661 // col
   1662 #define GROUP_COLGROUP_CONTENT (1 << 12)
   1663 
   1664 // param
   1665 #define GROUP_OBJECT_CONTENT (1 << 13)
   1666 
   1667 // li
   1668 #define GROUP_LI (1 << 14)
   1669 
   1670 // area
   1671 #define GROUP_MAP_CONTENT (1 << 15)
   1672 
   1673 // optgroup, option
   1674 #define GROUP_SELECT_CONTENT (1 << 16)
   1675 
   1676 // option
   1677 #define GROUP_OPTIONS (1 << 17)
   1678 
   1679 // dd, dt
   1680 #define GROUP_DL_CONTENT (1 << 18)
   1681 
   1682 // p
   1683 #define GROUP_P (1 << 19)
   1684 
   1685 // text, white-space, newline, comment
   1686 #define GROUP_LEAF (1 << 20)
   1687 
   1688 // XXX This is because the editor does sublists illegally.
   1689 // ol, ul
   1690 #define GROUP_OL_UL (1 << 21)
   1691 
   1692 // h1, h2, h3, h4, h5, h6
   1693 #define GROUP_HEADING (1 << 22)
   1694 
   1695 // figcaption
   1696 #define GROUP_FIGCAPTION (1 << 23)
   1697 
   1698 // picture members (img, source)
   1699 #define GROUP_PICTURE_CONTENT (1 << 24)
   1700 
   1701 #define GROUP_INLINE_ELEMENT                                            \
   1702  (GROUP_FONTSTYLE | GROUP_PHRASE | GROUP_SPECIAL | GROUP_FORMCONTROL | \
   1703   GROUP_LEAF)
   1704 
   1705 #define GROUP_FLOW_ELEMENT (GROUP_INLINE_ELEMENT | GROUP_BLOCK)
   1706 
   1707 struct ElementInfo final {
   1708 #ifdef DEBUG
   1709  nsHTMLTag mTag;
   1710 #endif
   1711  // See `GROUP_NONE`'s comment.
   1712  uint32_t mGroup;
   1713  // See `GROUP_NONE`'s comment.
   1714  uint32_t mCanContainGroups;
   1715  bool mIsContainer;
   1716  bool mCanContainSelf;
   1717 };
   1718 
   1719 #ifdef DEBUG
   1720 #  define ELEM(_tag, _isContainer, _canContainSelf, _group, _canContainGroups) \
   1721    {eHTMLTag_##_tag, _group, _canContainGroups, _isContainer, _canContainSelf}
   1722 #else
   1723 #  define ELEM(_tag, _isContainer, _canContainSelf, _group, _canContainGroups) \
   1724    {_group, _canContainGroups, _isContainer, _canContainSelf}
   1725 #endif
   1726 
   1727 static const ElementInfo kElements[eHTMLTag_userdefined] = {
   1728    ELEM(a, true, false, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   1729    ELEM(abbr, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1730    ELEM(acronym, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1731    ELEM(address, true, true, GROUP_BLOCK, GROUP_INLINE_ELEMENT | GROUP_P),
   1732    // While applet is no longer a valid tag, removing it here breaks the editor
   1733    // (compiles, but causes many tests to fail in odd ways). This list is
   1734    // tracked against the main HTML Tag list, so any changes will require more
   1735    // than just removing entries.
   1736    ELEM(applet, true, true, GROUP_SPECIAL | GROUP_BLOCK,
   1737         GROUP_FLOW_ELEMENT | GROUP_OBJECT_CONTENT),
   1738    ELEM(area, false, false, GROUP_MAP_CONTENT, GROUP_NONE),
   1739    ELEM(article, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1740    ELEM(aside, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1741    ELEM(audio, false, false, GROUP_NONE, GROUP_NONE),
   1742    ELEM(b, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   1743    ELEM(base, false, false, GROUP_HEAD_CONTENT, GROUP_NONE),
   1744    ELEM(basefont, false, false, GROUP_SPECIAL, GROUP_NONE),
   1745    ELEM(bdi, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   1746    ELEM(bdo, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   1747    ELEM(bgsound, false, false, GROUP_NONE, GROUP_NONE),
   1748    ELEM(big, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   1749    ELEM(blockquote, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1750    ELEM(body, true, true, GROUP_TOPLEVEL, GROUP_FLOW_ELEMENT),
   1751    ELEM(br, false, false, GROUP_SPECIAL, GROUP_NONE),
   1752    ELEM(button, true, true, GROUP_FORMCONTROL | GROUP_BLOCK,
   1753         GROUP_FLOW_ELEMENT),
   1754    ELEM(canvas, false, false, GROUP_NONE, GROUP_NONE),
   1755    ELEM(caption, true, true, GROUP_NONE, GROUP_INLINE_ELEMENT),
   1756    ELEM(center, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1757    ELEM(cite, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1758    ELEM(code, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1759    ELEM(col, false, false, GROUP_TABLE_CONTENT | GROUP_COLGROUP_CONTENT,
   1760         GROUP_NONE),
   1761    ELEM(colgroup, true, false, GROUP_NONE, GROUP_COLGROUP_CONTENT),
   1762    ELEM(data, true, false, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1763    ELEM(datalist, true, false, GROUP_PHRASE,
   1764         GROUP_OPTIONS | GROUP_INLINE_ELEMENT),
   1765    ELEM(dd, true, false, GROUP_DL_CONTENT, GROUP_FLOW_ELEMENT),
   1766    ELEM(del, true, true, GROUP_PHRASE | GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1767    ELEM(details, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1768    ELEM(dfn, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1769    ELEM(dialog, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1770    ELEM(dir, true, false, GROUP_BLOCK, GROUP_LI),
   1771    ELEM(div, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1772    ELEM(dl, true, false, GROUP_BLOCK, GROUP_DL_CONTENT),
   1773    ELEM(dt, true, true, GROUP_DL_CONTENT, GROUP_INLINE_ELEMENT),
   1774    ELEM(em, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1775    ELEM(embed, false, false, GROUP_NONE, GROUP_NONE),
   1776    ELEM(fieldset, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1777    ELEM(figcaption, true, false, GROUP_FIGCAPTION, GROUP_FLOW_ELEMENT),
   1778    ELEM(figure, true, true, GROUP_BLOCK,
   1779         GROUP_FLOW_ELEMENT | GROUP_FIGCAPTION),
   1780    ELEM(font, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   1781    ELEM(footer, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1782    ELEM(form, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1783    ELEM(frame, false, false, GROUP_FRAME, GROUP_NONE),
   1784    ELEM(frameset, true, true, GROUP_FRAME, GROUP_FRAME),
   1785    ELEM(h1, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
   1786    ELEM(h2, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
   1787    ELEM(h3, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
   1788    ELEM(h4, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
   1789    ELEM(h5, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
   1790    ELEM(h6, true, false, GROUP_BLOCK | GROUP_HEADING, GROUP_INLINE_ELEMENT),
   1791    ELEM(head, true, false, GROUP_TOPLEVEL, GROUP_HEAD_CONTENT),
   1792    ELEM(header, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1793    ELEM(hgroup, true, false, GROUP_BLOCK, GROUP_HEADING),
   1794    ELEM(hr, false, false, GROUP_BLOCK, GROUP_NONE),
   1795    ELEM(html, true, false, GROUP_TOPLEVEL, GROUP_TOPLEVEL),
   1796    ELEM(i, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   1797    ELEM(iframe, true, true, GROUP_SPECIAL | GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1798    ELEM(image, false, false, GROUP_NONE, GROUP_NONE),
   1799    ELEM(img, false, false, GROUP_SPECIAL | GROUP_PICTURE_CONTENT, GROUP_NONE),
   1800    ELEM(input, false, false, GROUP_FORMCONTROL, GROUP_NONE),
   1801    ELEM(ins, true, true, GROUP_PHRASE | GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1802    ELEM(kbd, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1803    ELEM(keygen, false, false, GROUP_NONE, GROUP_NONE),
   1804    ELEM(label, true, false, GROUP_FORMCONTROL, GROUP_INLINE_ELEMENT),
   1805    ELEM(legend, true, true, GROUP_NONE, GROUP_INLINE_ELEMENT),
   1806    ELEM(li, true, false, GROUP_LI, GROUP_FLOW_ELEMENT),
   1807    ELEM(link, false, false, GROUP_HEAD_CONTENT, GROUP_NONE),
   1808    ELEM(listing, true, true, GROUP_BLOCK, GROUP_INLINE_ELEMENT),
   1809    ELEM(main, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1810    ELEM(map, true, true, GROUP_SPECIAL, GROUP_BLOCK | GROUP_MAP_CONTENT),
   1811    ELEM(mark, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1812    ELEM(marquee, true, false, GROUP_NONE, GROUP_NONE),
   1813    ELEM(menu, true, true, GROUP_BLOCK, GROUP_LI | GROUP_FLOW_ELEMENT),
   1814    ELEM(meta, false, false, GROUP_HEAD_CONTENT, GROUP_NONE),
   1815    ELEM(meter, true, false, GROUP_SPECIAL, GROUP_FLOW_ELEMENT),
   1816    ELEM(multicol, false, false, GROUP_NONE, GROUP_NONE),
   1817    ELEM(nav, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1818    ELEM(nobr, true, false, GROUP_NONE, GROUP_NONE),
   1819    ELEM(noembed, false, false, GROUP_NONE, GROUP_NONE),
   1820    ELEM(noframes, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1821    ELEM(noscript, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1822    ELEM(object, true, true, GROUP_SPECIAL | GROUP_BLOCK,
   1823         GROUP_FLOW_ELEMENT | GROUP_OBJECT_CONTENT),
   1824    // XXX Can contain self and ul because editor does sublists illegally.
   1825    ELEM(ol, true, true, GROUP_BLOCK | GROUP_OL_UL, GROUP_LI | GROUP_OL_UL),
   1826    ELEM(optgroup, true, false, GROUP_SELECT_CONTENT, GROUP_OPTIONS),
   1827    ELEM(option, true, false, GROUP_SELECT_CONTENT | GROUP_OPTIONS, GROUP_LEAF),
   1828    ELEM(output, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   1829    ELEM(p, true, false, GROUP_BLOCK | GROUP_P, GROUP_INLINE_ELEMENT),
   1830    ELEM(param, false, false, GROUP_OBJECT_CONTENT, GROUP_NONE),
   1831    ELEM(picture, true, false, GROUP_SPECIAL, GROUP_PICTURE_CONTENT),
   1832    ELEM(plaintext, false, false, GROUP_NONE, GROUP_NONE),
   1833    ELEM(pre, true, true, GROUP_BLOCK, GROUP_INLINE_ELEMENT),
   1834    ELEM(progress, true, false, GROUP_SPECIAL, GROUP_FLOW_ELEMENT),
   1835    ELEM(q, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   1836    ELEM(rb, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1837    ELEM(rp, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1838    ELEM(rt, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1839    ELEM(rtc, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1840    ELEM(ruby, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1841    ELEM(s, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   1842    ELEM(samp, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1843    ELEM(script, true, false, GROUP_HEAD_CONTENT | GROUP_SPECIAL, GROUP_LEAF),
   1844    ELEM(search, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1845    ELEM(section, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1846    ELEM(select, true, false, GROUP_FORMCONTROL, GROUP_SELECT_CONTENT),
   1847    ELEM(small, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   1848    ELEM(slot, true, false, GROUP_NONE, GROUP_FLOW_ELEMENT),
   1849    ELEM(source, false, false, GROUP_PICTURE_CONTENT, GROUP_NONE),
   1850    ELEM(span, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   1851    ELEM(strike, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   1852    ELEM(strong, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1853    ELEM(style, true, false, GROUP_HEAD_CONTENT, GROUP_LEAF),
   1854    ELEM(sub, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   1855    ELEM(summary, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   1856    ELEM(sup, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   1857    ELEM(table, true, false, GROUP_BLOCK, GROUP_TABLE_CONTENT),
   1858    ELEM(tbody, true, false, GROUP_TABLE_CONTENT, GROUP_TBODY_CONTENT),
   1859    ELEM(td, true, false, GROUP_TR_CONTENT, GROUP_FLOW_ELEMENT),
   1860    ELEM(textarea, true, false, GROUP_FORMCONTROL, GROUP_LEAF),
   1861    ELEM(tfoot, true, false, GROUP_NONE, GROUP_TBODY_CONTENT),
   1862    ELEM(th, true, false, GROUP_TR_CONTENT, GROUP_FLOW_ELEMENT),
   1863    ELEM(thead, true, false, GROUP_NONE, GROUP_TBODY_CONTENT),
   1864    ELEM(template, false, false, GROUP_NONE, GROUP_NONE),
   1865    ELEM(time, true, false, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1866    ELEM(title, true, false, GROUP_HEAD_CONTENT, GROUP_LEAF),
   1867    ELEM(tr, true, false, GROUP_TBODY_CONTENT, GROUP_TR_CONTENT),
   1868    ELEM(track, false, false, GROUP_NONE, GROUP_NONE),
   1869    ELEM(tt, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   1870    ELEM(u, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   1871    // XXX Can contain self and ol because editor does sublists illegally.
   1872    ELEM(ul, true, true, GROUP_BLOCK | GROUP_OL_UL, GROUP_LI | GROUP_OL_UL),
   1873    ELEM(var, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   1874    ELEM(video, false, false, GROUP_NONE, GROUP_NONE),
   1875    ELEM(wbr, false, false, GROUP_NONE, GROUP_NONE),
   1876    ELEM(xmp, true, false, GROUP_BLOCK, GROUP_NONE),
   1877 
   1878    // These aren't elements.
   1879    ELEM(text, false, false, GROUP_LEAF, GROUP_NONE),
   1880    ELEM(whitespace, false, false, GROUP_LEAF, GROUP_NONE),
   1881    ELEM(newline, false, false, GROUP_LEAF, GROUP_NONE),
   1882    ELEM(comment, false, false, GROUP_LEAF, GROUP_NONE),
   1883    ELEM(entity, false, false, GROUP_NONE, GROUP_NONE),
   1884    ELEM(doctypeDecl, false, false, GROUP_NONE, GROUP_NONE),
   1885    ELEM(markupDecl, false, false, GROUP_NONE, GROUP_NONE),
   1886    ELEM(instruction, false, false, GROUP_NONE, GROUP_NONE),
   1887 
   1888    ELEM(userdefined, true, false, GROUP_NONE, GROUP_FLOW_ELEMENT)};
   1889 
   1890 bool HTMLEditUtils::CanNodeContain(nsHTMLTag aParentTagId,
   1891                                   nsHTMLTag aChildTagId) {
   1892  NS_ASSERTION(
   1893      aParentTagId > eHTMLTag_unknown && aParentTagId <= eHTMLTag_userdefined,
   1894      "aParentTagId out of range!");
   1895  NS_ASSERTION(
   1896      aChildTagId > eHTMLTag_unknown && aChildTagId <= eHTMLTag_userdefined,
   1897      "aChildTagId out of range!");
   1898 
   1899 #ifdef DEBUG
   1900  static bool checked = false;
   1901  if (!checked) {
   1902    checked = true;
   1903    int32_t i;
   1904    for (i = 1; i <= eHTMLTag_userdefined; ++i) {
   1905      NS_ASSERTION(kElements[i - 1].mTag == i,
   1906                   "You need to update kElements (missing tags).");
   1907    }
   1908  }
   1909 #endif
   1910 
   1911  // Special-case button.
   1912  if (aParentTagId == eHTMLTag_button) {
   1913    static const nsHTMLTag kButtonExcludeKids[] = {
   1914        eHTMLTag_a,     eHTMLTag_fieldset, eHTMLTag_form,    eHTMLTag_iframe,
   1915        eHTMLTag_input, eHTMLTag_select,   eHTMLTag_textarea};
   1916 
   1917    uint32_t j;
   1918    for (j = 0; j < std::size(kButtonExcludeKids); ++j) {
   1919      if (kButtonExcludeKids[j] == aChildTagId) {
   1920        return false;
   1921      }
   1922    }
   1923  }
   1924 
   1925  // Deprecated elements.
   1926  if (aChildTagId == eHTMLTag_bgsound) {
   1927    return false;
   1928  }
   1929 
   1930  // Bug #67007, dont strip userdefined tags.
   1931  if (aChildTagId == eHTMLTag_userdefined) {
   1932    return true;
   1933  }
   1934 
   1935  const ElementInfo& parent = kElements[aParentTagId - 1];
   1936  if (aParentTagId == aChildTagId) {
   1937    return parent.mCanContainSelf;
   1938  }
   1939 
   1940  const ElementInfo& child = kElements[aChildTagId - 1];
   1941  return !!(parent.mCanContainGroups & child.mGroup);
   1942 }
   1943 
   1944 bool HTMLEditUtils::ContentIsInert(const nsIContent& aContent) {
   1945  for (nsIContent* content :
   1946       aContent.InclusiveFlatTreeAncestorsOfType<nsIContent>()) {
   1947    if (nsIFrame* frame = content->GetPrimaryFrame()) {
   1948      return frame->StyleUI()->IsInert();
   1949    }
   1950    // If it doesn't have primary frame, we need to check its ancestors.
   1951    // This may occur if it's an invisible text node or element nodes whose
   1952    // display is an invisible value.
   1953    if (!content->IsElement()) {
   1954      continue;
   1955    }
   1956    if (content->AsElement()->State().HasState(dom::ElementState::INERT)) {
   1957      return true;
   1958    }
   1959  }
   1960  return false;
   1961 }
   1962 
   1963 bool HTMLEditUtils::IsContainerNode(nsHTMLTag aTagId) {
   1964  NS_ASSERTION(aTagId > eHTMLTag_unknown && aTagId <= eHTMLTag_userdefined,
   1965               "aTagId out of range!");
   1966 
   1967  return kElements[aTagId - 1].mIsContainer;
   1968 }
   1969 
   1970 bool HTMLEditUtils::IsNonListSingleLineContainer(const nsIContent& aContent) {
   1971  return aContent.IsAnyOfHTMLElements(
   1972      nsGkAtoms::address, nsGkAtoms::div, nsGkAtoms::h1, nsGkAtoms::h2,
   1973      nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6,
   1974      nsGkAtoms::listing, nsGkAtoms::p, nsGkAtoms::pre, nsGkAtoms::xmp);
   1975 }
   1976 
   1977 bool HTMLEditUtils::IsSingleLineContainer(const nsIContent& aContent) {
   1978  return IsNonListSingleLineContainer(aContent) ||
   1979         aContent.IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::dt,
   1980                                      nsGkAtoms::dd);
   1981 }
   1982 
   1983 // static
   1984 template <typename PT, typename CT>
   1985 nsIContent* HTMLEditUtils::GetPreviousContent(
   1986    const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions,
   1987    BlockInlineCheck aBlockInlineCheck,
   1988    const Element* aAncestorLimiter /* = nullptr */) {
   1989  MOZ_ASSERT(aPoint.IsSetAndValid());
   1990  NS_WARNING_ASSERTION(
   1991      !aPoint.IsInDataNode() || aPoint.IsInTextNode(),
   1992      "GetPreviousContent() doesn't assume that the start point is a "
   1993      "data node except text node");
   1994 
   1995  // If we are at the beginning of the node, or it is a text node, then just
   1996  // look before it.
   1997  if (aPoint.IsStartOfContainer() || aPoint.IsInTextNode()) {
   1998    if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   1999        aPoint.IsInContentNode() &&
   2000        HTMLEditUtils::IsBlockElement(
   2001            *aPoint.template ContainerAs<nsIContent>(),
   2002            UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) {
   2003      // If we aren't allowed to cross blocks, don't look before this block.
   2004      return nullptr;
   2005    }
   2006    return HTMLEditUtils::GetPreviousContent(
   2007        *aPoint.GetContainer(), aOptions, aBlockInlineCheck, aAncestorLimiter);
   2008  }
   2009 
   2010  // else look before the child at 'aOffset'
   2011  if (aPoint.GetChild()) {
   2012    return HTMLEditUtils::GetPreviousContent(
   2013        *aPoint.GetChild(), aOptions, aBlockInlineCheck, aAncestorLimiter);
   2014  }
   2015 
   2016  // unless there isn't one, in which case we are at the end of the node
   2017  // and want the deep-right child.
   2018  nsIContent* lastLeafContent = HTMLEditUtils::GetLastLeafContent(
   2019      *aPoint.GetContainer(),
   2020      {aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
   2021           ? LeafNodeType::LeafNodeOrChildBlock
   2022           : LeafNodeType::OnlyLeafNode},
   2023      aBlockInlineCheck);
   2024  if (!lastLeafContent) {
   2025    return nullptr;
   2026  }
   2027 
   2028  if (!HTMLEditUtils::IsContentIgnored(*lastLeafContent, aOptions)) {
   2029    return lastLeafContent;
   2030  }
   2031 
   2032  // restart the search from the non-editable node we just found
   2033  return HTMLEditUtils::GetPreviousContent(*lastLeafContent, aOptions,
   2034                                           aBlockInlineCheck, aAncestorLimiter);
   2035 }
   2036 
   2037 // static
   2038 template <typename PT, typename CT>
   2039 nsIContent* HTMLEditUtils::GetNextContent(
   2040    const EditorDOMPointBase<PT, CT>& aPoint, const WalkTreeOptions& aOptions,
   2041    BlockInlineCheck aBlockInlineCheck,
   2042    const Element* aAncestorLimiter /* = nullptr */) {
   2043  MOZ_ASSERT(aPoint.IsSetAndValid());
   2044  NS_WARNING_ASSERTION(
   2045      !aPoint.IsInDataNode() || aPoint.IsInTextNode(),
   2046      "GetNextContent() doesn't assume that the start point is a "
   2047      "data node except text node");
   2048 
   2049  auto point = aPoint.template To<EditorRawDOMPoint>();
   2050 
   2051  // if the container is a text node, use its location instead
   2052  if (point.IsInTextNode()) {
   2053    point.SetAfter(point.GetContainer());
   2054    if (NS_WARN_IF(!point.IsSet())) {
   2055      return nullptr;
   2056    }
   2057  }
   2058 
   2059  if (point.GetChild()) {
   2060    if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   2061        HTMLEditUtils::IsBlockElement(
   2062            *point.GetChild(),
   2063            UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) {
   2064      return point.GetChild();
   2065    }
   2066 
   2067    nsIContent* firstLeafContent = HTMLEditUtils::GetFirstLeafContent(
   2068        *point.GetChild(),
   2069        {aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
   2070             ? LeafNodeType::LeafNodeOrChildBlock
   2071             : LeafNodeType::OnlyLeafNode},
   2072        aBlockInlineCheck);
   2073    if (!firstLeafContent) {
   2074      return point.GetChild();
   2075    }
   2076 
   2077    // XXX Why do we need to do this check?  The leaf node must be a descendant
   2078    //     of `point.GetChild()`.
   2079    if (aAncestorLimiter &&
   2080        (firstLeafContent == aAncestorLimiter ||
   2081         !firstLeafContent->IsInclusiveDescendantOf(aAncestorLimiter))) {
   2082      return nullptr;
   2083    }
   2084 
   2085    if (!HTMLEditUtils::IsContentIgnored(*firstLeafContent, aOptions)) {
   2086      return firstLeafContent;
   2087    }
   2088 
   2089    // restart the search from the non-editable node we just found
   2090    return HTMLEditUtils::GetNextContent(*firstLeafContent, aOptions,
   2091                                         aBlockInlineCheck, aAncestorLimiter);
   2092  }
   2093 
   2094  // unless there isn't one, in which case we are at the end of the node
   2095  // and want the next one.
   2096  if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   2097      point.IsInContentNode() &&
   2098      HTMLEditUtils::IsBlockElement(
   2099          *point.template ContainerAs<nsIContent>(),
   2100          UseComputedDisplayStyleIfAuto(aBlockInlineCheck))) {
   2101    // don't cross out of parent block
   2102    return nullptr;
   2103  }
   2104 
   2105  return HTMLEditUtils::GetNextContent(*point.GetContainer(), aOptions,
   2106                                       aBlockInlineCheck, aAncestorLimiter);
   2107 }
   2108 
   2109 // static
   2110 nsIContent* HTMLEditUtils::GetAdjacentLeafContent(
   2111    const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
   2112    const WalkTreeOptions& aOptions, BlockInlineCheck aBlockInlineCheck,
   2113    const Element* aAncestorLimiter /* = nullptr */) {
   2114  // called only by GetPriorNode so we don't need to check params.
   2115  MOZ_ASSERT(&aNode != aAncestorLimiter);
   2116  MOZ_ASSERT_IF(aAncestorLimiter,
   2117                aAncestorLimiter->IsInclusiveDescendantOf(aAncestorLimiter));
   2118 
   2119  const nsINode* node = &aNode;
   2120  for (;;) {
   2121    // if aNode has a sibling in the right direction, return
   2122    // that sibling's closest child (or itself if it has no children)
   2123    nsIContent* sibling = aWalkTreeDirection == WalkTreeDirection::Forward
   2124                              ? node->GetNextSibling()
   2125                              : node->GetPreviousSibling();
   2126    if (sibling) {
   2127      // XXX If `sibling` belongs to siblings of inclusive ancestors of aNode,
   2128      //     perhaps, we need to use
   2129      //     PreferDisplayOutsideIfUsingDisplay(aBlockInlineCheck) here.
   2130      if (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   2131          HTMLEditUtils::IsBlockElement(
   2132              *sibling,
   2133              UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) {
   2134        // don't look inside previous sibling, since it is a block
   2135        return sibling;
   2136      }
   2137      const LeafNodeTypes leafNodeTypes = {
   2138          aOptions.contains(WalkTreeOption::StopAtBlockBoundary)
   2139              ? LeafNodeType::LeafNodeOrChildBlock
   2140              : LeafNodeType::OnlyLeafNode};
   2141      nsIContent* leafContent =
   2142          aWalkTreeDirection == WalkTreeDirection::Forward
   2143              ? HTMLEditUtils::GetFirstLeafContent(*sibling, leafNodeTypes,
   2144                                                   aBlockInlineCheck)
   2145              : HTMLEditUtils::GetLastLeafContent(*sibling, leafNodeTypes,
   2146                                                  aBlockInlineCheck);
   2147      return leafContent ? leafContent : sibling;
   2148    }
   2149 
   2150    nsIContent* parent = node->GetParent();
   2151    if (!parent) {
   2152      return nullptr;
   2153    }
   2154 
   2155    if (parent == aAncestorLimiter ||
   2156        (aOptions.contains(WalkTreeOption::StopAtBlockBoundary) &&
   2157         HTMLEditUtils::IsBlockElement(
   2158             *parent, UseComputedDisplayStyleIfAuto(aBlockInlineCheck)))) {
   2159      return nullptr;
   2160    }
   2161 
   2162    node = parent;
   2163  }
   2164 
   2165  MOZ_ASSERT_UNREACHABLE("What part of for(;;) do you not understand?");
   2166  return nullptr;
   2167 }
   2168 
   2169 // static
   2170 nsIContent* HTMLEditUtils::GetAdjacentContent(
   2171    const nsINode& aNode, WalkTreeDirection aWalkTreeDirection,
   2172    const WalkTreeOptions& aOptions, BlockInlineCheck aBlockInlineCheck,
   2173    const Element* aAncestorLimiter /* = nullptr */) {
   2174  if (&aNode == aAncestorLimiter) {
   2175    // Don't allow traversal above the root node! This helps
   2176    // prevent us from accidentally editing browser content
   2177    // when the editor is in a text widget.
   2178    return nullptr;
   2179  }
   2180 
   2181  nsIContent* leafContent = HTMLEditUtils::GetAdjacentLeafContent(
   2182      aNode, aWalkTreeDirection, aOptions, aBlockInlineCheck, aAncestorLimiter);
   2183  if (!leafContent) {
   2184    return nullptr;
   2185  }
   2186 
   2187  if (!HTMLEditUtils::IsContentIgnored(*leafContent, aOptions)) {
   2188    return leafContent;
   2189  }
   2190 
   2191  return HTMLEditUtils::GetAdjacentContent(*leafContent, aWalkTreeDirection,
   2192                                           aOptions, aBlockInlineCheck,
   2193                                           aAncestorLimiter);
   2194 }
   2195 
   2196 // static
   2197 template <typename EditorDOMPointType>
   2198 EditorDOMPointType HTMLEditUtils::GetPreviousEditablePoint(
   2199    nsIContent& aContent, const Element* aAncestorLimiter,
   2200    InvisibleWhiteSpaces aInvisibleWhiteSpaces,
   2201    TableBoundary aHowToTreatTableBoundary) {
   2202  MOZ_ASSERT(HTMLEditUtils::IsSimplyEditableNode(aContent));
   2203  NS_ASSERTION(!HTMLEditUtils::IsAnyTableElementExceptColumnElement(aContent) ||
   2204                   HTMLEditUtils::IsTableCellOrCaptionElement(aContent),
   2205               "HTMLEditUtils::GetPreviousEditablePoint() may return a point "
   2206               "between table structure elements");
   2207 
   2208  if (&aContent == aAncestorLimiter) {
   2209    return EditorDOMPointType();
   2210  }
   2211 
   2212  // First, look for previous content.
   2213  nsIContent* previousContent = aContent.GetPreviousSibling();
   2214  if (!previousContent) {
   2215    if (!aContent.GetParentElement()) {
   2216      return EditorDOMPointType();
   2217    }
   2218    nsIContent* inclusiveAncestor = &aContent;
   2219    for (Element* const parentElement : aContent.AncestorsOfType<Element>()) {
   2220      if (parentElement == aAncestorLimiter ||
   2221          !HTMLEditUtils::IsSimplyEditableNode(*parentElement) ||
   2222          !HTMLEditUtils::CanCrossContentBoundary(*parentElement,
   2223                                                  aHowToTreatTableBoundary)) {
   2224        // If cannot cross the parent element boundary, return the point of
   2225        // last inclusive ancestor point.
   2226        return EditorDOMPointType(inclusiveAncestor);
   2227      }
   2228 
   2229      // Start of the parent element is a next editable point if it's an
   2230      // element which is not a table structure element.
   2231      if (!HTMLEditUtils::IsAnyTableElementExceptColumnElement(
   2232              *parentElement) ||
   2233          HTMLEditUtils::IsTableCellOrCaptionElement(*parentElement)) {
   2234        inclusiveAncestor = parentElement;
   2235      }
   2236 
   2237      previousContent = parentElement->GetPreviousSibling();
   2238      if (!previousContent) {
   2239        continue;  // Keep looking for previous sibling of an ancestor.
   2240      }
   2241 
   2242      // XXX Should we ignore data node like CDATA, Comment, etc?
   2243 
   2244      // If previous content is not editable, let's return the point after it.
   2245      if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
   2246        return EditorDOMPointType::After(*previousContent);
   2247      }
   2248 
   2249      // If cannot cross previous content boundary, return start of last
   2250      // inclusive ancestor.
   2251      if (!HTMLEditUtils::CanCrossContentBoundary(*previousContent,
   2252                                                  aHowToTreatTableBoundary)) {
   2253        return inclusiveAncestor == &aContent
   2254                   ? EditorDOMPointType(inclusiveAncestor)
   2255                   : EditorDOMPointType(inclusiveAncestor, 0);
   2256      }
   2257      break;
   2258    }
   2259    if (!previousContent) {
   2260      return EditorDOMPointType(inclusiveAncestor);
   2261    }
   2262  } else if (!HTMLEditUtils::IsSimplyEditableNode(*previousContent)) {
   2263    return EditorDOMPointType::After(*previousContent);
   2264  } else if (!HTMLEditUtils::CanCrossContentBoundary(
   2265                 *previousContent, aHowToTreatTableBoundary)) {
   2266    return EditorDOMPointType(&aContent);
   2267  }
   2268 
   2269  // Next, look for end of the previous content.
   2270  nsIContent* leafContent = previousContent;
   2271  if (previousContent->GetChildCount() &&
   2272      HTMLEditUtils::IsContainerNode(*previousContent)) {
   2273    for (nsIContent* maybeLeafContent = previousContent->GetLastChild();
   2274         maybeLeafContent;
   2275         maybeLeafContent = maybeLeafContent->GetLastChild()) {
   2276      // If it's not an editable content or cannot cross the boundary,
   2277      // return the point after the content.  Note that in this case,
   2278      // the content must not be any table elements except `<table>`
   2279      // because we've climbed down the tree.
   2280      if (!HTMLEditUtils::IsSimplyEditableNode(*maybeLeafContent) ||
   2281          !HTMLEditUtils::CanCrossContentBoundary(*maybeLeafContent,
   2282                                                  aHowToTreatTableBoundary)) {
   2283        return EditorDOMPointType::After(*maybeLeafContent);
   2284      }
   2285      leafContent = maybeLeafContent;
   2286      if (!HTMLEditUtils::IsContainerNode(*leafContent)) {
   2287        break;
   2288      }
   2289    }
   2290  }
   2291 
   2292  if (leafContent->IsText()) {
   2293    Text* textNode = leafContent->AsText();
   2294    if (aInvisibleWhiteSpaces == InvisibleWhiteSpaces::Preserve) {
   2295      return EditorDOMPointType::AtEndOf(*textNode);
   2296    }
   2297    // There may be invisible trailing white-spaces which should be
   2298    // ignored.  Let's scan its start.
   2299    return WSRunScanner::GetAfterLastVisiblePoint<EditorDOMPointType>(
   2300        {WSRunScanner::Option::OnlyEditableNodes}, *textNode);
   2301  }
   2302 
   2303  // If it's a container element, return end of it.  Otherwise, return
   2304  // the point after the non-container element.
   2305  return HTMLEditUtils::IsContainerNode(*leafContent)
   2306             ? EditorDOMPointType::AtEndOf(*leafContent)
   2307             : EditorDOMPointType::After(*leafContent);
   2308 }
   2309 
   2310 // static
   2311 template <typename EditorDOMPointType>
   2312 EditorDOMPointType HTMLEditUtils::GetNextEditablePoint(
   2313    nsIContent& aContent, const Element* aAncestorLimiter,
   2314    InvisibleWhiteSpaces aInvisibleWhiteSpaces,
   2315    TableBoundary aHowToTreatTableBoundary) {
   2316  MOZ_ASSERT(HTMLEditUtils::IsSimplyEditableNode(aContent));
   2317  NS_ASSERTION(!HTMLEditUtils::IsAnyTableElementExceptColumnElement(aContent) ||
   2318                   HTMLEditUtils::IsTableCellOrCaptionElement(aContent),
   2319               "HTMLEditUtils::GetPreviousEditablePoint() may return a point "
   2320               "between table structure elements");
   2321 
   2322  if (&aContent == aAncestorLimiter) {
   2323    return EditorDOMPointType();
   2324  }
   2325 
   2326  // First, look for next content.
   2327  nsIContent* nextContent = aContent.GetNextSibling();
   2328  if (!nextContent) {
   2329    if (!aContent.GetParentElement()) {
   2330      return EditorDOMPointType();
   2331    }
   2332    nsIContent* inclusiveAncestor = &aContent;
   2333    for (Element* const parentElement : aContent.AncestorsOfType<Element>()) {
   2334      if (parentElement == aAncestorLimiter ||
   2335          !HTMLEditUtils::IsSimplyEditableNode(*parentElement) ||
   2336          !HTMLEditUtils::CanCrossContentBoundary(*parentElement,
   2337                                                  aHowToTreatTableBoundary)) {
   2338        // If cannot cross the parent element boundary, return the point of
   2339        // last inclusive ancestor point.
   2340        return EditorDOMPointType(inclusiveAncestor);
   2341      }
   2342 
   2343      // End of the parent element is a next editable point if it's an
   2344      // element which is not a table structure element.
   2345      if (!HTMLEditUtils::IsAnyTableElementExceptColumnElement(
   2346              *parentElement) ||
   2347          HTMLEditUtils::IsTableCellOrCaptionElement(*parentElement)) {
   2348        inclusiveAncestor = parentElement;
   2349      }
   2350 
   2351      nextContent = parentElement->GetNextSibling();
   2352      if (!nextContent) {
   2353        continue;  // Keep looking for next sibling of an ancestor.
   2354      }
   2355 
   2356      // XXX Should we ignore data node like CDATA, Comment, etc?
   2357 
   2358      // If next content is not editable, let's return the point after
   2359      // the last inclusive ancestor.
   2360      if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
   2361        return EditorDOMPointType::After(*parentElement);
   2362      }
   2363 
   2364      // If cannot cross next content boundary, return after the last
   2365      // inclusive ancestor.
   2366      if (!HTMLEditUtils::CanCrossContentBoundary(*nextContent,
   2367                                                  aHowToTreatTableBoundary)) {
   2368        return EditorDOMPointType::After(*inclusiveAncestor);
   2369      }
   2370      break;
   2371    }
   2372    if (!nextContent) {
   2373      return EditorDOMPointType::After(*inclusiveAncestor);
   2374    }
   2375  } else if (!HTMLEditUtils::IsSimplyEditableNode(*nextContent)) {
   2376    return EditorDOMPointType::After(aContent);
   2377  } else if (!HTMLEditUtils::CanCrossContentBoundary(
   2378                 *nextContent, aHowToTreatTableBoundary)) {
   2379    return EditorDOMPointType::After(aContent);
   2380  }
   2381 
   2382  // Next, look for start of the next content.
   2383  nsIContent* leafContent = nextContent;
   2384  if (nextContent->GetChildCount() &&
   2385      HTMLEditUtils::IsContainerNode(*nextContent)) {
   2386    for (nsIContent* maybeLeafContent = nextContent->GetFirstChild();
   2387         maybeLeafContent;
   2388         maybeLeafContent = maybeLeafContent->GetFirstChild()) {
   2389      // If it's not an editable content or cannot cross the boundary,
   2390      // return the point at the content (i.e., start of its parent).  Note
   2391      // that in this case, the content must not be any table elements except
   2392      // `<table>` because we've climbed down the tree.
   2393      if (!HTMLEditUtils::IsSimplyEditableNode(*maybeLeafContent) ||
   2394          !HTMLEditUtils::CanCrossContentBoundary(*maybeLeafContent,
   2395                                                  aHowToTreatTableBoundary)) {
   2396        return EditorDOMPointType(maybeLeafContent);
   2397      }
   2398      leafContent = maybeLeafContent;
   2399      if (!HTMLEditUtils::IsContainerNode(*leafContent)) {
   2400        break;
   2401      }
   2402    }
   2403  }
   2404 
   2405  if (leafContent->IsText()) {
   2406    Text* textNode = leafContent->AsText();
   2407    if (aInvisibleWhiteSpaces == InvisibleWhiteSpaces::Preserve) {
   2408      return EditorDOMPointType(textNode, 0);
   2409    }
   2410    // There may be invisible leading white-spaces which should be
   2411    // ignored.  Let's scan its start.
   2412    return WSRunScanner::GetFirstVisiblePoint<EditorDOMPointType>(
   2413        {WSRunScanner::Option::OnlyEditableNodes}, *textNode);
   2414  }
   2415 
   2416  // If it's a container element, return start of it.  Otherwise, return
   2417  // the point at the non-container element (i.e., start of its parent).
   2418  return HTMLEditUtils::IsContainerNode(*leafContent)
   2419             ? EditorDOMPointType(leafContent, 0)
   2420             : EditorDOMPointType(leafContent);
   2421 }
   2422 
   2423 // static
   2424 Element* HTMLEditUtils::GetAncestorElement(
   2425    const nsIContent& aContent, const AncestorTypes& aAncestorTypes,
   2426    BlockInlineCheck aBlockInlineCheck,
   2427    const Element* aAncestorLimiter /* = nullptr */) {
   2428  MOZ_ASSERT(
   2429      aAncestorTypes.contains(AncestorType::ClosestBlockElement) ||
   2430      aAncestorTypes.contains(AncestorType::ClosestContainerElement) ||
   2431      aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock) ||
   2432      aAncestorTypes.contains(AncestorType::ClosestButtonElement) ||
   2433      aAncestorTypes.contains(
   2434          AncestorType::ReturnAncestorLimiterIfNoProperAncestor));
   2435  MOZ_ASSERT_IF(
   2436      aAncestorTypes.contains(AncestorType::ClosestBlockElement),
   2437      !aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock));
   2438  MOZ_ASSERT_IF(
   2439      aAncestorTypes.contains(AncestorType::ClosestContainerElement),
   2440      !aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock));
   2441  MOZ_ASSERT_IF(
   2442      aAncestorTypes.contains(AncestorType::StopAtClosestButtonElement),
   2443      !aAncestorTypes.contains(AncestorType::ClosestButtonElement));
   2444  MOZ_ASSERT_IF(
   2445      aAncestorTypes.contains(AncestorType::ClosestButtonElement),
   2446      !aAncestorTypes.contains(AncestorType::StopAtClosestButtonElement));
   2447 
   2448  aBlockInlineCheck = UseComputedDisplayStyleIfAuto(aBlockInlineCheck);
   2449 
   2450  const Element* theBodyElement = aContent.OwnerDoc()->GetBody();
   2451  const Element* theDocumentElement = aContent.OwnerDoc()->GetDocumentElement();
   2452  Element* lastAncestorElement = nullptr;
   2453  const bool editableElementOnly =
   2454      aAncestorTypes.contains(AncestorType::EditableElement);
   2455  const bool lookingForClosestBlockElement =
   2456      aAncestorTypes.contains(AncestorType::ClosestBlockElement);
   2457  const bool lookingForClosestContainerElement =
   2458      aAncestorTypes.contains(AncestorType::ClosestContainerElement);
   2459  const bool lookingForMostDistantInlineElementInBlock =
   2460      aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock);
   2461  const bool stopAtClosestBlockElement =
   2462      lookingForClosestBlockElement ||
   2463      lookingForMostDistantInlineElementInBlock;
   2464  const bool fallbackToLimiter = aAncestorTypes.contains(
   2465      AncestorType::ReturnAncestorLimiterIfNoProperAncestor);
   2466  const bool stopAtButton =
   2467      aAncestorTypes.contains(AncestorType::StopAtClosestButtonElement);
   2468  const bool lookingForButtonElement =
   2469      aAncestorTypes.contains(AncestorType::ClosestButtonElement);
   2470  const bool ignoreHRElement =
   2471      aAncestorTypes.contains(AncestorType::IgnoreHRElement);
   2472  const auto IsLimiter = [&](const nsIContent& aContent) -> bool {
   2473    return &aContent == aAncestorLimiter ||
   2474           // If aContent is the body element or the document element, we
   2475           // shouldn't climb up to its parent.
   2476           (editableElementOnly &&
   2477            (&aContent == theBodyElement || &aContent == theDocumentElement ||
   2478             aContent.IsEditingHost()));
   2479  };
   2480  const auto IsSearchingElementTypeExceptFallbackToRoot =
   2481      [&](const nsIContent& aContent) -> bool {
   2482    if (!aContent.IsElement() ||
   2483        (ignoreHRElement && aContent.IsHTMLElement(nsGkAtoms::hr))) {
   2484      return false;
   2485    }
   2486    if (editableElementOnly &&
   2487        !EditorUtils::IsEditableContent(aContent, EditorType::HTML)) {
   2488      return false;
   2489    }
   2490    return (lookingForClosestBlockElement &&
   2491            HTMLEditUtils::IsBlockElement(aContent, aBlockInlineCheck)) ||
   2492           (lookingForClosestContainerElement && aContent.IsElement() &&
   2493            HTMLEditUtils::IsContainerNode(aContent)) ||
   2494           (lookingForMostDistantInlineElementInBlock &&
   2495            HTMLEditUtils::IsInlineContent(aContent, aBlockInlineCheck)) ||
   2496           (lookingForButtonElement &&
   2497            aContent.IsHTMLElement(nsGkAtoms::button));
   2498  };
   2499  if (IsLimiter(aContent)) {
   2500    return nullptr;
   2501  }
   2502  for (Element* element : aContent.AncestorsOfType<Element>()) {
   2503    if (editableElementOnly &&
   2504        !EditorUtils::IsEditableContent(*element, EditorType::HTML)) {
   2505      return lastAncestorElement;  // editing host (can be inline element)
   2506    }
   2507    if (ignoreHRElement && element->IsHTMLElement(nsGkAtoms::hr)) {
   2508      if (IsLimiter(*element)) {
   2509        if (fallbackToLimiter && !lastAncestorElement) {
   2510          lastAncestorElement = element;
   2511        }
   2512        return lastAncestorElement;
   2513      }
   2514      continue;
   2515    }
   2516    if (stopAtButton && element->IsHTMLElement(nsGkAtoms::button)) {
   2517      return lastAncestorElement;
   2518    }
   2519    if (lookingForButtonElement && element->IsHTMLElement(nsGkAtoms::button)) {
   2520      return element;  // closest button element
   2521    }
   2522    if (lookingForClosestContainerElement &&
   2523        HTMLEditUtils::IsContainerNode(*element)) {
   2524      return element;  // closest container element
   2525    }
   2526    if (stopAtClosestBlockElement &&
   2527        HTMLEditUtils::IsBlockElement(*element, aBlockInlineCheck)) {
   2528      if (lookingForClosestBlockElement) {
   2529        return element;  // closest block element
   2530      }
   2531      MOZ_ASSERT_IF(lastAncestorElement,
   2532                    HTMLEditUtils::IsInlineContent(*lastAncestorElement,
   2533                                                   aBlockInlineCheck));
   2534      if (!lastAncestorElement && fallbackToLimiter && IsLimiter(*element)) {
   2535        return element;  // closest block element and a limiter
   2536      }
   2537      return lastAncestorElement;  // the last inline element which we found
   2538    }
   2539    if (IsSearchingElementTypeExceptFallbackToRoot(*element)) {
   2540      lastAncestorElement = element;
   2541    }
   2542    if (IsLimiter(*element)) {
   2543      if (fallbackToLimiter && !lastAncestorElement) {
   2544        lastAncestorElement = element;
   2545      }
   2546      return lastAncestorElement;
   2547    }
   2548  }
   2549  return lastAncestorElement;
   2550 }
   2551 
   2552 // static
   2553 Element* HTMLEditUtils::GetInclusiveAncestorElement(
   2554    const nsIContent& aContent, const AncestorTypes& aAncestorTypes,
   2555    BlockInlineCheck aBlockInlineCheck,
   2556    const Element* aAncestorLimiter /* = nullptr */) {
   2557  MOZ_ASSERT(
   2558      aAncestorTypes.contains(AncestorType::ClosestBlockElement) ||
   2559      aAncestorTypes.contains(AncestorType::ClosestContainerElement) ||
   2560      aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock) ||
   2561      aAncestorTypes.contains(AncestorType::ClosestButtonElement) ||
   2562      aAncestorTypes.contains(
   2563          AncestorType::ReturnAncestorLimiterIfNoProperAncestor));
   2564  MOZ_ASSERT_IF(
   2565      aAncestorTypes.contains(AncestorType::ClosestBlockElement),
   2566      !aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock));
   2567  MOZ_ASSERT_IF(
   2568      aAncestorTypes.contains(AncestorType::ClosestContainerElement),
   2569      !aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock));
   2570  MOZ_ASSERT_IF(
   2571      aAncestorTypes.contains(AncestorType::StopAtClosestButtonElement),
   2572      !aAncestorTypes.contains(AncestorType::ClosestButtonElement));
   2573  MOZ_ASSERT_IF(
   2574      aAncestorTypes.contains(AncestorType::ClosestButtonElement),
   2575      !aAncestorTypes.contains(AncestorType::StopAtClosestButtonElement));
   2576 
   2577  aBlockInlineCheck = UseComputedDisplayStyleIfAuto(aBlockInlineCheck);
   2578 
   2579  const Element* theBodyElement = aContent.OwnerDoc()->GetBody();
   2580  const Element* theDocumentElement = aContent.OwnerDoc()->GetDocumentElement();
   2581  const bool editableElementOnly =
   2582      aAncestorTypes.contains(AncestorType::EditableElement);
   2583  const bool lookingForClosestBlockElement =
   2584      aAncestorTypes.contains(AncestorType::ClosestBlockElement);
   2585  const bool lookingForClosestContainerElement =
   2586      aAncestorTypes.contains(AncestorType::ClosestContainerElement);
   2587  const bool lookingForMostDistantInlineElementInBlock =
   2588      aAncestorTypes.contains(AncestorType::MostDistantInlineElementInBlock);
   2589  const bool stopAtClosestBlockElement =
   2590      lookingForClosestBlockElement ||
   2591      lookingForMostDistantInlineElementInBlock;
   2592  const bool stopAtButton =
   2593      aAncestorTypes.contains(AncestorType::StopAtClosestButtonElement);
   2594  const bool lookingForButtonElement =
   2595      aAncestorTypes.contains(AncestorType::ClosestButtonElement);
   2596  const bool ignoreHRElement =
   2597      aAncestorTypes.contains(AncestorType::IgnoreHRElement);
   2598  const bool fallbackToLimiter = aAncestorTypes.contains(
   2599      AncestorType::ReturnAncestorLimiterIfNoProperAncestor);
   2600  const bool lookingForMostDistantElement =
   2601      lookingForMostDistantInlineElementInBlock;
   2602  const auto IsLimiter = [&](const nsIContent& aContent) -> bool {
   2603    return &aContent == aAncestorLimiter || !aContent.GetParent() ||
   2604           // If aContent is the body element or the document element, we
   2605           // shouldn't climb up to its parent.
   2606           (editableElementOnly &&
   2607            (&aContent == theBodyElement || &aContent == theDocumentElement ||
   2608             aContent.IsEditingHost()));
   2609  };
   2610  const auto IsSearchingElementTypeExceptFallbackToRoot =
   2611      [&](const nsIContent& aContent) -> bool {
   2612    if (!aContent.IsElement() ||
   2613        (ignoreHRElement && aContent.IsHTMLElement(nsGkAtoms::hr))) {
   2614      return false;
   2615    }
   2616    if (editableElementOnly &&
   2617        !EditorUtils::IsEditableContent(aContent, EditorType::HTML)) {
   2618      return false;
   2619    }
   2620    return (lookingForClosestBlockElement &&
   2621            HTMLEditUtils::IsBlockElement(aContent, aBlockInlineCheck)) ||
   2622           (lookingForClosestContainerElement && aContent.IsElement() &&
   2623            HTMLEditUtils::IsContainerNode(aContent)) ||
   2624           (lookingForMostDistantInlineElementInBlock &&
   2625            HTMLEditUtils::IsInlineContent(aContent, aBlockInlineCheck)) ||
   2626           (lookingForButtonElement &&
   2627            aContent.IsHTMLElement(nsGkAtoms::button));
   2628  };
   2629 
   2630  if (IsLimiter(aContent)) {
   2631    return fallbackToLimiter ||
   2632                   IsSearchingElementTypeExceptFallbackToRoot(aContent)
   2633               ? const_cast<Element*>(aContent.AsElement())
   2634               : nullptr;
   2635  }
   2636 
   2637  if (stopAtButton && aContent.IsHTMLElement(nsGkAtoms::button)) {
   2638    return IsSearchingElementTypeExceptFallbackToRoot(aContent)
   2639               ? const_cast<Element*>(aContent.AsElement())
   2640               : nullptr;
   2641  }
   2642 
   2643  if (lookingForButtonElement && aContent.IsHTMLElement(nsGkAtoms::button)) {
   2644    return const_cast<Element*>(aContent.AsElement());
   2645  }
   2646 
   2647  if (lookingForClosestContainerElement && aContent.IsElement() &&
   2648      HTMLEditUtils::IsContainerNode(aContent)) {
   2649    return IsSearchingElementTypeExceptFallbackToRoot(aContent)
   2650               ? const_cast<Element*>(aContent.AsElement())
   2651               : nullptr;
   2652  }
   2653 
   2654  // If aContent is a block element, we don't need to climb up the tree.
   2655  // Consider the result right now.
   2656  if (stopAtClosestBlockElement &&
   2657      HTMLEditUtils::IsBlockElement(aContent, aBlockInlineCheck) &&
   2658      !(ignoreHRElement && aContent.IsHTMLElement(nsGkAtoms::hr))) {
   2659    return IsSearchingElementTypeExceptFallbackToRoot(aContent)
   2660               ? const_cast<Element*>(aContent.AsElement())
   2661               : nullptr;
   2662  }
   2663 
   2664  Element* const result = HTMLEditUtils::GetAncestorElement(
   2665      aContent, aAncestorTypes, aBlockInlineCheck, aAncestorLimiter);
   2666  // If we're looking for the most distant ancestor of a type and there is no
   2667  // such ancestor, aContent may be the most distant inclusive ancestor of the
   2668  // type.
   2669  if (lookingForMostDistantElement &&
   2670      (!result || (result != &aContent && IsLimiter(*result) &&
   2671                   !IsSearchingElementTypeExceptFallbackToRoot(*result))) &&
   2672      IsSearchingElementTypeExceptFallbackToRoot(aContent)) {
   2673    return const_cast<Element*>(aContent.AsElement());
   2674  }
   2675  return result;
   2676 }
   2677 
   2678 // static
   2679 Element* HTMLEditUtils::GetClosestAncestorAnyListElement(
   2680    const nsIContent& aContent) {
   2681  for (Element* const element : aContent.AncestorsOfType<Element>()) {
   2682    if (HTMLEditUtils::IsListElement(*element)) {
   2683      return element;
   2684    }
   2685  }
   2686  return nullptr;
   2687 }
   2688 
   2689 // static
   2690 Element* HTMLEditUtils::GetClosestInclusiveAncestorAnyListElement(
   2691    const nsIContent& aContent) {
   2692  for (Element* const element : aContent.InclusiveAncestorsOfType<Element>()) {
   2693    if (HTMLEditUtils::IsListElement(*element)) {
   2694      return element;
   2695    }
   2696  }
   2697  return nullptr;
   2698 }
   2699 
   2700 EditAction HTMLEditUtils::GetEditActionForInsert(const nsAtom& aTagName) {
   2701  // This method may be in a hot path.  So, return only necessary
   2702  // EditAction::eInsert*Element.
   2703  if (&aTagName == nsGkAtoms::ul) {
   2704    // For InputEvent.inputType, "insertUnorderedList".
   2705    return EditAction::eInsertUnorderedListElement;
   2706  }
   2707  if (&aTagName == nsGkAtoms::ol) {
   2708    // For InputEvent.inputType, "insertOrderedList".
   2709    return EditAction::eInsertOrderedListElement;
   2710  }
   2711  if (&aTagName == nsGkAtoms::hr) {
   2712    // For InputEvent.inputType, "insertHorizontalRule".
   2713    return EditAction::eInsertHorizontalRuleElement;
   2714  }
   2715  return EditAction::eInsertNode;
   2716 }
   2717 
   2718 EditAction HTMLEditUtils::GetEditActionForRemoveList(const nsAtom& aTagName) {
   2719  // This method may be in a hot path.  So, return only necessary
   2720  // EditAction::eRemove*Element.
   2721  if (&aTagName == nsGkAtoms::ul) {
   2722    // For InputEvent.inputType, "insertUnorderedList".
   2723    return EditAction::eRemoveUnorderedListElement;
   2724  }
   2725  if (&aTagName == nsGkAtoms::ol) {
   2726    // For InputEvent.inputType, "insertOrderedList".
   2727    return EditAction::eRemoveOrderedListElement;
   2728  }
   2729  return EditAction::eRemoveListElement;
   2730 }
   2731 
   2732 EditAction HTMLEditUtils::GetEditActionForInsert(const Element& aElement) {
   2733  return GetEditActionForInsert(*aElement.NodeInfo()->NameAtom());
   2734 }
   2735 
   2736 EditAction HTMLEditUtils::GetEditActionForFormatText(const nsAtom& aProperty,
   2737                                                     const nsAtom* aAttribute,
   2738                                                     bool aToSetStyle) {
   2739  // This method may be in a hot path.  So, return only necessary
   2740  // EditAction::eSet*Property or EditAction::eRemove*Property.
   2741  if (&aProperty == nsGkAtoms::b) {
   2742    return aToSetStyle ? EditAction::eSetFontWeightProperty
   2743                       : EditAction::eRemoveFontWeightProperty;
   2744  }
   2745  if (&aProperty == nsGkAtoms::i) {
   2746    return aToSetStyle ? EditAction::eSetTextStyleProperty
   2747                       : EditAction::eRemoveTextStyleProperty;
   2748  }
   2749  if (&aProperty == nsGkAtoms::u) {
   2750    return aToSetStyle ? EditAction::eSetTextDecorationPropertyUnderline
   2751                       : EditAction::eRemoveTextDecorationPropertyUnderline;
   2752  }
   2753  if (&aProperty == nsGkAtoms::strike) {
   2754    return aToSetStyle ? EditAction::eSetTextDecorationPropertyLineThrough
   2755                       : EditAction::eRemoveTextDecorationPropertyLineThrough;
   2756  }
   2757  if (&aProperty == nsGkAtoms::sup) {
   2758    return aToSetStyle ? EditAction::eSetVerticalAlignPropertySuper
   2759                       : EditAction::eRemoveVerticalAlignPropertySuper;
   2760  }
   2761  if (&aProperty == nsGkAtoms::sub) {
   2762    return aToSetStyle ? EditAction::eSetVerticalAlignPropertySub
   2763                       : EditAction::eRemoveVerticalAlignPropertySub;
   2764  }
   2765  if (&aProperty == nsGkAtoms::font) {
   2766    if (aAttribute == nsGkAtoms::face) {
   2767      return aToSetStyle ? EditAction::eSetFontFamilyProperty
   2768                         : EditAction::eRemoveFontFamilyProperty;
   2769    }
   2770    if (aAttribute == nsGkAtoms::color) {
   2771      return aToSetStyle ? EditAction::eSetColorProperty
   2772                         : EditAction::eRemoveColorProperty;
   2773    }
   2774    if (aAttribute == nsGkAtoms::bgcolor) {
   2775      return aToSetStyle ? EditAction::eSetBackgroundColorPropertyInline
   2776                         : EditAction::eRemoveBackgroundColorPropertyInline;
   2777    }
   2778  }
   2779  return aToSetStyle ? EditAction::eSetInlineStyleProperty
   2780                     : EditAction::eRemoveInlineStyleProperty;
   2781 }
   2782 
   2783 EditAction HTMLEditUtils::GetEditActionForAlignment(
   2784    const nsAString& aAlignType) {
   2785  // This method may be in a hot path.  So, return only necessary
   2786  // EditAction::eAlign*.
   2787  if (aAlignType.EqualsLiteral("left")) {
   2788    return EditAction::eAlignLeft;
   2789  }
   2790  if (aAlignType.EqualsLiteral("right")) {
   2791    return EditAction::eAlignRight;
   2792  }
   2793  if (aAlignType.EqualsLiteral("center")) {
   2794    return EditAction::eAlignCenter;
   2795  }
   2796  if (aAlignType.EqualsLiteral("justify")) {
   2797    return EditAction::eJustify;
   2798  }
   2799  return EditAction::eSetAlignment;
   2800 }
   2801 
   2802 // static
   2803 template <typename EditorDOMPointType>
   2804 nsIContent* HTMLEditUtils::GetContentToPreserveInlineStyles(
   2805    const EditorDOMPointType& aPoint, const Element& aEditingHost) {
   2806  MOZ_ASSERT(aPoint.IsSetAndValid());
   2807  if (MOZ_UNLIKELY(!aPoint.IsInContentNode())) {
   2808    return nullptr;
   2809  }
   2810  // If it points middle of a text node, use it.  Otherwise, scan next visible
   2811  // thing and use the style of following text node if there is.
   2812  if (aPoint.IsInTextNode() && !aPoint.IsEndOfContainer()) {
   2813    return aPoint.template ContainerAs<nsIContent>();
   2814  }
   2815  for (auto point = aPoint.template To<EditorRawDOMPoint>(); point.IsSet();) {
   2816    const WSScanResult nextVisibleThing =
   2817        WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
   2818            {WSRunScanner::Option::OnlyEditableNodes}, point);
   2819    if (nextVisibleThing.InVisibleOrCollapsibleCharacters()) {
   2820      return nextVisibleThing.TextPtr();
   2821    }
   2822    if (nextVisibleThing.IsContentEditableRoot()) {
   2823      break;
   2824    }
   2825    // Ignore empty inline container elements because it's not visible for
   2826    // users so that using the style will appear suddenly from point of
   2827    // view of users.
   2828    if (nextVisibleThing.ReachedSpecialContent() &&
   2829        nextVisibleThing.IsContentEditable() &&
   2830        nextVisibleThing.ContentIsElement() &&
   2831        !nextVisibleThing.ElementPtr()->HasChildNodes() &&
   2832        HTMLEditUtils::IsContainerNode(*nextVisibleThing.ElementPtr())) {
   2833      point.SetAfter(nextVisibleThing.ElementPtr());
   2834      continue;
   2835    }
   2836    // Otherwise, we should use style of the container of the start point.
   2837    break;
   2838  }
   2839  return aPoint.template ContainerAs<nsIContent>();
   2840 }
   2841 
   2842 template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
   2843 EditorDOMPointType HTMLEditUtils::GetBetterInsertionPointFor(
   2844    const nsIContent& aContentToInsert,
   2845    const EditorDOMPointTypeInput& aPointToInsert) {
   2846  if (NS_WARN_IF(!aPointToInsert.IsSet())) {
   2847    return EditorDOMPointType();
   2848  }
   2849 
   2850  auto pointToInsert =
   2851      aPointToInsert.template GetNonAnonymousSubtreePoint<EditorDOMPointType>();
   2852  if (NS_WARN_IF(!pointToInsert.IsSet()) ||
   2853      NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(
   2854          *pointToInsert.GetContainer()))) {
   2855    // Cannot insert aContentToInsert into this DOM tree.
   2856    return EditorDOMPointType();
   2857  }
   2858 
   2859  // If the node to insert is not a block level element, we can insert it
   2860  // at any point.
   2861  if (!HTMLEditUtils::IsBlockElement(
   2862          aContentToInsert, BlockInlineCheck::UseComputedDisplayStyle)) {
   2863    return pointToInsert;
   2864  }
   2865 
   2866  const WSRunScanner wsScannerForPointToInsert(
   2867      {WSRunScanner::Option::OnlyEditableNodes}, pointToInsert);
   2868 
   2869  // If the insertion position is after the last visible item in a line,
   2870  // i.e., the insertion position is just before a visible line break <br>,
   2871  // we want to skip to the position just after the line break (see bug 68767).
   2872  const WSScanResult forwardScanFromPointToInsertResult =
   2873      wsScannerForPointToInsert.ScanInclusiveNextVisibleNodeOrBlockBoundaryFrom(
   2874          pointToInsert);
   2875  // So, if the next visible node isn't a <br> element, we can insert the block
   2876  // level element to the point.
   2877  if (!forwardScanFromPointToInsertResult.ReachedBRElement()) {
   2878    return pointToInsert;
   2879  }
   2880 
   2881  // However, we must not skip next <br> element when the caret appears to be
   2882  // positioned at the beginning of a block, in that case skipping the <br>
   2883  // would not insert the <br> at the caret position, but after the current
   2884  // empty line.
   2885  const WSScanResult backwardScanFromPointToInsertResult =
   2886      wsScannerForPointToInsert.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
   2887          pointToInsert);
   2888  // So, if there is no previous visible node,
   2889  // or, if both nodes of the insertion point is <br> elements,
   2890  // or, if the previous visible node is different block,
   2891  // we need to skip the following <br>.  So, otherwise, we can insert the
   2892  // block at the insertion point.
   2893  if (NS_WARN_IF(backwardScanFromPointToInsertResult.Failed()) ||
   2894      backwardScanFromPointToInsertResult.ReachedInlineEditingHostBoundary() ||
   2895      backwardScanFromPointToInsertResult.ReachedBRElement() ||
   2896      backwardScanFromPointToInsertResult.ReachedCurrentBlockBoundary()) {
   2897    return pointToInsert;
   2898  }
   2899 
   2900  return forwardScanFromPointToInsertResult
   2901      .template PointAfterReachedContent<EditorDOMPointType>();
   2902 }
   2903 
   2904 // static
   2905 template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
   2906 EditorDOMPointType HTMLEditUtils::GetBetterCaretPositionToInsertText(
   2907    const EditorDOMPointTypeInput& aPoint) {
   2908  MOZ_ASSERT(aPoint.IsSetAndValid());
   2909  MOZ_ASSERT(HTMLEditUtils::IsSimplyEditableNode(*aPoint.GetContainer()));
   2910 
   2911  if (aPoint.IsInTextNode()) {
   2912    return aPoint.template To<EditorDOMPointType>();
   2913  }
   2914  if (!aPoint.IsEndOfContainer() && aPoint.GetChild() &&
   2915      aPoint.GetChild()->IsText()) {
   2916    return EditorDOMPointType(aPoint.GetChild(), 0u);
   2917  }
   2918  if (aPoint.IsEndOfContainer()) {
   2919    const WSScanResult previousThing =
   2920        WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
   2921            {WSRunScanner::Option::OnlyEditableNodes}, aPoint);
   2922    if (previousThing.InVisibleOrCollapsibleCharacters()) {
   2923      return EditorDOMPointType::AtEndOf(*previousThing.TextPtr());
   2924    }
   2925  }
   2926  if (HTMLEditUtils::CanNodeContain(*aPoint.GetContainer(),
   2927                                    *nsGkAtoms::textTagName)) {
   2928    return aPoint.template To<EditorDOMPointType>();
   2929  }
   2930  if (MOZ_UNLIKELY(aPoint.GetContainer()->IsEditingHost() ||
   2931                   !aPoint.template GetContainerParentAs<nsIContent>() ||
   2932                   !HTMLEditUtils::CanNodeContain(
   2933                       *aPoint.template ContainerParentAs<nsIContent>(),
   2934                       *nsGkAtoms::textTagName))) {
   2935    return EditorDOMPointType();
   2936  }
   2937  return aPoint.ParentPoint().template To<EditorDOMPointType>();
   2938 }
   2939 
   2940 // static
   2941 template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
   2942 Result<EditorDOMPointType, nsresult>
   2943 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(
   2944    const Element& aElement, const EditorDOMPointTypeInput& aCurrentPoint) {
   2945  MOZ_ASSERT(aCurrentPoint.IsSet());
   2946 
   2947  // FYI: This was moved from
   2948  // https://searchfox.org/mozilla-central/rev/d3c2f51d89c3ca008ff0cb5a057e77ccd973443e/editor/libeditor/HTMLEditSubActionHandler.cpp#9193
   2949 
   2950  // Use range boundaries and RangeUtils::CompareNodeToRange() to compare
   2951  // selection start to new block.
   2952  bool nodeBefore, nodeAfter;
   2953  nsresult rv =
   2954      RangeUtils::CompareNodeToRangeBoundaries<TreeKind::ShadowIncludingDOM>(
   2955          const_cast<Element*>(&aElement), aCurrentPoint.ToRawRangeBoundary(),
   2956          aCurrentPoint.ToRawRangeBoundary(), &nodeBefore, &nodeAfter);
   2957  if (NS_FAILED(rv)) {
   2958    NS_WARNING("RangeUtils::CompareNodeToRange() failed");
   2959    return Err(rv);
   2960  }
   2961 
   2962  if (nodeBefore && nodeAfter) {
   2963    return EditorDOMPointType();  // aCurrentPoint is in aElement
   2964  }
   2965 
   2966  if (nodeBefore) {
   2967    // selection is after block.  put at end of block.
   2968    const nsIContent* lastEditableContent = HTMLEditUtils::GetLastChild(
   2969        aElement, {WalkTreeOption::IgnoreNonEditableNode});
   2970    if (!lastEditableContent) {
   2971      lastEditableContent = &aElement;
   2972    }
   2973    if (lastEditableContent->IsText() ||
   2974        HTMLEditUtils::IsContainerNode(*lastEditableContent)) {
   2975      return EditorDOMPointType::AtEndOf(*lastEditableContent);
   2976    }
   2977    MOZ_ASSERT(lastEditableContent->GetParentNode());
   2978    return EditorDOMPointType::After(*lastEditableContent);
   2979  }
   2980 
   2981  // selection is before block.  put at start of block.
   2982  const nsIContent* firstEditableContent = HTMLEditUtils::GetFirstChild(
   2983      aElement, {WalkTreeOption::IgnoreNonEditableNode});
   2984  if (!firstEditableContent) {
   2985    firstEditableContent = &aElement;
   2986  }
   2987  if (firstEditableContent->IsText() ||
   2988      HTMLEditUtils::IsContainerNode(*firstEditableContent)) {
   2989    MOZ_ASSERT(firstEditableContent->GetParentNode());
   2990    // XXX Shouldn't this be EditorDOMPointType(firstEditableContent, 0u)?
   2991    return EditorDOMPointType(firstEditableContent);
   2992  }
   2993  // XXX And shouldn't this be EditorDOMPointType(firstEditableContent)?
   2994  return EditorDOMPointType(firstEditableContent, 0u);
   2995 }
   2996 
   2997 // static
   2998 template <typename EditorLineBreakType, typename EditorDOMPointType>
   2999 Maybe<EditorLineBreakType>
   3000 HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem(
   3001    const EditorDOMPointType& aPoint, const Element& aEditingHost) {
   3002  MOZ_ASSERT(aPoint.IsSet());
   3003  if (MOZ_UNLIKELY(!aPoint.IsInContentNode())) {
   3004    return Nothing{};
   3005  }
   3006  const WSScanResult previousThing =
   3007      WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary({}, aPoint,
   3008                                                           &aEditingHost);
   3009  if (!previousThing.ReachedLineBreak()) {
   3010    return Nothing{};  // No preceding line break.
   3011  }
   3012  const WSScanResult nextThing =
   3013      WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary({}, aPoint,
   3014                                                                &aEditingHost);
   3015  if (!nextThing.ReachedBlockBoundary()) {
   3016    return Nothing{};  // The line break is not followed by a block boundary so
   3017                       // that it's a visible line break.
   3018  }
   3019  return Some(previousThing.CreateEditorLineBreak<EditorLineBreakType>());
   3020 }
   3021 
   3022 // static
   3023 bool HTMLEditUtils::IsInlineStyleSetByElement(
   3024    const nsIContent& aContent, const EditorInlineStyle& aStyle,
   3025    const nsAString* aValue, nsAString* aOutValue /* = nullptr */) {
   3026  for (Element* element : aContent.InclusiveAncestorsOfType<Element>()) {
   3027    if (aStyle.mHTMLProperty != element->NodeInfo()->NameAtom()) {
   3028      continue;
   3029    }
   3030    if (!aStyle.mAttribute) {
   3031      return true;
   3032    }
   3033    nsAutoString value;
   3034    element->GetAttr(aStyle.mAttribute, value);
   3035    if (aOutValue) {
   3036      *aOutValue = value;
   3037    }
   3038    if (!value.IsEmpty()) {
   3039      if (!aValue) {
   3040        return true;
   3041      }
   3042      if (aValue->Equals(value, nsCaseInsensitiveStringComparator)) {
   3043        return true;
   3044      }
   3045      // We found the prop with the attribute, but the value doesn't match.
   3046      return false;
   3047    }
   3048  }
   3049  return false;
   3050 }
   3051 
   3052 // static
   3053 size_t HTMLEditUtils::CollectChildren(
   3054    const nsINode& aNode,
   3055    nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
   3056    size_t aIndexToInsertChildren, const CollectChildrenOptions& aOptions) {
   3057  // FYI: This was moved from
   3058  // https://searchfox.org/mozilla-central/rev/4bce7d85ba4796dd03c5dcc7cfe8eee0e4c07b3b/editor/libeditor/HTMLEditSubActionHandler.cpp#6261
   3059 
   3060  size_t numberOfFoundChildren = 0;
   3061  for (nsIContent* content =
   3062           GetFirstChild(aNode, {WalkTreeOption::IgnoreNonEditableNode});
   3063       content; content = content->GetNextSibling()) {
   3064    if ((aOptions.contains(CollectChildrenOption::CollectListChildren) &&
   3065         (HTMLEditUtils::IsListElement(*content) ||
   3066          HTMLEditUtils::IsListItemElement(*content))) ||
   3067        (aOptions.contains(CollectChildrenOption::CollectTableChildren) &&
   3068         HTMLEditUtils::IsAnyTableElementExceptColumnElement(*content))) {
   3069      numberOfFoundChildren += HTMLEditUtils::CollectChildren(
   3070          *content, aOutArrayOfContents,
   3071          aIndexToInsertChildren + numberOfFoundChildren, aOptions);
   3072      continue;
   3073    }
   3074 
   3075    if (aOptions.contains(CollectChildrenOption::IgnoreNonEditableChildren) &&
   3076        !EditorUtils::IsEditableContent(*content, EditorType::HTML)) {
   3077      continue;
   3078    }
   3079    if (aOptions.contains(CollectChildrenOption::IgnoreInvisibleTextNodes) &&
   3080        content->IsText() &&
   3081        !HTMLEditUtils::IsVisibleTextNode(*content->AsText())) {
   3082      continue;
   3083    }
   3084    aOutArrayOfContents.InsertElementAt(
   3085        aIndexToInsertChildren + numberOfFoundChildren++, *content);
   3086  }
   3087  return numberOfFoundChildren;
   3088 }
   3089 
   3090 // static
   3091 size_t HTMLEditUtils::CollectEmptyInlineContainerDescendants(
   3092    const nsINode& aNode,
   3093    nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
   3094    const EmptyCheckOptions& aOptions, BlockInlineCheck aBlockInlineCheck) {
   3095  size_t numberOfFoundElements = 0;
   3096  for (Element* element = aNode.GetFirstElementChild(); element;) {
   3097    if (HTMLEditUtils::IsEmptyInlineContainer(
   3098            *element, aOptions,
   3099            UseComputedDisplayOutsideStyleIfAuto(aBlockInlineCheck))) {
   3100      aOutArrayOfContents.AppendElement(*element);
   3101      numberOfFoundElements++;
   3102      nsIContent* nextContent = element->GetNextNonChildNode(&aNode);
   3103      element = nullptr;
   3104      for (; nextContent; nextContent = nextContent->GetNextNode(&aNode)) {
   3105        if (nextContent->IsElement()) {
   3106          element = nextContent->AsElement();
   3107          break;
   3108        }
   3109      }
   3110      continue;
   3111    }
   3112 
   3113    nsIContent* nextContent = element->GetNextNode(&aNode);
   3114    element = nullptr;
   3115    for (; nextContent; nextContent = nextContent->GetNextNode(&aNode)) {
   3116      if (nextContent->IsElement()) {
   3117        element = nextContent->AsElement();
   3118        break;
   3119      }
   3120    }
   3121  }
   3122  return numberOfFoundElements;
   3123 }
   3124 
   3125 // static
   3126 bool HTMLEditUtils::ElementHasAttributeExcept(const Element& aElement,
   3127                                              const nsAtom& aAttribute1,
   3128                                              const nsAtom& aAttribute2,
   3129                                              const nsAtom& aAttribute3) {
   3130  // FYI: This was moved from
   3131  // https://searchfox.org/mozilla-central/rev/0b1543e85d13c30a13c57e959ce9815a3f0fa1d3/editor/libeditor/HTMLStyleEditor.cpp#1626
   3132  for (auto i : IntegerRange<uint32_t>(aElement.GetAttrCount())) {
   3133    const nsAttrName* name = aElement.GetAttrNameAt(i);
   3134    if (!name->NamespaceEquals(kNameSpaceID_None)) {
   3135      return true;
   3136    }
   3137 
   3138    if (name->LocalName() == &aAttribute1 ||
   3139        name->LocalName() == &aAttribute2 ||
   3140        name->LocalName() == &aAttribute3) {
   3141      continue;  // Ignore the given attribute
   3142    }
   3143 
   3144    // Ignore empty style, class and id attributes because those attributes are
   3145    // not meaningful with empty value.
   3146    if (name->LocalName() == nsGkAtoms::style ||
   3147        name->LocalName() == nsGkAtoms::_class ||
   3148        name->LocalName() == nsGkAtoms::id) {
   3149      if (aElement.HasNonEmptyAttr(name->LocalName())) {
   3150        return true;
   3151      }
   3152      continue;
   3153    }
   3154 
   3155    // Ignore special _moz attributes
   3156    nsAutoString attrString;
   3157    name->LocalName()->ToString(attrString);
   3158    if (!StringBeginsWith(attrString, u"_moz"_ns)) {
   3159      return true;
   3160    }
   3161  }
   3162  // if we made it through all of them without finding a real attribute
   3163  // other than aAttribute, then return true
   3164  return false;
   3165 }
   3166 
   3167 bool HTMLEditUtils::GetNormalizedHTMLColorValue(const nsAString& aColorValue,
   3168                                                nsAString& aNormalizedValue) {
   3169  nsAttrValue value;
   3170  if (!value.ParseColor(aColorValue)) {
   3171    aNormalizedValue = aColorValue;
   3172    return false;
   3173  }
   3174  nscolor color = NS_RGB(0, 0, 0);
   3175  MOZ_ALWAYS_TRUE(value.GetColorValue(color));
   3176  aNormalizedValue = NS_ConvertASCIItoUTF16(nsPrintfCString(
   3177      "#%02x%02x%02x", NS_GET_R(color), NS_GET_G(color), NS_GET_B(color)));
   3178  return true;
   3179 }
   3180 
   3181 bool HTMLEditUtils::IsSameHTMLColorValue(
   3182    const nsAString& aColorA, const nsAString& aColorB,
   3183    TransparentKeyword aTransparentKeyword) {
   3184  if (aTransparentKeyword == TransparentKeyword::Allowed) {
   3185    const bool isATransparent = aColorA.LowerCaseEqualsLiteral("transparent");
   3186    const bool isBTransparent = aColorB.LowerCaseEqualsLiteral("transparent");
   3187    if (isATransparent || isBTransparent) {
   3188      return isATransparent && isBTransparent;
   3189    }
   3190  }
   3191  nsAttrValue valueA, valueB;
   3192  if (!valueA.ParseColor(aColorA) || !valueB.ParseColor(aColorB)) {
   3193    return false;
   3194  }
   3195  nscolor colorA = NS_RGB(0, 0, 0), colorB = NS_RGB(0, 0, 0);
   3196  MOZ_ALWAYS_TRUE(valueA.GetColorValue(colorA));
   3197  MOZ_ALWAYS_TRUE(valueB.GetColorValue(colorB));
   3198  return colorA == colorB;
   3199 }
   3200 
   3201 bool HTMLEditUtils::MaybeCSSSpecificColorValue(const nsAString& aColorValue) {
   3202  if (aColorValue.IsEmpty() || aColorValue.First() == '#') {
   3203    return false;  // Quick return for the most cases.
   3204  }
   3205 
   3206  nsAutoString colorValue(aColorValue);
   3207  colorValue.CompressWhitespace(true, true);
   3208  if (colorValue.LowerCaseEqualsASCII("transparent")) {
   3209    return true;
   3210  }
   3211  nscolor color = NS_RGB(0, 0, 0);
   3212  if (colorValue.IsEmpty() || colorValue.First() == '#') {
   3213    return false;
   3214  }
   3215  const NS_ConvertUTF16toUTF8 colorU8(colorValue);
   3216  if (Servo_ColorNameToRgb(&colorU8, &color)) {
   3217    return false;
   3218  }
   3219  if (colorValue.LowerCaseEqualsASCII("initial") ||
   3220      colorValue.LowerCaseEqualsASCII("inherit") ||
   3221      colorValue.LowerCaseEqualsASCII("unset") ||
   3222      colorValue.LowerCaseEqualsASCII("revert") ||
   3223      colorValue.LowerCaseEqualsASCII("currentcolor")) {
   3224    return true;
   3225  }
   3226  return ServoCSSParser::IsValidCSSColor(colorU8);
   3227 }
   3228 
   3229 static bool ComputeColor(const nsAString& aColorValue, nscolor* aColor,
   3230                         bool* aIsCurrentColor) {
   3231  return ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0),
   3232                                      NS_ConvertUTF16toUTF8(aColorValue),
   3233                                      aColor, aIsCurrentColor);
   3234 }
   3235 
   3236 static bool ComputeColor(const nsACString& aColorValue, nscolor* aColor,
   3237                         bool* aIsCurrentColor) {
   3238  return ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0), aColorValue,
   3239                                      aColor, aIsCurrentColor);
   3240 }
   3241 
   3242 bool HTMLEditUtils::CanConvertToHTMLColorValue(const nsAString& aColorValue) {
   3243  bool isCurrentColor = false;
   3244  nscolor color = NS_RGB(0, 0, 0);
   3245  return ComputeColor(aColorValue, &color, &isCurrentColor) &&
   3246         !isCurrentColor && NS_GET_A(color) == 0xFF;
   3247 }
   3248 
   3249 bool HTMLEditUtils::ConvertToNormalizedHTMLColorValue(
   3250    const nsAString& aColorValue, nsAString& aNormalizedValue) {
   3251  bool isCurrentColor = false;
   3252  nscolor color = NS_RGB(0, 0, 0);
   3253  if (!ComputeColor(aColorValue, &color, &isCurrentColor) || isCurrentColor ||
   3254      NS_GET_A(color) != 0xFF) {
   3255    aNormalizedValue = aColorValue;
   3256    return false;
   3257  }
   3258  aNormalizedValue.Truncate();
   3259  aNormalizedValue.AppendPrintf("#%02x%02x%02x", NS_GET_R(color),
   3260                                NS_GET_G(color), NS_GET_B(color));
   3261  return true;
   3262 }
   3263 
   3264 bool HTMLEditUtils::GetNormalizedCSSColorValue(const nsAString& aColorValue,
   3265                                               ZeroAlphaColor aZeroAlphaColor,
   3266                                               nsAString& aNormalizedValue) {
   3267  bool isCurrentColor = false;
   3268  nscolor color = NS_RGB(0, 0, 0);
   3269  if (!ComputeColor(aColorValue, &color, &isCurrentColor)) {
   3270    aNormalizedValue = aColorValue;
   3271    return false;
   3272  }
   3273 
   3274  // If it's currentcolor, let's return it as-is since we cannot resolve it
   3275  // without ancestors.
   3276  if (isCurrentColor) {
   3277    aNormalizedValue = aColorValue;
   3278    return true;
   3279  }
   3280 
   3281  if (aZeroAlphaColor == ZeroAlphaColor::TransparentKeyword &&
   3282      NS_GET_A(color) == 0) {
   3283    aNormalizedValue.AssignLiteral("transparent");
   3284    return true;
   3285  }
   3286 
   3287  // Get serialized color value (i.e., "rgb()" or "rgba()").
   3288  aNormalizedValue.Truncate();
   3289  nsStyleUtil::GetSerializedColorValue(color, aNormalizedValue);
   3290  return true;
   3291 }
   3292 
   3293 template <typename CharType>
   3294 bool HTMLEditUtils::IsSameCSSColorValue(const nsTSubstring<CharType>& aColorA,
   3295                                        const nsTSubstring<CharType>& aColorB) {
   3296  bool isACurrentColor = false;
   3297  nscolor colorA = NS_RGB(0, 0, 0);
   3298  if (!ComputeColor(aColorA, &colorA, &isACurrentColor)) {
   3299    return false;
   3300  }
   3301  bool isBCurrentColor = false;
   3302  nscolor colorB = NS_RGB(0, 0, 0);
   3303  if (!ComputeColor(aColorB, &colorB, &isBCurrentColor)) {
   3304    return false;
   3305  }
   3306  if (isACurrentColor || isBCurrentColor) {
   3307    return isACurrentColor && isBCurrentColor;
   3308  }
   3309  return colorA == colorB;
   3310 }
   3311 
   3312 bool HTMLEditUtils::IsTransparentCSSColor(const nsAString& aColor) {
   3313  nsAutoString normalizedCSSColorValue;
   3314  return GetNormalizedCSSColorValue(aColor, ZeroAlphaColor::TransparentKeyword,
   3315                                    normalizedCSSColorValue) &&
   3316         normalizedCSSColorValue.EqualsASCII("transparent");
   3317 }
   3318 
   3319 /******************************************************************************
   3320 * operator<<() for enum classes of HTMLEditUtils
   3321 ******************************************************************************/
   3322 
   3323 std::ostream& operator<<(std::ostream& aStream,
   3324                         const HTMLEditUtils::AncestorType& aType) {
   3325  constexpr static const char* names[] = {
   3326      "ClosestBlockElement",
   3327      "ClosestContainerElement",
   3328      "MostDistantInlineElementInBlock",
   3329      "IgnoreHRElement",
   3330      "ClosestButtonElement",
   3331      "StopAtClosestButtonElement",
   3332      "ReturnAncestorLimiterIfNoProperAncestor",
   3333      "EditableElement",
   3334  };
   3335  return aStream << names[static_cast<uint32_t>(aType)];
   3336 }
   3337 
   3338 std::ostream& operator<<(std::ostream& aStream,
   3339                         const HTMLEditUtils::AncestorTypes& aTypes) {
   3340  aStream << "{";
   3341  bool first = true;
   3342  for (const auto t : aTypes) {
   3343    if (!first) {
   3344      aStream << ", ";
   3345    }
   3346    aStream << ToString(t).c_str();
   3347    first = false;
   3348  }
   3349  return aStream << "}";
   3350 }
   3351 
   3352 std::ostream& operator<<(std::ostream& aStream,
   3353                         const HTMLEditUtils::EditablePointOption& aOption) {
   3354  constexpr static const char* names[] = {
   3355      "RecognizeInvisibleWhiteSpaces",
   3356      "StopAtComment",
   3357      "StopAtListElement",
   3358      "StopAtListItemElement",
   3359      "StopAtTableElement",
   3360      "StopAtAnyTableElement",
   3361  };
   3362  return aStream << names[static_cast<uint32_t>(aOption)];
   3363 }
   3364 
   3365 std::ostream& operator<<(std::ostream& aStream,
   3366                         const HTMLEditUtils::EditablePointOptions& aOptions) {
   3367  aStream << "{";
   3368  bool first = true;
   3369  for (const auto option : aOptions) {
   3370    if (!first) {
   3371      aStream << ", ";
   3372    }
   3373    aStream << ToString(option).c_str();
   3374    first = false;
   3375  }
   3376  return aStream << "}";
   3377 }
   3378 
   3379 std::ostream& operator<<(std::ostream& aStream,
   3380                         const HTMLEditUtils::EmptyCheckOption& aOption) {
   3381  constexpr static const char* names[] = {
   3382      "TreatSingleBRElementAsVisible",
   3383      "TreatBlockAsVisible",
   3384      "TreatListItemAsVisible",
   3385      "TreatTableCellAsVisible",
   3386      "TreatNonEditableContentAsInvisible",
   3387      "TreatCommentAsVisible",
   3388      "SafeToAskLayout",
   3389  };
   3390  return aStream << names[static_cast<uint32_t>(aOption)];
   3391 }
   3392 
   3393 std::ostream& operator<<(std::ostream& aStream,
   3394                         const HTMLEditUtils::EmptyCheckOptions& aOptions) {
   3395  aStream << "{";
   3396  bool first = true;
   3397  for (const auto t : aOptions) {
   3398    if (!first) {
   3399      aStream << ", ";
   3400    }
   3401    aStream << ToString(t).c_str();
   3402    first = false;
   3403  }
   3404  return aStream << "}";
   3405 }
   3406 
   3407 std::ostream& operator<<(std::ostream& aStream,
   3408                         const HTMLEditUtils::LeafNodeType& aLeafNodeType) {
   3409  constexpr static const char* names[] = {
   3410      "OnlyLeafNode",
   3411      "LeafNodeOrChildBlock",
   3412      "LeafNodeOrNonEditableNode",
   3413      "OnlyEditableLeafNode",
   3414      "TreatCommentAsLeafNode",
   3415  };
   3416  return aStream << names[static_cast<uint32_t>(aLeafNodeType)];
   3417 }
   3418 
   3419 std::ostream& operator<<(std::ostream& aStream,
   3420                         const HTMLEditUtils::LeafNodeTypes& aLeafNodeTypes) {
   3421  aStream << "{";
   3422  bool first = true;
   3423  for (const auto t : aLeafNodeTypes) {
   3424    if (!first) {
   3425      aStream << ", ";
   3426    }
   3427    aStream << ToString(t).c_str();
   3428    first = false;
   3429  }
   3430  return aStream << "}";
   3431 }
   3432 
   3433 /******************************************************************************
   3434 * SelectedTableCellScanner
   3435 ******************************************************************************/
   3436 
   3437 SelectedTableCellScanner::SelectedTableCellScanner(
   3438    const AutoClonedRangeArray& aRanges) {
   3439  if (aRanges.Ranges().IsEmpty()) {
   3440    return;
   3441  }
   3442  Element* firstSelectedCellElement =
   3443      HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(
   3444          aRanges.FirstRangeRef());
   3445  if (!firstSelectedCellElement) {
   3446    return;  // We're not in table cell selection mode.
   3447  }
   3448  mSelectedCellElements.SetCapacity(aRanges.Ranges().Length());
   3449  mSelectedCellElements.AppendElement(*firstSelectedCellElement);
   3450  for (uint32_t i = 1; i < aRanges.Ranges().Length(); i++) {
   3451    nsRange* range = aRanges.Ranges()[i];
   3452    if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) {
   3453      continue;  // Shouldn't occur in normal conditions.
   3454    }
   3455    // Just ignore selection ranges which do not select only one table
   3456    // cell element.  This is possible case if web apps sets multiple
   3457    // selections and first range selects a table cell element.
   3458    if (Element* selectedCellElement =
   3459            HTMLEditUtils::GetTableCellElementIfOnlyOneSelected(*range)) {
   3460      mSelectedCellElements.AppendElement(*selectedCellElement);
   3461    }
   3462  }
   3463 }
   3464 
   3465 }  // namespace mozilla