AutoClonedRangeArray.cpp (68197B)
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 "AutoClonedRangeArray.h" 7 8 #include "EditAction.h" 9 #include "EditorDOMPoint.h" // for EditorDOMPoint, EditorDOMRange, etc 10 #include "EditorForwards.h" // for CollectChildrenOptions 11 #include "HTMLEditUtils.h" // for HTMLEditUtils 12 #include "HTMLEditHelpers.h" // for SplitNodeResult 13 #include "TextEditor.h" // for TextEditor 14 #include "WSRunScanner.h" // for WSRunScanner 15 16 #include "mozilla/CaretAssociationHint.h" // for CaretAssociationHint 17 #include "mozilla/IntegerRange.h" // for IntegerRange 18 #include "mozilla/OwningNonNull.h" // for OwningNonNull 19 #include "mozilla/PresShell.h" // for PresShell 20 #include "mozilla/dom/CharacterDataBuffer.h" // for CharacterDataBuffer 21 #include "mozilla/dom/Document.h" // for dom::Document 22 #include "mozilla/dom/HTMLBRElement.h" // for dom HTMLBRElement 23 #include "mozilla/dom/Selection.h" // for dom::Selection 24 #include "mozilla/dom/Text.h" // for dom::Text 25 26 #include "gfxFontUtils.h" // for gfxFontUtils 27 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_* 28 #include "nsFrameSelection.h" // for nsFrameSelection 29 #include "nsIContent.h" // for nsIContent 30 #include "nsINode.h" // for nsINode 31 #include "nsRange.h" // for nsRange 32 33 namespace mozilla { 34 35 using namespace dom; 36 37 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; 38 using ReplaceOrVoidElementOption = HTMLEditUtils::ReplaceOrVoidElementOption; 39 40 /****************************************************************************** 41 * mozilla::AutoClonedRangeArray 42 *****************************************************************************/ 43 44 template AutoClonedRangeArray::AutoClonedRangeArray( 45 const EditorDOMRange& aRange); 46 template AutoClonedRangeArray::AutoClonedRangeArray( 47 const EditorRawDOMRange& aRange); 48 template AutoClonedRangeArray::AutoClonedRangeArray( 49 const EditorDOMPoint& aRange); 50 template AutoClonedRangeArray::AutoClonedRangeArray( 51 const EditorRawDOMPoint& aRange); 52 53 AutoClonedRangeArray::AutoClonedRangeArray(const AutoClonedRangeArray& aOther) 54 : mAnchorFocusRange(aOther.mAnchorFocusRange), 55 mDirection(aOther.mDirection) { 56 mRanges.SetCapacity(aOther.mRanges.Length()); 57 for (const OwningNonNull<nsRange>& range : aOther.mRanges) { 58 RefPtr<nsRange> clonedRange = range->CloneRange(); 59 mRanges.AppendElement(std::move(clonedRange)); 60 } 61 mAnchorFocusRange = aOther.mAnchorFocusRange; 62 } 63 64 template <typename PointType> 65 AutoClonedRangeArray::AutoClonedRangeArray( 66 const EditorDOMRangeBase<PointType>& aRange) { 67 MOZ_ASSERT(aRange.IsPositionedAndValid()); 68 RefPtr<nsRange> range = aRange.CreateRange(IgnoreErrors()); 69 if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) { 70 return; 71 } 72 mRanges.AppendElement(*range); 73 mAnchorFocusRange = std::move(range); 74 } 75 76 template <typename PT, typename CT> 77 AutoClonedRangeArray::AutoClonedRangeArray( 78 const EditorDOMPointBase<PT, CT>& aPoint) { 79 MOZ_ASSERT(aPoint.IsSetAndValid()); 80 RefPtr<nsRange> range = aPoint.CreateCollapsedRange(IgnoreErrors()); 81 if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) { 82 return; 83 } 84 mRanges.AppendElement(*range); 85 mAnchorFocusRange = std::move(range); 86 } 87 88 AutoClonedRangeArray::AutoClonedRangeArray(const nsRange& aRange) { 89 MOZ_ASSERT(aRange.IsPositioned()); 90 mRanges.AppendElement(aRange.CloneRange()); 91 mAnchorFocusRange = mRanges[0]; 92 } 93 94 // static 95 bool AutoClonedRangeArray::IsEditableRange(const dom::AbstractRange& aRange, 96 const Element& aEditingHost) { 97 // TODO: Perhaps, we should check whether the start/end boundaries are 98 // first/last point of non-editable element. 99 // See https://github.com/w3c/editing/issues/283#issuecomment-788654850 100 EditorRawDOMPoint atStart(aRange.StartRef()); 101 if (!atStart.IsInContentNode() || !HTMLEditUtils::IsSimplyEditableNode( 102 *atStart.ContainerAs<nsIContent>())) { 103 return false; 104 } 105 106 if (aRange.GetStartContainer() != aRange.GetEndContainer()) { 107 EditorRawDOMPoint atEnd(aRange.EndRef()); 108 if (!atEnd.IsInContentNode() || !HTMLEditUtils::IsSimplyEditableNode( 109 *atEnd.ContainerAs<nsIContent>())) { 110 return false; 111 } 112 113 // Now, both start and end points are editable, but if they are in 114 // different editing host, we cannot edit the range. 115 if (atStart.ContainerAs<nsIContent>() != atEnd.ContainerAs<nsIContent>() && 116 atStart.ContainerAs<nsIContent>()->GetEditingHost() != 117 atEnd.ContainerAs<nsIContent>()->GetEditingHost()) { 118 return false; 119 } 120 } 121 122 // HTMLEditor does not support modifying outside `<body>` element for now. 123 nsINode* commonAncestor = aRange.GetClosestCommonInclusiveAncestor(); 124 return commonAncestor && commonAncestor->IsContent() && 125 commonAncestor->IsInclusiveDescendantOf(&aEditingHost); 126 } 127 128 void AutoClonedRangeArray::EnsureOnlyEditableRanges( 129 const Element& aEditingHost) { 130 for (const size_t index : Reversed(IntegerRange(mRanges.Length()))) { 131 const OwningNonNull<nsRange>& range = mRanges[index]; 132 if (!AutoClonedRangeArray::IsEditableRange(range, aEditingHost)) { 133 mRanges.RemoveElementAt(index); 134 continue; 135 } 136 // Special handling for `inert` attribute. If anchor node is inert, the 137 // range should be treated as not editable. 138 nsIContent* anchorContent = 139 mDirection == eDirNext 140 ? nsIContent::FromNode(range->GetStartContainer()) 141 : nsIContent::FromNode(range->GetEndContainer()); 142 if (anchorContent && HTMLEditUtils::ContentIsInert(*anchorContent)) { 143 mRanges.RemoveElementAt(index); 144 continue; 145 } 146 // Additionally, if focus node is inert, the range should be collapsed to 147 // anchor node. 148 nsIContent* focusContent = 149 mDirection == eDirNext 150 ? nsIContent::FromNode(range->GetEndContainer()) 151 : nsIContent::FromNode(range->GetStartContainer()); 152 if (focusContent && focusContent != anchorContent && 153 HTMLEditUtils::ContentIsInert(*focusContent)) { 154 range->Collapse(mDirection == eDirNext); 155 } 156 } 157 mAnchorFocusRange = mRanges.IsEmpty() ? nullptr : mRanges.LastElement().get(); 158 } 159 160 bool AutoClonedRangeArray::AdjustRangesNotInReplacedNorVoidElements( 161 RangeInReplacedOrVoidElement aRangeInReplacedOrVoidElement, 162 const dom::Element& aEditingHost) { 163 bool adjusted = false; 164 for (const size_t index : Reversed(IntegerRange(mRanges.Length()))) { 165 const OwningNonNull<nsRange>& range = mRanges[index]; 166 // If the range is in a replaced element or a void element, we should adjust 167 // the range boundaries outside of the element. 168 if (Element* const replacedOrVoidElementAtStart = 169 HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement( 170 *range->StartRef().GetContainer()->AsContent(), 171 ReplaceOrVoidElementOption::LookForReplacedOrVoidElement)) { 172 adjusted = true; 173 if (MOZ_UNLIKELY(!replacedOrVoidElementAtStart->IsInclusiveDescendantOf( 174 &aEditingHost))) { 175 mRanges.RemoveElementAt(index); 176 continue; 177 } 178 nsIContent* const commonAncestorContent = 179 nsIContent::FromNode(range->GetClosestCommonInclusiveAncestor()); 180 if (commonAncestorContent && 181 commonAncestorContent->IsInclusiveDescendantOf( 182 replacedOrVoidElementAtStart)) { 183 // If the range is completely in a replaced element or a void element, 184 // let's treat that it's collapsed before the element or just delete the 185 // range. 186 if (aRangeInReplacedOrVoidElement == 187 RangeInReplacedOrVoidElement::Delete || 188 NS_WARN_IF(NS_FAILED(range->CollapseTo(RawRangeBoundary( 189 replacedOrVoidElementAtStart->GetParentNode(), 190 replacedOrVoidElementAtStart->GetPreviousSibling())))) || 191 MOZ_UNLIKELY( 192 !AutoClonedRangeArray::IsEditableRange(range, aEditingHost))) { 193 mRanges.RemoveElementAt(index); 194 continue; 195 } 196 adjusted = true; 197 } else { 198 // If the range does not end in the replaced element or the void 199 // element, let's treat that the range starts after the element. 200 if (NS_WARN_IF(NS_FAILED(range->SetStartAndEnd( 201 RawRangeBoundary(replacedOrVoidElementAtStart->GetParentNode(), 202 replacedOrVoidElementAtStart), 203 range->EndRef()))) || 204 MOZ_UNLIKELY( 205 !AutoClonedRangeArray::IsEditableRange(range, aEditingHost))) { 206 mRanges.RemoveElementAt(index); 207 continue; 208 } 209 } 210 } 211 if (!range->Collapsed() && 212 range->GetStartContainer() != range->GetEndContainer()) { 213 if (Element* const replacedOrVoidElementAtEnd = 214 HTMLEditUtils::GetInclusiveAncestorReplacedOrVoidElement( 215 *range->EndRef().GetContainer()->AsContent(), 216 ReplaceOrVoidElementOption::LookForReplacedOrVoidElement)) { 217 MOZ_ASSERT( 218 replacedOrVoidElementAtEnd->IsInclusiveDescendantOf(&aEditingHost)); 219 adjusted = true; 220 // If the range ends in a replaced element or a void element, let's 221 // treat that the range ends before the element. 222 if (NS_WARN_IF(NS_FAILED(range->SetStartAndEnd( 223 range->StartRef(), 224 RawRangeBoundary( 225 replacedOrVoidElementAtEnd->GetParentNode(), 226 replacedOrVoidElementAtEnd->GetPreviousSibling())))) || 227 MOZ_UNLIKELY( 228 !AutoClonedRangeArray::IsEditableRange(range, aEditingHost))) { 229 mRanges.RemoveElementAt(index); 230 continue; 231 } 232 } 233 } 234 } 235 return adjusted; 236 } 237 238 void AutoClonedRangeArray::EnsureRangesInTextNode(const Text& aTextNode) { 239 auto GetOffsetInTextNode = [&aTextNode](const nsINode* aNode, 240 uint32_t aOffset) -> uint32_t { 241 MOZ_DIAGNOSTIC_ASSERT(aNode); 242 if (aNode == &aTextNode) { 243 return aOffset; 244 } 245 const nsIContent* anonymousDivElement = aTextNode.GetParent(); 246 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement); 247 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement->IsHTMLElement(nsGkAtoms::div)); 248 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement->GetFirstChild() == &aTextNode); 249 if (aNode == anonymousDivElement && aOffset == 0u) { 250 return 0u; // Point before the text node so that use start of the text. 251 } 252 MOZ_DIAGNOSTIC_ASSERT(aNode->IsInclusiveDescendantOf(anonymousDivElement)); 253 // Point after the text node so that use end of the text. 254 return aTextNode.TextDataLength(); 255 }; 256 for (const OwningNonNull<nsRange>& range : mRanges) { 257 if (MOZ_LIKELY(range->GetStartContainer() == &aTextNode && 258 range->GetEndContainer() == &aTextNode)) { 259 continue; 260 } 261 range->SetStartAndEnd( 262 const_cast<Text*>(&aTextNode), 263 GetOffsetInTextNode(range->GetStartContainer(), range->StartOffset()), 264 const_cast<Text*>(&aTextNode), 265 GetOffsetInTextNode(range->GetEndContainer(), range->EndOffset())); 266 } 267 268 if (MOZ_UNLIKELY(mRanges.Length() >= 2)) { 269 // For avoiding to handle same things in same range, we should drop and 270 // merge unnecessary ranges. Note that the ranges never overlap 271 // because selection ranges are not allowed it so that we need to check only 272 // end offset vs start offset of next one. 273 for (const size_t i : Reversed(IntegerRange(mRanges.Length() - 1u))) { 274 MOZ_ASSERT(mRanges[i]->EndOffset() < mRanges[i + 1]->StartOffset()); 275 // XXX Should we delete collapsed range unless the index is 0? Without 276 // Selection API, such situation cannot happen so that `TextEditor` 277 // may behave unexpectedly. 278 if (MOZ_UNLIKELY(mRanges[i]->EndOffset() >= 279 mRanges[i + 1]->StartOffset())) { 280 const uint32_t newEndOffset = mRanges[i + 1]->EndOffset(); 281 mRanges.RemoveElementAt(i + 1); 282 if (MOZ_UNLIKELY(NS_WARN_IF(newEndOffset > mRanges[i]->EndOffset()))) { 283 // So, this case shouldn't happen. 284 mRanges[i]->SetStartAndEnd( 285 const_cast<Text*>(&aTextNode), mRanges[i]->StartOffset(), 286 const_cast<Text*>(&aTextNode), newEndOffset); 287 } 288 } 289 } 290 } 291 } 292 293 Result<bool, nsresult> 294 AutoClonedRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent( 295 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount, 296 IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent) { 297 if (IsCollapsed()) { 298 return false; 299 } 300 301 switch (aDirectionAndAmount) { 302 case nsIEditor::eNext: 303 case nsIEditor::eNextWord: 304 case nsIEditor::ePrevious: 305 case nsIEditor::ePreviousWord: 306 break; 307 default: 308 return false; 309 } 310 311 bool changed = false; 312 for (const OwningNonNull<nsRange>& range : mRanges) { 313 MOZ_ASSERT(!range->IsInAnySelection(), 314 "Changing range in selection may cause running script"); 315 Result<bool, nsresult> result = 316 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent( 317 {WSRunScanner::Option::OnlyEditableNodes}, range); 318 if (result.isErr()) { 319 NS_WARNING( 320 "WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() " 321 "failed"); 322 return Err(result.inspectErr()); 323 } 324 changed |= result.inspect(); 325 } 326 327 if (mRanges.Length() == 1 && aIfSelectingOnlyOneAtomicContent == 328 IfSelectingOnlyOneAtomicContent::Collapse) { 329 MOZ_ASSERT(mRanges[0].get() == mAnchorFocusRange.get()); 330 if (mAnchorFocusRange->GetStartContainer() == 331 mAnchorFocusRange->GetEndContainer() && 332 mAnchorFocusRange->GetChildAtStartOffset() && 333 mAnchorFocusRange->StartRef().GetNextSiblingOfChildAtOffset() == 334 mAnchorFocusRange->GetChildAtEndOffset()) { 335 mAnchorFocusRange->Collapse(aDirectionAndAmount == nsIEditor::eNext || 336 aDirectionAndAmount == nsIEditor::eNextWord); 337 changed = true; 338 } 339 } 340 341 return changed; 342 } 343 344 // static 345 void AutoClonedRangeArray:: 346 UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement( 347 EditorDOMPoint& aStartPoint, EditorDOMPoint& aEndPoint, 348 const Element& aEditingHost) { 349 // FYI: This was moved from 350 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6743 351 352 // MOOSE major hack: 353 // The GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() and 354 // GetPointAfterFollowingLineBreakOrAtFollowingBlock() don't really do the 355 // right thing for collapsed ranges inside block elements that contain nothing 356 // but a solo <br>. It's easier/ to put a workaround here than to revamp 357 // them. :-( 358 if (aStartPoint != aEndPoint) { 359 return; 360 } 361 362 if (!aStartPoint.IsInContentNode()) { 363 return; 364 } 365 366 // XXX Perhaps, this should be more careful. This may not select only one 367 // node because this just check whether the block is empty or not, 368 // and may not select in non-editable block. However, for inline 369 // editing host case, it's right to look for block element without 370 // editable state check. Now, this method is used for preparation for 371 // other things. So, cannot write test for this method behavior. 372 // So, perhaps, we should get rid of this method and each caller should 373 // handle its job better. 374 Element* const maybeNonEditableBlockElement = 375 HTMLEditUtils::GetInclusiveAncestorElement( 376 *aStartPoint.ContainerAs<nsIContent>(), 377 HTMLEditUtils::ClosestBlockElement, 378 BlockInlineCheck::UseComputedDisplayStyle); 379 if (!maybeNonEditableBlockElement) { 380 return; 381 } 382 383 // Make sure we don't go higher than our root element in the content tree 384 if (aEditingHost.IsInclusiveDescendantOf(maybeNonEditableBlockElement)) { 385 return; 386 } 387 388 if (HTMLEditUtils::IsEmptyNode( 389 *maybeNonEditableBlockElement, 390 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) { 391 aStartPoint.Set(maybeNonEditableBlockElement, 0u); 392 aEndPoint.SetToEndOf(maybeNonEditableBlockElement); 393 } 394 } 395 396 /** 397 * Get the point before the line containing aPointInLine. 398 * 399 * @return If the line starts after a `<br>` element, returns next 400 * sibling of the `<br>` element. 401 * If the line is first line of a block, returns point of 402 * the block. 403 * NOTE: The result may be point of editing host. I.e., the container may be 404 * outside of editing host. 405 */ 406 MOZ_NEVER_INLINE_DEBUG static EditorDOMPoint 407 GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock( 408 const EditorDOMPoint& aPointInLine, EditSubAction aEditSubAction, 409 BlockInlineCheck aBlockInlineCheck, const Element& aAncestorLimiter) { 410 // FYI: This was moved from 411 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6447 412 413 if (NS_WARN_IF(!aPointInLine.IsSet())) { 414 return EditorDOMPoint(); 415 } 416 417 EditorDOMPoint point(aPointInLine); 418 // Start scanning from the container node if aPoint is in a text node. 419 // XXX Perhaps, IsInDataNode() must be expected. 420 if (point.IsInTextNode()) { 421 if (!point.GetContainer()->GetParentNode()) { 422 // Okay, can't promote any further 423 // XXX Why don't we return start of the text node? 424 return point; 425 } 426 // If there is a preformatted linefeed in the text node, let's return 427 // the point after it. 428 EditorDOMPoint atLastPreformattedNewLine = 429 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode<EditorDOMPoint>( 430 point); 431 if (atLastPreformattedNewLine.IsSet()) { 432 return atLastPreformattedNewLine.NextPoint(); 433 } 434 point.Set(point.GetContainer()); 435 } 436 437 // Look back through any further inline nodes that aren't across a <br> 438 // from us, and that are enclosed in the same block. 439 // I.e., looking for start of current hard line. 440 constexpr HTMLEditUtils::WalkTreeOptions 441 ignoreNonEditableNodeAndStopAtBlockBoundary{ 442 HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode, 443 HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary}; 444 for (nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( 445 point, ignoreNonEditableNodeAndStopAtBlockBoundary, 446 aBlockInlineCheck, &aAncestorLimiter); 447 previousEditableContent && previousEditableContent->GetParentNode() && 448 !HTMLEditUtils::IsVisibleBRElement(*previousEditableContent) && 449 !HTMLEditUtils::IsBlockElement(*previousEditableContent, 450 aBlockInlineCheck); 451 previousEditableContent = HTMLEditUtils::GetPreviousContent( 452 point, ignoreNonEditableNodeAndStopAtBlockBoundary, 453 aBlockInlineCheck, &aAncestorLimiter)) { 454 EditorDOMPoint atLastPreformattedNewLine = 455 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode<EditorDOMPoint>( 456 EditorRawDOMPoint::AtEndOf(*previousEditableContent)); 457 if (atLastPreformattedNewLine.IsSet()) { 458 return atLastPreformattedNewLine.NextPoint(); 459 } 460 point.Set(previousEditableContent); 461 } 462 463 // Finding the real start for this point unless current line starts after 464 // <br> element. Look up the tree for as long as we are the first node in 465 // the container (typically, start of nearest block ancestor), and as long 466 // as we haven't hit the body node. 467 for (nsIContent* nearContent = HTMLEditUtils::GetPreviousContent( 468 point, ignoreNonEditableNodeAndStopAtBlockBoundary, 469 aBlockInlineCheck, &aAncestorLimiter); 470 !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) && 471 point.GetContainerParent(); 472 nearContent = HTMLEditUtils::GetPreviousContent( 473 point, ignoreNonEditableNodeAndStopAtBlockBoundary, 474 aBlockInlineCheck, &aAncestorLimiter)) { 475 // Don't keep looking up if we have found a blockquote element to act on 476 // when we handle outdent. 477 // XXX Sounds like this is hacky. If possible, it should be check in 478 // outdent handler for consistency between edit sub-actions. 479 // We should check Chromium's behavior of outdent when Selection 480 // starts from `<blockquote>` and starts from first child of 481 // `<blockquote>`. 482 if (aEditSubAction == EditSubAction::eOutdent && 483 point.IsContainerHTMLElement(nsGkAtoms::blockquote)) { 484 break; 485 } 486 487 // Don't walk past the editable section. Note that we need to check 488 // before walking up to a parent because we need to return the parent 489 // object, so the parent itself might not be in the editable area, but 490 // it's OK if we're not performing a block-level action. 491 bool blockLevelAction = 492 aEditSubAction == EditSubAction::eIndent || 493 aEditSubAction == EditSubAction::eOutdent || 494 aEditSubAction == EditSubAction::eSetOrClearAlignment || 495 aEditSubAction == EditSubAction::eCreateOrRemoveBlock || 496 aEditSubAction == EditSubAction::eFormatBlockForHTMLCommand; 497 // XXX So, does this check whether the container is removable or not? It 498 // seems that here can be rewritten as obviously what here tries to 499 // check. 500 if (!point.GetContainerParent()->IsInclusiveDescendantOf( 501 &aAncestorLimiter) && 502 (blockLevelAction || 503 !point.GetContainer()->IsInclusiveDescendantOf(&aAncestorLimiter))) { 504 break; 505 } 506 507 // If we're formatting a block, we should reformat first ancestor format 508 // block. 509 if (aEditSubAction == EditSubAction::eFormatBlockForHTMLCommand && 510 point.IsContainerElement() && 511 HTMLEditUtils::IsFormatElementForFormatBlockCommand( 512 *point.ContainerAs<Element>())) { 513 point.Set(point.GetContainer()); 514 break; 515 } 516 517 point.Set(point.GetContainer()); 518 } 519 return point; 520 } 521 522 /** 523 * Get the point after the following line break or the block which breaks the 524 * line containing aPointInLine. 525 * 526 * @return If the line ends with a visible `<br>` element, returns 527 * the point after the `<br>` element. 528 * If the line ends with a preformatted linefeed, returns 529 * the point after the linefeed unless it's an invisible 530 * line break immediately before a block boundary. 531 * If the line ends with a block boundary, returns the 532 * point of the block. 533 */ 534 MOZ_NEVER_INLINE_DEBUG static EditorDOMPoint 535 GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock( 536 const EditorDOMPoint& aPointInLine, EditSubAction aEditSubAction, 537 BlockInlineCheck aBlockInlineCheck, const Element& aAncestorLimiter) { 538 // FYI: This was moved from 539 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6541 540 541 if (NS_WARN_IF(!aPointInLine.IsSet())) { 542 return EditorDOMPoint(); 543 } 544 545 EditorDOMPoint point(aPointInLine); 546 // Start scanning from the container node if aPoint is in a text node. 547 // XXX Perhaps, IsInDataNode() must be expected. 548 if (point.IsInTextNode()) { 549 if (NS_WARN_IF(!point.GetContainer()->GetParentNode())) { 550 // Okay, can't promote any further 551 // XXX Why don't we return end of the text node? 552 return point; 553 } 554 EditorDOMPoint atNextPreformattedNewLine = 555 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode< 556 EditorDOMPoint>(point); 557 if (atNextPreformattedNewLine.IsSet()) { 558 // If the linefeed is last character of the text node, it may be 559 // invisible if it's immediately before a block boundary. In such 560 // case, we should return the block boundary. 561 Element* maybeNonEditableBlockElement = nullptr; 562 if (HTMLEditUtils::IsInvisiblePreformattedNewLine( 563 atNextPreformattedNewLine, &maybeNonEditableBlockElement) && 564 maybeNonEditableBlockElement) { 565 // If the block is a parent of the editing host, let's return end 566 // of editing host. 567 if (maybeNonEditableBlockElement == &aAncestorLimiter || 568 !maybeNonEditableBlockElement->IsInclusiveDescendantOf( 569 &aAncestorLimiter)) { 570 return EditorDOMPoint::AtEndOf(aAncestorLimiter); 571 } 572 // If it's invisible because of parent block boundary, return end 573 // of the block. Otherwise, i.e., it's followed by a child block, 574 // returns the point of the child block. 575 if (atNextPreformattedNewLine.ContainerAs<Text>() 576 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement)) { 577 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement); 578 } 579 return EditorDOMPoint(maybeNonEditableBlockElement); 580 } 581 // Otherwise, return the point after the preformatted linefeed. 582 return atNextPreformattedNewLine.NextPoint(); 583 } 584 // want to be after the text node 585 point.SetAfter(point.GetContainer()); 586 NS_WARNING_ASSERTION(point.IsSet(), "Failed to set to after the text node"); 587 } 588 589 // Look ahead through any further inline nodes that aren't across a <br> from 590 // us, and that are enclosed in the same block. 591 // XXX Currently, we stop block-extending when finding visible <br> element. 592 // This might be different from "block-extend" of execCommand spec. 593 // However, the spec is really unclear. 594 // XXX Probably, scanning only editable nodes is wrong for 595 // EditSubAction::eCreateOrRemoveBlock and 596 // EditSubAction::eFormatBlockForHTMLCommand because it might be better to 597 // wrap existing inline elements even if it's non-editable. For example, 598 // following examples with insertParagraph causes different result: 599 // * <div contenteditable>foo[]<b contenteditable="false">bar</b></div> 600 // * <div contenteditable>foo[]<b>bar</b></div> 601 // * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div> 602 // Only in the first case, after the caret position isn't wrapped with 603 // new <div> element. 604 constexpr HTMLEditUtils::WalkTreeOptions 605 ignoreNonEditableNodeAndStopAtBlockBoundary{ 606 HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode, 607 HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary}; 608 for (nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( 609 point, ignoreNonEditableNodeAndStopAtBlockBoundary, 610 aBlockInlineCheck, &aAncestorLimiter); 611 nextEditableContent && 612 !HTMLEditUtils::IsBlockElement(*nextEditableContent, 613 aBlockInlineCheck) && 614 nextEditableContent->GetParent(); 615 nextEditableContent = HTMLEditUtils::GetNextContent( 616 point, ignoreNonEditableNodeAndStopAtBlockBoundary, 617 aBlockInlineCheck, &aAncestorLimiter)) { 618 EditorDOMPoint atFirstPreformattedNewLine = 619 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode< 620 EditorDOMPoint>(EditorRawDOMPoint(nextEditableContent, 0)); 621 if (atFirstPreformattedNewLine.IsSet()) { 622 // If the linefeed is last character of the text node, it may be 623 // invisible if it's immediately before a block boundary. In such 624 // case, we should return the block boundary. 625 Element* maybeNonEditableBlockElement = nullptr; 626 if (HTMLEditUtils::IsInvisiblePreformattedNewLine( 627 atFirstPreformattedNewLine, &maybeNonEditableBlockElement) && 628 maybeNonEditableBlockElement) { 629 // If the block is a parent of the editing host, let's return end 630 // of editing host. 631 if (maybeNonEditableBlockElement == &aAncestorLimiter || 632 !maybeNonEditableBlockElement->IsInclusiveDescendantOf( 633 &aAncestorLimiter)) { 634 return EditorDOMPoint::AtEndOf(aAncestorLimiter); 635 } 636 // If it's invisible because of parent block boundary, return end 637 // of the block. Otherwise, i.e., it's followed by a child block, 638 // returns the point of the child block. 639 if (atFirstPreformattedNewLine.ContainerAs<Text>() 640 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement)) { 641 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement); 642 } 643 return EditorDOMPoint(maybeNonEditableBlockElement); 644 } 645 // Otherwise, return the point after the preformatted linefeed. 646 return atFirstPreformattedNewLine.NextPoint(); 647 } 648 point.SetAfter(nextEditableContent); 649 if (NS_WARN_IF(!point.IsSet())) { 650 break; 651 } 652 if (HTMLEditUtils::IsVisibleBRElement(*nextEditableContent)) { 653 break; 654 } 655 } 656 657 // Finding the real end for this point unless current line ends with a <br> 658 // element. Look up the tree for as long as we are the last node in the 659 // container (typically, block node), and as long as we haven't hit the body 660 // node. 661 for (nsIContent* nearContent = HTMLEditUtils::GetNextContent( 662 point, ignoreNonEditableNodeAndStopAtBlockBoundary, 663 aBlockInlineCheck, &aAncestorLimiter); 664 !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) && 665 point.GetContainerParent(); 666 nearContent = HTMLEditUtils::GetNextContent( 667 point, ignoreNonEditableNodeAndStopAtBlockBoundary, 668 aBlockInlineCheck, &aAncestorLimiter)) { 669 // Don't walk past the editable section. Note that we need to check before 670 // walking up to a parent because we need to return the parent object, so 671 // the parent itself might not be in the editable area, but it's OK. 672 // XXX Maybe returning parent of editing host is really error prone since 673 // everybody need to check whether the end point is in editing host 674 // when they touch there. 675 if (!point.GetContainer()->IsInclusiveDescendantOf(&aAncestorLimiter) && 676 !point.GetContainerParent()->IsInclusiveDescendantOf( 677 &aAncestorLimiter)) { 678 break; 679 } 680 681 // If we're formatting a block, we should reformat first ancestor format 682 // block. 683 if (aEditSubAction == EditSubAction::eFormatBlockForHTMLCommand && 684 point.IsContainerElement() && 685 HTMLEditUtils::IsFormatElementForFormatBlockCommand( 686 *point.ContainerAs<Element>())) { 687 point.SetAfter(point.GetContainer()); 688 break; 689 } 690 691 point.SetAfter(point.GetContainer()); 692 if (NS_WARN_IF(!point.IsSet())) { 693 break; 694 } 695 } 696 return point; 697 } 698 699 void AutoClonedRangeArray::ExtendRangesToWrapLines( 700 EditSubAction aEditSubAction, BlockInlineCheck aBlockInlineCheck, 701 const Element& aAncestorLimiter) { 702 // FYI: This is originated in 703 // https://searchfox.org/mozilla-central/rev/1739f1301d658c9bff544a0a095ab11fca2e549d/editor/libeditor/HTMLEditSubActionHandler.cpp#6712 704 705 bool removeSomeRanges = false; 706 for (const OwningNonNull<nsRange>& range : mRanges) { 707 // Remove non-positioned ranges. 708 if (MOZ_UNLIKELY(!range->IsPositioned())) { 709 removeSomeRanges = true; 710 continue; 711 } 712 // If the range is native anonymous subtrees, we must meet a bug of 713 // `Selection` so that we need to hack here. 714 if (MOZ_UNLIKELY(range->GetStartContainer()->IsInNativeAnonymousSubtree() || 715 range->GetEndContainer()->IsInNativeAnonymousSubtree())) { 716 EditorRawDOMRange rawRange(range); 717 if (!rawRange.EnsureNotInNativeAnonymousSubtree()) { 718 range->Reset(); 719 removeSomeRanges = true; 720 continue; 721 } 722 if (NS_FAILED( 723 range->SetStartAndEnd(rawRange.StartRef().ToRawRangeBoundary(), 724 rawRange.EndRef().ToRawRangeBoundary())) || 725 MOZ_UNLIKELY(!range->IsPositioned())) { 726 range->Reset(); 727 removeSomeRanges = true; 728 continue; 729 } 730 } 731 // Finally, extend the range. 732 if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries( 733 range, aEditSubAction, aBlockInlineCheck, aAncestorLimiter))) { 734 // If we failed to extend the range, we should use the original range 735 // as-is unless the range is broken at setting the range. 736 if (NS_WARN_IF(!range->IsPositioned())) { 737 removeSomeRanges = true; 738 } 739 } 740 } 741 if (removeSomeRanges) { 742 for (const size_t i : Reversed(IntegerRange(mRanges.Length()))) { 743 if (!mRanges[i]->IsPositioned()) { 744 mRanges.RemoveElementAt(i); 745 } 746 } 747 if (!mAnchorFocusRange || !mAnchorFocusRange->IsPositioned()) { 748 if (mRanges.IsEmpty()) { 749 mAnchorFocusRange = nullptr; 750 } else { 751 mAnchorFocusRange = mRanges.LastElement(); 752 } 753 } 754 } 755 } 756 757 // static 758 nsresult 759 AutoClonedRangeArray::ExtendRangeToWrapStartAndEndLinesContainingBoundaries( 760 nsRange& aRange, EditSubAction aEditSubAction, 761 BlockInlineCheck aBlockInlineCheck, const Element& aEditingHost) { 762 MOZ_DIAGNOSTIC_ASSERT( 763 !EditorRawDOMPoint(aRange.StartRef()).IsInNativeAnonymousSubtree()); 764 MOZ_DIAGNOSTIC_ASSERT( 765 !EditorRawDOMPoint(aRange.EndRef()).IsInNativeAnonymousSubtree()); 766 767 if (NS_WARN_IF(!aRange.IsPositioned())) { 768 return NS_ERROR_INVALID_ARG; 769 } 770 771 EditorDOMPoint startPoint(aRange.StartRef()), endPoint(aRange.EndRef()); 772 773 // If we're joining blocks, we call this for selecting a line to move. 774 // Therefore, we don't want to select the ancestor blocks in this case 775 // even if they are empty. 776 if (aEditSubAction != EditSubAction::eMergeBlockContents) { 777 AutoClonedRangeArray:: 778 UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement( 779 startPoint, endPoint, aEditingHost); 780 } 781 782 // Make a new adjusted range to represent the appropriate block content. 783 // This is tricky. The basic idea is to push out the range endpoints to 784 // truly enclose the blocks that we will affect. 785 786 // Make sure that the new range ends up to be in the editable section. 787 // XXX Looks like that this check wastes the time. Perhaps, we should 788 // implement a method which checks both two DOM points in the editor 789 // root. 790 791 startPoint = 792 GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock( 793 startPoint, aEditSubAction, aBlockInlineCheck, aEditingHost); 794 // XXX GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() may 795 // return point of editing host. Perhaps, we should change it and stop 796 // checking it here since this check may be expensive. 797 // XXX If the container is an element in the editing host but it points end of 798 // the container, this returns nullptr. Is it intentional? 799 if (!startPoint.GetChildOrContainerIfDataNode() || 800 !startPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf( 801 &aEditingHost)) { 802 return NS_ERROR_FAILURE; 803 } 804 endPoint = GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock( 805 endPoint, aEditSubAction, aBlockInlineCheck, aEditingHost); 806 const EditorDOMPoint lastRawPoint = 807 endPoint.IsStartOfContainer() ? endPoint : endPoint.PreviousPoint(); 808 // XXX GetPointAfterFollowingLineBreakOrAtFollowingBlock() may return point of 809 // editing host. Perhaps, we should change it and stop checking it here 810 // since this check may be expensive. 811 // XXX If the container is an element in the editing host but it points end of 812 // the container, this returns nullptr. Is it intentional? 813 if (!lastRawPoint.GetChildOrContainerIfDataNode() || 814 !lastRawPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf( 815 &aEditingHost)) { 816 return NS_ERROR_FAILURE; 817 } 818 819 nsresult rv = aRange.SetStartAndEnd(startPoint.ToRawRangeBoundary(), 820 endPoint.ToRawRangeBoundary()); 821 if (NS_FAILED(rv)) { 822 return NS_ERROR_FAILURE; 823 } 824 return NS_OK; 825 } 826 827 Result<EditorDOMPoint, nsresult> AutoClonedRangeArray:: 828 SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries( 829 HTMLEditor& aHTMLEditor, BlockInlineCheck aBlockInlineCheck, 830 const Element& aEditingHost, 831 const nsIContent* aAncestorLimiter /* = nullptr */) { 832 // FYI: The following code is originated in 833 // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#6971 834 835 // Split text nodes. This is necessary, since given ranges may end in text 836 // nodes in case where part of a pre-formatted elements needs to be moved. 837 EditorDOMPoint pointToPutCaret; 838 IgnoredErrorResult ignoredError; 839 for (const OwningNonNull<nsRange>& range : mRanges) { 840 EditorDOMPoint atEnd(range->EndRef()); 841 if (NS_WARN_IF(!atEnd.IsSet()) || !atEnd.IsInTextNode() || 842 atEnd.GetContainer() == aAncestorLimiter) { 843 continue; 844 } 845 846 if (!atEnd.IsStartOfContainer() && !atEnd.IsEndOfContainer()) { 847 // Split the text node. 848 Result<SplitNodeResult, nsresult> splitAtEndResult = 849 aHTMLEditor.SplitNodeWithTransaction(atEnd); 850 if (MOZ_UNLIKELY(splitAtEndResult.isErr())) { 851 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 852 return splitAtEndResult.propagateErr(); 853 } 854 SplitNodeResult unwrappedSplitAtEndResult = splitAtEndResult.unwrap(); 855 unwrappedSplitAtEndResult.MoveCaretPointTo( 856 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 857 858 // Correct the range. 859 // The new end parent becomes the parent node of the text. 860 MOZ_ASSERT(!range->IsInAnySelection()); 861 range->SetEnd(unwrappedSplitAtEndResult.AtNextContent<EditorRawDOMPoint>() 862 .ToRawRangeBoundary(), 863 ignoredError); 864 NS_WARNING_ASSERTION(!ignoredError.Failed(), 865 "nsRange::SetEnd() failed, but ignored"); 866 ignoredError.SuppressException(); 867 } 868 } 869 870 // FYI: The following code is originated in 871 // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#7023 872 AutoTArray<OwningNonNull<RangeItem>, 8> rangeItemArray; 873 rangeItemArray.AppendElements(mRanges.Length()); 874 875 // First register ranges for special editor gravity 876 Maybe<size_t> anchorFocusRangeIndex; 877 for (const size_t index : IntegerRange(rangeItemArray.Length())) { 878 rangeItemArray[index] = new RangeItem(); 879 rangeItemArray[index]->StoreRange(*mRanges[index]); 880 aHTMLEditor.RangeUpdaterRef().RegisterRangeItem(*rangeItemArray[index]); 881 if (mRanges[index] == mAnchorFocusRange) { 882 anchorFocusRangeIndex = Some(index); 883 } 884 } 885 // TODO: We should keep the array, and just update the ranges. 886 mRanges.Clear(); 887 mAnchorFocusRange = nullptr; 888 // Now bust up inlines. 889 nsresult rv = NS_OK; 890 for (const OwningNonNull<RangeItem>& item : Reversed(rangeItemArray)) { 891 // MOZ_KnownLive because 'rangeItemArray' is guaranteed to keep it alive. 892 Result<EditorDOMPoint, nsresult> splitParentsResult = 893 aHTMLEditor.SplitInlineAncestorsAtRangeBoundaries( 894 MOZ_KnownLive(*item), aBlockInlineCheck, aEditingHost, 895 aAncestorLimiter); 896 if (MOZ_UNLIKELY(splitParentsResult.isErr())) { 897 NS_WARNING("HTMLEditor::SplitInlineAncestorsAtRangeBoundaries() failed"); 898 rv = splitParentsResult.unwrapErr(); 899 break; 900 } 901 if (splitParentsResult.inspect().IsSet()) { 902 pointToPutCaret = splitParentsResult.unwrap(); 903 } 904 } 905 // Then unregister the ranges 906 for (const size_t index : IntegerRange(rangeItemArray.Length())) { 907 aHTMLEditor.RangeUpdaterRef().DropRangeItem(rangeItemArray[index]); 908 RefPtr<nsRange> range = rangeItemArray[index]->GetRange(); 909 if (range && range->IsPositioned()) { 910 if (anchorFocusRangeIndex.isSome() && index == *anchorFocusRangeIndex) { 911 mAnchorFocusRange = range; 912 } 913 mRanges.AppendElement(std::move(range)); 914 } 915 } 916 if (!mAnchorFocusRange && !mRanges.IsEmpty()) { 917 mAnchorFocusRange = mRanges.LastElement(); 918 } 919 920 // XXX Why do we ignore the other errors here?? 921 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 922 return Err(NS_ERROR_EDITOR_DESTROYED); 923 } 924 return pointToPutCaret; 925 } 926 927 nsresult AutoClonedRangeArray::CollectEditTargetNodes( 928 const HTMLEditor& aHTMLEditor, 929 nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents, 930 EditSubAction aEditSubAction, 931 CollectNonEditableNodes aCollectNonEditableNodes) const { 932 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable()); 933 934 // FYI: This was moved from 935 // https://searchfox.org/mozilla-central/rev/4bce7d85ba4796dd03c5dcc7cfe8eee0e4c07b3b/editor/libeditor/HTMLEditSubActionHandler.cpp#7060 936 937 // Gather up a list of all the nodes 938 for (const OwningNonNull<nsRange>& range : mRanges) { 939 DOMSubtreeIterator iter; 940 nsresult rv = iter.Init(*range); 941 if (NS_FAILED(rv)) { 942 NS_WARNING("DOMSubtreeIterator::Init() failed"); 943 return rv; 944 } 945 if (aOutArrayOfContents.IsEmpty()) { 946 iter.AppendAllNodesToArray(aOutArrayOfContents); 947 } else { 948 AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfTopChildren; 949 iter.AppendNodesToArray( 950 +[](nsINode& aNode, void* aArray) -> bool { 951 MOZ_ASSERT(aArray); 952 return !static_cast<nsTArray<OwningNonNull<nsIContent>>*>(aArray) 953 ->Contains(&aNode); 954 }, 955 arrayOfTopChildren, &aOutArrayOfContents); 956 aOutArrayOfContents.AppendElements(std::move(arrayOfTopChildren)); 957 } 958 if (aCollectNonEditableNodes == CollectNonEditableNodes::No) { 959 for (const size_t i : 960 Reversed(IntegerRange(aOutArrayOfContents.Length()))) { 961 if (!EditorUtils::IsEditableContent(aOutArrayOfContents[i], 962 EditorUtils::EditorType::HTML)) { 963 aOutArrayOfContents.RemoveElementAt(i); 964 } 965 } 966 } 967 } 968 969 switch (aEditSubAction) { 970 case EditSubAction::eCreateOrRemoveBlock: 971 case EditSubAction::eFormatBlockForHTMLCommand: { 972 // Certain operations should not act on li's and td's, but rather inside 973 // them. Alter the list as needed. 974 CollectChildrenOptions options = { 975 CollectChildrenOption::CollectListChildren, 976 CollectChildrenOption::CollectTableChildren}; 977 if (aCollectNonEditableNodes == CollectNonEditableNodes::No) { 978 options += CollectChildrenOption::IgnoreNonEditableChildren; 979 } 980 if (aEditSubAction == EditSubAction::eCreateOrRemoveBlock) { 981 for (const size_t index : 982 Reversed(IntegerRange(aOutArrayOfContents.Length()))) { 983 const OwningNonNull<nsIContent> content = aOutArrayOfContents[index]; 984 if (HTMLEditUtils::IsListItemElement(*content)) { 985 aOutArrayOfContents.RemoveElementAt(index); 986 HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index, 987 options); 988 } 989 } 990 } else { 991 // <dd> and <dt> are format blocks. Therefore, we should not handle 992 // their children directly. They should be replaced with new format 993 // block. 994 MOZ_ASSERT( 995 HTMLEditUtils::IsFormatTagForFormatBlockCommand(*nsGkAtoms::dt)); 996 MOZ_ASSERT( 997 HTMLEditUtils::IsFormatTagForFormatBlockCommand(*nsGkAtoms::dd)); 998 for (const size_t index : 999 Reversed(IntegerRange(aOutArrayOfContents.Length()))) { 1000 const OwningNonNull<nsIContent> content = aOutArrayOfContents[index]; 1001 MOZ_ASSERT_IF(HTMLEditUtils::IsListItemElement(*content), 1002 content->IsAnyOfHTMLElements( 1003 nsGkAtoms::dd, nsGkAtoms::dt, nsGkAtoms::li)); 1004 if (content->IsHTMLElement(nsGkAtoms::li)) { 1005 aOutArrayOfContents.RemoveElementAt(index); 1006 HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index, 1007 options); 1008 } 1009 } 1010 } 1011 // Empty text node shouldn't be selected if unnecessary 1012 for (const size_t index : 1013 Reversed(IntegerRange(aOutArrayOfContents.Length()))) { 1014 if (const Text* text = aOutArrayOfContents[index]->GetAsText()) { 1015 // Don't select empty text except to empty block 1016 if (!HTMLEditUtils::IsVisibleTextNode(*text)) { 1017 aOutArrayOfContents.RemoveElementAt(index); 1018 } 1019 } 1020 } 1021 break; 1022 } 1023 case EditSubAction::eCreateOrChangeList: { 1024 // XXX aCollectNonEditableNodes is ignored here. Maybe a bug. 1025 CollectChildrenOptions options = { 1026 CollectChildrenOption::CollectTableChildren}; 1027 for (const size_t index : 1028 Reversed(IntegerRange(aOutArrayOfContents.Length()))) { 1029 // Scan for table elements. If we find table elements other than 1030 // table, replace it with a list of any editable non-table content 1031 // because if a selection range starts from end in a table-cell and 1032 // ends at or starts from outside the `<table>`, we need to make 1033 // lists in each selected table-cells. 1034 const OwningNonNull<nsIContent> content = aOutArrayOfContents[index]; 1035 if (HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement( 1036 content)) { 1037 aOutArrayOfContents.RemoveElementAt(index); 1038 HTMLEditUtils::CollectChildren(content, aOutArrayOfContents, index, 1039 options); 1040 } 1041 } 1042 // If there is only one node in the array, and it is a `<div>`, 1043 // `<blockquote>` or a list element, then look inside of it until we 1044 // find inner list or content. 1045 if (aOutArrayOfContents.Length() != 1) { 1046 break; 1047 } 1048 Element* deepestDivBlockquoteOrListElement = 1049 HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild( 1050 aOutArrayOfContents[0], 1051 {HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode}, 1052 BlockInlineCheck::Unused, nsGkAtoms::div, nsGkAtoms::blockquote, 1053 nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl); 1054 if (!deepestDivBlockquoteOrListElement) { 1055 break; 1056 } 1057 if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements( 1058 nsGkAtoms::div, nsGkAtoms::blockquote)) { 1059 aOutArrayOfContents.Clear(); 1060 // XXX Before we're called, non-editable nodes are ignored. However, 1061 // we may append non-editable nodes here. 1062 HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement, 1063 aOutArrayOfContents, 0, {}); 1064 break; 1065 } 1066 aOutArrayOfContents.ReplaceElementAt( 1067 0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement)); 1068 break; 1069 } 1070 case EditSubAction::eOutdent: 1071 case EditSubAction::eIndent: 1072 case EditSubAction::eSetPositionToAbsolute: { 1073 // Indent/outdent already do something special for list items, but we 1074 // still need to make sure we don't act on table elements 1075 CollectChildrenOptions options = { 1076 CollectChildrenOption::CollectListChildren, 1077 CollectChildrenOption::CollectTableChildren}; 1078 if (aCollectNonEditableNodes == CollectNonEditableNodes::No) { 1079 options += CollectChildrenOption::IgnoreNonEditableChildren; 1080 } 1081 for (const size_t index : 1082 Reversed(IntegerRange(aOutArrayOfContents.Length()))) { 1083 const OwningNonNull<nsIContent> content = aOutArrayOfContents[index]; 1084 if (HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement( 1085 content)) { 1086 aOutArrayOfContents.RemoveElementAt(index); 1087 HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index, 1088 options); 1089 } 1090 } 1091 break; 1092 } 1093 default: 1094 break; 1095 } 1096 1097 // Outdent should look inside of divs. 1098 if (aEditSubAction == EditSubAction::eOutdent && 1099 !aHTMLEditor.IsCSSEnabled()) { 1100 CollectChildrenOptions options = {}; 1101 if (aCollectNonEditableNodes == CollectNonEditableNodes::No) { 1102 options += CollectChildrenOption::IgnoreNonEditableChildren; 1103 } 1104 for (const size_t index : 1105 Reversed(IntegerRange(aOutArrayOfContents.Length()))) { 1106 OwningNonNull<nsIContent> content = aOutArrayOfContents[index]; 1107 if (content->IsHTMLElement(nsGkAtoms::div)) { 1108 aOutArrayOfContents.RemoveElementAt(index); 1109 HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index, 1110 options); 1111 } 1112 } 1113 } 1114 1115 return NS_OK; 1116 } 1117 1118 Element* AutoClonedRangeArray::GetClosestAncestorAnyListElementOfRange() const { 1119 for (const OwningNonNull<nsRange>& range : mRanges) { 1120 nsINode* commonAncestorNode = range->GetClosestCommonInclusiveAncestor(); 1121 if (MOZ_UNLIKELY(!commonAncestorNode)) { 1122 continue; 1123 } 1124 for (Element* const element : 1125 commonAncestorNode->InclusiveAncestorsOfType<Element>()) { 1126 if (HTMLEditUtils::IsListElement(*element)) { 1127 return element; 1128 } 1129 } 1130 } 1131 return nullptr; 1132 } 1133 1134 void AutoClonedRangeArray::RemoveCollapsedRanges() { 1135 for (const auto index : Reversed(IntegerRange(mRanges.Length()))) { 1136 if (mRanges[index]->Collapsed()) { 1137 mRanges.RemoveElementAt(index); 1138 } 1139 } 1140 if (mAnchorFocusRange->Collapsed()) { 1141 MOZ_ASSERT(!mRanges.Contains(mAnchorFocusRange.get())); 1142 if (mRanges.IsEmpty()) { 1143 RemoveAllRanges(); 1144 } else { 1145 mAnchorFocusRange = mRanges.LastElement(); 1146 } 1147 } else { 1148 MOZ_ASSERT(mRanges.Contains(mAnchorFocusRange.get())); 1149 } 1150 } 1151 1152 void AutoClonedRangeArray::ExtendRangeToContainSurroundingInvisibleWhiteSpaces( 1153 nsIEditor::EStripWrappers aStripWrappers) { 1154 const auto PointAfterLineBoundary = 1155 [](const WSScanResult& aPreviousThing) -> EditorRawDOMPoint { 1156 if (aPreviousThing.ReachedCurrentBlockBoundary()) { 1157 return EditorRawDOMPoint(aPreviousThing.ElementPtr(), 0u); 1158 } 1159 return aPreviousThing.PointAfterReachedContent<EditorRawDOMPoint>(); 1160 }; 1161 const auto PointeAtLineBoundary = 1162 [](const WSScanResult& aNextThing) -> EditorRawDOMPoint { 1163 if (aNextThing.ReachedCurrentBlockBoundary()) { 1164 return EditorRawDOMPoint::AtEndOf(*aNextThing.ElementPtr()); 1165 } 1166 return aNextThing.PointAtReachedContent<EditorRawDOMPoint>(); 1167 }; 1168 for (const OwningNonNull<nsRange>& range : mRanges) { 1169 if (MOZ_UNLIKELY(range->Collapsed())) { 1170 // Don't extend the collapsed range to do nothing for the range. 1171 continue; 1172 } 1173 const WSScanResult previousThing = 1174 WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary( 1175 {WSRunScanner::Option::OnlyEditableNodes}, 1176 EditorRawDOMPoint(range->StartRef())); 1177 if (previousThing.ReachedLineBoundary()) { 1178 const EditorRawDOMPoint mostDistantNewStart = 1179 [&]() MOZ_NEVER_INLINE_DEBUG { 1180 if (aStripWrappers == nsIEditor::eStrip) { 1181 nsINode* const commonAncestor = 1182 range->GetClosestCommonInclusiveAncestor(); 1183 MOZ_ASSERT(commonAncestor); 1184 Element* const commonContainer = 1185 commonAncestor->GetAsElementOrParentElement(); 1186 if (NS_WARN_IF(!commonContainer)) { 1187 return EditorRawDOMPoint(); 1188 } 1189 return EditorRawDOMPoint(commonContainer, 0u); 1190 } 1191 Element* const container = 1192 range->StartRef().GetContainer()->GetAsElementOrParentElement(); 1193 if (NS_WARN_IF(!container)) { 1194 return EditorRawDOMPoint(); 1195 } 1196 return EditorRawDOMPoint(container, 0u); 1197 }(); 1198 const EditorRawDOMPoint afterLineBoundary = 1199 PointAfterLineBoundary(previousThing); 1200 const auto& newStart = 1201 [&]() MOZ_NEVER_INLINE_DEBUG -> const EditorRawDOMPoint& { 1202 // If the container wraps the line boundary, we can extend the range 1203 // to the line boundary. 1204 if (MOZ_UNLIKELY(!mostDistantNewStart.IsSet()) || 1205 mostDistantNewStart.IsBefore(afterLineBoundary)) { 1206 return afterLineBoundary; 1207 } 1208 // If the container does not wrap the line boundary, we can delete 1209 // first content of the container. 1210 return mostDistantNewStart; 1211 }(); 1212 const auto betterNewStart = [&]() MOZ_NEVER_INLINE_DEBUG { 1213 if (MOZ_UNLIKELY(!newStart.IsSet())) { 1214 return EditorRawDOMPoint(); 1215 } 1216 MOZ_ASSERT_IF(mostDistantNewStart.IsSet(), 1217 mostDistantNewStart.IsStartOfContainer()); 1218 auto* const firstText = Text::FromNodeOrNull( 1219 newStart == mostDistantNewStart 1220 ? mostDistantNewStart.GetContainer()->GetFirstChild() 1221 : newStart.GetChild()); 1222 if (!firstText) { 1223 return newStart; 1224 } 1225 return EditorRawDOMPoint(firstText, 0u); 1226 }(); 1227 if (MOZ_LIKELY(!NS_WARN_IF(!betterNewStart.IsSet())) && 1228 betterNewStart != range->StartRef()) { 1229 IgnoredErrorResult ignoredError; 1230 range->SetStart(betterNewStart.ToRawRangeBoundary(), ignoredError); 1231 NS_WARNING_ASSERTION(!ignoredError.Failed(), 1232 "nsRange::SetStart() failed, but ignored"); 1233 } 1234 } 1235 const WSScanResult nextThing = 1236 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1237 {WSRunScanner::Option::OnlyEditableNodes}, 1238 EditorRawDOMPoint(range->EndRef())); 1239 if (!nextThing.ReachedLineBoundary()) { 1240 continue; 1241 } 1242 const EditorRawDOMPoint mostDistantNewEnd = [&]() MOZ_NEVER_INLINE_DEBUG { 1243 if (aStripWrappers == nsIEditor::eStrip) { 1244 nsINode* const commonAncestor = 1245 range->GetClosestCommonInclusiveAncestor(); 1246 MOZ_ASSERT(commonAncestor); 1247 Element* const commonContainer = 1248 commonAncestor->GetAsElementOrParentElement(); 1249 if (NS_WARN_IF(!commonContainer)) { 1250 return EditorRawDOMPoint(); 1251 } 1252 return EditorRawDOMPoint::AtEndOf(*commonContainer); 1253 } 1254 Element* const container = 1255 range->EndRef().GetContainer()->GetAsElementOrParentElement(); 1256 if (NS_WARN_IF(!container)) { 1257 return EditorRawDOMPoint(); 1258 } 1259 return EditorRawDOMPoint::AtEndOf(*container); 1260 }(); 1261 if (MOZ_UNLIKELY(!mostDistantNewEnd.IsSet())) { 1262 continue; 1263 } 1264 const EditorRawDOMPoint atLineBoundary = PointeAtLineBoundary(nextThing); 1265 const auto& newEnd = 1266 [&]() MOZ_NEVER_INLINE_DEBUG -> const EditorRawDOMPoint& { 1267 // If the container wraps the line boundary, we can use the boundary 1268 // point. 1269 if (atLineBoundary.IsBefore(mostDistantNewEnd)) { 1270 return atLineBoundary; 1271 } 1272 // If the container does not wrap the line boundary, we can delete last 1273 // content of the container. 1274 return mostDistantNewEnd; 1275 }(); 1276 if (MOZ_UNLIKELY(!newEnd.IsSet())) { 1277 continue; 1278 } 1279 const auto betterNewEnd = [&]() MOZ_NEVER_INLINE_DEBUG { 1280 MOZ_ASSERT_IF(mostDistantNewEnd.IsSet(), 1281 mostDistantNewEnd.IsEndOfContainer()); 1282 auto* const lastText = Text::FromNodeOrNull( 1283 newEnd == mostDistantNewEnd 1284 ? mostDistantNewEnd.GetContainer()->GetLastChild() 1285 : (!newEnd.IsStartOfContainer() 1286 ? newEnd.GetPreviousSiblingOfChild() 1287 : nullptr)); 1288 if (!lastText) { 1289 return newEnd; 1290 } 1291 return EditorRawDOMPoint::AtEndOf(*lastText); 1292 }(); 1293 if (NS_WARN_IF(!betterNewEnd.IsSet()) || betterNewEnd == range->EndRef()) { 1294 continue; 1295 } 1296 IgnoredErrorResult ignoredError; 1297 range->SetEnd(betterNewEnd.ToRawRangeBoundary(), ignoredError); 1298 NS_WARNING_ASSERTION(!ignoredError.Failed(), 1299 "nsRange::SetEnd() failed, but ignored"); 1300 } 1301 } 1302 1303 /****************************************************************************** 1304 * mozilla::AutoClonedSelectionRangeArray 1305 *****************************************************************************/ 1306 1307 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray( 1308 const EditorDOMRange& aRange, 1309 const LimitersAndCaretData& aLimitersAndCaretData); 1310 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray( 1311 const EditorRawDOMRange& aRange, 1312 const LimitersAndCaretData& aLimitersAndCaretData); 1313 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray( 1314 const EditorDOMPoint& aRange, 1315 const LimitersAndCaretData& aLimitersAndCaretData); 1316 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray( 1317 const EditorRawDOMPoint& aRange, 1318 const LimitersAndCaretData& aLimitersAndCaretData); 1319 1320 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray( 1321 const dom::Selection& aSelection) { 1322 Initialize(aSelection); 1323 } 1324 1325 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray( 1326 const AutoClonedSelectionRangeArray& aOther) 1327 : AutoClonedRangeArray(aOther), 1328 mLimitersAndCaretData(aOther.mLimitersAndCaretData) {} 1329 1330 template <typename PointType> 1331 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray( 1332 const EditorDOMRangeBase<PointType>& aRange, 1333 const LimitersAndCaretData& aLimitersAndCaretData) 1334 : mLimitersAndCaretData(aLimitersAndCaretData) { 1335 MOZ_ASSERT(aRange.IsPositionedAndValid()); 1336 RefPtr<nsRange> range = aRange.CreateRange(IgnoreErrors()); 1337 if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned()) || 1338 NS_WARN_IF(!RangeIsInLimiters(*range))) { 1339 return; 1340 } 1341 mRanges.AppendElement(*range); 1342 mAnchorFocusRange = std::move(range); 1343 } 1344 1345 template <typename PT, typename CT> 1346 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray( 1347 const EditorDOMPointBase<PT, CT>& aPoint, 1348 const LimitersAndCaretData& aLimitersAndCaretData) 1349 : mLimitersAndCaretData(aLimitersAndCaretData) { 1350 MOZ_ASSERT(aPoint.IsSetAndValid()); 1351 if (NS_WARN_IF(!NodeIsInLimiters(aPoint.GetContainer()))) { 1352 return; 1353 } 1354 RefPtr<nsRange> range = aPoint.CreateCollapsedRange(IgnoreErrors()); 1355 if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) { 1356 return; 1357 } 1358 mRanges.AppendElement(*range); 1359 mAnchorFocusRange = std::move(range); 1360 SetNewCaretAssociationHint(aPoint.ToRawRangeBoundary(), 1361 aPoint.GetInterlinePosition()); 1362 } 1363 1364 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray( 1365 const nsRange& aRange, const LimitersAndCaretData& aLimitersAndCaretData) 1366 : mLimitersAndCaretData(aLimitersAndCaretData) { 1367 MOZ_ASSERT(aRange.IsPositioned()); 1368 if (NS_WARN_IF(!RangeIsInLimiters(aRange))) { 1369 return; 1370 } 1371 mRanges.AppendElement(aRange.CloneRange()); 1372 mAnchorFocusRange = mRanges[0]; 1373 } 1374 1375 void AutoClonedSelectionRangeArray::SetNewCaretAssociationHint( 1376 const RawRangeBoundary& aRawRangeBoundary, 1377 InterlinePosition aInternlinePosition) { 1378 if (aInternlinePosition == Selection::InterlinePosition::Undefined) { 1379 mLimitersAndCaretData.mCaretAssociationHint = ComputeCaretAssociationHint( 1380 mLimitersAndCaretData.mCaretAssociationHint, 1381 mLimitersAndCaretData.mCaretBidiLevel, aRawRangeBoundary); 1382 } else { 1383 SetInterlinePosition(aInternlinePosition); 1384 } 1385 } 1386 1387 bool AutoClonedSelectionRangeArray::SaveAndTrackRanges( 1388 HTMLEditor& aHTMLEditor) { 1389 if (mSavedRanges.isSome()) { 1390 return false; 1391 } 1392 mSavedRanges.emplace(*this); 1393 aHTMLEditor.RangeUpdaterRef().RegisterSelectionState(mSavedRanges.ref()); 1394 mTrackingHTMLEditor = &aHTMLEditor; 1395 return true; 1396 } 1397 1398 void AutoClonedSelectionRangeArray::ClearSavedRanges() { 1399 if (mSavedRanges.isNothing()) { 1400 return; 1401 } 1402 OwningNonNull<HTMLEditor> htmlEditor(std::move(mTrackingHTMLEditor)); 1403 MOZ_ASSERT(!mTrackingHTMLEditor); 1404 htmlEditor->RangeUpdaterRef().DropSelectionState(mSavedRanges.ref()); 1405 mSavedRanges.reset(); 1406 } 1407 1408 Result<nsIEditor::EDirection, nsresult> 1409 AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor( 1410 const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount) { 1411 MOZ_ASSERT(aEditorBase.IsEditActionDataAvailable()); 1412 MOZ_ASSERT(mAnchorFocusRange); 1413 MOZ_ASSERT(mAnchorFocusRange->IsPositioned()); 1414 MOZ_ASSERT(mAnchorFocusRange->StartRef().IsSet()); 1415 MOZ_ASSERT(mAnchorFocusRange->EndRef().IsSet()); 1416 1417 if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection( 1418 aDirectionAndAmount, *this)) { 1419 return aDirectionAndAmount; 1420 } 1421 1422 if (NS_WARN_IF(mRanges.IsEmpty())) { 1423 return Err(NS_ERROR_FAILURE); 1424 } 1425 1426 const RefPtr<PresShell> presShell = aEditorBase.GetPresShell(); 1427 if (NS_WARN_IF(!presShell)) { 1428 return Err(NS_ERROR_FAILURE); 1429 } 1430 1431 const RefPtr<Element> editingHost = 1432 aEditorBase.IsHTMLEditor() 1433 ? aEditorBase.AsHTMLEditor()->ComputeEditingHost() 1434 : nullptr; 1435 if (aEditorBase.IsHTMLEditor() && NS_WARN_IF(!editingHost)) { 1436 return Err(NS_ERROR_FAILURE); 1437 } 1438 1439 Result<RefPtr<nsRange>, nsresult> result(NS_ERROR_UNEXPECTED); 1440 const OwningNonNull<nsRange> anchorFocusRange = *mAnchorFocusRange; 1441 const LimitersAndCaretData limitersAndCaretData = mLimitersAndCaretData; 1442 const nsDirection rangeDirection = 1443 mDirection == eDirNext ? eDirNext : eDirPrevious; 1444 nsIEditor::EDirection directionAndAmountResult = aDirectionAndAmount; 1445 switch (aDirectionAndAmount) { 1446 case nsIEditor::eNextWord: 1447 result = nsFrameSelection::CreateRangeExtendedToNextWordBoundary<nsRange>( 1448 *presShell, limitersAndCaretData, anchorFocusRange, rangeDirection); 1449 if (NS_WARN_IF(aEditorBase.Destroyed())) { 1450 return Err(NS_ERROR_EDITOR_DESTROYED); 1451 } 1452 NS_WARNING_ASSERTION( 1453 result.isOk(), 1454 "nsFrameSelection::CreateRangeExtendedToNextWordBoundary() failed"); 1455 // DeleteSelectionWithTransaction() doesn't handle these actions 1456 // because it's inside batching, so don't confuse it: 1457 directionAndAmountResult = nsIEditor::eNone; 1458 break; 1459 case nsIEditor::ePreviousWord: 1460 result = 1461 nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary<nsRange>( 1462 *presShell, limitersAndCaretData, anchorFocusRange, 1463 rangeDirection); 1464 if (NS_WARN_IF(aEditorBase.Destroyed())) { 1465 return Err(NS_ERROR_EDITOR_DESTROYED); 1466 } 1467 NS_WARNING_ASSERTION( 1468 result.isOk(), 1469 "nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary() " 1470 "failed"); 1471 // DeleteSelectionWithTransaction() doesn't handle these actions 1472 // because it's inside batching, so don't confuse it: 1473 directionAndAmountResult = nsIEditor::eNone; 1474 break; 1475 case nsIEditor::eNext: 1476 result = 1477 nsFrameSelection::CreateRangeExtendedToNextGraphemeClusterBoundary< 1478 nsRange>(*presShell, limitersAndCaretData, anchorFocusRange, 1479 rangeDirection); 1480 if (NS_WARN_IF(aEditorBase.Destroyed())) { 1481 return Err(NS_ERROR_EDITOR_DESTROYED); 1482 } 1483 NS_WARNING_ASSERTION(result.isOk(), 1484 "nsFrameSelection::" 1485 "CreateRangeExtendedToNextGraphemeClusterBoundary() " 1486 "failed"); 1487 // Don't set directionAndAmount to eNone (see Bug 502259) 1488 break; 1489 case nsIEditor::ePrevious: { 1490 // Only extend the selection where the selection is after a UTF-16 1491 // surrogate pair or a variation selector. 1492 // For other cases we don't want to do that, in order 1493 // to make sure that pressing backspace will only delete the last 1494 // typed character. 1495 // XXX This is odd if the previous one is a sequence for a grapheme 1496 // cluster. 1497 const auto atStartOfSelection = GetFirstRangeStartPoint<EditorDOMPoint>(); 1498 if (MOZ_UNLIKELY(NS_WARN_IF(!atStartOfSelection.IsSet()))) { 1499 return Err(NS_ERROR_FAILURE); 1500 } 1501 1502 // node might be anonymous DIV, so we find better text node 1503 const EditorDOMPoint insertionPoint = 1504 aEditorBase.IsTextEditor() 1505 ? aEditorBase.AsTextEditor()->FindBetterInsertionPoint( 1506 atStartOfSelection) 1507 : atStartOfSelection.GetPointInTextNodeIfPointingAroundTextNode< 1508 EditorDOMPoint>(); 1509 if (MOZ_UNLIKELY(!insertionPoint.IsSet())) { 1510 NS_WARNING( 1511 "EditorBase::FindBetterInsertionPoint() failed, but ignored"); 1512 return aDirectionAndAmount; 1513 } 1514 1515 if (!insertionPoint.IsInTextNode()) { 1516 return aDirectionAndAmount; 1517 } 1518 1519 const CharacterDataBuffer* data = 1520 &insertionPoint.ContainerAs<Text>()->DataBuffer(); 1521 uint32_t offset = insertionPoint.Offset(); 1522 if (!(offset > 1 && 1523 data->IsLowSurrogateFollowingHighSurrogateAt(offset - 1)) && 1524 !(offset > 0 && 1525 gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) { 1526 return aDirectionAndAmount; 1527 } 1528 // Different from the `eNext` case, we look for character boundary. 1529 // I'm not sure whether this inconsistency between "Delete" and 1530 // "Backspace" is intentional or not. 1531 result = nsFrameSelection::CreateRangeExtendedToPreviousCharacterBoundary< 1532 nsRange>(*presShell, limitersAndCaretData, anchorFocusRange, 1533 rangeDirection); 1534 if (NS_WARN_IF(aEditorBase.Destroyed())) { 1535 return Err(NS_ERROR_EDITOR_DESTROYED); 1536 } 1537 NS_WARNING_ASSERTION( 1538 result.isOk(), 1539 "nsFrameSelection::" 1540 "CreateRangeExtendedToPreviousGraphemeClusterBoundary() failed"); 1541 break; 1542 } 1543 case nsIEditor::eToBeginningOfLine: 1544 result = 1545 nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak<nsRange>( 1546 *presShell, limitersAndCaretData, anchorFocusRange, 1547 rangeDirection); 1548 if (NS_WARN_IF(aEditorBase.Destroyed())) { 1549 return Err(NS_ERROR_EDITOR_DESTROYED); 1550 } 1551 NS_WARNING_ASSERTION( 1552 result.isOk(), 1553 "nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak() " 1554 "failed"); 1555 directionAndAmountResult = nsIEditor::eNone; 1556 break; 1557 case nsIEditor::eToEndOfLine: 1558 result = 1559 nsFrameSelection::CreateRangeExtendedToNextHardLineBreak<nsRange>( 1560 *presShell, limitersAndCaretData, anchorFocusRange, 1561 rangeDirection); 1562 if (NS_WARN_IF(aEditorBase.Destroyed())) { 1563 return Err(NS_ERROR_EDITOR_DESTROYED); 1564 } 1565 NS_WARNING_ASSERTION( 1566 result.isOk(), 1567 "nsFrameSelection::CreateRangeExtendedToNextHardLineBreak() failed"); 1568 directionAndAmountResult = nsIEditor::eNext; 1569 break; 1570 default: 1571 return aDirectionAndAmount; 1572 } 1573 1574 if (result.isErr()) { 1575 return Err(result.inspectErr()); 1576 } 1577 RefPtr<nsRange> extendedRange(result.unwrap().forget()); 1578 if (!extendedRange || NS_WARN_IF(!extendedRange->IsPositioned())) { 1579 NS_WARNING("Failed to extend the range, but ignored"); 1580 return directionAndAmountResult; 1581 } 1582 1583 // If the new range isn't editable, keep using the original range. 1584 if (aEditorBase.IsHTMLEditor() && 1585 !AutoClonedRangeArray::IsEditableRange(*extendedRange, *editingHost)) { 1586 return aDirectionAndAmount; 1587 } 1588 1589 if (NS_WARN_IF(!mLimitersAndCaretData.RangeInLimiters(*extendedRange))) { 1590 NS_WARNING("A range was extended to outer of selection limiter"); 1591 return Err(NS_ERROR_FAILURE); 1592 } 1593 1594 // Swap focus/anchor range with the extended range. 1595 DebugOnly<bool> found = false; 1596 for (OwningNonNull<nsRange>& range : mRanges) { 1597 if (range == mAnchorFocusRange) { 1598 range = *extendedRange; 1599 found = true; 1600 break; 1601 } 1602 } 1603 MOZ_ASSERT(found); 1604 mAnchorFocusRange.swap(extendedRange); 1605 return directionAndAmountResult; 1606 } 1607 1608 } // namespace mozilla