Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/editor/libeditor/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 92 kB image not shown  

Quellcode-Bibliothek HTMLEditorNestedClasses.h

  Sprache: C
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#ifndef HTMLEditorNestedClasses_h
#define HTMLEditorNestedClasses_h

#include "EditorDOMPoint.h"
#include "EditorForwards.h"
#include "HTMLEditor.h"       // for HTMLEditor
#include "HTMLEditHelpers.h"  // for EditorInlineStyleAndValue
#include "HTMLEditUtils.h"    // for HTMLEditUtils::IsContainerNode

#include "mozilla/Attributes.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/Result.h"
#include "mozilla/dom/CharacterDataBuffer.h"
#include "mozilla/dom/Text.h"

namespace mozilla {

struct LimitersAndCaretData;  // Declared in nsFrameSelection.h
namespace dom {
class HTMLBRElement;
};

/*****************************************************************************
 * AutoInlineStyleSetter is a temporary class to set an inline style to
 * specific nodes.
 ****************************************************************************/


class MOZ_STACK_CLASS HTMLEditor::AutoInlineStyleSetter final
    : private EditorInlineStyleAndValue {
  using Element = dom::Element;
  using Text = dom::Text;

 public:
  explicit AutoInlineStyleSetter(
      const EditorInlineStyleAndValue& aStyleAndValue)
      : EditorInlineStyleAndValue(aStyleAndValue) {}

  void Reset() {
    mFirstHandledPoint.Clear();
    mLastHandledPoint.Clear();
  }

  const EditorDOMPoint& FirstHandledPointRef() const {
    return mFirstHandledPoint;
  }
  const EditorDOMPoint& LastHandledPointRef() const {
    return mLastHandledPoint;
  }

  /**
   * Split aText at aStartOffset and aEndOffset (except when they are start or
   * end of its data) and wrap the middle text node in an element to apply the
   * style.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitRangeOffFromNodeResult, nsresult>
  SplitTextNodeAndApplyStyleToMiddleNode(HTMLEditor& aHTMLEditor, Text& aText,
                                         uint32_t aStartOffset,
                                         uint32_t aEndOffset);

  /**
   * Remove same style from children and apply the style entire (except
   * non-editable nodes) aContent.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
  ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(HTMLEditor& aHTMLEditor,
                                                     nsIContent& aContent);

  /**
   * Invert the style with creating new element or something.  This should
   * be called only when IsInvertibleWithCSS() returns true.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  InvertStyleIfApplied(HTMLEditor& aHTMLEditor, Element& aElement);
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitRangeOffFromNodeResult, nsresult>
  InvertStyleIfApplied(HTMLEditor& aHTMLEditor, Text& aTextNode,
                       uint32_t aStartOffset, uint32_t aEndOffset);

  /**
   * Extend or shrink aRange for applying the style to the range.
   * See comments in the definition what this does.
   */

  Result<EditorRawDOMRange, nsresult> ExtendOrShrinkRangeToApplyTheStyle(
      const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
      const Element& aEditingHost) const;

  /**
   * Returns next/previous sibling of aContent or an ancestor of it if it's
   * editable and does not cross block boundary.
   */

  [[nodiscard]] static nsIContent* GetNextEditableInlineContent(
      const nsIContent& aContent, const nsINode* aLimiter = nullptr);
  [[nodiscard]] static nsIContent* GetPreviousEditableInlineContent(
      const nsIContent& aContent, const nsINode* aLimiter = nullptr);

  /**
   * GetEmptyTextNodeToApplyNewStyle creates new empty text node to insert
   * a new element which will contain newly inserted text or returns existing
   * empty text node if aCandidatePointToInsert is around it.
   *
   * NOTE: Unfortunately, editor does not want to insert text into empty inline
   * element in some places (e.g., automatically adjusting caret position to
   * nearest text node).  Therefore, we need to create new empty text node to
   * prepare new styles for inserting text.  This method is designed for the
   * preparation.
   *
   * @param aHTMLEditor                 The editor.
   * @param aCandidatePointToInsert     The point where the caller wants to
   *                                    insert new text.
   * @return            If this creates new empty text node returns it.
   *                    If this couldn't create new empty text node due to
   *                    the point or aEditingHost cannot have text node,
   *                    returns nullptr.
   *                    Otherwise, returns error.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<RefPtr<Text>, nsresult>
  GetEmptyTextNodeToApplyNewStyle(
      HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCandidatePointToInsert);

 private:
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> ApplyStyle(
      HTMLEditor& aHTMLEditor, nsIContent& aContent);

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
  ApplyCSSTextDecoration(HTMLEditor& aHTMLEditor, nsIContent& aContent);

  /**
   * Returns true if aStyledElement is a good element to set `style` attribute.
   */

  [[nodiscard]] bool ElementIsGoodContainerToSetStyle(
      nsStyledElement& aStyledElement) const;

  /**
   * ElementIsGoodContainerForTheStyle() returns true if aElement is a
   * good container for applying the style to a node.  I.e., if this returns
   * true, moving nodes into aElement is enough to apply the style to them.
   * Otherwise, you need to create new element for the style.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<bool, nsresult>
  ElementIsGoodContainerForTheStyle(HTMLEditor& aHTMLEditor,
                                    Element& aElement) const;

  /**
   * Return true if the node is an element node and it represents the style or
   * sets the style (including when setting different value) with `style`
   * attribute.
   */

  [[nodiscard]] bool ContentIsElementSettingTheStyle(
      const HTMLEditor& aHTMLEditor, nsIContent& aContent) const;

  /**
   * Helper methods to shrink range to apply the style.
   */

  [[nodiscard]] EditorRawDOMPoint GetShrunkenRangeStart(
      const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
      const nsINode& aCommonAncestorOfRange,
      const nsIContent* aFirstEntirelySelectedContentNodeInRange) const;
  [[nodiscard]] EditorRawDOMPoint GetShrunkenRangeEnd(
      const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
      const nsINode& aCommonAncestorOfRange,
      const nsIContent* aLastEntirelySelectedContentNodeInRange) const;

  /**
   * Helper methods to extend the range to apply the style.
   */

  [[nodiscard]] EditorRawDOMPoint
  GetExtendedRangeStartToWrapAncestorApplyingSameStyle(
      const HTMLEditor& aHTMLEditor,
      const EditorRawDOMPoint& aStartPoint) const;
  [[nodiscard]] EditorRawDOMPoint
  GetExtendedRangeEndToWrapAncestorApplyingSameStyle(
      const HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aEndPoint) const;
  [[nodiscard]] EditorRawDOMRange
  GetExtendedRangeToMinimizeTheNumberOfNewElements(
      const HTMLEditor& aHTMLEditor, const nsINode& aCommonAncestor,
      EditorRawDOMPoint&& aStartPoint, EditorRawDOMPoint&& aEndPoint) const;

  /**
   * OnHandled() are called when this class creates new element to apply the
   * style, applies new style to existing element or ignores to apply the style
   * due to already set.
   */

  void OnHandled(const EditorDOMPoint& aStartPoint,
                 const EditorDOMPoint& aEndPoint) {
    if (!mFirstHandledPoint.IsSet()) {
      mFirstHandledPoint = aStartPoint;
    }
    mLastHandledPoint = aEndPoint;
  }
  void OnHandled(nsIContent& aContent) {
    if (aContent.IsElement() && !HTMLEditUtils::IsContainerNode(aContent)) {
      if (!mFirstHandledPoint.IsSet()) {
        mFirstHandledPoint.Set(&aContent);
      }
      mLastHandledPoint.SetAfter(&aContent);
      return;
    }
    if (!mFirstHandledPoint.IsSet()) {
      mFirstHandledPoint.Set(&aContent, 0u);
    }
    mLastHandledPoint = EditorDOMPoint::AtEndOf(aContent);
  }

  // mFirstHandledPoint and mLastHandledPoint store the first and last points
  // which are newly created or apply the new style, or just ignored at trying
  // to split a text node.
  EditorDOMPoint mFirstHandledPoint;
  EditorDOMPoint mLastHandledPoint;
};

/**
 * AutoMoveOneLineHandler moves the content in a line (between line breaks/block
 * boundaries) to specific point or end of a container element.
 */

class MOZ_STACK_CLASS HTMLEditor::AutoMoveOneLineHandler final {
 public:
  /**
   * Use this constructor when you want a line to move specific point.
   */

  explicit AutoMoveOneLineHandler(const EditorDOMPoint& aPointToInsert)
      : mPointToInsert(aPointToInsert),
        mMoveToEndOfContainer(MoveToEndOfContainer::No) {
    MOZ_ASSERT(mPointToInsert.IsSetAndValid());
    MOZ_ASSERT(mPointToInsert.IsInContentNode());
  }
  /**
   * Use this constructor when you want a line to move end of
   * aNewContainerElement.
   */

  explicit AutoMoveOneLineHandler(Element& aNewContainerElement)
      : mPointToInsert(&aNewContainerElement, 0),
        mMoveToEndOfContainer(MoveToEndOfContainer::Yes) {
    MOZ_ASSERT(mPointToInsert.IsSetAndValid());
  }

  /**
   * Must be called before calling Run().
   *
   * @param aHTMLEditor         The HTML editor.
   * @param aPointInHardLine    A point in a line which you want to move.
   * @param aEditingHost        The editing host.
   */

  [[nodiscard]] nsresult Prepare(HTMLEditor& aHTMLEditor,
                                 const EditorDOMPoint& aPointInHardLine,
                                 const Element& aEditingHost);
  /**
   * Must be called if Prepare() returned NS_OK.
   *
   * @param aHTMLEditor         The HTML editor.
   * @param aEditingHost        The editing host.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<MoveNodeResult, nsresult> Run(
      HTMLEditor& aHTMLEditor, const Element& aEditingHost);

  /**
   * Returns true if there are some content nodes which can be moved to another
   * place or deleted in the line containing aPointInHardLine.  Note that if
   * there is only a padding <br> element in an empty block element, this
   * returns false even though it may be deleted.
   */

  static Result<bool, nsresult> CanMoveOrDeleteSomethingInLine(
      const EditorDOMPoint& aPointInHardLine, const Element& aEditingHost);

  AutoMoveOneLineHandler(const AutoMoveOneLineHandler& aOther) = delete;
  AutoMoveOneLineHandler(AutoMoveOneLineHandler&& aOther) = delete;

 private:
  [[nodiscard]] bool ForceMoveToEndOfContainer() const {
    return mMoveToEndOfContainer == MoveToEndOfContainer::Yes;
  }
  [[nodiscard]] EditorDOMPoint& NextInsertionPointRef() {
    if (ForceMoveToEndOfContainer()) {
      mPointToInsert.SetToEndOf(mPointToInsert.GetContainer());
    }
    return mPointToInsert;
  }

