Bug 1874684 - Part 17: Fix uninitialised variable warnings from clang-tidy. r=allstarschh
[gecko.git] / editor / libeditor / AutoRangeArray.cpp
blob57b9b26a3546eeaccdf473bc138b4c466cad9e96
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/. */
6 #include "AutoRangeArray.h"
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 "WSRunObject.h" // for WSRunScanner
16 #include "mozilla/IntegerRange.h" // for IntegerRange
17 #include "mozilla/OwningNonNull.h" // for OwningNonNull
18 #include "mozilla/dom/Document.h" // for dom::Document
19 #include "mozilla/dom/HTMLBRElement.h" // for dom HTMLBRElement
20 #include "mozilla/dom/Selection.h" // for dom::Selection
21 #include "mozilla/dom/Text.h" // for dom::Text
23 #include "gfxFontUtils.h" // for gfxFontUtils
24 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_*
25 #include "nsFrameSelection.h" // for nsFrameSelection
26 #include "nsIContent.h" // for nsIContent
27 #include "nsINode.h" // for nsINode
28 #include "nsRange.h" // for nsRange
29 #include "nsTextFragment.h" // for nsTextFragment
31 namespace mozilla {
33 using namespace dom;
35 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
37 /******************************************************************************
38 * mozilla::AutoRangeArray
39 *****************************************************************************/
41 template AutoRangeArray::AutoRangeArray(const EditorDOMRange& aRange);
42 template AutoRangeArray::AutoRangeArray(const EditorRawDOMRange& aRange);
43 template AutoRangeArray::AutoRangeArray(const EditorDOMPoint& aRange);
44 template AutoRangeArray::AutoRangeArray(const EditorRawDOMPoint& aRange);
46 AutoRangeArray::AutoRangeArray(const dom::Selection& aSelection) {
47 Initialize(aSelection);
50 AutoRangeArray::AutoRangeArray(const AutoRangeArray& aOther)
51 : mAnchorFocusRange(aOther.mAnchorFocusRange),
52 mDirection(aOther.mDirection) {
53 mRanges.SetCapacity(aOther.mRanges.Length());
54 for (const OwningNonNull<nsRange>& range : aOther.mRanges) {
55 RefPtr<nsRange> clonedRange = range->CloneRange();
56 mRanges.AppendElement(std::move(clonedRange));
58 mAnchorFocusRange = aOther.mAnchorFocusRange;
61 template <typename PointType>
62 AutoRangeArray::AutoRangeArray(const EditorDOMRangeBase<PointType>& aRange) {
63 MOZ_ASSERT(aRange.IsPositionedAndValid());
64 RefPtr<nsRange> range = aRange.CreateRange(IgnoreErrors());
65 if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) {
66 return;
68 mRanges.AppendElement(*range);
69 mAnchorFocusRange = std::move(range);
72 template <typename PT, typename CT>
73 AutoRangeArray::AutoRangeArray(const EditorDOMPointBase<PT, CT>& aPoint) {
74 MOZ_ASSERT(aPoint.IsSetAndValid());
75 RefPtr<nsRange> range = aPoint.CreateCollapsedRange(IgnoreErrors());
76 if (NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned())) {
77 return;
79 mRanges.AppendElement(*range);
80 mAnchorFocusRange = std::move(range);
83 AutoRangeArray::~AutoRangeArray() {
84 if (mSavedRanges.isSome()) {
85 ClearSavedRanges();
89 AutoRangeArray::AutoRangeArray(nsRange& aRange) {
90 MOZ_ASSERT(aRange.IsPositioned());
91 if (NS_WARN_IF(!aRange.IsPositioned())) {
92 return;
94 mRanges.AppendElement(aRange);
95 mAnchorFocusRange = &aRange;
98 // static
99 bool AutoRangeArray::IsEditableRange(const dom::AbstractRange& aRange,
100 const Element& aEditingHost) {
101 // TODO: Perhaps, we should check whether the start/end boundaries are
102 // first/last point of non-editable element.
103 // See https://github.com/w3c/editing/issues/283#issuecomment-788654850
104 EditorRawDOMPoint atStart(aRange.StartRef());
105 const bool isStartEditable =
106 atStart.IsInContentNode() &&
107 EditorUtils::IsEditableContent(*atStart.ContainerAs<nsIContent>(),
108 EditorUtils::EditorType::HTML) &&
109 !HTMLEditUtils::IsNonEditableReplacedContent(
110 *atStart.ContainerAs<nsIContent>());
111 if (!isStartEditable) {
112 return false;
115 if (aRange.GetStartContainer() != aRange.GetEndContainer()) {
116 EditorRawDOMPoint atEnd(aRange.EndRef());
117 const bool isEndEditable =
118 atEnd.IsInContentNode() &&
119 EditorUtils::IsEditableContent(*atEnd.ContainerAs<nsIContent>(),
120 EditorUtils::EditorType::HTML) &&
121 !HTMLEditUtils::IsNonEditableReplacedContent(
122 *atEnd.ContainerAs<nsIContent>());
123 if (!isEndEditable) {
124 return false;
127 // Now, both start and end points are editable, but if they are in
128 // different editing host, we cannot edit the range.
129 if (atStart.ContainerAs<nsIContent>() != atEnd.ContainerAs<nsIContent>() &&
130 atStart.ContainerAs<nsIContent>()->GetEditingHost() !=
131 atEnd.ContainerAs<nsIContent>()->GetEditingHost()) {
132 return false;
136 // HTMLEditor does not support modifying outside `<body>` element for now.
137 nsINode* commonAncestor = aRange.GetClosestCommonInclusiveAncestor();
138 return commonAncestor && commonAncestor->IsContent() &&
139 commonAncestor->IsInclusiveDescendantOf(&aEditingHost);
142 void AutoRangeArray::EnsureOnlyEditableRanges(const Element& aEditingHost) {
143 for (const size_t index : Reversed(IntegerRange(mRanges.Length()))) {
144 const OwningNonNull<nsRange>& range = mRanges[index];
145 if (!AutoRangeArray::IsEditableRange(range, aEditingHost)) {
146 mRanges.RemoveElementAt(index);
147 continue;
149 // Special handling for `inert` attribute. If anchor node is inert, the
150 // range should be treated as not editable.
151 nsIContent* anchorContent =
152 mDirection == eDirNext
153 ? nsIContent::FromNode(range->GetStartContainer())
154 : nsIContent::FromNode(range->GetEndContainer());
155 if (anchorContent && HTMLEditUtils::ContentIsInert(*anchorContent)) {
156 mRanges.RemoveElementAt(index);
157 continue;
159 // Additionally, if focus node is inert, the range should be collapsed to
160 // anchor node.
161 nsIContent* focusContent =
162 mDirection == eDirNext
163 ? nsIContent::FromNode(range->GetEndContainer())
164 : nsIContent::FromNode(range->GetStartContainer());
165 if (focusContent && focusContent != anchorContent &&
166 HTMLEditUtils::ContentIsInert(*focusContent)) {
167 range->Collapse(mDirection == eDirNext);
170 mAnchorFocusRange = mRanges.IsEmpty() ? nullptr : mRanges.LastElement().get();
173 void AutoRangeArray::EnsureRangesInTextNode(const Text& aTextNode) {
174 auto GetOffsetInTextNode = [&aTextNode](const nsINode* aNode,
175 uint32_t aOffset) -> uint32_t {
176 MOZ_DIAGNOSTIC_ASSERT(aNode);
177 if (aNode == &aTextNode) {
178 return aOffset;
180 const nsIContent* anonymousDivElement = aTextNode.GetParent();
181 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement);
182 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement->IsHTMLElement(nsGkAtoms::div));
183 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement->GetFirstChild() == &aTextNode);
184 if (aNode == anonymousDivElement && aOffset == 0u) {
185 return 0u; // Point before the text node so that use start of the text.
187 MOZ_DIAGNOSTIC_ASSERT(aNode->IsInclusiveDescendantOf(anonymousDivElement));
188 // Point after the text node so that use end of the text.
189 return aTextNode.TextDataLength();
191 for (const OwningNonNull<nsRange>& range : mRanges) {
192 if (MOZ_LIKELY(range->GetStartContainer() == &aTextNode &&
193 range->GetEndContainer() == &aTextNode)) {
194 continue;
196 range->SetStartAndEnd(
197 const_cast<Text*>(&aTextNode),
198 GetOffsetInTextNode(range->GetStartContainer(), range->StartOffset()),
199 const_cast<Text*>(&aTextNode),
200 GetOffsetInTextNode(range->GetEndContainer(), range->EndOffset()));
203 if (MOZ_UNLIKELY(mRanges.Length() >= 2)) {
204 // For avoiding to handle same things in same range, we should drop and
205 // merge unnecessary ranges. Note that the ranges never overlap
206 // because selection ranges are not allowed it so that we need to check only
207 // end offset vs start offset of next one.
208 for (const size_t i : Reversed(IntegerRange(mRanges.Length() - 1u))) {
209 MOZ_ASSERT(mRanges[i]->EndOffset() < mRanges[i + 1]->StartOffset());
210 // XXX Should we delete collapsed range unless the index is 0? Without
211 // Selection API, such situation cannot happen so that `TextEditor`
212 // may behave unexpectedly.
213 if (MOZ_UNLIKELY(mRanges[i]->EndOffset() >=
214 mRanges[i + 1]->StartOffset())) {
215 const uint32_t newEndOffset = mRanges[i + 1]->EndOffset();
216 mRanges.RemoveElementAt(i + 1);
217 if (MOZ_UNLIKELY(NS_WARN_IF(newEndOffset > mRanges[i]->EndOffset()))) {
218 // So, this case shouldn't happen.
219 mRanges[i]->SetStartAndEnd(
220 const_cast<Text*>(&aTextNode), mRanges[i]->StartOffset(),
221 const_cast<Text*>(&aTextNode), newEndOffset);
228 Result<nsIEditor::EDirection, nsresult>
229 AutoRangeArray::ExtendAnchorFocusRangeFor(
230 const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount) {
231 MOZ_ASSERT(aEditorBase.IsEditActionDataAvailable());
232 MOZ_ASSERT(mAnchorFocusRange);
233 MOZ_ASSERT(mAnchorFocusRange->IsPositioned());
234 MOZ_ASSERT(mAnchorFocusRange->StartRef().IsSet());
235 MOZ_ASSERT(mAnchorFocusRange->EndRef().IsSet());
237 if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection(
238 aDirectionAndAmount, *this)) {
239 return aDirectionAndAmount;
242 if (NS_WARN_IF(!aEditorBase.SelectionRef().RangeCount())) {
243 return Err(NS_ERROR_FAILURE);
246 // By a preceding call of EnsureOnlyEditableRanges(), anchor/focus range may
247 // have been changed. In that case, we cannot use nsFrameSelection anymore.
248 // FIXME: We should make `nsFrameSelection::CreateRangeExtendedToSomewhere`
249 // work without `Selection` instance.
250 if (MOZ_UNLIKELY(
251 aEditorBase.SelectionRef().GetAnchorFocusRange()->StartRef() !=
252 mAnchorFocusRange->StartRef() ||
253 aEditorBase.SelectionRef().GetAnchorFocusRange()->EndRef() !=
254 mAnchorFocusRange->EndRef())) {
255 return aDirectionAndAmount;
258 RefPtr<nsFrameSelection> frameSelection =
259 aEditorBase.SelectionRef().GetFrameSelection();
260 if (NS_WARN_IF(!frameSelection)) {
261 return Err(NS_ERROR_NOT_INITIALIZED);
264 RefPtr<Element> editingHost;
265 if (aEditorBase.IsHTMLEditor()) {
266 editingHost = aEditorBase.AsHTMLEditor()->ComputeEditingHost();
267 if (!editingHost) {
268 return Err(NS_ERROR_FAILURE);
272 Result<RefPtr<nsRange>, nsresult> result(NS_ERROR_UNEXPECTED);
273 nsIEditor::EDirection directionAndAmountResult = aDirectionAndAmount;
274 switch (aDirectionAndAmount) {
275 case nsIEditor::eNextWord:
276 result = frameSelection->CreateRangeExtendedToNextWordBoundary<nsRange>();
277 if (NS_WARN_IF(aEditorBase.Destroyed())) {
278 return Err(NS_ERROR_EDITOR_DESTROYED);
280 NS_WARNING_ASSERTION(
281 result.isOk(),
282 "nsFrameSelection::CreateRangeExtendedToNextWordBoundary() failed");
283 // DeleteSelectionWithTransaction() doesn't handle these actions
284 // because it's inside batching, so don't confuse it:
285 directionAndAmountResult = nsIEditor::eNone;
286 break;
287 case nsIEditor::ePreviousWord:
288 result =
289 frameSelection->CreateRangeExtendedToPreviousWordBoundary<nsRange>();
290 if (NS_WARN_IF(aEditorBase.Destroyed())) {
291 return Err(NS_ERROR_EDITOR_DESTROYED);
293 NS_WARNING_ASSERTION(
294 result.isOk(),
295 "nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary() "
296 "failed");
297 // DeleteSelectionWithTransaction() doesn't handle these actions
298 // because it's inside batching, so don't confuse it:
299 directionAndAmountResult = nsIEditor::eNone;
300 break;
301 case nsIEditor::eNext:
302 result =
303 frameSelection
304 ->CreateRangeExtendedToNextGraphemeClusterBoundary<nsRange>();
305 if (NS_WARN_IF(aEditorBase.Destroyed())) {
306 return Err(NS_ERROR_EDITOR_DESTROYED);
308 NS_WARNING_ASSERTION(result.isOk(),
309 "nsFrameSelection::"
310 "CreateRangeExtendedToNextGraphemeClusterBoundary() "
311 "failed");
312 // Don't set directionAndAmount to eNone (see Bug 502259)
313 break;
314 case nsIEditor::ePrevious: {
315 // Only extend the selection where the selection is after a UTF-16
316 // surrogate pair or a variation selector.
317 // For other cases we don't want to do that, in order
318 // to make sure that pressing backspace will only delete the last
319 // typed character.
320 // XXX This is odd if the previous one is a sequence for a grapheme
321 // cluster.
322 const auto atStartOfSelection = GetFirstRangeStartPoint<EditorDOMPoint>();
323 if (MOZ_UNLIKELY(NS_WARN_IF(!atStartOfSelection.IsSet()))) {
324 return Err(NS_ERROR_FAILURE);
327 // node might be anonymous DIV, so we find better text node
328 const EditorDOMPoint insertionPoint =
329 aEditorBase.IsTextEditor()
330 ? aEditorBase.AsTextEditor()->FindBetterInsertionPoint(
331 atStartOfSelection)
332 : atStartOfSelection.GetPointInTextNodeIfPointingAroundTextNode<
333 EditorDOMPoint>();
334 if (MOZ_UNLIKELY(!insertionPoint.IsSet())) {
335 NS_WARNING(
336 "EditorBase::FindBetterInsertionPoint() failed, but ignored");
337 return aDirectionAndAmount;
340 if (!insertionPoint.IsInTextNode()) {
341 return aDirectionAndAmount;
344 const nsTextFragment* data =
345 &insertionPoint.ContainerAs<Text>()->TextFragment();
346 uint32_t offset = insertionPoint.Offset();
347 if (!(offset > 1 &&
348 data->IsLowSurrogateFollowingHighSurrogateAt(offset - 1)) &&
349 !(offset > 0 &&
350 gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) {
351 return aDirectionAndAmount;
353 // Different from the `eNext` case, we look for character boundary.
354 // I'm not sure whether this inconsistency between "Delete" and
355 // "Backspace" is intentional or not.
356 result = frameSelection
357 ->CreateRangeExtendedToPreviousCharacterBoundary<nsRange>();
358 if (NS_WARN_IF(aEditorBase.Destroyed())) {
359 return Err(NS_ERROR_EDITOR_DESTROYED);
361 NS_WARNING_ASSERTION(
362 result.isOk(),
363 "nsFrameSelection::"
364 "CreateRangeExtendedToPreviousGraphemeClusterBoundary() failed");
365 break;
367 case nsIEditor::eToBeginningOfLine:
368 result =
369 frameSelection->CreateRangeExtendedToPreviousHardLineBreak<nsRange>();
370 if (NS_WARN_IF(aEditorBase.Destroyed())) {
371 return Err(NS_ERROR_EDITOR_DESTROYED);
373 NS_WARNING_ASSERTION(
374 result.isOk(),
375 "nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak() "
376 "failed");
377 directionAndAmountResult = nsIEditor::eNone;
378 break;
379 case nsIEditor::eToEndOfLine:
380 result =
381 frameSelection->CreateRangeExtendedToNextHardLineBreak<nsRange>();
382 if (NS_WARN_IF(aEditorBase.Destroyed())) {
383 return Err(NS_ERROR_EDITOR_DESTROYED);
385 NS_WARNING_ASSERTION(
386 result.isOk(),
387 "nsFrameSelection::CreateRangeExtendedToNextHardLineBreak() failed");
388 directionAndAmountResult = nsIEditor::eNext;
389 break;
390 default:
391 return aDirectionAndAmount;
394 if (result.isErr()) {
395 return Err(result.inspectErr());
397 RefPtr<nsRange> extendedRange(result.unwrap().forget());
398 if (!extendedRange || NS_WARN_IF(!extendedRange->IsPositioned())) {
399 NS_WARNING("Failed to extend the range, but ignored");
400 return directionAndAmountResult;
403 // If the new range isn't editable, keep using the original range.
404 if (aEditorBase.IsHTMLEditor() &&
405 !AutoRangeArray::IsEditableRange(*extendedRange, *editingHost)) {
406 return aDirectionAndAmount;
409 if (NS_WARN_IF(!frameSelection->IsValidSelectionPoint(
410 extendedRange->GetStartContainer())) ||
411 NS_WARN_IF(!frameSelection->IsValidSelectionPoint(
412 extendedRange->GetEndContainer()))) {
413 NS_WARNING("A range was extended to outer of selection limiter");
414 return Err(NS_ERROR_FAILURE);
417 // Swap focus/anchor range with the extended range.
418 DebugOnly<bool> found = false;
419 for (OwningNonNull<nsRange>& range : mRanges) {
420 if (range == mAnchorFocusRange) {
421 range = *extendedRange;
422 found = true;
423 break;
426 MOZ_ASSERT(found);
427 mAnchorFocusRange.swap(extendedRange);
428 return directionAndAmountResult;
431 Result<bool, nsresult>
432 AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent(
433 const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
434 IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent,
435 const Element* aEditingHost) {
436 if (IsCollapsed()) {
437 return false;
440 switch (aDirectionAndAmount) {
441 case nsIEditor::eNext:
442 case nsIEditor::eNextWord:
443 case nsIEditor::ePrevious:
444 case nsIEditor::ePreviousWord:
445 break;
446 default:
447 return false;
450 bool changed = false;
451 for (const OwningNonNull<nsRange>& range : mRanges) {
452 MOZ_ASSERT(!range->IsInAnySelection(),
453 "Changing range in selection may cause running script");
454 Result<bool, nsresult> result =
455 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
456 aHTMLEditor, range, aEditingHost);
457 if (result.isErr()) {
458 NS_WARNING(
459 "WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() "
460 "failed");
461 return Err(result.inspectErr());
463 changed |= result.inspect();
466 if (mRanges.Length() == 1 && aIfSelectingOnlyOneAtomicContent ==
467 IfSelectingOnlyOneAtomicContent::Collapse) {
468 MOZ_ASSERT(mRanges[0].get() == mAnchorFocusRange.get());
469 if (mAnchorFocusRange->GetStartContainer() ==
470 mAnchorFocusRange->GetEndContainer() &&
471 mAnchorFocusRange->GetChildAtStartOffset() &&
472 mAnchorFocusRange->StartRef().GetNextSiblingOfChildAtOffset() ==
473 mAnchorFocusRange->GetChildAtEndOffset()) {
474 mAnchorFocusRange->Collapse(aDirectionAndAmount == nsIEditor::eNext ||
475 aDirectionAndAmount == nsIEditor::eNextWord);
476 changed = true;
480 return changed;
483 bool AutoRangeArray::SaveAndTrackRanges(HTMLEditor& aHTMLEditor) {
484 if (mSavedRanges.isSome()) {
485 return false;
487 mSavedRanges.emplace(*this);
488 aHTMLEditor.RangeUpdaterRef().RegisterSelectionState(mSavedRanges.ref());
489 mTrackingHTMLEditor = &aHTMLEditor;
490 return true;
493 void AutoRangeArray::ClearSavedRanges() {
494 if (mSavedRanges.isNothing()) {
495 return;
497 OwningNonNull<HTMLEditor> htmlEditor(std::move(mTrackingHTMLEditor));
498 MOZ_ASSERT(!mTrackingHTMLEditor);
499 htmlEditor->RangeUpdaterRef().DropSelectionState(mSavedRanges.ref());
500 mSavedRanges.reset();
503 // static
504 void AutoRangeArray::
505 UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
506 EditorDOMPoint& aStartPoint, EditorDOMPoint& aEndPoint,
507 const Element& aEditingHost) {
508 // FYI: This was moved from
509 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6743
511 // MOOSE major hack:
512 // The GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() and
513 // GetPointAfterFollowingLineBreakOrAtFollowingBlock() don't really do the
514 // right thing for collapsed ranges inside block elements that contain nothing
515 // but a solo <br>. It's easier/ to put a workaround here than to revamp
516 // them. :-(
517 if (aStartPoint != aEndPoint) {
518 return;
521 if (!aStartPoint.IsInContentNode()) {
522 return;
525 // XXX Perhaps, this should be more careful. This may not select only one
526 // node because this just check whether the block is empty or not,
527 // and may not select in non-editable block. However, for inline
528 // editing host case, it's right to look for block element without
529 // editable state check. Now, this method is used for preparation for
530 // other things. So, cannot write test for this method behavior.
531 // So, perhaps, we should get rid of this method and each caller should
532 // handle its job better.
533 Element* const maybeNonEditableBlockElement =
534 HTMLEditUtils::GetInclusiveAncestorElement(
535 *aStartPoint.ContainerAs<nsIContent>(),
536 HTMLEditUtils::ClosestBlockElement,
537 BlockInlineCheck::UseComputedDisplayStyle);
538 if (!maybeNonEditableBlockElement) {
539 return;
542 // Make sure we don't go higher than our root element in the content tree
543 if (aEditingHost.IsInclusiveDescendantOf(maybeNonEditableBlockElement)) {
544 return;
547 if (HTMLEditUtils::IsEmptyNode(
548 *maybeNonEditableBlockElement,
549 {EmptyCheckOption::TreatNonEditableContentAsInvisible})) {
550 aStartPoint.Set(maybeNonEditableBlockElement, 0u);
551 aEndPoint.SetToEndOf(maybeNonEditableBlockElement);
556 * Get the point before the line containing aPointInLine.
558 * @return If the line starts after a `<br>` element, returns next
559 * sibling of the `<br>` element.
560 * If the line is first line of a block, returns point of
561 * the block.
562 * NOTE: The result may be point of editing host. I.e., the container may be
563 * outside of editing host.
565 static EditorDOMPoint
566 GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock(
567 const EditorDOMPoint& aPointInLine, EditSubAction aEditSubAction,
568 BlockInlineCheck aBlockInlineCheck, const Element& aEditingHost) {
569 // FYI: This was moved from
570 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6447
572 if (NS_WARN_IF(!aPointInLine.IsSet())) {
573 return EditorDOMPoint();
576 EditorDOMPoint point(aPointInLine);
577 // Start scanning from the container node if aPoint is in a text node.
578 // XXX Perhaps, IsInDataNode() must be expected.
579 if (point.IsInTextNode()) {
580 if (!point.GetContainer()->GetParentNode()) {
581 // Okay, can't promote any further
582 // XXX Why don't we return start of the text node?
583 return point;
585 // If there is a preformatted linefeed in the text node, let's return
586 // the point after it.
587 EditorDOMPoint atLastPreformattedNewLine =
588 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode<EditorDOMPoint>(
589 point);
590 if (atLastPreformattedNewLine.IsSet()) {
591 return atLastPreformattedNewLine.NextPoint();
593 point.Set(point.GetContainer());
596 // Look back through any further inline nodes that aren't across a <br>
597 // from us, and that are enclosed in the same block.
598 // I.e., looking for start of current hard line.
599 constexpr HTMLEditUtils::WalkTreeOptions
600 ignoreNonEditableNodeAndStopAtBlockBoundary{
601 HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode,
602 HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary};
603 for (nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent(
604 point, ignoreNonEditableNodeAndStopAtBlockBoundary,
605 aBlockInlineCheck, &aEditingHost);
606 previousEditableContent && previousEditableContent->GetParentNode() &&
607 !HTMLEditUtils::IsVisibleBRElement(*previousEditableContent) &&
608 !HTMLEditUtils::IsBlockElement(*previousEditableContent,
609 aBlockInlineCheck);
610 previousEditableContent = HTMLEditUtils::GetPreviousContent(
611 point, ignoreNonEditableNodeAndStopAtBlockBoundary,
612 aBlockInlineCheck, &aEditingHost)) {
613 EditorDOMPoint atLastPreformattedNewLine =
614 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode<EditorDOMPoint>(
615 EditorRawDOMPoint::AtEndOf(*previousEditableContent));
616 if (atLastPreformattedNewLine.IsSet()) {
617 return atLastPreformattedNewLine.NextPoint();
619 point.Set(previousEditableContent);
622 // Finding the real start for this point unless current line starts after
623 // <br> element. Look up the tree for as long as we are the first node in
624 // the container (typically, start of nearest block ancestor), and as long
625 // as we haven't hit the body node.
626 for (nsIContent* nearContent = HTMLEditUtils::GetPreviousContent(
627 point, ignoreNonEditableNodeAndStopAtBlockBoundary,
628 aBlockInlineCheck, &aEditingHost);
629 !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
630 point.GetContainerParent();
631 nearContent = HTMLEditUtils::GetPreviousContent(
632 point, ignoreNonEditableNodeAndStopAtBlockBoundary,
633 aBlockInlineCheck, &aEditingHost)) {
634 // Don't keep looking up if we have found a blockquote element to act on
635 // when we handle outdent.
636 // XXX Sounds like this is hacky. If possible, it should be check in
637 // outdent handler for consistency between edit sub-actions.
638 // We should check Chromium's behavior of outdent when Selection
639 // starts from `<blockquote>` and starts from first child of
640 // `<blockquote>`.
641 if (aEditSubAction == EditSubAction::eOutdent &&
642 point.IsContainerHTMLElement(nsGkAtoms::blockquote)) {
643 break;
646 // Don't walk past the editable section. Note that we need to check
647 // before walking up to a parent because we need to return the parent
648 // object, so the parent itself might not be in the editable area, but
649 // it's OK if we're not performing a block-level action.
650 bool blockLevelAction =
651 aEditSubAction == EditSubAction::eIndent ||
652 aEditSubAction == EditSubAction::eOutdent ||
653 aEditSubAction == EditSubAction::eSetOrClearAlignment ||
654 aEditSubAction == EditSubAction::eCreateOrRemoveBlock ||
655 aEditSubAction == EditSubAction::eFormatBlockForHTMLCommand;
656 // XXX So, does this check whether the container is removable or not? It
657 // seems that here can be rewritten as obviously what here tries to
658 // check.
659 if (!point.GetContainerParent()->IsInclusiveDescendantOf(&aEditingHost) &&
660 (blockLevelAction ||
661 !point.GetContainer()->IsInclusiveDescendantOf(&aEditingHost))) {
662 break;
665 // If we're formatting a block, we should reformat first ancestor format
666 // block.
667 if (aEditSubAction == EditSubAction::eFormatBlockForHTMLCommand &&
668 HTMLEditUtils::IsFormatElementForFormatBlockCommand(
669 *point.ContainerAs<Element>())) {
670 point.Set(point.GetContainer());
671 break;
674 point.Set(point.GetContainer());
676 return point;
680 * Get the point after the following line break or the block which breaks the
681 * line containing aPointInLine.
683 * @return If the line ends with a visible `<br>` element, returns
684 * the point after the `<br>` element.
685 * If the line ends with a preformatted linefeed, returns
686 * the point after the linefeed unless it's an invisible
687 * line break immediately before a block boundary.
688 * If the line ends with a block boundary, returns the
689 * point of the block.
691 static EditorDOMPoint GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock(
692 const EditorDOMPoint& aPointInLine, EditSubAction aEditSubAction,
693 BlockInlineCheck aBlockInlineCheck, const Element& aEditingHost) {
694 // FYI: This was moved from
695 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6541
697 if (NS_WARN_IF(!aPointInLine.IsSet())) {
698 return EditorDOMPoint();
701 EditorDOMPoint point(aPointInLine);
702 // Start scanning from the container node if aPoint is in a text node.
703 // XXX Perhaps, IsInDataNode() must be expected.
704 if (point.IsInTextNode()) {
705 if (NS_WARN_IF(!point.GetContainer()->GetParentNode())) {
706 // Okay, can't promote any further
707 // XXX Why don't we return end of the text node?
708 return point;
710 EditorDOMPoint atNextPreformattedNewLine =
711 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode<
712 EditorDOMPoint>(point);
713 if (atNextPreformattedNewLine.IsSet()) {
714 // If the linefeed is last character of the text node, it may be
715 // invisible if it's immediately before a block boundary. In such
716 // case, we should return the block boundary.
717 Element* maybeNonEditableBlockElement = nullptr;
718 if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
719 atNextPreformattedNewLine, &maybeNonEditableBlockElement) &&
720 maybeNonEditableBlockElement) {
721 // If the block is a parent of the editing host, let's return end
722 // of editing host.
723 if (maybeNonEditableBlockElement == &aEditingHost ||
724 !maybeNonEditableBlockElement->IsInclusiveDescendantOf(
725 &aEditingHost)) {
726 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement);
728 // If it's invisible because of parent block boundary, return end
729 // of the block. Otherwise, i.e., it's followed by a child block,
730 // returns the point of the child block.
731 if (atNextPreformattedNewLine.ContainerAs<Text>()
732 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement)) {
733 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement);
735 return EditorDOMPoint(maybeNonEditableBlockElement);
737 // Otherwise, return the point after the preformatted linefeed.
738 return atNextPreformattedNewLine.NextPoint();
740 // want to be after the text node
741 point.SetAfter(point.GetContainer());
742 NS_WARNING_ASSERTION(point.IsSet(), "Failed to set to after the text node");
745 // Look ahead through any further inline nodes that aren't across a <br> from
746 // us, and that are enclosed in the same block.
747 // XXX Currently, we stop block-extending when finding visible <br> element.
748 // This might be different from "block-extend" of execCommand spec.
749 // However, the spec is really unclear.
750 // XXX Probably, scanning only editable nodes is wrong for
751 // EditSubAction::eCreateOrRemoveBlock and
752 // EditSubAction::eFormatBlockForHTMLCommand because it might be better to
753 // wrap existing inline elements even if it's non-editable. For example,
754 // following examples with insertParagraph causes different result:
755 // * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
756 // * <div contenteditable>foo[]<b>bar</b></div>
757 // * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
758 // Only in the first case, after the caret position isn't wrapped with
759 // new <div> element.
760 constexpr HTMLEditUtils::WalkTreeOptions
761 ignoreNonEditableNodeAndStopAtBlockBoundary{
762 HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode,
763 HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary};
764 for (nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent(
765 point, ignoreNonEditableNodeAndStopAtBlockBoundary,
766 aBlockInlineCheck, &aEditingHost);
767 nextEditableContent &&
768 !HTMLEditUtils::IsBlockElement(*nextEditableContent,
769 aBlockInlineCheck) &&
770 nextEditableContent->GetParent();
771 nextEditableContent = HTMLEditUtils::GetNextContent(
772 point, ignoreNonEditableNodeAndStopAtBlockBoundary,
773 aBlockInlineCheck, &aEditingHost)) {
774 EditorDOMPoint atFirstPreformattedNewLine =
775 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode<
776 EditorDOMPoint>(EditorRawDOMPoint(nextEditableContent, 0));
777 if (atFirstPreformattedNewLine.IsSet()) {
778 // If the linefeed is last character of the text node, it may be
779 // invisible if it's immediately before a block boundary. In such
780 // case, we should return the block boundary.
781 Element* maybeNonEditableBlockElement = nullptr;
782 if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
783 atFirstPreformattedNewLine, &maybeNonEditableBlockElement) &&
784 maybeNonEditableBlockElement) {
785 // If the block is a parent of the editing host, let's return end
786 // of editing host.
787 if (maybeNonEditableBlockElement == &aEditingHost ||
788 !maybeNonEditableBlockElement->IsInclusiveDescendantOf(
789 &aEditingHost)) {
790 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement);
792 // If it's invisible because of parent block boundary, return end
793 // of the block. Otherwise, i.e., it's followed by a child block,
794 // returns the point of the child block.
795 if (atFirstPreformattedNewLine.ContainerAs<Text>()
796 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement)) {
797 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement);
799 return EditorDOMPoint(maybeNonEditableBlockElement);
801 // Otherwise, return the point after the preformatted linefeed.
802 return atFirstPreformattedNewLine.NextPoint();
804 point.SetAfter(nextEditableContent);
805 if (NS_WARN_IF(!point.IsSet())) {
806 break;
808 if (HTMLEditUtils::IsVisibleBRElement(*nextEditableContent)) {
809 break;
813 // Finding the real end for this point unless current line ends with a <br>
814 // element. Look up the tree for as long as we are the last node in the
815 // container (typically, block node), and as long as we haven't hit the body
816 // node.
817 for (nsIContent* nearContent = HTMLEditUtils::GetNextContent(
818 point, ignoreNonEditableNodeAndStopAtBlockBoundary,
819 aBlockInlineCheck, &aEditingHost);
820 !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
821 point.GetContainerParent();
822 nearContent = HTMLEditUtils::GetNextContent(
823 point, ignoreNonEditableNodeAndStopAtBlockBoundary,
824 aBlockInlineCheck, &aEditingHost)) {
825 // Don't walk past the editable section. Note that we need to check before
826 // walking up to a parent because we need to return the parent object, so
827 // the parent itself might not be in the editable area, but it's OK.
828 // XXX Maybe returning parent of editing host is really error prone since
829 // everybody need to check whether the end point is in editing host
830 // when they touch there.
831 if (!point.GetContainer()->IsInclusiveDescendantOf(&aEditingHost) &&
832 !point.GetContainerParent()->IsInclusiveDescendantOf(&aEditingHost)) {
833 break;
836 // If we're formatting a block, we should reformat first ancestor format
837 // block.
838 if (aEditSubAction == EditSubAction::eFormatBlockForHTMLCommand &&
839 HTMLEditUtils::IsFormatElementForFormatBlockCommand(
840 *point.ContainerAs<Element>())) {
841 point.SetAfter(point.GetContainer());
842 break;
845 point.SetAfter(point.GetContainer());
846 if (NS_WARN_IF(!point.IsSet())) {
847 break;
850 return point;
853 void AutoRangeArray::ExtendRangesToWrapLines(EditSubAction aEditSubAction,
854 BlockInlineCheck aBlockInlineCheck,
855 const Element& aEditingHost) {
856 // FYI: This is originated in
857 // https://searchfox.org/mozilla-central/rev/1739f1301d658c9bff544a0a095ab11fca2e549d/editor/libeditor/HTMLEditSubActionHandler.cpp#6712
859 bool removeSomeRanges = false;
860 for (const OwningNonNull<nsRange>& range : mRanges) {
861 // Remove non-positioned ranges.
862 if (MOZ_UNLIKELY(!range->IsPositioned())) {
863 removeSomeRanges = true;
864 continue;
866 // If the range is native anonymous subtrees, we must meet a bug of
867 // `Selection` so that we need to hack here.
868 if (MOZ_UNLIKELY(range->GetStartContainer()->IsInNativeAnonymousSubtree() ||
869 range->GetEndContainer()->IsInNativeAnonymousSubtree())) {
870 EditorRawDOMRange rawRange(range);
871 if (!rawRange.EnsureNotInNativeAnonymousSubtree()) {
872 range->Reset();
873 removeSomeRanges = true;
874 continue;
876 if (NS_FAILED(
877 range->SetStartAndEnd(rawRange.StartRef().ToRawRangeBoundary(),
878 rawRange.EndRef().ToRawRangeBoundary())) ||
879 MOZ_UNLIKELY(!range->IsPositioned())) {
880 range->Reset();
881 removeSomeRanges = true;
882 continue;
885 // Finally, extend the range.
886 if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
887 range, aEditSubAction, aBlockInlineCheck, aEditingHost))) {
888 // If we failed to extend the range, we should use the original range
889 // as-is unless the range is broken at setting the range.
890 if (NS_WARN_IF(!range->IsPositioned())) {
891 removeSomeRanges = true;
895 if (removeSomeRanges) {
896 for (const size_t i : Reversed(IntegerRange(mRanges.Length()))) {
897 if (!mRanges[i]->IsPositioned()) {
898 mRanges.RemoveElementAt(i);
901 if (!mAnchorFocusRange || !mAnchorFocusRange->IsPositioned()) {
902 if (mRanges.IsEmpty()) {
903 mAnchorFocusRange = nullptr;
904 } else {
905 mAnchorFocusRange = mRanges.LastElement();
911 // static
912 nsresult AutoRangeArray::ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
913 nsRange& aRange, EditSubAction aEditSubAction,
914 BlockInlineCheck aBlockInlineCheck, const Element& aEditingHost) {
915 MOZ_DIAGNOSTIC_ASSERT(
916 !EditorRawDOMPoint(aRange.StartRef()).IsInNativeAnonymousSubtree());
917 MOZ_DIAGNOSTIC_ASSERT(
918 !EditorRawDOMPoint(aRange.EndRef()).IsInNativeAnonymousSubtree());
920 if (NS_WARN_IF(!aRange.IsPositioned())) {
921 return NS_ERROR_INVALID_ARG;
924 EditorDOMPoint startPoint(aRange.StartRef()), endPoint(aRange.EndRef());
926 // If we're joining blocks, we call this for selecting a line to move.
927 // Therefore, we don't want to select the ancestor blocks in this case
928 // even if they are empty.
929 if (aEditSubAction != EditSubAction::eMergeBlockContents) {
930 AutoRangeArray::
931 UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
932 startPoint, endPoint, aEditingHost);
935 // Make a new adjusted range to represent the appropriate block content.
936 // This is tricky. The basic idea is to push out the range endpoints to
937 // truly enclose the blocks that we will affect.
939 // Make sure that the new range ends up to be in the editable section.
940 // XXX Looks like that this check wastes the time. Perhaps, we should
941 // implement a method which checks both two DOM points in the editor
942 // root.
944 startPoint =
945 GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock(
946 startPoint, aEditSubAction, aBlockInlineCheck, aEditingHost);
947 // XXX GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() may
948 // return point of editing host. Perhaps, we should change it and stop
949 // checking it here since this check may be expensive.
950 // XXX If the container is an element in the editing host but it points end of
951 // the container, this returns nullptr. Is it intentional?
952 if (!startPoint.GetChildOrContainerIfDataNode() ||
953 !startPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
954 &aEditingHost)) {
955 return NS_ERROR_FAILURE;
957 endPoint = GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock(
958 endPoint, aEditSubAction, aBlockInlineCheck, aEditingHost);
959 const EditorDOMPoint lastRawPoint =
960 endPoint.IsStartOfContainer() ? endPoint : endPoint.PreviousPoint();
961 // XXX GetPointAfterFollowingLineBreakOrAtFollowingBlock() may return point of
962 // editing host. Perhaps, we should change it and stop checking it here
963 // since this check may be expensive.
964 // XXX If the container is an element in the editing host but it points end of
965 // the container, this returns nullptr. Is it intentional?
966 if (!lastRawPoint.GetChildOrContainerIfDataNode() ||
967 !lastRawPoint.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
968 &aEditingHost)) {
969 return NS_ERROR_FAILURE;
972 nsresult rv = aRange.SetStartAndEnd(startPoint.ToRawRangeBoundary(),
973 endPoint.ToRawRangeBoundary());
974 if (NS_FAILED(rv)) {
975 return NS_ERROR_FAILURE;
977 return NS_OK;
980 Result<EditorDOMPoint, nsresult>
981 AutoRangeArray::SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
982 HTMLEditor& aHTMLEditor, BlockInlineCheck aBlockInlineCheck,
983 const Element& aEditingHost,
984 const nsIContent* aAncestorLimiter /* = nullptr */) {
985 // FYI: The following code is originated in
986 // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#6971
988 // Split text nodes. This is necessary, since given ranges may end in text
989 // nodes in case where part of a pre-formatted elements needs to be moved.
990 EditorDOMPoint pointToPutCaret;
991 IgnoredErrorResult ignoredError;
992 for (const OwningNonNull<nsRange>& range : mRanges) {
993 EditorDOMPoint atEnd(range->EndRef());
994 if (NS_WARN_IF(!atEnd.IsSet()) || !atEnd.IsInTextNode() ||
995 atEnd.GetContainer() == aAncestorLimiter) {
996 continue;
999 if (!atEnd.IsStartOfContainer() && !atEnd.IsEndOfContainer()) {
1000 // Split the text node.
1001 Result<SplitNodeResult, nsresult> splitAtEndResult =
1002 aHTMLEditor.SplitNodeWithTransaction(atEnd);
1003 if (MOZ_UNLIKELY(splitAtEndResult.isErr())) {
1004 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
1005 return splitAtEndResult.propagateErr();
1007 SplitNodeResult unwrappedSplitAtEndResult = splitAtEndResult.unwrap();
1008 unwrappedSplitAtEndResult.MoveCaretPointTo(
1009 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
1011 // Correct the range.
1012 // The new end parent becomes the parent node of the text.
1013 MOZ_ASSERT(!range->IsInAnySelection());
1014 range->SetEnd(unwrappedSplitAtEndResult.AtNextContent<EditorRawDOMPoint>()
1015 .ToRawRangeBoundary(),
1016 ignoredError);
1017 NS_WARNING_ASSERTION(!ignoredError.Failed(),
1018 "nsRange::SetEnd() failed, but ignored");
1019 ignoredError.SuppressException();
1023 // FYI: The following code is originated in
1024 // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#7023
1025 AutoTArray<OwningNonNull<RangeItem>, 8> rangeItemArray;
1026 rangeItemArray.AppendElements(mRanges.Length());
1028 // First register ranges for special editor gravity
1029 Maybe<size_t> anchorFocusRangeIndex;
1030 for (const size_t index : IntegerRange(rangeItemArray.Length())) {
1031 rangeItemArray[index] = new RangeItem();
1032 rangeItemArray[index]->StoreRange(*mRanges[index]);
1033 aHTMLEditor.RangeUpdaterRef().RegisterRangeItem(*rangeItemArray[index]);
1034 if (mRanges[index] == mAnchorFocusRange) {
1035 anchorFocusRangeIndex = Some(index);
1038 // TODO: We should keep the array, and just update the ranges.
1039 mRanges.Clear();
1040 mAnchorFocusRange = nullptr;
1041 // Now bust up inlines.
1042 nsresult rv = NS_OK;
1043 for (const OwningNonNull<RangeItem>& item : Reversed(rangeItemArray)) {
1044 // MOZ_KnownLive because 'rangeItemArray' is guaranteed to keep it alive.
1045 Result<EditorDOMPoint, nsresult> splitParentsResult =
1046 aHTMLEditor.SplitInlineAncestorsAtRangeBoundaries(
1047 MOZ_KnownLive(*item), aBlockInlineCheck, aEditingHost,
1048 aAncestorLimiter);
1049 if (MOZ_UNLIKELY(splitParentsResult.isErr())) {
1050 NS_WARNING("HTMLEditor::SplitInlineAncestorsAtRangeBoundaries() failed");
1051 rv = splitParentsResult.unwrapErr();
1052 break;
1054 if (splitParentsResult.inspect().IsSet()) {
1055 pointToPutCaret = splitParentsResult.unwrap();
1058 // Then unregister the ranges
1059 for (const size_t index : IntegerRange(rangeItemArray.Length())) {
1060 aHTMLEditor.RangeUpdaterRef().DropRangeItem(rangeItemArray[index]);
1061 RefPtr<nsRange> range = rangeItemArray[index]->GetRange();
1062 if (range && range->IsPositioned()) {
1063 if (anchorFocusRangeIndex.isSome() && index == *anchorFocusRangeIndex) {
1064 mAnchorFocusRange = range;
1066 mRanges.AppendElement(std::move(range));
1069 if (!mAnchorFocusRange && !mRanges.IsEmpty()) {
1070 mAnchorFocusRange = mRanges.LastElement();
1073 // XXX Why do we ignore the other errors here??
1074 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1075 return Err(NS_ERROR_EDITOR_DESTROYED);
1077 return pointToPutCaret;
1080 nsresult AutoRangeArray::CollectEditTargetNodes(
1081 const HTMLEditor& aHTMLEditor,
1082 nsTArray<OwningNonNull<nsIContent>>& aOutArrayOfContents,
1083 EditSubAction aEditSubAction,
1084 CollectNonEditableNodes aCollectNonEditableNodes) const {
1085 MOZ_ASSERT(aHTMLEditor.IsEditActionDataAvailable());
1087 // FYI: This was moved from
1088 // https://searchfox.org/mozilla-central/rev/4bce7d85ba4796dd03c5dcc7cfe8eee0e4c07b3b/editor/libeditor/HTMLEditSubActionHandler.cpp#7060
1090 // Gather up a list of all the nodes
1091 for (const OwningNonNull<nsRange>& range : mRanges) {
1092 DOMSubtreeIterator iter;
1093 nsresult rv = iter.Init(*range);
1094 if (NS_FAILED(rv)) {
1095 NS_WARNING("DOMSubtreeIterator::Init() failed");
1096 return rv;
1098 if (aOutArrayOfContents.IsEmpty()) {
1099 iter.AppendAllNodesToArray(aOutArrayOfContents);
1100 } else {
1101 AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfTopChildren;
1102 iter.AppendNodesToArray(
1103 +[](nsINode& aNode, void* aArray) -> bool {
1104 MOZ_ASSERT(aArray);
1105 return !static_cast<nsTArray<OwningNonNull<nsIContent>>*>(aArray)
1106 ->Contains(&aNode);
1108 arrayOfTopChildren, &aOutArrayOfContents);
1109 aOutArrayOfContents.AppendElements(std::move(arrayOfTopChildren));
1111 if (aCollectNonEditableNodes == CollectNonEditableNodes::No) {
1112 for (const size_t i :
1113 Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
1114 if (!EditorUtils::IsEditableContent(aOutArrayOfContents[i],
1115 EditorUtils::EditorType::HTML)) {
1116 aOutArrayOfContents.RemoveElementAt(i);
1122 switch (aEditSubAction) {
1123 case EditSubAction::eCreateOrRemoveBlock:
1124 case EditSubAction::eFormatBlockForHTMLCommand: {
1125 // Certain operations should not act on li's and td's, but rather inside
1126 // them. Alter the list as needed.
1127 CollectChildrenOptions options = {
1128 CollectChildrenOption::CollectListChildren,
1129 CollectChildrenOption::CollectTableChildren};
1130 if (aCollectNonEditableNodes == CollectNonEditableNodes::No) {
1131 options += CollectChildrenOption::IgnoreNonEditableChildren;
1133 if (aEditSubAction == EditSubAction::eCreateOrRemoveBlock) {
1134 for (const size_t index :
1135 Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
1136 OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
1137 if (HTMLEditUtils::IsListItem(content)) {
1138 aOutArrayOfContents.RemoveElementAt(index);
1139 HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index,
1140 options);
1143 } else {
1144 // <dd> and <dt> are format blocks. Therefore, we should not handle
1145 // their children directly. They should be replaced with new format
1146 // block.
1147 MOZ_ASSERT(
1148 HTMLEditUtils::IsFormatTagForFormatBlockCommand(*nsGkAtoms::dt));
1149 MOZ_ASSERT(
1150 HTMLEditUtils::IsFormatTagForFormatBlockCommand(*nsGkAtoms::dd));
1151 for (const size_t index :
1152 Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
1153 OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
1154 MOZ_ASSERT_IF(HTMLEditUtils::IsListItem(content),
1155 content->IsAnyOfHTMLElements(
1156 nsGkAtoms::dd, nsGkAtoms::dt, nsGkAtoms::li));
1157 if (content->IsHTMLElement(nsGkAtoms::li)) {
1158 aOutArrayOfContents.RemoveElementAt(index);
1159 HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index,
1160 options);
1164 // Empty text node shouldn't be selected if unnecessary
1165 for (const size_t index :
1166 Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
1167 if (const Text* text = aOutArrayOfContents[index]->GetAsText()) {
1168 // Don't select empty text except to empty block
1169 if (!HTMLEditUtils::IsVisibleTextNode(*text)) {
1170 aOutArrayOfContents.RemoveElementAt(index);
1174 break;
1176 case EditSubAction::eCreateOrChangeList: {
1177 // XXX aCollectNonEditableNodes is ignored here. Maybe a bug.
1178 CollectChildrenOptions options = {
1179 CollectChildrenOption::CollectTableChildren};
1180 for (const size_t index :
1181 Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
1182 // Scan for table elements. If we find table elements other than
1183 // table, replace it with a list of any editable non-table content
1184 // because if a selection range starts from end in a table-cell and
1185 // ends at or starts from outside the `<table>`, we need to make
1186 // lists in each selected table-cells.
1187 OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
1188 if (HTMLEditUtils::IsAnyTableElementButNotTable(content)) {
1189 aOutArrayOfContents.RemoveElementAt(index);
1190 HTMLEditUtils::CollectChildren(content, aOutArrayOfContents, index,
1191 options);
1194 // If there is only one node in the array, and it is a `<div>`,
1195 // `<blockquote>` or a list element, then look inside of it until we
1196 // find inner list or content.
1197 if (aOutArrayOfContents.Length() != 1) {
1198 break;
1200 Element* deepestDivBlockquoteOrListElement =
1201 HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
1202 aOutArrayOfContents[0],
1203 {HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode},
1204 BlockInlineCheck::Unused, nsGkAtoms::div, nsGkAtoms::blockquote,
1205 nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl);
1206 if (!deepestDivBlockquoteOrListElement) {
1207 break;
1209 if (deepestDivBlockquoteOrListElement->IsAnyOfHTMLElements(
1210 nsGkAtoms::div, nsGkAtoms::blockquote)) {
1211 aOutArrayOfContents.Clear();
1212 // XXX Before we're called, non-editable nodes are ignored. However,
1213 // we may append non-editable nodes here.
1214 HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement,
1215 aOutArrayOfContents, 0, {});
1216 break;
1218 aOutArrayOfContents.ReplaceElementAt(
1219 0, OwningNonNull<nsIContent>(*deepestDivBlockquoteOrListElement));
1220 break;
1222 case EditSubAction::eOutdent:
1223 case EditSubAction::eIndent:
1224 case EditSubAction::eSetPositionToAbsolute: {
1225 // Indent/outdent already do something special for list items, but we
1226 // still need to make sure we don't act on table elements
1227 CollectChildrenOptions options = {
1228 CollectChildrenOption::CollectListChildren,
1229 CollectChildrenOption::CollectTableChildren};
1230 if (aCollectNonEditableNodes == CollectNonEditableNodes::No) {
1231 options += CollectChildrenOption::IgnoreNonEditableChildren;
1233 for (const size_t index :
1234 Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
1235 OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
1236 if (HTMLEditUtils::IsAnyTableElementButNotTable(content)) {
1237 aOutArrayOfContents.RemoveElementAt(index);
1238 HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index,
1239 options);
1242 break;
1244 default:
1245 break;
1248 // Outdent should look inside of divs.
1249 if (aEditSubAction == EditSubAction::eOutdent &&
1250 !aHTMLEditor.IsCSSEnabled()) {
1251 CollectChildrenOptions options = {};
1252 if (aCollectNonEditableNodes == CollectNonEditableNodes::No) {
1253 options += CollectChildrenOption::IgnoreNonEditableChildren;
1255 for (const size_t index :
1256 Reversed(IntegerRange(aOutArrayOfContents.Length()))) {
1257 OwningNonNull<nsIContent> content = aOutArrayOfContents[index];
1258 if (content->IsHTMLElement(nsGkAtoms::div)) {
1259 aOutArrayOfContents.RemoveElementAt(index);
1260 HTMLEditUtils::CollectChildren(*content, aOutArrayOfContents, index,
1261 options);
1266 return NS_OK;
1269 Element* AutoRangeArray::GetClosestAncestorAnyListElementOfRange() const {
1270 for (const OwningNonNull<nsRange>& range : mRanges) {
1271 nsINode* commonAncestorNode = range->GetClosestCommonInclusiveAncestor();
1272 if (MOZ_UNLIKELY(!commonAncestorNode)) {
1273 continue;
1275 for (Element* const element :
1276 commonAncestorNode->InclusiveAncestorsOfType<Element>()) {
1277 if (HTMLEditUtils::IsAnyListElement(element)) {
1278 return element;
1282 return nullptr;
1285 } // namespace mozilla