/* -*- 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 WhiteSpaceVisibilityKeeper_h #define WhiteSpaceVisibilityKeeper_h #include "EditAction.h" #include "EditorBase.h" #include "EditorForwards.h" #include "EditorDOMPoint.h" // for EditorDOMPoint #include "EditorUtils.h" // for CaretPoint #include "HTMLEditHelpers.h" #include "HTMLEditor.h" #include "HTMLEditUtils.h" #include "WSRunScanner.h" #include "mozilla/Assertions.h" #include "mozilla/Maybe.h" #include "mozilla/Result.h" #include "mozilla/StaticPrefs_editor.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLBRElement.h" #include "mozilla/dom/Text.h" #include "nsCOMPtr.h" #include "nsIContent.h" namespace mozilla { /** * WhiteSpaceVisibilityKeeper class helps `HTMLEditor` modifying the DOM tree * with keeps white-space sequence visibility automatically. E.g., invisible * leading/trailing white-spaces becomes visible, this class members delete * them. E.g., when splitting visible-white-space sequence, this class may * replace ASCII white-spaces at split edges with NBSPs. */ class WhiteSpaceVisibilityKeeper final { private: using AutoTransactionsConserveSelection = EditorBase::AutoTransactionsConserveSelection; using EditorType = EditorBase::EditorType; using Element = dom::Element; using HTMLBRElement = dom::HTMLBRElement; using IgnoreNonEditableNodes = WSRunScanner::IgnoreNonEditableNodes; using InsertTextTo = EditorBase::InsertTextTo; using LineBreakType = HTMLEditor::LineBreakType; using PointPosition = WSRunScanner::PointPosition; using Scan = WSRunScanner::Scan; using TextFragmentData = WSRunScanner::TextFragmentData; using VisibleWhiteSpacesData = WSRunScanner::VisibleWhiteSpacesData; public: WhiteSpaceVisibilityKeeper() = delete; explicit WhiteSpaceVisibilityKeeper( const WhiteSpaceVisibilityKeeper& aOther) = delete; WhiteSpaceVisibilityKeeper(WhiteSpaceVisibilityKeeper&& aOther) = delete; /** * Remove invisible leading white-spaces and trailing white-spaces if there * are around aPoint. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result DeleteInvisibleASCIIWhiteSpaces(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint); /** * PrepareToSplitBlockElement() makes sure that the invisible white-spaces * not to become visible and returns splittable point. * * @param aHTMLEditor The HTML editor. * @param aPointToSplit The splitting point in aSplittingBlockElement. * @param aSplittingBlockElement A block element which will be split. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result PrepareToSplitBlockElement(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit, const Element& aSplittingBlockElement); enum class NormalizeOption { // If set, don't normalize white-spaces before the point. HandleOnlyFollowingWhiteSpaces, // If set, don't normalize white-spaces after the point. HandleOnlyPrecedingWhiteSpaces, // If set, don't normalize following white-spaces if starts with an NBSP. StopIfFollowingWhiteSpacesStartsWithNBSP, // If set, don't normalize preceding white-spaces if ends with an NBSP. StopIfPrecedingWhiteSpacesEndsWithNBP, }; using NormalizeOptions = EnumSet; /** * Normalize preceding white-spaces of aPoint. aPoint should not be middle of * a Text node. * * @return If this updates some characters of the last `Text` node, this * returns the end of the `Text`. Otherwise, this returns the position * of the found `Text` which ends with a visible character or aPoint. * Note that returning aPoint does not mean nothing is changed. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result NormalizeWhiteSpacesBefore(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint, NormalizeOptions aOptions); /** * Normalize following white-spaces of aPoint. aPoint should not be middle of * a Text node. * * @return If this updates some characters of the first `Text` node, this * returns the start of the `Text`. Otherwise, this returns the position * of the found `Text` which starts with a visible character or aPoint. * Note that returning aPoint does not mean nothing is changed. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result NormalizeWhiteSpacesAfter(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint, NormalizeOptions aOptions); /** * Normalize surrounding white-spaces of aPointToSplit. This may normalize * 2 `Text` nodes if the point is surrounded by them. * Note that this is designed only for the new normalizer. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result NormalizeWhiteSpacesToSplitAt(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToSplit, NormalizeOptions aOptions); /** * Normalize surrounding white-spaces of both boundaries of aRangeToDelete. * This returns the range which should be deleted later. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result NormalizeSurroundingWhiteSpacesToJoin(HTMLEditor& aHTMLEditor, const EditorDOMRange& aRangeToDelete); /** * MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges * first line in aRightBlockElement into end of aLeftBlockElement which * is a descendant of aRightBlockElement. * * @param aHTMLEditor The HTML editor. * @param aLeftBlockElement The content will be merged into end of * this element. * @param aRightBlockElement The first line in this element will be * moved to aLeftBlockElement. * @param aAtRightBlockChild At a child of aRightBlockElement and inclusive * ancestor of aLeftBlockElement. * @param aListElementTagName Set some if aRightBlockElement is a list * element and it'll be merged with another * list element. * @param aEditingHost The editing host. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement( HTMLEditor& aHTMLEditor, Element& aLeftBlockElement, Element& aRightBlockElement, const EditorDOMPoint& aAtRightBlockChild, const Maybe& aListElementTagName, const HTMLBRElement* aPrecedingInvisibleBRElement, const Element& aEditingHost); /** * MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() merges * first line in aRightBlockElement into end of aLeftBlockElement which * is an ancestor of aRightBlockElement, then, removes aRightBlockElement * if it becomes empty. * * @param aHTMLEditor The HTML editor. * @param aLeftBlockElement The content will be merged into end of * this element. * @param aRightBlockElement The first line in this element will be * moved to aLeftBlockElement and maybe * removed when this becomes empty. * @param aAtLeftBlockChild At a child of aLeftBlockElement and inclusive * ancestor of aRightBlockElement. * @param aLeftContentInBlock The content whose inclusive ancestor is * aLeftBlockElement. * @param aListElementTagName Set some if aRightBlockElement is a list * element and it'll be merged with another * list element. * @param aEditingHost The editing host. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement( HTMLEditor& aHTMLEditor, Element& aLeftBlockElement, Element& aRightBlockElement, const EditorDOMPoint& aAtLeftBlockChild, nsIContent& aLeftContentInBlock, const Maybe& aListElementTagName, const HTMLBRElement* aPrecedingInvisibleBRElement, const Element& aEditingHost); /** * MergeFirstLineOfRightBlockElementIntoLeftBlockElement() merges first * line in aRightBlockElement into end of aLeftBlockElement and removes * aRightBlockElement when it has only one line. * * @param aHTMLEditor The HTML editor. * @param aLeftBlockElement The content will be merged into end of * this element. * @param aRightBlockElement The first line in this element will be * moved to aLeftBlockElement and maybe * removed when this becomes empty. * @param aListElementTagName Set some if aRightBlockElement is a list * element and its type needs to be changed. * @param aEditingHost The editing host. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result MergeFirstLineOfRightBlockElementIntoLeftBlockElement( HTMLEditor& aHTMLEditor, Element& aLeftBlockElement, Element& aRightBlockElement, const Maybe& aListElementTagName, const HTMLBRElement* aPrecedingInvisibleBRElement, const Element& aEditingHost); /** * InsertLineBreak() inserts a line break at (before) aPointToInsert and * delete unnecessary white-spaces around there and/or replaces white-spaces * with non-breaking spaces. Note that if the point is in a text node, the * text node will be split and insert new
node between the left node * and the right node. * * @param aPointToInsert The point to insert new line break. Note that * it'll be inserted before this point. I.e., the * point will be the point of new line break. * @return If succeeded, returns the new line break and * point to put caret. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result InsertLineBreak(LineBreakType aLineBreakType, HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert); using InsertTextFor = EditorBase::InsertTextFor; /** * Insert aStringToInsert to aPointToInsert and makes any needed adjustments * to white-spaces around the insertion point. * * @param aStringToInsert The string to insert. * @param aRangeToBeReplaced The range to be replaced. * @param aInsertTextTo Whether forcibly creates a new `Text` node in * specific condition or use existing `Text` if * available. */ template [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result InsertText(HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert, const EditorDOMPointType& aPointToInsert, InsertTextTo aInsertTextTo) { return WhiteSpaceVisibilityKeeper:: InsertTextOrInsertOrUpdateCompositionString( aHTMLEditor, aStringToInsert, EditorDOMRange(aPointToInsert), aInsertTextTo, InsertTextFor::NormalText); } /** * Insert aCompositionString to the start boundary of aCompositionStringRange * or update existing composition string with aCompositionString. * If inserting composition string, this may normalize white-spaces around * there. However, if updating composition string, this will skip it to * avoid CompositionTransaction work. * * @param aCompositionString The new composition string. * @param aCompositionStringRange * If there is old composition string, this should * cover all of it. Otherwise, this should be * collapsed and indicate the insertion point. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result InsertOrUpdateCompositionString(HTMLEditor& aHTMLEditor, const nsAString& aCompositionString, const EditorDOMRange& aCompositionStringRange, InsertTextFor aPurpose) { MOZ_ASSERT(EditorBase::InsertingTextForComposition(aPurpose)); return InsertTextOrInsertOrUpdateCompositionString( aHTMLEditor, aCompositionString, aCompositionStringRange, HTMLEditor::InsertTextTo::ExistingTextNodeIfAvailable, aPurpose); } /** * Normalize white-space sequence containing aPoint or starts from next to * aPoint. This assumes all white-spaces in the sequence is visible. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces( HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPoint); /** * Delete aContentToDelete and may remove/replace white-spaces around it. * Then, if deleting content makes 2 text nodes around it are adjacent * siblings, this joins them and put selection at the joined point. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result DeleteContentNodeAndJoinTextNodesAroundIt(HTMLEditor& aHTMLEditor, nsIContent& aContentToDelete, const EditorDOMPoint& aCaretPoint, const Element& aEditingHost); private: /** * ReplaceTextAndRemoveEmptyTextNodes() replaces the range between * aRangeToReplace with aReplaceString simply. Additionally, removes * empty text nodes in the range. * * @param aRangeToReplace Range to replace text. * @param aReplaceString The new string. Empty string is allowed. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult ReplaceTextAndRemoveEmptyTextNodes( HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace, const nsAString& aReplaceString); /** * Normalize surrounding white-spaces of aPointToSplit. * * @return The split point which you specified before. Note that the result * may be different from aPointToSplit if this deletes some invisible * white-spaces. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result NormalizeWhiteSpacesToSplitTextNodeAt( HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPointToSplit, NormalizeOptions aOptions); /** * Normalize surrounding white-spaces of the range between aOffset - aOffset + * aLength. * * @return The delete range after normalized. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result NormalizeSurroundingWhiteSpacesToDeleteCharacters(HTMLEditor& aHTMLEditor, dom::Text& aTextNode, uint32_t aOffset, uint32_t aLength); /** * Delete leading or trailing invisible white-spaces around block boundaries * or collapsed white-spaces in a white-space sequence if aPoint is around * them. * * @param aHTMLEditor The HTMLEditor. * @param aPoint Point must be in an editable content node. * @return If deleted some invisible white-spaces, returns the * removed point. * If this does nothing, returns unset point. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result EnsureNoInvisibleWhiteSpaces(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint); /** * Delete preceding invisible white-spaces before aPoint. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult EnsureNoInvisibleWhiteSpacesBefore(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint); /** * Delete following invisible white-spaces after aPoint. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult EnsureNoInvisibleWhiteSpacesAfter(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPoint); /** * If aPoint points a collapsible white-space, normalize entire the * white-space sequence. * * @return Equivalent point of aPoint after normalizing the white-spaces. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result NormalizeWhiteSpacesAt(HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPoint); /** * Insert aStringToInsert to aRangeToBeReplaced.StartRef() with normalizing * white-spaces around there. * * @param aStringToInsert The string to insert. * @param aRangeToBeReplaced If you insert non-composing text, this MUST be * collapsed to the insertion point. * If you update composition string, this may be * not collapsed. The range is required to * normalizing the new composition string. * Therefore, this should match the range of the * latest composition string. * @param aInsertTextTo Whether forcibly creates a new `Text` node in * specific condition or use existing `Text` if * available. * @param aPurpose Whether it's handling normal text input or * updating composition. */ [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result InsertTextOrInsertOrUpdateCompositionString( HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert, const EditorDOMRange& aRangeToBeReplaced, InsertTextTo aInsertTextTo, InsertTextFor aPurpose); }; } // namespace mozilla #endif // #ifndef WhiteSpaceVisibilityKeeper_h