  /**
   * Consider whether Run() should preserve or does not preserve white-space
   * style of moving content.
   *
   * @param aContentInLine      Specify a content node in the moving line.
   *                            Typically, container of aPointInHardLine of
   *                            Prepare().
   * @param aInclusiveAncestorBlockOfInsertionPoint
   *                            Inclusive ancestor block element of insertion
   *                            point.  Typically, computed
   *                            mDestInclusiveAncestorBlock.
   */

  [[nodiscard]] static PreserveWhiteSpaceStyle
  ConsiderWhetherPreserveWhiteSpaceStyle(
      const nsIContent* aContentInLine,
      const Element* aInclusiveAncestorBlockOfInsertionPoint);

  /**
   * Look for inclusive ancestor block element of aBlockElement and a descendant
   * of aAncestorElement.  If aBlockElement and aAncestorElement are same one,
   * this returns nullptr.
   *
   * @param aBlockElement       A block element which is a descendant of
   *                            aAncestorElement.
   * @param aAncestorElement    An inclusive ancestor block element of
   *                            aBlockElement.
   */

  [[nodiscard]] static Element*
  GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement(
      Element& aBlockElement, const Element& aAncestorElement);

  /**
   * Split ancestors at the line range boundaries and collect array of contents
   * in the line to aOutArrayOfContents.  Specify aNewContainer to the container
   * of insertion point to avoid splitting the destination.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
  SplitToMakeTheLineIsolated(
      HTMLEditor& aHTMLEditor, const nsIContent& aNewContainer,
      const Element& aEditingHost,
      nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) const;

  /**
   * Delete unnecessary trailing line break in aMovedContentRange if there is.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  DeleteUnnecessaryTrailingLineBreakInMovedLineEnd(
      HTMLEditor& aHTMLEditor, const EditorDOMRange& aMovedContentRange,
      const Element& aEditingHost) const;

  // Range of selected line.
  EditorDOMRange mLineRange;
  // Next insertion point.  If mMoveToEndOfContainer is `Yes`, this is
  // recomputed with its container in NextInsertionPointRef.  Therefore, this
  // should not be referred directly.
  EditorDOMPoint mPointToInsert;
  // An inclusive ancestor block element of the moving line.
  RefPtr<Element> mSrcInclusiveAncestorBlock;
  // An inclusive ancestor block element of the insertion point.
  RefPtr<Element> mDestInclusiveAncestorBlock;
  // nullptr if mMovingToParentBlock is false.
  // Must be non-nullptr if mMovingToParentBlock is true.  The topmost ancestor
  // block element which contains mSrcInclusiveAncestorBlock and a descendant of
  // mDestInclusiveAncestorBlock.  I.e., this may be same as
  // mSrcInclusiveAncestorBlock, but never same as mDestInclusiveAncestorBlock.
  RefPtr<Element> mTopmostSrcAncestorBlockInDestBlock;
  enum class MoveToEndOfContainer { No, Yes };
  MoveToEndOfContainer mMoveToEndOfContainer;
  PreserveWhiteSpaceStyle mPreserveWhiteSpaceStyle =
      PreserveWhiteSpaceStyle::No;
  // true if mDestInclusiveAncestorBlock is an ancestor of
  // mSrcInclusiveAncestorBlock.
  bool mMovingToParentBlock = false;
};

/**
 * Convert contents around aRanges of Run() to specified list element.  If there
 * are some different type of list elements, this method converts them to
 * specified list items too.  Basically, each line will be wrapped in a list
 * item element.  However, only when <p> element is selected, its child <br>
 * elements won't be treated as line separators.  Perhaps, this is a bug.
 */

class MOZ_STACK_CLASS HTMLEditor::AutoListElementCreator final {
 public:
  /**
   * @param aListElementTagName         The new list element tag name.
   * @param aListItemElementTagName     The new list item element tag name.
   * @param aBulletType                 If this is not empty string, it's set
   *                                    to `type` attribute of new list item
   *                                    elements.  Otherwise, existing `type`
   *                                    attributes will be removed.
   */

  AutoListElementCreator(const nsStaticAtom& aListElementTagName,
                         const nsStaticAtom& aListItemElementTagName,
                         const nsAString& aBulletType)
      // Needs const_cast hack here because the struct users may want
      // non-const nsStaticAtom pointer due to bug 1794954
      : mListTagName(const_cast<nsStaticAtom&>(aListElementTagName)),
        mListItemTagName(const_cast<nsStaticAtom&>(aListItemElementTagName)),
        mBulletType(aBulletType) {
    MOZ_ASSERT(&mListTagName == nsGkAtoms::ul ||
               &mListTagName == nsGkAtoms::ol ||
               &mListTagName == nsGkAtoms::dl);
    MOZ_ASSERT_IF(
        &mListTagName == nsGkAtoms::ul || &mListTagName == nsGkAtoms::ol,
        &mListItemTagName == nsGkAtoms::li);
    MOZ_ASSERT_IF(&mListTagName == nsGkAtoms::dl,
                  &mListItemTagName == nsGkAtoms::dt ||
                      &mListItemTagName == nsGkAtoms::dd);
  }

  /**
   * @param aHTMLEditor The HTML editor.
   * @param aRanges     [in/out] The ranges which will be converted to list.
   *                    The instance must not have saved ranges because it'll
   *                    be used in this method.
   *                    If succeeded, this will have selection ranges which
   *                    should be applied to `Selection`.
   *                    If failed, this keeps storing original selection
   *                    ranges.
   * @param aSelectAllOfCurrentList     Yes if this should treat all of
   *                                    ancestor list element at selection.
   * @param aEditingHost                The editing host.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
      HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRanges,
      HTMLEditor::SelectAllOfCurrentList aSelectAllOfCurrentList,
      const Element& aEditingHost) const;

 private:
  using ContentNodeArray = nsTArray<OwningNonNull<nsIContent>>;
  using AutoContentNodeArray = AutoTArray<OwningNonNull<nsIContent>, 64>;

  /**
   * If aSelectAllOfCurrentList is "Yes" and aRanges is in a list element,
   * returns the list element.
   * Otherwise, extend aRanges to select start and end lines selected by it and
   * correct all topmost content nodes in the extended ranges with splitting
   * ancestors at range edges.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList(
      HTMLEditor& aHTMLEditor, AutoClonedRangeArray& aRanges,
      SelectAllOfCurrentList aSelectAllOfCurrentList,
      const Element& aEditingHost, ContentNodeArray& aOutArrayOfContents) const;

  /**
   * Return true if aArrayOfContents has only <br> elements or empty inline
   * container elements.  I.e., it means that aArrayOfContents represents
   * only empty line(s) if this returns true.
   */

  [[nodiscard]] static bool
  IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements(
      const ContentNodeArray& aArrayOfContents);

  /**
   * Delete all content nodes ina ArrayOfContents, and if we can put new list
   * element at start of the first range of aRanges, insert new list element
   * there.
   *
   * @return            The empty list item element in new list element.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<RefPtr<Element>, nsresult>
  ReplaceContentNodesWithEmptyNewList(
      HTMLEditor& aHTMLEditor, const AutoClonedRangeArray& aRanges,
      const AutoContentNodeArray& aArrayOfContents,
      const Element& aEditingHost) const;

  /**
   * Creat new list elements or use existing list elements and move
   * aArrayOfContents into list item elements.
   *
   * @return            A list or list item element which should have caret.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<RefPtr<Element>, nsresult>
  WrapContentNodesIntoNewListElements(HTMLEditor& aHTMLEditor,
                                      AutoClonedRangeArray& aRanges,
                                      AutoContentNodeArray& aArrayOfContents,
                                      const Element& aEditingHost) const;

  struct MOZ_STACK_CLASS AutoHandlingState final {
    // Current list element which is a good container to create new list item
    // element.
    RefPtr<Element> mCurrentListElement;
    // Previously handled list item element.
    RefPtr<Element> mPreviousListItemElement;
    // List or list item element which should have caret after handling all
    // contents.
    RefPtr<Element> mListOrListItemElementToPutCaret;
    // Replacing block element.  This is typically already removed from the DOM
    // tree.
    RefPtr<Element> mReplacingBlockElement;
    // Once id attribute of mReplacingBlockElement copied, the id attribute
    // shouldn't be copied again.
    bool mMaybeCopiedReplacingBlockElementId = false;
  };

  /**
   * Helper methods of WrapContentNodesIntoNewListElements.  They are called for
   * handling one content node of aArrayOfContents.  It's set to aHandling*.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildContent(
      HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
      AutoHandlingState& aState, const Element& aEditingHost) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  HandleChildListElement(HTMLEditor& aHTMLEditor, Element& aHandlingListElement,
                         AutoHandlingState& aState) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildListItemElement(
      HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
      AutoHandlingState& aState) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  HandleChildListItemInDifferentTypeList(HTMLEditor& aHTMLEditor,
                                         Element& aHandlingListItemElement,
                                         AutoHandlingState& aState) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildListItemInSameTypeList(
      HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement,
      AutoHandlingState& aState) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildDivOrParagraphElement(
      HTMLEditor& aHTMLEditor, Element& aHandlingDivOrParagraphElement,
      AutoHandlingState& aState, const Element& aEditingHost) const;
  enum class EmptyListItem { NotCreate, Create };
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult CreateAndUpdateCurrentListElement(
      HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert,
      EmptyListItem aEmptyListItem, AutoHandlingState& aState,
      const Element& aEditingHost) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult>
  AppendListItemElement(HTMLEditor& aHTMLEditor, const Element& aListElement,
                        AutoHandlingState& aState) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
  MaybeCloneAttributesToNewListItem(HTMLEditor& aHTMLEditor,
                                    Element& aListItemElement,
                                    AutoHandlingState& aState);
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildInlineContent(
      HTMLEditor& aHTMLEditor, nsIContent& aHandlingInlineContent,
      AutoHandlingState& aState) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult WrapContentIntoNewListItemElement(
      HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent,
      AutoHandlingState& aState) const;

  /**
   * If aRanges is collapsed outside aListItemOrListToPutCaret, this collapse
   * aRanges in aListItemOrListToPutCaret again.
   */

  nsresult EnsureCollapsedRangeIsInListItemOrListElement(
      Element& aListItemOrListToPutCaret, AutoClonedRangeArray& aRanges) const;

