/* 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, 0 u);
}
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() : 0 u),
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 + 1 u;
}();
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, 0 u, 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 = 0 u;
const uint32_t mReplaceLengthAfter = 0 u;
// 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 = 0 u;
const uint32_t mNewLengthAfter = 0 u;
};
/******************************************************************************
* 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 + 1 u;
}();
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, 0 u,
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 = 0 u;
const uint32_t mReplaceEndOffset = 0 u;
// 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