tor-browser

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

HTMLEditHelpers.h (56575B)


      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 HTMLEditHelpers_h
      7 #define HTMLEditHelpers_h
      8 
      9 /**
     10 * This header declares/defines trivial helper classes which are used by
     11 * HTMLEditor.  If you want to create or look for static utility methods,
     12 * see HTMLEditUtils.h.
     13 */
     14 
     15 #include "EditorDOMPoint.h"
     16 #include "EditorForwards.h"
     17 #include "EditorUtils.h"  // for CaretPoint
     18 
     19 #include "mozilla/Attributes.h"
     20 #include "mozilla/ContentIterator.h"
     21 #include "mozilla/Maybe.h"
     22 #include "mozilla/RangeBoundary.h"
     23 #include "mozilla/Result.h"
     24 #include "mozilla/dom/Element.h"
     25 #include "mozilla/dom/StaticRange.h"
     26 
     27 #include "nsCOMPtr.h"
     28 #include "nsDebug.h"
     29 #include "nsError.h"
     30 #include "nsGkAtoms.h"
     31 #include "nsIContent.h"
     32 #include "nsRange.h"
     33 #include "nsString.h"
     34 
     35 class nsISimpleEnumerator;
     36 
     37 namespace mozilla {
     38 
     39 enum class BlockInlineCheck : uint8_t {
     40  // BlockInlineCheck is not expected by the root caller.
     41  Unused,
     42  // Refer only the HTML default style at considering whether block or inline.
     43  // All non-HTML elements are treated as inline.
     44  UseHTMLDefaultStyle,
     45  // Refer the element's computed style of display-outside at considering
     46  // whether block or inline.
     47  UseComputedDisplayOutsideStyle,
     48  // Refer the element's computed style of display at considering whether block
     49  // or inline.  I.e., this is a good value to look for any block boundary.
     50  // E.g., this is proper value when:
     51  // * Checking visibility of collapsible white-spaces or <br>
     52  // * Looking for whether a padding <br> is required
     53  // * Looking for a caret position
     54  UseComputedDisplayStyle,
     55  // UseComputedDisplayOutsideStyle if referring siblings or children.
     56  // UseComputedDisplayStyle if referring ancestors.
     57  Auto,
     58 };
     59 
     60 [[nodiscard]] inline BlockInlineCheck PreferDisplayOutsideIfUsingDisplay(
     61    BlockInlineCheck aBlockInlineCheck) {
     62  return aBlockInlineCheck == BlockInlineCheck::UseComputedDisplayStyle
     63             ? BlockInlineCheck::UseComputedDisplayOutsideStyle
     64             : aBlockInlineCheck;
     65 }
     66 
     67 [[nodiscard]] inline BlockInlineCheck PreferDisplayIfUsingDisplayOutside(
     68    BlockInlineCheck aBlockInlineCheck) {
     69  return aBlockInlineCheck == BlockInlineCheck::UseComputedDisplayOutsideStyle
     70             ? BlockInlineCheck::UseComputedDisplayStyle
     71             : aBlockInlineCheck;
     72 }
     73 
     74 [[nodiscard]] inline BlockInlineCheck UseComputedDisplayStyleIfAuto(
     75    BlockInlineCheck aBlockInlineCheck) {
     76  return aBlockInlineCheck == BlockInlineCheck::Auto
     77             // Treat flow-root as a block such as inline-block.
     78             ? BlockInlineCheck::UseComputedDisplayStyle
     79             : aBlockInlineCheck;
     80 }
     81 
     82 [[nodiscard]] inline BlockInlineCheck UseComputedDisplayOutsideStyleIfAuto(
     83    BlockInlineCheck aBlockInlineCheck) {
     84  return aBlockInlineCheck == BlockInlineCheck::Auto
     85             // Use display-outside for checking a sibling or child element as a
     86             // block.
     87             ? BlockInlineCheck::UseComputedDisplayOutsideStyle
     88             : aBlockInlineCheck;
     89 }
     90 
     91 enum class WithTransaction { No, Yes };
     92 inline std::ostream& operator<<(std::ostream& aStream,
     93                                WithTransaction aWithTransaction) {
     94  aStream << "WithTransaction::"
     95          << (aWithTransaction == WithTransaction::Yes ? "Yes" : "No");
     96  return aStream;
     97 }
     98 
     99 /*****************************************************************************
    100 * MoveNodeResult is a simple class for MoveSomething() methods.
    101 * This stores whether it's handled or not, and next insertion point and a
    102 * suggestion for new caret position and the moved content range which contains
    103 * all content which are moved by the moves.
    104 *****************************************************************************/
    105 class MOZ_STACK_CLASS MoveNodeResult final : public CaretPoint,
    106                                             public EditActionResult {
    107 public:
    108  constexpr const EditorDOMPoint& NextInsertionPointRef() const {
    109    return mNextInsertionPoint;
    110  }
    111  constexpr EditorDOMPoint&& UnwrapNextInsertionPoint() {
    112    return std::move(mNextInsertionPoint);
    113  }
    114  constexpr const EditorDOMRange& MovedContentRangeRef() const {
    115    return mMovedContentRange;
    116  }
    117  constexpr EditorDOMRange&& UnwrapMovedContentRange() {
    118    return std::move(mMovedContentRange);
    119  }
    120  template <typename EditorDOMPointType>
    121  EditorDOMPointType NextInsertionPoint() const {
    122    return mNextInsertionPoint.To<EditorDOMPointType>();
    123  }
    124 
    125  MoveNodeResult(const MoveNodeResult& aOther) = delete;
    126  MoveNodeResult& operator=(const MoveNodeResult& aOther) = delete;
    127  MoveNodeResult(MoveNodeResult&& aOther) = default;
    128  MoveNodeResult& operator=(MoveNodeResult&& aOther) = default;
    129 
    130  MoveNodeResult& operator|=(const MoveNodeResult& aOther) {
    131    MOZ_ASSERT(this != &aOther);
    132    // aOther is merged with this instance so that its caret suggestion
    133    // shouldn't be handled anymore.
    134    aOther.IgnoreCaretPointSuggestion();
    135    // Should be handled again even if it's already handled
    136    UnmarkAsHandledCaretPoint();
    137 
    138    if (aOther.Canceled()) {
    139      MOZ_ASSERT_UNREACHABLE("How was aOther canceled?");
    140      MarkAsCanceled();
    141    } else if (aOther.Handled()) {
    142      MarkAsHandled();
    143      UnmarkAsCanceled();
    144      if (!mMovedContentRange.IsPositioned() && mNextInsertionPoint.IsSet()) {
    145        MOZ_ASSERT(mNextInsertionPoint.IsSetAndValid());
    146        mMovedContentRange.SetStartAndEnd(mNextInsertionPoint,
    147                                          mNextInsertionPoint);
    148      }
    149      if (aOther.mMovedContentRange.IsPositioned()) {
    150        mMovedContentRange.MergeWith(aOther.mMovedContentRange);
    151      } else if (aOther.mNextInsertionPoint.IsSet()) {
    152        MOZ_ASSERT(aOther.mNextInsertionPoint.IsSetAndValid());
    153        mMovedContentRange.MergeWith(aOther.mNextInsertionPoint);
    154      }
    155    }
    156 
    157    // Take the new one for the next insertion point.
    158    mNextInsertionPoint = aOther.mNextInsertionPoint;
    159 
    160    // Take the new caret point if and only if it's suggested.
    161    if (aOther.HasCaretPointSuggestion()) {
    162      SetCaretPoint(aOther.CaretPointRef());
    163    }
    164    return *this;
    165  }
    166 
    167  void ForceToMarkAsHandled() {
    168    if (Handled()) {
    169      return;
    170    }
    171    MarkAsHandled();
    172    if (!mMovedContentRange.IsPositioned()) {
    173      MOZ_ASSERT(mNextInsertionPoint.IsSetAndValidInComposedDoc());
    174      mMovedContentRange.SetStartAndEnd(mNextInsertionPoint,
    175                                        mNextInsertionPoint);
    176    }
    177  }
    178 
    179 #ifdef DEBUG
    180  ~MoveNodeResult() {
    181    MOZ_ASSERT_IF(Handled(), !HasCaretPointSuggestion() || CaretPointHandled());
    182  }
    183 #endif
    184 
    185  /*****************************************************************************
    186   * When a move node handler (or its helper) does nothing,
    187   * the result of these factory methods should be returned.
    188   * aNextInsertionPoint Must be set and valid.
    189   *****************************************************************************/
    190  static MoveNodeResult IgnoredResult(
    191      const EditorDOMPoint& aNextInsertionPoint) {
    192    return MoveNodeResult(aNextInsertionPoint, false);
    193  }
    194  static MoveNodeResult IgnoredResult(EditorDOMPoint&& aNextInsertionPoint) {
    195    return MoveNodeResult(std::forward<EditorDOMPoint>(aNextInsertionPoint),
    196                          false);
    197  }
    198 
    199  /*****************************************************************************
    200   * When a move node handler (or its helper) handled and not canceled,
    201   * the result of these factory methods should be returned.
    202   * aNextInsertionPoint Must be set and valid.
    203   *****************************************************************************/
    204  static MoveNodeResult HandledResult(
    205      const EditorDOMPoint& aNextInsertionPoint) {
    206    return MoveNodeResult(aNextInsertionPoint, true);
    207  }
    208 
    209  static MoveNodeResult HandledResult(EditorDOMPoint&& aNextInsertionPoint) {
    210    return MoveNodeResult(std::forward<EditorDOMPoint>(aNextInsertionPoint),
    211                          true);
    212  }
    213 
    214  static MoveNodeResult HandledResult(const EditorDOMPoint& aNextInsertionPoint,
    215                                      const EditorDOMPoint& aPointToPutCaret) {
    216    return MoveNodeResult(aNextInsertionPoint, aPointToPutCaret);
    217  }
    218 
    219  static MoveNodeResult HandledResult(EditorDOMPoint&& aNextInsertionPoint,
    220                                      const EditorDOMPoint& aPointToPutCaret) {
    221    return MoveNodeResult(std::forward<EditorDOMPoint>(aNextInsertionPoint),
    222                          aPointToPutCaret);
    223  }
    224 
    225  static MoveNodeResult HandledResult(const EditorDOMPoint& aNextInsertionPoint,
    226                                      EditorDOMPoint&& aPointToPutCaret) {
    227    return MoveNodeResult(aNextInsertionPoint,
    228                          std::forward<EditorDOMPoint>(aPointToPutCaret));
    229  }
    230 
    231  static MoveNodeResult HandledResult(EditorDOMPoint&& aNextInsertionPoint,
    232                                      EditorDOMPoint&& aPointToPutCaret) {
    233    return MoveNodeResult(std::forward<EditorDOMPoint>(aNextInsertionPoint),
    234                          std::forward<EditorDOMPoint>(aPointToPutCaret));
    235  }
    236  // A factory method when consecutive siblings are moved once.
    237  static MoveNodeResult HandledResult(const nsIContent& aFirstMovedContent,
    238                                      EditorDOMPoint&& aNextInsertionPoint) {
    239    return MoveNodeResult(aFirstMovedContent,
    240                          std::forward<EditorDOMPoint>(aNextInsertionPoint));
    241  }
    242  // A factory method when consecutive siblings are moved once.
    243  static MoveNodeResult HandledResult(const nsIContent& aFirstMovedContent,
    244                                      EditorDOMPoint&& aNextInsertionPoint,
    245                                      EditorDOMPoint&& aPointToPutCaret) {
    246    return MoveNodeResult(aFirstMovedContent,
    247                          std::forward<EditorDOMPoint>(aNextInsertionPoint),
    248                          std::forward<EditorDOMPoint>(aPointToPutCaret));
    249  }
    250 
    251 private:
    252  explicit MoveNodeResult(const EditorDOMPoint& aNextInsertionPoint,
    253                          bool aHandled)
    254      : EditActionResult(false, aHandled && aNextInsertionPoint.IsSet()),
    255        mNextInsertionPoint(aNextInsertionPoint) {
    256    if (Handled()) {
    257      mMovedContentRange = EditorDOMRange(mNextInsertionPoint);
    258    }
    259  }
    260  explicit MoveNodeResult(EditorDOMPoint&& aNextInsertionPoint, bool aHandled)
    261      : EditActionResult(false, aHandled && aNextInsertionPoint.IsSet()),
    262        mNextInsertionPoint(std::move(aNextInsertionPoint)) {
    263    if (Handled()) {
    264      mMovedContentRange = EditorDOMRange(mNextInsertionPoint);
    265    }
    266  }
    267  explicit MoveNodeResult(const EditorDOMPoint& aNextInsertionPoint,
    268                          const EditorDOMPoint& aPointToPutCaret)
    269      : CaretPoint(aPointToPutCaret),
    270        EditActionResult(false, aNextInsertionPoint.IsSet()),
    271        mNextInsertionPoint(aNextInsertionPoint) {
    272    if (Handled()) {
    273      mMovedContentRange = EditorDOMRange(mNextInsertionPoint);
    274    }
    275  }
    276  explicit MoveNodeResult(EditorDOMPoint&& aNextInsertionPoint,
    277                          const EditorDOMPoint& aPointToPutCaret)
    278      : CaretPoint(aPointToPutCaret),
    279        EditActionResult(false, aNextInsertionPoint.IsSet()),
    280        mNextInsertionPoint(std::move(aNextInsertionPoint)) {
    281    if (Handled()) {
    282      mMovedContentRange = EditorDOMRange(mNextInsertionPoint);
    283    }
    284  }
    285  explicit MoveNodeResult(const EditorDOMPoint& aNextInsertionPoint,
    286                          EditorDOMPoint&& aPointToPutCaret)
    287      : CaretPoint(std::forward<EditorDOMPoint>(aPointToPutCaret)),
    288        EditActionResult(false, aNextInsertionPoint.IsSet()),
    289        mNextInsertionPoint(aNextInsertionPoint) {
    290    if (Handled()) {
    291      mMovedContentRange = EditorDOMRange(mNextInsertionPoint);
    292    }
    293  }
    294  explicit MoveNodeResult(EditorDOMPoint&& aNextInsertionPoint,
    295                          EditorDOMPoint&& aPointToPutCaret)
    296      : CaretPoint(std::forward<EditorDOMPoint>(aPointToPutCaret)),
    297        EditActionResult(false, aNextInsertionPoint.IsSet()),
    298        mNextInsertionPoint(std::forward<EditorDOMPoint>(aNextInsertionPoint)) {
    299    if (Handled()) {
    300      mMovedContentRange = EditorDOMRange(mNextInsertionPoint);
    301    }
    302  }
    303  explicit MoveNodeResult(const nsIContent& aFirstMovedContent,
    304                          EditorDOMPoint&& aNextInsertionPoint)
    305      : EditActionResult(false, aNextInsertionPoint.IsSet()),
    306        mNextInsertionPoint(std::forward<EditorDOMPoint>(aNextInsertionPoint)) {
    307    if (Handled()) {
    308      EditorDOMPoint pointAfterFirstMovedContent =
    309          EditorDOMPoint::After(aFirstMovedContent);
    310      if (MOZ_LIKELY(pointAfterFirstMovedContent.EqualsOrIsBefore(
    311              mNextInsertionPoint))) {
    312        mMovedContentRange = EditorDOMRange(
    313            std::move(pointAfterFirstMovedContent), mNextInsertionPoint);
    314      } else {
    315        mMovedContentRange = EditorDOMRange(
    316            mNextInsertionPoint, std::move(pointAfterFirstMovedContent));
    317      }
    318    }
    319  }
    320  explicit MoveNodeResult(const nsIContent& aFirstMovedContent,
    321                          EditorDOMPoint&& aNextInsertionPoint,
    322                          EditorDOMPoint&& aPointToPutCaret)
    323      : CaretPoint(std::forward<EditorDOMPoint>(aPointToPutCaret)),
    324        EditActionResult(false, aNextInsertionPoint.IsSet()),
    325        mNextInsertionPoint(std::forward<EditorDOMPoint>(aNextInsertionPoint)) {
    326    if (Handled()) {
    327      EditorDOMPoint pointAfterFirstMovedContent =
    328          EditorDOMPoint::After(aFirstMovedContent);
    329      if (MOZ_LIKELY(pointAfterFirstMovedContent.EqualsOrIsBefore(
    330              mNextInsertionPoint))) {
    331        mMovedContentRange = EditorDOMRange(
    332            std::move(pointAfterFirstMovedContent), mNextInsertionPoint);
    333      } else {
    334        mMovedContentRange = EditorDOMRange(
    335            mNextInsertionPoint, std::move(pointAfterFirstMovedContent));
    336      }
    337    }
    338  }
    339 
    340  using EditActionResult::CanceledResult;
    341  using EditActionResult::MarkAsCanceled;
    342  using EditActionResult::MarkAsHandled;
    343 
    344  EditorDOMPoint mNextInsertionPoint;
    345  EditorDOMRange mMovedContentRange;
    346 
    347  friend class AutoTrackDOMMoveNodeResult;
    348 };
    349 
    350 /*****************************************************************************
    351 * DeleteRangeResult is a simple class for various delete handlers of
    352 * HTMLEditor.
    353 *****************************************************************************/
    354 class MOZ_STACK_CLASS DeleteRangeResult final : public CaretPoint,
    355                                                public EditActionResult {
    356 public:
    357  DeleteRangeResult() : CaretPoint(EditorDOMPoint()) {};
    358  DeleteRangeResult(const EditorDOMPoint& aDeletePoint,
    359                    const EditorDOMPoint& aCaretPoint)
    360      : CaretPoint(aCaretPoint),
    361        EditActionResult(false, true),
    362        mDeleteRange(aDeletePoint) {
    363    MOZ_ASSERT(aDeletePoint.IsSetAndValidInComposedDoc());
    364    MOZ_ASSERT_IF(aCaretPoint.IsSet(),
    365                  aCaretPoint.IsSetAndValidInComposedDoc());
    366  }
    367  DeleteRangeResult(const EditorDOMPoint& aDeletePoint,
    368                    EditorDOMPoint&& aCaretPoint)
    369      : CaretPoint(std::move(aCaretPoint)),
    370        EditActionResult(false, true),
    371        mDeleteRange(aDeletePoint) {
    372    MOZ_ASSERT(aDeletePoint.IsSetAndValidInComposedDoc());
    373    MOZ_ASSERT_IF(HasCaretPointSuggestion(),
    374                  CaretPointRef().IsSetAndValidInComposedDoc());
    375  }
    376  DeleteRangeResult(const EditorDOMRange& aDeleteRange,
    377                    const EditorDOMPoint& aCaretPoint)
    378      : CaretPoint(aCaretPoint),
    379        EditActionResult(false, true),
    380        mDeleteRange(aDeleteRange) {
    381    MOZ_ASSERT(aDeleteRange.IsPositionedAndValid());
    382    MOZ_ASSERT_IF(aCaretPoint.IsSet(),
    383                  aCaretPoint.IsSetAndValidInComposedDoc());
    384  }
    385  DeleteRangeResult(EditorDOMRange&& aDeleteRange,
    386                    const EditorDOMPoint& aCaretPoint)
    387      : CaretPoint(aCaretPoint),
    388        EditActionResult(false, true),
    389        mDeleteRange(std::move(aDeleteRange)) {
    390    MOZ_ASSERT(mDeleteRange.IsPositionedAndValid());
    391    MOZ_ASSERT_IF(aCaretPoint.IsSet(),
    392                  aCaretPoint.IsSetAndValidInComposedDoc());
    393  }
    394  DeleteRangeResult(const EditorDOMRange& aDeleteRange,
    395                    EditorDOMPoint&& aCaretPoint)
    396      : CaretPoint(std::move(aCaretPoint)),
    397        EditActionResult(false, true),
    398        mDeleteRange(aDeleteRange) {
    399    MOZ_ASSERT(aDeleteRange.IsPositionedAndValidInComposedDoc());
    400    MOZ_ASSERT_IF(HasCaretPointSuggestion(),
    401                  CaretPointRef().IsSetAndValidInComposedDoc());
    402  }
    403  DeleteRangeResult(EditorDOMRange&& aDeleteRange, EditorDOMPoint&& aCaretPoint)
    404      : CaretPoint(std::move(aCaretPoint)),
    405        EditActionResult(false, true),
    406        mDeleteRange(std::move(aDeleteRange)) {
    407    MOZ_ASSERT(mDeleteRange.IsPositionedAndValid());
    408    MOZ_ASSERT_IF(HasCaretPointSuggestion(),
    409                  CaretPointRef().IsSetAndValidInComposedDoc());
    410  }
    411 
    412  [[nodiscard]] static DeleteRangeResult IgnoredResult() {
    413    return DeleteRangeResult(EditActionResult::IgnoredResult());
    414  }
    415  [[nodiscard]] static DeleteRangeResult CanceledResult() {
    416    return DeleteRangeResult(EditActionResult::CanceledResult());
    417  }
    418 
    419  [[nodiscard]] EditorDOMRange&& UnwrapDeleteRange() {
    420    return std::move(mDeleteRange);
    421  }
    422  [[nodiscard]] const EditorDOMRange& DeleteRangeRef() const {
    423    return mDeleteRange;
    424  }
    425 
    426  template <typename EditorDOMPointType>
    427  void SetDeleteRangeStart(const EditorDOMPointType& aPoint) {
    428    MOZ_ASSERT(aPoint.IsSetAndValidInComposedDoc());
    429    if (mDeleteRange.IsPositioned()) {
    430      mDeleteRange.SetStart(aPoint);
    431    } else {
    432      mDeleteRange.SetStartAndEnd(aPoint, aPoint);
    433    }
    434  }
    435 
    436  template <typename EditorDOMPointType>
    437  void SetDeleteRangeEnd(const EditorDOMPointType& aPoint) {
    438    MOZ_ASSERT(aPoint.IsSetAndValidInComposedDoc());
    439    if (mDeleteRange.IsPositioned()) {
    440      mDeleteRange.SetEnd(aPoint);
    441    } else {
    442      mDeleteRange.SetStartAndEnd(aPoint, aPoint);
    443    }
    444  }
    445 
    446  DeleteRangeResult& operator|=(const DeleteRangeResult& aOtherResult) {
    447    if (aOtherResult.Ignored() || aOtherResult.Canceled()) {
    448      return *this;
    449    }
    450    MarkAsHandled();
    451    UnmarkAsCanceled();
    452    if (aOtherResult.mDeleteRange.IsPositioned()) {
    453      mDeleteRange.MergeWith(aOtherResult.mDeleteRange);
    454    }
    455    return operator|=(static_cast<const CaretPoint&>(aOtherResult));
    456  }
    457 
    458  DeleteRangeResult& operator|=(const CaretPoint& aCaretPoint) {
    459    if (MOZ_UNLIKELY(!aCaretPoint.HasCaretPointSuggestion())) {
    460      return *this;
    461    }
    462    SetCaretPoint(aCaretPoint.CaretPointRef());
    463    aCaretPoint.IgnoreCaretPointSuggestion();
    464    return *this;
    465  }
    466 
    467 private:
    468  explicit DeleteRangeResult(EditActionResult&& aEditActionResult)
    469      : CaretPoint(EditorDOMPoint()),
    470        EditActionResult(std::move(aEditActionResult)) {}
    471 
    472  using EditActionResult::MarkAsCanceled;
    473  using EditActionResult::MarkAsHandled;
    474 
    475  EditorDOMRange mDeleteRange;
    476 
    477  friend class AutoTrackDOMDeleteRangeResult;
    478 };
    479 
    480 /*****************************************************************************
    481 * SplitNodeResult is a simple class for
    482 * HTMLEditor::SplitNodeDeepWithTransaction().
    483 * This makes the callers' code easier to read.
    484 *****************************************************************************/
    485 class MOZ_STACK_CLASS SplitNodeResult final : public CaretPoint {
    486 public:
    487  bool Handled() const { return mPreviousNode || mNextNode; }
    488 
    489  /**
    490   * DidSplit() returns true if a node was actually split.
    491   */
    492  bool DidSplit() const { return mPreviousNode && mNextNode; }
    493 
    494  /**
    495   * GetPreviousContent() returns previous content node at the split point.
    496   */
    497  MOZ_KNOWN_LIVE nsIContent* GetPreviousContent() const {
    498    if (mGivenSplitPoint.IsSet()) {
    499      return mGivenSplitPoint.IsEndOfContainer() ? mGivenSplitPoint.GetChild()
    500                                                 : nullptr;
    501    }
    502    return mPreviousNode;
    503  }
    504  template <typename NodeType>
    505  MOZ_KNOWN_LIVE NodeType* GetPreviousContentAs() const {
    506    return NodeType::FromNodeOrNull(GetPreviousContent());
    507  }
    508  template <typename EditorDOMPointType>
    509  EditorDOMPointType AtPreviousContent() const {
    510    if (nsIContent* previousContent = GetPreviousContent()) {
    511      return EditorDOMPointType(previousContent);
    512    }
    513    return EditorDOMPointType();
    514  }
    515 
    516  /**
    517   * GetNextContent() returns next content node at the split point.
    518   */
    519  MOZ_KNOWN_LIVE nsIContent* GetNextContent() const {
    520    if (mGivenSplitPoint.IsSet()) {
    521      return !mGivenSplitPoint.IsEndOfContainer() ? mGivenSplitPoint.GetChild()
    522                                                  : nullptr;
    523    }
    524    return mNextNode;
    525  }
    526  template <typename NodeType>
    527  MOZ_KNOWN_LIVE NodeType* GetNextContentAs() const {
    528    return NodeType::FromNodeOrNull(GetNextContent());
    529  }
    530  template <typename EditorDOMPointType>
    531  EditorDOMPointType AtNextContent() const {
    532    if (nsIContent* nextContent = GetNextContent()) {
    533      return EditorDOMPointType(nextContent);
    534    }
    535    return EditorDOMPointType();
    536  }
    537 
    538  /**
    539   * Returns new content node which is created at splitting a node.  I.e., this
    540   * returns nullptr if no node was split.
    541   */
    542  MOZ_KNOWN_LIVE nsIContent* GetNewContent() const {
    543    if (!DidSplit()) {
    544      return nullptr;
    545    }
    546    return mNextNode;
    547  }
    548  template <typename NodeType>
    549  MOZ_KNOWN_LIVE NodeType* GetNewContentAs() const {
    550    return NodeType::FromNodeOrNull(GetNewContent());
    551  }
    552  template <typename EditorDOMPointType>
    553  EditorDOMPointType AtNewContent() const {
    554    if (nsIContent* newContent = GetNewContent()) {
    555      return EditorDOMPointType(newContent);
    556    }
    557    return EditorDOMPointType();
    558  }
    559 
    560  /**
    561   * Returns original content node which is (or is just tried to be) split.
    562   */
    563  MOZ_KNOWN_LIVE nsIContent* GetOriginalContent() const {
    564    if (mGivenSplitPoint.IsSet()) {
    565      // Different from previous/next content, if the creator didn't split a
    566      // node, the container of the split point is the original node.
    567      return mGivenSplitPoint.GetContainerAs<nsIContent>();
    568    }
    569    return mPreviousNode ? mPreviousNode : mNextNode;
    570  }
    571  template <typename NodeType>
    572  MOZ_KNOWN_LIVE NodeType* GetOriginalContentAs() const {
    573    return NodeType::FromNodeOrNull(GetOriginalContent());
    574  }
    575  template <typename EditorDOMPointType>
    576  EditorDOMPointType AtOriginalContent() const {
    577    if (nsIContent* originalContent = GetOriginalContent()) {
    578      return EditorDOMPointType(originalContent);
    579    }
    580    return EditorDOMPointType();
    581  }
    582 
    583  /**
    584   * AtSplitPoint() returns the split point in the container.
    585   * HTMLEditor::CreateAndInsertElement() or something similar methods.
    586   */
    587  template <typename EditorDOMPointType>
    588  EditorDOMPointType AtSplitPoint() const {
    589    if (mGivenSplitPoint.IsSet()) {
    590      return mGivenSplitPoint.To<EditorDOMPointType>();
    591    }
    592    if (!mPreviousNode) {
    593      return EditorDOMPointType(mNextNode);
    594    }
    595    return EditorDOMPointType::After(mPreviousNode);
    596  }
    597 
    598  SplitNodeResult(const SplitNodeResult&) = delete;
    599  SplitNodeResult& operator=(const SplitNodeResult&) = delete;
    600  SplitNodeResult(SplitNodeResult&&) = default;
    601  SplitNodeResult& operator=(SplitNodeResult&&) = default;
    602 
    603  /**
    604   * This constructor should be used for setting specific caret point instead of
    605   * aSplitResult's one.
    606   */
    607  SplitNodeResult(SplitNodeResult&& aSplitResult,
    608                  const EditorDOMPoint& aNewCaretPoint)
    609      : SplitNodeResult(std::move(aSplitResult)) {
    610    SetCaretPoint(aNewCaretPoint);
    611  }
    612  SplitNodeResult(SplitNodeResult&& aSplitResult,
    613                  EditorDOMPoint&& aNewCaretPoint)
    614      : SplitNodeResult(std::move(aSplitResult)) {
    615    SetCaretPoint(std::move(aNewCaretPoint));
    616  }
    617 
    618  /**
    619   * This constructor shouldn't be used by anybody except methods which
    620   * use this as result when it succeeds.
    621   *
    622   * @param aNewNode    The node which is newly created.
    623   * @param aSplitNode  The node which was split.
    624   * @param aNewCaretPoint
    625   *                    An optional new caret position.  If this is omitted,
    626   *                    the point between new node and split node will be
    627   *                    suggested.
    628   */
    629  SplitNodeResult(nsIContent& aNewNode, nsIContent& aSplitNode,
    630                  const Maybe<EditorDOMPoint>& aNewCaretPoint = Nothing())
    631      : CaretPoint(aNewCaretPoint.isSome()
    632                       ? aNewCaretPoint.ref()
    633                       : EditorDOMPoint::AtEndOf(aSplitNode)),
    634        mPreviousNode(&aSplitNode),
    635        mNextNode(&aNewNode) {}
    636 
    637  SplitNodeResult ToHandledResult() const {
    638    CaretPointHandled();
    639    SplitNodeResult result;
    640    result.mPreviousNode = GetPreviousContent();
    641    result.mNextNode = GetNextContent();
    642    MOZ_DIAGNOSTIC_ASSERT(result.Handled());
    643    // Don't recompute the caret position because in this case, split has not
    644    // occurred yet.  In the case,  the caller shouldn't need to update
    645    // selection.
    646    result.SetCaretPoint(CaretPointRef());
    647    return result;
    648  }
    649 
    650  /**
    651   * The following factory methods creates a SplitNodeResult instance for the
    652   * special cases.
    653   *
    654   * @param aDeeperSplitNodeResult
    655   *                    If the splitter has already split a child or a
    656   *                    descendant of the latest split node, the split node
    657   *                    result should be specified.
    658   */
    659  static inline SplitNodeResult HandledButDidNotSplitDueToEndOfContainer(
    660      nsIContent& aNotSplitNode,
    661      const SplitNodeResult* aDeeperSplitNodeResult = nullptr) {
    662    SplitNodeResult result;
    663    result.mPreviousNode = &aNotSplitNode;
    664    // Caret should be put at the last split point instead of current node.
    665    if (aDeeperSplitNodeResult) {
    666      result.SetCaretPoint(aDeeperSplitNodeResult->CaretPointRef());
    667      aDeeperSplitNodeResult->IgnoreCaretPointSuggestion();
    668    }
    669    return result;
    670  }
    671 
    672  static inline SplitNodeResult HandledButDidNotSplitDueToStartOfContainer(
    673      nsIContent& aNotSplitNode,
    674      const SplitNodeResult* aDeeperSplitNodeResult = nullptr) {
    675    SplitNodeResult result;
    676    result.mNextNode = &aNotSplitNode;
    677    // Caret should be put at the last split point instead of current node.
    678    if (aDeeperSplitNodeResult) {
    679      result.SetCaretPoint(aDeeperSplitNodeResult->CaretPointRef());
    680      aDeeperSplitNodeResult->IgnoreCaretPointSuggestion();
    681    }
    682    return result;
    683  }
    684 
    685  template <typename PT, typename CT>
    686  static inline SplitNodeResult NotHandled(
    687      const EditorDOMPointBase<PT, CT>& aGivenSplitPoint,
    688      const SplitNodeResult* aDeeperSplitNodeResult = nullptr) {
    689    SplitNodeResult result;
    690    result.mGivenSplitPoint = aGivenSplitPoint;
    691    // Caret should be put at the last split point instead of current node.
    692    if (aDeeperSplitNodeResult) {
    693      result.SetCaretPoint(aDeeperSplitNodeResult->CaretPointRef());
    694      aDeeperSplitNodeResult->IgnoreCaretPointSuggestion();
    695    }
    696    return result;
    697  }
    698 
    699  /**
    700   * Returns aSplitNodeResult as-is unless it didn't split a node but
    701   * aDeeperSplitNodeResult has already split a child or a descendant and has a
    702   * valid point to put caret around there.  In the case, this return
    703   * aSplitNodeResult which suggests a caret position around the last split
    704   * point.
    705   */
    706  static inline SplitNodeResult MergeWithDeeperSplitNodeResult(
    707      SplitNodeResult&& aSplitNodeResult,
    708      const SplitNodeResult& aDeeperSplitNodeResult) {
    709    aSplitNodeResult.UnmarkAsHandledCaretPoint();
    710    aDeeperSplitNodeResult.IgnoreCaretPointSuggestion();
    711    if (aSplitNodeResult.DidSplit() ||
    712        !aDeeperSplitNodeResult.HasCaretPointSuggestion()) {
    713      return std::move(aSplitNodeResult);
    714    }
    715    SplitNodeResult result(std::move(aSplitNodeResult));
    716    result.SetCaretPoint(aDeeperSplitNodeResult.CaretPointRef());
    717    return result;
    718  }
    719 
    720 #ifdef DEBUG
    721  ~SplitNodeResult() {
    722    MOZ_ASSERT(!HasCaretPointSuggestion() || CaretPointHandled());
    723  }
    724 #endif
    725 
    726 private:
    727  SplitNodeResult() = default;
    728 
    729  // When methods which return this class split some nodes actually, they
    730  // need to set a set of left node and right node to this class.  However,
    731  // one or both of them may be moved or removed by mutation observer.
    732  // In such case, we cannot represent the point with EditorDOMPoint since
    733  // it requires current container node.  Therefore, we need to use
    734  // nsCOMPtr<nsIContent> here instead.
    735  nsCOMPtr<nsIContent> mPreviousNode;
    736  nsCOMPtr<nsIContent> mNextNode;
    737 
    738  // Methods which return this class may not split any nodes actually.  Then,
    739  // they may want to return given split point as is since such behavior makes
    740  // their callers simpler.  In this case, the point may be in a text node
    741  // which cannot be represented as a node.  Therefore, we need EditorDOMPoint
    742  // for representing the point.
    743  EditorDOMPoint mGivenSplitPoint;
    744 };
    745 
    746 /*****************************************************************************
    747 * JoinNodesResult is a simple class for HTMLEditor::JoinNodesWithTransaction().
    748 * This makes the callers' code easier to read.
    749 *****************************************************************************/
    750 class MOZ_STACK_CLASS JoinNodesResult final {
    751 public:
    752  MOZ_KNOWN_LIVE nsIContent* ExistingContent() const {
    753    return mJoinedPoint.ContainerAs<nsIContent>();
    754  }
    755  template <typename EditorDOMPointType>
    756  EditorDOMPointType AtExistingContent() const {
    757    return EditorDOMPointType(mJoinedPoint.ContainerAs<nsIContent>());
    758  }
    759 
    760  MOZ_KNOWN_LIVE nsIContent* RemovedContent() const { return mRemovedContent; }
    761  template <typename EditorDOMPointType>
    762  EditorDOMPointType AtRemovedContent() const {
    763    if (mRemovedContent) {
    764      return EditorDOMPointType(mRemovedContent);
    765    }
    766    return EditorDOMPointType();
    767  }
    768 
    769  template <typename EditorDOMPointType>
    770  EditorDOMPointType AtJoinedPoint() const {
    771    return mJoinedPoint.To<EditorDOMPointType>();
    772  }
    773 
    774  JoinNodesResult() = delete;
    775 
    776  /**
    777   * This constructor shouldn't be used by anybody except methods which
    778   * use this as result when it succeeds.
    779   *
    780   * @param aJoinedPoint        First child of right node or first character.
    781   * @param aRemovedContent     The node which was removed from the parent.
    782   */
    783  JoinNodesResult(const EditorDOMPoint& aJoinedPoint,
    784                  nsIContent& aRemovedContent)
    785      : mJoinedPoint(aJoinedPoint), mRemovedContent(&aRemovedContent) {
    786    MOZ_DIAGNOSTIC_ASSERT(aJoinedPoint.IsInContentNode());
    787  }
    788 
    789  JoinNodesResult(const JoinNodesResult& aOther) = delete;
    790  JoinNodesResult& operator=(const JoinNodesResult& aOther) = delete;
    791  JoinNodesResult(JoinNodesResult&& aOther) = default;
    792  JoinNodesResult& operator=(JoinNodesResult&& aOther) = default;
    793 
    794 private:
    795  EditorDOMPoint mJoinedPoint;
    796  MOZ_KNOWN_LIVE nsCOMPtr<nsIContent> mRemovedContent;
    797 };
    798 
    799 /*****************************************************************************
    800 * SplitRangeOffFromNodeResult class is a simple class for methods which split a
    801 * node at 2 points for making part of the node split off from the node.
    802 *****************************************************************************/
    803 class MOZ_STACK_CLASS SplitRangeOffFromNodeResult final : public CaretPoint {
    804 public:
    805  /**
    806   * GetLeftContent() returns new created node before the part of quarried out.
    807   * This may return nullptr if the method didn't split at start edge of
    808   * the node.
    809   */
    810  MOZ_KNOWN_LIVE nsIContent* GetLeftContent() const { return mLeftContent; }
    811  template <typename ContentNodeType>
    812  MOZ_KNOWN_LIVE ContentNodeType* GetLeftContentAs() const {
    813    return ContentNodeType::FromNodeOrNull(GetLeftContent());
    814  }
    815  constexpr nsCOMPtr<nsIContent>&& UnwrapLeftContent() {
    816    mMovedContent = true;
    817    return std::move(mLeftContent);
    818  }
    819 
    820  /**
    821   * GetMiddleContent() returns new created node between left node and right
    822   * node.  I.e., this is quarried out from the node.  This may return nullptr
    823   * if the method unwrapped the middle node.
    824   */
    825  MOZ_KNOWN_LIVE nsIContent* GetMiddleContent() const { return mMiddleContent; }
    826  template <typename ContentNodeType>
    827  MOZ_KNOWN_LIVE ContentNodeType* GetMiddleContentAs() const {
    828    return ContentNodeType::FromNodeOrNull(GetMiddleContent());
    829  }
    830  constexpr nsCOMPtr<nsIContent>&& UnwrapMiddleContent() {
    831    mMovedContent = true;
    832    return std::move(mMiddleContent);
    833  }
    834 
    835  /**
    836   * GetRightContent() returns the right node after the part of quarried out.
    837   * This may return nullptr it the method didn't split at end edge of the
    838   * node.
    839   */
    840  MOZ_KNOWN_LIVE nsIContent* GetRightContent() const { return mRightContent; }
    841  template <typename ContentNodeType>
    842  MOZ_KNOWN_LIVE ContentNodeType* GetRightContentAs() const {
    843    return ContentNodeType::FromNodeOrNull(GetRightContent());
    844  }
    845  constexpr nsCOMPtr<nsIContent>&& UnwrapRightContent() {
    846    mMovedContent = true;
    847    return std::move(mRightContent);
    848  }
    849 
    850  /**
    851   * GetLeftmostContent() returns the leftmost content after trying to
    852   * split twice.  If the node was not split, this returns the original node.
    853   */
    854  MOZ_KNOWN_LIVE nsIContent* GetLeftmostContent() const {
    855    MOZ_ASSERT(!mMovedContent);
    856    return mLeftContent ? mLeftContent
    857                        : (mMiddleContent ? mMiddleContent : mRightContent);
    858  }
    859  template <typename ContentNodeType>
    860  MOZ_KNOWN_LIVE ContentNodeType* GetLeftmostContentAs() const {
    861    return ContentNodeType::FromNodeOrNull(GetLeftmostContent());
    862  }
    863 
    864  /**
    865   * GetRightmostContent() returns the rightmost content after trying to
    866   * split twice.  If the node was not split, this returns the original node.
    867   */
    868  MOZ_KNOWN_LIVE nsIContent* GetRightmostContent() const {
    869    MOZ_ASSERT(!mMovedContent);
    870    return mRightContent ? mRightContent
    871                         : (mMiddleContent ? mMiddleContent : mLeftContent);
    872  }
    873  template <typename ContentNodeType>
    874  MOZ_KNOWN_LIVE ContentNodeType* GetRightmostContentAs() const {
    875    return ContentNodeType::FromNodeOrNull(GetRightmostContent());
    876  }
    877 
    878  [[nodiscard]] bool DidSplit() const { return mLeftContent || mRightContent; }
    879 
    880  SplitRangeOffFromNodeResult() = delete;
    881 
    882  SplitRangeOffFromNodeResult(nsIContent* aLeftContent,
    883                              nsIContent* aMiddleContent,
    884                              nsIContent* aRightContent)
    885      : mLeftContent(aLeftContent),
    886        mMiddleContent(aMiddleContent),
    887        mRightContent(aRightContent) {}
    888 
    889  SplitRangeOffFromNodeResult(nsIContent* aLeftContent,
    890                              nsIContent* aMiddleContent,
    891                              nsIContent* aRightContent,
    892                              EditorDOMPoint&& aPointToPutCaret)
    893      : CaretPoint(std::move(aPointToPutCaret)),
    894        mLeftContent(aLeftContent),
    895        mMiddleContent(aMiddleContent),
    896        mRightContent(aRightContent) {}
    897 
    898  SplitRangeOffFromNodeResult(const SplitRangeOffFromNodeResult& aOther) =
    899      delete;
    900  SplitRangeOffFromNodeResult& operator=(
    901      const SplitRangeOffFromNodeResult& aOther) = delete;
    902  SplitRangeOffFromNodeResult(SplitRangeOffFromNodeResult&& aOther) noexcept
    903      : CaretPoint(aOther.UnwrapCaretPoint()),
    904        mLeftContent(std::move(aOther.mLeftContent)),
    905        mMiddleContent(std::move(aOther.mMiddleContent)),
    906        mRightContent(std::move(aOther.mRightContent)) {
    907    MOZ_ASSERT(!aOther.mMovedContent);
    908  }
    909  SplitRangeOffFromNodeResult& operator=(SplitRangeOffFromNodeResult&& aOther) =
    910      delete;  // due to bug 1792638
    911 
    912 #ifdef DEBUG
    913  ~SplitRangeOffFromNodeResult() {
    914    MOZ_ASSERT(!HasCaretPointSuggestion() || CaretPointHandled());
    915  }
    916 #endif
    917 
    918 private:
    919  MOZ_KNOWN_LIVE nsCOMPtr<nsIContent> mLeftContent;
    920  MOZ_KNOWN_LIVE nsCOMPtr<nsIContent> mMiddleContent;
    921  MOZ_KNOWN_LIVE nsCOMPtr<nsIContent> mRightContent;
    922 
    923  bool mutable mMovedContent = false;
    924 };
    925 
    926 /*****************************************************************************
    927 * SplitRangeOffResult class is a simple class for methods which splits
    928 * specific ancestor elements at 2 DOM points.
    929 *****************************************************************************/
    930 class MOZ_STACK_CLASS SplitRangeOffResult final : public CaretPoint {
    931 public:
    932  constexpr bool Handled() const { return mHandled; }
    933 
    934  /**
    935   * The start boundary is at the right of split at split point.  The end
    936   * boundary is at right node of split at end point, i.e., the end boundary
    937   * points out of the range to have been split off.
    938   */
    939  constexpr const EditorDOMRange& RangeRef() const { return mRange; }
    940 
    941  SplitRangeOffResult() = delete;
    942 
    943  /**
    944   * Constructor for success case.
    945   *
    946   * @param aTrackedRangeStart          The range whose start is at topmost
    947   *                                    right node child at start point if
    948   *                                    actually split there, or at the point
    949   *                                    to be tried to split, and whose end is
    950   *                                    at topmost right node child at end point
    951   *                                    if actually split there, or at the point
    952   *                                    to be tried to split.  Note that if the
    953   *                                    method allows to run script after
    954   *                                    splitting the range boundaries, they
    955   *                                    should be tracked with
    956   *                                    AutoTrackDOMRange.
    957   * @param aSplitNodeResultAtStart     Raw split node result at start point.
    958   * @param aSplitNodeResultAtEnd       Raw split node result at start point.
    959   */
    960  SplitRangeOffResult(EditorDOMRange&& aTrackedRange,
    961                      SplitNodeResult&& aSplitNodeResultAtStart,
    962                      SplitNodeResult&& aSplitNodeResultAtEnd)
    963      : mRange(std::move(aTrackedRange)),
    964        mHandled(aSplitNodeResultAtStart.Handled() ||
    965                 aSplitNodeResultAtEnd.Handled()) {
    966    MOZ_ASSERT(mRange.StartRef().IsSet());
    967    MOZ_ASSERT(mRange.EndRef().IsSet());
    968    // The given results are created for creating this instance so that the
    969    // caller may not need to handle with them.  For making who taking the
    970    // responsible clearer, we should move them into this constructor.
    971    EditorDOMPoint pointToPutCaret;
    972    SplitNodeResult splitNodeResultAtStart(std::move(aSplitNodeResultAtStart));
    973    SplitNodeResult splitNodeResultAtEnd(std::move(aSplitNodeResultAtEnd));
    974    splitNodeResultAtStart.MoveCaretPointTo(
    975        pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
    976    splitNodeResultAtEnd.MoveCaretPointTo(pointToPutCaret,
    977                                          {SuggestCaret::OnlyIfHasSuggestion});
    978    SetCaretPoint(std::move(pointToPutCaret));
    979  }
    980 
    981  SplitRangeOffResult(const SplitRangeOffResult& aOther) = delete;
    982  SplitRangeOffResult& operator=(const SplitRangeOffResult& aOther) = delete;
    983  SplitRangeOffResult(SplitRangeOffResult&& aOther) = default;
    984  SplitRangeOffResult& operator=(SplitRangeOffResult&& aOther) = default;
    985 
    986 private:
    987  EditorDOMRange mRange;
    988 
    989  // If you need to store previous and/or next node at start/end point,
    990  // you might be able to use `SplitNodeResult::GetPreviousNode()` etc in the
    991  // constructor only when `SplitNodeResult::Handled()` returns true.  But
    992  // the node might have gone with another DOM tree mutation.  So, be careful
    993  // if you do it.
    994 
    995  bool mHandled;
    996 };
    997 
    998 /******************************************************************************
    999 * DOM tree iterators
   1000 *****************************************************************************/
   1001 
   1002 class MOZ_RAII DOMIterator {
   1003 public:
   1004  explicit DOMIterator();
   1005  explicit DOMIterator(nsINode& aNode);
   1006  virtual ~DOMIterator() = default;
   1007 
   1008  nsresult Init(nsRange& aRange);
   1009  nsresult Init(const RawRangeBoundary& aStartRef,
   1010                const RawRangeBoundary& aEndRef);
   1011 
   1012  template <class NodeClass>
   1013  void AppendAllNodesToArray(
   1014      nsTArray<OwningNonNull<NodeClass>>& aArrayOfNodes) const;
   1015 
   1016  /**
   1017   * AppendNodesToArray() calls aFunctor before appending found node to
   1018   * aArrayOfNodes.  If aFunctor returns false, the node will be ignored.
   1019   * You can use aClosure instead of capturing something with lambda.
   1020   * Note that aNode is guaranteed that it's an instance of NodeClass
   1021   * or its sub-class.
   1022   * XXX If we can make type of aNode templated without std::function,
   1023   *     it'd be better, though.
   1024   */
   1025  using BoolFunctor = bool (*)(nsINode& aNode, void* aClosure);
   1026  template <class NodeClass>
   1027  void AppendNodesToArray(BoolFunctor aFunctor,
   1028                          nsTArray<OwningNonNull<NodeClass>>& aArrayOfNodes,
   1029                          void* aClosure = nullptr) const;
   1030 
   1031 protected:
   1032  SafeContentIteratorBase* mIter;
   1033  PostContentIterator mPostOrderIter;
   1034 };
   1035 
   1036 class MOZ_RAII DOMSubtreeIterator final : public DOMIterator {
   1037 public:
   1038  explicit DOMSubtreeIterator();
   1039  virtual ~DOMSubtreeIterator() = default;
   1040 
   1041  nsresult Init(nsRange& aRange);
   1042 
   1043 private:
   1044  ContentSubtreeIterator mSubtreeIter;
   1045  explicit DOMSubtreeIterator(nsINode& aNode) = delete;
   1046 };
   1047 
   1048 /******************************************************************************
   1049 * ReplaceRangeData
   1050 *
   1051 * This represents range to be replaced and replacing string.
   1052 *****************************************************************************/
   1053 
   1054 template <typename EditorDOMPointType>
   1055 class MOZ_STACK_CLASS ReplaceRangeDataBase final {
   1056 public:
   1057  ReplaceRangeDataBase() = default;
   1058  template <typename OtherEditorDOMRangeType>
   1059  ReplaceRangeDataBase(const OtherEditorDOMRangeType& aRange,
   1060                       const nsAString& aReplaceString)
   1061      : mRange(aRange), mReplaceString(aReplaceString) {}
   1062  template <typename StartPointType, typename EndPointType>
   1063  ReplaceRangeDataBase(const StartPointType& aStart, const EndPointType& aEnd,
   1064                       const nsAString& aReplaceString)
   1065      : mRange(aStart, aEnd), mReplaceString(aReplaceString) {}
   1066 
   1067  bool IsSet() const { return mRange.IsPositioned(); }
   1068  bool IsSetAndValid() const { return mRange.IsPositionedAndValid(); }
   1069  bool Collapsed() const { return mRange.Collapsed(); }
   1070  bool HasReplaceString() const { return !mReplaceString.IsEmpty(); }
   1071  const EditorDOMPointType& StartRef() const { return mRange.StartRef(); }
   1072  const EditorDOMPointType& EndRef() const { return mRange.EndRef(); }
   1073  const EditorDOMRangeBase<EditorDOMPointType>& RangeRef() const {
   1074    return mRange;
   1075  }
   1076  const nsString& ReplaceStringRef() const { return mReplaceString; }
   1077 
   1078  template <typename PointType>
   1079  MOZ_NEVER_INLINE_DEBUG void SetStart(const PointType& aStart) {
   1080    mRange.SetStart(aStart);
   1081  }
   1082  template <typename PointType>
   1083  MOZ_NEVER_INLINE_DEBUG void SetEnd(const PointType& aEnd) {
   1084    mRange.SetEnd(aEnd);
   1085  }
   1086  template <typename StartPointType, typename EndPointType>
   1087  MOZ_NEVER_INLINE_DEBUG void SetStartAndEnd(const StartPointType& aStart,
   1088                                             const EndPointType& aEnd) {
   1089    mRange.SetRange(aStart, aEnd);
   1090  }
   1091  template <typename OtherEditorDOMRangeType>
   1092  MOZ_NEVER_INLINE_DEBUG void SetRange(const OtherEditorDOMRangeType& aRange) {
   1093    mRange = aRange;
   1094  }
   1095  void SetReplaceString(const nsAString& aReplaceString) {
   1096    mReplaceString = aReplaceString;
   1097  }
   1098  template <typename StartPointType, typename EndPointType>
   1099  MOZ_NEVER_INLINE_DEBUG void SetStartAndEnd(const StartPointType& aStart,
   1100                                             const EndPointType& aEnd,
   1101                                             const nsAString& aReplaceString) {
   1102    SetStartAndEnd(aStart, aEnd);
   1103    SetReplaceString(aReplaceString);
   1104  }
   1105  template <typename OtherEditorDOMRangeType>
   1106  MOZ_NEVER_INLINE_DEBUG void Set(const OtherEditorDOMRangeType& aRange,
   1107                                  const nsAString& aReplaceString) {
   1108    SetRange(aRange);
   1109    SetReplaceString(aReplaceString);
   1110  }
   1111 
   1112 private:
   1113  EditorDOMRangeBase<EditorDOMPointType> mRange;
   1114  // This string may be used with ReplaceTextTransaction.  Therefore, for
   1115  // avoiding memory copy, we should store it with nsString rather than
   1116  // nsAutoString.
   1117  nsString mReplaceString;
   1118 };
   1119 
   1120 /******************************************************************************
   1121 * EditorElementStyle represents a generic style of element
   1122 ******************************************************************************/
   1123 
   1124 class MOZ_STACK_CLASS EditorElementStyle {
   1125 public:
   1126 #define DEFINE_FACTORY(aName, aAttr)            \
   1127  constexpr static EditorElementStyle aName() { \
   1128    return EditorElementStyle(*(aAttr));        \
   1129  }
   1130 
   1131  // text-align, caption-side, a pair of margin-left and margin-right
   1132  DEFINE_FACTORY(Align, nsGkAtoms::align)
   1133  // background-color
   1134  DEFINE_FACTORY(BGColor, nsGkAtoms::bgcolor)
   1135  // background-image
   1136  DEFINE_FACTORY(Background, nsGkAtoms::background)
   1137  // border
   1138  DEFINE_FACTORY(Border, nsGkAtoms::border)
   1139  // height
   1140  DEFINE_FACTORY(Height, nsGkAtoms::height)
   1141  // color
   1142  DEFINE_FACTORY(Text, nsGkAtoms::text)
   1143  // list-style-type
   1144  DEFINE_FACTORY(Type, nsGkAtoms::type)
   1145  // vertical-align
   1146  DEFINE_FACTORY(VAlign, nsGkAtoms::valign)
   1147  // width
   1148  DEFINE_FACTORY(Width, nsGkAtoms::width)
   1149 
   1150  static EditorElementStyle Create(const nsAtom& aAttribute) {
   1151    MOZ_DIAGNOSTIC_ASSERT(IsHTMLStyle(&aAttribute));
   1152    return EditorElementStyle(*aAttribute.AsStatic());
   1153  }
   1154 
   1155  [[nodiscard]] static bool IsHTMLStyle(const nsAtom* aAttribute) {
   1156    return aAttribute == nsGkAtoms::align || aAttribute == nsGkAtoms::bgcolor ||
   1157           aAttribute == nsGkAtoms::background ||
   1158           aAttribute == nsGkAtoms::border || aAttribute == nsGkAtoms::height ||
   1159           aAttribute == nsGkAtoms::text || aAttribute == nsGkAtoms::type ||
   1160           aAttribute == nsGkAtoms::valign || aAttribute == nsGkAtoms::width;
   1161  }
   1162 
   1163  /**
   1164   * Returns true if the style can be represented by CSS and it's possible to
   1165   * apply the style with CSS.
   1166   */
   1167  [[nodiscard]] bool IsCSSSettable(const nsStaticAtom& aTagName) const;
   1168  [[nodiscard]] bool IsCSSSettable(const dom::Element& aElement) const;
   1169 
   1170  /**
   1171   * Returns true if the style can be represented by CSS and it's possible to
   1172   * remove the style with CSS.
   1173   */
   1174  [[nodiscard]] bool IsCSSRemovable(const nsStaticAtom& aTagName) const;
   1175  [[nodiscard]] bool IsCSSRemovable(const dom::Element& aElement) const;
   1176 
   1177  nsStaticAtom* Style() const { return mStyle; }
   1178 
   1179  [[nodiscard]] bool IsInlineStyle() const { return !mStyle; }
   1180  inline EditorInlineStyle& AsInlineStyle();
   1181  inline const EditorInlineStyle& AsInlineStyle() const;
   1182 
   1183 protected:
   1184  MOZ_KNOWN_LIVE nsStaticAtom* mStyle = nullptr;
   1185  EditorElementStyle() = default;
   1186 
   1187 private:
   1188  constexpr explicit EditorElementStyle(const nsStaticAtom& aStyle)
   1189      // Needs const_cast hack here because the this class users may want
   1190      // non-const nsStaticAtom pointer due to bug 1794954
   1191      : mStyle(const_cast<nsStaticAtom*>(&aStyle)) {}
   1192 };
   1193 
   1194 /******************************************************************************
   1195 * EditorInlineStyle represents an inline style.
   1196 ******************************************************************************/
   1197 
   1198 struct MOZ_STACK_CLASS EditorInlineStyle : public EditorElementStyle {
   1199  // nullptr if you want to remove all inline styles.
   1200  // Otherwise, one of the presentation tag names which we support in style
   1201  // editor, and there special cases: nsGkAtoms::href means <a href="...">,
   1202  // and nsGkAtoms::name means <a name="...">.
   1203  MOZ_KNOWN_LIVE nsStaticAtom* const mHTMLProperty = nullptr;
   1204  // For some mHTMLProperty values, need to be set to its attribute name.
   1205  // E.g., nsGkAtoms::size and nsGkAtoms::face for nsGkAtoms::font.
   1206  // Otherwise, nullptr.
   1207  // TODO: Once we stop using these structure to wrap selected content nodes
   1208  //       with <a href> elements, we can make this nsStaticAtom*.
   1209  MOZ_KNOWN_LIVE const RefPtr<nsAtom> mAttribute;
   1210 
   1211  /**
   1212   * Returns true if the style means that all inline styles should be removed.
   1213   */
   1214  [[nodiscard]] bool IsStyleToClearAllInlineStyles() const {
   1215    return !mHTMLProperty;
   1216  }
   1217 
   1218  /**
   1219   * Returns true if the style is about <a>.
   1220   */
   1221  [[nodiscard]] bool IsStyleOfAnchorElement() const {
   1222    return mHTMLProperty == nsGkAtoms::a || mHTMLProperty == nsGkAtoms::href ||
   1223           mHTMLProperty == nsGkAtoms::name;
   1224  }
   1225 
   1226  /**
   1227   * Returns true if the style is invertible with CSS.
   1228   */
   1229  [[nodiscard]] bool IsInvertibleWithCSS() const {
   1230    return mHTMLProperty == nsGkAtoms::b;
   1231  }
   1232 
   1233  /**
   1234   * Returns true if the style can be specified with text-decoration.
   1235   */
   1236  enum class IgnoreSElement { No, Yes };
   1237  [[nodiscard]] bool IsStyleOfTextDecoration(
   1238      IgnoreSElement aIgnoreSElement) const {
   1239    return mHTMLProperty == nsGkAtoms::u ||
   1240           mHTMLProperty == nsGkAtoms::strike ||
   1241           (aIgnoreSElement == IgnoreSElement::No &&
   1242            mHTMLProperty == nsGkAtoms::s);
   1243  }
   1244 
   1245  /**
   1246   * Returns true if the style can be represented with <font>.
   1247   */
   1248  [[nodiscard]] bool IsStyleOfFontElement() const {
   1249    MOZ_ASSERT_IF(
   1250        mHTMLProperty == nsGkAtoms::font,
   1251        mAttribute == nsGkAtoms::bgcolor || mAttribute == nsGkAtoms::color ||
   1252            mAttribute == nsGkAtoms::face || mAttribute == nsGkAtoms::size);
   1253    return mHTMLProperty == nsGkAtoms::font && mAttribute != nsGkAtoms::bgcolor;
   1254  }
   1255 
   1256  /**
   1257   * Returns true if the style is font-size or <font size="...">.
   1258   */
   1259  [[nodiscard]] bool IsStyleOfFontSize() const {
   1260    return mHTMLProperty == nsGkAtoms::font && mAttribute == nsGkAtoms::size;
   1261  }
   1262 
   1263  /**
   1264   * Returns true if the style is conflict with vertical-align even though
   1265   * they are not mapped to vertical-align in the CSS mode.
   1266   */
   1267  [[nodiscard]] bool IsStyleConflictingWithVerticalAlign() const {
   1268    return mHTMLProperty == nsGkAtoms::sup || mHTMLProperty == nsGkAtoms::sub;
   1269  }
   1270 
   1271  /**
   1272   * If the style has a similar element  which should be removed when applying
   1273   * the style, this retuns an element name.  Otherwise, returns nullptr.
   1274   */
   1275  [[nodiscard]] nsStaticAtom* GetSimilarElementNameAtom() const {
   1276    if (mHTMLProperty == nsGkAtoms::b) {
   1277      return nsGkAtoms::strong;
   1278    }
   1279    if (mHTMLProperty == nsGkAtoms::i) {
   1280      return nsGkAtoms::em;
   1281    }
   1282    if (mHTMLProperty == nsGkAtoms::strike) {
   1283      return nsGkAtoms::s;
   1284    }
   1285    return nullptr;
   1286  }
   1287 
   1288  /**
   1289   * Returns true if aContent is an HTML element and represents the style.
   1290   */
   1291  [[nodiscard]] bool IsRepresentedBy(const nsIContent& aContent) const;
   1292 
   1293  /**
   1294   * Returns true if aElement has style attribute and specifies this style.
   1295   *
   1296   * TODO: Make aElement be constant, but it needs to touch CSSEditUtils a lot.
   1297   */
   1298  [[nodiscard]] Result<bool, nsresult> IsSpecifiedBy(
   1299      const HTMLEditor& aHTMLEditor, dom::Element& aElement) const;
   1300 
   1301  explicit EditorInlineStyle(const nsStaticAtom& aHTMLProperty,
   1302                             nsAtom* aAttribute = nullptr)
   1303      : EditorInlineStyle(aHTMLProperty, aAttribute, HasValue::No) {}
   1304  EditorInlineStyle(const nsStaticAtom& aHTMLProperty,
   1305                    RefPtr<nsAtom>&& aAttribute)
   1306      : EditorInlineStyle(aHTMLProperty, aAttribute, HasValue::No) {}
   1307 
   1308  /**
   1309   * Returns the instance which means remove all inline styles.
   1310   */
   1311  static EditorInlineStyle RemoveAllStyles() { return EditorInlineStyle(); }
   1312 
   1313  PendingStyleCache ToPendingStyleCache(nsAString&& aValue) const;
   1314 
   1315  bool operator==(const EditorInlineStyle& aOther) const {
   1316    return mHTMLProperty == aOther.mHTMLProperty &&
   1317           mAttribute == aOther.mAttribute;
   1318  }
   1319 
   1320  bool MaybeHasValue() const { return mMaybeHasValue; }
   1321  inline EditorInlineStyleAndValue& AsInlineStyleAndValue();
   1322  inline const EditorInlineStyleAndValue& AsInlineStyleAndValue() const;
   1323 
   1324 protected:
   1325  const bool mMaybeHasValue = false;
   1326 
   1327  enum class HasValue { No, Yes };
   1328  EditorInlineStyle(const nsStaticAtom& aHTMLProperty, nsAtom* aAttribute,
   1329                    HasValue aHasValue)
   1330      // Needs const_cast hack here because the struct users may want
   1331      // non-const nsStaticAtom pointer due to bug 1794954
   1332      : mHTMLProperty(const_cast<nsStaticAtom*>(&aHTMLProperty)),
   1333        mAttribute(aAttribute),
   1334        mMaybeHasValue(aHasValue == HasValue::Yes) {}
   1335  EditorInlineStyle(const nsStaticAtom& aHTMLProperty,
   1336                    RefPtr<nsAtom>&& aAttribute, HasValue aHasValue)
   1337      // Needs const_cast hack here because the struct users may want
   1338      // non-const nsStaticAtom pointer due to bug 1794954
   1339      : mHTMLProperty(const_cast<nsStaticAtom*>(&aHTMLProperty)),
   1340        mAttribute(std::move(aAttribute)),
   1341        mMaybeHasValue(aHasValue == HasValue::Yes) {}
   1342  EditorInlineStyle(const EditorInlineStyle& aStyle, HasValue aHasValue)
   1343      : mHTMLProperty(aStyle.mHTMLProperty),
   1344        mAttribute(aStyle.mAttribute),
   1345        mMaybeHasValue(aHasValue == HasValue::Yes) {}
   1346 
   1347 private:
   1348  EditorInlineStyle() = default;
   1349 
   1350  using EditorElementStyle::AsInlineStyle;
   1351  using EditorElementStyle::IsInlineStyle;
   1352  using EditorElementStyle::Style;
   1353 };
   1354 
   1355 inline EditorInlineStyle& EditorElementStyle::AsInlineStyle() {
   1356  return reinterpret_cast<EditorInlineStyle&>(*this);
   1357 }
   1358 
   1359 inline const EditorInlineStyle& EditorElementStyle::AsInlineStyle() const {
   1360  return reinterpret_cast<const EditorInlineStyle&>(*this);
   1361 }
   1362 
   1363 /******************************************************************************
   1364 * EditorInlineStyleAndValue represents an inline style and stores its value.
   1365 ******************************************************************************/
   1366 
   1367 struct MOZ_STACK_CLASS EditorInlineStyleAndValue : public EditorInlineStyle {
   1368  // Stores the value of mAttribute.
   1369  nsString const mAttributeValue;
   1370 
   1371  bool IsStyleToClearAllInlineStyles() const = delete;
   1372  EditorInlineStyleAndValue() = delete;
   1373 
   1374  explicit EditorInlineStyleAndValue(nsStaticAtom& aHTMLProperty)
   1375      : EditorInlineStyle(aHTMLProperty, nullptr, HasValue::No) {}
   1376  EditorInlineStyleAndValue(nsStaticAtom& aHTMLProperty, nsAtom& aAttribute,
   1377                            const nsAString& aValue)
   1378      : EditorInlineStyle(aHTMLProperty, &aAttribute, HasValue::Yes),
   1379        mAttributeValue(aValue) {}
   1380  EditorInlineStyleAndValue(nsStaticAtom& aHTMLProperty,
   1381                            RefPtr<nsAtom>&& aAttribute,
   1382                            const nsAString& aValue)
   1383      : EditorInlineStyle(aHTMLProperty, std::move(aAttribute), HasValue::Yes),
   1384        mAttributeValue(aValue) {
   1385    MOZ_ASSERT(mAttribute);
   1386  }
   1387  EditorInlineStyleAndValue(nsStaticAtom& aHTMLProperty, nsAtom& aAttribute,
   1388                            nsString&& aValue)
   1389      : EditorInlineStyle(aHTMLProperty, &aAttribute, HasValue::Yes),
   1390        mAttributeValue(std::move(aValue)) {}
   1391  EditorInlineStyleAndValue(nsStaticAtom& aHTMLProperty,
   1392                            RefPtr<nsAtom>&& aAttribute, nsString&& aValue)
   1393      : EditorInlineStyle(aHTMLProperty, std::move(aAttribute), HasValue::Yes),
   1394        mAttributeValue(aValue) {}
   1395 
   1396  [[nodiscard]] static EditorInlineStyleAndValue ToInvert(
   1397      const EditorInlineStyle& aStyle) {
   1398    MOZ_ASSERT(aStyle.IsInvertibleWithCSS());
   1399    return EditorInlineStyleAndValue(aStyle, u"-moz-editor-invert-value"_ns);
   1400  }
   1401 
   1402  // mHTMLProperty is never nullptr since all constructors guarantee it.
   1403  // Therefore, hide it and expose its reference instead.
   1404  MOZ_KNOWN_LIVE nsStaticAtom& HTMLPropertyRef() const {
   1405    MOZ_DIAGNOSTIC_ASSERT(mHTMLProperty);
   1406    return *mHTMLProperty;
   1407  }
   1408 
   1409  [[nodiscard]] bool IsStyleToInvert() const {
   1410    return mAttributeValue.EqualsLiteral(u"-moz-editor-invert-value");
   1411  }
   1412 
   1413  /**
   1414   * Returns true if this style is representable with HTML.
   1415   */
   1416  [[nodiscard]] bool IsRepresentableWithHTML() const {
   1417    // Use background-color in any elements
   1418    if (mAttribute == nsGkAtoms::bgcolor) {
   1419      return false;
   1420    }
   1421    // Inverting the style means that it's invertible with CSS
   1422    if (IsStyleToInvert()) {
   1423      return false;
   1424    }
   1425    return true;
   1426  }
   1427 
   1428 private:
   1429  using EditorInlineStyle::mHTMLProperty;
   1430 
   1431  EditorInlineStyleAndValue(const EditorInlineStyle& aStyle,
   1432                            const nsAString& aValue)
   1433      : EditorInlineStyle(aStyle, HasValue::Yes), mAttributeValue(aValue) {}
   1434 
   1435  using EditorInlineStyle::AsInlineStyleAndValue;
   1436  using EditorInlineStyle::HasValue;
   1437 };
   1438 
   1439 inline EditorInlineStyleAndValue& EditorInlineStyle::AsInlineStyleAndValue() {
   1440  return reinterpret_cast<EditorInlineStyleAndValue&>(*this);
   1441 }
   1442 
   1443 inline const EditorInlineStyleAndValue&
   1444 EditorInlineStyle::AsInlineStyleAndValue() const {
   1445  return reinterpret_cast<const EditorInlineStyleAndValue&>(*this);
   1446 }
   1447 
   1448 }  // namespace mozilla
   1449 
   1450 #endif  // #ifndef HTMLEditHelpers_h