TextServicesDocument.h (15528B)
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 mozilla_TextServicesDocument_h 7 #define mozilla_TextServicesDocument_h 8 9 #include "mozilla/Maybe.h" 10 #include "mozilla/UniquePtr.h" 11 #include "nsCOMPtr.h" 12 #include "nsCycleCollectionParticipant.h" 13 #include "nsIEditActionListener.h" 14 #include "nsISupportsImpl.h" 15 #include "nsStringFwd.h" 16 #include "nsTArray.h" 17 #include "nscore.h" 18 19 class nsIContent; 20 class nsIEditor; 21 class nsINode; 22 class nsISelectionController; 23 class nsRange; 24 25 namespace mozilla { 26 27 class EditorBase; 28 class FilteredContentIterator; 29 class OffsetEntry; 30 31 namespace dom { 32 class AbstractRange; 33 class Document; 34 class Element; 35 class StaticRange; 36 }; // namespace dom 37 38 /** 39 * The TextServicesDocument presents the document in as a bunch of flattened 40 * text blocks. Each text block can be retrieved as an nsString. 41 */ 42 class TextServicesDocument final : public nsIEditActionListener { 43 private: 44 enum class IteratorStatus : uint8_t { 45 // No iterator (I), or iterator doesn't point to anything valid. 46 eDone = 0, 47 // I points to first text node (TN) in current block (CB). 48 eValid, 49 // No TN in CB, I points to first TN in prev block. 50 ePrev, 51 // No TN in CB, I points to first TN in next block. 52 eNext, 53 }; 54 55 class OffsetEntryArray final : public nsTArray<UniquePtr<OffsetEntry>> { 56 public: 57 /** 58 * Init() initializes this array with aFilteredIter. 59 * 60 * @param[in] aIterRange Can be nullptr. 61 * @param[out] aAllTextInBlock 62 * Returns all text in the block. 63 */ 64 Result<IteratorStatus, nsresult> Init( 65 FilteredContentIterator& aFilteredIter, IteratorStatus aIteratorStatus, 66 nsRange* aIterRange, nsAString* aAllTextInBlock = nullptr); 67 68 /** 69 * Returns index of first `OffsetEntry` which manages aTextNode. 70 */ 71 Maybe<size_t> FirstIndexOf(const dom::Text& aTextNode) const; 72 73 /** 74 * FindWordRange() returns a word range starting from aStartPointToScan 75 * in aAllTextInBlock. 76 */ 77 Result<EditorDOMRangeInTexts, nsresult> FindWordRange( 78 nsAString& aAllTextInBlock, const EditorRawDOMPoint& aStartPointToScan); 79 80 /** 81 * SplitElementAt() splits an `OffsetEntry` at aIndex if aOffsetInTextNode 82 * is middle of the range in the text node. 83 * 84 * @param aIndex Index of the entry which you want to split. 85 * @param aOffsetInTextNode 86 * Offset in the text node. I.e., the offset should be 87 * greater than 0 and less than `mLength`. 88 */ 89 nsresult SplitElementAt(size_t aIndex, uint32_t aOffsetInTextNode); 90 91 /** 92 * Remove all `OffsetEntry` elements whose `mIsValid` is set to false. 93 */ 94 void RemoveInvalidElements(); 95 96 /** 97 * Called when non-collapsed selection will be deleted. 98 */ 99 nsresult WillDeleteSelection(); 100 101 /** 102 * Called when non-collapsed selection is deleteded. 103 */ 104 OffsetEntry* DidDeleteSelection(); 105 106 /** 107 * Called when aInsertedText is inserted. 108 */ 109 MOZ_CAN_RUN_SCRIPT nsresult DidInsertText(dom::Selection* aSelection, 110 const nsAString& aInsertedString); 111 112 /** 113 * Called when selection range will be applied to the DOM Selection. 114 */ 115 Result<EditorRawDOMRangeInTexts, nsresult> WillSetSelection( 116 uint32_t aOffsetInTextInBlock, uint32_t aLength); 117 118 class Selection final { 119 public: 120 size_t StartIndex() const { 121 MOZ_ASSERT(IsIndexesSet()); 122 return *mStartIndex; 123 } 124 size_t EndIndex() const { 125 MOZ_ASSERT(IsIndexesSet()); 126 return *mEndIndex; 127 } 128 129 uint32_t StartOffsetInTextInBlock() const { 130 MOZ_ASSERT(IsSet()); 131 return *mStartOffsetInTextInBlock; 132 } 133 uint32_t EndOffsetInTextInBlock() const { 134 MOZ_ASSERT(IsSet()); 135 return *mEndOffsetInTextInBlock; 136 } 137 uint32_t LengthInTextInBlock() const { 138 MOZ_ASSERT(IsSet()); 139 return *mEndOffsetInTextInBlock - *mStartOffsetInTextInBlock; 140 } 141 142 bool IsCollapsed() { 143 return !IsSet() || (IsInSameElement() && StartOffsetInTextInBlock() == 144 EndOffsetInTextInBlock()); 145 } 146 147 bool IsIndexesSet() const { 148 return mStartIndex.isSome() && mEndIndex.isSome(); 149 } 150 bool IsSet() const { 151 return IsIndexesSet() && mStartOffsetInTextInBlock.isSome() && 152 mEndOffsetInTextInBlock.isSome(); 153 } 154 bool IsInSameElement() const { 155 return IsIndexesSet() && StartIndex() == EndIndex(); 156 } 157 158 void Reset() { 159 mStartIndex.reset(); 160 mEndIndex.reset(); 161 mStartOffsetInTextInBlock.reset(); 162 mEndOffsetInTextInBlock.reset(); 163 } 164 void SetIndex(size_t aIndex) { mEndIndex = mStartIndex = Some(aIndex); } 165 void Set(size_t aIndex, uint32_t aOffsetInTextInBlock) { 166 mEndIndex = mStartIndex = Some(aIndex); 167 mStartOffsetInTextInBlock = mEndOffsetInTextInBlock = 168 Some(aOffsetInTextInBlock); 169 } 170 void SetIndexes(size_t aStartIndex, size_t aEndIndex) { 171 MOZ_DIAGNOSTIC_ASSERT(aStartIndex <= aEndIndex); 172 mStartIndex = Some(aStartIndex); 173 mEndIndex = Some(aEndIndex); 174 } 175 void Set(size_t aStartIndex, size_t aEndIndex, 176 uint32_t aStartOffsetInTextInBlock, 177 uint32_t aEndOffsetInTextInBlock) { 178 MOZ_DIAGNOSTIC_ASSERT(aStartIndex <= aEndIndex); 179 mStartIndex = Some(aStartIndex); 180 mEndIndex = Some(aEndIndex); 181 mStartOffsetInTextInBlock = Some(aStartOffsetInTextInBlock); 182 mEndOffsetInTextInBlock = Some(aEndOffsetInTextInBlock); 183 } 184 185 void CollapseToStart() { 186 MOZ_ASSERT(mStartIndex.isSome()); 187 MOZ_ASSERT(mStartOffsetInTextInBlock.isSome()); 188 mEndIndex = mStartIndex; 189 mEndOffsetInTextInBlock = mStartOffsetInTextInBlock; 190 } 191 192 private: 193 Maybe<size_t> mStartIndex; 194 Maybe<size_t> mEndIndex; 195 // Selected start and end offset in all text in a block element. 196 Maybe<uint32_t> mStartOffsetInTextInBlock; 197 Maybe<uint32_t> mEndOffsetInTextInBlock; 198 }; 199 Selection mSelection; 200 }; 201 202 RefPtr<dom::Document> mDocument; 203 nsCOMPtr<nsISelectionController> mSelCon; 204 RefPtr<EditorBase> mEditorBase; 205 RefPtr<FilteredContentIterator> mFilteredIter; 206 nsCOMPtr<nsIContent> mPrevTextBlock; 207 nsCOMPtr<nsIContent> mNextTextBlock; 208 OffsetEntryArray mOffsetTable; 209 RefPtr<nsRange> mExtent; 210 211 uint32_t mTxtSvcFilterType; 212 IteratorStatus mIteratorStatus; 213 214 protected: 215 virtual ~TextServicesDocument() = default; 216 217 public: 218 TextServicesDocument(); 219 220 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 221 NS_DECL_CYCLE_COLLECTION_CLASS(TextServicesDocument) 222 223 /** 224 * Initializes the text services document to use a particular editor. The 225 * text services document will use the DOM document and presentation shell 226 * used by the editor. 227 * 228 * @param aEditor The editor to use. 229 */ 230 nsresult InitWithEditor(nsIEditor* aEditor); 231 232 /** 233 * Sets the range/extent over which the text services document will iterate. 234 * Note that InitWithEditor() should have been called prior to calling this 235 * method. If this method is never called, the text services defaults to 236 * iterating over the entire document. 237 * 238 * @param aAbstractRange The range to use. aAbstractRange must point to a 239 * valid range object. 240 */ 241 nsresult SetExtent(const dom::AbstractRange* aAbstractRange); 242 243 /** 244 * Expands the end points of the range so that it spans complete words. This 245 * call does not change any internal state of the text services document. 246 * 247 * @param aStaticRange [in/out] The range to be expanded/adjusted. 248 */ 249 nsresult ExpandRangeToWordBoundaries(dom::StaticRange* aStaticRange); 250 251 /** 252 * Sets the filter type to be used while iterating over content. 253 * This will clear the current filter type if it's not either 254 * FILTERTYPE_NORMAL or FILTERTYPE_MAIL. 255 * 256 * @param aFilterType The filter type to be used while iterating over 257 * content. 258 */ 259 nsresult SetFilterType(uint32_t aFilterType); 260 261 /** 262 * Returns the text in the current text block. 263 * 264 * @param aStr [OUT] This will contain the text. 265 */ 266 nsresult GetCurrentTextBlock(nsAString& aStr); 267 268 /** 269 * Tells the document to point to the first text block in the document. This 270 * method does not adjust the current cursor position or selection. 271 */ 272 nsresult FirstBlock(); 273 274 enum class BlockSelectionStatus { 275 // There is no text block (TB) in or before the selection (S). 276 eBlockNotFound = 0, 277 // No TB in S, but found one before/after S. 278 eBlockOutside, 279 // S extends beyond the start and end of TB. 280 eBlockInside, 281 // TB contains entire S. 282 eBlockContains, 283 // S begins or ends in TB but extends outside of TB. 284 eBlockPartial, 285 }; 286 287 /** 288 * Tells the document to point to the last text block that contains the 289 * current selection or caret. 290 * 291 * @param aSelectionStatus [OUT] This will contain the text block 292 * selection status. 293 * @param aSelectionOffset [OUT] This will contain the offset into the 294 * string returned by GetCurrentTextBlock() where 295 * the selection begins. 296 * @param aLength [OUT] This will contain the number of 297 * characters that are selected in the string. 298 */ 299 MOZ_CAN_RUN_SCRIPT 300 nsresult LastSelectedBlock(BlockSelectionStatus* aSelStatus, 301 uint32_t* aSelOffset, uint32_t* aSelLength); 302 303 /** 304 * Tells the document to point to the text block before the current one. 305 * This method will return NS_OK, even if there is no previous block. 306 * Callers should call IsDone() to check if we have gone beyond the first 307 * text block in the document. 308 */ 309 nsresult PrevBlock(); 310 311 /** 312 * Tells the document to point to the text block after the current one. 313 * This method will return NS_OK, even if there is no next block. Callers 314 * should call IsDone() to check if we have gone beyond the last text block 315 * in the document. 316 */ 317 nsresult NextBlock(); 318 319 /** 320 * IsDone() will always set aIsDone == false unless the document contains 321 * no text, PrevBlock() was called while the document was already pointing 322 * to the first text block in the document, or NextBlock() was called while 323 * the document was already pointing to the last text block in the document. 324 * 325 * @param aIsDone [OUT] This will contain the result. 326 */ 327 nsresult IsDone(bool* aIsDone); 328 329 /** 330 * SetSelection() allows the caller to set the selection based on an offset 331 * into the string returned by GetCurrentTextBlock(). A length of zero 332 * places the cursor at that offset. A positive non-zero length "n" selects 333 * n characters in the string. 334 * 335 * @param aOffset Offset into string returned by 336 * GetCurrentTextBlock(). 337 * @param aLength Number of characters selected. 338 */ 339 MOZ_CAN_RUN_SCRIPT nsresult SetSelection(uint32_t aOffset, uint32_t aLength); 340 341 /** 342 * Scrolls the document so that the current selection is visible. 343 */ 344 nsresult ScrollSelectionIntoView(); 345 346 /** 347 * Deletes the text selected by SetSelection(). Calling DeleteSelection() 348 * with nothing selected, or with a collapsed selection (cursor) does 349 * nothing and returns NS_OK. 350 */ 351 MOZ_CAN_RUN_SCRIPT 352 nsresult DeleteSelection(); 353 354 /** 355 * Inserts the given text at the current cursor position. If there is a 356 * selection, it will be deleted before the text is inserted. 357 */ 358 MOZ_CAN_RUN_SCRIPT 359 nsresult InsertText(const nsAString& aText); 360 361 /** 362 * nsIEditActionListener method implementations. 363 */ 364 NS_DECL_NSIEDITACTIONLISTENER 365 366 /** 367 * Actual edit action listeners. When you add new method here for listening 368 * to new edit action, you need to make it called by EditorBase. 369 * Additionally, you need to call it from proper method of 370 * nsIEditActionListener too because if this is created not for inline 371 * spell checker of the editor, edit actions will be notified via 372 * nsIEditActionListener (slow path, though). 373 */ 374 void DidDeleteContent(const nsIContent& aChildContent); 375 void DidJoinContents(const EditorRawDOMPoint& aJoinedPoint, 376 const nsIContent& aRemovedContent); 377 378 private: 379 // TODO: We should get rid of this method since `aAbstractRange` has 380 // enough simple API to get them. 381 static nsresult GetRangeEndPoints(const dom::AbstractRange* aAbstractRange, 382 nsINode** aStartContainer, 383 uint32_t* aStartOffset, 384 nsINode** aEndContainer, 385 uint32_t* aEndOffset); 386 387 nsresult CreateFilteredContentIterator( 388 const dom::AbstractRange* aAbstractRange, 389 FilteredContentIterator** aFilteredIter); 390 391 dom::Element* GetDocumentContentRootNode() const; 392 already_AddRefed<nsRange> CreateDocumentContentRange(); 393 already_AddRefed<nsRange> CreateDocumentContentRootToNodeOffsetRange( 394 nsINode* aParent, uint32_t aOffset, bool aToStart); 395 nsresult CreateDocumentContentIterator( 396 FilteredContentIterator** aFilteredIter); 397 398 nsresult AdjustContentIterator(); 399 400 static nsresult FirstTextNode(FilteredContentIterator* aFilteredIter, 401 IteratorStatus* aIteratorStatus); 402 static nsresult LastTextNode(FilteredContentIterator* aFilteredIter, 403 IteratorStatus* aIteratorStatus); 404 405 static nsresult FirstTextNodeInCurrentBlock( 406 FilteredContentIterator* aFilteredIter); 407 static nsresult FirstTextNodeInPrevBlock( 408 FilteredContentIterator* aFilteredIter); 409 static nsresult FirstTextNodeInNextBlock( 410 FilteredContentIterator* aFilteredIter); 411 412 nsresult GetFirstTextNodeInPrevBlock(nsIContent** aContent); 413 nsresult GetFirstTextNodeInNextBlock(nsIContent** aContent); 414 415 static bool DidSkip(FilteredContentIterator* aFilteredIter); 416 static void ClearDidSkip(FilteredContentIterator* aFilteredIter); 417 418 static bool HasSameBlockNodeParent(dom::Text& aTextNode1, 419 dom::Text& aTextNode2); 420 421 MOZ_CAN_RUN_SCRIPT nsresult SetSelectionInternal(uint32_t aOffset, 422 uint32_t aLength, 423 bool aDoUpdate); 424 MOZ_CAN_RUN_SCRIPT nsresult GetSelection(BlockSelectionStatus* aSelStatus, 425 uint32_t* aSelOffset, 426 uint32_t* aSelLength); 427 MOZ_CAN_RUN_SCRIPT nsresult 428 GetCollapsedSelection(BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset, 429 uint32_t* aSelLength); 430 nsresult GetUncollapsedSelection(BlockSelectionStatus* aSelStatus, 431 uint32_t* aSelOffset, uint32_t* aSelLength); 432 }; 433 434 } // namespace mozilla 435 436 #endif // #ifndef mozilla_TextServicesDocument_h