TextServicesDocument.cpp (90619B)
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 #include "TextServicesDocument.h" 7 8 #include "EditorBase.h" // for EditorBase 9 #include "EditorUtils.h" // for AutoTransactionBatchExternal 10 #include "FilteredContentIterator.h" // for FilteredContentIterator 11 #include "HTMLEditHelpers.h" // for BlockInlineCheck 12 #include "HTMLEditUtils.h" // for HTMLEditUtils 13 14 #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc 15 #include "mozilla/IntegerRange.h" // for IntegerRange 16 #include "mozilla/mozalloc.h" // for operator new, etc 17 #include "mozilla/OwningNonNull.h" 18 #include "mozilla/UniquePtr.h" // for UniquePtr 19 #include "mozilla/dom/AbstractRange.h" // for AbstractRange 20 #include "mozilla/dom/Element.h" 21 #include "mozilla/dom/Selection.h" 22 #include "mozilla/dom/StaticRange.h" // for StaticRange 23 #include "mozilla/dom/Text.h" 24 #include "mozilla/intl/WordBreaker.h" // for WordRange, WordBreaker 25 26 #include "nsAString.h" // for nsAString::Length, etc 27 #include "nsContentUtils.h" // for nsContentUtils 28 #include "nsComposeTxtSrvFilter.h" 29 #include "nsDebug.h" // for NS_ENSURE_TRUE, etc 30 #include "nsDependentSubstring.h" // for Substring 31 #include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc 32 #include "nsGenericHTMLElement.h" // for nsGenericHTMLElement 33 #include "nsIContent.h" // for nsIContent, etc 34 #include "nsID.h" // for NS_GET_IID 35 #include "nsIEditor.h" // for nsIEditor, etc 36 #include "nsIEditorSpellCheck.h" // for nsIEditorSpellCheck, etc 37 #include "nsINode.h" // for nsINode 38 #include "nsISelectionController.h" // for nsISelectionController, etc 39 #include "nsISupports.h" // for nsISupports 40 #include "nsISupportsUtils.h" // for NS_IF_ADDREF, NS_ADDREF, etc 41 #include "nsRange.h" // for nsRange 42 #include "nsString.h" // for nsString, nsAutoString 43 #include "nscore.h" // for nsresult, NS_IMETHODIMP, etc 44 45 namespace mozilla { 46 47 using namespace dom; 48 49 /** 50 * OffsetEntry manages a range in a text node. It stores 2 offset values, 51 * one is offset in the text node, the other is offset in all text in 52 * the ancestor block of the text node. And the length is managing length 53 * in the text node, starting from the offset in text node. 54 * In other words, a text node may be managed by multiple instances of this 55 * class. 56 */ 57 class OffsetEntry final { 58 public: 59 OffsetEntry() = delete; 60 61 /** 62 * @param aTextNode The text node which will be manged by the instance. 63 * @param aOffsetInTextInBlock 64 * Start offset in the text node which will be managed by 65 * the instance. 66 * @param aLength Length in the text node which will be managed by the 67 * instance. 68 */ 69 OffsetEntry(Text& aTextNode, uint32_t aOffsetInTextInBlock, uint32_t aLength) 70 : mTextNode(aTextNode), 71 mOffsetInTextNode(0), 72 mOffsetInTextInBlock(aOffsetInTextInBlock), 73 mLength(aLength), 74 mIsInsertedText(false), 75 mIsValid(true) {} 76 77 /** 78 * EndOffsetInTextNode() returns end offset in the text node, which is 79 * managed by the instance. 80 */ 81 uint32_t EndOffsetInTextNode() const { return mOffsetInTextNode + mLength; } 82 83 /** 84 * OffsetInTextNodeIsInRangeOrEndOffset() checks whether the offset in 85 * the text node is managed by the instance or not. 86 */ 87 bool OffsetInTextNodeIsInRangeOrEndOffset(uint32_t aOffsetInTextNode) const { 88 return aOffsetInTextNode >= mOffsetInTextNode && 89 aOffsetInTextNode <= EndOffsetInTextNode(); 90 } 91 92 /** 93 * EndOffsetInTextInBlock() returns end offset in the all text in ancestor 94 * block of the text node, which is managed by the instance. 95 */ 96 uint32_t EndOffsetInTextInBlock() const { 97 return mOffsetInTextInBlock + mLength; 98 } 99 100 /** 101 * OffsetInTextNodeIsInRangeOrEndOffset() checks whether the offset in 102 * the all text in ancestor block of the text node is managed by the instance 103 * or not. 104 */ 105 bool OffsetInTextInBlockIsInRangeOrEndOffset( 106 uint32_t aOffsetInTextInBlock) const { 107 return aOffsetInTextInBlock >= mOffsetInTextInBlock && 108 aOffsetInTextInBlock <= EndOffsetInTextInBlock(); 109 } 110 111 OwningNonNull<Text> mTextNode; 112 uint32_t mOffsetInTextNode; 113 // Offset in all text in the closest ancestor block of mTextNode. 114 uint32_t mOffsetInTextInBlock; 115 uint32_t mLength; 116 bool mIsInsertedText; 117 bool mIsValid; 118 }; 119 120 template <typename ElementType> 121 struct MOZ_STACK_CLASS ArrayLengthMutationGuard final { 122 ArrayLengthMutationGuard() = delete; 123 explicit ArrayLengthMutationGuard(const nsTArray<ElementType>& aArray) 124 : mArray(aArray), mOldLength(aArray.Length()) {} 125 ~ArrayLengthMutationGuard() { 126 if (mArray.Length() != mOldLength) { 127 MOZ_CRASH("The array length was changed unexpectedly"); 128 } 129 } 130 131 private: 132 const nsTArray<ElementType>& mArray; 133 size_t mOldLength; 134 }; 135 136 #define LockOffsetEntryArrayLengthInDebugBuild(aName, aArray) \ 137 DebugOnly<ArrayLengthMutationGuard<UniquePtr<OffsetEntry>>> const aName = \ 138 ArrayLengthMutationGuard<UniquePtr<OffsetEntry>>(aArray); 139 140 TextServicesDocument::TextServicesDocument() 141 : mTxtSvcFilterType(0), mIteratorStatus(IteratorStatus::eDone) {} 142 143 NS_IMPL_CYCLE_COLLECTING_ADDREF(TextServicesDocument) 144 NS_IMPL_CYCLE_COLLECTING_RELEASE(TextServicesDocument) 145 146 NS_INTERFACE_MAP_BEGIN(TextServicesDocument) 147 NS_INTERFACE_MAP_ENTRY(nsIEditActionListener) 148 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditActionListener) 149 NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextServicesDocument) 150 NS_INTERFACE_MAP_END 151 152 NS_IMPL_CYCLE_COLLECTION(TextServicesDocument, mDocument, mSelCon, mEditorBase, 153 mFilteredIter, mPrevTextBlock, mNextTextBlock, mExtent) 154 155 nsresult TextServicesDocument::InitWithEditor(nsIEditor* aEditor) { 156 nsCOMPtr<nsISelectionController> selCon; 157 158 NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER); 159 160 // Check to see if we already have an mSelCon. If we do, it 161 // better be the same one the editor uses! 162 163 nsresult rv = aEditor->GetSelectionController(getter_AddRefs(selCon)); 164 165 if (NS_FAILED(rv)) { 166 return rv; 167 } 168 169 if (!selCon || (mSelCon && selCon != mSelCon)) { 170 return NS_ERROR_FAILURE; 171 } 172 173 if (!mSelCon) { 174 mSelCon = selCon; 175 } 176 177 // Check to see if we already have an mDocument. If we do, it 178 // better be the same one the editor uses! 179 180 RefPtr<Document> doc = aEditor->AsEditorBase()->GetDocument(); 181 if (!doc || (mDocument && doc != mDocument)) { 182 return NS_ERROR_FAILURE; 183 } 184 185 if (!mDocument) { 186 mDocument = doc; 187 188 rv = CreateDocumentContentIterator(getter_AddRefs(mFilteredIter)); 189 190 if (NS_FAILED(rv)) { 191 return rv; 192 } 193 194 mIteratorStatus = IteratorStatus::eDone; 195 196 rv = FirstBlock(); 197 198 if (NS_FAILED(rv)) { 199 return rv; 200 } 201 } 202 203 mEditorBase = aEditor->AsEditorBase(); 204 205 rv = aEditor->AddEditActionListener(this); 206 207 return rv; 208 } 209 210 nsresult TextServicesDocument::SetExtent(const AbstractRange* aAbstractRange) { 211 MOZ_ASSERT(aAbstractRange); 212 213 if (NS_WARN_IF(!mDocument)) { 214 return NS_ERROR_FAILURE; 215 } 216 217 // We need to store a copy of aAbstractRange since we don't know where it 218 // came from. 219 mExtent = nsRange::Create(aAbstractRange, IgnoreErrors()); 220 if (NS_WARN_IF(!mExtent)) { 221 return NS_ERROR_FAILURE; 222 } 223 224 // Create a new iterator based on our new extent range. 225 nsresult rv = 226 CreateFilteredContentIterator(mExtent, getter_AddRefs(mFilteredIter)); 227 if (NS_WARN_IF(NS_FAILED(rv))) { 228 return rv; 229 } 230 231 // Now position the iterator at the start of the first block 232 // in the range. 233 mIteratorStatus = IteratorStatus::eDone; 234 235 rv = FirstBlock(); 236 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "FirstBlock() failed"); 237 return rv; 238 } 239 240 nsresult TextServicesDocument::ExpandRangeToWordBoundaries( 241 StaticRange* aStaticRange) { 242 MOZ_ASSERT(aStaticRange); 243 244 // Get the end points of the range. 245 246 nsCOMPtr<nsINode> rngStartNode, rngEndNode; 247 uint32_t rngStartOffset, rngEndOffset; 248 249 nsresult rv = GetRangeEndPoints(aStaticRange, getter_AddRefs(rngStartNode), 250 &rngStartOffset, getter_AddRefs(rngEndNode), 251 &rngEndOffset); 252 if (NS_WARN_IF(NS_FAILED(rv))) { 253 return rv; 254 } 255 256 // Create a content iterator based on the range. 257 RefPtr<FilteredContentIterator> filteredIter; 258 rv = 259 CreateFilteredContentIterator(aStaticRange, getter_AddRefs(filteredIter)); 260 if (NS_WARN_IF(NS_FAILED(rv))) { 261 return rv; 262 } 263 264 // Find the first text node in the range. 265 IteratorStatus iterStatus = IteratorStatus::eDone; 266 rv = FirstTextNode(filteredIter, &iterStatus); 267 if (NS_WARN_IF(NS_FAILED(rv))) { 268 return rv; 269 } 270 271 if (iterStatus == IteratorStatus::eDone) { 272 // No text was found so there's no adjustment necessary! 273 return NS_OK; 274 } 275 276 nsINode* firstText = filteredIter->GetCurrentNode(); 277 if (NS_WARN_IF(!firstText)) { 278 return NS_ERROR_FAILURE; 279 } 280 281 // Find the last text node in the range. 282 283 rv = LastTextNode(filteredIter, &iterStatus); 284 if (NS_WARN_IF(NS_FAILED(rv))) { 285 return rv; 286 } 287 288 if (iterStatus == IteratorStatus::eDone) { 289 // We should never get here because a first text block 290 // was found above. 291 NS_ASSERTION(false, "Found a first without a last!"); 292 return NS_ERROR_FAILURE; 293 } 294 295 nsINode* lastText = filteredIter->GetCurrentNode(); 296 if (NS_WARN_IF(!lastText)) { 297 return NS_ERROR_FAILURE; 298 } 299 300 // Now make sure our end points are in terms of text nodes in the range! 301 302 if (rngStartNode != firstText) { 303 // The range includes the start of the first text node! 304 rngStartNode = firstText; 305 rngStartOffset = 0; 306 } 307 308 if (rngEndNode != lastText) { 309 // The range includes the end of the last text node! 310 rngEndNode = lastText; 311 rngEndOffset = lastText->Length(); 312 } 313 314 // Create a doc iterator so that we can scan beyond 315 // the bounds of the extent range. 316 317 RefPtr<FilteredContentIterator> docFilteredIter; 318 rv = CreateDocumentContentIterator(getter_AddRefs(docFilteredIter)); 319 if (NS_WARN_IF(NS_FAILED(rv))) { 320 return rv; 321 } 322 323 // Grab all the text in the block containing our 324 // first text node. 325 rv = docFilteredIter->PositionAt(firstText); 326 if (NS_WARN_IF(NS_FAILED(rv))) { 327 return rv; 328 } 329 330 iterStatus = IteratorStatus::eValid; 331 332 OffsetEntryArray offsetTable; 333 nsAutoString blockStr; 334 Result<IteratorStatus, nsresult> result = offsetTable.Init( 335 *docFilteredIter, IteratorStatus::eValid, nullptr, &blockStr); 336 if (result.isErr()) { 337 return result.unwrapErr(); 338 } 339 340 Result<EditorDOMRangeInTexts, nsresult> maybeWordRange = 341 offsetTable.FindWordRange( 342 blockStr, EditorRawDOMPoint(rngStartNode, rngStartOffset)); 343 offsetTable.Clear(); 344 if (maybeWordRange.isErr()) { 345 NS_WARNING( 346 "TextServicesDocument::OffsetEntryArray::FindWordRange() failed"); 347 return maybeWordRange.unwrapErr(); 348 } 349 rngStartNode = maybeWordRange.inspect().StartRef().GetContainerAs<Text>(); 350 rngStartOffset = maybeWordRange.inspect().StartRef().Offset(); 351 352 // Grab all the text in the block containing our 353 // last text node. 354 355 rv = docFilteredIter->PositionAt(lastText); 356 if (NS_WARN_IF(NS_FAILED(rv))) { 357 return rv; 358 } 359 360 result = offsetTable.Init(*docFilteredIter, IteratorStatus::eValid, nullptr, 361 &blockStr); 362 if (result.isErr()) { 363 return result.unwrapErr(); 364 } 365 366 maybeWordRange = offsetTable.FindWordRange( 367 blockStr, EditorRawDOMPoint(rngEndNode, rngEndOffset)); 368 offsetTable.Clear(); 369 if (maybeWordRange.isErr()) { 370 NS_WARNING( 371 "TextServicesDocument::OffsetEntryArray::FindWordRange() failed"); 372 return maybeWordRange.unwrapErr(); 373 } 374 375 // To prevent expanding the range too much, we only change 376 // rngEndNode and rngEndOffset if it isn't already at the start of the 377 // word and isn't equivalent to rngStartNode and rngStartOffset. 378 379 if (rngEndNode != 380 maybeWordRange.inspect().StartRef().GetContainerAs<Text>() || 381 rngEndOffset != maybeWordRange.inspect().StartRef().Offset() || 382 (rngEndNode == rngStartNode && rngEndOffset == rngStartOffset)) { 383 rngEndNode = maybeWordRange.inspect().EndRef().GetContainerAs<Text>(); 384 rngEndOffset = maybeWordRange.inspect().EndRef().Offset(); 385 } 386 387 // Now adjust the range so that it uses our new end points. 388 rv = aStaticRange->SetStartAndEnd(rngStartNode, rngStartOffset, rngEndNode, 389 rngEndOffset); 390 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to update the given range"); 391 return rv; 392 } 393 394 nsresult TextServicesDocument::SetFilterType(uint32_t aFilterType) { 395 mTxtSvcFilterType = aFilterType; 396 397 return NS_OK; 398 } 399 400 nsresult TextServicesDocument::GetCurrentTextBlock(nsAString& aStr) { 401 aStr.Truncate(); 402 403 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE); 404 405 Result<IteratorStatus, nsresult> result = 406 mOffsetTable.Init(*mFilteredIter, mIteratorStatus, mExtent, &aStr); 407 if (result.isErr()) { 408 NS_WARNING("OffsetEntryArray::Init() failed"); 409 return result.unwrapErr(); 410 } 411 mIteratorStatus = result.unwrap(); 412 return NS_OK; 413 } 414 415 nsresult TextServicesDocument::FirstBlock() { 416 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE); 417 418 nsresult rv = FirstTextNode(mFilteredIter, &mIteratorStatus); 419 420 if (NS_FAILED(rv)) { 421 return rv; 422 } 423 424 // Keep track of prev and next blocks, just in case 425 // the text service blows away the current block. 426 427 if (mIteratorStatus == IteratorStatus::eValid) { 428 mPrevTextBlock = nullptr; 429 rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); 430 } else { 431 // There's no text block in the document! 432 433 mPrevTextBlock = nullptr; 434 mNextTextBlock = nullptr; 435 } 436 437 // XXX Result of FirstTextNode() or GetFirstTextNodeInNextBlock(). 438 return rv; 439 } 440 441 nsresult TextServicesDocument::LastSelectedBlock( 442 BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset, 443 uint32_t* aSelLength) { 444 NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER); 445 446 mIteratorStatus = IteratorStatus::eDone; 447 448 *aSelStatus = BlockSelectionStatus::eBlockNotFound; 449 *aSelOffset = *aSelLength = UINT32_MAX; 450 451 if (!mSelCon || !mFilteredIter) { 452 return NS_ERROR_FAILURE; 453 } 454 455 RefPtr<Selection> selection = 456 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL); 457 if (NS_WARN_IF(!selection)) { 458 return NS_ERROR_FAILURE; 459 } 460 461 RefPtr<const nsRange> range; 462 nsCOMPtr<nsINode> parent; 463 464 if (selection->IsCollapsed()) { 465 // We have a caret. Check if the caret is in a text node. 466 // If it is, make the text node's block the current block. 467 // If the caret isn't in a text node, search forwards in 468 // the document, till we find a text node. 469 470 range = selection->GetRangeAt(0); 471 if (!range) { 472 return NS_ERROR_FAILURE; 473 } 474 475 parent = range->GetStartContainer(); 476 if (!parent) { 477 return NS_ERROR_FAILURE; 478 } 479 480 nsresult rv; 481 if (parent->IsText()) { 482 // The caret is in a text node. Find the beginning 483 // of the text block containing this text node and 484 // return. 485 486 rv = mFilteredIter->PositionAt(parent->AsText()); 487 if (NS_FAILED(rv)) { 488 return rv; 489 } 490 491 rv = FirstTextNodeInCurrentBlock(mFilteredIter); 492 if (NS_FAILED(rv)) { 493 return rv; 494 } 495 496 Result<IteratorStatus, nsresult> result = 497 mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent); 498 if (result.isErr()) { 499 NS_WARNING("OffsetEntryArray::Init() failed"); 500 mIteratorStatus = IteratorStatus::eValid; // XXX 501 return result.unwrapErr(); 502 } 503 mIteratorStatus = result.unwrap(); 504 505 rv = GetSelection(aSelStatus, aSelOffset, aSelLength); 506 if (NS_FAILED(rv)) { 507 return rv; 508 } 509 510 if (*aSelStatus == BlockSelectionStatus::eBlockContains) { 511 rv = SetSelectionInternal(*aSelOffset, *aSelLength, false); 512 } 513 } else { 514 // The caret isn't in a text node. Create an iterator 515 // based on a range that extends from the current caret 516 // position to the end of the document, then walk forwards 517 // till you find a text node, then find the beginning of it's block. 518 519 range = CreateDocumentContentRootToNodeOffsetRange( 520 parent, range->StartOffset(), false); 521 if (NS_WARN_IF(!range)) { 522 return NS_ERROR_FAILURE; 523 } 524 525 if (range->Collapsed()) { 526 // If we get here, the range is collapsed because there is nothing after 527 // the caret! Just return NS_OK; 528 return NS_OK; 529 } 530 531 RefPtr<FilteredContentIterator> filteredIter; 532 rv = CreateFilteredContentIterator(range, getter_AddRefs(filteredIter)); 533 if (NS_FAILED(rv)) { 534 return rv; 535 } 536 537 filteredIter->First(); 538 539 Text* textNode = nullptr; 540 for (; !filteredIter->IsDone(); filteredIter->Next()) { 541 nsINode* currentNode = filteredIter->GetCurrentNode(); 542 if (currentNode->IsText()) { 543 textNode = currentNode->AsText(); 544 break; 545 } 546 } 547 548 if (!textNode) { 549 return NS_OK; 550 } 551 552 rv = mFilteredIter->PositionAt(textNode); 553 if (NS_FAILED(rv)) { 554 return rv; 555 } 556 557 rv = FirstTextNodeInCurrentBlock(mFilteredIter); 558 if (NS_FAILED(rv)) { 559 return rv; 560 } 561 562 Result<IteratorStatus, nsresult> result = mOffsetTable.Init( 563 *mFilteredIter, IteratorStatus::eValid, mExtent, nullptr); 564 if (result.isErr()) { 565 NS_WARNING("OffsetEntryArray::Init() failed"); 566 mIteratorStatus = IteratorStatus::eValid; // XXX 567 return result.unwrapErr(); 568 } 569 mIteratorStatus = result.inspect(); 570 571 rv = GetSelection(aSelStatus, aSelOffset, aSelLength); 572 if (NS_FAILED(rv)) { 573 return rv; 574 } 575 } 576 577 // Result of SetSelectionInternal() in the |if| block or NS_OK. 578 return rv; 579 } 580 581 // If we get here, we have an uncollapsed selection! 582 // Look backwards through each range in the selection till you 583 // find the first text node. If you find one, find the 584 // beginning of its text block, and make it the current 585 // block. 586 587 const uint32_t rangeCount = selection->RangeCount(); 588 MOZ_ASSERT( 589 rangeCount, 590 "Selection is not collapsed, so, the range count should be 1 or larger"); 591 592 // XXX: We may need to add some code here to make sure 593 // the ranges are sorted in document appearance order! 594 595 for (const uint32_t i : Reversed(IntegerRange(rangeCount))) { 596 MOZ_ASSERT(selection->RangeCount() == rangeCount); 597 range = selection->GetRangeAt(i); 598 if (MOZ_UNLIKELY(!range)) { 599 return NS_OK; // XXX Really? 600 } 601 602 // Create an iterator for the range. 603 604 RefPtr<FilteredContentIterator> filteredIter; 605 nsresult rv = 606 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter)); 607 if (NS_FAILED(rv)) { 608 return rv; 609 } 610 611 filteredIter->Last(); 612 613 // Now walk through the range till we find a text node. 614 615 for (; !filteredIter->IsDone(); filteredIter->Prev()) { 616 if (filteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) { 617 // We found a text node, so position the document's 618 // iterator at the beginning of the block, then get 619 // the selection in terms of the string offset. 620 621 nsresult rv = mFilteredIter->PositionAt(filteredIter->GetCurrentNode()); 622 if (NS_FAILED(rv)) { 623 return rv; 624 } 625 626 rv = FirstTextNodeInCurrentBlock(mFilteredIter); 627 if (NS_FAILED(rv)) { 628 return rv; 629 } 630 631 mIteratorStatus = IteratorStatus::eValid; 632 633 Result<IteratorStatus, nsresult> result = 634 mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent); 635 if (result.isErr()) { 636 NS_WARNING("OffsetEntryArray::Init() failed"); 637 mIteratorStatus = IteratorStatus::eValid; // XXX 638 return result.unwrapErr(); 639 } 640 mIteratorStatus = result.unwrap(); 641 642 return GetSelection(aSelStatus, aSelOffset, aSelLength); 643 } 644 } 645 } 646 647 // If we get here, we didn't find any text node in the selection! 648 // Create a range that extends from the end of the selection, 649 // to the end of the document, then iterate forwards through 650 // it till you find a text node! 651 range = rangeCount > 0 ? selection->GetRangeAt(rangeCount - 1) : nullptr; 652 if (!range) { 653 return NS_ERROR_FAILURE; 654 } 655 656 parent = range->GetEndContainer(); 657 if (!parent) { 658 return NS_ERROR_FAILURE; 659 } 660 661 range = CreateDocumentContentRootToNodeOffsetRange(parent, range->EndOffset(), 662 false); 663 if (NS_WARN_IF(!range)) { 664 return NS_ERROR_FAILURE; 665 } 666 667 if (range->Collapsed()) { 668 // If we get here, the range is collapsed because there is nothing after 669 // the current selection! Just return NS_OK; 670 return NS_OK; 671 } 672 673 RefPtr<FilteredContentIterator> filteredIter; 674 nsresult rv = 675 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter)); 676 if (NS_FAILED(rv)) { 677 return rv; 678 } 679 680 filteredIter->First(); 681 682 for (; !filteredIter->IsDone(); filteredIter->Next()) { 683 if (filteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) { 684 // We found a text node! Adjust the document's iterator to point 685 // to the beginning of its text block, then get the current selection. 686 nsresult rv = mFilteredIter->PositionAt(filteredIter->GetCurrentNode()); 687 if (NS_FAILED(rv)) { 688 return rv; 689 } 690 691 rv = FirstTextNodeInCurrentBlock(mFilteredIter); 692 if (NS_FAILED(rv)) { 693 return rv; 694 } 695 696 Result<IteratorStatus, nsresult> result = 697 mOffsetTable.Init(*mFilteredIter, IteratorStatus::eValid, mExtent); 698 if (result.isErr()) { 699 NS_WARNING("OffsetEntryArray::Init() failed"); 700 mIteratorStatus = IteratorStatus::eValid; // XXX 701 return result.unwrapErr(); 702 } 703 mIteratorStatus = result.unwrap(); 704 705 rv = GetSelection(aSelStatus, aSelOffset, aSelLength); 706 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 707 "TextServicesDocument::GetSelection() failed"); 708 return rv; 709 } 710 } 711 712 // If we get here, we didn't find any block before or inside 713 // the selection! Just return OK. 714 return NS_OK; 715 } 716 717 nsresult TextServicesDocument::PrevBlock() { 718 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE); 719 720 if (mIteratorStatus == IteratorStatus::eDone) { 721 return NS_OK; 722 } 723 724 switch (mIteratorStatus) { 725 case IteratorStatus::eValid: 726 case IteratorStatus::eNext: { 727 nsresult rv = FirstTextNodeInPrevBlock(mFilteredIter); 728 729 if (NS_FAILED(rv)) { 730 mIteratorStatus = IteratorStatus::eDone; 731 return rv; 732 } 733 734 if (mFilteredIter->IsDone()) { 735 mIteratorStatus = IteratorStatus::eDone; 736 return NS_OK; 737 } 738 739 mIteratorStatus = IteratorStatus::eValid; 740 break; 741 } 742 case IteratorStatus::ePrev: 743 744 // The iterator already points to the previous 745 // block, so don't do anything. 746 747 mIteratorStatus = IteratorStatus::eValid; 748 break; 749 750 default: 751 752 mIteratorStatus = IteratorStatus::eDone; 753 break; 754 } 755 756 // Keep track of prev and next blocks, just in case 757 // the text service blows away the current block. 758 nsresult rv = NS_OK; 759 if (mIteratorStatus == IteratorStatus::eValid) { 760 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock)); 761 rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); 762 } else { 763 // We must be done! 764 mPrevTextBlock = nullptr; 765 mNextTextBlock = nullptr; 766 } 767 768 // XXX The result of GetFirstTextNodeInNextBlock() or NS_OK. 769 return rv; 770 } 771 772 nsresult TextServicesDocument::NextBlock() { 773 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE); 774 775 if (mIteratorStatus == IteratorStatus::eDone) { 776 return NS_OK; 777 } 778 779 switch (mIteratorStatus) { 780 case IteratorStatus::eValid: { 781 // Advance the iterator to the next text block. 782 783 nsresult rv = FirstTextNodeInNextBlock(mFilteredIter); 784 785 if (NS_FAILED(rv)) { 786 mIteratorStatus = IteratorStatus::eDone; 787 return rv; 788 } 789 790 if (mFilteredIter->IsDone()) { 791 mIteratorStatus = IteratorStatus::eDone; 792 return NS_OK; 793 } 794 795 mIteratorStatus = IteratorStatus::eValid; 796 break; 797 } 798 case IteratorStatus::eNext: 799 800 // The iterator already points to the next block, 801 // so don't do anything to it! 802 803 mIteratorStatus = IteratorStatus::eValid; 804 break; 805 806 case IteratorStatus::ePrev: 807 808 // If the iterator is pointing to the previous block, 809 // we know that there is no next text block! Just 810 // fall through to the default case! 811 812 default: 813 814 mIteratorStatus = IteratorStatus::eDone; 815 break; 816 } 817 818 // Keep track of prev and next blocks, just in case 819 // the text service blows away the current block. 820 nsresult rv = NS_OK; 821 if (mIteratorStatus == IteratorStatus::eValid) { 822 GetFirstTextNodeInPrevBlock(getter_AddRefs(mPrevTextBlock)); 823 rv = GetFirstTextNodeInNextBlock(getter_AddRefs(mNextTextBlock)); 824 } else { 825 // We must be done. 826 mPrevTextBlock = nullptr; 827 mNextTextBlock = nullptr; 828 } 829 830 // The result of GetFirstTextNodeInNextBlock() or NS_OK. 831 return rv; 832 } 833 834 nsresult TextServicesDocument::IsDone(bool* aIsDone) { 835 NS_ENSURE_TRUE(aIsDone, NS_ERROR_NULL_POINTER); 836 837 *aIsDone = false; 838 839 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE); 840 841 *aIsDone = mIteratorStatus == IteratorStatus::eDone; 842 843 return NS_OK; 844 } 845 846 nsresult TextServicesDocument::SetSelection(uint32_t aOffset, 847 uint32_t aLength) { 848 NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE); 849 850 return SetSelectionInternal(aOffset, aLength, true); 851 } 852 853 nsresult TextServicesDocument::ScrollSelectionIntoView() { 854 NS_ENSURE_TRUE(mSelCon, NS_ERROR_FAILURE); 855 856 // After ScrollSelectionIntoView(), the pending notifications might be flushed 857 // and PresShell/PresContext/Frames may be dead. See bug 418470. 858 const nsCOMPtr selCon = mSelCon; 859 return selCon->ScrollSelectionIntoView( 860 SelectionType::eNormal, nsISelectionController::SELECTION_FOCUS_REGION, 861 ScrollAxis(), ScrollAxis(), ScrollFlags::None, 862 SelectionScrollMode::SyncFlush); 863 } 864 865 nsresult TextServicesDocument::OffsetEntryArray::WillDeleteSelection() { 866 MOZ_ASSERT(mSelection.IsSet()); 867 MOZ_ASSERT(!mSelection.IsCollapsed()); 868 869 for (size_t i = mSelection.StartIndex(); i <= mSelection.EndIndex(); i++) { 870 OffsetEntry* entry = ElementAt(i).get(); 871 if (i == mSelection.StartIndex()) { 872 // Calculate the length of the selection. Note that the 873 // selection length can be zero if the start of the selection 874 // is at the very end of a text node entry. 875 uint32_t selLength; 876 if (entry->mIsInsertedText) { 877 // Inserted text offset entries have no width when 878 // talking in terms of string offsets! If the beginning 879 // of the selection is in an inserted text offset entry, 880 // the caret is always at the end of the entry! 881 selLength = 0; 882 } else { 883 selLength = entry->EndOffsetInTextInBlock() - 884 mSelection.StartOffsetInTextInBlock(); 885 } 886 887 if (selLength > 0) { 888 if (mSelection.StartOffsetInTextInBlock() > 889 entry->mOffsetInTextInBlock) { 890 // Selection doesn't start at the beginning of the 891 // text node entry. We need to split this entry into 892 // two pieces, the piece before the selection, and 893 // the piece inside the selection. 894 nsresult rv = SplitElementAt(i, selLength); 895 if (NS_FAILED(rv)) { 896 NS_WARNING("selLength was invalid for the OffsetEntry"); 897 return rv; 898 } 899 900 // Adjust selection indexes to account for new entry: 901 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() + 1 < Length()); 902 MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() + 1 < Length()); 903 mSelection.SetIndexes(mSelection.StartIndex() + 1, 904 mSelection.EndIndex() + 1); 905 entry = ElementAt(++i).get(); 906 } 907 908 if (mSelection.StartIndex() < mSelection.EndIndex()) { 909 // The entire entry is contained in the selection. Mark the 910 // entry invalid. 911 entry->mIsValid = false; 912 } 913 } 914 } 915 916 if (i == mSelection.EndIndex()) { 917 if (entry->mIsInsertedText) { 918 // Inserted text offset entries have no width when 919 // talking in terms of string offsets! If the end 920 // of the selection is in an inserted text offset entry, 921 // the selection includes the entire entry! 922 entry->mIsValid = false; 923 } else { 924 // Calculate the length of the selection. Note that the 925 // selection length can be zero if the end of the selection 926 // is at the very beginning of a text node entry. 927 928 const uint32_t selLength = 929 mSelection.EndOffsetInTextInBlock() - entry->mOffsetInTextInBlock; 930 if (selLength) { 931 if (mSelection.EndOffsetInTextInBlock() < 932 entry->EndOffsetInTextInBlock()) { 933 // mOffsetInTextInBlock is guaranteed to be inside the selection, 934 // even when mSelection.IsInSameElement() is true. 935 nsresult rv = SplitElementAt(i, entry->mLength - selLength); 936 if (NS_FAILED(rv)) { 937 NS_WARNING( 938 "entry->mLength - selLength was invalid for the OffsetEntry"); 939 return rv; 940 } 941 942 // Update the entry fields: 943 ElementAt(i + 1)->mOffsetInTextNode = entry->mOffsetInTextNode; 944 } 945 946 if (mSelection.EndOffsetInTextInBlock() == 947 entry->EndOffsetInTextInBlock()) { 948 // The entire entry is contained in the selection. Mark the 949 // entry invalid. 950 entry->mIsValid = false; 951 } 952 } 953 } 954 } 955 956 if (i != mSelection.StartIndex() && i != mSelection.EndIndex()) { 957 // The entire entry is contained in the selection. Mark the 958 // entry invalid. 959 entry->mIsValid = false; 960 } 961 } 962 963 return NS_OK; 964 } 965 966 nsresult TextServicesDocument::DeleteSelection() { 967 if (NS_WARN_IF(!mEditorBase) || 968 NS_WARN_IF(!mOffsetTable.mSelection.IsSet())) { 969 return NS_ERROR_FAILURE; 970 } 971 972 if (mOffsetTable.mSelection.IsCollapsed()) { 973 return NS_OK; 974 } 975 976 // If we have an mExtent, save off its current set of 977 // end points so we can compare them against mExtent's 978 // set after the deletion of the content. 979 980 nsCOMPtr<nsINode> origStartNode, origEndNode; 981 uint32_t origStartOffset = 0, origEndOffset = 0; 982 983 if (mExtent) { 984 nsresult rv = GetRangeEndPoints( 985 mExtent, getter_AddRefs(origStartNode), &origStartOffset, 986 getter_AddRefs(origEndNode), &origEndOffset); 987 988 if (NS_FAILED(rv)) { 989 return rv; 990 } 991 } 992 993 if (NS_FAILED(mOffsetTable.WillDeleteSelection())) { 994 NS_WARNING( 995 "TextServicesDocument::OffsetEntryTable::WillDeleteSelection() failed"); 996 return NS_ERROR_FAILURE; 997 } 998 999 // Make sure mFilteredIter always points to something valid! 1000 AdjustContentIterator(); 1001 1002 // Now delete the actual content! 1003 OwningNonNull<EditorBase> editorBase = *mEditorBase; 1004 nsresult rv = editorBase->DeleteSelectionAsAction(nsIEditor::ePrevious, 1005 nsIEditor::eStrip); 1006 if (NS_FAILED(rv)) { 1007 return rv; 1008 } 1009 1010 // Now that we've actually deleted the selected content, 1011 // check to see if our mExtent has changed, if so, then 1012 // we have to create a new content iterator! 1013 1014 if (origStartNode && origEndNode) { 1015 nsCOMPtr<nsINode> curStartNode, curEndNode; 1016 uint32_t curStartOffset = 0, curEndOffset = 0; 1017 1018 rv = GetRangeEndPoints(mExtent, getter_AddRefs(curStartNode), 1019 &curStartOffset, getter_AddRefs(curEndNode), 1020 &curEndOffset); 1021 1022 if (NS_FAILED(rv)) { 1023 return rv; 1024 } 1025 1026 if (origStartNode != curStartNode || origEndNode != curEndNode) { 1027 // The range has changed, so we need to create a new content 1028 // iterator based on the new range. 1029 nsCOMPtr<nsIContent> curContent; 1030 if (mIteratorStatus != IteratorStatus::eDone) { 1031 // The old iterator is still pointing to something valid, 1032 // so get its current node so we can restore it after we 1033 // create the new iterator! 1034 curContent = mFilteredIter->GetCurrentNode() 1035 ? mFilteredIter->GetCurrentNode()->AsContent() 1036 : nullptr; 1037 } 1038 1039 // Create the new iterator. 1040 rv = 1041 CreateFilteredContentIterator(mExtent, getter_AddRefs(mFilteredIter)); 1042 if (NS_FAILED(rv)) { 1043 return rv; 1044 } 1045 1046 // Now make the new iterator point to the content node 1047 // the old one was pointing at. 1048 if (curContent) { 1049 rv = mFilteredIter->PositionAt(curContent); 1050 if (NS_FAILED(rv)) { 1051 mIteratorStatus = IteratorStatus::eDone; 1052 } else { 1053 mIteratorStatus = IteratorStatus::eValid; 1054 } 1055 } 1056 } 1057 } 1058 1059 OffsetEntry* entry = mOffsetTable.DidDeleteSelection(); 1060 if (entry) { 1061 SetSelection(mOffsetTable.mSelection.StartOffsetInTextInBlock(), 0); 1062 } 1063 1064 // Now remove any invalid entries from the offset table. 1065 mOffsetTable.RemoveInvalidElements(); 1066 return NS_OK; 1067 } 1068 1069 OffsetEntry* TextServicesDocument::OffsetEntryArray::DidDeleteSelection() { 1070 MOZ_ASSERT(mSelection.IsSet()); 1071 1072 // Move the caret to the end of the first valid entry. 1073 // Start with SelectionStartIndex() since it may still be valid. 1074 OffsetEntry* entry = nullptr; 1075 for (size_t i = mSelection.StartIndex() + 1; !entry && i > 0; i--) { 1076 entry = ElementAt(i - 1).get(); 1077 if (!entry->mIsValid) { 1078 entry = nullptr; 1079 } else { 1080 MOZ_DIAGNOSTIC_ASSERT(i - 1 < Length()); 1081 mSelection.Set(i - 1, entry->EndOffsetInTextInBlock()); 1082 } 1083 } 1084 1085 // If we still don't have a valid entry, move the caret 1086 // to the next valid entry after the selection: 1087 for (size_t i = mSelection.EndIndex(); !entry && i < Length(); i++) { 1088 entry = ElementAt(i).get(); 1089 if (!entry->mIsValid) { 1090 entry = nullptr; 1091 } else { 1092 MOZ_DIAGNOSTIC_ASSERT(i < Length()); 1093 mSelection.Set(i, entry->mOffsetInTextInBlock); 1094 } 1095 } 1096 1097 if (!entry) { 1098 // Uuughh we have no valid offset entry to place our 1099 // caret ... just mark the selection invalid. 1100 mSelection.Reset(); 1101 } 1102 1103 return entry; 1104 } 1105 1106 nsresult TextServicesDocument::InsertText(const nsAString& aText) { 1107 if (NS_WARN_IF(!mEditorBase) || 1108 NS_WARN_IF(!mOffsetTable.mSelection.IsSet())) { 1109 return NS_ERROR_FAILURE; 1110 } 1111 1112 // If the selection is not collapsed, we need to save 1113 // off the selection offsets so we can restore the 1114 // selection and delete the selected content after we've 1115 // inserted the new text. This is necessary to try and 1116 // retain as much of the original style of the content 1117 // being deleted. 1118 1119 const bool wasSelectionCollapsed = mOffsetTable.mSelection.IsCollapsed(); 1120 const uint32_t savedSelOffset = 1121 mOffsetTable.mSelection.StartOffsetInTextInBlock(); 1122 const uint32_t savedSelLength = mOffsetTable.mSelection.LengthInTextInBlock(); 1123 1124 if (!wasSelectionCollapsed) { 1125 // Collapse to the start of the current selection 1126 // for the insert! 1127 nsresult rv = 1128 SetSelection(mOffsetTable.mSelection.StartOffsetInTextInBlock(), 0); 1129 NS_ENSURE_SUCCESS(rv, rv); 1130 } 1131 1132 // AutoTransactionBatchExternal grabs mEditorBase, so, we don't need to grab 1133 // the instance with local variable here. 1134 OwningNonNull<EditorBase> editorBase = *mEditorBase; 1135 AutoTransactionBatchExternal treatAsOneTransaction(editorBase); 1136 1137 nsresult rv = editorBase->InsertTextAsAction(aText); 1138 if (NS_FAILED(rv)) { 1139 NS_WARNING("InsertTextAsAction() failed"); 1140 return rv; 1141 } 1142 1143 RefPtr<Selection> selection = 1144 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL); 1145 rv = mOffsetTable.DidInsertText(selection, aText); 1146 if (NS_FAILED(rv)) { 1147 NS_WARNING("TextServicesDocument::OffsetEntry::DidInsertText() failed"); 1148 return rv; 1149 } 1150 1151 if (!wasSelectionCollapsed) { 1152 nsresult rv = SetSelection(savedSelOffset, savedSelLength); 1153 if (NS_FAILED(rv)) { 1154 return rv; 1155 } 1156 1157 rv = DeleteSelection(); 1158 if (NS_FAILED(rv)) { 1159 return rv; 1160 } 1161 } 1162 1163 return NS_OK; 1164 } 1165 1166 nsresult TextServicesDocument::OffsetEntryArray::DidInsertText( 1167 dom::Selection* aSelection, const nsAString& aInsertedString) { 1168 MOZ_ASSERT(mSelection.IsSet()); 1169 1170 // When you touch this method, please make sure that the entry instance 1171 // won't be deleted. If you know it'll be deleted, you should set it to 1172 // `nullptr`. 1173 OffsetEntry* entry = ElementAt(mSelection.StartIndex()).get(); 1174 OwningNonNull<Text> const textNodeAtStartEntry = entry->mTextNode; 1175 1176 NS_ASSERTION((entry->mIsValid), "Invalid insertion point!"); 1177 1178 if (entry->mOffsetInTextInBlock == mSelection.StartOffsetInTextInBlock()) { 1179 if (entry->mIsInsertedText) { 1180 // If the caret is in an inserted text offset entry, 1181 // we simply insert the text at the end of the entry. 1182 entry->mLength += aInsertedString.Length(); 1183 } else { 1184 // Insert an inserted text offset entry before the current 1185 // entry! 1186 UniquePtr<OffsetEntry> newInsertedTextEntry = 1187 MakeUnique<OffsetEntry>(entry->mTextNode, entry->mOffsetInTextInBlock, 1188 aInsertedString.Length()); 1189 newInsertedTextEntry->mIsInsertedText = true; 1190 newInsertedTextEntry->mOffsetInTextNode = entry->mOffsetInTextNode; 1191 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1192 // pretended earlier. 1193 InsertElementAt(mSelection.StartIndex(), std::move(newInsertedTextEntry)); 1194 } 1195 } else if (entry->EndOffsetInTextInBlock() == 1196 mSelection.EndOffsetInTextInBlock()) { 1197 // We are inserting text at the end of the current offset entry. 1198 // Look at the next valid entry in the table. If it's an inserted 1199 // text entry, add to its length and adjust its node offset. If 1200 // it isn't, add a new inserted text entry. 1201 uint32_t nextIndex = mSelection.StartIndex() + 1; 1202 OffsetEntry* insertedTextEntry = nullptr; 1203 if (Length() > nextIndex) { 1204 insertedTextEntry = ElementAt(nextIndex).get(); 1205 if (!insertedTextEntry) { 1206 return NS_ERROR_FAILURE; 1207 } 1208 1209 // Check if the entry is a match. If it isn't, set 1210 // iEntry to zero. 1211 if (!insertedTextEntry->mIsInsertedText || 1212 insertedTextEntry->mOffsetInTextInBlock != 1213 mSelection.StartOffsetInTextInBlock()) { 1214 insertedTextEntry = nullptr; 1215 } 1216 } 1217 1218 if (!insertedTextEntry) { 1219 // We didn't find an inserted text offset entry, so 1220 // create one. 1221 UniquePtr<OffsetEntry> newInsertedTextEntry = MakeUnique<OffsetEntry>( 1222 entry->mTextNode, mSelection.StartOffsetInTextInBlock(), 0); 1223 newInsertedTextEntry->mOffsetInTextNode = entry->EndOffsetInTextNode(); 1224 newInsertedTextEntry->mIsInsertedText = true; 1225 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1226 // pretended earlier. 1227 insertedTextEntry = 1228 InsertElementAt(nextIndex, std::move(newInsertedTextEntry))->get(); 1229 } 1230 1231 // We have a valid inserted text offset entry. Update its 1232 // length, adjust the selection indexes, and make sure the 1233 // caret is properly placed! 1234 1235 insertedTextEntry->mLength += aInsertedString.Length(); 1236 1237 MOZ_DIAGNOSTIC_ASSERT(nextIndex < Length()); 1238 mSelection.SetIndex(nextIndex); 1239 1240 if (!aSelection) { 1241 return NS_OK; 1242 } 1243 1244 OwningNonNull<Text> textNode = insertedTextEntry->mTextNode; 1245 nsresult rv = aSelection->CollapseInLimiter( 1246 textNode, insertedTextEntry->EndOffsetInTextNode()); 1247 if (NS_FAILED(rv)) { 1248 NS_WARNING("Selection::CollapseInLimiter() failed"); 1249 return rv; 1250 } 1251 } else if (entry->EndOffsetInTextInBlock() > 1252 mSelection.StartOffsetInTextInBlock()) { 1253 // We are inserting text into the middle of the current offset entry. 1254 // split the current entry into two parts, then insert an inserted text 1255 // entry between them! 1256 nsresult rv = SplitElementAt(mSelection.StartIndex(), 1257 entry->EndOffsetInTextInBlock() - 1258 mSelection.StartOffsetInTextInBlock()); 1259 if (NS_FAILED(rv)) { 1260 NS_WARNING( 1261 "entry->EndOffsetInTextInBlock() - " 1262 "mSelection.StartOffsetInTextInBlock() was invalid for the " 1263 "OffsetEntry"); 1264 return rv; 1265 } 1266 1267 // XXX(Bug 1631371) Check if this should use a fallible operation as it 1268 // pretended earlier. 1269 UniquePtr<OffsetEntry>& insertedTextEntry = *InsertElementAt( 1270 mSelection.StartIndex() + 1, 1271 MakeUnique<OffsetEntry>(entry->mTextNode, 1272 mSelection.StartOffsetInTextInBlock(), 1273 aInsertedString.Length())); 1274 LockOffsetEntryArrayLengthInDebugBuild(observer, *this); 1275 insertedTextEntry->mIsInsertedText = true; 1276 insertedTextEntry->mOffsetInTextNode = entry->EndOffsetInTextNode(); 1277 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() + 1 < Length()); 1278 mSelection.SetIndex(mSelection.StartIndex() + 1); 1279 } 1280 1281 // We've just finished inserting an inserted text offset entry. 1282 // update all entries with the same mTextNode pointer that follow 1283 // it in the table! 1284 1285 for (size_t i = mSelection.StartIndex() + 1; i < Length(); i++) { 1286 const UniquePtr<OffsetEntry>& entry = ElementAt(i); 1287 LockOffsetEntryArrayLengthInDebugBuild(observer, *this); 1288 if (entry->mTextNode != textNodeAtStartEntry) { 1289 break; 1290 } 1291 if (entry->mIsValid) { 1292 entry->mOffsetInTextNode += aInsertedString.Length(); 1293 } 1294 } 1295 1296 return NS_OK; 1297 } 1298 1299 void TextServicesDocument::DidDeleteContent(const nsIContent& aChildContent) { 1300 if (NS_WARN_IF(!mFilteredIter) || !aChildContent.IsText()) { 1301 return; 1302 } 1303 1304 Maybe<size_t> maybeNodeIndex = 1305 mOffsetTable.FirstIndexOf(*aChildContent.AsText()); 1306 if (maybeNodeIndex.isNothing()) { 1307 // It's okay if the node isn't in the offset table, the 1308 // editor could be cleaning house. 1309 return; 1310 } 1311 1312 nsINode* node = mFilteredIter->GetCurrentNode(); 1313 if (node && node == &aChildContent && 1314 mIteratorStatus != IteratorStatus::eDone) { 1315 // XXX: This should never really happen because 1316 // AdjustContentIterator() should have been called prior 1317 // to the delete to try and position the iterator on the 1318 // next valid text node in the offset table, and if there 1319 // wasn't a next, it would've set mIteratorStatus to eIsDone. 1320 1321 NS_ERROR("DeleteNode called for current iterator node."); 1322 } 1323 1324 for (size_t nodeIndex = *maybeNodeIndex; nodeIndex < mOffsetTable.Length(); 1325 nodeIndex++) { 1326 const UniquePtr<OffsetEntry>& entry = mOffsetTable[nodeIndex]; 1327 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable); 1328 if (!entry) { 1329 return; 1330 } 1331 1332 if (entry->mTextNode == &aChildContent) { 1333 entry->mIsValid = false; 1334 } 1335 } 1336 } 1337 1338 void TextServicesDocument::DidJoinContents( 1339 const EditorRawDOMPoint& aJoinedPoint, const nsIContent& aRemovedContent) { 1340 // Make sure that both nodes are text nodes -- otherwise we don't care. 1341 if (!aJoinedPoint.IsInTextNode() || !aRemovedContent.IsText()) { 1342 return; 1343 } 1344 1345 // Note: The editor merges the contents of the left node into the 1346 // contents of the right. 1347 1348 Maybe<size_t> maybeRemovedIndex = 1349 mOffsetTable.FirstIndexOf(*aRemovedContent.AsText()); 1350 if (maybeRemovedIndex.isNothing()) { 1351 // It's okay if the node isn't in the offset table, the 1352 // editor could be cleaning house. 1353 return; 1354 } 1355 1356 Maybe<size_t> maybeJoinedIndex = 1357 mOffsetTable.FirstIndexOf(*aJoinedPoint.ContainerAs<Text>()); 1358 if (maybeJoinedIndex.isNothing()) { 1359 // It's okay if the node isn't in the offset table, the 1360 // editor could be cleaning house. 1361 return; 1362 } 1363 1364 const size_t removedIndex = *maybeRemovedIndex; 1365 const size_t joinedIndex = *maybeJoinedIndex; 1366 1367 if (MOZ_UNLIKELY(joinedIndex > removedIndex)) { 1368 NS_ASSERTION(joinedIndex < removedIndex, "Indexes out of order."); 1369 return; 1370 } 1371 NS_ASSERTION(mOffsetTable[removedIndex]->mOffsetInTextNode == 0, 1372 "Unexpected offset value for rightIndex."); 1373 1374 // Run through the table and change all entries referring to 1375 // the removed node so that they now refer to the joined node, 1376 // and adjust offsets if necessary. 1377 const uint32_t movedTextDataLength = 1378 aJoinedPoint.ContainerAs<Text>()->TextDataLength() - 1379 aJoinedPoint.Offset(); 1380 for (uint32_t i = removedIndex; i < mOffsetTable.Length(); i++) { 1381 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i]; 1382 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable); 1383 if (entry->mTextNode != aRemovedContent.AsText()) { 1384 break; 1385 } 1386 if (entry->mIsValid) { 1387 entry->mTextNode = aJoinedPoint.ContainerAs<Text>(); 1388 // The text was moved from aRemovedContent to end of the container of 1389 // aJoinedPoint. 1390 entry->mOffsetInTextNode += movedTextDataLength; 1391 } 1392 } 1393 1394 // Now check to see if the iterator is pointing to the 1395 // left node. If it is, make it point to the joined node! 1396 if (mFilteredIter->GetCurrentNode() == aRemovedContent.AsText()) { 1397 mFilteredIter->PositionAt(aJoinedPoint.ContainerAs<Text>()); 1398 } 1399 } 1400 1401 nsresult TextServicesDocument::CreateFilteredContentIterator( 1402 const AbstractRange* aAbstractRange, 1403 FilteredContentIterator** aFilteredIter) { 1404 if (NS_WARN_IF(!aAbstractRange) || NS_WARN_IF(!aFilteredIter)) { 1405 return NS_ERROR_INVALID_ARG; 1406 } 1407 1408 *aFilteredIter = nullptr; 1409 1410 UniquePtr<nsComposeTxtSrvFilter> composeFilter; 1411 switch (mTxtSvcFilterType) { 1412 case nsIEditorSpellCheck::FILTERTYPE_NORMAL: 1413 composeFilter = nsComposeTxtSrvFilter::CreateNormalFilter(); 1414 break; 1415 case nsIEditorSpellCheck::FILTERTYPE_MAIL: 1416 composeFilter = nsComposeTxtSrvFilter::CreateMailFilter(); 1417 break; 1418 } 1419 1420 // Create a FilteredContentIterator 1421 // This class wraps the ContentIterator in order to give itself a chance 1422 // to filter out certain content nodes 1423 RefPtr<FilteredContentIterator> filter = 1424 new FilteredContentIterator(std::move(composeFilter)); 1425 nsresult rv = filter->Init(aAbstractRange); 1426 if (NS_FAILED(rv)) { 1427 return rv; 1428 } 1429 1430 filter.forget(aFilteredIter); 1431 return NS_OK; 1432 } 1433 1434 Element* TextServicesDocument::GetDocumentContentRootNode() const { 1435 if (NS_WARN_IF(!mDocument)) { 1436 return nullptr; 1437 } 1438 1439 if (mDocument->IsHTMLOrXHTML()) { 1440 Element* rootElement = mDocument->GetRootElement(); 1441 if (rootElement && rootElement->IsXULElement()) { 1442 // HTML documents with root XUL elements should eventually be transitioned 1443 // to a regular document structure, but for now the content root node will 1444 // be the document element. 1445 return mDocument->GetDocumentElement(); 1446 } 1447 // For HTML documents, the content root node is the body. 1448 return mDocument->GetBody(); 1449 } 1450 1451 // For non-HTML documents, the content root node will be the document element. 1452 return mDocument->GetDocumentElement(); 1453 } 1454 1455 already_AddRefed<nsRange> TextServicesDocument::CreateDocumentContentRange() { 1456 nsCOMPtr<nsINode> node = GetDocumentContentRootNode(); 1457 if (NS_WARN_IF(!node)) { 1458 return nullptr; 1459 } 1460 1461 RefPtr<nsRange> range = nsRange::Create(node); 1462 IgnoredErrorResult ignoredError; 1463 range->SelectNodeContents(*node, ignoredError); 1464 NS_WARNING_ASSERTION(!ignoredError.Failed(), "SelectNodeContents() failed"); 1465 return range.forget(); 1466 } 1467 1468 already_AddRefed<nsRange> 1469 TextServicesDocument::CreateDocumentContentRootToNodeOffsetRange( 1470 nsINode* aParent, uint32_t aOffset, bool aToStart) { 1471 if (NS_WARN_IF(!aParent)) { 1472 return nullptr; 1473 } 1474 1475 nsCOMPtr<nsINode> bodyNode = GetDocumentContentRootNode(); 1476 if (NS_WARN_IF(!bodyNode)) { 1477 return nullptr; 1478 } 1479 1480 nsCOMPtr<nsINode> startNode; 1481 nsCOMPtr<nsINode> endNode; 1482 uint32_t startOffset, endOffset; 1483 1484 if (aToStart) { 1485 // The range should begin at the start of the document 1486 // and extend up until (aParent, aOffset). 1487 startNode = bodyNode; 1488 startOffset = 0; 1489 endNode = aParent; 1490 endOffset = aOffset; 1491 } else { 1492 // The range should begin at (aParent, aOffset) and 1493 // extend to the end of the document. 1494 startNode = aParent; 1495 startOffset = aOffset; 1496 endNode = bodyNode; 1497 endOffset = endNode ? endNode->GetChildCount() : 0; 1498 } 1499 1500 RefPtr<nsRange> range = nsRange::Create(startNode, startOffset, endNode, 1501 endOffset, IgnoreErrors()); 1502 NS_WARNING_ASSERTION(range, 1503 "nsRange::Create() failed to create new valid range"); 1504 return range.forget(); 1505 } 1506 1507 nsresult TextServicesDocument::CreateDocumentContentIterator( 1508 FilteredContentIterator** aFilteredIter) { 1509 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER); 1510 1511 RefPtr<nsRange> range = CreateDocumentContentRange(); 1512 if (NS_WARN_IF(!range)) { 1513 *aFilteredIter = nullptr; 1514 return NS_ERROR_FAILURE; 1515 } 1516 1517 return CreateFilteredContentIterator(range, aFilteredIter); 1518 } 1519 1520 nsresult TextServicesDocument::AdjustContentIterator() { 1521 NS_ENSURE_TRUE(mFilteredIter, NS_ERROR_FAILURE); 1522 1523 nsCOMPtr<nsINode> node = mFilteredIter->GetCurrentNode(); 1524 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); 1525 1526 Text* prevValidTextNode = nullptr; 1527 Text* nextValidTextNode = nullptr; 1528 bool foundEntry = false; 1529 1530 const size_t tableLength = mOffsetTable.Length(); 1531 for (size_t i = 0; i < tableLength && !nextValidTextNode; i++) { 1532 UniquePtr<OffsetEntry>& entry = mOffsetTable[i]; 1533 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable); 1534 if (entry->mTextNode == node) { 1535 if (entry->mIsValid) { 1536 // The iterator is still pointing to something valid! 1537 // Do nothing! 1538 return NS_OK; 1539 } 1540 // We found an invalid entry that points to 1541 // the current iterator node. Stop looking for 1542 // a previous valid node! 1543 foundEntry = true; 1544 } 1545 1546 if (entry->mIsValid) { 1547 if (!foundEntry) { 1548 prevValidTextNode = entry->mTextNode; 1549 } else { 1550 nextValidTextNode = entry->mTextNode; 1551 } 1552 } 1553 } 1554 1555 Text* validTextNode = nullptr; 1556 if (prevValidTextNode) { 1557 validTextNode = prevValidTextNode; 1558 } else if (nextValidTextNode) { 1559 validTextNode = nextValidTextNode; 1560 } 1561 1562 if (validTextNode) { 1563 nsresult rv = mFilteredIter->PositionAt(validTextNode); 1564 if (NS_FAILED(rv)) { 1565 mIteratorStatus = IteratorStatus::eDone; 1566 } else { 1567 mIteratorStatus = IteratorStatus::eValid; 1568 } 1569 return rv; 1570 } 1571 1572 // If we get here, there aren't any valid entries 1573 // in the offset table! Try to position the iterator 1574 // on the next text block first, then previous if 1575 // one doesn't exist! 1576 1577 if (mNextTextBlock) { 1578 nsresult rv = mFilteredIter->PositionAt(mNextTextBlock); 1579 if (NS_FAILED(rv)) { 1580 mIteratorStatus = IteratorStatus::eDone; 1581 return rv; 1582 } 1583 1584 mIteratorStatus = IteratorStatus::eNext; 1585 } else if (mPrevTextBlock) { 1586 nsresult rv = mFilteredIter->PositionAt(mPrevTextBlock); 1587 if (NS_FAILED(rv)) { 1588 mIteratorStatus = IteratorStatus::eDone; 1589 return rv; 1590 } 1591 1592 mIteratorStatus = IteratorStatus::ePrev; 1593 } else { 1594 mIteratorStatus = IteratorStatus::eDone; 1595 } 1596 return NS_OK; 1597 } 1598 1599 // static 1600 bool TextServicesDocument::DidSkip(FilteredContentIterator* aFilteredIter) { 1601 return aFilteredIter && aFilteredIter->DidSkip(); 1602 } 1603 1604 // static 1605 void TextServicesDocument::ClearDidSkip( 1606 FilteredContentIterator* aFilteredIter) { 1607 // Clear filter's skip flag 1608 if (aFilteredIter) { 1609 aFilteredIter->ClearDidSkip(); 1610 } 1611 } 1612 1613 // static 1614 bool TextServicesDocument::HasSameBlockNodeParent(Text& aTextNode1, 1615 Text& aTextNode2) { 1616 // XXX How about the case that both text nodes are orphan nodes? 1617 if (aTextNode1.GetParent() == aTextNode2.GetParent()) { 1618 return true; 1619 } 1620 1621 // I think that spellcheck should be available only in editable nodes. 1622 // So, we also need to check whether they are in same editing host. 1623 const Element* editableBlockElementOrInlineEditingHost1 = 1624 HTMLEditUtils::GetAncestorElement( 1625 aTextNode1, 1626 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost, 1627 BlockInlineCheck::UseHTMLDefaultStyle); 1628 const Element* editableBlockElementOrInlineEditingHost2 = 1629 HTMLEditUtils::GetAncestorElement( 1630 aTextNode2, 1631 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost, 1632 BlockInlineCheck::UseHTMLDefaultStyle); 1633 return editableBlockElementOrInlineEditingHost1 && 1634 editableBlockElementOrInlineEditingHost1 == 1635 editableBlockElementOrInlineEditingHost2; 1636 } 1637 1638 Result<EditorRawDOMRangeInTexts, nsresult> 1639 TextServicesDocument::OffsetEntryArray::WillSetSelection( 1640 uint32_t aOffsetInTextInBlock, uint32_t aLength) { 1641 // Find start of selection in node offset terms: 1642 EditorRawDOMPointInText newStart; 1643 for (size_t i = 0; !newStart.IsSet() && i < Length(); i++) { 1644 const UniquePtr<OffsetEntry>& entry = ElementAt(i); 1645 LockOffsetEntryArrayLengthInDebugBuild(observer, *this); 1646 if (entry->mIsValid) { 1647 if (entry->mIsInsertedText) { 1648 // Caret can only be placed at the end of an 1649 // inserted text offset entry, if the offsets 1650 // match exactly! 1651 if (entry->mOffsetInTextInBlock == aOffsetInTextInBlock) { 1652 newStart.Set(entry->mTextNode, entry->EndOffsetInTextNode()); 1653 } 1654 } else if (aOffsetInTextInBlock >= entry->mOffsetInTextInBlock) { 1655 bool foundEntry = false; 1656 if (aOffsetInTextInBlock < entry->EndOffsetInTextInBlock()) { 1657 foundEntry = true; 1658 } else if (aOffsetInTextInBlock == entry->EndOffsetInTextInBlock()) { 1659 // Peek after this entry to see if we have any 1660 // inserted text entries belonging to the same 1661 // entry->mTextNode. If so, we have to place the selection 1662 // after it! 1663 if (i + 1 < Length()) { 1664 const UniquePtr<OffsetEntry>& nextEntry = ElementAt(i + 1); 1665 LockOffsetEntryArrayLengthInDebugBuild(observer, *this); 1666 if (!nextEntry->mIsValid || 1667 nextEntry->mOffsetInTextInBlock != aOffsetInTextInBlock) { 1668 // Next offset entry isn't an exact match, so we'll 1669 // just use the current entry. 1670 foundEntry = true; 1671 } 1672 } 1673 } 1674 1675 if (foundEntry) { 1676 newStart.Set(entry->mTextNode, entry->mOffsetInTextNode + 1677 aOffsetInTextInBlock - 1678 entry->mOffsetInTextInBlock); 1679 } 1680 } 1681 1682 if (newStart.IsSet()) { 1683 MOZ_DIAGNOSTIC_ASSERT(i < Length()); 1684 mSelection.Set(i, aOffsetInTextInBlock); 1685 } 1686 } 1687 } 1688 1689 if (NS_WARN_IF(!newStart.IsSet())) { 1690 return Err(NS_ERROR_FAILURE); 1691 } 1692 1693 if (!aLength) { 1694 mSelection.CollapseToStart(); 1695 return EditorRawDOMRangeInTexts(newStart); 1696 } 1697 1698 // Find the end of the selection in node offset terms: 1699 EditorRawDOMPointInText newEnd; 1700 const uint32_t endOffset = aOffsetInTextInBlock + aLength; 1701 for (uint32_t i = Length(); !newEnd.IsSet() && i > 0; i--) { 1702 const UniquePtr<OffsetEntry>& entry = ElementAt(i - 1); 1703 LockOffsetEntryArrayLengthInDebugBuild(observer, *this); 1704 if (entry->mIsValid) { 1705 if (entry->mIsInsertedText) { 1706 if (entry->mOffsetInTextInBlock == 1707 (newEnd.IsSet() ? newEnd.Offset() : 0)) { 1708 // If the selection ends on an inserted text offset entry, 1709 // the selection includes the entire entry! 1710 newEnd.Set(entry->mTextNode, entry->EndOffsetInTextNode()); 1711 } 1712 } else if (entry->OffsetInTextInBlockIsInRangeOrEndOffset(endOffset)) { 1713 newEnd.Set(entry->mTextNode, entry->mOffsetInTextNode + endOffset - 1714 entry->mOffsetInTextInBlock); 1715 } 1716 1717 if (newEnd.IsSet()) { 1718 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() < Length()); 1719 MOZ_DIAGNOSTIC_ASSERT(i - 1 < Length()); 1720 mSelection.Set(mSelection.StartIndex(), i - 1, 1721 mSelection.StartOffsetInTextInBlock(), endOffset); 1722 } 1723 } 1724 } 1725 1726 return newEnd.IsSet() ? EditorRawDOMRangeInTexts(newStart, newEnd) 1727 : EditorRawDOMRangeInTexts(newStart); 1728 } 1729 1730 nsresult TextServicesDocument::SetSelectionInternal( 1731 uint32_t aOffsetInTextInBlock, uint32_t aLength, bool aDoUpdate) { 1732 if (NS_WARN_IF(!mSelCon)) { 1733 return NS_ERROR_INVALID_ARG; 1734 } 1735 1736 Result<EditorRawDOMRangeInTexts, nsresult> newSelectionRange = 1737 mOffsetTable.WillSetSelection(aOffsetInTextInBlock, aLength); 1738 if (newSelectionRange.isErr()) { 1739 NS_WARNING( 1740 "TextServicesDocument::OffsetEntryArray::WillSetSelection() failed"); 1741 return newSelectionRange.unwrapErr(); 1742 } 1743 1744 if (!aDoUpdate) { 1745 return NS_OK; 1746 } 1747 1748 // XXX: If we ever get a SetSelection() method in nsIEditor, we should 1749 // use it. 1750 RefPtr<Selection> selection = 1751 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL); 1752 if (NS_WARN_IF(!selection)) { 1753 return NS_ERROR_FAILURE; 1754 } 1755 1756 if (newSelectionRange.inspect().Collapsed()) { 1757 nsresult rv = 1758 selection->CollapseInLimiter(newSelectionRange.inspect().StartRef()); 1759 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1760 "Selection::CollapseInLimiter() failed"); 1761 return rv; 1762 } 1763 1764 ErrorResult error; 1765 selection->SetStartAndEndInLimiter(newSelectionRange.inspect().StartRef(), 1766 newSelectionRange.inspect().EndRef(), 1767 error); 1768 NS_WARNING_ASSERTION(!error.Failed(), 1769 "Selection::SetStartAndEndInLimiter() failed"); 1770 return error.StealNSResult(); 1771 } 1772 1773 nsresult TextServicesDocument::GetSelection(BlockSelectionStatus* aSelStatus, 1774 uint32_t* aSelOffset, 1775 uint32_t* aSelLength) { 1776 NS_ENSURE_TRUE(aSelStatus && aSelOffset && aSelLength, NS_ERROR_NULL_POINTER); 1777 1778 *aSelStatus = BlockSelectionStatus::eBlockNotFound; 1779 *aSelOffset = UINT32_MAX; 1780 *aSelLength = UINT32_MAX; 1781 1782 NS_ENSURE_TRUE(mDocument && mSelCon, NS_ERROR_FAILURE); 1783 1784 if (mIteratorStatus == IteratorStatus::eDone) { 1785 return NS_OK; 1786 } 1787 1788 RefPtr<Selection> selection = 1789 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL); 1790 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); 1791 1792 if (selection->IsCollapsed()) { 1793 return GetCollapsedSelection(aSelStatus, aSelOffset, aSelLength); 1794 } 1795 1796 return GetUncollapsedSelection(aSelStatus, aSelOffset, aSelLength); 1797 } 1798 1799 nsresult TextServicesDocument::GetCollapsedSelection( 1800 BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset, 1801 uint32_t* aSelLength) { 1802 RefPtr<Selection> selection = 1803 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL); 1804 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); 1805 1806 // The calling function should have done the GetIsCollapsed() 1807 // check already. Just assume it's collapsed! 1808 *aSelStatus = BlockSelectionStatus::eBlockOutside; 1809 *aSelOffset = *aSelLength = UINT32_MAX; 1810 1811 const uint32_t tableCount = mOffsetTable.Length(); 1812 if (!tableCount) { 1813 return NS_OK; 1814 } 1815 1816 // Get pointers to the first and last offset entries 1817 // in the table. 1818 1819 UniquePtr<OffsetEntry>& eStart = mOffsetTable[0]; 1820 UniquePtr<OffsetEntry>& eEnd = 1821 tableCount > 1 ? mOffsetTable[tableCount - 1] : eStart; 1822 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable); 1823 1824 const uint32_t eStartOffset = eStart->mOffsetInTextNode; 1825 const uint32_t eEndOffset = eEnd->EndOffsetInTextNode(); 1826 1827 RefPtr<const nsRange> range = selection->GetRangeAt(0); 1828 NS_ENSURE_STATE(range); 1829 1830 nsCOMPtr<nsINode> parent = range->GetStartContainer(); 1831 MOZ_ASSERT(parent); 1832 1833 uint32_t offset = range->StartOffset(); 1834 1835 const Maybe<int32_t> e1s1 = nsContentUtils::ComparePointsWithIndices( 1836 eStart->mTextNode, eStartOffset, parent, offset); 1837 const Maybe<int32_t> e2s1 = nsContentUtils::ComparePointsWithIndices( 1838 eEnd->mTextNode, eEndOffset, parent, offset); 1839 1840 if (MOZ_UNLIKELY(NS_WARN_IF(!e1s1) || NS_WARN_IF(!e2s1))) { 1841 return NS_ERROR_FAILURE; 1842 } 1843 1844 if (*e1s1 > 0 || *e2s1 < 0) { 1845 // We're done if the caret is outside the current text block. 1846 return NS_OK; 1847 } 1848 1849 if (parent->IsText()) { 1850 // Good news, the caret is in a text node. Look 1851 // through the offset table for the entry that 1852 // matches its parent and offset. 1853 1854 for (uint32_t i = 0; i < tableCount; i++) { 1855 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i]; 1856 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable); 1857 if (entry->mTextNode == parent->AsText() && 1858 entry->OffsetInTextNodeIsInRangeOrEndOffset(offset)) { 1859 *aSelStatus = BlockSelectionStatus::eBlockContains; 1860 *aSelOffset = 1861 entry->mOffsetInTextInBlock + (offset - entry->mOffsetInTextNode); 1862 *aSelLength = 0; 1863 return NS_OK; 1864 } 1865 } 1866 1867 // If we get here, we didn't find a text node entry 1868 // in our offset table that matched. 1869 return NS_ERROR_FAILURE; 1870 } 1871 1872 // The caret is in our text block, but it's positioned in some 1873 // non-text node (ex. <b>). Create a range based on the start 1874 // and end of the text block, then create an iterator based on 1875 // this range, with its initial position set to the closest 1876 // child of this non-text node. Then look for the closest text 1877 // node. 1878 1879 range = nsRange::Create(eStart->mTextNode, eStartOffset, eEnd->mTextNode, 1880 eEndOffset, IgnoreErrors()); 1881 if (NS_WARN_IF(!range)) { 1882 return NS_ERROR_FAILURE; 1883 } 1884 1885 RefPtr<FilteredContentIterator> filteredIter; 1886 nsresult rv = 1887 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter)); 1888 NS_ENSURE_SUCCESS(rv, rv); 1889 1890 nsIContent* saveNode; 1891 if (parent->HasChildren()) { 1892 // XXX: We need to make sure that all of parent's 1893 // children are in the text block. 1894 1895 // If the parent has children, position the iterator 1896 // on the child that is to the left of the offset. 1897 1898 nsIContent* content = range->GetChildAtStartOffset(); 1899 if (content && parent->GetFirstChild() != content) { 1900 content = content->GetPreviousSibling(); 1901 } 1902 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); 1903 1904 nsresult rv = filteredIter->PositionAt(content); 1905 NS_ENSURE_SUCCESS(rv, rv); 1906 1907 saveNode = content; 1908 } else { 1909 // The parent has no children, so position the iterator 1910 // on the parent. 1911 NS_ENSURE_TRUE(parent->IsContent(), NS_ERROR_FAILURE); 1912 nsCOMPtr<nsIContent> content = parent->AsContent(); 1913 1914 nsresult rv = filteredIter->PositionAt(content); 1915 NS_ENSURE_SUCCESS(rv, rv); 1916 1917 saveNode = content; 1918 } 1919 1920 // Now iterate to the left, towards the beginning of 1921 // the text block, to find the first text node you 1922 // come across. 1923 1924 Text* textNode = nullptr; 1925 for (; !filteredIter->IsDone(); filteredIter->Prev()) { 1926 nsINode* current = filteredIter->GetCurrentNode(); 1927 if (current->IsText()) { 1928 textNode = current->AsText(); 1929 break; 1930 } 1931 } 1932 1933 if (textNode) { 1934 // We found a node, now set the offset to the end 1935 // of the text node. 1936 offset = textNode->TextLength(); 1937 } else { 1938 // We should never really get here, but I'm paranoid. 1939 1940 // We didn't find a text node above, so iterate to 1941 // the right, towards the end of the text block, looking 1942 // for a text node. 1943 1944 nsresult rv = filteredIter->PositionAt(saveNode); 1945 NS_ENSURE_SUCCESS(rv, rv); 1946 1947 textNode = nullptr; 1948 for (; !filteredIter->IsDone(); filteredIter->Next()) { 1949 nsINode* current = filteredIter->GetCurrentNode(); 1950 if (current->IsText()) { 1951 textNode = current->AsText(); 1952 break; 1953 } 1954 } 1955 NS_ENSURE_TRUE(textNode, NS_ERROR_FAILURE); 1956 1957 // We found a text node, so set the offset to 1958 // the beginning of the node. 1959 offset = 0; 1960 } 1961 1962 for (size_t i = 0; i < tableCount; i++) { 1963 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i]; 1964 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable); 1965 if (entry->mTextNode == textNode && 1966 entry->OffsetInTextNodeIsInRangeOrEndOffset(offset)) { 1967 *aSelStatus = BlockSelectionStatus::eBlockContains; 1968 *aSelOffset = 1969 entry->mOffsetInTextInBlock + (offset - entry->mOffsetInTextNode); 1970 *aSelLength = 0; 1971 1972 // Now move the caret so that it is actually in the text node. 1973 // We do this to keep things in sync. 1974 // 1975 // In most cases, the user shouldn't see any movement in the caret 1976 // on screen. 1977 return SetSelectionInternal(*aSelOffset, *aSelLength, true); 1978 } 1979 } 1980 1981 return NS_ERROR_FAILURE; 1982 } 1983 1984 nsresult TextServicesDocument::GetUncollapsedSelection( 1985 BlockSelectionStatus* aSelStatus, uint32_t* aSelOffset, 1986 uint32_t* aSelLength) { 1987 RefPtr<const nsRange> range; 1988 RefPtr<Selection> selection = 1989 mSelCon->GetSelection(nsISelectionController::SELECTION_NORMAL); 1990 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); 1991 1992 // It is assumed that the calling function has made sure that the 1993 // selection is not collapsed, and that the input params to this 1994 // method are initialized to some defaults. 1995 1996 nsCOMPtr<nsINode> startContainer, endContainer; 1997 1998 const size_t tableCount = mOffsetTable.Length(); 1999 2000 // Get pointers to the first and last offset entries 2001 // in the table. 2002 2003 UniquePtr<OffsetEntry>& eStart = mOffsetTable[0]; 2004 UniquePtr<OffsetEntry>& eEnd = 2005 tableCount > 1 ? mOffsetTable[tableCount - 1] : eStart; 2006 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable); 2007 2008 const uint32_t eStartOffset = eStart->mOffsetInTextNode; 2009 const uint32_t eEndOffset = eEnd->EndOffsetInTextNode(); 2010 2011 const uint32_t rangeCount = selection->RangeCount(); 2012 MOZ_ASSERT(rangeCount); 2013 2014 // Find the first range in the selection that intersects 2015 // the current text block. 2016 Maybe<int32_t> e1s2; 2017 Maybe<int32_t> e2s1; 2018 uint32_t startOffset, endOffset; 2019 for (const uint32_t i : IntegerRange(rangeCount)) { 2020 MOZ_ASSERT(selection->RangeCount() == rangeCount); 2021 range = selection->GetRangeAt(i); 2022 if (MOZ_UNLIKELY(NS_WARN_IF(!range))) { 2023 return NS_ERROR_FAILURE; 2024 } 2025 2026 nsresult rv = 2027 GetRangeEndPoints(range, getter_AddRefs(startContainer), &startOffset, 2028 getter_AddRefs(endContainer), &endOffset); 2029 2030 NS_ENSURE_SUCCESS(rv, rv); 2031 2032 e1s2 = nsContentUtils::ComparePointsWithIndices( 2033 eStart->mTextNode, eStartOffset, endContainer, endOffset); 2034 if (NS_WARN_IF(!e1s2)) { 2035 return NS_ERROR_FAILURE; 2036 } 2037 2038 e2s1 = nsContentUtils::ComparePointsWithIndices( 2039 eEnd->mTextNode, eEndOffset, startContainer, startOffset); 2040 if (NS_WARN_IF(!e2s1)) { 2041 return NS_ERROR_FAILURE; 2042 } 2043 2044 // Break out of the loop if the text block intersects the current range. 2045 2046 if (*e1s2 <= 0 && *e2s1 >= 0) { 2047 break; 2048 } 2049 } 2050 2051 // We're done if we didn't find an intersecting range. 2052 2053 if (rangeCount < 1 || *e1s2 > 0 || *e2s1 < 0) { 2054 *aSelStatus = BlockSelectionStatus::eBlockOutside; 2055 *aSelOffset = *aSelLength = UINT32_MAX; 2056 return NS_OK; 2057 } 2058 2059 // Now that we have an intersecting range, find out more info: 2060 const Maybe<int32_t> e1s1 = nsContentUtils::ComparePointsWithIndices( 2061 eStart->mTextNode, eStartOffset, startContainer, startOffset); 2062 if (NS_WARN_IF(!e1s1)) { 2063 return NS_ERROR_FAILURE; 2064 } 2065 2066 const Maybe<int32_t> e2s2 = nsContentUtils::ComparePointsWithIndices( 2067 eEnd->mTextNode, eEndOffset, endContainer, endOffset); 2068 if (NS_WARN_IF(!e2s2)) { 2069 return NS_ERROR_FAILURE; 2070 } 2071 2072 if (rangeCount > 1) { 2073 // There are multiple selection ranges, we only deal 2074 // with the first one that intersects the current, 2075 // text block, so mark this a as a partial. 2076 *aSelStatus = BlockSelectionStatus::eBlockPartial; 2077 } else if (*e1s1 > 0 && *e2s2 < 0) { 2078 // The range extends beyond the start and 2079 // end of the current text block. 2080 *aSelStatus = BlockSelectionStatus::eBlockInside; 2081 } else if (*e1s1 <= 0 && *e2s2 >= 0) { 2082 // The current text block contains the entire 2083 // range. 2084 *aSelStatus = BlockSelectionStatus::eBlockContains; 2085 } else { 2086 // The range partially intersects the block. 2087 *aSelStatus = BlockSelectionStatus::eBlockPartial; 2088 } 2089 2090 // Now create a range based on the intersection of the 2091 // text block and range: 2092 2093 nsCOMPtr<nsINode> p1, p2; 2094 uint32_t o1, o2; 2095 2096 // The start of the range will be the rightmost 2097 // start node. 2098 2099 if (*e1s1 >= 0) { 2100 p1 = eStart->mTextNode; 2101 o1 = eStartOffset; 2102 } else { 2103 p1 = startContainer; 2104 o1 = startOffset; 2105 } 2106 2107 // The end of the range will be the leftmost 2108 // end node. 2109 2110 if (*e2s2 <= 0) { 2111 p2 = eEnd->mTextNode; 2112 o2 = eEndOffset; 2113 } else { 2114 p2 = endContainer; 2115 o2 = endOffset; 2116 } 2117 2118 range = nsRange::Create(p1, o1, p2, o2, IgnoreErrors()); 2119 if (NS_WARN_IF(!range)) { 2120 return NS_ERROR_FAILURE; 2121 } 2122 2123 // Now iterate over this range to figure out the selection's 2124 // block offset and length. 2125 2126 RefPtr<FilteredContentIterator> filteredIter; 2127 nsresult rv = 2128 CreateFilteredContentIterator(range, getter_AddRefs(filteredIter)); 2129 NS_ENSURE_SUCCESS(rv, rv); 2130 2131 // Find the first text node in the range. 2132 nsCOMPtr<nsIContent> content; 2133 filteredIter->First(); 2134 if (!p1->IsText()) { 2135 bool found = false; 2136 for (; !filteredIter->IsDone(); filteredIter->Next()) { 2137 nsINode* node = filteredIter->GetCurrentNode(); 2138 if (node->IsText()) { 2139 p1 = node->AsText(); 2140 o1 = 0; 2141 found = true; 2142 break; 2143 } 2144 } 2145 NS_ENSURE_TRUE(found, NS_ERROR_FAILURE); 2146 } 2147 2148 // Find the last text node in the range. 2149 filteredIter->Last(); 2150 if (!p2->IsText()) { 2151 bool found = false; 2152 for (; !filteredIter->IsDone(); filteredIter->Prev()) { 2153 nsINode* node = filteredIter->GetCurrentNode(); 2154 if (node->IsText()) { 2155 p2 = node->AsText(); 2156 o2 = p2->AsText()->Length(); 2157 found = true; 2158 2159 break; 2160 } 2161 } 2162 NS_ENSURE_TRUE(found, NS_ERROR_FAILURE); 2163 } 2164 2165 bool found = false; 2166 *aSelLength = 0; 2167 2168 for (size_t i = 0; i < tableCount; i++) { 2169 const UniquePtr<OffsetEntry>& entry = mOffsetTable[i]; 2170 LockOffsetEntryArrayLengthInDebugBuild(observer, mOffsetTable); 2171 if (!found) { 2172 if (entry->mTextNode == p1.get() && 2173 entry->OffsetInTextNodeIsInRangeOrEndOffset(o1)) { 2174 *aSelOffset = 2175 entry->mOffsetInTextInBlock + (o1 - entry->mOffsetInTextNode); 2176 if (p1 == p2 && entry->OffsetInTextNodeIsInRangeOrEndOffset(o2)) { 2177 // The start and end of the range are in the same offset 2178 // entry. Calculate the length of the range then we're done. 2179 *aSelLength = o2 - o1; 2180 break; 2181 } 2182 // Add the length of the sub string in this offset entry 2183 // that follows the start of the range. 2184 *aSelLength = entry->EndOffsetInTextNode() - o1; 2185 found = true; 2186 } 2187 } else { // Found. 2188 if (entry->mTextNode == p2.get() && 2189 entry->OffsetInTextNodeIsInRangeOrEndOffset(o2)) { 2190 // We found the end of the range. Calculate the length of the 2191 // sub string that is before the end of the range, then we're done. 2192 *aSelLength += o2 - entry->mOffsetInTextNode; 2193 break; 2194 } 2195 // The entire entry must be in the range. 2196 *aSelLength += entry->mLength; 2197 } 2198 } 2199 2200 return NS_OK; 2201 } 2202 2203 // static 2204 nsresult TextServicesDocument::GetRangeEndPoints( 2205 const AbstractRange* aAbstractRange, nsINode** aStartContainer, 2206 uint32_t* aStartOffset, nsINode** aEndContainer, uint32_t* aEndOffset) { 2207 if (NS_WARN_IF(!aAbstractRange) || NS_WARN_IF(!aStartContainer) || 2208 NS_WARN_IF(!aEndContainer) || NS_WARN_IF(!aEndOffset)) { 2209 return NS_ERROR_INVALID_ARG; 2210 } 2211 2212 nsCOMPtr<nsINode> startContainer = aAbstractRange->GetStartContainer(); 2213 if (NS_WARN_IF(!startContainer)) { 2214 return NS_ERROR_FAILURE; 2215 } 2216 nsCOMPtr<nsINode> endContainer = aAbstractRange->GetEndContainer(); 2217 if (NS_WARN_IF(!endContainer)) { 2218 return NS_ERROR_FAILURE; 2219 } 2220 2221 startContainer.forget(aStartContainer); 2222 endContainer.forget(aEndContainer); 2223 *aStartOffset = aAbstractRange->StartOffset(); 2224 *aEndOffset = aAbstractRange->EndOffset(); 2225 return NS_OK; 2226 } 2227 2228 // static 2229 nsresult TextServicesDocument::FirstTextNode( 2230 FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus) { 2231 if (aIteratorStatus) { 2232 *aIteratorStatus = IteratorStatus::eDone; 2233 } 2234 2235 for (aFilteredIter->First(); !aFilteredIter->IsDone(); 2236 aFilteredIter->Next()) { 2237 if (aFilteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) { 2238 if (aIteratorStatus) { 2239 *aIteratorStatus = IteratorStatus::eValid; 2240 } 2241 break; 2242 } 2243 } 2244 2245 return NS_OK; 2246 } 2247 2248 // static 2249 nsresult TextServicesDocument::LastTextNode( 2250 FilteredContentIterator* aFilteredIter, IteratorStatus* aIteratorStatus) { 2251 if (aIteratorStatus) { 2252 *aIteratorStatus = IteratorStatus::eDone; 2253 } 2254 2255 for (aFilteredIter->Last(); !aFilteredIter->IsDone(); aFilteredIter->Prev()) { 2256 if (aFilteredIter->GetCurrentNode()->NodeType() == nsINode::TEXT_NODE) { 2257 if (aIteratorStatus) { 2258 *aIteratorStatus = IteratorStatus::eValid; 2259 } 2260 break; 2261 } 2262 } 2263 2264 return NS_OK; 2265 } 2266 2267 // static 2268 nsresult TextServicesDocument::FirstTextNodeInCurrentBlock( 2269 FilteredContentIterator* aFilteredIter) { 2270 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER); 2271 2272 ClearDidSkip(aFilteredIter); 2273 2274 // Walk backwards over adjacent text nodes until 2275 // we hit a block boundary: 2276 RefPtr<Text> lastTextNode; 2277 while (!aFilteredIter->IsDone()) { 2278 nsCOMPtr<nsIContent> content = 2279 aFilteredIter->GetCurrentNode()->IsContent() 2280 ? aFilteredIter->GetCurrentNode()->AsContent() 2281 : nullptr; 2282 // We don't observe layout updates, therefore, we should consider whether 2283 // block or inline only with the default definition of the element. 2284 if (lastTextNode && content && 2285 (HTMLEditUtils::IsBlockElement(*content, 2286 BlockInlineCheck::UseHTMLDefaultStyle) || 2287 content->IsHTMLElement(nsGkAtoms::br))) { 2288 break; 2289 } 2290 if (content && content->IsText()) { 2291 if (lastTextNode && !TextServicesDocument::HasSameBlockNodeParent( 2292 *content->AsText(), *lastTextNode)) { 2293 // We're done, the current text node is in a 2294 // different block. 2295 break; 2296 } 2297 lastTextNode = content->AsText(); 2298 } 2299 2300 aFilteredIter->Prev(); 2301 2302 if (DidSkip(aFilteredIter)) { 2303 break; 2304 } 2305 } 2306 2307 if (lastTextNode) { 2308 aFilteredIter->PositionAt(lastTextNode); 2309 } 2310 2311 // XXX: What should we return if last is null? 2312 2313 return NS_OK; 2314 } 2315 2316 // static 2317 nsresult TextServicesDocument::FirstTextNodeInPrevBlock( 2318 FilteredContentIterator* aFilteredIter) { 2319 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER); 2320 2321 // XXX: What if mFilteredIter is not currently on a text node? 2322 2323 // Make sure mFilteredIter is pointing to the first text node in the 2324 // current block: 2325 2326 nsresult rv = FirstTextNodeInCurrentBlock(aFilteredIter); 2327 2328 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); 2329 2330 // Point mFilteredIter to the first node before the first text node: 2331 2332 aFilteredIter->Prev(); 2333 2334 if (aFilteredIter->IsDone()) { 2335 return NS_ERROR_FAILURE; 2336 } 2337 2338 // Now find the first text node of the next block: 2339 2340 return FirstTextNodeInCurrentBlock(aFilteredIter); 2341 } 2342 2343 // static 2344 nsresult TextServicesDocument::FirstTextNodeInNextBlock( 2345 FilteredContentIterator* aFilteredIter) { 2346 bool crossedBlockBoundary = false; 2347 2348 NS_ENSURE_TRUE(aFilteredIter, NS_ERROR_NULL_POINTER); 2349 2350 ClearDidSkip(aFilteredIter); 2351 2352 RefPtr<Text> previousTextNode; 2353 while (!aFilteredIter->IsDone()) { 2354 if (nsCOMPtr<nsIContent> content = 2355 aFilteredIter->GetCurrentNode()->IsContent() 2356 ? aFilteredIter->GetCurrentNode()->AsContent() 2357 : nullptr) { 2358 if (content->IsText()) { 2359 if (crossedBlockBoundary || 2360 (previousTextNode && !TextServicesDocument::HasSameBlockNodeParent( 2361 *previousTextNode, *content->AsText()))) { 2362 break; 2363 } 2364 previousTextNode = content->AsText(); 2365 } 2366 // We don't observe layout updates, therefore, we should consider whether 2367 // block or inline only with the default definition of the element. 2368 else if (!crossedBlockBoundary && 2369 (HTMLEditUtils::IsBlockElement( 2370 *content, BlockInlineCheck::UseHTMLDefaultStyle) || 2371 content->IsHTMLElement(nsGkAtoms::br))) { 2372 crossedBlockBoundary = true; 2373 } 2374 } 2375 2376 aFilteredIter->Next(); 2377 2378 if (!crossedBlockBoundary && DidSkip(aFilteredIter)) { 2379 crossedBlockBoundary = true; 2380 } 2381 } 2382 2383 return NS_OK; 2384 } 2385 2386 nsresult TextServicesDocument::GetFirstTextNodeInPrevBlock( 2387 nsIContent** aContent) { 2388 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER); 2389 2390 *aContent = 0; 2391 2392 // Save the iterator's current content node so we can restore 2393 // it when we are done: 2394 2395 nsINode* node = mFilteredIter->GetCurrentNode(); 2396 2397 nsresult rv = FirstTextNodeInPrevBlock(mFilteredIter); 2398 2399 if (NS_FAILED(rv)) { 2400 // Try to restore the iterator before returning. 2401 mFilteredIter->PositionAt(node); 2402 return rv; 2403 } 2404 2405 if (!mFilteredIter->IsDone()) { 2406 nsCOMPtr<nsIContent> current = 2407 mFilteredIter->GetCurrentNode()->IsContent() 2408 ? mFilteredIter->GetCurrentNode()->AsContent() 2409 : nullptr; 2410 current.forget(aContent); 2411 } 2412 2413 // Restore the iterator: 2414 2415 return mFilteredIter->PositionAt(node); 2416 } 2417 2418 nsresult TextServicesDocument::GetFirstTextNodeInNextBlock( 2419 nsIContent** aContent) { 2420 NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER); 2421 2422 *aContent = 0; 2423 2424 // Save the iterator's current content node so we can restore 2425 // it when we are done: 2426 2427 nsINode* node = mFilteredIter->GetCurrentNode(); 2428 2429 nsresult rv = FirstTextNodeInNextBlock(mFilteredIter); 2430 2431 if (NS_FAILED(rv)) { 2432 // Try to restore the iterator before returning. 2433 mFilteredIter->PositionAt(node); 2434 return rv; 2435 } 2436 2437 if (!mFilteredIter->IsDone()) { 2438 nsCOMPtr<nsIContent> current = 2439 mFilteredIter->GetCurrentNode()->IsContent() 2440 ? mFilteredIter->GetCurrentNode()->AsContent() 2441 : nullptr; 2442 current.forget(aContent); 2443 } 2444 2445 // Restore the iterator: 2446 return mFilteredIter->PositionAt(node); 2447 } 2448 2449 Result<TextServicesDocument::IteratorStatus, nsresult> 2450 TextServicesDocument::OffsetEntryArray::Init( 2451 FilteredContentIterator& aFilteredIter, IteratorStatus aIteratorStatus, 2452 nsRange* aIterRange, nsAString* aAllTextInBlock /* = nullptr */) { 2453 Clear(); 2454 2455 if (aAllTextInBlock) { 2456 aAllTextInBlock->Truncate(); 2457 } 2458 2459 if (aIteratorStatus == IteratorStatus::eDone) { 2460 return IteratorStatus::eDone; 2461 } 2462 2463 // If we have an aIterRange, retrieve the endpoints so 2464 // they can be used in the while loop below to trim entries 2465 // for text nodes that are partially selected by aIterRange. 2466 2467 nsCOMPtr<nsINode> rngStartNode, rngEndNode; 2468 uint32_t rngStartOffset = 0, rngEndOffset = 0; 2469 if (aIterRange) { 2470 nsresult rv = TextServicesDocument::GetRangeEndPoints( 2471 aIterRange, getter_AddRefs(rngStartNode), &rngStartOffset, 2472 getter_AddRefs(rngEndNode), &rngEndOffset); 2473 if (NS_FAILED(rv)) { 2474 NS_WARNING("TextServicesDocument::GetRangeEndPoints() failed"); 2475 return Err(rv); 2476 } 2477 } 2478 2479 // The text service could have added text nodes to the beginning 2480 // of the current block and called this method again. Make sure 2481 // we really are at the beginning of the current block: 2482 2483 nsresult rv = 2484 TextServicesDocument::FirstTextNodeInCurrentBlock(&aFilteredIter); 2485 if (NS_FAILED(rv)) { 2486 NS_WARNING("TextServicesDocument::FirstTextNodeInCurrentBlock() failed"); 2487 return Err(rv); 2488 } 2489 2490 TextServicesDocument::ClearDidSkip(&aFilteredIter); 2491 2492 uint32_t offset = 0; 2493 RefPtr<Text> firstTextNode, previousTextNode; 2494 while (!aFilteredIter.IsDone()) { 2495 if (nsCOMPtr<nsIContent> content = 2496 aFilteredIter.GetCurrentNode()->IsContent() 2497 ? aFilteredIter.GetCurrentNode()->AsContent() 2498 : nullptr) { 2499 // We don't observe layout updates, therefore, we should consider whether 2500 // block or inline only with the default definition of the element. 2501 if (HTMLEditUtils::IsBlockElement( 2502 *content, BlockInlineCheck::UseHTMLDefaultStyle) || 2503 content->IsHTMLElement(nsGkAtoms::br)) { 2504 break; 2505 } 2506 if (content->IsText()) { 2507 if (previousTextNode && !TextServicesDocument::HasSameBlockNodeParent( 2508 *previousTextNode, *content->AsText())) { 2509 break; 2510 } 2511 2512 nsString str; 2513 content->AsText()->GetNodeValue(str); 2514 2515 // Add an entry for this text node into the offset table: 2516 2517 UniquePtr<OffsetEntry>& entry = *AppendElement( 2518 MakeUnique<OffsetEntry>(*content->AsText(), offset, str.Length())); 2519 LockOffsetEntryArrayLengthInDebugBuild(observer, *this); 2520 2521 // If one or both of the endpoints of the iteration range 2522 // are in the text node for this entry, make sure the entry 2523 // only accounts for the portion of the text node that is 2524 // in the range. 2525 2526 uint32_t startOffset = 0; 2527 uint32_t endOffset = str.Length(); 2528 bool adjustStr = false; 2529 2530 if (entry->mTextNode == rngStartNode) { 2531 entry->mOffsetInTextNode = startOffset = rngStartOffset; 2532 adjustStr = true; 2533 } 2534 2535 if (entry->mTextNode == rngEndNode) { 2536 endOffset = rngEndOffset; 2537 adjustStr = true; 2538 } 2539 2540 if (adjustStr) { 2541 entry->mLength = endOffset - startOffset; 2542 str = Substring(str, startOffset, entry->mLength); 2543 } 2544 2545 offset += str.Length(); 2546 2547 if (aAllTextInBlock) { 2548 // Append the text node's string to the output string: 2549 if (!firstTextNode) { 2550 *aAllTextInBlock = str; 2551 } else { 2552 *aAllTextInBlock += str; 2553 } 2554 } 2555 2556 previousTextNode = content->AsText(); 2557 2558 if (!firstTextNode) { 2559 firstTextNode = content->AsText(); 2560 } 2561 } 2562 } 2563 2564 aFilteredIter.Next(); 2565 2566 if (TextServicesDocument::DidSkip(&aFilteredIter)) { 2567 break; 2568 } 2569 } 2570 2571 if (firstTextNode) { 2572 // Always leave the iterator pointing at the first 2573 // text node of the current block! 2574 aFilteredIter.PositionAt(firstTextNode); 2575 return aIteratorStatus; 2576 } 2577 2578 // If we never ran across a text node, the iterator 2579 // might have been pointing to something invalid to 2580 // begin with. 2581 return IteratorStatus::eDone; 2582 } 2583 2584 void TextServicesDocument::OffsetEntryArray::RemoveInvalidElements() { 2585 for (size_t i = 0; i < Length();) { 2586 if (ElementAt(i)->mIsValid) { 2587 i++; 2588 continue; 2589 } 2590 2591 RemoveElementAt(i); 2592 if (!mSelection.IsSet()) { 2593 continue; 2594 } 2595 if (mSelection.StartIndex() == i) { 2596 NS_ASSERTION(false, "What should we do in this case?"); 2597 mSelection.Reset(); 2598 } else if (mSelection.StartIndex() > i) { 2599 MOZ_DIAGNOSTIC_ASSERT(mSelection.StartIndex() - 1 < Length()); 2600 MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() - 1 < Length()); 2601 mSelection.SetIndexes(mSelection.StartIndex() - 1, 2602 mSelection.EndIndex() - 1); 2603 } else if (mSelection.EndIndex() >= i) { 2604 MOZ_DIAGNOSTIC_ASSERT(mSelection.EndIndex() - 1 < Length()); 2605 mSelection.SetIndexes(mSelection.StartIndex(), mSelection.EndIndex() - 1); 2606 } 2607 } 2608 } 2609 2610 nsresult TextServicesDocument::OffsetEntryArray::SplitElementAt( 2611 size_t aIndex, uint32_t aOffsetInTextNode) { 2612 OffsetEntry* leftEntry = ElementAt(aIndex).get(); 2613 MOZ_ASSERT(leftEntry); 2614 NS_ASSERTION((aOffsetInTextNode > 0), "aOffsetInTextNode == 0"); 2615 NS_ASSERTION((aOffsetInTextNode < leftEntry->mLength), 2616 "aOffsetInTextNode >= mLength"); 2617 2618 if (aOffsetInTextNode < 1 || aOffsetInTextNode >= leftEntry->mLength) { 2619 return NS_ERROR_FAILURE; 2620 } 2621 2622 const uint32_t oldLength = leftEntry->mLength - aOffsetInTextNode; 2623 2624 // XXX(Bug 1631371) Check if this should use a fallible operation as it 2625 // pretended earlier. 2626 UniquePtr<OffsetEntry>& rightEntry = *InsertElementAt( 2627 aIndex + 1, 2628 MakeUnique<OffsetEntry>(leftEntry->mTextNode, 2629 leftEntry->mOffsetInTextInBlock + oldLength, 2630 aOffsetInTextNode)); 2631 LockOffsetEntryArrayLengthInDebugBuild(observer, *this); 2632 leftEntry->mLength = oldLength; 2633 rightEntry->mOffsetInTextNode = leftEntry->mOffsetInTextNode + oldLength; 2634 2635 return NS_OK; 2636 } 2637 2638 Maybe<size_t> TextServicesDocument::OffsetEntryArray::FirstIndexOf( 2639 const Text& aTextNode) const { 2640 for (size_t i = 0; i < Length(); i++) { 2641 if (ElementAt(i)->mTextNode == &aTextNode) { 2642 return Some(i); 2643 } 2644 } 2645 return Nothing(); 2646 } 2647 2648 // Spellchecker code has this. See bug 211343 2649 #define IS_NBSP_CHAR(c) (((unsigned char)0xa0) == (c)) 2650 2651 Result<EditorDOMRangeInTexts, nsresult> 2652 TextServicesDocument::OffsetEntryArray::FindWordRange( 2653 nsAString& aAllTextInBlock, const EditorRawDOMPoint& aStartPointToScan) { 2654 MOZ_ASSERT(aStartPointToScan.IsInTextNode()); 2655 // It's assumed that aNode is a text node. The first thing 2656 // we do is get its index in the offset table so we can 2657 // calculate the dom point's string offset. 2658 Maybe<size_t> maybeEntryIndex = 2659 FirstIndexOf(*aStartPointToScan.ContainerAs<Text>()); 2660 if (NS_WARN_IF(maybeEntryIndex.isNothing())) { 2661 NS_WARNING( 2662 "TextServicesDocument::OffsetEntryArray::FirstIndexOf() didn't find " 2663 "entries"); 2664 return Err(NS_ERROR_FAILURE); 2665 } 2666 2667 // Next we map offset into a string offset. 2668 2669 const UniquePtr<OffsetEntry>& entry = ElementAt(*maybeEntryIndex); 2670 LockOffsetEntryArrayLengthInDebugBuild(observer, *this); 2671 uint32_t strOffset = entry->mOffsetInTextInBlock + 2672 aStartPointToScan.Offset() - entry->mOffsetInTextNode; 2673 2674 // Now we use the word breaker to find the beginning and end 2675 // of the word from our calculated string offset. 2676 2677 const char16_t* str = aAllTextInBlock.BeginReading(); 2678 MOZ_ASSERT(strOffset <= aAllTextInBlock.Length(), 2679 "The string offset shouldn't be greater than the string length!"); 2680 2681 intl::WordRange res = intl::WordBreaker::FindWord(aAllTextInBlock, strOffset); 2682 2683 // Strip out the NBSPs at the ends 2684 while (res.mBegin <= res.mEnd && IS_NBSP_CHAR(str[res.mBegin])) { 2685 res.mBegin++; 2686 } 2687 if (str[res.mEnd] == static_cast<char16_t>(0x20)) { 2688 uint32_t realEndWord = res.mEnd - 1; 2689 while (realEndWord > res.mBegin && IS_NBSP_CHAR(str[realEndWord])) { 2690 realEndWord--; 2691 } 2692 if (realEndWord < res.mEnd - 1) { 2693 res.mEnd = realEndWord + 1; 2694 } 2695 } 2696 2697 // Now that we have the string offsets for the beginning 2698 // and end of the word, run through the offset table and 2699 // convert them back into dom points. 2700 2701 EditorDOMPointInText wordStart, wordEnd; 2702 size_t lastIndex = Length() - 1; 2703 for (size_t i = 0; i <= lastIndex; i++) { 2704 // Check to see if res.mBegin is within the range covered 2705 // by this entry. Note that if res.mBegin is after the last 2706 // character covered by this entry, we will use the next 2707 // entry if there is one. 2708 const UniquePtr<OffsetEntry>& entry = ElementAt(i); 2709 LockOffsetEntryArrayLengthInDebugBuild(observer, *this); 2710 if (entry->mOffsetInTextInBlock <= res.mBegin && 2711 (res.mBegin < entry->EndOffsetInTextInBlock() || 2712 (res.mBegin == entry->EndOffsetInTextInBlock() && i == lastIndex))) { 2713 wordStart.Set(entry->mTextNode, entry->mOffsetInTextNode + res.mBegin - 2714 entry->mOffsetInTextInBlock); 2715 } 2716 2717 // Check to see if res.mEnd is within the range covered 2718 // by this entry. 2719 if (entry->mOffsetInTextInBlock <= res.mEnd && 2720 res.mEnd <= entry->EndOffsetInTextInBlock()) { 2721 if (res.mBegin == res.mEnd && 2722 res.mEnd == entry->EndOffsetInTextInBlock() && i != lastIndex) { 2723 // Wait for the next round so that we use the same entry 2724 // we did for aWordStartNode. 2725 continue; 2726 } 2727 2728 wordEnd.Set(entry->mTextNode, entry->mOffsetInTextNode + res.mEnd - 2729 entry->mOffsetInTextInBlock); 2730 break; 2731 } 2732 } 2733 2734 return EditorDOMRangeInTexts(wordStart, wordEnd); 2735 } 2736 2737 /** 2738 * nsIEditActionListener implementation: 2739 * Don't implement the behavior directly here. The methods won't be called 2740 * if the instance is created for inline spell checker created for editor. 2741 * If you need to listen a new edit action, you need to add similar 2742 * non-virtual method and you need to call it from EditorBase directly. 2743 */ 2744 2745 NS_IMETHODIMP 2746 TextServicesDocument::DidDeleteNode(nsINode* aChild, nsresult aResult) { 2747 if (NS_WARN_IF(NS_FAILED(aResult)) || NS_WARN_IF(!aChild) || 2748 !aChild->IsContent()) { 2749 return NS_OK; 2750 } 2751 DidDeleteContent(*aChild->AsContent()); 2752 return NS_OK; 2753 } 2754 2755 NS_IMETHODIMP TextServicesDocument::DidJoinContents( 2756 const EditorRawDOMPoint& aJoinedPoint, const nsINode* aRemovedNode) { 2757 if (MOZ_UNLIKELY(NS_WARN_IF(!aJoinedPoint.IsSetAndValid()) || 2758 NS_WARN_IF(!aRemovedNode->IsContent()))) { 2759 return NS_OK; 2760 } 2761 DidJoinContents(aJoinedPoint, *aRemovedNode->AsContent()); 2762 return NS_OK; 2763 } 2764 2765 NS_IMETHODIMP 2766 TextServicesDocument::DidInsertText(CharacterData* aTextNode, int32_t aOffset, 2767 const nsAString& aString, 2768 nsresult aResult) { 2769 return NS_OK; 2770 } 2771 2772 NS_IMETHODIMP 2773 TextServicesDocument::WillDeleteText(CharacterData* aTextNode, int32_t aOffset, 2774 int32_t aLength) { 2775 return NS_OK; 2776 } 2777 2778 NS_IMETHODIMP 2779 TextServicesDocument::WillDeleteRanges( 2780 const nsTArray<RefPtr<nsRange>>& aRangesToDelete) { 2781 return NS_OK; 2782 } 2783 2784 #undef LockOffsetEntryArrayLengthInDebugBuild 2785 2786 } // namespace mozilla