  MOZ_KNOWN_LIVE nsStaticAtom& mListTagName;
  MOZ_KNOWN_LIVE nsStaticAtom& mListItemTagName;
  const nsAutoString mBulletType;
};

/**
 * Handle "insertParagraph" command.
 */

class MOZ_STACK_CLASS HTMLEditor::AutoInsertParagraphHandler final {
 public:
  AutoInsertParagraphHandler() = delete;
  AutoInsertParagraphHandler(const AutoInsertParagraphHandler&) = delete;
  AutoInsertParagraphHandler(AutoInsertParagraphHandler&&) = delete;

  MOZ_CAN_RUN_SCRIPT explicit AutoInsertParagraphHandler(
      HTMLEditor& aHTMLEditor, const Element& aEditingHost)
      : mHTMLEditor(aHTMLEditor),
        mEditingHost(aEditingHost),
        mDefaultParagraphSeparatorTagName(
            aHTMLEditor.DefaultParagraphSeparatorTagName()),
        mDefaultParagraphSeparator(aHTMLEditor.GetDefaultParagraphSeparator()) {
  }

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run();

 private:
  /**
   * Insert <br> element.
   *
   * @param aPointToInsert      The position where the new <br> should be
   *                            inserted.
   * @param aBlockElementWhichShouldHaveCaret
   *                            [optional] If set, this collapse selection into
   *                            the element with
   *                            CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret().
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  HandleInsertBRElement(
      const EditorDOMPoint& aPointToInsert,
      const Element* aBlockElementWhichShouldHaveCaret = nullptr);

  /**
   * Insert a linefeed.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  HandleInsertLinefeed(const EditorDOMPoint& aPointToInsert);

  /**
   * SplitParagraphWithTransaction() splits the parent block, aParentDivOrP, at
   * aPointToSplit.
   *
   * @param aBlockElementToSplit    The current paragraph which should be split.
   * @param aPointToSplit           The point to split aBlockElementToSplit.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult>
  SplitParagraphWithTransaction(Element& aBlockElementToSplit,
                                const EditorDOMPoint& aPointToSplit);

  /**
   * Delete preceding invisible line break before aPointToSplit if and only if
   * there is.
   *
   * @return New point to split aBlockElementToSplit
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
  EnsureNoInvisibleLineBreakBeforePointToSplit(
      const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit);

  /**
   * Maybe insert a <br> element if it's required to keep the inline container
   * visible after splitting aBlockElementToSplit at aPointToSplit.
   *
   * @return New point to split aBlockElementToSplit
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
  MaybeInsertFollowingBRElementToPreserveRightBlock(
      const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit);

  /**
   * Return true if the HTMLEditor is in the mode which `insertParagraph` should
   * always create a new paragraph or in the cases that we create a new
   * paragraph in the legacy mode.
   */

  [[nodiscard]] bool ShouldCreateNewParagraph(
      Element& aParentDivOrP, const EditorDOMPoint& aPointToSplit) const;

  /**
   * Return true if aBRElement is nullptr or an invisible <br> or a padding <br>
   * for making the last empty line visible.
   */

  [[nodiscard]] static bool
  IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine(
      const dom::HTMLBRElement* aBRElement);

  /**
   * Handle insertParagraph command (i.e., handling Enter key press) in a
   * heading element.  This splits aHeadingElement element at aPointToSplit.
   * Then, if right heading element is empty, it'll be removed and new paragraph
   * is created (its type is decided with default paragraph separator).
   *
   * @param aHeadingElement     The heading element to be split.
   * @param aPointToSplit       The point to split aHeadingElement.
   * @return                    New paragraph element, meaning right heading
   *                            element if aHeadingElement is split, or newly
   *                            created or existing paragraph element.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult>
  HandleInHeadingElement(Element& aHeadingElement,
                         const EditorDOMPoint& aPointToSplit);

  /**
   * Handle insertParagraph command at end of a heading element.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult>
  HandleAtEndOfHeadingElement(Element& aHeadingElement);

  /**
   * Handle insertParagraph command (i.e., handling Enter key press) in a list
   * item element.
   *
   * @param aListItemElement    The list item which has the following point.
   * @param aPointToSplit       The point to split aListItemElement.
   * @return                    New paragraph element, meaning right list item
   *                            element if aListItemElement is split, or newly
   *                            created paragraph element.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult>
  HandleInListItemElement(Element& aListItemElement,
                          const EditorDOMPoint& aPointToSplit);

  /**
   * Split aMailCiteElement at aPointToSplit.
   *
   * @param aMailCiteElement    The mail-cite element which should be split.
   * @param aPointToSplit       The point to split.
   * @return                    Candidate caret position where is at inserted
   *                            <br> element into the split point.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
  HandleInMailCiteElement(Element& aMailCiteElement,
                          const EditorDOMPoint& aPointToSplit);

  /**
   * Insert a <br> element into aPointToBreak.
   * This may split container elements at the point and/or may move following
   * <br> element to immediately after the new <br> element if necessary.
   *
   * @param aPointToBreak       The point where new <br> element will be
   *                            inserted before.
   * @return                    If succeeded, returns new <br> element and
   *                            candidate caret point.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult>
  InsertBRElement(const EditorDOMPoint& aPointToBreak);

  /**
   * Return true if we should insert a line break instead of a paragraph.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool ShouldInsertLineBreakInstead(
      const Element* aEditableBlockElement,
      const EditorDOMPoint& aCandidatePointToSplit);

  enum class InsertBRElementIntoEmptyBlock : bool { Start, End };

  /**
   * Make sure that aMaybeBlockElement is visible with putting a <br> element if
   * and only if it's an empty block element.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateLineBreakResult, nsresult>
  InsertBRElementIfEmptyBlockElement(
      Element& aMaybeBlockElement,
      InsertBRElementIntoEmptyBlock aInsertBRElementIntoEmptyBlock,
      BlockInlineCheck aBlockInlineCheck);

  /**
   * Split aMailCiteElement at aPointToSplit.  This deletes all inclusive
   * ancestors of aPointToSplit in aMailCiteElement too.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult>
  SplitMailCiteElement(const EditorDOMPoint& aPointToSplit,
                       Element& aMailCiteElement);

  /**
   * aMailCiteElement may be a <span> element which is styled as block.  If it's
   * followed by a block boundary, it requires a padding <br> element when it's
   * serialized.  This method may insert a <br> element if it's required.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  MaybeInsertPaddingBRElementToInlineMailCiteElement(
      const EditorDOMPoint& aPointToInsertBRElement, Element& aMailCiteElement);

  /**
   * Return the deepest inline container element which is the first leaf or the
   * first leaf container of aBlockElement.
   */

  [[nodiscard]] static Element* GetDeepestFirstChildInlineContainerElement(
      Element& aBlockElement);

  /**
   * Collapse `Selection` to aCandidatePointToPutCaret or into
   * aBlockElementShouldHaveCaret.  If aBlockElementShouldHaveCaret is specified
   * and aCandidatePointToPutCaret is outside it, this ignores
   * aCandidatePointToPutCaret and collapse `Selection` into
   * aBlockElementShouldHaveCaret.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret(
      const EditorDOMPoint& aCandidatePointToPutCaret,
      const Element* aBlockElementShouldHaveCaret,
      const SuggestCaretOptions& aOptions);

  /**
   * Return a better point to split the paragraph to avoid to keep a typing in a
   * link or a paragraph in list item in the new paragraph.
   */

  [[nodiscard]] EditorDOMPoint GetBetterPointToSplitParagraph(
      const Element& aBlockElementToSplit,
      const EditorDOMPoint& aCandidatePointToSplit,
      const Element& aEditingHost);

  enum class IgnoreBlockBoundaries : bool { No, Yes };

  /**
   * Return true if splitting aBlockElementToSplit at aPointToSplit will create
   * empty left element.
   *
   * @param aBlockElementToSplit    The paragraph element which we want to
   *                                split.
   * @param aPointToSplit           The split position in aBlockElementToSplit.
   * @param aIgnoreBlockBoundaries  If No, return true only when aPointToSplit
   *                                is immediately after a block boundary of
   *                                aBlockElementToSplit.  In other words,
   *                                may return true only when aPointToSplit
   *                                is not in a child block of
   *                                aBlockElementToSplit.
   *                                If Yes, return true even when aPointToSplit
   *                                is immediately after any current block
   *                                boundary which is followed by the block
   *                                boundary of aBlockElementToSplit.  In other
   *                                words, return true when aPointToSplit is in
   *                                a child block which is start of any ancestor
   *                                block elements.
   */

  [[nodiscard]] static bool SplitPointIsStartOfSplittingBlock(
      const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit,
      IgnoreBlockBoundaries aIgnoreBlockBoundaries);

  /**
   * Return true if splitting aBlockElementToSplit at aPointToSplit will create
   * empty right element.
   *
   * @param aBlockElementToSplit    The paragraph element which we want to
   *                                split.
   * @param aPointToSplit           The split position in aBlockElementToSplit.
   * @param aIgnoreBlockBoundaries  If No, return true only when aPointToSplit
   *                                is immediately before a block boundary of
   *                                aBlockElementToSplit.  In other words,
   *                                may return true only when aPointToSplit
   *                                is not in a child block of
   *                                aBlockElementToSplit.
   *                                If Yes, return true even when aPointToSplit
   *                                is immediately before any current block
   *                                boundary which is followed by the block
   *                                boundary of aBlockElementToSplit.  In other
   *                                words, return true when aPointToSplit is in
   *                                a child block which is end of any ancestor
   *                                block elements.
   */

  [[nodiscard]] static bool SplitPointIsEndOfSplittingBlock(
      const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit,
      IgnoreBlockBoundaries aIgnoreBlockBoundaries);

  MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor;
  MOZ_KNOWN_LIVE const Element& mEditingHost;
  MOZ_KNOWN_LIVE nsStaticAtom& mDefaultParagraphSeparatorTagName;
  const ParagraphSeparator mDefaultParagraphSeparator;
};

/**
 * Handle "insertLineBreak" command.
 */

class MOZ_STACK_CLASS HTMLEditor::AutoInsertLineBreakHandler final {
 public:
  AutoInsertLineBreakHandler() = delete;
  AutoInsertLineBreakHandler(const AutoInsertLineBreakHandler&) = delete;
  AutoInsertLineBreakHandler(AutoInsertLineBreakHandler&&) = delete;

  MOZ_CAN_RUN_SCRIPT explicit AutoInsertLineBreakHandler(
      HTMLEditor& aHTMLEditor, const Element& aEditingHost)
      : mHTMLEditor(aHTMLEditor), mEditingHost(aEditingHost) {}

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult Run();

