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