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