 private:
  /**
   * Insert a linefeed character into aPointToBreak.
   *
   * @param aPointToBreak       The point where new linefeed character will be
   *                            inserted before.
   * @param aEditingHost        Current active editing host.
   * @return                    A suggest point to put caret.
   */

  [[nodiscard]] static MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
  InsertLinefeed(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToBreak,
                 const Element& aEditingHost);

  /**
   * Insert <br> element at `Selection`.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleInsertBRElement();

  /**
   * Insert a linefeed at `Selection`.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleInsertLinefeed();

  friend class AutoInsertParagraphHandler;

  MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor;
  MOZ_KNOWN_LIVE const Element& mEditingHost;
};

/**
 * Handle delete multiple ranges, typically they are the selection ranges.
 */

class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final {
 public:
  explicit AutoDeleteRangesHandler(
      const AutoDeleteRangesHandler* aParent = nullptr)
      : mParent(aParent),
        mOriginalDirectionAndAmount(nsIEditor::eNone),
        mOriginalStripWrappers(nsIEditor::eNoStrip) {}

  /**
   * ComputeRangesToDelete() computes actual deletion ranges.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ComputeRangesToDelete(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      AutoClonedSelectionRangeArray& aRangesToDelete,
      const Element& aEditingHost);

  /**
   * Deletes content in or around aRangesToDelete.
   * NOTE: This method creates SelectionBatcher.  Therefore, each caller
   *       needs to check if the editor is still available even if this returns
   *       NS_OK.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsIEditor::EStripWrappers aStripWrappers,
      AutoClonedSelectionRangeArray& aRangesToDelete,
      const Element& aEditingHost);

 private:
  enum class ComputeRangeFor : bool { GetTargetRanges, ToDeleteTheRange };

  [[nodiscard]] bool IsHandlingRecursively() const {
    return mParent != nullptr;
  }

  [[nodiscard]] bool CanFallbackToDeleteRangeWithTransaction(
      const nsRange& aRangeToDelete) const;

  [[nodiscard]] bool CanFallbackToDeleteRangesWithTransaction(
      const AutoClonedSelectionRangeArray& aRangesToDelete) const;

  /**
   * HandleDeleteAroundCollapsedRanges() handles deletion with collapsed
   * ranges.  Callers must guarantee that this is called only when
   * aRangesToDelete.IsCollapsed() returns true.
   *
   * @param aDirectionAndAmount Direction of the deletion.
   * @param aStripWrappers      Must be eStrip or eNoStrip.
   * @param aRangesToDelete     Ranges to delete.  This `IsCollapsed()` must
   *                            return true.
   * @param aWSRunScannerAtCaret        Scanner instance which scanned from
   *                                    caret point.
   * @param aScanFromCaretPointResult   Scan result of aWSRunScannerAtCaret
   *                                    toward aDirectionAndAmount.
   * @param aEditingHost        The editing host.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  HandleDeleteAroundCollapsedRanges(
      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsIEditor::EStripWrappers aStripWrappers,
      AutoClonedSelectionRangeArray& aRangesToDelete,
      const WSRunScanner& aWSRunScannerAtCaret,
      const WSScanResult& aScanFromCaretPointResult,
      const Element& aEditingHost);
  nsresult ComputeRangesToDeleteAroundCollapsedRanges(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      AutoClonedSelectionRangeArray& aRangesToDelete,
      const WSRunScanner& aWSRunScannerAtCaret,
      const WSScanResult& aScanFromCaretPointResult,
      const Element& aEditingHost) const;

  /**
   * HandleDeleteNonCollapsedRanges() handles deletion with non-collapsed
   * ranges.  Callers must guarantee that this is called only when
   * aRangesToDelete.IsCollapsed() returns false.
   *
   * @param aDirectionAndAmount         Direction of the deletion.
   * @param aStripWrappers              Must be eStrip or eNoStrip.
   * @param aRangesToDelete             The ranges to delete.
   * @param aSelectionWasCollapsed      If the caller extended `Selection`
   *                                    from collapsed, set this to `Yes`.
   *                                    Otherwise, i.e., `Selection` is not
   *                                    collapsed from the beginning, set
   *                                    this to `No`.
   * @param aEditingHost                The editing host.
   */

  enum class SelectionWasCollapsed { Yes, No };
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  HandleDeleteNonCollapsedRanges(HTMLEditor& aHTMLEditor,
                                 nsIEditor::EDirection aDirectionAndAmount,
                                 nsIEditor::EStripWrappers aStripWrappers,
                                 AutoClonedSelectionRangeArray& aRangesToDelete,
                                 SelectionWasCollapsed aSelectionWasCollapsed,
                                 const Element& aEditingHost);
  nsresult ComputeRangesToDeleteNonCollapsedRanges(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      AutoClonedSelectionRangeArray& aRangesToDelete,
      SelectionWasCollapsed aSelectionWasCollapsed,
      const Element& aEditingHost) const;

  /**
   * Handle deletion of collapsed ranges in a text node.
   *
   * @param aDirectionAndAmount Must be eNext or ePrevious.
   * @param aCaretPosition      The position where caret is.  This container
   *                            must be a text node.
   * @param aEditingHost        The editing host.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
  HandleDeleteTextAroundCollapsedRanges(
      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      AutoClonedSelectionRangeArray& aRangesToDelete,
      const Element& aEditingHost);
  nsresult ComputeRangesToDeleteTextAroundCollapsedRanges(
      nsIEditor::EDirection aDirectionAndAmount,
      AutoClonedSelectionRangeArray& aRangesToDelete) const;

  /**
   * Handle deletion of atomic elements like <br>, <hr>, <img>, <input>, etc and
   * data nodes except text node (e.g., comment node). Note that don't call this
   * directly with `<hr>` element.
   *
   * @param aAtomicContent      The atomic content to be deleted.
   * @param aCaretPoint         The caret point (i.e., selection start or
   *                            end).
   * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
   *                             with the caret point.
   * @param aEditingHost        The editing host.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
  HandleDeleteAtomicContent(HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent,
                            const EditorDOMPoint& aCaretPoint,
                            const WSRunScanner& aWSRunScannerAtCaret,
                            const Element& aEditingHost);
  nsresult ComputeRangesToDeleteAtomicContent(
      const nsIContent& aAtomicContent,
      AutoClonedSelectionRangeArray& aRangesToDelete) const;

  /**
   * GetAtomicContnetToDelete() returns better content that is deletion of
   * atomic element.  If aScanFromCaretPointResult is special, since this
   * point may not be editable, we look for better point to remove atomic
   * content.
   *
   * @param aDirectionAndAmount       Direction of the deletion.
   * @param aWSRunScannerAtCaret      WSRunScanner instance which was
   *                                  initialized with the caret point.
   * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret
   *                                  toward aDirectionAndAmount.
   */

  [[nodiscard]] static nsIContent* GetAtomicContentToDelete(
      nsIEditor::EDirection aDirectionAndAmount,
      const WSRunScanner& aWSRunScannerAtCaret,
      const WSScanResult& aScanFromCaretPointResult) MOZ_NONNULL_RETURN;

  /**
   * HandleDeleteAtOtherBlockBoundary() handles deletion at other block boundary
   * (i.e., immediately before or after a block). If this does not join blocks,
   * `Run()` may be called recursively with creating another instance.
   *
   * @param aDirectionAndAmount Direction of the deletion.
   * @param aStripWrappers      Must be eStrip or eNoStrip.
   * @param aOtherBlockElement  The block element which follows the caret or
   *                            is followed by caret.
   * @param aCaretPoint         The caret point (i.e., selection start or
   *                            end).
   * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
   *                             with the caret point.
   * @param aRangesToDelete     Ranges to delete of the caller.  This should
   *                            be collapsed and the point should match with
   *                            aCaretPoint.
   * @param aEditingHost        The editing host.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  HandleDeleteAtOtherBlockBoundary(
      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsIEditor::EStripWrappers aStripWrappers, Element& aOtherBlockElement,
      const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret,
      AutoClonedSelectionRangeArray& aRangesToDelete,
      const Element& aEditingHost);

  /**
   * ExtendOrShrinkRangeToDelete() extends aRangeToDelete if there are
   * an invisible <br> element and/or some parent empty elements.
   *
   * @param aLimitersAndCaretData The frame selection data.
   * @param aRangeToDelete       The range to be extended for deletion.  This
   *                            must not be collapsed, must be positioned.
   */

  template <typename EditorDOMRangeType>
  [[nodiscard]] Result<EditorRawDOMRange, nsresult> ExtendOrShrinkRangeToDelete(
      const HTMLEditor& aHTMLEditor,
      const LimitersAndCaretData& aLimitersAndCaretData,
      const EditorDOMRangeType& aRangeToDelete,
      SelectionWasCollapsed aSelectionWasCollapsed,
      ComputeRangeFor aComputeRangeFor, const Element& aEditingHost) const;

  /**
   * Extend the start boundary of aRangeToDelete to contain ancestor inline
   * elements which will be empty once the content in aRangeToDelete is removed
   * from the tree.
   *
   * NOTE: This is designed for deleting inline elements which become empty if
   * aRangeToDelete which crosses a block boundary of right block child.
   * Therefore, you may need to improve this method if you want to use this in
   * the other cases.
   *
   * @param aRangeToDelete      [in/out] The range to delete.  This start
   *                            boundary may be modified.
   * @param aEditingHost        The editing host.
   * @return                    true if aRangeToDelete is modified.
   *                            false if aRangeToDelete is not modified.
   *                            error if aRangeToDelete gets unexpected
   *                            situation.
   */

  [[nodiscard]] static Result<bool, nsresult>
  ExtendRangeToContainAncestorInlineElementsAtStart(
      nsRange& aRangeToDelete, const Element& aEditingHost);

  /**
   * A helper method for ExtendOrShrinkRangeToDelete().  This returns shrunken
   * range if aRangeToDelete selects all over list elements which have some list
   * item elements to avoid to delete all list items from the list element.
   */

  [[nodiscard]] static EditorRawDOMRange
  GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements(
      const EditorRawDOMRange& aRangeToDelete,
      ComputeRangeFor aComputeRangeFor);

