HTMLEditorNestedClasses.h (94353B)
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 HTMLEditorNestedClasses_h 7 #define HTMLEditorNestedClasses_h 8 9 #include "EditorDOMPoint.h" 10 #include "EditorForwards.h" 11 #include "HTMLEditor.h" // for HTMLEditor 12 #include "HTMLEditHelpers.h" // for EditorInlineStyleAndValue 13 #include "HTMLEditUtils.h" // for HTMLEditUtils::IsContainerNode 14 15 #include "mozilla/Attributes.h" 16 #include "mozilla/OwningNonNull.h" 17 #include "mozilla/Result.h" 18 #include "mozilla/dom/CharacterDataBuffer.h" 19 #include "mozilla/dom/Text.h" 20 21 namespace mozilla { 22 23 struct LimitersAndCaretData; // Declared in nsFrameSelection.h 24 namespace dom { 25 class HTMLBRElement; 26 }; 27 28 /***************************************************************************** 29 * AutoInlineStyleSetter is a temporary class to set an inline style to 30 * specific nodes. 31 ****************************************************************************/ 32 33 class MOZ_STACK_CLASS HTMLEditor::AutoInlineStyleSetter final 34 : private EditorInlineStyleAndValue { 35 using Element = dom::Element; 36 using Text = dom::Text; 37 38 public: 39 explicit AutoInlineStyleSetter( 40 const EditorInlineStyleAndValue& aStyleAndValue) 41 : EditorInlineStyleAndValue(aStyleAndValue) {} 42 43 void Reset() { 44 mFirstHandledPoint.Clear(); 45 mLastHandledPoint.Clear(); 46 } 47 48 const EditorDOMPoint& FirstHandledPointRef() const { 49 return mFirstHandledPoint; 50 } 51 const EditorDOMPoint& LastHandledPointRef() const { 52 return mLastHandledPoint; 53 } 54 55 /** 56 * Split aText at aStartOffset and aEndOffset (except when they are start or 57 * end of its data) and wrap the middle text node in an element to apply the 58 * style. 59 */ 60 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitRangeOffFromNodeResult, nsresult> 61 SplitTextNodeAndApplyStyleToMiddleNode(HTMLEditor& aHTMLEditor, Text& aText, 62 uint32_t aStartOffset, 63 uint32_t aEndOffset); 64 65 /** 66 * Remove same style from children and apply the style entire (except 67 * non-editable nodes) aContent. 68 */ 69 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> 70 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(HTMLEditor& aHTMLEditor, 71 nsIContent& aContent); 72 73 /** 74 * Invert the style with creating new element or something. This should 75 * be called only when IsInvertibleWithCSS() returns true. 76 */ 77 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 78 InvertStyleIfApplied(HTMLEditor& aHTMLEditor, Element& aElement); 79 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitRangeOffFromNodeResult, nsresult> 80 InvertStyleIfApplied(HTMLEditor& aHTMLEditor, Text& aTextNode, 81 uint32_t aStartOffset, uint32_t aEndOffset); 82 83 /** 84 * Extend or shrink aRange for applying the style to the range. 85 * See comments in the definition what this does. 86 */ 87 Result<EditorRawDOMRange, nsresult> ExtendOrShrinkRangeToApplyTheStyle( 88 const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange) const; 89 90 /** 91 * Returns next/previous sibling of aContent or an ancestor of it if it's 92 * editable and does not cross block boundary. 93 */ 94 [[nodiscard]] static nsIContent* GetNextEditableInlineContent( 95 const nsIContent& aContent, const nsINode* aLimiter = nullptr); 96 [[nodiscard]] static nsIContent* GetPreviousEditableInlineContent( 97 const nsIContent& aContent, const nsINode* aLimiter = nullptr); 98 99 /** 100 * GetEmptyTextNodeToApplyNewStyle creates new empty text node to insert 101 * a new element which will contain newly inserted text or returns existing 102 * empty text node if aCandidatePointToInsert is around it. 103 * 104 * NOTE: Unfortunately, editor does not want to insert text into empty inline 105 * element in some places (e.g., automatically adjusting caret position to 106 * nearest text node). Therefore, we need to create new empty text node to 107 * prepare new styles for inserting text. This method is designed for the 108 * preparation. 109 * 110 * @param aHTMLEditor The editor. 111 * @param aCandidatePointToInsert The point where the caller wants to 112 * insert new text. 113 * @return If this creates new empty text node returns it. 114 * If this couldn't create new empty text node due to 115 * the point or aEditingHost cannot have text node, 116 * returns nullptr. 117 * Otherwise, returns error. 118 */ 119 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<RefPtr<Text>, nsresult> 120 GetEmptyTextNodeToApplyNewStyle( 121 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCandidatePointToInsert); 122 123 private: 124 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> ApplyStyle( 125 HTMLEditor& aHTMLEditor, nsIContent& aContent); 126 127 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> 128 ApplyCSSTextDecoration(HTMLEditor& aHTMLEditor, nsIContent& aContent); 129 130 /** 131 * Returns true if aStyledElement is a good element to set `style` attribute. 132 */ 133 [[nodiscard]] bool ElementIsGoodContainerToSetStyle( 134 nsStyledElement& aStyledElement) const; 135 136 /** 137 * ElementIsGoodContainerForTheStyle() returns true if aElement is a 138 * good container for applying the style to a node. I.e., if this returns 139 * true, moving nodes into aElement is enough to apply the style to them. 140 * Otherwise, you need to create new element for the style. 141 */ 142 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<bool, nsresult> 143 ElementIsGoodContainerForTheStyle(HTMLEditor& aHTMLEditor, 144 Element& aElement) const; 145 146 /** 147 * Return true if the node is an element node and it represents the style or 148 * sets the style (including when setting different value) with `style` 149 * attribute. 150 */ 151 [[nodiscard]] bool ContentIsElementSettingTheStyle( 152 const HTMLEditor& aHTMLEditor, nsIContent& aContent) const; 153 154 /** 155 * Helper methods to shrink range to apply the style. 156 */ 157 [[nodiscard]] EditorRawDOMPoint GetShrunkenRangeStart( 158 const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange, 159 const nsINode& aCommonAncestorOfRange, 160 const nsIContent* aFirstEntirelySelectedContentNodeInRange) const; 161 [[nodiscard]] EditorRawDOMPoint GetShrunkenRangeEnd( 162 const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange, 163 const nsINode& aCommonAncestorOfRange, 164 const nsIContent* aLastEntirelySelectedContentNodeInRange) const; 165 166 /** 167 * Helper methods to extend the range to apply the style. 168 */ 169 [[nodiscard]] EditorRawDOMPoint 170 GetExtendedRangeStartToWrapAncestorApplyingSameStyle( 171 const HTMLEditor& aHTMLEditor, 172 const EditorRawDOMPoint& aStartPoint) const; 173 [[nodiscard]] EditorRawDOMPoint 174 GetExtendedRangeEndToWrapAncestorApplyingSameStyle( 175 const HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aEndPoint) const; 176 [[nodiscard]] EditorRawDOMRange 177 GetExtendedRangeToMinimizeTheNumberOfNewElements( 178 const HTMLEditor& aHTMLEditor, const nsINode& aCommonAncestor, 179 EditorRawDOMPoint&& aStartPoint, EditorRawDOMPoint&& aEndPoint) const; 180 181 /** 182 * OnHandled() are called when this class creates new element to apply the 183 * style, applies new style to existing element or ignores to apply the style 184 * due to already set. 185 */ 186 void OnHandled(const EditorDOMPoint& aStartPoint, 187 const EditorDOMPoint& aEndPoint) { 188 if (!mFirstHandledPoint.IsSet()) { 189 mFirstHandledPoint = aStartPoint; 190 } 191 mLastHandledPoint = aEndPoint; 192 } 193 void OnHandled(nsIContent& aContent) { 194 if (aContent.IsElement() && !HTMLEditUtils::IsContainerNode(aContent)) { 195 if (!mFirstHandledPoint.IsSet()) { 196 mFirstHandledPoint.Set(&aContent); 197 } 198 mLastHandledPoint.SetAfter(&aContent); 199 return; 200 } 201 if (!mFirstHandledPoint.IsSet()) { 202 mFirstHandledPoint.Set(&aContent, 0u); 203 } 204 mLastHandledPoint = EditorDOMPoint::AtEndOf(aContent); 205 } 206 207 // mFirstHandledPoint and mLastHandledPoint store the first and last points 208 // which are newly created or apply the new style, or just ignored at trying 209 // to split a text node. 210 EditorDOMPoint mFirstHandledPoint; 211 EditorDOMPoint mLastHandledPoint; 212 }; 213 214 /** 215 * AutoMoveOneLineHandler moves the content in a line (between line breaks/block 216 * boundaries) to specific point or end of a container element. 217 */ 218 class MOZ_STACK_CLASS HTMLEditor::AutoMoveOneLineHandler final { 219 public: 220 /** 221 * Use this constructor when you want a line to move specific point. 222 */ 223 explicit AutoMoveOneLineHandler(const EditorDOMPoint& aPointToInsert) 224 : mPointToInsert(aPointToInsert), 225 mMoveToEndOfContainer(MoveToEndOfContainer::No) { 226 MOZ_ASSERT(mPointToInsert.IsSetAndValid()); 227 MOZ_ASSERT(mPointToInsert.IsInContentNode()); 228 } 229 /** 230 * Use this constructor when you want a line to move end of 231 * aNewContainerElement. 232 */ 233 explicit AutoMoveOneLineHandler(Element& aNewContainerElement) 234 : mPointToInsert(&aNewContainerElement, 0), 235 mMoveToEndOfContainer(MoveToEndOfContainer::Yes) { 236 MOZ_ASSERT(mPointToInsert.IsSetAndValid()); 237 } 238 239 /** 240 * Must be called before calling Run(). 241 * 242 * @param aHTMLEditor The HTML editor. 243 * @param aPointInHardLine A point in a line which you want to move. 244 * @param aEditingHost The editing host. 245 */ 246 [[nodiscard]] nsresult Prepare(HTMLEditor& aHTMLEditor, 247 const EditorDOMPoint& aPointInHardLine, 248 const Element& aEditingHost); 249 /** 250 * Must be called if Prepare() returned NS_OK. 251 * 252 * @param aHTMLEditor The HTML editor. 253 * @param aEditingHost The editing host. 254 */ 255 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<MoveNodeResult, nsresult> Run( 256 HTMLEditor& aHTMLEditor, const Element& aEditingHost); 257 258 /** 259 * Returns true if there are some content nodes which can be moved to another 260 * place or deleted in the line containing aPointInHardLine. Note that if 261 * there is only a padding <br> element in an empty block element, this 262 * returns false even though it may be deleted. 263 */ 264 static Result<bool, nsresult> CanMoveOrDeleteSomethingInLine( 265 const EditorDOMPoint& aPointInHardLine, const Element& aEditingHost); 266 267 AutoMoveOneLineHandler(const AutoMoveOneLineHandler& aOther) = delete; 268 AutoMoveOneLineHandler(AutoMoveOneLineHandler&& aOther) = delete; 269 270 private: 271 [[nodiscard]] bool ForceMoveToEndOfContainer() const { 272 return mMoveToEndOfContainer == MoveToEndOfContainer::Yes; 273 } 274 [[nodiscard]] EditorDOMPoint& NextInsertionPointRef() { 275 if (ForceMoveToEndOfContainer()) { 276 mPointToInsert.SetToEndOf(mPointToInsert.GetContainer()); 277 } 278 return mPointToInsert; 279 } 280 281 /** 282 * Consider whether Run() should preserve or does not preserve white-space 283 * style of moving content. 284 * 285 * @param aContentInLine Specify a content node in the moving line. 286 * Typically, container of aPointInHardLine of 287 * Prepare(). 288 * @param aInclusiveAncestorBlockOfInsertionPoint 289 * Inclusive ancestor block element of insertion 290 * point. Typically, computed 291 * mDestInclusiveAncestorBlock. 292 */ 293 [[nodiscard]] static PreserveWhiteSpaceStyle 294 ConsiderWhetherPreserveWhiteSpaceStyle( 295 const nsIContent* aContentInLine, 296 const Element* aInclusiveAncestorBlockOfInsertionPoint); 297 298 /** 299 * Look for inclusive ancestor block element of aBlockElement and a descendant 300 * of aAncestorElement. If aBlockElement and aAncestorElement are same one, 301 * this returns nullptr. 302 * 303 * @param aBlockElement A block element which is a descendant of 304 * aAncestorElement. 305 * @param aAncestorElement An inclusive ancestor block element of 306 * aBlockElement. 307 */ 308 [[nodiscard]] static Element* 309 GetMostDistantInclusiveAncestorBlockInSpecificAncestorElement( 310 Element& aBlockElement, const Element& aAncestorElement); 311 312 /** 313 * Split ancestors at the line range boundaries and collect array of contents 314 * in the line to aOutArrayOfContents. Specify aNewContainer to the container 315 * of insertion point to avoid splitting the destination. 316 */ 317 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> 318 SplitToMakeTheLineIsolated( 319 HTMLEditor& aHTMLEditor, const nsIContent& aNewContainer, 320 const Element& aEditingHost, 321 nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents) const; 322 323 /** 324 * Delete unnecessary trailing line break in aMovedContentRange if there is. 325 */ 326 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 327 DeleteUnnecessaryTrailingLineBreakInMovedLineEnd( 328 HTMLEditor& aHTMLEditor, const EditorDOMRange& aMovedContentRange, 329 const Element& aEditingHost) const; 330 331 // Range of selected line. 332 EditorDOMRange mLineRange; 333 // Next insertion point. If mMoveToEndOfContainer is `Yes`, this is 334 // recomputed with its container in NextInsertionPointRef. Therefore, this 335 // should not be referred directly. 336 EditorDOMPoint mPointToInsert; 337 // An inclusive ancestor block element of the moving line. 338 RefPtr<Element> mSrcInclusiveAncestorBlock; 339 // An inclusive ancestor block element of the insertion point. 340 RefPtr<Element> mDestInclusiveAncestorBlock; 341 // nullptr if mMovingToParentBlock is false. 342 // Must be non-nullptr if mMovingToParentBlock is true. The topmost ancestor 343 // block element which contains mSrcInclusiveAncestorBlock and a descendant of 344 // mDestInclusiveAncestorBlock. I.e., this may be same as 345 // mSrcInclusiveAncestorBlock, but never same as mDestInclusiveAncestorBlock. 346 RefPtr<Element> mTopmostSrcAncestorBlockInDestBlock; 347 enum class MoveToEndOfContainer { No, Yes }; 348 MoveToEndOfContainer mMoveToEndOfContainer; 349 PreserveWhiteSpaceStyle mPreserveWhiteSpaceStyle = 350 PreserveWhiteSpaceStyle::No; 351 // true if mDestInclusiveAncestorBlock is an ancestor of 352 // mSrcInclusiveAncestorBlock. 353 bool mMovingToParentBlock = false; 354 }; 355 356 /** 357 * Convert contents around aRanges of Run() to specified list element. If there 358 * are some different type of list elements, this method converts them to 359 * specified list items too. Basically, each line will be wrapped in a list 360 * item element. However, only when <p> element is selected, its child <br> 361 * elements won't be treated as line separators. Perhaps, this is a bug. 362 */ 363 class MOZ_STACK_CLASS HTMLEditor::AutoListElementCreator final { 364 public: 365 /** 366 * @param aListElementTagName The new list element tag name. 367 * @param aListItemElementTagName The new list item element tag name. 368 * @param aBulletType If this is not empty string, it's set 369 * to `type` attribute of new list item 370 * elements. Otherwise, existing `type` 371 * attributes will be removed. 372 */ 373 AutoListElementCreator(const nsStaticAtom& aListElementTagName, 374 const nsStaticAtom& aListItemElementTagName, 375 const nsAString& aBulletType) 376 // Needs const_cast hack here because the struct users may want 377 // non-const nsStaticAtom pointer due to bug 1794954 378 : mListTagName(const_cast<nsStaticAtom&>(aListElementTagName)), 379 mListItemTagName(const_cast<nsStaticAtom&>(aListItemElementTagName)), 380 mBulletType(aBulletType) { 381 MOZ_ASSERT(&mListTagName == nsGkAtoms::ul || 382 &mListTagName == nsGkAtoms::ol || 383 &mListTagName == nsGkAtoms::dl); 384 MOZ_ASSERT_IF( 385 &mListTagName == nsGkAtoms::ul || &mListTagName == nsGkAtoms::ol, 386 &mListItemTagName == nsGkAtoms::li); 387 MOZ_ASSERT_IF(&mListTagName == nsGkAtoms::dl, 388 &mListItemTagName == nsGkAtoms::dt || 389 &mListItemTagName == nsGkAtoms::dd); 390 } 391 392 /** 393 * @param aHTMLEditor The HTML editor. 394 * @param aRanges [in/out] The ranges which will be converted to list. 395 * The instance must not have saved ranges because it'll 396 * be used in this method. 397 * If succeeded, this will have selection ranges which 398 * should be applied to `Selection`. 399 * If failed, this keeps storing original selection 400 * ranges. 401 * @param aSelectAllOfCurrentList Yes if this should treat all of 402 * ancestor list element at selection. 403 * @param aEditingHost The editing host. 404 */ 405 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run( 406 HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRanges, 407 HTMLEditor::SelectAllOfCurrentList aSelectAllOfCurrentList, 408 const Element& aEditingHost) const; 409 410 private: 411 using ContentNodeArray = nsTArray<OwningNonNull<nsIContent>>; 412 using AutoContentNodeArray = AutoTArray<OwningNonNull<nsIContent>, 64>; 413 414 /** 415 * If aSelectAllOfCurrentList is "Yes" and aRanges is in a list element, 416 * returns the list element. 417 * Otherwise, extend aRanges to select start and end lines selected by it and 418 * correct all topmost content nodes in the extended ranges with splitting 419 * ancestors at range edges. 420 */ 421 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 422 SplitAtRangeEdgesAndCollectContentNodesToMoveIntoList( 423 HTMLEditor& aHTMLEditor, AutoClonedRangeArray& aRanges, 424 SelectAllOfCurrentList aSelectAllOfCurrentList, 425 const Element& aEditingHost, ContentNodeArray& aOutArrayOfContents) const; 426 427 /** 428 * Return true if aArrayOfContents has only <br> elements or empty inline 429 * container elements. I.e., it means that aArrayOfContents represents 430 * only empty line(s) if this returns true. 431 */ 432 [[nodiscard]] static bool 433 IsEmptyOrContainsOnlyBRElementsOrEmptyInlineElements( 434 const ContentNodeArray& aArrayOfContents); 435 436 /** 437 * Delete all content nodes ina ArrayOfContents, and if we can put new list 438 * element at start of the first range of aRanges, insert new list element 439 * there. 440 * 441 * @return The empty list item element in new list element. 442 */ 443 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<RefPtr<Element>, nsresult> 444 ReplaceContentNodesWithEmptyNewList( 445 HTMLEditor& aHTMLEditor, const AutoClonedRangeArray& aRanges, 446 const AutoContentNodeArray& aArrayOfContents, 447 const Element& aEditingHost) const; 448 449 /** 450 * Creat new list elements or use existing list elements and move 451 * aArrayOfContents into list item elements. 452 * 453 * @return A list or list item element which should have caret. 454 */ 455 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<RefPtr<Element>, nsresult> 456 WrapContentNodesIntoNewListElements(HTMLEditor& aHTMLEditor, 457 AutoClonedRangeArray& aRanges, 458 AutoContentNodeArray& aArrayOfContents, 459 const Element& aEditingHost) const; 460 461 struct MOZ_STACK_CLASS AutoHandlingState final { 462 // Current list element which is a good container to create new list item 463 // element. 464 RefPtr<Element> mCurrentListElement; 465 // Previously handled list item element. 466 RefPtr<Element> mPreviousListItemElement; 467 // List or list item element which should have caret after handling all 468 // contents. 469 RefPtr<Element> mListOrListItemElementToPutCaret; 470 // Replacing block element. This is typically already removed from the DOM 471 // tree. 472 RefPtr<Element> mReplacingBlockElement; 473 // Once id attribute of mReplacingBlockElement copied, the id attribute 474 // shouldn't be copied again. 475 bool mMaybeCopiedReplacingBlockElementId = false; 476 }; 477 478 /** 479 * Helper methods of WrapContentNodesIntoNewListElements. They are called for 480 * handling one content node of aArrayOfContents. It's set to aHandling*. 481 */ 482 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildContent( 483 HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent, 484 AutoHandlingState& aState, const Element& aEditingHost) const; 485 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 486 HandleChildListElement(HTMLEditor& aHTMLEditor, Element& aHandlingListElement, 487 AutoHandlingState& aState) const; 488 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildListItemElement( 489 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement, 490 AutoHandlingState& aState) const; 491 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 492 HandleChildListItemInDifferentTypeList(HTMLEditor& aHTMLEditor, 493 Element& aHandlingListItemElement, 494 AutoHandlingState& aState) const; 495 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildListItemInSameTypeList( 496 HTMLEditor& aHTMLEditor, Element& aHandlingListItemElement, 497 AutoHandlingState& aState) const; 498 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildDivOrParagraphElement( 499 HTMLEditor& aHTMLEditor, Element& aHandlingDivOrParagraphElement, 500 AutoHandlingState& aState, const Element& aEditingHost) const; 501 enum class EmptyListItem { NotCreate, Create }; 502 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult CreateAndUpdateCurrentListElement( 503 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToInsert, 504 EmptyListItem aEmptyListItem, AutoHandlingState& aState, 505 const Element& aEditingHost) const; 506 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> 507 AppendListItemElement(HTMLEditor& aHTMLEditor, const Element& aListElement, 508 AutoHandlingState& aState) const; 509 [[nodiscard]] MOZ_CAN_RUN_SCRIPT static nsresult 510 MaybeCloneAttributesToNewListItem(HTMLEditor& aHTMLEditor, 511 Element& aListItemElement, 512 AutoHandlingState& aState); 513 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleChildInlineContent( 514 HTMLEditor& aHTMLEditor, nsIContent& aHandlingInlineContent, 515 AutoHandlingState& aState) const; 516 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult WrapContentIntoNewListItemElement( 517 HTMLEditor& aHTMLEditor, nsIContent& aHandlingContent, 518 AutoHandlingState& aState) const; 519 520 /** 521 * If aRanges is collapsed outside aListItemOrListToPutCaret, this collapse 522 * aRanges in aListItemOrListToPutCaret again. 523 */ 524 nsresult EnsureCollapsedRangeIsInListItemOrListElement( 525 Element& aListItemOrListToPutCaret, AutoClonedRangeArray& aRanges) const; 526 527 MOZ_KNOWN_LIVE nsStaticAtom& mListTagName; 528 MOZ_KNOWN_LIVE nsStaticAtom& mListItemTagName; 529 const nsAutoString mBulletType; 530 }; 531 532 /** 533 * Handle "insertParagraph" command. 534 */ 535 class MOZ_STACK_CLASS HTMLEditor::AutoInsertParagraphHandler final { 536 public: 537 AutoInsertParagraphHandler() = delete; 538 AutoInsertParagraphHandler(const AutoInsertParagraphHandler&) = delete; 539 AutoInsertParagraphHandler(AutoInsertParagraphHandler&&) = delete; 540 541 MOZ_CAN_RUN_SCRIPT explicit AutoInsertParagraphHandler( 542 HTMLEditor& aHTMLEditor, const Element& aEditingHost) 543 : mHTMLEditor(aHTMLEditor), 544 mEditingHost(aEditingHost), 545 mDefaultParagraphSeparatorTagName( 546 aHTMLEditor.DefaultParagraphSeparatorTagName()), 547 mDefaultParagraphSeparator(aHTMLEditor.GetDefaultParagraphSeparator()) { 548 } 549 550 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run(); 551 552 private: 553 /** 554 * Insert <br> element. 555 * 556 * @param aPointToInsert The position where the new <br> should be 557 * inserted. 558 * @param aBlockElementWhichShouldHaveCaret 559 * [optional] If set, this collapse selection into 560 * the element with 561 * CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret(). 562 */ 563 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 564 HandleInsertBRElement( 565 const EditorDOMPoint& aPointToInsert, 566 const Element* aBlockElementWhichShouldHaveCaret = nullptr); 567 568 /** 569 * Insert a linefeed. 570 */ 571 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 572 HandleInsertLinefeed(const EditorDOMPoint& aPointToInsert); 573 574 /** 575 * SplitParagraphWithTransaction() splits the parent block, aParentDivOrP, at 576 * aPointToSplit. 577 * 578 * @param aBlockElementToSplit The current paragraph which should be split. 579 * @param aPointToSplit The point to split aBlockElementToSplit. 580 */ 581 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult> 582 SplitParagraphWithTransaction(Element& aBlockElementToSplit, 583 const EditorDOMPoint& aPointToSplit); 584 585 /** 586 * Delete preceding invisible line break before aPointToSplit if and only if 587 * there is. 588 * 589 * @return New point to split aBlockElementToSplit 590 */ 591 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> 592 EnsureNoInvisibleLineBreakBeforePointToSplit( 593 const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit); 594 595 /** 596 * Maybe insert a <br> element if it's required to keep the inline container 597 * visible after splitting aBlockElementToSplit at aPointToSplit. 598 * 599 * @return New point to split aBlockElementToSplit 600 */ 601 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> 602 MaybeInsertFollowingBRElementToPreserveRightBlock( 603 const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit); 604 605 /** 606 * Return true if the HTMLEditor is in the mode which `insertParagraph` should 607 * always create a new paragraph or in the cases that we create a new 608 * paragraph in the legacy mode. 609 */ 610 [[nodiscard]] bool ShouldCreateNewParagraph( 611 Element& aParentDivOrP, const EditorDOMPoint& aPointToSplit) const; 612 613 /** 614 * Return true if aBRElement is nullptr or an invisible <br> or a padding <br> 615 * for making the last empty line visible. 616 */ 617 [[nodiscard]] static bool 618 IsNullOrInvisibleBRElementOrPaddingOneForEmptyLastLine( 619 const dom::HTMLBRElement* aBRElement); 620 621 /** 622 * Handle insertParagraph command (i.e., handling Enter key press) in a 623 * heading element. This splits aHeadingElement element at aPointToSplit. 624 * Then, if right heading element is empty, it'll be removed and new paragraph 625 * is created (its type is decided with default paragraph separator). 626 * 627 * @param aHeadingElement The heading element to be split. 628 * @param aPointToSplit The point to split aHeadingElement. 629 * @return New paragraph element, meaning right heading 630 * element if aHeadingElement is split, or newly 631 * created or existing paragraph element. 632 */ 633 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult> 634 HandleInHeadingElement(Element& aHeadingElement, 635 const EditorDOMPoint& aPointToSplit); 636 637 /** 638 * Handle insertParagraph command at end of a heading element. 639 */ 640 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult> 641 HandleAtEndOfHeadingElement(Element& aHeadingElement); 642 643 /** 644 * Handle insertParagraph command (i.e., handling Enter key press) in a list 645 * item element. 646 * 647 * @param aListItemElement The list item which has the following point. 648 * @param aPointToSplit The point to split aListItemElement. 649 * @return New paragraph element, meaning right list item 650 * element if aListItemElement is split, or newly 651 * created paragraph element. 652 */ 653 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<InsertParagraphResult, nsresult> 654 HandleInListItemElement(Element& aListItemElement, 655 const EditorDOMPoint& aPointToSplit); 656 657 /** 658 * Split aMailCiteElement at aPointToSplit. 659 * 660 * @param aMailCiteElement The mail-cite element which should be split. 661 * @param aPointToSplit The point to split. 662 * @return Candidate caret position where is at inserted 663 * <br> element into the split point. 664 */ 665 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> 666 HandleInMailCiteElement(Element& aMailCiteElement, 667 const EditorDOMPoint& aPointToSplit); 668 669 /** 670 * Insert a <br> element into aPointToBreak. 671 * This may split container elements at the point and/or may move following 672 * <br> element to immediately after the new <br> element if necessary. 673 * 674 * @param aPointToBreak The point where new <br> element will be 675 * inserted before. 676 * @return If succeeded, returns new <br> element and 677 * candidate caret point. 678 */ 679 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateElementResult, nsresult> 680 InsertBRElement(const EditorDOMPoint& aPointToBreak); 681 682 /** 683 * Return true if we should insert a line break instead of a paragraph. 684 */ 685 [[nodiscard]] MOZ_CAN_RUN_SCRIPT bool ShouldInsertLineBreakInstead( 686 const Element* aEditableBlockElement, 687 const EditorDOMPoint& aCandidatePointToSplit); 688 689 enum class InsertBRElementIntoEmptyBlock : bool { Start, End }; 690 691 /** 692 * Make sure that aMaybeBlockElement is visible with putting a <br> element if 693 * and only if it's an empty block element. 694 */ 695 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateLineBreakResult, nsresult> 696 InsertBRElementIfEmptyBlockElement( 697 Element& aMaybeBlockElement, 698 InsertBRElementIntoEmptyBlock aInsertBRElementIntoEmptyBlock, 699 BlockInlineCheck aBlockInlineCheck); 700 701 /** 702 * Split aMailCiteElement at aPointToSplit. This deletes all inclusive 703 * ancestors of aPointToSplit in aMailCiteElement too. 704 */ 705 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<SplitNodeResult, nsresult> 706 SplitMailCiteElement(const EditorDOMPoint& aPointToSplit, 707 Element& aMailCiteElement); 708 709 /** 710 * aMailCiteElement may be a <span> element which is styled as block. If it's 711 * followed by a block boundary, it requires a padding <br> element when it's 712 * serialized. This method may insert a <br> element if it's required. 713 */ 714 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 715 MaybeInsertPaddingBRElementToInlineMailCiteElement( 716 const EditorDOMPoint& aPointToInsertBRElement, Element& aMailCiteElement); 717 718 /** 719 * Return the deepest inline container element which is the first leaf or the 720 * first leaf container of aBlockElement. 721 */ 722 [[nodiscard]] static Element* GetDeepestFirstChildInlineContainerElement( 723 Element& aBlockElement); 724 725 /** 726 * Collapse `Selection` to aCandidatePointToPutCaret or into 727 * aBlockElementShouldHaveCaret. If aBlockElementShouldHaveCaret is specified 728 * and aCandidatePointToPutCaret is outside it, this ignores 729 * aCandidatePointToPutCaret and collapse `Selection` into 730 * aBlockElementShouldHaveCaret. 731 */ 732 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 733 CollapseSelectionToPointOrIntoBlockWhichShouldHaveCaret( 734 const EditorDOMPoint& aCandidatePointToPutCaret, 735 const Element* aBlockElementShouldHaveCaret, 736 const SuggestCaretOptions& aOptions); 737 738 /** 739 * Return a better point to split the paragraph to avoid to keep a typing in a 740 * link or a paragraph in list item in the new paragraph. 741 */ 742 [[nodiscard]] EditorDOMPoint GetBetterPointToSplitParagraph( 743 const Element& aBlockElementToSplit, 744 const EditorDOMPoint& aCandidatePointToSplit); 745 746 enum class IgnoreBlockBoundaries : bool { No, Yes }; 747 748 /** 749 * Return true if splitting aBlockElementToSplit at aPointToSplit will create 750 * empty left element. 751 * 752 * @param aBlockElementToSplit The paragraph element which we want to 753 * split. 754 * @param aPointToSplit The split position in aBlockElementToSplit. 755 * @param aIgnoreBlockBoundaries If No, return true only when aPointToSplit 756 * is immediately after a block boundary of 757 * aBlockElementToSplit. In other words, 758 * may return true only when aPointToSplit 759 * is not in a child block of 760 * aBlockElementToSplit. 761 * If Yes, return true even when aPointToSplit 762 * is immediately after any current block 763 * boundary which is followed by the block 764 * boundary of aBlockElementToSplit. In other 765 * words, return true when aPointToSplit is in 766 * a child block which is start of any ancestor 767 * block elements. 768 */ 769 [[nodiscard]] static bool SplitPointIsStartOfSplittingBlock( 770 const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit, 771 IgnoreBlockBoundaries aIgnoreBlockBoundaries); 772 773 /** 774 * Return true if splitting aBlockElementToSplit at aPointToSplit will create 775 * empty right element. 776 * 777 * @param aBlockElementToSplit The paragraph element which we want to 778 * split. 779 * @param aPointToSplit The split position in aBlockElementToSplit. 780 * @param aIgnoreBlockBoundaries If No, return true only when aPointToSplit 781 * is immediately before a block boundary of 782 * aBlockElementToSplit. In other words, 783 * may return true only when aPointToSplit 784 * is not in a child block of 785 * aBlockElementToSplit. 786 * If Yes, return true even when aPointToSplit 787 * is immediately before any current block 788 * boundary which is followed by the block 789 * boundary of aBlockElementToSplit. In other 790 * words, return true when aPointToSplit is in 791 * a child block which is end of any ancestor 792 * block elements. 793 */ 794 [[nodiscard]] static bool SplitPointIsEndOfSplittingBlock( 795 const Element& aBlockElementToSplit, const EditorDOMPoint& aPointToSplit, 796 IgnoreBlockBoundaries aIgnoreBlockBoundaries); 797 798 MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor; 799 MOZ_KNOWN_LIVE const Element& mEditingHost; 800 MOZ_KNOWN_LIVE nsStaticAtom& mDefaultParagraphSeparatorTagName; 801 const ParagraphSeparator mDefaultParagraphSeparator; 802 }; 803 804 /** 805 * Handle "insertLineBreak" command. 806 */ 807 class MOZ_STACK_CLASS HTMLEditor::AutoInsertLineBreakHandler final { 808 public: 809 AutoInsertLineBreakHandler() = delete; 810 AutoInsertLineBreakHandler(const AutoInsertLineBreakHandler&) = delete; 811 AutoInsertLineBreakHandler(AutoInsertLineBreakHandler&&) = delete; 812 813 MOZ_CAN_RUN_SCRIPT explicit AutoInsertLineBreakHandler( 814 HTMLEditor& aHTMLEditor, const Element& aEditingHost) 815 : mHTMLEditor(aHTMLEditor), mEditingHost(aEditingHost) {} 816 817 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult Run(); 818 819 private: 820 /** 821 * Insert a linefeed character into aPointToBreak. 822 * 823 * @param aPointToBreak The point where new linefeed character will be 824 * inserted before. 825 * @param aEditingHost Current active editing host. 826 * @return A suggest point to put caret. 827 */ 828 [[nodiscard]] static MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> 829 InsertLinefeed(HTMLEditor& aHTMLEditor, const EditorDOMPoint& aPointToBreak, 830 const Element& aEditingHost); 831 832 /** 833 * Insert <br> element at `Selection`. 834 */ 835 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleInsertBRElement(); 836 837 /** 838 * Insert a linefeed at `Selection`. 839 */ 840 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult HandleInsertLinefeed(); 841 842 friend class AutoInsertParagraphHandler; 843 844 MOZ_KNOWN_LIVE HTMLEditor& mHTMLEditor; 845 MOZ_KNOWN_LIVE const Element& mEditingHost; 846 }; 847 848 /** 849 * Handle delete multiple ranges, typically they are the selection ranges. 850 */ 851 class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler final { 852 public: 853 explicit AutoDeleteRangesHandler( 854 const AutoDeleteRangesHandler* aParent = nullptr) 855 : mParent(aParent), 856 mOriginalDirectionAndAmount(nsIEditor::eNone), 857 mOriginalStripWrappers(nsIEditor::eNoStrip) {} 858 859 /** 860 * ComputeRangesToDelete() computes actual deletion ranges. 861 */ 862 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ComputeRangesToDelete( 863 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 864 AutoClonedSelectionRangeArray& aRangesToDelete, 865 const Element& aEditingHost); 866 867 /** 868 * Deletes content in or around aRangesToDelete. 869 * NOTE: This method creates SelectionBatcher. Therefore, each caller 870 * needs to check if the editor is still available even if this returns 871 * NS_OK. 872 */ 873 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run( 874 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 875 nsIEditor::EStripWrappers aStripWrappers, 876 AutoClonedSelectionRangeArray& aRangesToDelete, 877 const Element& aEditingHost); 878 879 private: 880 [[nodiscard]] bool IsHandlingRecursively() const { 881 return mParent != nullptr; 882 } 883 884 [[nodiscard]] bool CanFallbackToDeleteRangeWithTransaction( 885 const nsRange& aRangeToDelete) const; 886 887 [[nodiscard]] bool CanFallbackToDeleteRangesWithTransaction( 888 const AutoClonedSelectionRangeArray& aRangesToDelete) const; 889 890 /** 891 * HandleDeleteAroundCollapsedRanges() handles deletion with collapsed 892 * ranges. Callers must guarantee that this is called only when 893 * aRangesToDelete.IsCollapsed() returns true. 894 * 895 * @param aDirectionAndAmount Direction of the deletion. 896 * @param aStripWrappers Must be eStrip or eNoStrip. 897 * @param aRangesToDelete Ranges to delete. This `IsCollapsed()` must 898 * return true. 899 * @param aWSRunScannerAtCaret Scanner instance which scanned from 900 * caret point. 901 * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret 902 * toward aDirectionAndAmount. 903 * @param aEditingHost The editing host. 904 */ 905 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 906 HandleDeleteAroundCollapsedRanges( 907 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 908 nsIEditor::EStripWrappers aStripWrappers, 909 AutoClonedSelectionRangeArray& aRangesToDelete, 910 const WSRunScanner& aWSRunScannerAtCaret, 911 const WSScanResult& aScanFromCaretPointResult, 912 const Element& aEditingHost); 913 nsresult ComputeRangesToDeleteAroundCollapsedRanges( 914 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 915 AutoClonedSelectionRangeArray& aRangesToDelete, 916 const WSRunScanner& aWSRunScannerAtCaret, 917 const WSScanResult& aScanFromCaretPointResult, 918 const Element& aEditingHost) const; 919 920 /** 921 * HandleDeleteNonCollapsedRanges() handles deletion with non-collapsed 922 * ranges. Callers must guarantee that this is called only when 923 * aRangesToDelete.IsCollapsed() returns false. 924 * 925 * @param aDirectionAndAmount Direction of the deletion. 926 * @param aStripWrappers Must be eStrip or eNoStrip. 927 * @param aRangesToDelete The ranges to delete. 928 * @param aSelectionWasCollapsed If the caller extended `Selection` 929 * from collapsed, set this to `Yes`. 930 * Otherwise, i.e., `Selection` is not 931 * collapsed from the beginning, set 932 * this to `No`. 933 * @param aEditingHost The editing host. 934 */ 935 enum class SelectionWasCollapsed { Yes, No }; 936 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 937 HandleDeleteNonCollapsedRanges(HTMLEditor& aHTMLEditor, 938 nsIEditor::EDirection aDirectionAndAmount, 939 nsIEditor::EStripWrappers aStripWrappers, 940 AutoClonedSelectionRangeArray& aRangesToDelete, 941 SelectionWasCollapsed aSelectionWasCollapsed, 942 const Element& aEditingHost); 943 nsresult ComputeRangesToDeleteNonCollapsedRanges( 944 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 945 AutoClonedSelectionRangeArray& aRangesToDelete, 946 SelectionWasCollapsed aSelectionWasCollapsed, 947 const Element& aEditingHost) const; 948 949 /** 950 * Handle deletion of collapsed ranges in a text node. 951 * 952 * @param aDirectionAndAmount Must be eNext or ePrevious. 953 * @param aCaretPosition The position where caret is. This container 954 * must be a text node. 955 * @param aEditingHost The editing host. 956 */ 957 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> 958 HandleDeleteTextAroundCollapsedRanges( 959 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 960 AutoClonedSelectionRangeArray& aRangesToDelete, 961 const Element& aEditingHost); 962 nsresult ComputeRangesToDeleteTextAroundCollapsedRanges( 963 nsIEditor::EDirection aDirectionAndAmount, 964 AutoClonedSelectionRangeArray& aRangesToDelete) const; 965 966 /** 967 * Handle deletion of atomic elements like <br>, <hr>, <img>, <input>, etc and 968 * data nodes except text node (e.g., comment node). Note that don't call this 969 * directly with `<hr>` element. 970 * 971 * @param aAtomicContent The atomic content to be deleted. 972 * @param aCaretPoint The caret point (i.e., selection start or 973 * end). 974 * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized 975 * with the caret point. 976 * @param aEditingHost The editing host. 977 */ 978 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> 979 HandleDeleteAtomicContent(HTMLEditor& aHTMLEditor, nsIContent& aAtomicContent, 980 const EditorDOMPoint& aCaretPoint, 981 const WSRunScanner& aWSRunScannerAtCaret, 982 const Element& aEditingHost); 983 nsresult ComputeRangesToDeleteAtomicContent( 984 const nsIContent& aAtomicContent, 985 AutoClonedSelectionRangeArray& aRangesToDelete) const; 986 987 /** 988 * GetAtomicContnetToDelete() returns better content that is deletion of 989 * atomic element. If aScanFromCaretPointResult is special, since this 990 * point may not be editable, we look for better point to remove atomic 991 * content. 992 * 993 * @param aDirectionAndAmount Direction of the deletion. 994 * @param aWSRunScannerAtCaret WSRunScanner instance which was 995 * initialized with the caret point. 996 * @param aScanFromCaretPointResult Scan result of aWSRunScannerAtCaret 997 * toward aDirectionAndAmount. 998 */ 999 [[nodiscard]] static nsIContent* GetAtomicContentToDelete( 1000 nsIEditor::EDirection aDirectionAndAmount, 1001 const WSRunScanner& aWSRunScannerAtCaret, 1002 const WSScanResult& aScanFromCaretPointResult) MOZ_NONNULL_RETURN; 1003 1004 /** 1005 * HandleDeleteAtOtherBlockBoundary() handles deletion at other block boundary 1006 * (i.e., immediately before or after a block). If this does not join blocks, 1007 * `Run()` may be called recursively with creating another instance. 1008 * 1009 * @param aDirectionAndAmount Direction of the deletion. 1010 * @param aStripWrappers Must be eStrip or eNoStrip. 1011 * @param aOtherBlockElement The block element which follows the caret or 1012 * is followed by caret. 1013 * @param aCaretPoint The caret point (i.e., selection start or 1014 * end). 1015 * @param aWSRunScannerAtCaret WSRunScanner instance which was initialized 1016 * with the caret point. 1017 * @param aRangesToDelete Ranges to delete of the caller. This should 1018 * be collapsed and the point should match with 1019 * aCaretPoint. 1020 * @param aEditingHost The editing host. 1021 */ 1022 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 1023 HandleDeleteAtOtherBlockBoundary( 1024 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1025 nsIEditor::EStripWrappers aStripWrappers, Element& aOtherBlockElement, 1026 const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret, 1027 AutoClonedSelectionRangeArray& aRangesToDelete, 1028 const Element& aEditingHost); 1029 1030 /** 1031 * ExtendOrShrinkRangeToDelete() extends aRangeToDelete if there are 1032 * an invisible <br> element and/or some parent empty elements. 1033 * 1034 * @param aLimitersAndCaretData The frame selection data. 1035 * @param aRangeToDelete The range to be extended for deletion. This 1036 * must not be collapsed, must be positioned. 1037 */ 1038 template <typename EditorDOMRangeType> 1039 [[nodiscard]] Result<EditorRawDOMRange, nsresult> ExtendOrShrinkRangeToDelete( 1040 const HTMLEditor& aHTMLEditor, 1041 const LimitersAndCaretData& aLimitersAndCaretData, 1042 const EditorDOMRangeType& aRangeToDelete) const; 1043 1044 /** 1045 * Extend the start boundary of aRangeToDelete to contain ancestor inline 1046 * elements which will be empty once the content in aRangeToDelete is removed 1047 * from the tree. 1048 * 1049 * NOTE: This is designed for deleting inline elements which become empty if 1050 * aRangeToDelete which crosses a block boundary of right block child. 1051 * Therefore, you may need to improve this method if you want to use this in 1052 * the other cases. 1053 * 1054 * @param aRangeToDelete [in/out] The range to delete. This start 1055 * boundary may be modified. 1056 * @param aEditingHost The editing host. 1057 * @return true if aRangeToDelete is modified. 1058 * false if aRangeToDelete is not modified. 1059 * error if aRangeToDelete gets unexpected 1060 * situation. 1061 */ 1062 [[nodiscard]] static Result<bool, nsresult> 1063 ExtendRangeToContainAncestorInlineElementsAtStart( 1064 nsRange& aRangeToDelete, const Element& aEditingHost); 1065 1066 /** 1067 * A helper method for ExtendOrShrinkRangeToDelete(). This returns shrunken 1068 * range if aRangeToDelete selects all over list elements which have some list 1069 * item elements to avoid to delete all list items from the list element. 1070 */ 1071 [[nodiscard]] static EditorRawDOMRange 1072 GetRangeToAvoidDeletingAllListItemsIfSelectingAllOverListElements( 1073 const EditorRawDOMRange& aRangeToDelete); 1074 1075 /** 1076 * DeleteUnnecessaryNodes() removes unnecessary nodes around aRange. 1077 */ 1078 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 1079 DeleteUnnecessaryNodes(HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange, 1080 const Element& aEditingHost); 1081 1082 /** 1083 * If aContent is a text node that contains only collapsed white-space or 1084 * empty and editable. 1085 */ 1086 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 1087 DeleteNodeIfInvisibleAndEditableTextNode(HTMLEditor& aHTMLEditor, 1088 nsIContent& aContent); 1089 1090 /** 1091 * DeleteParentBlocksIfEmpty() removes parent block elements if they 1092 * don't have visible contents. Note that due performance issue of 1093 * WhiteSpaceVisibilityKeeper, this call may be expensive. And also note that 1094 * this removes a empty block with a transaction. So, please make sure that 1095 * you've already created `AutoPlaceholderBatch`. 1096 * 1097 * @param aPoint The point whether this method climbing up the DOM 1098 * tree to remove empty parent blocks. 1099 * @return NS_OK if one or more empty block parents are deleted. 1100 * NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND if the point is 1101 * not in empty block. 1102 * Or NS_ERROR_* if something unexpected occurs. 1103 */ 1104 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 1105 DeleteParentBlocksWithTransactionIfEmpty(HTMLEditor& aHTMLEditor, 1106 const EditorDOMPoint& aPoint, 1107 const Element& aEditingHost); 1108 1109 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> 1110 FallbackToDeleteRangeWithTransaction(HTMLEditor& aHTMLEditor, 1111 nsRange& aRangeToDelete) const { 1112 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 1113 MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete)); 1114 Result<CaretPoint, nsresult> caretPointOrError = 1115 aHTMLEditor.DeleteRangeWithTransaction(mOriginalDirectionAndAmount, 1116 mOriginalStripWrappers, 1117 aRangeToDelete); 1118 NS_WARNING_ASSERTION(caretPointOrError.isOk(), 1119 "EditorBase::DeleteRangeWithTransaction() failed"); 1120 return caretPointOrError; 1121 } 1122 1123 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> 1124 FallbackToDeleteRangesWithTransaction( 1125 HTMLEditor& aHTMLEditor, AutoClonedSelectionRangeArray& aRangesToDelete, 1126 const Element& aEditingHost) const; 1127 1128 /** 1129 * Compute target range(s) which will be called by 1130 * `EditorBase::DeleteRangeWithTransaction()` or 1131 * `HTMLEditor::DeleteRangesWithTransaction()`. 1132 * TODO: We should not use it for consistency with each deletion handler 1133 * in this and nested classes. 1134 */ 1135 nsresult ComputeRangeToDeleteRangeWithTransaction( 1136 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1137 nsRange& aRange, const Element& aEditingHost) const; 1138 nsresult ComputeRangesToDeleteRangesWithTransaction( 1139 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1140 AutoClonedSelectionRangeArray& aRangesToDelete, 1141 const Element& aEditingHost) const; 1142 1143 nsresult FallbackToComputeRangeToDeleteRangeWithTransaction( 1144 const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete, 1145 const Element& aEditingHost) const { 1146 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 1147 MOZ_ASSERT(CanFallbackToDeleteRangeWithTransaction(aRangeToDelete)); 1148 nsresult rv = ComputeRangeToDeleteRangeWithTransaction( 1149 aHTMLEditor, mOriginalDirectionAndAmount, aRangeToDelete, aEditingHost); 1150 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1151 "AutoDeleteRangesHandler::" 1152 "ComputeRangeToDeleteRangeWithTransaction() failed"); 1153 return rv; 1154 } 1155 nsresult FallbackToComputeRangesToDeleteRangesWithTransaction( 1156 const HTMLEditor& aHTMLEditor, 1157 AutoClonedSelectionRangeArray& aRangesToDelete, 1158 const Element& aEditingHost) const { 1159 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 1160 MOZ_ASSERT(CanFallbackToDeleteRangesWithTransaction(aRangesToDelete)); 1161 nsresult rv = ComputeRangesToDeleteRangesWithTransaction( 1162 aHTMLEditor, mOriginalDirectionAndAmount, aRangesToDelete, 1163 aEditingHost); 1164 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1165 "AutoDeleteRangesHandler::" 1166 "ComputeRangesToDeleteRangesWithTransaction() failed"); 1167 return rv; 1168 } 1169 1170 class MOZ_STACK_CLASS AutoBlockElementsJoiner; 1171 class MOZ_STACK_CLASS AutoEmptyBlockAncestorDeleter; 1172 1173 const AutoDeleteRangesHandler* const mParent; 1174 nsIEditor::EDirection mOriginalDirectionAndAmount; 1175 nsIEditor::EStripWrappers mOriginalStripWrappers; 1176 }; 1177 1178 /** 1179 * Handle join block elements. Despite the name, this may just move first line 1180 * of a block into another block or just deleted the range with keeping table 1181 * structure. 1182 */ 1183 class MOZ_STACK_CLASS 1184 HTMLEditor::AutoDeleteRangesHandler::AutoBlockElementsJoiner final { 1185 public: 1186 AutoBlockElementsJoiner() = delete; 1187 explicit AutoBlockElementsJoiner( 1188 AutoDeleteRangesHandler& aDeleteRangesHandler) 1189 : mDeleteRangesHandler(&aDeleteRangesHandler), 1190 mDeleteRangesHandlerConst(aDeleteRangesHandler) {} 1191 explicit AutoBlockElementsJoiner( 1192 const AutoDeleteRangesHandler& aDeleteRangesHandler) 1193 : mDeleteRangesHandler(nullptr), 1194 mDeleteRangesHandlerConst(aDeleteRangesHandler) {} 1195 1196 /** 1197 * PrepareToDeleteAtCurrentBlockBoundary() considers left content and right 1198 * content which are joined for handling deletion at current block boundary 1199 * (i.e., at start or end of the current block). 1200 * 1201 * @param aHTMLEditor The HTML editor. 1202 * @param aDirectionAndAmount Direction of the deletion. 1203 * @param aCurrentBlockElement The current block element. 1204 * @param aCaretPoint The caret point (i.e., selection start 1205 * or end). 1206 * @param aEditingHost The editing host. 1207 * @return true if can continue to handle the 1208 * deletion. 1209 */ 1210 [[nodiscard]] bool PrepareToDeleteAtCurrentBlockBoundary( 1211 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1212 Element& aCurrentBlockElement, const EditorDOMPoint& aCaretPoint, 1213 const Element& aEditingHost); 1214 1215 /** 1216 * PrepareToDeleteAtOtherBlockBoundary() considers left content and right 1217 * content which are joined for handling deletion at other block boundary 1218 * (i.e., immediately before or after a block). 1219 * 1220 * @param aHTMLEditor The HTML editor. 1221 * @param aDirectionAndAmount Direction of the deletion. 1222 * @param aOtherBlockElement The block element which follows the 1223 * caret or is followed by caret. 1224 * @param aCaretPoint The caret point (i.e., selection start 1225 * or end). 1226 * @param aWSRunScannerAtCaret WSRunScanner instance which was 1227 * initialized with the caret point. 1228 * @return true if can continue to handle the 1229 * deletion. 1230 */ 1231 [[nodiscard]] bool PrepareToDeleteAtOtherBlockBoundary( 1232 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1233 Element& aOtherBlockElement, const EditorDOMPoint& aCaretPoint, 1234 const WSRunScanner& aWSRunScannerAtCaret); 1235 1236 /** 1237 * PrepareToDeleteNonCollapsedRange() considers left block element and 1238 * right block element which are inclusive ancestor block element of 1239 * start and end container of aRangeToDelete 1240 * 1241 * @param aHTMLEditor The HTML editor. 1242 * @param aRangeToDelete The range to delete. Must not be 1243 * collapsed. 1244 * @param aEditingHost The editing host. 1245 * @return true if can continue to handle the 1246 * deletion. 1247 */ 1248 [[nodiscard]] bool PrepareToDeleteNonCollapsedRange( 1249 const HTMLEditor& aHTMLEditor, const nsRange& aRangeToDelete, 1250 const Element& aEditingHost); 1251 1252 /** 1253 * Run() executes the joining. 1254 * 1255 * @param aHTMLEditor The HTML editor. 1256 * @param aDirectionAndAmount Direction of the deletion. 1257 * @param aStripWrappers Must be eStrip or eNoStrip. 1258 * @param aCaretPoint The caret point (i.e., selection start 1259 * or end). 1260 * @param aRangeToDelete The range to delete. This should be 1261 * collapsed and match with aCaretPoint. 1262 */ 1263 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run( 1264 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1265 nsIEditor::EStripWrappers aStripWrappers, 1266 const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete, 1267 const Element& aEditingHost); 1268 1269 nsresult ComputeRangeToDelete(const HTMLEditor& aHTMLEditor, 1270 nsIEditor::EDirection aDirectionAndAmount, 1271 const EditorDOMPoint& aCaretPoint, 1272 nsRange& aRangeToDelete, 1273 const Element& aEditingHost) const; 1274 1275 /** 1276 * Run() executes the joining. 1277 * 1278 * @param aHTMLEditor The HTML editor. 1279 * @param aLimitersAndCaretData The data copied from nsFrameSelection. 1280 * @param aDirectionAndAmount Direction of the deletion. 1281 * @param aStripWrappers Whether delete or keep new empty 1282 * ancestor elements. 1283 * @param aRangeToDelete The range to delete. Must not be 1284 * collapsed. 1285 * @param aSelectionWasCollapsed Whether selection was or was not 1286 * collapsed when starting to handle 1287 * deletion. 1288 */ 1289 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> Run( 1290 HTMLEditor& aHTMLEditor, 1291 const LimitersAndCaretData& aLimitersAndCaretData, 1292 nsIEditor::EDirection aDirectionAndAmount, 1293 nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, 1294 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 1295 const Element& aEditingHost); 1296 1297 nsresult ComputeRangeToDelete( 1298 const HTMLEditor& aHTMLEditor, 1299 const AutoClonedSelectionRangeArray& aRangesToDelete, 1300 nsIEditor::EDirection aDirectionAndAmount, nsRange& aRangeToDelete, 1301 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 1302 const Element& aEditingHost) const; 1303 1304 [[nodiscard]] nsIContent* GetLeafContentInOtherBlockElement() const { 1305 MOZ_ASSERT(mMode == Mode::JoinOtherBlock); 1306 return mLeafContentInOtherBlock; 1307 } 1308 1309 private: 1310 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 1311 HandleDeleteAtCurrentBlockBoundary(HTMLEditor& aHTMLEditor, 1312 nsIEditor::EDirection aDirectionAndAmount, 1313 const EditorDOMPoint& aCaretPoint, 1314 const Element& aEditingHost); 1315 nsresult ComputeRangeToDeleteAtCurrentBlockBoundary( 1316 const HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCaretPoint, 1317 nsRange& aRangeToDelete, const Element& aEditingHost) const; 1318 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 1319 HandleDeleteAtOtherBlockBoundary(HTMLEditor& aHTMLEditor, 1320 nsIEditor::EDirection aDirectionAndAmount, 1321 nsIEditor::EStripWrappers aStripWrappers, 1322 const EditorDOMPoint& aCaretPoint, 1323 nsRange& aRangeToDelete, 1324 const Element& aEditingHost); 1325 // FYI: This method may modify selection, but it won't cause running 1326 // script because of `AutoHideSelectionChanges` which blocks 1327 // selection change listeners and the selection change event 1328 // dispatcher. 1329 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult ComputeRangeToDeleteAtOtherBlockBoundary( 1330 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1331 const EditorDOMPoint& aCaretPoint, nsRange& aRangeToDelete, 1332 const Element& aEditingHost) const; 1333 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 1334 JoinBlockElementsInSameParent( 1335 HTMLEditor& aHTMLEditor, 1336 const LimitersAndCaretData& aLimitersAndCaretData, 1337 nsIEditor::EDirection aDirectionAndAmount, 1338 nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, 1339 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 1340 const Element& aEditingHost); 1341 nsresult ComputeRangeToJoinBlockElementsInSameParent( 1342 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1343 nsRange& aRangeToDelete, const Element& aEditingHost) const; 1344 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 1345 HandleDeleteLineBreak(HTMLEditor& aHTMLEditor, 1346 nsIEditor::EDirection aDirectionAndAmount, 1347 const EditorDOMPoint& aCaretPoint, 1348 const Element& aEditingHost); 1349 enum class ComputeRangeFor : bool { GetTargetRanges, ToDeleteTheRange }; 1350 nsresult ComputeRangeToDeleteLineBreak( 1351 const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete, 1352 const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const; 1353 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 1354 DeleteContentInRange(HTMLEditor& aHTMLEditor, 1355 const LimitersAndCaretData& aLimitersAndCaretData, 1356 nsIEditor::EDirection aDirectionAndAmount, 1357 nsIEditor::EStripWrappers aStripWrappers, 1358 nsRange& aRangeToDelete, const Element& aEditingHost); 1359 nsresult ComputeRangeToDeleteContentInRange( 1360 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1361 nsRange& aRange, const Element& aEditingHost) const; 1362 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditActionResult, nsresult> 1363 HandleDeleteNonCollapsedRange( 1364 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1365 nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete, 1366 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 1367 const Element& aEditingHost); 1368 nsresult ComputeRangeToDeleteNonCollapsedRange( 1369 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1370 nsRange& aRangeToDelete, 1371 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed, 1372 const Element& aEditingHost) const; 1373 1374 /** 1375 * JoinNodesDeepWithTransaction() joins aLeftNode and aRightNode "deeply". 1376 * First, they are joined simply, then, new right node is assumed as the 1377 * child at length of the left node before joined and new left node is 1378 * assumed as its previous sibling. Then, they will be joined again. 1379 * And then, these steps are repeated. 1380 * 1381 * @param aLeftContent The node which will be removed form the tree. 1382 * @param aRightContent The node which will be inserted the contents of 1383 * aRightContent. 1384 * @return The point of the first child of the last right 1385 * node. The result is always set if this succeeded. 1386 */ 1387 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult> 1388 JoinNodesDeepWithTransaction(HTMLEditor& aHTMLEditor, 1389 nsIContent& aLeftContent, 1390 nsIContent& aRightContent); 1391 1392 enum class PutCaretTo : bool { StartOfRange, EndOfRange }; 1393 1394 /** 1395 * DeleteNodesEntirelyInRangeButKeepTableStructure() removes each node in 1396 * aArrayOfContent. However, if some nodes are part of a table, removes all 1397 * children of them instead. I.e., this does not make damage to table 1398 * structure at the range, but may remove table entirely if it's in the 1399 * range. 1400 */ 1401 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> 1402 DeleteNodesEntirelyInRangeButKeepTableStructure( 1403 HTMLEditor& aHTMLEditor, 1404 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContent, 1405 PutCaretTo aPutCaretTo); 1406 [[nodiscard]] bool 1407 NeedsToJoinNodesAfterDeleteNodesEntirelyInRangeButKeepTableStructure( 1408 const HTMLEditor& aHTMLEditor, 1409 const nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents, 1410 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) 1411 const; 1412 Result<bool, nsresult> 1413 ComputeRangeToDeleteNodesEntirelyInRangeButKeepTableStructure( 1414 const HTMLEditor& aHTMLEditor, nsRange& aRange, 1415 AutoDeleteRangesHandler::SelectionWasCollapsed aSelectionWasCollapsed) 1416 const; 1417 1418 /** 1419 * DeleteContentButKeepTableStructure() removes aContent if it's an element 1420 * which is part of a table structure. If it's a part of table structure, 1421 * removes its all children recursively. I.e., this may delete all of a 1422 * table, but won't break table structure partially. 1423 * 1424 * @param aContent The content which or whose all children should 1425 * be removed. 1426 */ 1427 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> 1428 DeleteContentButKeepTableStructure(HTMLEditor& aHTMLEditor, 1429 nsIContent& aContent); 1430 1431 /** 1432 * DeleteTextAtStartAndEndOfRange() removes text if start and/or end of 1433 * aRange is in a text node. 1434 */ 1435 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> 1436 DeleteTextAtStartAndEndOfRange(HTMLEditor& aHTMLEditor, nsRange& aRange, 1437 PutCaretTo aPutCaretTo); 1438 1439 /** 1440 * Return a block element which is an inclusive ancestor of the container of 1441 * aPoint if aPoint is start of ancestor blocks. For example, if `<div 1442 * id=div1>abc<div id=div2><div id=div3>[]def</div></div></div>`, return 1443 * #div2. 1444 */ 1445 template <typename EditorDOMPointType> 1446 [[nodiscard]] static Result<Element*, nsresult> 1447 GetMostDistantBlockAncestorIfPointIsStartAtBlock( 1448 const EditorDOMPointType& aPoint, const Element& aEditingHost, 1449 const Element* aAncestorLimiter = nullptr); 1450 1451 /** 1452 * Extend aRangeToDelete to contain new empty inline ancestors and contain 1453 * an invisible <br> element before right child block which causes an empty 1454 * line but the range starts after it. 1455 */ 1456 void ExtendRangeToDeleteNonCollapsedRange( 1457 const HTMLEditor& aHTMLEditor, nsRange& aRangeToDelete, 1458 const Element& aEditingHost, ComputeRangeFor aComputeRangeFor) const; 1459 1460 /** 1461 * Compute mLeafContentInOtherBlock from the DOM. 1462 */ 1463 [[nodiscard]] nsIContent* ComputeLeafContentInOtherBlockElement( 1464 nsIEditor::EDirection aDirectionAndAmount) const; 1465 1466 class MOZ_STACK_CLASS AutoInclusiveAncestorBlockElementsJoiner; 1467 1468 enum class Mode { 1469 NotInitialized, 1470 JoinCurrentBlock, 1471 JoinOtherBlock, 1472 JoinBlocksInSameParent, 1473 DeleteBRElement, 1474 // The instance will handle only the <br> element immediately before a 1475 // block. 1476 DeletePrecedingBRElementOfBlock, 1477 // The instance will handle only the preceding preformatted line break 1478 // before a block. 1479 DeletePrecedingPreformattedLineBreak, 1480 DeleteContentInRange, 1481 DeleteNonCollapsedRange, 1482 // The instance will handle preceding lines of the right block and content 1483 // in the range in the right block. 1484 DeletePrecedingLinesAndContentInRange, 1485 }; 1486 AutoDeleteRangesHandler* mDeleteRangesHandler; 1487 const AutoDeleteRangesHandler& mDeleteRangesHandlerConst; 1488 nsCOMPtr<nsIContent> mLeftContent; 1489 nsCOMPtr<nsIContent> mRightContent; 1490 nsCOMPtr<nsIContent> mLeafContentInOtherBlock; 1491 RefPtr<Element> mOtherBlockElement; 1492 // mSkippedInvisibleContents stores all content nodes which are skipped at 1493 // scanning mLeftContent and mRightContent. The content nodes should be 1494 // removed at deletion. 1495 AutoTArray<OwningNonNull<nsIContent>, 8> mSkippedInvisibleContents; 1496 RefPtr<dom::HTMLBRElement> mBRElement; 1497 EditorDOMPointInText mPreformattedLineBreak; 1498 Mode mMode = Mode::NotInitialized; 1499 }; 1500 1501 /** 1502 * Actually handle joining inclusive ancestor block elements. 1503 */ 1504 class MOZ_STACK_CLASS HTMLEditor::AutoDeleteRangesHandler:: 1505 AutoBlockElementsJoiner::AutoInclusiveAncestorBlockElementsJoiner final { 1506 public: 1507 AutoInclusiveAncestorBlockElementsJoiner() = delete; 1508 AutoInclusiveAncestorBlockElementsJoiner( 1509 nsIContent& aInclusiveDescendantOfLeftBlockElement, 1510 nsIContent& aInclusiveDescendantOfRightBlockElement) 1511 : mInclusiveDescendantOfLeftBlockElement( 1512 aInclusiveDescendantOfLeftBlockElement), 1513 mInclusiveDescendantOfRightBlockElement( 1514 aInclusiveDescendantOfRightBlockElement), 1515 mCanJoinBlocks(false), 1516 mFallbackToDeleteLeafContent(false) {} 1517 1518 [[nodiscard]] bool IsSet() const { 1519 return mLeftBlockElement && mRightBlockElement; 1520 } 1521 [[nodiscard]] bool IsSameBlockElement() const { 1522 return mLeftBlockElement && mLeftBlockElement == mRightBlockElement; 1523 } 1524 1525 /** 1526 * Prepare for joining inclusive ancestor block elements. When this 1527 * returns false, the deletion should be canceled. 1528 */ 1529 [[nodiscard]] Result<bool, nsresult> Prepare(const HTMLEditor& aHTMLEditor, 1530 const Element& aEditingHost); 1531 1532 /** 1533 * When this returns true, this can join the blocks with `Run()`. 1534 */ 1535 [[nodiscard]] bool CanJoinBlocks() const { return mCanJoinBlocks; } 1536 1537 /** 1538 * When this returns true, `Run()` must return "ignored" so that 1539 * caller can skip calling `Run()`. This is available only when 1540 * `CanJoinBlocks()` returns `true`. 1541 * TODO: This should be merged into `CanJoinBlocks()` in the future. 1542 */ 1543 [[nodiscard]] bool ShouldDeleteLeafContentInstead() const { 1544 MOZ_ASSERT(CanJoinBlocks()); 1545 return mFallbackToDeleteLeafContent; 1546 } 1547 1548 /** 1549 * ComputeRangesToDelete() extends aRangeToDelete includes the element 1550 * boundaries between joining blocks. If they won't be joined, this 1551 * collapses the range to aCaretPoint. 1552 */ 1553 [[nodiscard]] nsresult ComputeRangeToDelete(const HTMLEditor& aHTMLEditor, 1554 const EditorDOMPoint& aCaretPoint, 1555 nsRange& aRangeToDelete) const; 1556 1557 /** 1558 * Join inclusive ancestor block elements which are found by preceding 1559 * Prepare() call. 1560 * The right element is always joined to the left element. 1561 * If the elements are the same type and not nested within each other, 1562 * JoinEditableNodesWithTransaction() is called (example, joining two 1563 * list items together into one). 1564 * If the elements are not the same type, or one is a descendant of the 1565 * other, we instead destroy the right block placing its children into 1566 * left block. 1567 */ 1568 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> Run( 1569 HTMLEditor& aHTMLEditor, const Element& aEditingHost); 1570 1571 private: 1572 /** 1573 * This method returns true when 1574 * `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()`, 1575 * `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` and 1576 * `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` handle it 1577 * with the `if` block of the main lambda of them. 1578 */ 1579 [[nodiscard]] bool CanMergeLeftAndRightBlockElements() const { 1580 if (!IsSet()) { 1581 return false; 1582 } 1583 // `MergeFirstLineOfRightBlockElementIntoDescendantLeftBlockElement()` 1584 if (mPointContainingTheOtherBlockElement.GetContainer() == 1585 mRightBlockElement) { 1586 return mNewListElementTagNameOfRightListElement.isSome(); 1587 } 1588 // `MergeFirstLineOfRightBlockElementIntoAncestorLeftBlockElement()` 1589 if (mPointContainingTheOtherBlockElement.GetContainer() == 1590 mLeftBlockElement) { 1591 return mNewListElementTagNameOfRightListElement.isSome() && 1592 mRightBlockElement->GetChildCount(); 1593 } 1594 MOZ_ASSERT(!mPointContainingTheOtherBlockElement.IsSet()); 1595 // `MergeFirstLineOfRightBlockElementIntoLeftBlockElement()` 1596 return mNewListElementTagNameOfRightListElement.isSome() || 1597 (mLeftBlockElement->NodeInfo()->NameAtom() == 1598 mRightBlockElement->NodeInfo()->NameAtom() && 1599 EditorUtils::GetComputedWhiteSpaceStyles(*mLeftBlockElement) == 1600 EditorUtils::GetComputedWhiteSpaceStyles(*mRightBlockElement)); 1601 } 1602 1603 OwningNonNull<nsIContent> mInclusiveDescendantOfLeftBlockElement; 1604 OwningNonNull<nsIContent> mInclusiveDescendantOfRightBlockElement; 1605 RefPtr<Element> mLeftBlockElement; 1606 RefPtr<Element> mRightBlockElement; 1607 Maybe<nsAtom*> mNewListElementTagNameOfRightListElement; 1608 EditorDOMPoint mPointContainingTheOtherBlockElement; 1609 RefPtr<dom::HTMLBRElement> mPrecedingInvisibleBRElement; 1610 bool mCanJoinBlocks; 1611 bool mFallbackToDeleteLeafContent; 1612 }; 1613 1614 /** 1615 * Handle deleting empty block ancestors. 1616 */ 1617 class MOZ_STACK_CLASS 1618 HTMLEditor::AutoDeleteRangesHandler::AutoEmptyBlockAncestorDeleter final { 1619 public: 1620 /** 1621 * ScanEmptyBlockInclusiveAncestor() scans an inclusive ancestor element 1622 * which is empty and a block element. Then, stores the result and 1623 * returns the found empty block element. 1624 * 1625 * @param aHTMLEditor The HTMLEditor. 1626 * @param aStartContent Start content to look for empty ancestors. 1627 */ 1628 [[nodiscard]] Element* ScanEmptyBlockInclusiveAncestor( 1629 const HTMLEditor& aHTMLEditor, nsIContent& aStartContent); 1630 1631 /** 1632 * ComputeTargetRanges() computes "target ranges" for deleting 1633 * `mEmptyInclusiveAncestorBlockElement`. 1634 */ 1635 nsresult ComputeTargetRanges( 1636 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1637 const Element& aEditingHost, 1638 AutoClonedSelectionRangeArray& aRangesToDelete) const; 1639 1640 /** 1641 * Deletes found empty block element by `ScanEmptyBlockInclusiveAncestor()`. 1642 * If found one is a list item element, calls 1643 * `MaybeInsertBRElementBeforeEmptyListItemElement()` before deleting 1644 * the list item element. 1645 * If found empty ancestor is not a list item element, 1646 * `GetNewCaretPosition()` will be called to determine new caret position. 1647 * Finally, removes the empty block ancestor. 1648 * 1649 * @param aHTMLEditor The HTMLEditor. 1650 * @param aDirectionAndAmount If found empty ancestor block is a list item 1651 * element, this is ignored. Otherwise: 1652 * - If eNext, eNextWord or eToEndOfLine, 1653 * collapse Selection to after found empty 1654 * ancestor. 1655 * - If ePrevious, ePreviousWord or 1656 * eToBeginningOfLine, collapse Selection to 1657 * end of previous editable node. 1658 * - Otherwise, eNone is allowed but does 1659 * nothing. 1660 * @param aEditingHost The editing host. 1661 */ 1662 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> Run( 1663 HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1664 const Element& aEditingHost); 1665 1666 private: 1667 /** 1668 * MaybeReplaceSubListWithNewListItem() replaces 1669 * mEmptyInclusiveAncestorBlockElement with new list item element 1670 * (containing <br>) if: 1671 * - mEmptyInclusiveAncestorBlockElement is a list element 1672 * - The parent of mEmptyInclusiveAncestorBlockElement is a list element 1673 * - The parent becomes empty after deletion 1674 * If this does not perform the replacement, returns "ignored". 1675 */ 1676 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<DeleteRangeResult, nsresult> 1677 MaybeReplaceSubListWithNewListItem(HTMLEditor& aHTMLEditor); 1678 1679 /** 1680 * MaybeInsertBRElementBeforeEmptyListItemElement() inserts a `<br>` element 1681 * if `mEmptyInclusiveAncestorBlockElement` is a list item element which 1682 * is first editable element in its parent, and its grand parent is not a 1683 * list element, inserts a `<br>` element before the empty list item. 1684 */ 1685 [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CreateLineBreakResult, nsresult> 1686 MaybeInsertBRElementBeforeEmptyListItemElement(HTMLEditor& aHTMLEditor); 1687 1688 /** 1689 * GetNewCaretPosition() returns new caret position after deleting 1690 * `mEmptyInclusiveAncestorBlockElement`. 1691 */ 1692 [[nodiscard]] Result<CaretPoint, nsresult> GetNewCaretPosition( 1693 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 1694 const Element& aEditingHost) const; 1695 1696 RefPtr<Element> mEmptyInclusiveAncestorBlockElement; 1697 }; 1698 1699 /****************************************************************************** 1700 * NormalizedStringToInsertText stores normalized insertion string with 1701 * normalized surrounding white-spaces if the insertion point is surrounded by 1702 * collapsible white-spaces. For deleting invisible (collapsed) white-spaces, 1703 * this also stores the replace range and new white-space length before and 1704 * after the inserting text. 1705 ******************************************************************************/ 1706 1707 struct MOZ_STACK_CLASS HTMLEditor::NormalizedStringToInsertText final { 1708 NormalizedStringToInsertText( 1709 const nsAString& aStringToInsertWithoutSurroundingWhiteSpaces, 1710 const EditorDOMPoint& aPointToInsert) 1711 : mNormalizedString(aStringToInsertWithoutSurroundingWhiteSpaces), 1712 mReplaceStartOffset( 1713 aPointToInsert.IsInTextNode() ? aPointToInsert.Offset() : 0u), 1714 mReplaceEndOffset(mReplaceStartOffset) { 1715 MOZ_ASSERT(aStringToInsertWithoutSurroundingWhiteSpaces.Length() == 1716 InsertingTextLength()); 1717 } 1718 1719 NormalizedStringToInsertText( 1720 const nsAString& aStringToInsertWithSurroundingWhiteSpaces, 1721 uint32_t aInsertOffset, uint32_t aReplaceStartOffset, 1722 uint32_t aReplaceLength, 1723 uint32_t aNewPrecedingWhiteSpaceLengthBeforeInsertionString, 1724 uint32_t aNewFollowingWhiteSpaceLengthAfterInsertionString) 1725 : mNormalizedString(aStringToInsertWithSurroundingWhiteSpaces), 1726 mReplaceStartOffset(aReplaceStartOffset), 1727 mReplaceEndOffset(mReplaceStartOffset + aReplaceLength), 1728 mReplaceLengthBefore(aInsertOffset - mReplaceStartOffset), 1729 mReplaceLengthAfter(aReplaceLength - mReplaceLengthBefore), 1730 mNewLengthBefore(aNewPrecedingWhiteSpaceLengthBeforeInsertionString), 1731 mNewLengthAfter(aNewFollowingWhiteSpaceLengthAfterInsertionString) { 1732 MOZ_ASSERT(aReplaceStartOffset <= aInsertOffset); 1733 MOZ_ASSERT(aReplaceStartOffset + aReplaceLength >= aInsertOffset); 1734 MOZ_ASSERT(aNewPrecedingWhiteSpaceLengthBeforeInsertionString + 1735 aNewFollowingWhiteSpaceLengthAfterInsertionString < 1736 mNormalizedString.Length()); 1737 MOZ_ASSERT(mReplaceLengthBefore + mReplaceLengthAfter == ReplaceLength()); 1738 MOZ_ASSERT(mReplaceLengthBefore >= mNewLengthBefore); 1739 MOZ_ASSERT(mReplaceLengthAfter >= mNewLengthAfter); 1740 } 1741 1742 NormalizedStringToInsertText GetMinimizedData(const Text& aText) const { 1743 if (mNormalizedString.IsEmpty() || !ReplaceLength()) { 1744 return *this; 1745 } 1746 const dom::CharacterDataBuffer& characterDataBuffer = aText.DataBuffer(); 1747 const uint32_t minimizedReplaceStart = [&]() { 1748 const auto firstDiffCharOffset = 1749 mNewLengthBefore ? characterDataBuffer.FindFirstDifferentCharOffset( 1750 PrecedingWhiteSpaces(), mReplaceStartOffset) 1751 : dom::CharacterDataBuffer::kNotFound; 1752 if (firstDiffCharOffset == dom::CharacterDataBuffer::kNotFound) { 1753 return 1754 // We don't need to insert new normalized white-spaces before the 1755 // inserting string, 1756 (mReplaceStartOffset + mReplaceLengthBefore) 1757 // but keep extending the replacing range for deleting invisible 1758 // white-spaces. 1759 - DeletingPrecedingInvisibleWhiteSpaces(); 1760 } 1761 return firstDiffCharOffset; 1762 }(); 1763 const uint32_t minimizedReplaceEnd = [&]() { 1764 const auto lastDiffCharOffset = 1765 mNewLengthAfter ? characterDataBuffer.RFindFirstDifferentCharOffset( 1766 FollowingWhiteSpaces(), mReplaceEndOffset) 1767 : dom::CharacterDataBuffer::kNotFound; 1768 if (lastDiffCharOffset == dom::CharacterDataBuffer::kNotFound) { 1769 return 1770 // We don't need to insert new normalized white-spaces after the 1771 // inserting string, 1772 (mReplaceEndOffset - mReplaceLengthAfter) 1773 // but keep extending the replacing range for deleting invisible 1774 // white-spaces. 1775 + DeletingFollowingInvisibleWhiteSpaces(); 1776 } 1777 return lastDiffCharOffset + 1u; 1778 }(); 1779 if (minimizedReplaceStart == mReplaceStartOffset && 1780 minimizedReplaceEnd == mReplaceEndOffset) { 1781 return *this; 1782 } 1783 const uint32_t newPrecedingWhiteSpaceLength = 1784 mNewLengthBefore - (minimizedReplaceStart - mReplaceStartOffset); 1785 const uint32_t newFollowingWhiteSpaceLength = 1786 mNewLengthAfter - (mReplaceEndOffset - minimizedReplaceEnd); 1787 return NormalizedStringToInsertText( 1788 Substring(mNormalizedString, 1789 mNewLengthBefore - newPrecedingWhiteSpaceLength, 1790 mNormalizedString.Length() - 1791 (mNewLengthBefore - newPrecedingWhiteSpaceLength) - 1792 (mNewLengthAfter - newFollowingWhiteSpaceLength)), 1793 OffsetToInsertText(), minimizedReplaceStart, 1794 minimizedReplaceEnd - minimizedReplaceStart, 1795 newPrecedingWhiteSpaceLength, newFollowingWhiteSpaceLength); 1796 } 1797 1798 /** 1799 * Return offset to insert the given text. 1800 */ 1801 [[nodiscard]] uint32_t OffsetToInsertText() const { 1802 return mReplaceStartOffset + mReplaceLengthBefore; 1803 } 1804 1805 /** 1806 * Return inserting text length not containing the surrounding white-spaces. 1807 */ 1808 [[nodiscard]] uint32_t InsertingTextLength() const { 1809 return mNormalizedString.Length() - mNewLengthBefore - mNewLengthAfter; 1810 } 1811 1812 /** 1813 * Return end offset of inserted string after replacing the text with 1814 * mNormalizedString. 1815 */ 1816 [[nodiscard]] uint32_t EndOffsetOfInsertedText() const { 1817 return OffsetToInsertText() + InsertingTextLength(); 1818 } 1819 1820 /** 1821 * Return the length to replace with mNormalizedString. The result means that 1822 * it's the length of surrounding white-spaces at the insertion point. 1823 */ 1824 [[nodiscard]] uint32_t ReplaceLength() const { 1825 return mReplaceEndOffset - mReplaceStartOffset; 1826 } 1827 1828 [[nodiscard]] uint32_t DeletingPrecedingInvisibleWhiteSpaces() const { 1829 return mReplaceLengthBefore - mNewLengthBefore; 1830 } 1831 [[nodiscard]] uint32_t DeletingFollowingInvisibleWhiteSpaces() const { 1832 return mReplaceLengthAfter - mNewLengthAfter; 1833 } 1834 1835 [[nodiscard]] nsDependentSubstring PrecedingWhiteSpaces() const { 1836 return Substring(mNormalizedString, 0u, mNewLengthBefore); 1837 } 1838 [[nodiscard]] nsDependentSubstring FollowingWhiteSpaces() const { 1839 return Substring(mNormalizedString, 1840 mNormalizedString.Length() - mNewLengthAfter); 1841 } 1842 1843 // Normalizes string which should be inserted. 1844 nsAutoString mNormalizedString; 1845 // Start offset in the `Text` to replace. 1846 const uint32_t mReplaceStartOffset; 1847 // End offset in the `Text` to replace. 1848 const uint32_t mReplaceEndOffset; 1849 // If it needs to replace preceding and/or following white-spaces, these 1850 // members store the length of white-spaces which should be replaced 1851 // before/after the insertion point. 1852 const uint32_t mReplaceLengthBefore = 0u; 1853 const uint32_t mReplaceLengthAfter = 0u; 1854 // If it needs to replace preceding and/or following white-spaces, these 1855 // members store the new length of white-spaces before/after the insertion 1856 // string. 1857 const uint32_t mNewLengthBefore = 0u; 1858 const uint32_t mNewLengthAfter = 0u; 1859 }; 1860 1861 /****************************************************************************** 1862 * ReplaceWhiteSpacesData stores normalized string to replace white-spaces in 1863 * a `Text`. If ReplaceLength() returns 0, this user needs to do nothing. 1864 ******************************************************************************/ 1865 1866 struct MOZ_STACK_CLASS HTMLEditor::ReplaceWhiteSpacesData final { 1867 ReplaceWhiteSpacesData() = default; 1868 1869 /** 1870 * @param aWhiteSpaces The new white-spaces which we will replace the 1871 * range with. 1872 * @param aStartOffset Replace start offset in the text node. 1873 * @param aReplaceLength Replace length in the text node. 1874 * @param aOffsetAfterReplacing 1875 * [optional] If the caller may want to put caret 1876 * middle of the white-spaces, the offset may be 1877 * changed by deleting some invisible white-spaces. 1878 * Therefore, this may be set for the purpose. 1879 */ 1880 ReplaceWhiteSpacesData(const nsAString& aWhiteSpaces, uint32_t aStartOffset, 1881 uint32_t aReplaceLength, 1882 uint32_t aOffsetAfterReplacing = UINT32_MAX) 1883 : mNormalizedString(aWhiteSpaces), 1884 mReplaceStartOffset(aStartOffset), 1885 mReplaceEndOffset(aStartOffset + aReplaceLength), 1886 mNewOffsetAfterReplace(aOffsetAfterReplacing) { 1887 MOZ_ASSERT(ReplaceLength() >= mNormalizedString.Length()); 1888 MOZ_ASSERT_IF(mNewOffsetAfterReplace != UINT32_MAX, 1889 mNewOffsetAfterReplace <= 1890 mReplaceStartOffset + mNormalizedString.Length()); 1891 } 1892 1893 /** 1894 * @param aWhiteSpaces The new white-spaces which we will replace the 1895 * range with. 1896 * @param aStartOffset Replace start offset in the text node. 1897 * @param aReplaceLength Replace length in the text node. 1898 * @param aOffsetAfterReplacing 1899 * [optional] If the caller may want to put caret 1900 * middle of the white-spaces, the offset may be 1901 * changed by deleting some invisible white-spaces. 1902 * Therefore, this may be set for the purpose. 1903 */ 1904 ReplaceWhiteSpacesData(nsAutoString&& aWhiteSpaces, uint32_t aStartOffset, 1905 uint32_t aReplaceLength, 1906 uint32_t aOffsetAfterReplacing = UINT32_MAX) 1907 : mNormalizedString(std::forward<nsAutoString>(aWhiteSpaces)), 1908 mReplaceStartOffset(aStartOffset), 1909 mReplaceEndOffset(aStartOffset + aReplaceLength), 1910 mNewOffsetAfterReplace(aOffsetAfterReplacing) { 1911 MOZ_ASSERT(ReplaceLength() >= mNormalizedString.Length()); 1912 MOZ_ASSERT_IF(mNewOffsetAfterReplace != UINT32_MAX, 1913 mNewOffsetAfterReplace <= 1914 mReplaceStartOffset + mNormalizedString.Length()); 1915 } 1916 1917 ReplaceWhiteSpacesData GetMinimizedData(const Text& aText) const { 1918 if (!ReplaceLength()) { 1919 return *this; 1920 } 1921 const dom::CharacterDataBuffer& characterDataBuffer = aText.DataBuffer(); 1922 const auto minimizedReplaceStart = [&]() -> uint32_t { 1923 if (mNormalizedString.IsEmpty()) { 1924 return mReplaceStartOffset; 1925 } 1926 const uint32_t firstDiffCharOffset = 1927 characterDataBuffer.FindFirstDifferentCharOffset(mNormalizedString, 1928 mReplaceStartOffset); 1929 if (firstDiffCharOffset == dom::CharacterDataBuffer::kNotFound) { 1930 // We don't need to insert new white-spaces, 1931 return mReplaceStartOffset + mNormalizedString.Length(); 1932 } 1933 return firstDiffCharOffset; 1934 }(); 1935 const auto minimizedReplaceEnd = [&]() -> uint32_t { 1936 if (mNormalizedString.IsEmpty()) { 1937 return mReplaceEndOffset; 1938 } 1939 if (minimizedReplaceStart == 1940 mReplaceStartOffset + mNormalizedString.Length()) { 1941 // Note that here may be invisible white-spaces before 1942 // mReplaceEndOffset. Then, this value may be larger than 1943 // minimizedReplaceStart. 1944 MOZ_ASSERT(mReplaceEndOffset >= minimizedReplaceStart); 1945 return mReplaceEndOffset; 1946 } 1947 if (ReplaceLength() != mNormalizedString.Length()) { 1948 // If we're deleting some invisible white-spaces, don't shrink the end 1949 // of the replacing range because it may shrink mNormalizedString too 1950 // much. 1951 return mReplaceEndOffset; 1952 } 1953 const auto lastDiffCharOffset = 1954 characterDataBuffer.RFindFirstDifferentCharOffset(mNormalizedString, 1955 mReplaceEndOffset); 1956 MOZ_ASSERT(lastDiffCharOffset != dom::CharacterDataBuffer::kNotFound); 1957 return lastDiffCharOffset == dom::CharacterDataBuffer::kNotFound 1958 ? mReplaceEndOffset 1959 : lastDiffCharOffset + 1u; 1960 }(); 1961 if (minimizedReplaceStart == mReplaceStartOffset && 1962 minimizedReplaceEnd == mReplaceEndOffset) { 1963 return *this; 1964 } 1965 const uint32_t precedingUnnecessaryLength = 1966 minimizedReplaceStart - mReplaceStartOffset; 1967 const uint32_t followingUnnecessaryLength = 1968 mReplaceEndOffset - minimizedReplaceEnd; 1969 return ReplaceWhiteSpacesData( 1970 Substring(mNormalizedString, precedingUnnecessaryLength, 1971 mNormalizedString.Length() - (precedingUnnecessaryLength + 1972 followingUnnecessaryLength)), 1973 minimizedReplaceStart, minimizedReplaceEnd - minimizedReplaceStart, 1974 mNewOffsetAfterReplace); 1975 } 1976 1977 /** 1978 * Return the normalized string before mNewOffsetAfterReplace. So, 1979 * mNewOffsetAfterReplace must not be UINT32_MAX and in the replaced range 1980 * when this is called. 1981 * 1982 * @param aReplaceEndOffset Specify the offset in the Text node of 1983 * mNewOffsetAfterReplace before replacing with the 1984 * data. 1985 * @return The substring before mNewOffsetAfterReplace which is typically set 1986 * for new caret position in the Text node or collapsed deleting range 1987 * surrounded by the white-spaces. 1988 */ 1989 [[nodiscard]] ReplaceWhiteSpacesData PreviousDataOfNewOffset( 1990 uint32_t aReplaceEndOffset) const { 1991 MOZ_ASSERT(mNewOffsetAfterReplace != UINT32_MAX); 1992 MOZ_ASSERT(mReplaceStartOffset <= mNewOffsetAfterReplace); 1993 MOZ_ASSERT(mReplaceEndOffset >= mNewOffsetAfterReplace); 1994 MOZ_ASSERT(mReplaceStartOffset <= aReplaceEndOffset); 1995 MOZ_ASSERT(mReplaceEndOffset >= aReplaceEndOffset); 1996 if (!ReplaceLength() || aReplaceEndOffset == mReplaceStartOffset) { 1997 return ReplaceWhiteSpacesData(); 1998 } 1999 return ReplaceWhiteSpacesData( 2000 Substring(mNormalizedString, 0u, 2001 mNewOffsetAfterReplace - mReplaceStartOffset), 2002 mReplaceStartOffset, aReplaceEndOffset - mReplaceStartOffset); 2003 } 2004 2005 /** 2006 * Return the normalized string after mNewOffsetAfterReplace. So, 2007 * mNewOffsetAfterReplace must not be UINT32_MAX and in the replaced range 2008 * when this is called. 2009 * 2010 * @param aReplaceStartOffset Specify the replace start offset with the 2011 * normalized white-spaces. 2012 * @return The substring after mNewOffsetAfterReplace which is typically set 2013 * for new caret position in the Text node or collapsed deleting range 2014 * surrounded by the white-spaces. 2015 */ 2016 [[nodiscard]] ReplaceWhiteSpacesData NextDataOfNewOffset( 2017 uint32_t aReplaceStartOffset) const { 2018 MOZ_ASSERT(mNewOffsetAfterReplace != UINT32_MAX); 2019 MOZ_ASSERT(mReplaceStartOffset <= mNewOffsetAfterReplace); 2020 MOZ_ASSERT(mReplaceEndOffset >= mNewOffsetAfterReplace); 2021 MOZ_ASSERT(mReplaceStartOffset <= aReplaceStartOffset); 2022 MOZ_ASSERT(mReplaceEndOffset >= aReplaceStartOffset); 2023 if (!ReplaceLength() || aReplaceStartOffset == mReplaceEndOffset) { 2024 return ReplaceWhiteSpacesData(); 2025 } 2026 return ReplaceWhiteSpacesData( 2027 Substring(mNormalizedString, 2028 mNewOffsetAfterReplace - mReplaceStartOffset), 2029 aReplaceStartOffset, mReplaceEndOffset - aReplaceStartOffset); 2030 } 2031 2032 [[nodiscard]] uint32_t ReplaceLength() const { 2033 return mReplaceEndOffset - mReplaceStartOffset; 2034 } 2035 [[nodiscard]] uint32_t DeletingInvisibleWhiteSpaces() const { 2036 return ReplaceLength() - mNormalizedString.Length(); 2037 } 2038 2039 [[nodiscard]] ReplaceWhiteSpacesData operator+( 2040 const ReplaceWhiteSpacesData& aOther) const { 2041 if (!ReplaceLength()) { 2042 return aOther; 2043 } 2044 if (!aOther.ReplaceLength()) { 2045 return *this; 2046 } 2047 MOZ_ASSERT(mReplaceEndOffset == aOther.mReplaceStartOffset); 2048 MOZ_ASSERT_IF( 2049 aOther.mNewOffsetAfterReplace != UINT32_MAX, 2050 aOther.mNewOffsetAfterReplace >= DeletingInvisibleWhiteSpaces()); 2051 return ReplaceWhiteSpacesData( 2052 nsAutoString(mNormalizedString + aOther.mNormalizedString), 2053 mReplaceStartOffset, aOther.mReplaceEndOffset, 2054 aOther.mNewOffsetAfterReplace != UINT32_MAX 2055 ? aOther.mNewOffsetAfterReplace - DeletingInvisibleWhiteSpaces() 2056 : mNewOffsetAfterReplace); 2057 } 2058 2059 nsAutoString mNormalizedString; 2060 const uint32_t mReplaceStartOffset = 0u; 2061 const uint32_t mReplaceEndOffset = 0u; 2062 // If the caller specifies a point in a white-space sequence, some invisible 2063 // white-spaces will be deleted with replacing them with normalized string. 2064 // Then, they may want to keep the position for putting caret or something. 2065 // So, this may store a specific offset in the text node after replacing. 2066 const uint32_t mNewOffsetAfterReplace = UINT32_MAX; 2067 }; 2068 2069 /****************************************************************************** 2070 * A runnable to run HTMLEditor::OnModifiedDocument when it's safe. 2071 ******************************************************************************/ 2072 2073 class HTMLEditor::DocumentModifiedEvent final : public Runnable { 2074 public: 2075 explicit DocumentModifiedEvent(HTMLEditor& aHTMLEditor) 2076 : Runnable("DocumentModifiedEvent"), mHTMLEditor(aHTMLEditor) {} 2077 2078 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() { 2079 (void)MOZ_KnownLive(mHTMLEditor)->OnModifyDocument(*this); 2080 return NS_OK; 2081 } 2082 2083 const nsTArray<EditorDOMPointInText>& NewInvisibleWhiteSpacesRef() const { 2084 return mNewInvisibleWhiteSpaces; 2085 } 2086 2087 private: 2088 ~DocumentModifiedEvent() = default; 2089 2090 const OwningNonNull<HTMLEditor> mHTMLEditor; 2091 nsTArray<EditorDOMPointInText> mNewInvisibleWhiteSpaces; 2092 }; 2093 2094 } // namespace mozilla 2095 2096 #endif // #ifndef HTMLEditorNestedClasses_h