tor-browser

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

WhiteSpaceVisibilityKeeper.h (18602B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #ifndef WhiteSpaceVisibilityKeeper_h
      7 #define WhiteSpaceVisibilityKeeper_h
      8 
      9 #include "EditAction.h"
     10 #include "EditorBase.h"
     11 #include "EditorForwards.h"
     12 #include "EditorDOMPoint.h"  // for EditorDOMPoint
     13 #include "EditorUtils.h"     // for CaretPoint
     14 #include "HTMLEditHelpers.h"
     15 #include "HTMLEditor.h"
     16 #include "HTMLEditUtils.h"
     17 #include "WSRunScanner.h"
     18 
     19 #include "mozilla/Assertions.h"
     20 #include "mozilla/Maybe.h"
     21 #include "mozilla/Result.h"
     22 #include "mozilla/StaticPrefs_editor.h"
     23 #include "mozilla/dom/Element.h"
     24 #include "mozilla/dom/HTMLBRElement.h"
     25 #include "mozilla/dom/Text.h"
     26 #include "nsCOMPtr.h"
     27 #include "nsIContent.h"
     28 
     29 namespace mozilla {
     30 
     31 /**
     32 * WhiteSpaceVisibilityKeeper class helps `HTMLEditor` modifying the DOM tree
     33 * with keeps white-space sequence visibility automatically.  E.g., invisible
     34 * leading/trailing white-spaces becomes visible, this class members delete
     35 * them.  E.g., when splitting visible-white-space sequence, this class may
     36 * replace ASCII white-spaces at split edges with NBSPs.
     37 */
     38 class WhiteSpaceVisibilityKeeper final {
     39 private:
     40  using AutoTransactionsConserveSelection =
     41      EditorBase::AutoTransactionsConserveSelection;
     42  using EditorType = EditorBase::EditorType;
     43  using Element = dom::Element;
     44  using HTMLBRElement = dom::HTMLBRElement;
     45  using IgnoreNonEditableNodes = WSRunScanner::IgnoreNonEditableNodes;
     46  using InsertTextTo = EditorBase::InsertTextTo;
     47  using LineBreakType = HTMLEditor::LineBreakType;
     48  using PointPosition = WSRunScanner::PointPosition;
     49  using ReferHTMLDefaultStyle = WSRunScanner::ReferHTMLDefaultStyle;
     50  using TextFragmentData = WSRunScanner::TextFragmentData;
     51  using VisibleWhiteSpacesData = WSRunScanner::VisibleWhiteSpacesData;
     52 
     53 public:
     54  WhiteSpaceVisibilityKeeper() = delete;
     55  explicit WhiteSpaceVisibilityKeeper(
     56      const WhiteSpaceVisibilityKeeper& aOther) = delete;
     57  WhiteSpaceVisibilityKeeper(WhiteSpaceVisibilityKeeper&& aOther) = delete;
     58 
     59  /**
     60   * Remove invisible leading white-spaces and trailing white-spaces if there
     61   * are around aPoint.
     62   */
     63  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
     64  DeleteInvisibleASCIIWhiteSpaces(HTMLEditor& aHTMLEditor,
     65                                  const EditorDOMPoint& aPoint);
     66 
     67  /**
     68   * PrepareToSplitBlockElement() makes sure that the invisible white-spaces
     69   * not to become visible and returns splittable point.
     70   *
     71   * @param aHTMLEditor         The HTML editor.
     72   * @param aPointToSplit       The splitting point in aSplittingBlockElement.
     73   * @param aSplittingBlockElement  A block element which will be split.
     74   */
     75  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
     76  PrepareToSplitBlockElement(HTMLEditor& aHTMLEditor,
     77                             const EditorDOMPoint& aPointToSplit,
     78                             const Element& aSplittingBlockElement);
     79 
     80  enum class NormalizeOption {
     81    // If set, don't normalize white-spaces before the point.
     82    HandleOnlyFollowingWhiteSpaces,
     83    // If set, don't normalize white-spaces after the point.
     84    HandleOnlyPrecedingWhiteSpaces,
     85    // If set, don't normalize following white-spaces if starts with an NBSP.
     86    StopIfFollowingWhiteSpacesStartsWithNBSP,
     87    // If set, don't normalize preceding white-spaces if ends with an NBSP.
     88    StopIfPrecedingWhiteSpacesEndsWithNBP,
     89  };
     90  using NormalizeOptions = EnumSet<NormalizeOption>;
     91 
     92  /**
     93   * Normalize preceding white-spaces of aPoint.  aPoint should not be middle of
     94   * a Text node.
     95   *
     96   * @return If this updates some characters of the last `Text` node, this
     97   * returns the end of the `Text`.  Otherwise, this returns the position
     98   * of the found `Text` which ends with a visible character or aPoint.
     99   * Note that returning aPoint does not mean nothing is changed.
    100   */
    101  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
    102  NormalizeWhiteSpacesBefore(HTMLEditor& aHTMLEditor,
    103                             const EditorDOMPoint& aPoint,
    104                             NormalizeOptions aOptions);
    105 
    106  /**
    107   * Normalize following white-spaces of aPoint.  aPoint should not be middle of
    108   * a Text node.
    109   *
    110   * @return If this updates some characters of the first `Text` node, this
    111   * returns the start of the `Text`.  Otherwise, this returns the position
    112   * of the found `Text` which starts with a visible character or aPoint.
    113   * Note that returning aPoint does not mean nothing is changed.
    114   */
    115  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
    116  NormalizeWhiteSpacesAfter(HTMLEditor& aHTMLEditor,
    117                            const EditorDOMPoint& aPoint,
    118                            NormalizeOptions aOptions);
    119 
    120  /**
    121   * Normalize surrounding white-spaces of aPointToSplit.  This may normalize
    122   * 2 `Text` nodes if the point is surrounded by them.
    123   * Note that this is designed only for the new normalizer.
    124   */
    125  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
    126  NormalizeWhiteSpacesToSplitAt(HTMLEditor& aHTMLEditor,
    127                                const EditorDOMPoint& aPointToSplit,
    128                                NormalizeOptions aOptions);
    129 
    130  /**
    131   * Normalize surrounding white-spaces of both boundaries of aRangeToDelete.
    132   * This returns the range which should be deleted later.
    133   */
    134  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMRange, nsresult>
    135  NormalizeSurroundingWhiteSpacesToJoin(HTMLEditor& aHTMLEditor,
    136                                        const EditorDOMRange& aRangeToDelete);
    137 
    138  /**
    139   * MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement() merges
    140   * first line in aRightBlockElement into end of aLeftBlockElement which
    141   * is a descendant of aRightBlockElement.
    142   *
    143   * @param aHTMLEditor         The HTML editor.
    144   * @param aLeftBlockElement   The content will be merged into end of
    145   *                            this element.
    146   * @param aRightBlockElement  The first line in this element will be
    147   *                            moved to aLeftBlockElement.
    148   * @param aAtRightBlockChild  At a child of aRightBlockElement and inclusive
    149   *                            ancestor of aLeftBlockElement.
    150   * @param aListElementTagName Set some if aRightBlockElement is a list
    151   *                            element and it'll be merged with another
    152   *                            list element.
    153   * @param aEditingHost        The editing host.
    154   */
    155  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<MoveNodeResult, nsresult>
    156  MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement(
    157      HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
    158      Element& aRightBlockElement, const EditorDOMPoint& aAtRightBlockChild,
    159      const Maybe<nsAtom*>& aListElementTagName,
    160      const HTMLBRElement* aPrecedingInvisibleBRElement,
    161      const Element& aEditingHost);
    162 
    163  /**
    164   * MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement() merges
    165   * first line in aRightBlockElement into end of aLeftBlockElement which
    166   * is an ancestor of aRightBlockElement, then, removes aRightBlockElement
    167   * if it becomes empty.
    168   *
    169   * @param aHTMLEditor         The HTML editor.
    170   * @param aLeftBlockElement   The content will be merged into end of
    171   *                            this element.
    172   * @param aRightBlockElement  The first line in this element will be
    173   *                            moved to aLeftBlockElement and maybe
    174   *                            removed when this becomes empty.
    175   * @param aAtLeftBlockChild   At a child of aLeftBlockElement and inclusive
    176   *                            ancestor of aRightBlockElement.
    177   * @param aLeftContentInBlock The content whose inclusive ancestor is
    178   *                            aLeftBlockElement.
    179   * @param aListElementTagName Set some if aRightBlockElement is a list
    180   *                            element and it'll be merged with another
    181   *                            list element.
    182   * @param aEditingHost        The editing host.
    183   */
    184  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<MoveNodeResult, nsresult>
    185  MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement(
    186      HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
    187      Element& aRightBlockElement, const EditorDOMPoint& aAtLeftBlockChild,
    188      nsIContent& aLeftContentInBlock,
    189      const Maybe<nsAtom*>& aListElementTagName,
    190      const HTMLBRElement* aPrecedingInvisibleBRElement,
    191      const Element& aEditingHost);
    192 
    193  /**
    194   * MergeFirstLineOfRightBlockElementIntoLeftBlockElement() merges first
    195   * line in aRightBlockElement into end of aLeftBlockElement and removes
    196   * aRightBlockElement when it has only one line.
    197   *
    198   * @param aHTMLEditor         The HTML editor.
    199   * @param aLeftBlockElement   The content will be merged into end of
    200   *                            this element.
    201   * @param aRightBlockElement  The first line in this element will be
    202   *                            moved to aLeftBlockElement and maybe
    203   *                            removed when this becomes empty.
    204   * @param aListElementTagName Set some if aRightBlockElement is a list
    205   *                            element and its type needs to be changed.
    206   * @param aEditingHost        The editing host.
    207   */
    208  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<MoveNodeResult, nsresult>
    209  MergeFirstLineOfRightBlockElementIntoLeftBlockElement(
    210      HTMLEditor& aHTMLEditor, Element& aLeftBlockElement,
    211      Element& aRightBlockElement, const Maybe<nsAtom*>& aListElementTagName,
    212      const HTMLBRElement* aPrecedingInvisibleBRElement,
    213      const Element& aEditingHost);
    214 
    215  /**
    216   * InsertLineBreak() inserts a line break at (before) aPointToInsert and
    217   * delete unnecessary white-spaces around there and/or replaces white-spaces
    218   * with non-breaking spaces.  Note that if the point is in a text node, the
    219   * text node will be split and insert new <br> node between the left node
    220   * and the right node.
    221   *
    222   * @param aPointToInsert  The point to insert new line break.  Note that
    223   *                        it'll be inserted before this point.  I.e., the
    224   *                        point will be the point of new line break.
    225   * @return                If succeeded, returns the new line break and
    226   *                        point to put caret.
    227   */
    228  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CreateLineBreakResult,
    229                                                 nsresult>
    230  InsertLineBreak(LineBreakType aLineBreakType, HTMLEditor& aHTMLEditor,
    231                  const EditorDOMPoint& aPointToInsert);
    232 
    233  using InsertTextFor = EditorBase::InsertTextFor;
    234 
    235  /**
    236   * Insert aStringToInsert to aPointToInsert and makes any needed adjustments
    237   * to white-spaces around the insertion point.
    238   *
    239   * @param aStringToInsert     The string to insert.
    240   * @param aRangeToBeReplaced  The range to be replaced.
    241   * @param aInsertTextTo       Whether forcibly creates a new `Text` node in
    242   *                            specific condition or use existing `Text` if
    243   *                            available.
    244   */
    245  template <typename EditorDOMPointType>
    246  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<InsertTextResult, nsresult>
    247  InsertText(HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert,
    248             const EditorDOMPointType& aPointToInsert,
    249             InsertTextTo aInsertTextTo) {
    250    return WhiteSpaceVisibilityKeeper::
    251        InsertTextOrInsertOrUpdateCompositionString(
    252            aHTMLEditor, aStringToInsert, EditorDOMRange(aPointToInsert),
    253            aInsertTextTo, InsertTextFor::NormalText);
    254  }
    255 
    256  /**
    257   * Insert aCompositionString to the start boundary of aCompositionStringRange
    258   * or update existing composition string with aCompositionString.
    259   * If inserting composition string, this may normalize white-spaces around
    260   * there.  However, if updating composition string, this will skip it to
    261   * avoid CompositionTransaction work.
    262   *
    263   * @param aCompositionString  The new composition string.
    264   * @param aCompositionStringRange
    265   *                            If there is old composition string, this should
    266   *                            cover all of it.  Otherwise, this should be
    267   *                            collapsed and indicate the insertion point.
    268   */
    269  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<InsertTextResult, nsresult>
    270  InsertOrUpdateCompositionString(HTMLEditor& aHTMLEditor,
    271                                  const nsAString& aCompositionString,
    272                                  const EditorDOMRange& aCompositionStringRange,
    273                                  InsertTextFor aPurpose) {
    274    MOZ_ASSERT(EditorBase::InsertingTextForComposition(aPurpose));
    275    return InsertTextOrInsertOrUpdateCompositionString(
    276        aHTMLEditor, aCompositionString, aCompositionStringRange,
    277        HTMLEditor::InsertTextTo::ExistingTextNodeIfAvailable, aPurpose);
    278  }
    279 
    280  /**
    281   * Normalize white-space sequence containing aPoint or starts from next to
    282   * aPoint.  This assumes all white-spaces in the sequence is visible.
    283   */
    284  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
    285  NormalizeVisibleWhiteSpacesWithoutDeletingInvisibleWhiteSpaces(
    286      HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPoint);
    287 
    288  /**
    289   * Delete aContentToDelete and may remove/replace white-spaces around it.
    290   * Then, if deleting content makes 2 text nodes around it are adjacent
    291   * siblings, this joins them and put selection at the joined point.
    292   */
    293  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<CaretPoint, nsresult>
    294  DeleteContentNodeAndJoinTextNodesAroundIt(HTMLEditor& aHTMLEditor,
    295                                            nsIContent& aContentToDelete,
    296                                            const EditorDOMPoint& aCaretPoint,
    297                                            const Element& aEditingHost);
    298 
    299 private:
    300  /**
    301   * ReplaceTextAndRemoveEmptyTextNodes() replaces the range between
    302   * aRangeToReplace with aReplaceString simply.  Additionally, removes
    303   * empty text nodes in the range.
    304   *
    305   * @param aRangeToReplace     Range to replace text.
    306   * @param aReplaceString      The new string.  Empty string is allowed.
    307   */
    308  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
    309  ReplaceTextAndRemoveEmptyTextNodes(
    310      HTMLEditor& aHTMLEditor, const EditorDOMRangeInTexts& aRangeToReplace,
    311      const nsAString& aReplaceString);
    312 
    313  /**
    314   * Normalize surrounding white-spaces of aPointToSplit.
    315   *
    316   * @return The split point which you specified before.  Note that the result
    317   *         may be different from aPointToSplit if this deletes some invisible
    318   *         white-spaces.
    319   */
    320  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
    321  NormalizeWhiteSpacesToSplitTextNodeAt(
    322      HTMLEditor& aHTMLEditor, const EditorDOMPointInText& aPointToSplit,
    323      NormalizeOptions aOptions);
    324 
    325  /**
    326   * Normalize surrounding white-spaces of the range between aOffset - aOffset +
    327   * aLength.
    328   *
    329   * @return The delete range after normalized.
    330   */
    331  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMRange, nsresult>
    332  NormalizeSurroundingWhiteSpacesToDeleteCharacters(HTMLEditor& aHTMLEditor,
    333                                                    dom::Text& aTextNode,
    334                                                    uint32_t aOffset,
    335                                                    uint32_t aLength);
    336 
    337  /**
    338   * Delete leading or trailing invisible white-spaces around block boundaries
    339   * or collapsed white-spaces in a white-space sequence if aPoint is around
    340   * them.
    341   *
    342   * @param aHTMLEditor The HTMLEditor.
    343   * @param aPoint      Point must be in an editable content node.
    344   * @return            If deleted some invisible white-spaces, returns the
    345   *                    removed point.
    346   *                    If this does nothing, returns unset point.
    347   */
    348  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
    349  EnsureNoInvisibleWhiteSpaces(HTMLEditor& aHTMLEditor,
    350                               const EditorDOMPoint& aPoint);
    351 
    352  /**
    353   * Delete preceding invisible white-spaces before aPoint.
    354   */
    355  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
    356  EnsureNoInvisibleWhiteSpacesBefore(HTMLEditor& aHTMLEditor,
    357                                     const EditorDOMPoint& aPoint);
    358 
    359  /**
    360   * Delete following invisible white-spaces after aPoint.
    361   */
    362  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult
    363  EnsureNoInvisibleWhiteSpacesAfter(HTMLEditor& aHTMLEditor,
    364                                    const EditorDOMPoint& aPoint);
    365 
    366  /**
    367   * If aPoint points a collapsible white-space, normalize entire the
    368   * white-space sequence.
    369   *
    370   * @return Equivalent point of aPoint after normalizing the white-spaces.
    371   */
    372  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<EditorDOMPoint, nsresult>
    373  NormalizeWhiteSpacesAt(HTMLEditor& aHTMLEditor,
    374                         const EditorDOMPointInText& aPoint);
    375 
    376  /**
    377   * Insert aStringToInsert to aRangeToBeReplaced.StartRef() with normalizing
    378   * white-spaces around there.
    379   *
    380   * @param aStringToInsert     The string to insert.
    381   * @param aRangeToBeReplaced  If you insert non-composing text, this MUST be
    382   *                            collapsed to the insertion point.
    383   *                            If you update composition string, this may be
    384   *                            not collapsed.  The range is required to
    385   *                            normalizing the new composition string.
    386   *                            Therefore, this should match the range of the
    387   *                            latest composition string.
    388   * @param aInsertTextTo       Whether forcibly creates a new `Text` node in
    389   *                            specific condition or use existing `Text` if
    390   *                            available.
    391   * @param aPurpose            Whether it's handling normal text input or
    392   *                            updating composition.
    393   */
    394  [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<InsertTextResult, nsresult>
    395  InsertTextOrInsertOrUpdateCompositionString(
    396      HTMLEditor& aHTMLEditor, const nsAString& aStringToInsert,
    397      const EditorDOMRange& aRangeToBeReplaced, InsertTextTo aInsertTextTo,
    398      InsertTextFor aPurpose);
    399 };
    400 
    401 }  // namespace mozilla
    402 
    403 #endif  // #ifndef WhiteSpaceVisibilityKeeper_h