  /**
   * DeleteUnnecessaryNodes() removes unnecessary nodes around aRange.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  DeleteUnnecessaryNodes(HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
                         const Element& aEditingHost);

  /**
   * If aContent is a text node that contains only collapsed white-space or
   * empty and editable.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  DeleteNodeIfInvisibleAndEditableTextNode(HTMLEditor& aHTMLEditor,
                                           nsIContent& aContent);

  /**
   * DeleteParentBlocksIfEmpty() removes parent block elements if they
   * don't have visible contents.  Note that due performance issue of
   * WhiteSpaceVisibilityKeeper, this call may be expensive.  And also note that
   * this removes a empty block with a transaction.  So, please make sure that
   * you've already created `AutoPlaceholderBatch`.
   *
   * @param aPoint      The point whether this method climbing up the DOM
   *                    tree to remove empty parent blocks.
   * @return            NS_OK if one or more empty block parents are deleted.
   *                    NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND if the point is
   *                    not in empty block.
   *                    Or NS_ERROR_* if something unexpected occurs.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult
  DeleteParentBlocksWithTransactionIfEmpty(HTMLEditor& aHTMLEditor,
                                           const EditorDOMPoint& aPoint,
                                           const Element& aEditingHost);

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
  FallbackToDeleteRangeWithTransaction(HTMLEditor& aHTMLEditor,
                                       nsRange& aRangeToDelete) const {
    MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
    MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete));
    Result<CaretPoint, nsresult> caretPointOrError =
        aHTMLEditor.DeleteRangeWithTransaction(mOriginalDirectionAndAmount,
                                               mOriginalStripWrappers,
                                               aRangeToDelete);
    NS_WARNING_ASSERTION(caretPointOrError.isOk(),
                         "EditorBase::DeleteRangeWithTransaction() failed");
    return caretPointOrError;
  }

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult>
  FallbackToDeleteRangesWithTransaction(
      HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRangesToDelete,
      const Element& aEditingHost) const;

  /**
   * Compute target range(s) which will be called by
   * `EditorBase::DeleteRangeWithTransaction()` or
   * `HTMLEditor::DeleteRangesWithTransaction()`.
   * TODO: We should not use it for consistency with each deletion handler
   *       in this and nested classes.
   */

  nsresult ComputeRangeToDeleteRangeWithTransaction(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsRange& aRange, const Element& aEditingHost) const;
  nsresult ComputeRangesToDeleteRangesWithTransaction(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      AutoClonedSelectionRangeArray& aRangesToDelete,
      const Element& aEditingHost) const;

  nsresult FallbackToComputeRangeToDeleteRangeWithTransaction(
      const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
      const Element& aEditingHost) const {
    MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
    MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete));
    nsresult rv = ComputeRangeToDeleteRangeWithTransaction(
        aHTMLEditor, mOriginalDirectionAndAmount, aRangeToDelete, aEditingHost);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "AutoDeleteRangesHandler::"
                         "ComputeRangeToDeleteRangeWithTransaction() failed");
    return rv;
  }
  nsresult FallbackToComputeRangesToDeleteRangesWithTransaction(
      const HTMLEditor& aHTMLEditor,
      AutoClonedSelectionRangeArray& aRangesToDelete,
      const Element& aEditingHost) const {
    MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
    MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete));
    nsresult rv = ComputeRangesToDeleteRangesWithTransaction(
        aHTMLEditor, mOriginalDirectionAndAmount, aRangesToDelete,
        aEditingHost);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                         "AutoDeleteRangesHandler::"
                         "ComputeRangesToDeleteRangesWithTransaction() failed");
    return rv;
  }

  class MOZ_STACK_CLASS AutoBlockElementsJoiner;
  class MOZ_STACK_CLASS AutoEmptyBlockAncestorDeleter;

  const AutoDeleteRangesHandler* const mParent;
  nsIEditor::EDirection mOriginalDirectionAndAmount;
  nsIEditor::EStripWrappers mOriginalStripWrappers;
};

/**
 * Handle join block elements.  Despite the name, this may just move first line
 * of a block into another block or just deleted the range with keeping table
 * structure.
 */

