Unfortunately, this patch becomes big because this includes multiple utility
methods to normalize white-spaces and touching a lot of places.
In some edge cases, this starts failing when the new white-space normalizer is
enabled.
```
FAIL execCommand("forwarddelete", false, ""): "a [] | b" - assert_equals: Modified text is wrong expected "a " but got "a "
FAIL execCommand("forwarddelete", false, ""): "a [] | b" - assert_equals: Modified text is wrong expected "a " but got "a "
FAIL execCommand("forwarddelete", false, ""): "a []b| c" - assert_equals: Modified text is wrong expected "a " but got "a "
FAIL execCommand("forwarddelete", false, ""): "a [] <span style=white-space:pre;> </span>" - assert_equals: expected "a <span style=\"white-space:pre;\"> </span>" but got "a <span style=\"white-space:pre;\"> </span>"
```
In these failures, we normalize the trailing white-spaces of the preceding
`Text` too, but Chrome does not do that. However, in some cases, they do
in similar cases. Therefore, it's hard to align all cases due to the handling
order difference. Additionally, these failure results are not odd. So, these
new failures are acceptable.
```
FAIL execCommand("forwarddelete", false, ""): "<span>abc[] <span> def</span></span>" - assert_equals: expected "<span>abc<span> def</span></span>" but got "<span>abc<span> def</span></span>"
FAIL execCommand("forwarddelete", false, ""): "<span><span>abc[] </span> def</span>" - assert_equals: expected "<span><span>abc</span> def</span>" but got "<span><span>abc</span> def</span>"
FAIL execCommand("forwarddelete", false, ""): "<span>abc[] </span><span> def</span>" - assert_equals: expected "<span>abc</span><span> def</span>" but got "<span>abc</span><span> def</span>"
FAIL execCommand("forwarddelete", false, ""): "<span>abc[] </span><span> def</span>" - assert_equals: expected "<span>abc </span><span> def</span>" but got "<span>abc </span><span> def</span>"
```
In these failures, we don't normalize the leading white-spaces of the following
`Text`. However, Chrome does so in some other cases. So, it's hard to align
the behavior in these cases too. The new normalizer ensures that the preceding
`Text` ends with a visible char. Therefore, the following `Text` won't starts
with new invisible white-spaces. Therefore, these failures are also acceptable.
```
FAIL execCommand("forwarddelete", false, ""): "<span style=white-space:pre;> [] </span> a" - assert_equals: expected "<span style=\"white-space:pre;\"> </span> a" but got "<span style=\"white-space:pre;\"> </span> a"
```
In this failure, we don't normalize the following `Text` but Chrome does it.
However, I don't think we should do it because this updates the `Text` which
is preformatted. Therefore, this is also acceptable.
So, I think that we can accept all new failures.
Differential Revision: https://phabricator.services.mozilla.com/D239468
900 lines
39 KiB
C++
900 lines
39 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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/Text.h"
|
|
#include "nsTextFragment.h"
|
|
|
|
namespace mozilla {
|
|
|
|
/*****************************************************************************
|
|
* 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;
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|
|
/******************************************************************************
|
|
* 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 nsTextFragment& textFragment = aText.TextFragment();
|
|
const uint32_t minimizedReplaceStart = [&]() {
|
|
const auto firstDiffCharOffset =
|
|
mNewLengthBefore ? textFragment.FindFirstDifferentCharOffset(
|
|
PrecedingWhiteSpaces(), mReplaceStartOffset)
|
|
: nsTextFragment::kNotFound;
|
|
if (firstDiffCharOffset == nsTextFragment::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 ? textFragment.RFindFirstDifferentCharOffset(
|
|
FollowingWhiteSpaces(), mReplaceEndOffset)
|
|
: nsTextFragment::kNotFound;
|
|
if (lastDiffCharOffset == nsTextFragment::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 nsTextFragment& textFragment = aText.TextFragment();
|
|
const auto minimizedReplaceStart = [&]() -> uint32_t {
|
|
if (mNormalizedString.IsEmpty()) {
|
|
return mReplaceStartOffset;
|
|
}
|
|
const uint32_t firstDiffCharOffset =
|
|
textFragment.FindFirstDifferentCharOffset(mNormalizedString,
|
|
mReplaceStartOffset);
|
|
if (firstDiffCharOffset == nsTextFragment::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 =
|
|
textFragment.RFindFirstDifferentCharOffset(mNormalizedString,
|
|
mReplaceEndOffset);
|
|
MOZ_ASSERT(lastDiffCharOffset != nsTextFragment::kNotFound);
|
|
return lastDiffCharOffset == nsTextFragment::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;
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // #ifndef HTMLEditorNestedClasses_h
|