tor-browser

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

HTMLEditorNestedClasses.h (94353B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #ifndef HTMLEditorNestedClasses_h
      7 #define HTMLEditorNestedClasses_h
      8 
      9 #include "EditorDOMPoint.h"
     10 #include "EditorForwards.h"
     11 #include "HTMLEditor.h"       // for HTMLEditor
     12 #include "HTMLEditHelpers.h"  // for EditorInlineStyleAndValue
     13 #include "HTMLEditUtils.h"    // for HTMLEditUtils::IsContainerNode
     14 
     15 #include "mozilla/Attributes.h"
     16 #include "mozilla/OwningNonNull.h"
     17 #include "mozilla/Result.h"
     18 #include "mozilla/dom/CharacterDataBuffer.h"
     19 #include "mozilla/dom/Text.h"
     20 
     21 namespace mozilla {
     22 
     23 struct LimitersAndCaretData;  // Declared in nsFrameSelection.h
     24 namespace dom {
     25 class HTMLBRElement;
     26 };
     27 
     28 /*****************************************************************************
     29 * AutoInlineStyleSetter is a temporary class to set an inline style to
     30 * specific nodes.
     31 ****************************************************************************/
     32 
     33 class MOZ_STACK_CLASS HTMLEditor::AutoInlineStyleSetter final
     34    : private EditorInlineStyleAndValue {
     35  using Element = dom::Element;
     36  using Text = dom::Text;
     37 
     38 public:
     39  explicit AutoInlineStyleSetter(
     40      const EditorInlineStyleAndValue& aStyleAndValue)
     41      : EditorInlineStyleAndValue(aStyleAndValue) {}
     42 
     43  void Reset() {
     44    mFirstHandledPoint.Clear();
     45    mLastHandledPoint.Clear();
     46  }
     47 
     48  const EditorDOMPoint& FirstHandledPointRef() const {
     49    return mFirstHandledPoint;
     50  }
     51  const EditorDOMPoint& LastHandledPointRef() const {
     52    return mLastHandledPoint;
     53  }
     54 
     55  /**
     56   * Split aText at aStartOffset and aEndOffset (except when they are start or
     57   * end of its data) and wrap the middle text node in an element to apply the
     58   * style.
     59   */
     60  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitRangeOffFromNodeResult, nsresult>
     61  SplitTextNodeAndApplyStyleToMiddleNode(HTMLEditor& aHTMLEditor, Text& aText,
     62                                         uint32_t aStartOffset,
     63                                         uint32_t aEndOffset);
     64 
     65  /**
     66   * Remove same style from children and apply the style entire (except
     67   * non-editable nodes) aContent.
     68   */
     69  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
     70  ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(HTMLEditor& aHTMLEditor,
     71                                                     nsIContent& aContent);
     72 
     73  /**
     74   * Invert the style with creating new element or something.  This should
     75   * be called only when IsInvertibleWithCSS() returns true.
     76   */
     77  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
     78  InvertStyleIfApplied(HTMLEditor& aHTMLEditor, Element& aElement);
     79  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitRangeOffFromNodeResult, nsresult>
     80  InvertStyleIfApplied(HTMLEditor& aHTMLEditor, Text& aTextNode,
     81                       uint32_t aStartOffset, uint32_t aEndOffset);
     82 
     83  /**
     84   * Extend or shrink aRange for applying the style to the range.
     85   * See comments in the definition what this does.
     86   */
     87  Result<EditorRawDOMRange, nsresult> ExtendOrShrinkRangeToApplyTheStyle(
     88      const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange) const;
     89 
     90  /**
     91   * Returns next/previous sibling of aContent or an ancestor of it if it's
     92   * editable and does not cross block boundary.
     93   */
     94  [[nodiscard]] static nsIContent* GetNextEditableInlineContent(
     95      const nsIContent& aContent, const nsINode* aLimiter = nullptr);
     96  [[nodiscard]] static nsIContent* GetPreviousEditableInlineContent(
     97      const nsIContent& aContent, const nsINode* aLimiter = nullptr);
     98 
     99  /**
    100   * GetEmptyTextNodeToApplyNewStyle creates new empty text node to insert
    101   * a new element which will contain newly inserted text or returns existing
    102   * empty text node if aCandidatePointToInsert is around it.
    103   *
    104   * NOTE: Unfortunately, editor does not want to insert text into empty inline
    105   * element in some places (e.g., automatically adjusting caret position to
    106   * nearest text node).  Therefore, we need to create new empty text node to
    107   * prepare new styles for inserting text.  This method is designed for the
    108   * preparation.
    109   *
    110   * @param aHTMLEditor                 The editor.
    111   * @param aCandidatePointToInsert     The point where the caller wants to
    112   *                                    insert new text.
    113   * @return            If this creates new empty text node returns it.
    114   *                    If this couldn't create new empty text node due to
    115   *                    the point or aEditingHost cannot have text node,
    116   *                    returns nullptr.
    117   *                    Otherwise, returns error.
    118   */
    119  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<RefPtr<Text>, nsresult>
    120  GetEmptyTextNodeToApplyNewStyle(
    121      HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCandidatePointToInsert);
    122 
    123 private:
    124  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> ApplyStyle(
    125      HTMLEditor& aHTMLEditor, nsIContent& aContent);
    126 
    127  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
    128  ApplyCSSTextDecoration(HTMLEditor& aHTMLEditor, nsIContent& aContent);
    129 
    130  /**
    131   * Returns true if aStyledElement is a good element to set `style` attribute.
    132   */
    133  [[nodiscard]] bool ElementIsGoodContainerToSetStyle(
    134      nsStyledElement& aStyledElement) const;
    135 
    136  /**
    137   * ElementIsGoodContainerForTheStyle() returns true if aElement is a
    138   * good container for applying the style to a node.  I.e., if this returns
    139   * true, moving nodes into aElement is enough to apply the style to them.
    140   * Otherwise, you need to create new element for the style.
    141   */
    142  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<bool, nsresult>
    143  ElementIsGoodContainerForTheStyle(HTMLEditor& aHTMLEditor,
    144                                    Element& aElement) const;
    145 
    146  /**
    147   * Return true if the node is an element node and it represents the style or
    148   * sets the style (including when setting different value) with `style`
    149   * attribute.
    150   */
    151  [[nodiscard]] bool ContentIsElementSettingTheStyle(
    152      const HTMLEditor& aHTMLEditor, nsIContent& aContent) const;
    153 
    154  /**
    155   * Helper methods to shrink range to apply the style.
    156   */
    157  [[nodiscard]] EditorRawDOMPoint GetShrunkenRangeStart(
    158      const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
    159      const nsINode& aCommonAncestorOfRange,
    160      const nsIContent* aFirstEntirelySelectedContentNodeInRange) const;
    161  [[nodiscard]] EditorRawDOMPoint GetShrunkenRangeEnd(
    162      const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
    163      const nsINode& aCommonAncestorOfRange,
    164      const nsIContent* aLastEntirelySelectedContentNodeInRange) const;
    165 
    166  /**
    167   * Helper methods to extend the range to apply the style.
    168   */
    169  [[nodiscard]] EditorRawDOMPoint
    170  GetExtendedRangeStartToWrapAncestorApplyingSameStyle(
    171      const HTMLEditor& aHTMLEditor,
    172      const EditorRawDOMPoint& aStartPoint) const;
    173  [[nodiscard]] EditorRawDOMPoint
    174  GetExtendedRangeEndToWrapAncestorApplyingSameStyle(
    175      const HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aEndPoint) const;
    176  [[nodiscard]] EditorRawDOMRange
    177  GetExtendedRangeToMinimizeTheNumberOfNewElements(
    178      const HTMLEditor& aHTMLEditor, const nsINode& aCommonAncestor,
    179      EditorRawDOMPoint&& aStartPoint, EditorRawDOMPoint&& aEndPoint) const;
    180 
    181  /**
    182   * OnHandled() are called when this class creates new element to apply the
    183   * style, applies new style to existing element or ignores to apply the style
    184   * due to already set.
    185   */
    186  void OnHandled(const EditorDOMPoint& aStartPoint,
    187                 const EditorDOMPoint& aEndPoint) {
    188    if (!mFirstHandledPoint.IsSet()) {
    189      mFirstHandledPoint = aStartPoint;
    190    }
    191    mLastHandledPoint = aEndPoint;
    192  }
    193  void OnHandled(nsIContent& aContent) {
    194    if (aContent.IsElement() && !HTMLEditUtils::IsContainerNode(aContent)) {
    195      if (!mFirstHandledPoint.IsSet()) {
    196        mFirstHandledPoint.Set(&aContent);
    197      }
    198      mLastHandledPoint.SetAfter(&aContent);
    199      return;
    200    }
    201    if (!mFirstHandledPoint.IsSet()) {
    202      mFirstHandledPoint.Set(&aContent, 0u);
    203    }
    204    mLastHandledPoint = EditorDOMPoint::AtEndOf(aContent);
    205  }
    206 
    207  // mFirstHandledPoint and mLastHandledPoint store the first and last points
    208  // which are newly created or apply the new style, or just ignored at trying
    209  // to split a text node.
    210  EditorDOMPoint mFirstHandledPoint;
    211  EditorDOMPoint mLastHandledPoint;
    212 };
    213 
    214 /**
    215 * AutoMoveOneLineHandler moves the content in a line (between line breaks/block
    216 * boundaries) to specific point or end of a container element.
    217 */
    218 class MOZ_STACK_CLASS HTMLEditor::AutoMoveOneLineHandler final {
    219 public:
    220  /**
    221   * Use this constructor when you want a line to move specific point.
    222   */
    223  explicit AutoMoveOneLineHandler(const EditorDOMPoint& aPointToInsert)
    224      : mPointToInsert(aPointToInsert),
    225        mMoveToEndOfContainer(MoveToEndOfContainer::No) {
    226    MOZ_ASSERT(mPointToInsert.IsSetAndValid());
    227    MOZ_ASSERT(mPointToInsert.IsInContentNode());
    228  }
    229  /**
    230   * Use this constructor when you want a line to move end of
    231   * aNewContainerElement.
    232   */
    233  explicit AutoMoveOneLineHandler(Element& aNewContainerElement)
    234      : mPointToInsert(&aNewContainerElement, 0),
    235        mMoveToEndOfContainer(MoveToEndOfContainer::Yes) {
    236    MOZ_ASSERT(mPointToInsert.IsSetAndValid());
    237  }
    238 
    239  /**
    240   * Must be called before calling Run().
    241   *
    242   * @param aHTMLEditor         The HTML editor.
    243   * @param aPointInHardLine    A point in a line which you want to move.
    244   * @param aEditingHost        The editing host.
    245   */
    246  [[nodiscard]] nsresult Prepare(HTMLEditor& aHTMLEditor,
    247                                 const EditorDOMPoint& aPointInHardLine,
    248                                 const Element& aEditingHost);
    249  /**
    250   * Must be called if Prepare() returned NS_OK.
    251   *
    252   * @param aHTMLEditor         The HTML editor.
    253   * @param aEditingHost        The editing host.
    254   */
    255  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<MoveNodeResult, nsresult> Run(
    256      HTMLEditor& aHTMLEditor, const Element& aEditingHost);
    257 
    258  /**
    259   * Returns true if there are some content nodes which can be moved to another
    260   * place or deleted in the line containing aPointInHardLine.  Note that if
    261   * there is only a padding <br> element in an empty block element, this
    262   * returns false even though it may be deleted.
    263   */
    264  static Result<bool, nsresult> CanMoveOrDeleteSomethingInLine(
    265      const EditorDOMPoint& aPointInHardLine, const Element& aEditingHost);
    266 
    267  AutoMoveOneLineHandler(const AutoMoveOneLineHandler& aOther) = delete;
    268  AutoMoveOneLineHandler(AutoMoveOneLineHandler&& aOther) = delete;
    269 
    270 private:
    271  [[nodiscard]] bool ForceMoveToEndOfContainer() const {
    272    return mMoveToEndOfContainer == MoveToEndOfContainer::Yes;
    273  }
    274  [[nodiscard]] EditorDOMPoint& NextInsertionPointRef() {
    275    if (ForceMoveToEndOfContainer()) {
    276      mPointToInsert.SetToEndOf(mPointToInsert.GetContainer());
    277    }
    278    return mPointToInsert;
    279  }
    280 
    281  /**
    282   * Consider whether Run() should preserve or does not preserve white-space
    283   * style of moving content.
    284   *
    285   * @param aContentInLine      Specify a content node in the moving line.
    286   *                            Typically, container of aPointInHardLine of
    287   *                            Prepare().
    288   * @param aInclusiveAncestorBlockOfInsertionPoint
    289   *                            Inclusive ancestor block element of insertion
    290   *                            point.  Typically, computed
    291   *                            mDestInclusiveAncestorBlock.
    292   */
    293  [[nodiscard]] static PreserveWhiteSpaceStyle
    294  ConsiderWhetherPreserveWhiteSpaceStyle(
    295      const nsIContent* aContentInLine,
    296      const Element* aInclusiveAncestorBlockOfInsertionPoint);
    297 
    298  /**
    299   * Look for inclusive ancestor block element of aBlockElement and a descendant
    300   * of aAncestorElement.  If aBlockElement and aAncestorElement are same one,
    301   * this returns nullptr.
    302   *
    303   * @param aBlockElement       A block element which is a descendant of
    304   *                            aAncestorElement.
    305   * @param aAncestorElement    An inclusive ancestor block element of
    306   *                            aBlockElement.
    307   */
    308  [[nodiscard]] static Element*
    309  GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement(
    310      Element& aBlockElement, const Element& aAncestorElement);
    311 
    312  /**
    313   * Split ancestors at the line range boundaries and collect array of contents
    314   * in the line to aOutArrayOfContents.  Specify aNewContainer to the container
    315   * of insertion point to avoid splitting the destination.
    316   */
    317  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
    318  SplitToMakeTheLineIsolated(
    319      HTMLEditor& aHTMLEditor, const nsIContent& aNewContainer,
    320      const Element& aEditingHost,
    321      nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) const;
    322 
    323  /**
    324   * Delete unnecessary trailing line break in aMovedContentRange if there is.
    325   */
    326  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
    327  DeleteUnnecessaryTrailingLineBreakInMovedLineEnd(
    328      HTMLEditor& aHTMLEditor, const EditorDOMRange& aMovedContentRange,
    329      const Element& aEditingHost) const;
    330 
    331  // Range of selected line.
    332  EditorDOMRange mLineRange;
    333  // Next insertion point.  If mMoveToEndOfContainer is `Yes`, this is
    334  // recomputed with its container in NextInsertionPointRef.  Therefore, this
    335  // should not be referred directly.
    336  EditorDOMPoint mPointToInsert;
    337  // An inclusive ancestor block element of the moving line.
    338  RefPtr<Element> mSrcInclusiveAncestorBlock;
    339  // An inclusive ancestor block element of the insertion point.
    340  RefPtr<Element> mDestInclusiveAncestorBlock;
    341  // nullptr if mMovingToParentBlock is false.
    342  // Must be non-nullptr if mMovingToParentBlock is true.  The topmost ancestor
    343  // block element which contains mSrcInclusiveAncestorBlock and a descendant of
    344  // mDestInclusiveAncestorBlock.  I.e., this may be same as
    345  // mSrcInclusiveAncestorBlock, but never same as mDestInclusiveAncestorBlock.
    346  RefPtr<Element> mTopmostSrcAncestorBlockInDestBlock;
    347  enum class MoveToEndOfContainer { No, Yes };
    348  MoveToEndOfContainer mMoveToEndOfContainer;
    349  PreserveWhiteSpaceStyle mPreserveWhiteSpaceStyle =
    350      PreserveWhiteSpaceStyle::No;
    351  // true if mDestInclusiveAncestorBlock is an ancestor of
    352  // mSrcInclusiveAncestorBlock.
    353  bool mMovingToParentBlock = false;
    354 };
    355 
    356 /**
    357 * Convert contents around aRanges of Run() to specified list element.  If there
    358 * are some different type of list elements, this method converts them to
    359 * specified list items too.  Basically, each line will be wrapped in a list
    360 * item element.  However, only when <p> element is selected, its child <br>
    361 * elements won't be treated as line separators.  Perhaps, this is a bug.
    362 */
    363 class MOZ_STACK_CLASS HTMLEditor::AutoListElementCreator final {
    364 public:
    365  /**
    366   * @param aListElementTagName         The new list element tag name.
    367   * @param aListItemElementTagName     The new list item element tag name.
    368   * @param aBulletType                 If this is not empty string, it's set
    369   *                                    to `type` attribute of new list item
    370   *                                    elements.  Otherwise, existing `type`
    371   *                                    attributes will be removed.
    372   */
    373  AutoListElementCreator(const nsStaticAtom& aListElementTagName,
    374                         const nsStaticAtom& aListItemElementTagName,
    375                         const nsAString& aBulletType)
    376      // Needs const_cast hack here because the struct users may want
    377      // non-const nsStaticAtom pointer due to bug 1794954
    378      : mListTagName(const_cast<nsStaticAtom&>(aListElementTagName)),
    379        mListItemTagName(const_cast<nsStaticAtom&>(aListItemElementTagName)),
    380        mBulletType(aBulletType) {
    381    MOZ_ASSERT(&mListTagName == nsGkAtoms::ul ||
    382               &mListTagName == nsGkAtoms::ol ||
    383               &mListTagName == nsGkAtoms::dl);
    384    MOZ_ASSERT_IF(
    385        &mListTagName == nsGkAtoms::ul || &mListTagName == nsGkAtoms::ol,
    386        &mListItemTagName == nsGkAtoms::li);
    387    MOZ_ASSERT_IF(&mListTagName == nsGkAtoms::dl,
    388                  &mListItemTagName == nsGkAtoms::dt ||
    389                      &mListItemTagName == nsGkAtoms::dd);
    390  }
    391 
    392  /**
    393   * @param aHTMLEditor The HTML editor.
    394   * @param aRanges     [in/out] The ranges which will be converted to list.
    395   *                    The instance must not have saved ranges because it'll
    396   *                    be used in this method.
    397   *                    If succeeded, this will have selection ranges which
    398   *                    should be applied to `Selection`.
    399   *                    If failed, this keeps storing original selection
    400   *                    ranges.
    401   * @param aSelectAllOfCurrentList     Yes if this should treat all of
    402   *                                    ancestor list element at selection.
    403   * @param aEditingHost                The editing host.
    404   */
    405  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
    406      HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRanges,
    407      HTMLEditor::SelectAllOfCurrentList aSelectAllOfCurrentList,
    408      const Element& aEditingHost) const;
    409 
    410 private:
    411  using ContentNodeArray = nsTArray<OwningNonNull<nsIContent>>;
    412  using AutoContentNodeArray = AutoTArray<OwningNonNull<nsIContent>, 64>;
    413 
    414  /**
    415   * If aSelectAllOfCurrentList is "Yes" and aRanges is in a list element,
    416   * returns the list element.
    417   * Otherwise, extend aRanges to select start and end lines selected by it and
    418   * correct all topmost content nodes in the extended ranges with splitting
    419   * ancestors at range edges.
    420   */
    421  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
    422  SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
    423      HTMLEditor& aHTMLEditor, AutoClonedRangeArray& aRanges,
    424      SelectAllOfCurrentList aSelectAllOfCurrentList,
    425      const Element& aEditingHost, ContentNodeArray& aOutArrayOfContents) const;
    426 
    427  /**
    428   * Return true if aArrayOfContents has only <br> elements or empty inline
    429   * container elements.  I.e., it means that aArrayOfContents represents
    430   * only empty line(s) if this returns true.
    431   */
    432  [[nodiscard]] static bool
    433  IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
    434      const ContentNodeArray& aArrayOfContents);
    435 
    436  /**
    437   * Delete all content nodes ina ArrayOfContents, and if we can put new list
    438   * element at start of the first range of aRanges, insert new list element
    439   * there.
    440   *
    441   * @return            The empty list item element in new list element.
    442   */
    443  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<RefPtr<Element>, nsresult>
    444  ReplaceContentNodesWithEmptyNewList(
    445      HTMLEditor& aHTMLEditor, const AutoClonedRangeArray& aRanges,
    446      const AutoContentNodeArray& aArrayOfContents,
    447      const Element& aEditingHost) const;
    448 
    449  /**
    450   * Creat new list elements or use existing list elements and move
    451   * aArrayOfContents into list item elements.
    452   *
    453   * @return            A list or list item element which should have caret.
    454   */
    455  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<RefPtr<Element>, nsresult>
    456  WrapContentNodesIntoNewListElements(HTMLEditor& aHTMLEditor,
    457                                      AutoClonedRangeArray& aRanges,
    458                                      AutoContentNodeArray& aArrayOfContents,
    459                                      const Element& aEditingHost) const;
    460 
    461  struct MOZ_STACK_CLASS AutoHandlingState final {
    462    // Current list element which is a good container to create new list item
    463    // element.
    464    RefPtr<Element> mCurrentListElement;
    465    // Previously handled list item element.
    466    RefPtr<Element> mPreviousListItemElement;
    467    // List or list item element which should have caret after handling all
    468    // contents.
    469    RefPtr<Element> mListOrListItemElementToPutCaret;
    470    // Replacing block element.  This is typically already removed from the DOM
    471    // tree.
    472    RefPtr<Element> mReplacingBlockElement;
    473    // Once id attribute of mReplacingBlockElement copied, the id attribute
    474    // shouldn't be copied again.
    475    bool mMaybeCopiedReplacingBlockElementId = false;
    476  };
    477 
    478  /**
    479   * Helper methods of WrapContentNodesIntoNewListElements.  They are called for
    480   * handling one content node of aArrayOfContents.  It's set to aHandling*.
    481   */
    482  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildContent(
    483      HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
    484      AutoHandlingState& aState, const Element& aEditingHost) const;
    485  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
    486  HandleChildListElement(HTMLEditor& aHTMLEditor, Element& aHandlingListElement,
    487                         AutoHandlingState& aState) const;
    488  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildListItemElement(
    489      HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
    490      AutoHandlingState& aState) const;
    491  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
    492  HandleChildListItemInDifferentTypeList(HTMLEditor& aHTMLEditor,
    493                                         Element& aHandlingListItemElement,
    494                                         AutoHandlingState& aState) const;
    495  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildListItemInSameTypeList(
    496      HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
    497      AutoHandlingState& aState) const;
    498  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildDivOrParagraphElement(
    499      HTMLEditor& aHTMLEditor, Element& aHandlingDivOrParagraphElement,
    500      AutoHandlingState& aState, const Element& aEditingHost) const;
    501  enum class EmptyListItem { NotCreate, Create };
    502  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult CreateAndUpdateCurrentListElement(
    503      HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert,
    504      EmptyListItem aEmptyListItem, AutoHandlingState& aState,
    505      const Element& aEditingHost) const;
    506  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult>
    507  AppendListItemElement(HTMLEditor& aHTMLEditor, const Element& aListElement,
    508                        AutoHandlingState& aState) const;
    509  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
    510  MaybeCloneAttributesToNewListItem(HTMLEditor& aHTMLEditor,
    511                                    Element& aListItemElement,
    512                                    AutoHandlingState& aState);
    513  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildInlineContent(
    514      HTMLEditor& aHTMLEditor, nsIContent& aHandlingInlineContent,
    515      AutoHandlingState& aState) const;
    516  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult WrapContentIntoNewListItemElement(
    517      HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
    518      AutoHandlingState& aState) const;
    519 
    520  /**
    521   * If aRanges is collapsed outside aListItemOrListToPutCaret, this collapse
    522   * aRanges in aListItemOrListToPutCaret again.
    523   */
    524  nsresult EnsureCollapsedRangeIsInListItemOrListElement(
    525      Element& aListItemOrListToPutCaret, AutoClonedRangeArray& aRanges) const;
    526 
    527  MOZ_KNOWN_LIVE nsStaticAtom& mListTagName;
    528  MOZ_KNOWN_LIVE nsStaticAtom& mListItemTagName;
    529  const nsAutoString mBulletType;
    530 };
    531 
    532 /**
    533 * Handle "insertParagraph" command.
    534 */
    535 class MOZ_STACK_CLASS HTMLEditor::AutoInsertParagraphHandler final {
    536 public:
    537  AutoInsertParagraphHandler() = delete;
    538  AutoInsertParagraphHandler(const AutoInsertParagraphHandler&) = delete;
    539  AutoInsertParagraphHandler(AutoInsertParagraphHandler&&) = delete;
    540 
    541  MOZ_CAN_RUN_SCRIPT explicit AutoInsertParagraphHandler(
    542      HTMLEditor& aHTMLEditor, const Element& aEditingHost)
    543      : mHTMLEditor(aHTMLEditor),
    544        mEditingHost(aEditingHost),
    545        mDefaultParagraphSeparatorTagName(
    546            aHTMLEditor.DefaultParagraphSeparatorTagName()),
    547        mDefaultParagraphSeparator(aHTMLEditor.GetDefaultParagraphSeparator()) {
    548  }
    549 
    550  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run();
    551 
    552 private:
    553  /**
    554   * Insert <br> element.
    555   *
    556   * @param aPointToInsert      The position where the new <br> should be
    557   *                            inserted.
    558   * @param aBlockElementWhichShouldHaveCaret
    559   *                            [optional] If set, this collapse selection into
    560   *                            the element with
    561   *                            CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret().
    562   */
    563  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
    564  HandleInsertBRElement(
    565      const EditorDOMPoint& aPointToInsert,
    566      const Element* aBlockElementWhichShouldHaveCaret = nullptr);
    567 
    568  /**
    569   * Insert a linefeed.
    570   */
    571  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
    572  HandleInsertLinefeed(const EditorDOMPoint& aPointToInsert);
    573 
    574  /**
    575   * SplitParagraphWithTransaction() splits the parent block, aParentDivOrP, at
    576   * aPointToSplit.
    577   *
    578   * @param aBlockElementToSplit    The current paragraph which should be split.
    579   * @param aPointToSplit           The point to split aBlockElementToSplit.
    580   */
    581  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult>
    582  SplitParagraphWithTransaction(Element& aBlockElementToSplit,
    583                                const EditorDOMPoint& aPointToSplit);
    584 
    585  /**
    586   * Delete preceding invisible line break before aPointToSplit if and only if
    587   * there is.
    588   *
    589   * @return New point to split aBlockElementToSplit
    590   */
    591  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
    592  EnsureNoInvisibleLineBreakBeforePointToSplit(
    593      const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit);
    594 
    595  /**
    596   * Maybe insert a <br> element if it's required to keep the inline container
    597   * visible after splitting aBlockElementToSplit at aPointToSplit.
    598   *
    599   * @return New point to split aBlockElementToSplit
    600   */
    601  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
    602  MaybeInsertFollowingBRElementToPreserveRightBlock(
    603      const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit);
    604 
    605  /**
    606   * Return true if the HTMLEditor is in the mode which `insertParagraph` should
    607   * always create a new paragraph or in the cases that we create a new
    608   * paragraph in the legacy mode.
    609   */
    610  [[nodiscard]] bool ShouldCreateNewParagraph(
    611      Element& aParentDivOrP, const EditorDOMPoint& aPointToSplit) const;
    612 
    613  /**
    614   * Return true if aBRElement is nullptr or an invisible <br> or a padding <br>
    615   * for making the last empty line visible.
    616   */
    617  [[nodiscard]] static bool
    618  IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine(
    619      const dom::HTMLBRElement* aBRElement);
    620 
    621  /**
    622   * Handle insertParagraph command (i.e., handling Enter key press) in a
    623   * heading element.  This splits aHeadingElement element at aPointToSplit.
    624   * Then, if right heading element is empty, it'll be removed and new paragraph
    625   * is created (its type is decided with default paragraph separator).
    626   *
    627   * @param aHeadingElement     The heading element to be split.
    628   * @param aPointToSplit       The point to split aHeadingElement.
    629   * @return                    New paragraph element, meaning right heading
    630   *                            element if aHeadingElement is split, or newly
    631   *                            created or existing paragraph element.
    632   */
    633  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult>
    634  HandleInHeadingElement(Element& aHeadingElement,
    635                         const EditorDOMPoint& aPointToSplit);
    636 
    637  /**
    638   * Handle insertParagraph command at end of a heading element.
    639   */
    640  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult>
    641  HandleAtEndOfHeadingElement(Element& aHeadingElement);
    642 
    643  /**
    644   * Handle insertParagraph command (i.e., handling Enter key press) in a list
    645   * item element.
    646   *
    647   * @param aListItemElement    The list item which has the following point.
    648   * @param aPointToSplit       The point to split aListItemElement.
    649   * @return                    New paragraph element, meaning right list item
    650   *                            element if aListItemElement is split, or newly
    651   *                            created paragraph element.
    652   */
    653  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult>
    654  HandleInListItemElement(Element& aListItemElement,
    655                          const EditorDOMPoint& aPointToSplit);
    656 
    657  /**
    658   * Split aMailCiteElement at aPointToSplit.
    659   *
    660   * @param aMailCiteElement    The mail-cite element which should be split.
    661   * @param aPointToSplit       The point to split.
    662   * @return                    Candidate caret position where is at inserted
    663   *                            <br> element into the split point.
    664   */
    665  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
    666  HandleInMailCiteElement(Element& aMailCiteElement,
    667                          const EditorDOMPoint& aPointToSplit);
    668 
    669  /**
    670   * Insert a <br> element into aPointToBreak.
    671   * This may split container elements at the point and/or may move following
    672   * <br> element to immediately after the new <br> element if necessary.
    673   *
    674   * @param aPointToBreak       The point where new <br> element will be
    675   *                            inserted before.
    676   * @return                    If succeeded, returns new <br> element and
    677   *                            candidate caret point.
    678   */
    679  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult>
    680  InsertBRElement(const EditorDOMPoint& aPointToBreak);
    681 
    682  /**
    683   * Return true if we should insert a line break instead of a paragraph.
    684   */
    685  [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool ShouldInsertLineBreakInstead(
    686      const Element* aEditableBlockElement,
    687      const EditorDOMPoint& aCandidatePointToSplit);
    688 
    689  enum class InsertBRElementIntoEmptyBlock : bool { Start, End };
    690 
    691  /**
    692   * Make sure that aMaybeBlockElement is visible with putting a <br> element if
    693   * and only if it's an empty block element.
    694   */
    695  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateLineBreakResult, nsresult>
    696  InsertBRElementIfEmptyBlockElement(
    697      Element& aMaybeBlockElement,
    698      InsertBRElementIntoEmptyBlock aInsertBRElementIntoEmptyBlock,
    699      BlockInlineCheck aBlockInlineCheck);
    700 
    701  /**
    702   * Split aMailCiteElement at aPointToSplit.  This deletes all inclusive
    703   * ancestors of aPointToSplit in aMailCiteElement too.
    704   */
    705  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult>
    706  SplitMailCiteElement(const EditorDOMPoint& aPointToSplit,
    707                       Element& aMailCiteElement);
    708 
    709  /**
    710   * aMailCiteElement may be a <span> element which is styled as block.  If it's
    711   * followed by a block boundary, it requires a padding <br> element when it's
    712   * serialized.  This method may insert a <br> element if it's required.
    713   */
    714  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
    715  MaybeInsertPaddingBRElementToInlineMailCiteElement(
    716      const EditorDOMPoint& aPointToInsertBRElement, Element& aMailCiteElement);
    717 
    718  /**
    719   * Return the deepest inline container element which is the first leaf or the
    720   * first leaf container of aBlockElement.
    721   */
    722  [[nodiscard]] static Element* GetDeepestFirstChildInlineContainerElement(
    723      Element& aBlockElement);
    724 
    725  /**
    726   * Collapse `Selection` to aCandidatePointToPutCaret or into
    727   * aBlockElementShouldHaveCaret.  If aBlockElementShouldHaveCaret is specified
    728   * and aCandidatePointToPutCaret is outside it, this ignores
    729   * aCandidatePointToPutCaret and collapse `Selection` into
    730   * aBlockElementShouldHaveCaret.
    731   */
    732  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
    733  CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret(
    734      const EditorDOMPoint& aCandidatePointToPutCaret,
    735      const Element* aBlockElementShouldHaveCaret,
    736      const SuggestCaretOptions& aOptions);
    737 
    738  /**
    739   * Return a better point to split the paragraph to avoid to keep a typing in a
    740   * link or a paragraph in list item in the new paragraph.
    741   */
    742  [[nodiscard]] EditorDOMPoint GetBetterPointToSplitParagraph(
    743      const Element& aBlockElementToSplit,
    744      const EditorDOMPoint& aCandidatePointToSplit);
    745 
    746  enum class IgnoreBlockBoundaries : bool { No, Yes };
    747 
    748  /**
    749   * Return true if splitting aBlockElementToSplit at aPointToSplit will create
    750   * empty left element.
    751   *
    752   * @param aBlockElementToSplit    The paragraph element which we want to
    753   *                                split.
    754   * @param aPointToSplit           The split position in aBlockElementToSplit.
    755   * @param aIgnoreBlockBoundaries  If No, return true only when aPointToSplit
    756   *                                is immediately after a block boundary of
    757   *                                aBlockElementToSplit.  In other words,
    758   *                                may return true only when aPointToSplit
    759   *                                is not in a child block of
    760   *                                aBlockElementToSplit.
    761   *                                If Yes, return true even when aPointToSplit
    762   *                                is immediately after any current block
    763   *                                boundary which is followed by the block
    764   *                                boundary of aBlockElementToSplit.  In other
    765   *                                words, return true when aPointToSplit is in
    766   *                                a child block which is start of any ancestor
    767   *                                block elements.
    768   */
    769  [[nodiscard]] static bool SplitPointIsStartOfSplittingBlock(
    770      const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit,
    771      IgnoreBlockBoundaries aIgnoreBlockBoundaries);
    772 
    773  /**
    774   * Return true if splitting aBlockElementToSplit at aPointToSplit will create
    775   * empty right element.
    776   *
    777   * @param aBlockElementToSplit    The paragraph element which we want to
    778   *                                split.
    779   * @param aPointToSplit           The split position in aBlockElementToSplit.
    780   * @param aIgnoreBlockBoundaries  If No, return true only when aPointToSplit
    781   *                                is immediately before a block boundary of
    782   *                                aBlockElementToSplit.  In other words,
    783   *                                may return true only when aPointToSplit
    784   *                                is not in a child block of
    785   *                                aBlockElementToSplit.
    786   *                                If Yes, return true even when aPointToSplit
    787   *                                is immediately before any current block
    788   *                                boundary which is followed by the block
    789   *                                boundary of aBlockElementToSplit.  In other
    790   *                                words, return true when aPointToSplit is in
    791   *                                a child block which is end of any ancestor
    792   *                                block elements.
    793   */
    794  [[nodiscard]] static bool SplitPointIsEndOfSplittingBlock(
    795      const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit,
    796      IgnoreBlockBoundaries aIgnoreBlockBoundaries);
    797 
    798  MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor;
    799  MOZ_KNOWN_LIVE const Element& mEditingHost;
    800  MOZ_KNOWN_LIVE nsStaticAtom& mDefaultParagraphSeparatorTagName;
    801  const ParagraphSeparator mDefaultParagraphSeparator;
    802 };
    803 
    804 /**
    805 * Handle "insertLineBreak" command.
    806 */
    807 class MOZ_STACK_CLASS HTMLEditor::AutoInsertLineBreakHandler final {
    808 public:
    809  AutoInsertLineBreakHandler() = delete;
    810  AutoInsertLineBreakHandler(const AutoInsertLineBreakHandler&) = delete;
    811  AutoInsertLineBreakHandler(AutoInsertLineBreakHandler&&) = delete;
    812 
    813  MOZ_CAN_RUN_SCRIPT explicit AutoInsertLineBreakHandler(
    814      HTMLEditor& aHTMLEditor, const Element& aEditingHost)
    815      : mHTMLEditor(aHTMLEditor), mEditingHost(aEditingHost) {}
    816 
    817  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult Run();
    818 
    819 private:
    820  /**
    821   * Insert a linefeed character into aPointToBreak.
    822   *
    823   * @param aPointToBreak       The point where new linefeed character will be
    824   *                            inserted before.
    825   * @param aEditingHost        Current active editing host.
    826   * @return                    A suggest point to put caret.
    827   */
    828  [[nodiscard]] static MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
    829  InsertLinefeed(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToBreak,
    830                 const Element& aEditingHost);
    831 
    832  /**
    833   * Insert <br> element at `Selection`.
    834   */
    835  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleInsertBRElement();
    836 
    837  /**
    838   * Insert a linefeed at `Selection`.
    839   */
    840  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleInsertLinefeed();
    841 
    842  friend class AutoInsertParagraphHandler;
    843 
    844  MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor;
    845  MOZ_KNOWN_LIVE const Element& mEditingHost;
    846 };
    847 
    848 /**
    849 * Handle delete multiple ranges, typically they are the selection ranges.
    850 */
    851 class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
    852 public:
    853  explicit AutoDeleteRangesHandler(
    854      const AutoDeleteRangesHandler* aParent = nullptr)
    855      : mParent(aParent),
    856        mOriginalDirectionAndAmount(nsIEditor::eNone),
    857        mOriginalStripWrappers(nsIEditor::eNoStrip) {}
    858 
    859  /**
    860   * ComputeRangesToDelete() computes actual deletion ranges.
    861   */
    862  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ComputeRangesToDelete(
    863      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    864      AutoClonedSelectionRangeArray& aRangesToDelete,
    865      const Element& aEditingHost);
    866 
    867  /**
    868   * Deletes content in or around aRangesToDelete.
    869   * NOTE: This method creates SelectionBatcher.  Therefore, each caller
    870   *       needs to check if the editor is still available even if this returns
    871   *       NS_OK.
    872   */
    873  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
    874      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    875      nsIEditor::EStripWrappers aStripWrappers,
    876      AutoClonedSelectionRangeArray& aRangesToDelete,
    877      const Element& aEditingHost);
    878 
    879 private:
    880  [[nodiscard]] bool IsHandlingRecursively() const {
    881    return mParent != nullptr;
    882  }
    883 
    884  [[nodiscard]] bool CanFallbackToDeleteRangeWithTransaction(
    885      const nsRange& aRangeToDelete) const;
    886 
    887  [[nodiscard]] bool CanFallbackToDeleteRangesWithTransaction(
    888      const AutoClonedSelectionRangeArray& aRangesToDelete) const;
    889 
    890  /**
    891   * HandleDeleteAroundCollapsedRanges() handles deletion with collapsed
    892   * ranges.  Callers must guarantee that this is called only when
    893   * aRangesToDelete.IsCollapsed() returns true.
    894   *
    895   * @param aDirectionAndAmount Direction of the deletion.
    896   * @param aStripWrappers      Must be eStrip or eNoStrip.
    897   * @param aRangesToDelete     Ranges to delete.  This `IsCollapsed()` must
    898   *                            return true.
    899   * @param aWSRunScannerAtCaret        Scanner instance which scanned from
    900   *                                    caret point.
    901   * @param aScanFromCaretPointResult   Scan result of aWSRunScannerAtCaret
    902   *                                    toward aDirectionAndAmount.
    903   * @param aEditingHost        The editing host.
    904   */
    905  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
    906  HandleDeleteAroundCollapsedRanges(
    907      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    908      nsIEditor::EStripWrappers aStripWrappers,
    909      AutoClonedSelectionRangeArray& aRangesToDelete,
    910      const WSRunScanner& aWSRunScannerAtCaret,
    911      const WSScanResult& aScanFromCaretPointResult,
    912      const Element& aEditingHost);
    913  nsresult ComputeRangesToDeleteAroundCollapsedRanges(
    914      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    915      AutoClonedSelectionRangeArray& aRangesToDelete,
    916      const WSRunScanner& aWSRunScannerAtCaret,
    917      const WSScanResult& aScanFromCaretPointResult,
    918      const Element& aEditingHost) const;
    919 
    920  /**
    921   * HandleDeleteNonCollapsedRanges() handles deletion with non-collapsed
    922   * ranges.  Callers must guarantee that this is called only when
    923   * aRangesToDelete.IsCollapsed() returns false.
    924   *
    925   * @param aDirectionAndAmount         Direction of the deletion.
    926   * @param aStripWrappers              Must be eStrip or eNoStrip.
    927   * @param aRangesToDelete             The ranges to delete.
    928   * @param aSelectionWasCollapsed      If the caller extended `Selection`
    929   *                                    from collapsed, set this to `Yes`.
    930   *                                    Otherwise, i.e., `Selection` is not
    931   *                                    collapsed from the beginning, set
    932   *                                    this to `No`.
    933   * @param aEditingHost                The editing host.
    934   */
    935  enum class SelectionWasCollapsed { Yes, No };
    936  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
    937  HandleDeleteNonCollapsedRanges(HTMLEditor& aHTMLEditor,
    938                                 nsIEditor::EDirection aDirectionAndAmount,
    939                                 nsIEditor::EStripWrappers aStripWrappers,
    940                                 AutoClonedSelectionRangeArray& aRangesToDelete,
    941                                 SelectionWasCollapsed aSelectionWasCollapsed,
    942                                 const Element& aEditingHost);
    943  nsresult ComputeRangesToDeleteNonCollapsedRanges(
    944      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    945      AutoClonedSelectionRangeArray& aRangesToDelete,
    946      SelectionWasCollapsed aSelectionWasCollapsed,
    947      const Element& aEditingHost) const;
    948 
    949  /**
    950   * Handle deletion of collapsed ranges in a text node.
    951   *
    952   * @param aDirectionAndAmount Must be eNext or ePrevious.
    953   * @param aCaretPosition      The position where caret is.  This container
    954   *                            must be a text node.
    955   * @param aEditingHost        The editing host.
    956   */
    957  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
    958  HandleDeleteTextAroundCollapsedRanges(
    959      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
    960      AutoClonedSelectionRangeArray& aRangesToDelete,
    961      const Element& aEditingHost);
    962  nsresult ComputeRangesToDeleteTextAroundCollapsedRanges(
    963      nsIEditor::EDirection aDirectionAndAmount,
    964      AutoClonedSelectionRangeArray& aRangesToDelete) const;
    965 
    966  /**
    967   * Handle deletion of atomic elements like <br>, <hr>, <img>, <input>, etc and
    968   * data nodes except text node (e.g., comment node). Note that don't call this
    969   * directly with `<hr>` element.
    970   *
    971   * @param aAtomicContent      The atomic content to be deleted.
    972   * @param aCaretPoint         The caret point (i.e., selection start or
    973   *                            end).
    974   * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
    975   *                             with the caret point.
    976   * @param aEditingHost        The editing host.
    977   */
    978  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
    979  HandleDeleteAtomicContent(HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent,
    980                            const EditorDOMPoint& aCaretPoint,
    981                            const WSRunScanner& aWSRunScannerAtCaret,
    982                            const Element& aEditingHost);
    983  nsresult ComputeRangesToDeleteAtomicContent(
    984      const nsIContent& aAtomicContent,
    985      AutoClonedSelectionRangeArray& aRangesToDelete) const;
    986 
    987  /**
    988   * GetAtomicContnetToDelete() returns better content that is deletion of
    989   * atomic element.  If aScanFromCaretPointResult is special, since this
    990   * point may not be editable, we look for better point to remove atomic
    991   * content.
    992   *
    993   * @param aDirectionAndAmount       Direction of the deletion.
    994   * @param aWSRunScannerAtCaret      WSRunScanner instance which was
    995   *                                  initialized with the caret point.
    996   * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret
    997   *                                  toward aDirectionAndAmount.
    998   */
    999  [[nodiscard]] static nsIContent* GetAtomicContentToDelete(
   1000      nsIEditor::EDirection aDirectionAndAmount,
   1001      const WSRunScanner& aWSRunScannerAtCaret,
   1002      const WSScanResult& aScanFromCaretPointResult) MOZ_NONNULL_RETURN;
   1003 
   1004  /**
   1005   * HandleDeleteAtOtherBlockBoundary() handles deletion at other block boundary
   1006   * (i.e., immediately before or after a block). If this does not join blocks,
   1007   * `Run()` may be called recursively with creating another instance.
   1008   *
   1009   * @param aDirectionAndAmount Direction of the deletion.
   1010   * @param aStripWrappers      Must be eStrip or eNoStrip.
   1011   * @param aOtherBlockElement  The block element which follows the caret or
   1012   *                            is followed by caret.
   1013   * @param aCaretPoint         The caret point (i.e., selection start or
   1014   *                            end).
   1015   * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
   1016   *                             with the caret point.
   1017   * @param aRangesToDelete     Ranges to delete of the caller.  This should
   1018   *                            be collapsed and the point should match with
   1019   *                            aCaretPoint.
   1020   * @param aEditingHost        The editing host.
   1021   */
   1022  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
   1023  HandleDeleteAtOtherBlockBoundary(
   1024      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1025      nsIEditor::EStripWrappers aStripWrappers, Element& aOtherBlockElement,
   1026      const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret,
   1027      AutoClonedSelectionRangeArray& aRangesToDelete,
   1028      const Element& aEditingHost);
   1029 
   1030  /**
   1031   * ExtendOrShrinkRangeToDelete() extends aRangeToDelete if there are
   1032   * an invisible <br> element and/or some parent empty elements.
   1033   *
   1034   * @param aLimitersAndCaretData The frame selection data.
   1035   * @param aRangeToDelete       The range to be extended for deletion.  This
   1036   *                            must not be collapsed, must be positioned.
   1037   */
   1038  template <typename EditorDOMRangeType>
   1039  [[nodiscard]] Result<EditorRawDOMRange, nsresult> ExtendOrShrinkRangeToDelete(
   1040      const HTMLEditor& aHTMLEditor,
   1041      const LimitersAndCaretData& aLimitersAndCaretData,
   1042      const EditorDOMRangeType& aRangeToDelete) const;
   1043 
   1044  /**
   1045   * Extend the start boundary of aRangeToDelete to contain ancestor inline
   1046   * elements which will be empty once the content in aRangeToDelete is removed
   1047   * from the tree.
   1048   *
   1049   * NOTE: This is designed for deleting inline elements which become empty if
   1050   * aRangeToDelete which crosses a block boundary of right block child.
   1051   * Therefore, you may need to improve this method if you want to use this in
   1052   * the other cases.
   1053   *
   1054   * @param aRangeToDelete      [in/out] The range to delete.  This start
   1055   *                            boundary may be modified.
   1056   * @param aEditingHost        The editing host.
   1057   * @return                    true if aRangeToDelete is modified.
   1058   *                            false if aRangeToDelete is not modified.
   1059   *                            error if aRangeToDelete gets unexpected
   1060   *                            situation.
   1061   */
   1062  [[nodiscard]] static Result<bool, nsresult>
   1063  ExtendRangeToContainAncestorInlineElementsAtStart(
   1064      nsRange& aRangeToDelete, const Element& aEditingHost);
   1065 
   1066  /**
   1067   * A helper method for ExtendOrShrinkRangeToDelete().  This returns shrunken
   1068   * range if aRangeToDelete selects all over list elements which have some list
   1069   * item elements to avoid to delete all list items from the list element.
   1070   */
   1071  [[nodiscard]] static EditorRawDOMRange
   1072  GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
   1073      const EditorRawDOMRange& aRangeToDelete);
   1074 
   1075  /**
   1076   * DeleteUnnecessaryNodes() removes unnecessary nodes around aRange.
   1077   */
   1078  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
   1079  DeleteUnnecessaryNodes(HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
   1080                         const Element& aEditingHost);
   1081 
   1082  /**
   1083   * If aContent is a text node that contains only collapsed white-space or
   1084   * empty and editable.
   1085   */
   1086  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
   1087  DeleteNodeIfInvisibleAndEditableTextNode(HTMLEditor& aHTMLEditor,
   1088                                           nsIContent& aContent);
   1089 
   1090  /**
   1091   * DeleteParentBlocksIfEmpty() removes parent block elements if they
   1092   * don't have visible contents.  Note that due performance issue of
   1093   * WhiteSpaceVisibilityKeeper, this call may be expensive.  And also note that
   1094   * this removes a empty block with a transaction.  So, please make sure that
   1095   * you've already created `AutoPlaceholderBatch`.
   1096   *
   1097   * @param aPoint      The point whether this method climbing up the DOM
   1098   *                    tree to remove empty parent blocks.
   1099   * @return            NS_OK if one or more empty block parents are deleted.
   1100   *                    NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND if the point is
   1101   *                    not in empty block.
   1102   *                    Or NS_ERROR_* if something unexpected occurs.
   1103   */
   1104  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
   1105  DeleteParentBlocksWithTransactionIfEmpty(HTMLEditor& aHTMLEditor,
   1106                                           const EditorDOMPoint& aPoint,
   1107                                           const Element& aEditingHost);
   1108 
   1109  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
   1110  FallbackToDeleteRangeWithTransaction(HTMLEditor& aHTMLEditor,
   1111                                       nsRange& aRangeToDelete) const {
   1112    MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   1113    MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete));
   1114    Result<CaretPoint, nsresult> caretPointOrError =
   1115        aHTMLEditor.DeleteRangeWithTransaction(mOriginalDirectionAndAmount,
   1116                                               mOriginalStripWrappers,
   1117                                               aRangeToDelete);
   1118    NS_WARNING_ASSERTION(caretPointOrError.isOk(),
   1119                         "EditorBase::DeleteRangeWithTransaction() failed");
   1120    return caretPointOrError;
   1121  }
   1122 
   1123  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
   1124  FallbackToDeleteRangesWithTransaction(
   1125      HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRangesToDelete,
   1126      const Element& aEditingHost) const;
   1127 
   1128  /**
   1129   * Compute target range(s) which will be called by
   1130   * `EditorBase::DeleteRangeWithTransaction()` or
   1131   * `HTMLEditor::DeleteRangesWithTransaction()`.
   1132   * TODO: We should not use it for consistency with each deletion handler
   1133   *       in this and nested classes.
   1134   */
   1135  nsresult ComputeRangeToDeleteRangeWithTransaction(
   1136      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1137      nsRange& aRange, const Element& aEditingHost) const;
   1138  nsresult ComputeRangesToDeleteRangesWithTransaction(
   1139      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1140      AutoClonedSelectionRangeArray& aRangesToDelete,
   1141      const Element& aEditingHost) const;
   1142 
   1143  nsresult FallbackToComputeRangeToDeleteRangeWithTransaction(
   1144      const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
   1145      const Element& aEditingHost) const {
   1146    MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   1147    MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete));
   1148    nsresult rv = ComputeRangeToDeleteRangeWithTransaction(
   1149        aHTMLEditor, mOriginalDirectionAndAmount, aRangeToDelete, aEditingHost);
   1150    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1151                         "AutoDeleteRangesHandler::"
   1152                         "ComputeRangeToDeleteRangeWithTransaction() failed");
   1153    return rv;
   1154  }
   1155  nsresult FallbackToComputeRangesToDeleteRangesWithTransaction(
   1156      const HTMLEditor& aHTMLEditor,
   1157      AutoClonedSelectionRangeArray& aRangesToDelete,
   1158      const Element& aEditingHost) const {
   1159    MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
   1160    MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete));
   1161    nsresult rv = ComputeRangesToDeleteRangesWithTransaction(
   1162        aHTMLEditor, mOriginalDirectionAndAmount, aRangesToDelete,
   1163        aEditingHost);
   1164    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
   1165                         "AutoDeleteRangesHandler::"
   1166                         "ComputeRangesToDeleteRangesWithTransaction() failed");
   1167    return rv;
   1168  }
   1169 
   1170  class MOZ_STACK_CLASS AutoBlockElementsJoiner;
   1171  class MOZ_STACK_CLASS AutoEmptyBlockAncestorDeleter;
   1172 
   1173  const AutoDeleteRangesHandler* const mParent;
   1174  nsIEditor::EDirection mOriginalDirectionAndAmount;
   1175  nsIEditor::EStripWrappers mOriginalStripWrappers;
   1176 };
   1177 
   1178 /**
   1179 * Handle join block elements.  Despite the name, this may just move first line
   1180 * of a block into another block or just deleted the range with keeping table
   1181 * structure.
   1182 */
   1183 class MOZ_STACK_CLASS
   1184 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner final {
   1185 public:
   1186  AutoBlockElementsJoiner() = delete;
   1187  explicit AutoBlockElementsJoiner(
   1188      AutoDeleteRangesHandler& aDeleteRangesHandler)
   1189      : mDeleteRangesHandler(&aDeleteRangesHandler),
   1190        mDeleteRangesHandlerConst(aDeleteRangesHandler) {}
   1191  explicit AutoBlockElementsJoiner(
   1192      const AutoDeleteRangesHandler& aDeleteRangesHandler)
   1193      : mDeleteRangesHandler(nullptr),
   1194        mDeleteRangesHandlerConst(aDeleteRangesHandler) {}
   1195 
   1196  /**
   1197   * PrepareToDeleteAtCurrentBlockBoundary() considers left content and right
   1198   * content which are joined for handling deletion at current block boundary
   1199   * (i.e., at start or end of the current block).
   1200   *
   1201   * @param aHTMLEditor               The HTML editor.
   1202   * @param aDirectionAndAmount       Direction of the deletion.
   1203   * @param aCurrentBlockElement      The current block element.
   1204   * @param aCaretPoint               The caret point (i.e., selection start
   1205   *                                  or end).
   1206   * @param aEditingHost              The editing host.
   1207   * @return                          true if can continue to handle the
   1208   *                                  deletion.
   1209   */
   1210  [[nodiscard]] bool PrepareToDeleteAtCurrentBlockBoundary(
   1211      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1212      Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint,
   1213      const Element& aEditingHost);
   1214 
   1215  /**
   1216   * PrepareToDeleteAtOtherBlockBoundary() considers left content and right
   1217   * content which are joined for handling deletion at other block boundary
   1218   * (i.e., immediately before or after a block).
   1219   *
   1220   * @param aHTMLEditor               The HTML editor.
   1221   * @param aDirectionAndAmount       Direction of the deletion.
   1222   * @param aOtherBlockElement        The block element which follows the
   1223   *                                  caret or is followed by caret.
   1224   * @param aCaretPoint               The caret point (i.e., selection start
   1225   *                                  or end).
   1226   * @param aWSRunScannerAtCaret      WSRunScanner instance which was
   1227   *                                  initialized with the caret point.
   1228   * @return                          true if can continue to handle the
   1229   *                                  deletion.
   1230   */
   1231  [[nodiscard]] bool PrepareToDeleteAtOtherBlockBoundary(
   1232      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1233      Element& aOtherBlockElement, const EditorDOMPoint& aCaretPoint,
   1234      const WSRunScanner& aWSRunScannerAtCaret);
   1235 
   1236  /**
   1237   * PrepareToDeleteNonCollapsedRange() considers left block element and
   1238   * right block element which are inclusive ancestor block element of
   1239   * start and end container of aRangeToDelete
   1240   *
   1241   * @param aHTMLEditor               The HTML editor.
   1242   * @param aRangeToDelete            The range to delete.  Must not be
   1243   *                                  collapsed.
   1244   * @param aEditingHost              The editing host.
   1245   * @return                          true if can continue to handle the
   1246   *                                  deletion.
   1247   */
   1248  [[nodiscard]] bool PrepareToDeleteNonCollapsedRange(
   1249      const HTMLEditor& aHTMLEditor, const nsRange& aRangeToDelete,
   1250      const Element& aEditingHost);
   1251 
   1252  /**
   1253   * Run() executes the joining.
   1254   *
   1255   * @param aHTMLEditor               The HTML editor.
   1256   * @param aDirectionAndAmount       Direction of the deletion.
   1257   * @param aStripWrappers            Must be eStrip or eNoStrip.
   1258   * @param aCaretPoint               The caret point (i.e., selection start
   1259   *                                  or end).
   1260   * @param aRangeToDelete            The range to delete.  This should be
   1261   *                                  collapsed and match with aCaretPoint.
   1262   */
   1263  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
   1264      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1265      nsIEditor::EStripWrappers aStripWrappers,
   1266      const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete,
   1267      const Element& aEditingHost);
   1268 
   1269  nsresult ComputeRangeToDelete(const HTMLEditor& aHTMLEditor,
   1270                                nsIEditor::EDirection aDirectionAndAmount,
   1271                                const EditorDOMPoint& aCaretPoint,
   1272                                nsRange& aRangeToDelete,
   1273                                const Element& aEditingHost) const;
   1274 
   1275  /**
   1276   * Run() executes the joining.
   1277   *
   1278   * @param aHTMLEditor               The HTML editor.
   1279   * @param aLimitersAndCaretData     The data copied from nsFrameSelection.
   1280   * @param aDirectionAndAmount       Direction of the deletion.
   1281   * @param aStripWrappers            Whether delete or keep new empty
   1282   *                                  ancestor elements.
   1283   * @param aRangeToDelete            The range to delete.  Must not be
   1284   *                                  collapsed.
   1285   * @param aSelectionWasCollapsed    Whether selection was or was not
   1286   *                                  collapsed when starting to handle
   1287   *                                  deletion.
   1288   */
   1289  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
   1290      HTMLEditor& aHTMLEditor,
   1291      const LimitersAndCaretData& aLimitersAndCaretData,
   1292      nsIEditor::EDirection aDirectionAndAmount,
   1293      nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
   1294      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
   1295      const Element& aEditingHost);
   1296 
   1297  nsresult ComputeRangeToDelete(
   1298      const HTMLEditor& aHTMLEditor,
   1299      const AutoClonedSelectionRangeArray& aRangesToDelete,
   1300      nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
   1301      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
   1302      const Element& aEditingHost) const;
   1303 
   1304  [[nodiscard]] nsIContent* GetLeafContentInOtherBlockElement() const {
   1305    MOZ_ASSERT(mMode == Mode::JoinOtherBlock);
   1306    return mLeafContentInOtherBlock;
   1307  }
   1308 
   1309 private:
   1310  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
   1311  HandleDeleteAtCurrentBlockBoundary(HTMLEditor& aHTMLEditor,
   1312                                     nsIEditor::EDirection aDirectionAndAmount,
   1313                                     const EditorDOMPoint& aCaretPoint,
   1314                                     const Element& aEditingHost);
   1315  nsresult ComputeRangeToDeleteAtCurrentBlockBoundary(
   1316      const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint,
   1317      nsRange& aRangeToDelete, const Element& aEditingHost) const;
   1318  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
   1319  HandleDeleteAtOtherBlockBoundary(HTMLEditor& aHTMLEditor,
   1320                                   nsIEditor::EDirection aDirectionAndAmount,
   1321                                   nsIEditor::EStripWrappers aStripWrappers,
   1322                                   const EditorDOMPoint& aCaretPoint,
   1323                                   nsRange& aRangeToDelete,
   1324                                   const Element& aEditingHost);
   1325  // FYI: This method may modify selection, but it won't cause running
   1326  //      script because of `AutoHideSelectionChanges` which blocks
   1327  //      selection change listeners and the selection change event
   1328  //      dispatcher.
   1329  MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult ComputeRangeToDeleteAtOtherBlockBoundary(
   1330      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1331      const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete,
   1332      const Element& aEditingHost) const;
   1333  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
   1334  JoinBlockElementsInSameParent(
   1335      HTMLEditor& aHTMLEditor,
   1336      const LimitersAndCaretData& aLimitersAndCaretData,
   1337      nsIEditor::EDirection aDirectionAndAmount,
   1338      nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
   1339      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
   1340      const Element& aEditingHost);
   1341  nsresult ComputeRangeToJoinBlockElementsInSameParent(
   1342      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1343      nsRange& aRangeToDelete, const Element& aEditingHost) const;
   1344  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
   1345  HandleDeleteLineBreak(HTMLEditor& aHTMLEditor,
   1346                        nsIEditor::EDirection aDirectionAndAmount,
   1347                        const EditorDOMPoint& aCaretPoint,
   1348                        const Element& aEditingHost);
   1349  enum class ComputeRangeFor : bool { GetTargetRanges, ToDeleteTheRange };
   1350  nsresult ComputeRangeToDeleteLineBreak(
   1351      const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
   1352      const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
   1353  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
   1354  DeleteContentInRange(HTMLEditor& aHTMLEditor,
   1355                       const LimitersAndCaretData& aLimitersAndCaretData,
   1356                       nsIEditor::EDirection aDirectionAndAmount,
   1357                       nsIEditor::EStripWrappers aStripWrappers,
   1358                       nsRange& aRangeToDelete, const Element& aEditingHost);
   1359  nsresult ComputeRangeToDeleteContentInRange(
   1360      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1361      nsRange& aRange, const Element& aEditingHost) const;
   1362  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
   1363  HandleDeleteNonCollapsedRange(
   1364      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1365      nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
   1366      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
   1367      const Element& aEditingHost);
   1368  nsresult ComputeRangeToDeleteNonCollapsedRange(
   1369      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1370      nsRange& aRangeToDelete,
   1371      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
   1372      const Element& aEditingHost) const;
   1373 
   1374  /**
   1375   * JoinNodesDeepWithTransaction() joins aLeftNode and aRightNode "deeply".
   1376   * First, they are joined simply, then, new right node is assumed as the
   1377   * child at length of the left node before joined and new left node is
   1378   * assumed as its previous sibling.  Then, they will be joined again.
   1379   * And then, these steps are repeated.
   1380   *
   1381   * @param aLeftContent    The node which will be removed form the tree.
   1382   * @param aRightContent   The node which will be inserted the contents of
   1383   *                        aRightContent.
   1384   * @return                The point of the first child of the last right
   1385   * node. The result is always set if this succeeded.
   1386   */
   1387  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
   1388  JoinNodesDeepWithTransaction(HTMLEditor& aHTMLEditor,
   1389                               nsIContent& aLeftContent,
   1390                               nsIContent& aRightContent);
   1391 
   1392  enum class PutCaretTo : bool { StartOfRange, EndOfRange };
   1393 
   1394  /**
   1395   * DeleteNodesEntirelyInRangeButKeepTableStructure() removes each node in
   1396   * aArrayOfContent.  However, if some nodes are part of a table, removes all
   1397   * children of them instead.  I.e., this does not make damage to table
   1398   * structure at the range, but may remove table entirely if it's in the
   1399   * range.
   1400   */
   1401  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
   1402  DeleteNodesEntirelyInRangeButKeepTableStructure(
   1403      HTMLEditor& aHTMLEditor,
   1404      const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContent,
   1405      PutCaretTo aPutCaretTo);
   1406  [[nodiscard]] bool
   1407  NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure(
   1408      const HTMLEditor& aHTMLEditor,
   1409      const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
   1410      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
   1411      const;
   1412  Result<bool, nsresult>
   1413  ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
   1414      const HTMLEditor& aHTMLEditor, nsRange& aRange,
   1415      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
   1416      const;
   1417 
   1418  /**
   1419   * DeleteContentButKeepTableStructure() removes aContent if it's an element
   1420   * which is part of a table structure.  If it's a part of table structure,
   1421   * removes its all children recursively.  I.e., this may delete all of a
   1422   * table, but won't break table structure partially.
   1423   *
   1424   * @param aContent            The content which or whose all children should
   1425   *                            be removed.
   1426   */
   1427  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
   1428  DeleteContentButKeepTableStructure(HTMLEditor& aHTMLEditor,
   1429                                     nsIContent& aContent);
   1430 
   1431  /**
   1432   * DeleteTextAtStartAndEndOfRange() removes text if start and/or end of
   1433   * aRange is in a text node.
   1434   */
   1435  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
   1436  DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange,
   1437                                 PutCaretTo aPutCaretTo);
   1438 
   1439  /**
   1440   * Return a block element which is an inclusive ancestor of the container of
   1441   * aPoint if aPoint is start of ancestor blocks.  For example, if `<div
   1442   * id=div1>abc<div id=div2><div id=div3>[]def</div></div></div>`, return
   1443   * #div2.
   1444   */
   1445  template <typename EditorDOMPointType>
   1446  [[nodiscard]] static Result<Element*, nsresult>
   1447  GetMostDistantBlockAncestorIfPointIsStartAtBlock(
   1448      const EditorDOMPointType& aPoint, const Element& aEditingHost,
   1449      const Element* aAncestorLimiter = nullptr);
   1450 
   1451  /**
   1452   * Extend aRangeToDelete to contain new empty inline ancestors and contain
   1453   * an invisible <br> element before right child block which causes an empty
   1454   * line but the range starts after it.
   1455   */
   1456  void ExtendRangeToDeleteNonCollapsedRange(
   1457      const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
   1458      const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
   1459 
   1460  /**
   1461   * Compute mLeafContentInOtherBlock from the DOM.
   1462   */
   1463  [[nodiscard]] nsIContent* ComputeLeafContentInOtherBlockElement(
   1464      nsIEditor::EDirection aDirectionAndAmount) const;
   1465 
   1466  class MOZ_STACK_CLASS AutoInclusiveAncestorBlockElementsJoiner;
   1467 
   1468  enum class Mode {
   1469    NotInitialized,
   1470    JoinCurrentBlock,
   1471    JoinOtherBlock,
   1472    JoinBlocksInSameParent,
   1473    DeleteBRElement,
   1474    // The instance will handle only the <br> element immediately before a
   1475    // block.
   1476    DeletePrecedingBRElementOfBlock,
   1477    // The instance will handle only the preceding preformatted line break
   1478    // before a block.
   1479    DeletePrecedingPreformattedLineBreak,
   1480    DeleteContentInRange,
   1481    DeleteNonCollapsedRange,
   1482    // The instance will handle preceding lines of the right block and content
   1483    // in the range in the right block.
   1484    DeletePrecedingLinesAndContentInRange,
   1485  };
   1486  AutoDeleteRangesHandler* mDeleteRangesHandler;
   1487  const AutoDeleteRangesHandler& mDeleteRangesHandlerConst;
   1488  nsCOMPtr<nsIContent> mLeftContent;
   1489  nsCOMPtr<nsIContent> mRightContent;
   1490  nsCOMPtr<nsIContent> mLeafContentInOtherBlock;
   1491  RefPtr<Element> mOtherBlockElement;
   1492  // mSkippedInvisibleContents stores all content nodes which are skipped at
   1493  // scanning mLeftContent and mRightContent.  The content nodes should be
   1494  // removed at deletion.
   1495  AutoTArray<OwningNonNull<nsIContent>, 8> mSkippedInvisibleContents;
   1496  RefPtr<dom::HTMLBRElement> mBRElement;
   1497  EditorDOMPointInText mPreformattedLineBreak;
   1498  Mode mMode = Mode::NotInitialized;
   1499 };
   1500 
   1501 /**
   1502 * Actually handle joining inclusive ancestor block elements.
   1503 */
   1504 class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler::
   1505    AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner final {
   1506 public:
   1507  AutoInclusiveAncestorBlockElementsJoiner() = delete;
   1508  AutoInclusiveAncestorBlockElementsJoiner(
   1509      nsIContent& aInclusiveDescendantOfLeftBlockElement,
   1510      nsIContent& aInclusiveDescendantOfRightBlockElement)
   1511      : mInclusiveDescendantOfLeftBlockElement(
   1512            aInclusiveDescendantOfLeftBlockElement),
   1513        mInclusiveDescendantOfRightBlockElement(
   1514            aInclusiveDescendantOfRightBlockElement),
   1515        mCanJoinBlocks(false),
   1516        mFallbackToDeleteLeafContent(false) {}
   1517 
   1518  [[nodiscard]] bool IsSet() const {
   1519    return mLeftBlockElement && mRightBlockElement;
   1520  }
   1521  [[nodiscard]] bool IsSameBlockElement() const {
   1522    return mLeftBlockElement && mLeftBlockElement == mRightBlockElement;
   1523  }
   1524 
   1525  /**
   1526   * Prepare for joining inclusive ancestor block elements.  When this
   1527   * returns false, the deletion should be canceled.
   1528   */
   1529  [[nodiscard]] Result<bool, nsresult> Prepare(const HTMLEditor& aHTMLEditor,
   1530                                               const Element& aEditingHost);
   1531 
   1532  /**
   1533   * When this returns true, this can join the blocks with `Run()`.
   1534   */
   1535  [[nodiscard]] bool CanJoinBlocks() const { return mCanJoinBlocks; }
   1536 
   1537  /**
   1538   * When this returns true, `Run()` must return "ignored" so that
   1539   * caller can skip calling `Run()`.  This is available only when
   1540   * `CanJoinBlocks()` returns `true`.
   1541   * TODO: This should be merged into `CanJoinBlocks()` in the future.
   1542   */
   1543  [[nodiscard]] bool ShouldDeleteLeafContentInstead() const {
   1544    MOZ_ASSERT(CanJoinBlocks());
   1545    return mFallbackToDeleteLeafContent;
   1546  }
   1547 
   1548  /**
   1549   * ComputeRangesToDelete() extends aRangeToDelete includes the element
   1550   * boundaries between joining blocks.  If they won't be joined, this
   1551   * collapses the range to aCaretPoint.
   1552   */
   1553  [[nodiscard]] nsresult ComputeRangeToDelete(const HTMLEditor& aHTMLEditor,
   1554                                              const EditorDOMPoint& aCaretPoint,
   1555                                              nsRange& aRangeToDelete) const;
   1556 
   1557  /**
   1558   * Join inclusive ancestor block elements which are found by preceding
   1559   * Prepare() call.
   1560   * The right element is always joined to the left element.
   1561   * If the elements are the same type and not nested within each other,
   1562   * JoinEditableNodesWithTransaction() is called (example, joining two
   1563   * list items together into one).
   1564   * If the elements are not the same type, or one is a descendant of the
   1565   * other, we instead destroy the right block placing its children into
   1566   * left block.
   1567   */
   1568  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> Run(
   1569      HTMLEditor& aHTMLEditor, const Element& aEditingHost);
   1570 
   1571 private:
   1572  /**
   1573   * This method returns true when
   1574   * `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`,
   1575   * `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` and
   1576   * `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` handle it
   1577   * with the `if` block of the main lambda of them.
   1578   */
   1579  [[nodiscard]] bool CanMergeLeftAndRightBlockElements() const {
   1580    if (!IsSet()) {
   1581      return false;
   1582    }
   1583    // `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
   1584    if (mPointContainingTheOtherBlockElement.GetContainer() ==
   1585        mRightBlockElement) {
   1586      return mNewListElementTagNameOfRightListElement.isSome();
   1587    }
   1588    // `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
   1589    if (mPointContainingTheOtherBlockElement.GetContainer() ==
   1590        mLeftBlockElement) {
   1591      return mNewListElementTagNameOfRightListElement.isSome() &&
   1592             mRightBlockElement->GetChildCount();
   1593    }
   1594    MOZ_ASSERT(!mPointContainingTheOtherBlockElement.IsSet());
   1595    // `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()`
   1596    return mNewListElementTagNameOfRightListElement.isSome() ||
   1597           (mLeftBlockElement->NodeInfo()->NameAtom() ==
   1598                mRightBlockElement->NodeInfo()->NameAtom() &&
   1599            EditorUtils::GetComputedWhiteSpaceStyles(*mLeftBlockElement) ==
   1600                EditorUtils::GetComputedWhiteSpaceStyles(*mRightBlockElement));
   1601  }
   1602 
   1603  OwningNonNull<nsIContent> mInclusiveDescendantOfLeftBlockElement;
   1604  OwningNonNull<nsIContent> mInclusiveDescendantOfRightBlockElement;
   1605  RefPtr<Element> mLeftBlockElement;
   1606  RefPtr<Element> mRightBlockElement;
   1607  Maybe<nsAtom*> mNewListElementTagNameOfRightListElement;
   1608  EditorDOMPoint mPointContainingTheOtherBlockElement;
   1609  RefPtr<dom::HTMLBRElement> mPrecedingInvisibleBRElement;
   1610  bool mCanJoinBlocks;
   1611  bool mFallbackToDeleteLeafContent;
   1612 };
   1613 
   1614 /**
   1615 * Handle deleting empty block ancestors.
   1616 */
   1617 class MOZ_STACK_CLASS
   1618 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter final {
   1619 public:
   1620  /**
   1621   * ScanEmptyBlockInclusiveAncestor() scans an inclusive ancestor element
   1622   * which is empty and a block element.  Then, stores the result and
   1623   * returns the found empty block element.
   1624   *
   1625   * @param aHTMLEditor         The HTMLEditor.
   1626   * @param aStartContent       Start content to look for empty ancestors.
   1627   */
   1628  [[nodiscard]] Element* ScanEmptyBlockInclusiveAncestor(
   1629      const HTMLEditor& aHTMLEditor, nsIContent& aStartContent);
   1630 
   1631  /**
   1632   * ComputeTargetRanges() computes "target ranges" for deleting
   1633   * `mEmptyInclusiveAncestorBlockElement`.
   1634   */
   1635  nsresult ComputeTargetRanges(
   1636      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1637      const Element& aEditingHost,
   1638      AutoClonedSelectionRangeArray& aRangesToDelete) const;
   1639 
   1640  /**
   1641   * Deletes found empty block element by `ScanEmptyBlockInclusiveAncestor()`.
   1642   * If found one is a list item element, calls
   1643   * `MaybeInsertBRElementBeforeEmptyListItemElement()` before deleting
   1644   * the list item element.
   1645   * If found empty ancestor is not a list item element,
   1646   * `GetNewCaretPosition()` will be called to determine new caret position.
   1647   * Finally, removes the empty block ancestor.
   1648   *
   1649   * @param aHTMLEditor         The HTMLEditor.
   1650   * @param aDirectionAndAmount If found empty ancestor block is a list item
   1651   *                            element, this is ignored.  Otherwise:
   1652   *                            - If eNext, eNextWord or eToEndOfLine,
   1653   *                              collapse Selection to after found empty
   1654   *                              ancestor.
   1655   *                            - If ePrevious, ePreviousWord or
   1656   *                              eToBeginningOfLine, collapse Selection to
   1657   *                              end of previous editable node.
   1658   *                            - Otherwise, eNone is allowed but does
   1659   *                              nothing.
   1660   * @param aEditingHost        The editing host.
   1661   */
   1662  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> Run(
   1663      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1664      const Element& aEditingHost);
   1665 
   1666 private:
   1667  /**
   1668   * MaybeReplaceSubListWithNewListItem() replaces
   1669   * mEmptyInclusiveAncestorBlockElement with new list item element
   1670   * (containing <br>) if:
   1671   * - mEmptyInclusiveAncestorBlockElement is a list element
   1672   * - The parent of mEmptyInclusiveAncestorBlockElement is a list element
   1673   * - The parent becomes empty after deletion
   1674   * If this does not perform the replacement, returns "ignored".
   1675   */
   1676  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
   1677  MaybeReplaceSubListWithNewListItem(HTMLEditor& aHTMLEditor);
   1678 
   1679  /**
   1680   * MaybeInsertBRElementBeforeEmptyListItemElement() inserts a `<br>` element
   1681   * if `mEmptyInclusiveAncestorBlockElement` is a list item element which
   1682   * is first editable element in its parent, and its grand parent is not a
   1683   * list element, inserts a `<br>` element before the empty list item.
   1684   */
   1685  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateLineBreakResult, nsresult>
   1686  MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor& aHTMLEditor);
   1687 
   1688  /**
   1689   * GetNewCaretPosition() returns new caret position after deleting
   1690   * `mEmptyInclusiveAncestorBlockElement`.
   1691   */
   1692  [[nodiscard]] Result<CaretPoint, nsresult> GetNewCaretPosition(
   1693      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
   1694      const Element& aEditingHost) const;
   1695 
   1696  RefPtr<Element> mEmptyInclusiveAncestorBlockElement;
   1697 };
   1698 
   1699 /******************************************************************************
   1700 * NormalizedStringToInsertText stores normalized insertion string with
   1701 * normalized surrounding white-spaces if the insertion point is surrounded by
   1702 * collapsible white-spaces.  For deleting invisible (collapsed) white-spaces,
   1703 * this also stores the replace range and new white-space length before and
   1704 * after the inserting text.
   1705 ******************************************************************************/
   1706 
   1707 struct MOZ_STACK_CLASS HTMLEditor::NormalizedStringToInsertText final {
   1708  NormalizedStringToInsertText(
   1709      const nsAString& aStringToInsertWithoutSurroundingWhiteSpaces,
   1710      const EditorDOMPoint& aPointToInsert)
   1711      : mNormalizedString(aStringToInsertWithoutSurroundingWhiteSpaces),
   1712        mReplaceStartOffset(
   1713            aPointToInsert.IsInTextNode() ? aPointToInsert.Offset() : 0u),
   1714        mReplaceEndOffset(mReplaceStartOffset) {
   1715    MOZ_ASSERT(aStringToInsertWithoutSurroundingWhiteSpaces.Length() ==
   1716               InsertingTextLength());
   1717  }
   1718 
   1719  NormalizedStringToInsertText(
   1720      const nsAString& aStringToInsertWithSurroundingWhiteSpaces,
   1721      uint32_t aInsertOffset, uint32_t aReplaceStartOffset,
   1722      uint32_t aReplaceLength,
   1723      uint32_t aNewPrecedingWhiteSpaceLengthBeforeInsertionString,
   1724      uint32_t aNewFollowingWhiteSpaceLengthAfterInsertionString)
   1725      : mNormalizedString(aStringToInsertWithSurroundingWhiteSpaces),
   1726        mReplaceStartOffset(aReplaceStartOffset),
   1727        mReplaceEndOffset(mReplaceStartOffset + aReplaceLength),
   1728        mReplaceLengthBefore(aInsertOffset - mReplaceStartOffset),
   1729        mReplaceLengthAfter(aReplaceLength - mReplaceLengthBefore),
   1730        mNewLengthBefore(aNewPrecedingWhiteSpaceLengthBeforeInsertionString),
   1731        mNewLengthAfter(aNewFollowingWhiteSpaceLengthAfterInsertionString) {
   1732    MOZ_ASSERT(aReplaceStartOffset <= aInsertOffset);
   1733    MOZ_ASSERT(aReplaceStartOffset + aReplaceLength >= aInsertOffset);
   1734    MOZ_ASSERT(aNewPrecedingWhiteSpaceLengthBeforeInsertionString +
   1735                   aNewFollowingWhiteSpaceLengthAfterInsertionString <
   1736               mNormalizedString.Length());
   1737    MOZ_ASSERT(mReplaceLengthBefore + mReplaceLengthAfter == ReplaceLength());
   1738    MOZ_ASSERT(mReplaceLengthBefore >= mNewLengthBefore);
   1739    MOZ_ASSERT(mReplaceLengthAfter >= mNewLengthAfter);
   1740  }
   1741 
   1742  NormalizedStringToInsertText GetMinimizedData(const Text& aText) const {
   1743    if (mNormalizedString.IsEmpty() || !ReplaceLength()) {
   1744      return *this;
   1745    }
   1746    const dom::CharacterDataBuffer& characterDataBuffer = aText.DataBuffer();
   1747    const uint32_t minimizedReplaceStart = [&]() {
   1748      const auto firstDiffCharOffset =
   1749          mNewLengthBefore ? characterDataBuffer.FindFirstDifferentCharOffset(
   1750                                 PrecedingWhiteSpaces(), mReplaceStartOffset)
   1751                           : dom::CharacterDataBuffer::kNotFound;
   1752      if (firstDiffCharOffset == dom::CharacterDataBuffer::kNotFound) {
   1753        return
   1754            // We don't need to insert new normalized white-spaces before the
   1755            // inserting string,
   1756            (mReplaceStartOffset + mReplaceLengthBefore)
   1757            // but keep extending the replacing range for deleting invisible
   1758            // white-spaces.
   1759            - DeletingPrecedingInvisibleWhiteSpaces();
   1760      }
   1761      return firstDiffCharOffset;
   1762    }();
   1763    const uint32_t minimizedReplaceEnd = [&]() {
   1764      const auto lastDiffCharOffset =
   1765          mNewLengthAfter ? characterDataBuffer.RFindFirstDifferentCharOffset(
   1766                                FollowingWhiteSpaces(), mReplaceEndOffset)
   1767                          : dom::CharacterDataBuffer::kNotFound;
   1768      if (lastDiffCharOffset == dom::CharacterDataBuffer::kNotFound) {
   1769        return
   1770            // We don't need to insert new normalized white-spaces after the
   1771            // inserting string,
   1772            (mReplaceEndOffset - mReplaceLengthAfter)
   1773            // but keep extending the replacing range for deleting invisible
   1774            // white-spaces.
   1775            + DeletingFollowingInvisibleWhiteSpaces();
   1776      }
   1777      return lastDiffCharOffset + 1u;
   1778    }();
   1779    if (minimizedReplaceStart == mReplaceStartOffset &&
   1780        minimizedReplaceEnd == mReplaceEndOffset) {
   1781      return *this;
   1782    }
   1783    const uint32_t newPrecedingWhiteSpaceLength =
   1784        mNewLengthBefore - (minimizedReplaceStart - mReplaceStartOffset);
   1785    const uint32_t newFollowingWhiteSpaceLength =
   1786        mNewLengthAfter - (mReplaceEndOffset - minimizedReplaceEnd);
   1787    return NormalizedStringToInsertText(
   1788        Substring(mNormalizedString,
   1789                  mNewLengthBefore - newPrecedingWhiteSpaceLength,
   1790                  mNormalizedString.Length() -
   1791                      (mNewLengthBefore - newPrecedingWhiteSpaceLength) -
   1792                      (mNewLengthAfter - newFollowingWhiteSpaceLength)),
   1793        OffsetToInsertText(), minimizedReplaceStart,
   1794        minimizedReplaceEnd - minimizedReplaceStart,
   1795        newPrecedingWhiteSpaceLength, newFollowingWhiteSpaceLength);
   1796  }
   1797 
   1798  /**
   1799   * Return offset to insert the given text.
   1800   */
   1801  [[nodiscard]] uint32_t OffsetToInsertText() const {
   1802    return mReplaceStartOffset + mReplaceLengthBefore;
   1803  }
   1804 
   1805  /**
   1806   * Return inserting text length not containing the surrounding white-spaces.
   1807   */
   1808  [[nodiscard]] uint32_t InsertingTextLength() const {
   1809    return mNormalizedString.Length() - mNewLengthBefore - mNewLengthAfter;
   1810  }
   1811 
   1812  /**
   1813   * Return end offset of inserted string after replacing the text with
   1814   * mNormalizedString.
   1815   */
   1816  [[nodiscard]] uint32_t EndOffsetOfInsertedText() const {
   1817    return OffsetToInsertText() + InsertingTextLength();
   1818  }
   1819 
   1820  /**
   1821   * Return the length to replace with mNormalizedString.  The result means that
   1822   * it's the length of surrounding white-spaces at the insertion point.
   1823   */
   1824  [[nodiscard]] uint32_t ReplaceLength() const {
   1825    return mReplaceEndOffset - mReplaceStartOffset;
   1826  }
   1827 
   1828  [[nodiscard]] uint32_t DeletingPrecedingInvisibleWhiteSpaces() const {
   1829    return mReplaceLengthBefore - mNewLengthBefore;
   1830  }
   1831  [[nodiscard]] uint32_t DeletingFollowingInvisibleWhiteSpaces() const {
   1832    return mReplaceLengthAfter - mNewLengthAfter;
   1833  }
   1834 
   1835  [[nodiscard]] nsDependentSubstring PrecedingWhiteSpaces() const {
   1836    return Substring(mNormalizedString, 0u, mNewLengthBefore);
   1837  }
   1838  [[nodiscard]] nsDependentSubstring FollowingWhiteSpaces() const {
   1839    return Substring(mNormalizedString,
   1840                     mNormalizedString.Length() - mNewLengthAfter);
   1841  }
   1842 
   1843  // Normalizes string which should be inserted.
   1844  nsAutoString mNormalizedString;
   1845  // Start offset in the `Text` to replace.
   1846  const uint32_t mReplaceStartOffset;
   1847  // End offset in the `Text` to replace.
   1848  const uint32_t mReplaceEndOffset;
   1849  // If it needs to replace preceding and/or following white-spaces, these
   1850  // members store the length of white-spaces which should be replaced
   1851  // before/after the insertion point.
   1852  const uint32_t mReplaceLengthBefore = 0u;
   1853  const uint32_t mReplaceLengthAfter = 0u;
   1854  // If it needs to replace preceding and/or following white-spaces, these
   1855  // members store the new length of white-spaces before/after the insertion
   1856  // string.
   1857  const uint32_t mNewLengthBefore = 0u;
   1858  const uint32_t mNewLengthAfter = 0u;
   1859 };
   1860 
   1861 /******************************************************************************
   1862 * ReplaceWhiteSpacesData stores normalized string to replace white-spaces in
   1863 * a `Text`.  If ReplaceLength() returns 0, this user needs to do nothing.
   1864 ******************************************************************************/
   1865 
   1866 struct MOZ_STACK_CLASS HTMLEditor::ReplaceWhiteSpacesData final {
   1867  ReplaceWhiteSpacesData() = default;
   1868 
   1869  /**
   1870   * @param aWhiteSpaces        The new white-spaces which we will replace the
   1871   *                            range with.
   1872   * @param aStartOffset        Replace start offset in the text node.
   1873   * @param aReplaceLength      Replace length in the text node.
   1874   * @param aOffsetAfterReplacing
   1875   *                            [optional] If the caller may want to put caret
   1876   *                            middle of the white-spaces, the offset may be
   1877   *                            changed by deleting some invisible white-spaces.
   1878   *                            Therefore, this may be set for the purpose.
   1879   */
   1880  ReplaceWhiteSpacesData(const nsAString& aWhiteSpaces, uint32_t aStartOffset,
   1881                         uint32_t aReplaceLength,
   1882                         uint32_t aOffsetAfterReplacing = UINT32_MAX)
   1883      : mNormalizedString(aWhiteSpaces),
   1884        mReplaceStartOffset(aStartOffset),
   1885        mReplaceEndOffset(aStartOffset + aReplaceLength),
   1886        mNewOffsetAfterReplace(aOffsetAfterReplacing) {
   1887    MOZ_ASSERT(ReplaceLength() >= mNormalizedString.Length());
   1888    MOZ_ASSERT_IF(mNewOffsetAfterReplace != UINT32_MAX,
   1889                  mNewOffsetAfterReplace <=
   1890                      mReplaceStartOffset + mNormalizedString.Length());
   1891  }
   1892 
   1893  /**
   1894   * @param aWhiteSpaces        The new white-spaces which we will replace the
   1895   *                            range with.
   1896   * @param aStartOffset        Replace start offset in the text node.
   1897   * @param aReplaceLength      Replace length in the text node.
   1898   * @param aOffsetAfterReplacing
   1899   *                            [optional] If the caller may want to put caret
   1900   *                            middle of the white-spaces, the offset may be
   1901   *                            changed by deleting some invisible white-spaces.
   1902   *                            Therefore, this may be set for the purpose.
   1903   */
   1904  ReplaceWhiteSpacesData(nsAutoString&& aWhiteSpaces, uint32_t aStartOffset,
   1905                         uint32_t aReplaceLength,
   1906                         uint32_t aOffsetAfterReplacing = UINT32_MAX)
   1907      : mNormalizedString(std::forward<nsAutoString>(aWhiteSpaces)),
   1908        mReplaceStartOffset(aStartOffset),
   1909        mReplaceEndOffset(aStartOffset + aReplaceLength),
   1910        mNewOffsetAfterReplace(aOffsetAfterReplacing) {
   1911    MOZ_ASSERT(ReplaceLength() >= mNormalizedString.Length());
   1912    MOZ_ASSERT_IF(mNewOffsetAfterReplace != UINT32_MAX,
   1913                  mNewOffsetAfterReplace <=
   1914                      mReplaceStartOffset + mNormalizedString.Length());
   1915  }
   1916 
   1917  ReplaceWhiteSpacesData GetMinimizedData(const Text& aText) const {
   1918    if (!ReplaceLength()) {
   1919      return *this;
   1920    }
   1921    const dom::CharacterDataBuffer& characterDataBuffer = aText.DataBuffer();
   1922    const auto minimizedReplaceStart = [&]() -> uint32_t {
   1923      if (mNormalizedString.IsEmpty()) {
   1924        return mReplaceStartOffset;
   1925      }
   1926      const uint32_t firstDiffCharOffset =
   1927          characterDataBuffer.FindFirstDifferentCharOffset(mNormalizedString,
   1928                                                           mReplaceStartOffset);
   1929      if (firstDiffCharOffset == dom::CharacterDataBuffer::kNotFound) {
   1930        // We don't need to insert new white-spaces,
   1931        return mReplaceStartOffset + mNormalizedString.Length();
   1932      }
   1933      return firstDiffCharOffset;
   1934    }();
   1935    const auto minimizedReplaceEnd = [&]() -> uint32_t {
   1936      if (mNormalizedString.IsEmpty()) {
   1937        return mReplaceEndOffset;
   1938      }
   1939      if (minimizedReplaceStart ==
   1940          mReplaceStartOffset + mNormalizedString.Length()) {
   1941        // Note that here may be invisible white-spaces before
   1942        // mReplaceEndOffset.  Then, this value may be larger than
   1943        // minimizedReplaceStart.
   1944        MOZ_ASSERT(mReplaceEndOffset >= minimizedReplaceStart);
   1945        return mReplaceEndOffset;
   1946      }
   1947      if (ReplaceLength() != mNormalizedString.Length()) {
   1948        // If we're deleting some invisible white-spaces, don't shrink the end
   1949        // of the replacing range because it may shrink mNormalizedString too
   1950        // much.
   1951        return mReplaceEndOffset;
   1952      }
   1953      const auto lastDiffCharOffset =
   1954          characterDataBuffer.RFindFirstDifferentCharOffset(mNormalizedString,
   1955                                                            mReplaceEndOffset);
   1956      MOZ_ASSERT(lastDiffCharOffset != dom::CharacterDataBuffer::kNotFound);
   1957      return lastDiffCharOffset == dom::CharacterDataBuffer::kNotFound
   1958                 ? mReplaceEndOffset
   1959                 : lastDiffCharOffset + 1u;
   1960    }();
   1961    if (minimizedReplaceStart == mReplaceStartOffset &&
   1962        minimizedReplaceEnd == mReplaceEndOffset) {
   1963      return *this;
   1964    }
   1965    const uint32_t precedingUnnecessaryLength =
   1966        minimizedReplaceStart - mReplaceStartOffset;
   1967    const uint32_t followingUnnecessaryLength =
   1968        mReplaceEndOffset - minimizedReplaceEnd;
   1969    return ReplaceWhiteSpacesData(
   1970        Substring(mNormalizedString, precedingUnnecessaryLength,
   1971                  mNormalizedString.Length() - (precedingUnnecessaryLength +
   1972                                                followingUnnecessaryLength)),
   1973        minimizedReplaceStart, minimizedReplaceEnd - minimizedReplaceStart,
   1974        mNewOffsetAfterReplace);
   1975  }
   1976 
   1977  /**
   1978   * Return the normalized string before mNewOffsetAfterReplace.  So,
   1979   * mNewOffsetAfterReplace must not be UINT32_MAX and in the replaced range
   1980   * when this is called.
   1981   *
   1982   * @param aReplaceEndOffset   Specify the offset in the Text node of
   1983   *                            mNewOffsetAfterReplace before replacing with the
   1984   *                            data.
   1985   * @return The substring before mNewOffsetAfterReplace which is typically set
   1986   * for new caret position in the Text node or collapsed deleting range
   1987   * surrounded by the white-spaces.
   1988   */
   1989  [[nodiscard]] ReplaceWhiteSpacesData PreviousDataOfNewOffset(
   1990      uint32_t aReplaceEndOffset) const {
   1991    MOZ_ASSERT(mNewOffsetAfterReplace != UINT32_MAX);
   1992    MOZ_ASSERT(mReplaceStartOffset <= mNewOffsetAfterReplace);
   1993    MOZ_ASSERT(mReplaceEndOffset >= mNewOffsetAfterReplace);
   1994    MOZ_ASSERT(mReplaceStartOffset <= aReplaceEndOffset);
   1995    MOZ_ASSERT(mReplaceEndOffset >= aReplaceEndOffset);
   1996    if (!ReplaceLength() || aReplaceEndOffset == mReplaceStartOffset) {
   1997      return ReplaceWhiteSpacesData();
   1998    }
   1999    return ReplaceWhiteSpacesData(
   2000        Substring(mNormalizedString, 0u,
   2001                  mNewOffsetAfterReplace - mReplaceStartOffset),
   2002        mReplaceStartOffset, aReplaceEndOffset - mReplaceStartOffset);
   2003  }
   2004 
   2005  /**
   2006   * Return the normalized string after mNewOffsetAfterReplace.  So,
   2007   * mNewOffsetAfterReplace must not be UINT32_MAX and in the replaced range
   2008   * when this is called.
   2009   *
   2010   * @param aReplaceStartOffset Specify the replace start offset with the
   2011   *                            normalized white-spaces.
   2012   * @return The substring after mNewOffsetAfterReplace which is typically set
   2013   * for new caret position in the Text node or collapsed deleting range
   2014   * surrounded by the white-spaces.
   2015   */
   2016  [[nodiscard]] ReplaceWhiteSpacesData NextDataOfNewOffset(
   2017      uint32_t aReplaceStartOffset) const {
   2018    MOZ_ASSERT(mNewOffsetAfterReplace != UINT32_MAX);
   2019    MOZ_ASSERT(mReplaceStartOffset <= mNewOffsetAfterReplace);
   2020    MOZ_ASSERT(mReplaceEndOffset >= mNewOffsetAfterReplace);
   2021    MOZ_ASSERT(mReplaceStartOffset <= aReplaceStartOffset);
   2022    MOZ_ASSERT(mReplaceEndOffset >= aReplaceStartOffset);
   2023    if (!ReplaceLength() || aReplaceStartOffset == mReplaceEndOffset) {
   2024      return ReplaceWhiteSpacesData();
   2025    }
   2026    return ReplaceWhiteSpacesData(
   2027        Substring(mNormalizedString,
   2028                  mNewOffsetAfterReplace - mReplaceStartOffset),
   2029        aReplaceStartOffset, mReplaceEndOffset - aReplaceStartOffset);
   2030  }
   2031 
   2032  [[nodiscard]] uint32_t ReplaceLength() const {
   2033    return mReplaceEndOffset - mReplaceStartOffset;
   2034  }
   2035  [[nodiscard]] uint32_t DeletingInvisibleWhiteSpaces() const {
   2036    return ReplaceLength() - mNormalizedString.Length();
   2037  }
   2038 
   2039  [[nodiscard]] ReplaceWhiteSpacesData operator+(
   2040      const ReplaceWhiteSpacesData& aOther) const {
   2041    if (!ReplaceLength()) {
   2042      return aOther;
   2043    }
   2044    if (!aOther.ReplaceLength()) {
   2045      return *this;
   2046    }
   2047    MOZ_ASSERT(mReplaceEndOffset == aOther.mReplaceStartOffset);
   2048    MOZ_ASSERT_IF(
   2049        aOther.mNewOffsetAfterReplace != UINT32_MAX,
   2050        aOther.mNewOffsetAfterReplace >= DeletingInvisibleWhiteSpaces());
   2051    return ReplaceWhiteSpacesData(
   2052        nsAutoString(mNormalizedString + aOther.mNormalizedString),
   2053        mReplaceStartOffset, aOther.mReplaceEndOffset,
   2054        aOther.mNewOffsetAfterReplace != UINT32_MAX
   2055            ? aOther.mNewOffsetAfterReplace - DeletingInvisibleWhiteSpaces()
   2056            : mNewOffsetAfterReplace);
   2057  }
   2058 
   2059  nsAutoString mNormalizedString;
   2060  const uint32_t mReplaceStartOffset = 0u;
   2061  const uint32_t mReplaceEndOffset = 0u;
   2062  // If the caller specifies a point in a white-space sequence, some invisible
   2063  // white-spaces will be deleted with replacing them with normalized string.
   2064  // Then, they may want to keep the position for putting caret or something.
   2065  // So, this may store a specific offset in the text node after replacing.
   2066  const uint32_t mNewOffsetAfterReplace = UINT32_MAX;
   2067 };
   2068 
   2069 /******************************************************************************
   2070 * A runnable to run HTMLEditor::OnModifiedDocument when it's safe.
   2071 ******************************************************************************/
   2072 
   2073 class HTMLEditor::DocumentModifiedEvent final : public Runnable {
   2074 public:
   2075  explicit DocumentModifiedEvent(HTMLEditor& aHTMLEditor)
   2076      : Runnable("DocumentModifiedEvent"), mHTMLEditor(aHTMLEditor) {}
   2077 
   2078  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() {
   2079    (void)MOZ_KnownLive(mHTMLEditor)->OnModifyDocument(*this);
   2080    return NS_OK;
   2081  }
   2082 
   2083  const nsTArray<EditorDOMPointInText>& NewInvisibleWhiteSpacesRef() const {
   2084    return mNewInvisibleWhiteSpaces;
   2085  }
   2086 
   2087 private:
   2088  ~DocumentModifiedEvent() = default;
   2089 
   2090  const OwningNonNull<HTMLEditor> mHTMLEditor;
   2091  nsTArray<EditorDOMPointInText> mNewInvisibleWhiteSpaces;
   2092 };
   2093 
   2094 }  // namespace mozilla
   2095 
   2096 #endif  // #ifndef HTMLEditorNestedClasses_h