class MOZ_STACK_CLASS
HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner final {
 public:
  AutoBlockElementsJoiner() = delete;
  explicit AutoBlockElementsJoiner(
      AutoDeleteRangesHandler& aDeleteRangesHandler)
      : mDeleteRangesHandler(&aDeleteRangesHandler),
        mDeleteRangesHandlerConst(aDeleteRangesHandler) {}
  explicit AutoBlockElementsJoiner(
      const AutoDeleteRangesHandler& aDeleteRangesHandler)
      : mDeleteRangesHandler(nullptr),
        mDeleteRangesHandlerConst(aDeleteRangesHandler) {}

  /**
   * PrepareToDeleteAtCurrentBlockBoundary() considers left content and right
   * content which are joined for handling deletion at current block boundary
   * (i.e., at start or end of the current block).
   *
   * @param aHTMLEditor               The HTML editor.
   * @param aDirectionAndAmount       Direction of the deletion.
   * @param aCurrentBlockElement      The current block element.
   * @param aCaretPoint               The caret point (i.e., selection start
   *                                  or end).
   * @param aEditingHost              The editing host.
   * @return                          true if can continue to handle the
   *                                  deletion.
   */

  [[nodiscard]] bool PrepareToDeleteAtCurrentBlockBoundary(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint,
      const Element& aEditingHost);

  /**
   * PrepareToDeleteAtOtherBlockBoundary() considers left content and right
   * content which are joined for handling deletion at other block boundary
   * (i.e., immediately before or after a block).
   *
   * @param aHTMLEditor               The HTML editor.
   * @param aDirectionAndAmount       Direction of the deletion.
   * @param aOtherBlockElement        The block element which follows the
   *                                  caret or is followed by caret.
   * @param aCaretPoint               The caret point (i.e., selection start
   *                                  or end).
   * @param aWSRunScannerAtCaret      WSRunScanner instance which was
   *                                  initialized with the caret point.
   * @return                          true if can continue to handle the
   *                                  deletion.
   */

  [[nodiscard]] bool PrepareToDeleteAtOtherBlockBoundary(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      Element& aOtherBlockElement, const EditorDOMPoint& aCaretPoint,
      const WSRunScanner& aWSRunScannerAtCaret);

  /**
   * PrepareToDeleteNonCollapsedRange() considers left block element and
   * right block element which are inclusive ancestor block element of
   * start and end container of aRangeToDelete
   *
   * @param aHTMLEditor               The HTML editor.
   * @param aRangeToDelete            The range to delete.  Must not be
   *                                  collapsed.
   * @param aEditingHost              The editing host.
   * @return                          true if can continue to handle the
   *                                  deletion.
   */

  [[nodiscard]] bool PrepareToDeleteNonCollapsedRange(
      const HTMLEditor& aHTMLEditor, const nsRange& aRangeToDelete,
      const Element& aEditingHost);

  /**
   * Run() executes the joining.
   *
   * @param aHTMLEditor               The HTML editor.
   * @param aDirectionAndAmount       Direction of the deletion.
   * @param aStripWrappers            Must be eStrip or eNoStrip.
   * @param aCaretPoint               The caret point (i.e., selection start
   *                                  or end).
   * @param aRangeToDelete            The range to delete.  This should be
   *                                  collapsed and match with aCaretPoint.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsIEditor::EStripWrappers aStripWrappers,
      const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete,
      const Element& aEditingHost);

  nsresult ComputeRangeToDelete(const HTMLEditor& aHTMLEditor,
                                nsIEditor::EDirection aDirectionAndAmount,
                                const EditorDOMPoint& aCaretPoint,
                                nsRange& aRangeToDelete,
                                const Element& aEditingHost) const;

  /**
   * Run() executes the joining.
   *
   * @param aHTMLEditor               The HTML editor.
   * @param aLimitersAndCaretData     The data copied from nsFrameSelection.
   * @param aDirectionAndAmount       Direction of the deletion.
   * @param aStripWrappers            Whether delete or keep new empty
   *                                  ancestor elements.
   * @param aRangeToDelete            The range to delete.  Must not be
   *                                  collapsed.
   * @param aSelectionWasCollapsed    Whether selection was or was not
   *                                  collapsed when starting to handle
   *                                  deletion.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(
      HTMLEditor& aHTMLEditor,
      const LimitersAndCaretData& aLimitersAndCaretData,
      nsIEditor::EDirection aDirectionAndAmount,
      nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
      const Element& aEditingHost);

  nsresult ComputeRangeToDelete(
      const HTMLEditor& aHTMLEditor,
      const AutoClonedSelectionRangeArray& aRangesToDelete,
      nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete,
      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
      const Element& aEditingHost) const;

  [[nodiscard]] nsIContent* GetLeafContentInOtherBlockElement() const {
    MOZ_ASSERT(mMode == Mode::JoinOtherBlock);
    return mLeafContentInOtherBlock;
  }

 private:
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  HandleDeleteAtCurrentBlockBoundary(HTMLEditor& aHTMLEditor,
                                     nsIEditor::EDirection aDirectionAndAmount,
                                     const EditorDOMPoint& aCaretPoint,
                                     const Element& aEditingHost);
  nsresult ComputeRangeToDeleteAtCurrentBlockBoundary(
      const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint,
      nsRange& aRangeToDelete, const Element& aEditingHost) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  HandleDeleteAtOtherBlockBoundary(HTMLEditor& aHTMLEditor,
                                   nsIEditor::EDirection aDirectionAndAmount,
                                   nsIEditor::EStripWrappers aStripWrappers,
                                   const EditorDOMPoint& aCaretPoint,
                                   nsRange& aRangeToDelete,
                                   const Element& aEditingHost);
  // FYI: This method may modify selection, but it won't cause running
  //      script because of `AutoHideSelectionChanges` which blocks
  //      selection change listeners and the selection change event
  //      dispatcher.
  MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult ComputeRangeToDeleteAtOtherBlockBoundary(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete,
      const Element& aEditingHost) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  JoinBlockElementsInSameParent(
      HTMLEditor& aHTMLEditor,
      const LimitersAndCaretData& aLimitersAndCaretData,
      nsIEditor::EDirection aDirectionAndAmount,
      nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
      const Element& aEditingHost);
  nsresult ComputeRangeToJoinBlockElementsInSameParent(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsRange& aRangeToDelete, const Element& aEditingHost) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  HandleDeleteLineBreak(HTMLEditor& aHTMLEditor,
                        nsIEditor::EDirection aDirectionAndAmount,
                        const EditorDOMPoint& aCaretPoint,
                        const Element& aEditingHost);
  nsresult ComputeRangeToDeleteLineBreak(
      const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
      const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  DeleteContentInRange(HTMLEditor& aHTMLEditor,
                       const LimitersAndCaretData& aLimitersAndCaretData,
                       nsIEditor::EDirection aDirectionAndAmount,
                       nsIEditor::EStripWrappers aStripWrappers,
                       nsRange& aRangeToDelete, const Element& aEditingHost);
  nsresult ComputeRangeToDeleteContentInRange(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsRange& aRange, const Element& aEditingHost) const;
  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult>
  HandleDeleteNonCollapsedRange(
      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete,
      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
      const Element& aEditingHost);
  nsresult ComputeRangeToDeleteNonCollapsedRange(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      nsRange& aRangeToDelete,
      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed,
      const Element& aEditingHost) const;

  /**
   * JoinNodesDeepWithTransaction() joins aLeftNode and aRightNode "deeply".
   * First, they are joined simply, then, new right node is assumed as the
   * child at length of the left node before joined and new left node is
   * assumed as its previous sibling.  Then, they will be joined again.
   * And then, these steps are repeated.
   *
   * @param aLeftContent    The node which will be removed form the tree.
   * @param aRightContent   The node which will be inserted the contents of
   *                        aRightContent.
   * @return                The point of the first child of the last right
   * node. The result is always set if this succeeded.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
  JoinNodesDeepWithTransaction(HTMLEditor& aHTMLEditor,
                               nsIContent& aLeftContent,
                               nsIContent& aRightContent);

  enum class PutCaretTo : bool { StartOfRange, EndOfRange };

  /**
   * DeleteNodesEntirelyInRangeButKeepTableStructure() removes each node in
   * aArrayOfContent.  However, if some nodes are part of a table, removes all
   * children of them instead.  I.e., this does not make damage to table
   * structure at the range, but may remove table entirely if it's in the
   * range.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
  DeleteNodesEntirelyInRangeButKeepTableStructure(
      HTMLEditor& aHTMLEditor,
      const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContent,
      PutCaretTo aPutCaretTo);
  [[nodiscard]] bool NeedsToJoinNodesAfterDeleteNodesEntirelyInRange() const;
  Result<bool, nsresult>
  ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure(
      const HTMLEditor& aHTMLEditor, nsRange& aRange,
      AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed)
      const;

  /**
   * DeleteContentButKeepTableStructure() removes aContent if it's an element
   * which is part of a table structure.  If it's a part of table structure,
   * removes its all children recursively.  I.e., this may delete all of a
   * table, but won't break table structure partially.
   *
   * @param aContent            The content which or whose all children should
   *                            be removed.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
  DeleteContentButKeepTableStructure(HTMLEditor& aHTMLEditor,
                                     nsIContent& aContent);

  /**
   * DeleteTextAtStartAndEndOfRange() removes text if start and/or end of
   * aRange is in a text node.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
  DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange,
                                 PutCaretTo aPutCaretTo);

  /**
   * Return a block element which is an inclusive ancestor of the container of
   * aPoint if aPoint is start of ancestor blocks.  For example, if `<div
   * id=div1>abc<div id=div2><div id=div3>[]def</div></div></div>`, return
   * #div2.
   */

  template <typename EditorDOMPointType>
  [[nodiscard]] static Result<Element*, nsresult>
  GetMostDistantBlockAncestorIfPointIsStartAtBlock(
      const EditorDOMPointType& aPoint, const Element& aEditingHost,
      const Element* aAncestorLimiter = nullptr);

  /**
   * Extend aRangeToDelete to contain new empty inline ancestors and contain
   * an invisible <br> element before right child block which causes an empty
   * line but the range starts after it.
   */

  void ExtendRangeToDeleteNonCollapsedRange(
      const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete,
      const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const;

  /**
   * Compute mLeafContentInOtherBlock from the DOM.
   */

  [[nodiscard]] nsIContent* ComputeLeafContentInOtherBlockElement(
      nsIEditor::EDirection aDirectionAndAmount) const;

  class MOZ_STACK_CLASS AutoInclusiveAncestorBlockElementsJoiner;

  enum class Mode {
    NotInitialized,
    JoinCurrentBlock,
    JoinOtherBlock,
    JoinBlocksInSameParent,
    DeleteBRElement,
    // The instance will handle only the <br> element immediately before a
    // block.
    DeletePrecedingBRElementOfBlock,
    // The instance will handle only the preceding preformatted line break
    // before a block.
    DeletePrecedingPreformattedLineBreak,
    DeleteContentInRange,
    DeleteNonCollapsedRange,
    // The instance will handle preceding lines of the right block and content
    // in the range in the right block.
    DeletePrecedingLinesAndContentInRange,
  };
  AutoDeleteRangesHandler* mDeleteRangesHandler;
  const AutoDeleteRangesHandler& mDeleteRangesHandlerConst;
  nsCOMPtr<nsIContent> mLeftContent;
  nsCOMPtr<nsIContent> mRightContent;
  nsCOMPtr<nsIContent> mLeafContentInOtherBlock;
  RefPtr<Element> mOtherBlockElement;
  // mSkippedInvisibleContents stores all content nodes which are skipped at
  // scanning mLeftContent and mRightContent.  The content nodes should be
  // removed at deletion.
  AutoTArray<OwningNonNull<nsIContent>, 8> mSkippedInvisibleContents;
  RefPtr<dom::HTMLBRElement> mBRElement;
  EditorDOMPointInText mPreformattedLineBreak;
  Mode mMode = Mode::NotInitialized;
};

/**
 * Actually handle joining inclusive ancestor block elements.
 */

class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler::
    AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner final {
 public:
  AutoInclusiveAncestorBlockElementsJoiner() = delete;
  AutoInclusiveAncestorBlockElementsJoiner(
      nsIContent& aInclusiveDescendantOfLeftBlockElement,
      nsIContent& aInclusiveDescendantOfRightBlockElement)
      : mInclusiveDescendantOfLeftBlockElement(
            aInclusiveDescendantOfLeftBlockElement),
        mInclusiveDescendantOfRightBlockElement(
            aInclusiveDescendantOfRightBlockElement),
        mCanJoinBlocks(false),
        mFallbackToDeleteLeafContent(false) {}

  [[nodiscard]] bool IsSet() const {
    return mLeftBlockElement && mRightBlockElement;
  }
  [[nodiscard]] bool IsSameBlockElement() const {
    return mLeftBlockElement && mLeftBlockElement == mRightBlockElement;
  }

  /**
   * Prepare for joining inclusive ancestor block elements.  When this
   * returns false, the deletion should be canceled.
   */

  [[nodiscard]] Result<bool, nsresult> Prepare(const HTMLEditor& aHTMLEditor,
                                               const Element& aEditingHost);

  /**
   * When this returns true, this can join the blocks with `Run()`.
   */

  [[nodiscard]] bool CanJoinBlocks() const { return mCanJoinBlocks; }

  /**
   * When this returns true, `Run()` must return "ignored" so that
   * caller can skip calling `Run()`.  This is available only when
   * `CanJoinBlocks()` returns `true`.
   * TODO: This should be merged into `CanJoinBlocks()` in the future.
   */

  [[nodiscard]] bool ShouldDeleteLeafContentInstead() const {
    MOZ_ASSERT(CanJoinBlocks());
    return mFallbackToDeleteLeafContent;
  }

  /**
   * ComputeRangesToDelete() extends aRangeToDelete includes the element
   * boundaries between joining blocks.  If they won't be joined, this
   * collapses the range to aCaretPoint.
   */

  [[nodiscard]] nsresult ComputeRangeToDelete(
      const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint,
      nsRange& aRangeToDelete, const Element& aEditingHost) const;

  /**
   * Join inclusive ancestor block elements which are found by preceding
   * Prepare() call.
   * The right element is always joined to the left element.
   * If the elements are the same type and not nested within each other,
   * JoinEditableNodesWithTransaction() is called (example, joining two
   * list items together into one).
   * If the elements are not the same type, or one is a descendant of the
   * other, we instead destroy the right block placing its children into
   * left block.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> Run(
      HTMLEditor& aHTMLEditor, const Element& aEditingHost);

 private:
  /**
   * This method returns true when
   * `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`,
   * `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` and
   * `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` handle it
   * with the `if` block of the main lambda of them.
   */

  [[nodiscard]] bool CanMergeLeftAndRightBlockElements() const {
    if (!IsSet()) {
      return false;
    }
    // `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`
    if (mPointContainingTheOtherBlockElement.GetContainer() ==
        mRightBlockElement) {
      return mNewListElementTagNameOfRightListElement.isSome();
    }
    // `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()`
    if (mPointContainingTheOtherBlockElement.GetContainer() ==
        mLeftBlockElement) {
      return mNewListElementTagNameOfRightListElement.isSome() &&
             mRightBlockElement->GetChildCount();
    }
    MOZ_ASSERT(!mPointContainingTheOtherBlockElement.IsSet());
    // `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()`
    return mNewListElementTagNameOfRightListElement.isSome() ||
           (mLeftBlockElement->NodeInfo()->NameAtom() ==
                mRightBlockElement->NodeInfo()->NameAtom() &&
            EditorUtils::GetComputedWhiteSpaceStyles(*mLeftBlockElement) ==
                EditorUtils::GetComputedWhiteSpaceStyles(*mRightBlockElement));
  }

  OwningNonNull<nsIContent> mInclusiveDescendantOfLeftBlockElement;
  OwningNonNull<nsIContent> mInclusiveDescendantOfRightBlockElement;
  RefPtr<Element> mLeftBlockElement;
  RefPtr<Element> mRightBlockElement;
  Maybe<nsAtom*> mNewListElementTagNameOfRightListElement;
  EditorDOMPoint mPointContainingTheOtherBlockElement;
  RefPtr<dom::HTMLBRElement> mPrecedingInvisibleBRElement;
  bool mCanJoinBlocks;
  bool mFallbackToDeleteLeafContent;
};

/**
 * Handle deleting empty block ancestors.
 */

class MOZ_STACK_CLASS
HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter final {
 public:
  /**
   * ScanEmptyBlockInclusiveAncestor() scans an inclusive ancestor element
   * which is empty and a block element.  Then, stores the result and
   * returns the found empty block element.
   *
   * @param aHTMLEditor         The HTMLEditor.
   * @param aStartContent       Start content to look for empty ancestors.
   */

  [[nodiscard]] Element* ScanEmptyBlockInclusiveAncestor(
      const HTMLEditor& aHTMLEditor, nsIContent& aStartContent);

  /**
   * ComputeTargetRanges() computes "target ranges" for deleting
   * `mEmptyInclusiveAncestorBlockElement`.
   */

  nsresult ComputeTargetRanges(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      const Element& aEditingHost,
      AutoClonedSelectionRangeArray& aRangesToDelete) const;

  /**
   * Deletes found empty block element by `ScanEmptyBlockInclusiveAncestor()`.
   * If found one is a list item element, calls
   * `MaybeInsertBRElementBeforeEmptyListItemElement()` before deleting
   * the list item element.
   * If found empty ancestor is not a list item element,
   * `GetNewCaretPosition()` will be called to determine new caret position.
   * Finally, removes the empty block ancestor.
   *
   * @param aHTMLEditor         The HTMLEditor.
   * @param aDirectionAndAmount If found empty ancestor block is a list item
   *                            element, this is ignored.  Otherwise:
   *                            - If eNext, eNextWord or eToEndOfLine,
   *                              collapse Selection to after found empty
   *                              ancestor.
   *                            - If ePrevious, ePreviousWord or
   *                              eToBeginningOfLine, collapse Selection to
   *                              end of previous editable node.
   *                            - Otherwise, eNone is allowed but does
   *                              nothing.
   * @param aEditingHost        The editing host.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> Run(
      HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      const Element& aEditingHost);

 private:
  /**
   * MaybeReplaceSubListWithNewListItem() replaces
   * mEmptyInclusiveAncestorBlockElement with new list item element
   * (containing <br>) if:
   * - mEmptyInclusiveAncestorBlockElement is a list element
   * - The parent of mEmptyInclusiveAncestorBlockElement is a list element
   * - The parent becomes empty after deletion
   * If this does not perform the replacement, returns "ignored".
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult>
  MaybeReplaceSubListWithNewListItem(HTMLEditor& aHTMLEditor);

  /**
   * MaybeInsertBRElementBeforeEmptyListItemElement() inserts a `<br>` element
   * if `mEmptyInclusiveAncestorBlockElement` is a list item element which
   * is first editable element in its parent, and its grand parent is not a
   * list element, inserts a `<br>` element before the empty list item.
   */

  [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateLineBreakResult, nsresult>
  MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor& aHTMLEditor);

  /**
   * GetNewCaretPosition() returns new caret position after deleting
   * `mEmptyInclusiveAncestorBlockElement`.
   */

  [[nodiscard]] Result<CaretPoint, nsresult> GetNewCaretPosition(
      const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
      const Element& aEditingHost) const;

  RefPtr<Element> mEmptyInclusiveAncestorBlockElement;
};

/******************************************************************************
 * NormalizedStringToInsertText stores normalized insertion string with
 * normalized surrounding white-spaces if the insertion point is surrounded by
 * collapsible white-spaces.  For deleting invisible (collapsed) white-spaces,
 * this also stores the replace range and new white-space length before and
 * after the inserting text.
 ******************************************************************************/


struct MOZ_STACK_CLASS HTMLEditor::NormalizedStringToInsertText final {
  NormalizedStringToInsertText(
      const nsAString& aStringToInsertWithoutSurroundingWhiteSpaces,
      const EditorDOMPoint& aPointToInsert)
      : mNormalizedString(aStringToInsertWithoutSurroundingWhiteSpaces),
        mReplaceStartOffset(
            aPointToInsert.IsInTextNode() ? aPointToInsert.Offset() : 0u),
        mReplaceEndOffset(mReplaceStartOffset) {
    MOZ_ASSERT(aStringToInsertWithoutSurroundingWhiteSpaces.Length() ==
               InsertingTextLength());
  }

  NormalizedStringToInsertText(
      const nsAString& aStringToInsertWithSurroundingWhiteSpaces,
      uint32_t aInsertOffset, uint32_t aReplaceStartOffset,
      uint32_t aReplaceLength,
      uint32_t aNewPrecedingWhiteSpaceLengthBeforeInsertionString,
      uint32_t aNewFollowingWhiteSpaceLengthAfterInsertionString)
      : mNormalizedString(aStringToInsertWithSurroundingWhiteSpaces),
        mReplaceStartOffset(aReplaceStartOffset),
        mReplaceEndOffset(mReplaceStartOffset + aReplaceLength),
        mReplaceLengthBefore(aInsertOffset - mReplaceStartOffset),
        mReplaceLengthAfter(aReplaceLength - mReplaceLengthBefore),
        mNewLengthBefore(aNewPrecedingWhiteSpaceLengthBeforeInsertionString),
        mNewLengthAfter(aNewFollowingWhiteSpaceLengthAfterInsertionString) {
    MOZ_ASSERT(aReplaceStartOffset <= aInsertOffset);
    MOZ_ASSERT(aReplaceStartOffset + aReplaceLength >= aInsertOffset);
    MOZ_ASSERT(aNewPrecedingWhiteSpaceLengthBeforeInsertionString +
                   aNewFollowingWhiteSpaceLengthAfterInsertionString <
               mNormalizedString.Length());
    MOZ_ASSERT(mReplaceLengthBefore + mReplaceLengthAfter == ReplaceLength());
    MOZ_ASSERT(mReplaceLengthBefore >= mNewLengthBefore);
    MOZ_ASSERT(mReplaceLengthAfter >= mNewLengthAfter);
  }

  NormalizedStringToInsertText GetMinimizedData(const Text& aText) const {
    if (mNormalizedString.IsEmpty() || !ReplaceLength()) {
      return *this;
    }
    const dom::CharacterDataBuffer& characterDataBuffer = aText.DataBuffer();
    const uint32_t minimizedReplaceStart = [&]() {
      const auto firstDiffCharOffset =
          mNewLengthBefore ? characterDataBuffer.FindFirstDifferentCharOffset(
                                 PrecedingWhiteSpaces(), mReplaceStartOffset)
                           : dom::CharacterDataBuffer::kNotFound;
      if (firstDiffCharOffset == dom::CharacterDataBuffer::kNotFound) {
        return
            // We don't need to insert new normalized white-spaces before the
            // inserting string,
            (mReplaceStartOffset + mReplaceLengthBefore)
            // but keep extending the replacing range for deleting invisible
            // white-spaces.
            - DeletingPrecedingInvisibleWhiteSpaces();
      }
      return firstDiffCharOffset;
    }();
    const uint32_t minimizedReplaceEnd = [&]() {
      const auto lastDiffCharOffset =
          mNewLengthAfter ? characterDataBuffer.RFindFirstDifferentCharOffset(
                                FollowingWhiteSpaces(), mReplaceEndOffset)
                          : dom::CharacterDataBuffer::kNotFound;
      if (lastDiffCharOffset == dom::CharacterDataBuffer::kNotFound) {
        return
            // We don't need to insert new normalized white-spaces after the
            // inserting string,
            (mReplaceEndOffset - mReplaceLengthAfter)
            // but keep extending the replacing range for deleting invisible
            // white-spaces.
            + DeletingFollowingInvisibleWhiteSpaces();
      }
      return lastDiffCharOffset + 1u;
    }();
    if (minimizedReplaceStart == mReplaceStartOffset &&
        minimizedReplaceEnd == mReplaceEndOffset) {
      return *this;
    }
    const uint32_t newPrecedingWhiteSpaceLength =
        mNewLengthBefore - (minimizedReplaceStart - mReplaceStartOffset);
    const uint32_t newFollowingWhiteSpaceLength =
        mNewLengthAfter - (mReplaceEndOffset - minimizedReplaceEnd);
    return NormalizedStringToInsertText(
        Substring(mNormalizedString,
                  mNewLengthBefore - newPrecedingWhiteSpaceLength,
                  mNormalizedString.Length() -
                      (mNewLengthBefore - newPrecedingWhiteSpaceLength) -
                      (mNewLengthAfter - newFollowingWhiteSpaceLength)),
        OffsetToInsertText(), minimizedReplaceStart,
        minimizedReplaceEnd - minimizedReplaceStart,
        newPrecedingWhiteSpaceLength, newFollowingWhiteSpaceLength);
  }

  /**
   * Return offset to insert the given text.
   */

  [[nodiscard]] uint32_t OffsetToInsertText() const {
    return mReplaceStartOffset + mReplaceLengthBefore;
  }

  /**
   * Return inserting text length not containing the surrounding white-spaces.
   */

  [[nodiscard]] uint32_t InsertingTextLength() const {
    return mNormalizedString.Length() - mNewLengthBefore - mNewLengthAfter;
  }

  /**
   * Return end offset of inserted string after replacing the text with
   * mNormalizedString.
   */

  [[nodiscard]] uint32_t EndOffsetOfInsertedText() const {
    return OffsetToInsertText() + InsertingTextLength();
  }

  /**
   * Return the length to replace with mNormalizedString.  The result means that
   * it's the length of surrounding white-spaces at the insertion point.
   */

  [[nodiscard]] uint32_t ReplaceLength() const {
    return mReplaceEndOffset - mReplaceStartOffset;
  }

  [[nodiscard]] uint32_t DeletingPrecedingInvisibleWhiteSpaces() const {
    return mReplaceLengthBefore - mNewLengthBefore;
  }
  [[nodiscard]] uint32_t DeletingFollowingInvisibleWhiteSpaces() const {
    return mReplaceLengthAfter - mNewLengthAfter;
  }

  [[nodiscard]] nsDependentSubstring PrecedingWhiteSpaces() const {
    return Substring(mNormalizedString, 0u, mNewLengthBefore);
  }
  [[nodiscard]] nsDependentSubstring FollowingWhiteSpaces() const {
    return Substring(mNormalizedString,
                     mNormalizedString.Length() - mNewLengthAfter);
  }

  // Normalizes string which should be inserted.
  nsAutoString mNormalizedString;
  // Start offset in the `Text` to replace.
  const uint32_t mReplaceStartOffset;
  // End offset in the `Text` to replace.
  const uint32_t mReplaceEndOffset;
  // If it needs to replace preceding and/or following white-spaces, these
  // members store the length of white-spaces which should be replaced
  // before/after the insertion point.
  const uint32_t mReplaceLengthBefore = 0u;
  const uint32_t mReplaceLengthAfter = 0u;
  // If it needs to replace preceding and/or following white-spaces, these
  // members store the new length of white-spaces before/after the insertion
  // string.
  const uint32_t mNewLengthBefore = 0u;
  const uint32_t mNewLengthAfter = 0u;
};

/******************************************************************************
 * ReplaceWhiteSpacesData stores normalized string to replace white-spaces in
 * a `Text`.  If ReplaceLength() returns 0, this user needs to do nothing.
 ******************************************************************************/


struct MOZ_STACK_CLASS HTMLEditor::ReplaceWhiteSpacesData final {
  ReplaceWhiteSpacesData() = default;

  /**
   * @param aWhiteSpaces        The new white-spaces which we will replace the
   *                            range with.
   * @param aStartOffset        Replace start offset in the text node.
   * @param aReplaceLength      Replace length in the text node.
   * @param aOffsetAfterReplacing
   *                            [optional] If the caller may want to put caret
   *                            middle of the white-spaces, the offset may be
   *                            changed by deleting some invisible white-spaces.
   *                            Therefore, this may be set for the purpose.
   */

  ReplaceWhiteSpacesData(const nsAString& aWhiteSpaces, uint32_t aStartOffset,
                         uint32_t aReplaceLength,
                         uint32_t aOffsetAfterReplacing = UINT32_MAX)
      : mNormalizedString(aWhiteSpaces),
        mReplaceStartOffset(aStartOffset),
        mReplaceEndOffset(aStartOffset + aReplaceLength),
        mNewOffsetAfterReplace(aOffsetAfterReplacing) {
    MOZ_ASSERT(ReplaceLength() >= mNormalizedString.Length());
    MOZ_ASSERT_IF(mNewOffsetAfterReplace != UINT32_MAX,
                  mNewOffsetAfterReplace <=
                      mReplaceStartOffset + mNormalizedString.Length());
  }

  /**
   * @param aWhiteSpaces        The new white-spaces which we will replace the
   *                            range with.
   * @param aStartOffset        Replace start offset in the text node.
   * @param aReplaceLength      Replace length in the text node.
   * @param aOffsetAfterReplacing
   *                            [optional] If the caller may want to put caret
   *                            middle of the white-spaces, the offset may be
   *                            changed by deleting some invisible white-spaces.
   *                            Therefore, this may be set for the purpose.
   */

  ReplaceWhiteSpacesData(nsAutoString&& aWhiteSpaces, uint32_t aStartOffset,
                         uint32_t aReplaceLength,
                         uint32_t aOffsetAfterReplacing = UINT32_MAX)
      : mNormalizedString(std::forward<nsAutoString>(aWhiteSpaces)),
        mReplaceStartOffset(aStartOffset),
        mReplaceEndOffset(aStartOffset + aReplaceLength),
        mNewOffsetAfterReplace(aOffsetAfterReplacing) {
    MOZ_ASSERT(ReplaceLength() >= mNormalizedString.Length());
    MOZ_ASSERT_IF(mNewOffsetAfterReplace != UINT32_MAX,
                  mNewOffsetAfterReplace <=
                      mReplaceStartOffset + mNormalizedString.Length());
  }

  ReplaceWhiteSpacesData GetMinimizedData(const Text& aText) const {
    if (!ReplaceLength()) {
      return *this;
    }
    const dom::CharacterDataBuffer& characterDataBuffer = aText.DataBuffer();
    const auto minimizedReplaceStart = [&]() -> uint32_t {
      if (mNormalizedString.IsEmpty()) {
        return mReplaceStartOffset;
      }
      const uint32_t firstDiffCharOffset =
          characterDataBuffer.FindFirstDifferentCharOffset(mNormalizedString,
                                                           mReplaceStartOffset);
      if (firstDiffCharOffset == dom::CharacterDataBuffer::kNotFound) {
        // We don't need to insert new white-spaces,
        return mReplaceStartOffset + mNormalizedString.Length();
      }
      return firstDiffCharOffset;
    }();
    const auto minimizedReplaceEnd = [&]() -> uint32_t {
      if (mNormalizedString.IsEmpty()) {
        return mReplaceEndOffset;
      }
      if (minimizedReplaceStart ==
          mReplaceStartOffset + mNormalizedString.Length()) {
        // Note that here may be invisible white-spaces before
        // mReplaceEndOffset.  Then, this value may be larger than
        // minimizedReplaceStart.
        MOZ_ASSERT(mReplaceEndOffset >= minimizedReplaceStart);
        return mReplaceEndOffset;
      }
      if (ReplaceLength() != mNormalizedString.Length()) {
        // If we're deleting some invisible white-spaces, don't shrink the end
        // of the replacing range because it may shrink mNormalizedString too
        // much.
        return mReplaceEndOffset;
      }
      const auto lastDiffCharOffset =
          characterDataBuffer.RFindFirstDifferentCharOffset(mNormalizedString,
                                                            mReplaceEndOffset);
      MOZ_ASSERT(lastDiffCharOffset != dom::CharacterDataBuffer::kNotFound);
      return lastDiffCharOffset == dom::CharacterDataBuffer::kNotFound
                 ? mReplaceEndOffset
                 : lastDiffCharOffset + 1u;
    }();
    if (minimizedReplaceStart == mReplaceStartOffset &&
        minimizedReplaceEnd == mReplaceEndOffset) {
      return *this;
    }
    const uint32_t precedingUnnecessaryLength =
        minimizedReplaceStart - mReplaceStartOffset;
    const uint32_t followingUnnecessaryLength =
        mReplaceEndOffset - minimizedReplaceEnd;
    return ReplaceWhiteSpacesData(
        Substring(mNormalizedString, precedingUnnecessaryLength,
                  mNormalizedString.Length() - (precedingUnnecessaryLength +
                                                followingUnnecessaryLength)),
        minimizedReplaceStart, minimizedReplaceEnd - minimizedReplaceStart,
        mNewOffsetAfterReplace);
  }

  /**
   * Return the normalized string before mNewOffsetAfterReplace.  So,
   * mNewOffsetAfterReplace must not be UINT32_MAX and in the replaced range
   * when this is called.
   *
   * @param aReplaceEndOffset   Specify the offset in the Text node of
   *                            mNewOffsetAfterReplace before replacing with the
   *                            data.
   * @return The substring before mNewOffsetAfterReplace which is typically set
   * for new caret position in the Text node or collapsed deleting range
   * surrounded by the white-spaces.
   */

  [[nodiscard]] ReplaceWhiteSpacesData PreviousDataOfNewOffset(
      uint32_t aReplaceEndOffset) const {
    MOZ_ASSERT(mNewOffsetAfterReplace != UINT32_MAX);
    MOZ_ASSERT(mReplaceStartOffset <= mNewOffsetAfterReplace);
    MOZ_ASSERT(mReplaceEndOffset >= mNewOffsetAfterReplace);
    MOZ_ASSERT(mReplaceStartOffset <= aReplaceEndOffset);
    MOZ_ASSERT(mReplaceEndOffset >= aReplaceEndOffset);
    if (!ReplaceLength() || aReplaceEndOffset == mReplaceStartOffset) {
      return ReplaceWhiteSpacesData();
    }
    return ReplaceWhiteSpacesData(
        Substring(mNormalizedString, 0u,
                  mNewOffsetAfterReplace - mReplaceStartOffset),
        mReplaceStartOffset, aReplaceEndOffset - mReplaceStartOffset);
  }

  /**
   * Return the normalized string after mNewOffsetAfterReplace.  So,
   * mNewOffsetAfterReplace must not be UINT32_MAX and in the replaced range
   * when this is called.
   *
   * @param aReplaceStartOffset Specify the replace start offset with the
   *                            normalized white-spaces.
   * @return The substring after mNewOffsetAfterReplace which is typically set
   * for new caret position in the Text node or collapsed deleting range
   * surrounded by the white-spaces.
   */

  [[nodiscard]] ReplaceWhiteSpacesData NextDataOfNewOffset(
      uint32_t aReplaceStartOffset) const {
    MOZ_ASSERT(mNewOffsetAfterReplace != UINT32_MAX);
    MOZ_ASSERT(mReplaceStartOffset <= mNewOffsetAfterReplace);
    MOZ_ASSERT(mReplaceEndOffset >= mNewOffsetAfterReplace);
    MOZ_ASSERT(mReplaceStartOffset <= aReplaceStartOffset);
    MOZ_ASSERT(mReplaceEndOffset >= aReplaceStartOffset);
    if (!ReplaceLength() || aReplaceStartOffset == mReplaceEndOffset) {
      return ReplaceWhiteSpacesData();
    }
    return ReplaceWhiteSpacesData(
        Substring(mNormalizedString,
                  mNewOffsetAfterReplace - mReplaceStartOffset),
        aReplaceStartOffset, mReplaceEndOffset - aReplaceStartOffset);
  }

  [[nodiscard]] uint32_t ReplaceLength() const {
    return mReplaceEndOffset - mReplaceStartOffset;
  }
  [[nodiscard]] uint32_t DeletingInvisibleWhiteSpaces() const {
    return ReplaceLength() - mNormalizedString.Length();
  }

  [[nodiscard]] ReplaceWhiteSpacesData operator+(
      const ReplaceWhiteSpacesData& aOther) const {
    if (!ReplaceLength()) {
      return aOther;
    }
    if (!aOther.ReplaceLength()) {
      return *this;
    }
    MOZ_ASSERT(mReplaceEndOffset == aOther.mReplaceStartOffset);
    MOZ_ASSERT_IF(
        aOther.mNewOffsetAfterReplace != UINT32_MAX,
        aOther.mNewOffsetAfterReplace >= DeletingInvisibleWhiteSpaces());
    return ReplaceWhiteSpacesData(
        nsAutoString(mNormalizedString + aOther.mNormalizedString),
        mReplaceStartOffset, aOther.mReplaceEndOffset,
        aOther.mNewOffsetAfterReplace != UINT32_MAX
            ? aOther.mNewOffsetAfterReplace - DeletingInvisibleWhiteSpaces()
            : mNewOffsetAfterReplace);
  }

  nsAutoString mNormalizedString;
  const uint32_t mReplaceStartOffset = 0u;
  const uint32_t mReplaceEndOffset = 0u;
  // If the caller specifies a point in a white-space sequence, some invisible
  // white-spaces will be deleted with replacing them with normalized string.
  // Then, they may want to keep the position for putting caret or something.
  // So, this may store a specific offset in the text node after replacing.
  const uint32_t mNewOffsetAfterReplace = UINT32_MAX;
};

/******************************************************************************
 * A runnable to run HTMLEditor::OnModifiedDocument when it's safe.
 ******************************************************************************/


class HTMLEditor::DocumentModifiedEvent final : public Runnable {
 public:
  explicit DocumentModifiedEvent(HTMLEditor& aHTMLEditor)
      : Runnable("DocumentModifiedEvent"), mHTMLEditor(aHTMLEditor) {}

  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() {
    (void)MOZ_KnownLive(mHTMLEditor)->OnModifyDocument(*this);
    return NS_OK;
  }

  const nsTArray<EditorDOMPointInText>& NewInvisibleWhiteSpacesRef() const {
    return mNewInvisibleWhiteSpaces;
  }

 private:
  ~DocumentModifiedEvent() = default;

  const OwningNonNull<HTMLEditor> mHTMLEditor;
  nsTArray<EditorDOMPointInText> mNewInvisibleWhiteSpaces;
};

}  // namespace mozilla

#endif  // #ifndef HTMLEditorNestedClasses_h

Messung V0.5 in Prozent
C=91 H=99 G=94

¤ Dauer der Verarbeitung: 0.117 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

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

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.