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 "EditorDOMPoint.h" // for EditorDOMPoint, EditorDOMRange, etc
9 #include "EditorForwards.h" // for CollectChildrenOptions
10 #include "HTMLEditUtils.h" // for HTMLEditUtils
11 #include "HTMLEditHelpers.h" // for SplitNodeResult
12 #include "WSRunObject.h" // for WSRunScanner
14 #include "mozilla/OwningNonNull.h" // for OwningNonNull
15 #include "mozilla/dom/Document.h" // for dom::Document
16 #include "mozilla/dom/HTMLBRElement.h" // for dom HTMLBRElement
17 #include "mozilla/dom/Selection.h" // for dom::Selection
18 #include "mozilla/dom/Text.h" // for dom::Text
20 #include "gfxFontUtils.h" // for gfxFontUtils
21 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_*
22 #include "nsFrameSelection.h" // for nsFrameSelection
23 #include "nsIContent.h" // for nsIContent
24 #include "nsINode.h" // for nsINode
25 #include "nsRange.h" // for nsRange
26 #include "nsTextFragment.h" // for nsTextFragment
32 /******************************************************************************
33 * mozilla::AutoRangeArray
34 *****************************************************************************/
36 template AutoRangeArray::AutoRangeArray(const EditorDOMRange
& aRange
);
37 template AutoRangeArray::AutoRangeArray(const EditorRawDOMRange
& aRange
);
38 template AutoRangeArray::AutoRangeArray(const EditorDOMPoint
& aRange
);
39 template AutoRangeArray::AutoRangeArray(const EditorRawDOMPoint
& aRange
);
41 AutoRangeArray::AutoRangeArray(const dom::Selection
& aSelection
) {
42 Initialize(aSelection
);
45 AutoRangeArray::AutoRangeArray(const AutoRangeArray
& aOther
)
46 : mAnchorFocusRange(aOther
.mAnchorFocusRange
),
47 mDirection(aOther
.mDirection
) {
48 mRanges
.SetCapacity(aOther
.mRanges
.Length());
49 for (const OwningNonNull
<nsRange
>& range
: aOther
.mRanges
) {
50 RefPtr
<nsRange
> clonedRange
= range
->CloneRange();
51 mRanges
.AppendElement(std::move(clonedRange
));
55 template <typename PointType
>
56 AutoRangeArray::AutoRangeArray(const EditorDOMRangeBase
<PointType
>& aRange
) {
57 MOZ_ASSERT(aRange
.IsPositionedAndValid());
58 RefPtr
<nsRange
> range
= aRange
.CreateRange(IgnoreErrors());
59 if (NS_WARN_IF(!range
) || NS_WARN_IF(!range
->IsPositioned())) {
62 mRanges
.AppendElement(std::move(range
));
65 template <typename PT
, typename CT
>
66 AutoRangeArray::AutoRangeArray(const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
67 MOZ_ASSERT(aPoint
.IsSetAndValid());
68 RefPtr
<nsRange
> range
= aPoint
.CreateCollapsedRange(IgnoreErrors());
69 if (NS_WARN_IF(!range
) || NS_WARN_IF(!range
->IsPositioned())) {
72 mRanges
.AppendElement(std::move(range
));
75 AutoRangeArray::~AutoRangeArray() {
76 if (mSavedRanges
.isSome()) {
82 bool AutoRangeArray::IsEditableRange(const dom::AbstractRange
& aRange
,
83 const Element
& aEditingHost
) {
84 // TODO: Perhaps, we should check whether the start/end boundaries are
85 // first/last point of non-editable element.
86 // See https://github.com/w3c/editing/issues/283#issuecomment-788654850
87 EditorRawDOMPoint
atStart(aRange
.StartRef());
88 const bool isStartEditable
=
89 atStart
.IsInContentNode() &&
90 EditorUtils::IsEditableContent(*atStart
.ContainerAs
<nsIContent
>(),
91 EditorUtils::EditorType::HTML
) &&
92 !HTMLEditUtils::IsNonEditableReplacedContent(
93 *atStart
.ContainerAs
<nsIContent
>());
94 if (!isStartEditable
) {
98 if (aRange
.GetStartContainer() != aRange
.GetEndContainer()) {
99 EditorRawDOMPoint
atEnd(aRange
.EndRef());
100 const bool isEndEditable
=
101 atEnd
.IsInContentNode() &&
102 EditorUtils::IsEditableContent(*atEnd
.ContainerAs
<nsIContent
>(),
103 EditorUtils::EditorType::HTML
) &&
104 !HTMLEditUtils::IsNonEditableReplacedContent(
105 *atEnd
.ContainerAs
<nsIContent
>());
106 if (!isEndEditable
) {
110 // Now, both start and end points are editable, but if they are in
111 // different editing host, we cannot edit the range.
112 if (atStart
.ContainerAs
<nsIContent
>() != atEnd
.ContainerAs
<nsIContent
>() &&
113 atStart
.ContainerAs
<nsIContent
>()->GetEditingHost() !=
114 atEnd
.ContainerAs
<nsIContent
>()->GetEditingHost()) {
119 // HTMLEditor does not support modifying outside `<body>` element for now.
120 nsINode
* commonAncestor
= aRange
.GetClosestCommonInclusiveAncestor();
121 return commonAncestor
&& commonAncestor
->IsContent() &&
122 commonAncestor
->IsInclusiveDescendantOf(&aEditingHost
);
125 void AutoRangeArray::EnsureOnlyEditableRanges(const Element
& aEditingHost
) {
126 for (size_t i
= mRanges
.Length(); i
> 0; i
--) {
127 const OwningNonNull
<nsRange
>& range
= mRanges
[i
- 1];
128 if (!AutoRangeArray::IsEditableRange(range
, aEditingHost
)) {
129 mRanges
.RemoveElementAt(i
- 1);
132 mAnchorFocusRange
= mRanges
.IsEmpty() ? nullptr : mRanges
.LastElement().get();
135 void AutoRangeArray::EnsureRangesInTextNode(const Text
& aTextNode
) {
136 auto GetOffsetInTextNode
= [&aTextNode
](const nsINode
* aNode
,
137 uint32_t aOffset
) -> uint32_t {
138 MOZ_DIAGNOSTIC_ASSERT(aNode
);
139 if (aNode
== &aTextNode
) {
142 const nsIContent
* anonymousDivElement
= aTextNode
.GetParent();
143 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement
);
144 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement
->IsHTMLElement(nsGkAtoms::div
));
145 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement
->GetFirstChild() == &aTextNode
);
146 if (aNode
== anonymousDivElement
&& aOffset
== 0u) {
147 return 0u; // Point before the text node so that use start of the text.
149 MOZ_DIAGNOSTIC_ASSERT(aNode
->IsInclusiveDescendantOf(anonymousDivElement
));
150 // Point after the text node so that use end of the text.
151 return aTextNode
.TextDataLength();
153 for (uint32_t i
: IntegerRange(mRanges
.Length())) {
154 const OwningNonNull
<nsRange
>& range
= mRanges
[i
];
155 if (MOZ_LIKELY(range
->GetStartContainer() == &aTextNode
&&
156 range
->GetEndContainer() == &aTextNode
)) {
159 range
->SetStartAndEnd(
160 const_cast<Text
*>(&aTextNode
),
161 GetOffsetInTextNode(range
->GetStartContainer(), range
->StartOffset()),
162 const_cast<Text
*>(&aTextNode
),
163 GetOffsetInTextNode(range
->GetEndContainer(), range
->EndOffset()));
166 if (MOZ_UNLIKELY(mRanges
.Length() >= 2)) {
167 // For avoiding to handle same things in same range, we should drop and
168 // merge unnecessary ranges. Note that the ranges never overlap
169 // because selection ranges are not allowed it so that we need to check only
170 // end offset vs start offset of next one.
171 for (uint32_t i
: Reversed(IntegerRange(mRanges
.Length() - 1u))) {
172 MOZ_ASSERT(mRanges
[i
]->EndOffset() < mRanges
[i
+ 1]->StartOffset());
173 // XXX Should we delete collapsed range unless the index is 0? Without
174 // Selection API, such situation cannot happen so that `TextEditor`
175 // may behave unexpectedly.
176 if (MOZ_UNLIKELY(mRanges
[i
]->EndOffset() >=
177 mRanges
[i
+ 1]->StartOffset())) {
178 const uint32_t newEndOffset
= mRanges
[i
+ 1]->EndOffset();
179 mRanges
.RemoveElementAt(i
+ 1);
180 if (MOZ_UNLIKELY(NS_WARN_IF(newEndOffset
> mRanges
[i
]->EndOffset()))) {
181 // So, this case shouldn't happen.
182 mRanges
[i
]->SetStartAndEnd(
183 const_cast<Text
*>(&aTextNode
), mRanges
[i
]->StartOffset(),
184 const_cast<Text
*>(&aTextNode
), newEndOffset
);
191 Result
<nsIEditor::EDirection
, nsresult
>
192 AutoRangeArray::ExtendAnchorFocusRangeFor(
193 const EditorBase
& aEditorBase
, nsIEditor::EDirection aDirectionAndAmount
) {
194 MOZ_ASSERT(aEditorBase
.IsEditActionDataAvailable());
195 MOZ_ASSERT(mAnchorFocusRange
);
196 MOZ_ASSERT(mAnchorFocusRange
->IsPositioned());
197 MOZ_ASSERT(mAnchorFocusRange
->StartRef().IsSet());
198 MOZ_ASSERT(mAnchorFocusRange
->EndRef().IsSet());
200 if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection(
201 aDirectionAndAmount
, *this)) {
202 return aDirectionAndAmount
;
205 if (NS_WARN_IF(!aEditorBase
.SelectionRef().RangeCount())) {
206 return Err(NS_ERROR_FAILURE
);
209 // At this point, the anchor-focus ranges must match for bidi information.
210 // See `EditorBase::AutoCaretBidiLevelManager`.
211 MOZ_ASSERT(aEditorBase
.SelectionRef().GetAnchorFocusRange()->StartRef() ==
212 mAnchorFocusRange
->StartRef());
213 MOZ_ASSERT(aEditorBase
.SelectionRef().GetAnchorFocusRange()->EndRef() ==
214 mAnchorFocusRange
->EndRef());
216 RefPtr
<nsFrameSelection
> frameSelection
=
217 aEditorBase
.SelectionRef().GetFrameSelection();
218 if (NS_WARN_IF(!frameSelection
)) {
219 return Err(NS_ERROR_NOT_INITIALIZED
);
222 RefPtr
<Element
> editingHost
;
223 if (aEditorBase
.IsHTMLEditor()) {
224 editingHost
= aEditorBase
.AsHTMLEditor()->ComputeEditingHost();
226 return Err(NS_ERROR_FAILURE
);
230 Result
<RefPtr
<nsRange
>, nsresult
> result(NS_ERROR_UNEXPECTED
);
231 nsIEditor::EDirection directionAndAmountResult
= aDirectionAndAmount
;
232 switch (aDirectionAndAmount
) {
233 case nsIEditor::eNextWord
:
234 result
= frameSelection
->CreateRangeExtendedToNextWordBoundary
<nsRange
>();
235 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
236 return Err(NS_ERROR_EDITOR_DESTROYED
);
238 NS_WARNING_ASSERTION(
240 "nsFrameSelection::CreateRangeExtendedToNextWordBoundary() failed");
241 // DeleteSelectionWithTransaction() doesn't handle these actions
242 // because it's inside batching, so don't confuse it:
243 directionAndAmountResult
= nsIEditor::eNone
;
245 case nsIEditor::ePreviousWord
:
247 frameSelection
->CreateRangeExtendedToPreviousWordBoundary
<nsRange
>();
248 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
249 return Err(NS_ERROR_EDITOR_DESTROYED
);
251 NS_WARNING_ASSERTION(
253 "nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary() "
255 // DeleteSelectionWithTransaction() doesn't handle these actions
256 // because it's inside batching, so don't confuse it:
257 directionAndAmountResult
= nsIEditor::eNone
;
259 case nsIEditor::eNext
:
262 ->CreateRangeExtendedToNextGraphemeClusterBoundary
<nsRange
>();
263 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
264 return Err(NS_ERROR_EDITOR_DESTROYED
);
266 NS_WARNING_ASSERTION(result
.isOk(),
268 "CreateRangeExtendedToNextGraphemeClusterBoundary() "
270 // Don't set directionAndAmount to eNone (see Bug 502259)
272 case nsIEditor::ePrevious
: {
273 // Only extend the selection where the selection is after a UTF-16
274 // surrogate pair or a variation selector.
275 // For other cases we don't want to do that, in order
276 // to make sure that pressing backspace will only delete the last
278 // XXX This is odd if the previous one is a sequence for a grapheme
280 const auto atStartOfSelection
= GetFirstRangeStartPoint
<EditorDOMPoint
>();
281 if (MOZ_UNLIKELY(NS_WARN_IF(!atStartOfSelection
.IsSet()))) {
282 return Err(NS_ERROR_FAILURE
);
285 // node might be anonymous DIV, so we find better text node
286 const EditorDOMPoint insertionPoint
=
287 aEditorBase
.FindBetterInsertionPoint(atStartOfSelection
);
288 if (MOZ_UNLIKELY(!insertionPoint
.IsSet())) {
290 "EditorBase::FindBetterInsertionPoint() failed, but ignored");
291 return aDirectionAndAmount
;
294 if (!insertionPoint
.IsInTextNode()) {
295 return aDirectionAndAmount
;
298 const nsTextFragment
* data
=
299 &insertionPoint
.ContainerAs
<Text
>()->TextFragment();
300 uint32_t offset
= insertionPoint
.Offset();
302 data
->IsLowSurrogateFollowingHighSurrogateAt(offset
- 1)) &&
304 gfxFontUtils::IsVarSelector(data
->CharAt(offset
- 1)))) {
305 return aDirectionAndAmount
;
307 // Different from the `eNext` case, we look for character boundary.
308 // I'm not sure whether this inconsistency between "Delete" and
309 // "Backspace" is intentional or not.
310 result
= frameSelection
311 ->CreateRangeExtendedToPreviousCharacterBoundary
<nsRange
>();
312 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
313 return Err(NS_ERROR_EDITOR_DESTROYED
);
315 NS_WARNING_ASSERTION(
318 "CreateRangeExtendedToPreviousGraphemeClusterBoundary() failed");
321 case nsIEditor::eToBeginningOfLine
:
323 frameSelection
->CreateRangeExtendedToPreviousHardLineBreak
<nsRange
>();
324 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
325 return Err(NS_ERROR_EDITOR_DESTROYED
);
327 NS_WARNING_ASSERTION(
329 "nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak() "
331 directionAndAmountResult
= nsIEditor::eNone
;
333 case nsIEditor::eToEndOfLine
:
335 frameSelection
->CreateRangeExtendedToNextHardLineBreak
<nsRange
>();
336 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
337 return Err(NS_ERROR_EDITOR_DESTROYED
);
339 NS_WARNING_ASSERTION(
341 "nsFrameSelection::CreateRangeExtendedToNextHardLineBreak() failed");
342 directionAndAmountResult
= nsIEditor::eNext
;
345 return aDirectionAndAmount
;
348 if (result
.isErr()) {
349 return Err(result
.inspectErr());
351 RefPtr
<nsRange
> extendedRange(result
.unwrap().forget());
352 if (!extendedRange
|| NS_WARN_IF(!extendedRange
->IsPositioned())) {
353 NS_WARNING("Failed to extend the range, but ignored");
354 return directionAndAmountResult
;
357 // If the new range isn't editable, keep using the original range.
358 if (aEditorBase
.IsHTMLEditor() &&
359 !AutoRangeArray::IsEditableRange(*extendedRange
, *editingHost
)) {
360 return aDirectionAndAmount
;
363 if (NS_WARN_IF(!frameSelection
->IsValidSelectionPoint(
364 extendedRange
->GetStartContainer())) ||
365 NS_WARN_IF(!frameSelection
->IsValidSelectionPoint(
366 extendedRange
->GetEndContainer()))) {
367 NS_WARNING("A range was extended to outer of selection limiter");
368 return Err(NS_ERROR_FAILURE
);
371 // Swap focus/anchor range with the extended range.
372 DebugOnly
<bool> found
= false;
373 for (OwningNonNull
<nsRange
>& range
: mRanges
) {
374 if (range
== mAnchorFocusRange
) {
375 range
= *extendedRange
;
381 mAnchorFocusRange
.swap(extendedRange
);
382 return directionAndAmountResult
;
385 Result
<bool, nsresult
>
386 AutoRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent(
387 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
388 IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent
,
389 const Element
* aEditingHost
) {
394 switch (aDirectionAndAmount
) {
395 case nsIEditor::eNext
:
396 case nsIEditor::eNextWord
:
397 case nsIEditor::ePrevious
:
398 case nsIEditor::ePreviousWord
:
404 bool changed
= false;
405 for (auto& range
: mRanges
) {
406 MOZ_ASSERT(!range
->IsInSelection(),
407 "Changing range in selection may cause running script");
408 Result
<bool, nsresult
> result
=
409 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
410 aHTMLEditor
, range
, aEditingHost
);
411 if (result
.isErr()) {
413 "WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() "
415 return Err(result
.inspectErr());
417 changed
|= result
.inspect();
420 if (mRanges
.Length() == 1 && aIfSelectingOnlyOneAtomicContent
==
421 IfSelectingOnlyOneAtomicContent::Collapse
) {
422 MOZ_ASSERT(mRanges
[0].get() == mAnchorFocusRange
.get());
423 if (mAnchorFocusRange
->GetStartContainer() ==
424 mAnchorFocusRange
->GetEndContainer() &&
425 mAnchorFocusRange
->GetChildAtStartOffset() &&
426 mAnchorFocusRange
->StartRef().GetNextSiblingOfChildAtOffset() ==
427 mAnchorFocusRange
->GetChildAtEndOffset()) {
428 mAnchorFocusRange
->Collapse(aDirectionAndAmount
== nsIEditor::eNext
||
429 aDirectionAndAmount
== nsIEditor::eNextWord
);
437 bool AutoRangeArray::SaveAndTrackRanges(HTMLEditor
& aHTMLEditor
) {
438 if (mSavedRanges
.isSome()) {
441 mSavedRanges
.emplace(*this);
442 aHTMLEditor
.RangeUpdaterRef().RegisterSelectionState(mSavedRanges
.ref());
443 mTrackingHTMLEditor
= &aHTMLEditor
;
447 void AutoRangeArray::ClearSavedRanges() {
448 if (mSavedRanges
.isNothing()) {
451 OwningNonNull
<HTMLEditor
> htmlEditor(std::move(mTrackingHTMLEditor
));
452 MOZ_ASSERT(!mTrackingHTMLEditor
);
453 htmlEditor
->RangeUpdaterRef().DropSelectionState(mSavedRanges
.ref());
454 mSavedRanges
.reset();
458 void AutoRangeArray::
459 UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
460 EditorDOMPoint
& aStartPoint
, EditorDOMPoint
& aEndPoint
,
461 const Element
& aEditingHost
) {
462 // FYI: This was moved from
463 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6743
466 // The GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() and
467 // GetPointAfterFollowingLineBreakOrAtFollowingBlock() don't really do the
468 // right thing for collapsed ranges inside block elements that contain nothing
469 // but a solo <br>. It's easier/ to put a workaround here than to revamp
471 if (aStartPoint
!= aEndPoint
) {
475 if (!aStartPoint
.IsInContentNode()) {
479 // XXX Perhaps, this should be more careful. This may not select only one
480 // node because this just check whether the block is empty or not,
481 // and may not select in non-editable block. However, for inline
482 // editing host case, it's right to look for block element without
483 // editable state check. Now, this method is used for preparation for
484 // other things. So, cannot write test for this method behavior.
485 // So, perhaps, we should get rid of this method and each caller should
486 // handle its job better.
487 Element
* const maybeNonEditableBlockElement
=
488 HTMLEditUtils::GetInclusiveAncestorElement(
489 *aStartPoint
.ContainerAs
<nsIContent
>(),
490 HTMLEditUtils::ClosestBlockElement
);
491 if (!maybeNonEditableBlockElement
) {
495 // Make sure we don't go higher than our root element in the content tree
496 if (aEditingHost
.IsInclusiveDescendantOf(maybeNonEditableBlockElement
)) {
500 if (HTMLEditUtils::IsEmptyNode(*maybeNonEditableBlockElement
)) {
501 aStartPoint
.Set(maybeNonEditableBlockElement
, 0u);
502 aEndPoint
.SetToEndOf(maybeNonEditableBlockElement
);
507 * Get the point before the line containing aPointInLine.
509 * @return If the line starts after a `<br>` element, returns next
510 * sibling of the `<br>` element.
511 * If the line is first line of a block, returns point of
513 * NOTE: The result may be point of editing host. I.e., the container may be
514 * outside of editing host.
516 static EditorDOMPoint
517 GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock(
518 const EditorDOMPoint
& aPointInLine
, EditSubAction aEditSubAction
,
519 const Element
& aEditingHost
) {
520 // FYI: This was moved from
521 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6447
523 if (NS_WARN_IF(!aPointInLine
.IsSet())) {
524 return EditorDOMPoint();
527 EditorDOMPoint
point(aPointInLine
);
528 // Start scanning from the container node if aPoint is in a text node.
529 // XXX Perhaps, IsInDataNode() must be expected.
530 if (point
.IsInTextNode()) {
531 if (!point
.GetContainer()->GetParentNode()) {
532 // Okay, can't promote any further
533 // XXX Why don't we return start of the text node?
536 // If there is a preformatted linefeed in the text node, let's return
537 // the point after it.
538 EditorDOMPoint atLastPreformattedNewLine
=
539 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode
<EditorDOMPoint
>(
541 if (atLastPreformattedNewLine
.IsSet()) {
542 return atLastPreformattedNewLine
.NextPoint();
544 point
.Set(point
.GetContainer());
547 // Look back through any further inline nodes that aren't across a <br>
548 // from us, and that are enclosed in the same block.
549 // I.e., looking for start of current hard line.
550 constexpr HTMLEditUtils::WalkTreeOptions
551 ignoreNonEditableNodeAndStopAtBlockBoundary
{
552 HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode
,
553 HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary
};
554 for (nsIContent
* previousEditableContent
= HTMLEditUtils::GetPreviousContent(
555 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
);
556 previousEditableContent
&& previousEditableContent
->GetParentNode() &&
557 !HTMLEditUtils::IsVisibleBRElement(*previousEditableContent
) &&
558 !HTMLEditUtils::IsBlockElement(*previousEditableContent
);
559 previousEditableContent
= HTMLEditUtils::GetPreviousContent(
560 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
)) {
561 EditorDOMPoint atLastPreformattedNewLine
=
562 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode
<EditorDOMPoint
>(
563 EditorRawDOMPoint::AtEndOf(*previousEditableContent
));
564 if (atLastPreformattedNewLine
.IsSet()) {
565 return atLastPreformattedNewLine
.NextPoint();
567 point
.Set(previousEditableContent
);
570 // Finding the real start for this point unless current line starts after
571 // <br> element. Look up the tree for as long as we are the first node in
572 // the container (typically, start of nearest block ancestor), and as long
573 // as we haven't hit the body node.
574 for (nsIContent
* nearContent
= HTMLEditUtils::GetPreviousContent(
575 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
);
576 !nearContent
&& !point
.IsContainerHTMLElement(nsGkAtoms::body
) &&
577 point
.GetContainerParent();
578 nearContent
= HTMLEditUtils::GetPreviousContent(
579 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
)) {
580 // Don't keep looking up if we have found a blockquote element to act on
581 // when we handle outdent.
582 // XXX Sounds like this is hacky. If possible, it should be check in
583 // outdent handler for consistency between edit sub-actions.
584 // We should check Chromium's behavior of outdent when Selection
585 // starts from `<blockquote>` and starts from first child of
587 if (aEditSubAction
== EditSubAction::eOutdent
&&
588 point
.IsContainerHTMLElement(nsGkAtoms::blockquote
)) {
592 // Don't walk past the editable section. Note that we need to check
593 // before walking up to a parent because we need to return the parent
594 // object, so the parent itself might not be in the editable area, but
595 // it's OK if we're not performing a block-level action.
596 bool blockLevelAction
=
597 aEditSubAction
== EditSubAction::eIndent
||
598 aEditSubAction
== EditSubAction::eOutdent
||
599 aEditSubAction
== EditSubAction::eSetOrClearAlignment
||
600 aEditSubAction
== EditSubAction::eCreateOrRemoveBlock
;
601 // XXX So, does this check whether the container is removable or not? It
602 // seems that here can be rewritten as obviously what here tries to
604 if (!point
.GetContainerParent()->IsInclusiveDescendantOf(&aEditingHost
) &&
606 !point
.GetContainer()->IsInclusiveDescendantOf(&aEditingHost
))) {
610 point
.Set(point
.GetContainer());
616 * Get the point after the following line break or the block which breaks the
617 * line containing aPointInLine.
619 * @return If the line ends with a visible `<br>` element, returns
620 * the point after the `<br>` element.
621 * If the line ends with a preformatted linefeed, returns
622 * the point after the linefeed unless it's an invisible
623 * line break immediately before a block boundary.
624 * If the line ends with a block boundary, returns the
625 * point of the block.
627 static EditorDOMPoint
GetPointAfterFollowingLineBreakOrAtFollowingBlock(
628 const EditorDOMPoint
& aPointInLine
, const Element
& aEditingHost
) {
629 // FYI: This was moved from
630 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6541
632 if (NS_WARN_IF(!aPointInLine
.IsSet())) {
633 return EditorDOMPoint();
636 EditorDOMPoint
point(aPointInLine
);
637 // Start scanning from the container node if aPoint is in a text node.
638 // XXX Perhaps, IsInDataNode() must be expected.
639 if (point
.IsInTextNode()) {
640 if (NS_WARN_IF(!point
.GetContainer()->GetParentNode())) {
641 // Okay, can't promote any further
642 // XXX Why don't we return end of the text node?
645 EditorDOMPoint atNextPreformattedNewLine
=
646 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode
<
647 EditorDOMPoint
>(point
);
648 if (atNextPreformattedNewLine
.IsSet()) {
649 // If the linefeed is last character of the text node, it may be
650 // invisible if it's immediately before a block boundary. In such
651 // case, we should retrun the block boundary.
652 Element
* maybeNonEditableBlockElement
= nullptr;
653 if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
654 atNextPreformattedNewLine
, &maybeNonEditableBlockElement
) &&
655 maybeNonEditableBlockElement
) {
656 // If the block is a parent of the editing host, let's return end
658 if (maybeNonEditableBlockElement
== &aEditingHost
||
659 !maybeNonEditableBlockElement
->IsInclusiveDescendantOf(
661 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
663 // If it's invisible because of parent block boundary, return end
664 // of the block. Otherwise, i.e., it's followed by a child block,
665 // returns the point of the child block.
666 if (atNextPreformattedNewLine
.ContainerAs
<Text
>()
667 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement
)) {
668 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
670 return EditorDOMPoint(maybeNonEditableBlockElement
);
672 // Otherwise, return the point after the preformatted linefeed.
673 return atNextPreformattedNewLine
.NextPoint();
675 // want to be after the text node
676 point
.SetAfter(point
.GetContainer());
677 NS_WARNING_ASSERTION(point
.IsSet(), "Failed to set to after the text node");
680 // Look ahead through any further inline nodes that aren't across a <br> from
681 // us, and that are enclosed in the same block.
682 // XXX Currently, we stop block-extending when finding visible <br> element.
683 // This might be different from "block-extend" of execCommand spec.
684 // However, the spec is really unclear.
685 // XXX Probably, scanning only editable nodes is wrong for
686 // EditSubAction::eCreateOrRemoveBlock because it might be better to wrap
687 // existing inline elements even if it's non-editable. For example,
688 // following examples with insertParagraph causes different result:
689 // * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
690 // * <div contenteditable>foo[]<b>bar</b></div>
691 // * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
692 // Only in the first case, after the caret position isn't wrapped with
693 // new <div> element.
694 constexpr HTMLEditUtils::WalkTreeOptions
695 ignoreNonEditableNodeAndStopAtBlockBoundary
{
696 HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode
,
697 HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary
};
698 for (nsIContent
* nextEditableContent
= HTMLEditUtils::GetNextContent(
699 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
);
700 nextEditableContent
&&
701 !HTMLEditUtils::IsBlockElement(*nextEditableContent
) &&
702 nextEditableContent
->GetParent();
703 nextEditableContent
= HTMLEditUtils::GetNextContent(
704 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
)) {
705 EditorDOMPoint atFirstPreformattedNewLine
=
706 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode
<
707 EditorDOMPoint
>(EditorRawDOMPoint(nextEditableContent
, 0));
708 if (atFirstPreformattedNewLine
.IsSet()) {
709 // If the linefeed is last character of the text node, it may be
710 // invisible if it's immediately before a block boundary. In such
711 // case, we should retrun the block boundary.
712 Element
* maybeNonEditableBlockElement
= nullptr;
713 if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
714 atFirstPreformattedNewLine
, &maybeNonEditableBlockElement
) &&
715 maybeNonEditableBlockElement
) {
716 // If the block is a parent of the editing host, let's return end
718 if (maybeNonEditableBlockElement
== &aEditingHost
||
719 !maybeNonEditableBlockElement
->IsInclusiveDescendantOf(
721 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
723 // If it's invisible because of parent block boundary, return end
724 // of the block. Otherwise, i.e., it's followed by a child block,
725 // returns the point of the child block.
726 if (atFirstPreformattedNewLine
.ContainerAs
<Text
>()
727 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement
)) {
728 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
730 return EditorDOMPoint(maybeNonEditableBlockElement
);
732 // Otherwise, return the point after the preformatted linefeed.
733 return atFirstPreformattedNewLine
.NextPoint();
735 point
.SetAfter(nextEditableContent
);
736 if (NS_WARN_IF(!point
.IsSet())) {
739 if (HTMLEditUtils::IsVisibleBRElement(*nextEditableContent
)) {
744 // Finding the real end for this point unless current line ends with a <br>
745 // element. Look up the tree for as long as we are the last node in the
746 // container (typically, block node), and as long as we haven't hit the body
748 for (nsIContent
* nearContent
= HTMLEditUtils::GetNextContent(
749 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
);
750 !nearContent
&& !point
.IsContainerHTMLElement(nsGkAtoms::body
) &&
751 point
.GetContainerParent();
752 nearContent
= HTMLEditUtils::GetNextContent(
753 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
, &aEditingHost
)) {
754 // Don't walk past the editable section. Note that we need to check before
755 // walking up to a parent because we need to return the parent object, so
756 // the parent itself might not be in the editable area, but it's OK.
757 // XXX Maybe returning parent of editing host is really error prone since
758 // everybody need to check whether the end point is in editing host
759 // when they touch there.
760 if (!point
.GetContainer()->IsInclusiveDescendantOf(&aEditingHost
) &&
761 !point
.GetContainerParent()->IsInclusiveDescendantOf(&aEditingHost
)) {
765 point
.SetAfter(point
.GetContainer());
766 if (NS_WARN_IF(!point
.IsSet())) {
773 void AutoRangeArray::ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
774 EditSubAction aEditSubAction
, const Element
& aEditingHost
) {
775 // FYI: This is originated in
776 // https://searchfox.org/mozilla-central/rev/1739f1301d658c9bff544a0a095ab11fca2e549d/editor/libeditor/HTMLEditSubActionHandler.cpp#6712
778 bool removeSomeRanges
= false;
779 for (OwningNonNull
<nsRange
>& range
: mRanges
) {
780 // Remove non-positioned ranges.
781 if (MOZ_UNLIKELY(!range
->IsPositioned())) {
782 removeSomeRanges
= true;
785 // If the range is native anonymous subtrees, we must meet a bug of
786 // `Selection` so that we need to hack here.
787 if (MOZ_UNLIKELY(range
->GetStartContainer()->IsInNativeAnonymousSubtree() ||
788 range
->GetEndContainer()->IsInNativeAnonymousSubtree())) {
789 EditorRawDOMRange
rawRange(range
);
790 if (!rawRange
.EnsureNotInNativeAnonymousSubtree()) {
792 removeSomeRanges
= true;
796 range
->SetStartAndEnd(rawRange
.StartRef().ToRawRangeBoundary(),
797 rawRange
.EndRef().ToRawRangeBoundary())) ||
798 MOZ_UNLIKELY(!range
->IsPositioned())) {
800 removeSomeRanges
= true;
804 // Finally, extend the range.
805 if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
806 range
, aEditSubAction
, aEditingHost
))) {
807 // If we failed to extend the range, we should use the original range
808 // as-is unless the range is broken at setting the range.
809 if (NS_WARN_IF(!range
->IsPositioned())) {
810 removeSomeRanges
= true;
814 if (removeSomeRanges
) {
815 for (size_t i
: Reversed(IntegerRange(mRanges
.Length()))) {
816 if (!mRanges
[i
]->IsPositioned()) {
817 mRanges
.RemoveElementAt(i
);
824 nsresult
AutoRangeArray::ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
825 nsRange
& aRange
, EditSubAction aEditSubAction
,
826 const Element
& aEditingHost
) {
827 MOZ_DIAGNOSTIC_ASSERT(
828 !EditorRawDOMPoint(aRange
.StartRef()).IsInNativeAnonymousSubtree());
829 MOZ_DIAGNOSTIC_ASSERT(
830 !EditorRawDOMPoint(aRange
.EndRef()).IsInNativeAnonymousSubtree());
832 if (NS_WARN_IF(!aRange
.IsPositioned())) {
833 return NS_ERROR_INVALID_ARG
;
836 EditorDOMPoint
startPoint(aRange
.StartRef()), endPoint(aRange
.EndRef());
837 AutoRangeArray::UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
838 startPoint
, endPoint
, aEditingHost
);
840 // Make a new adjusted range to represent the appropriate block content.
841 // This is tricky. The basic idea is to push out the range endpoints to
842 // truly enclose the blocks that we will affect.
844 // Make sure that the new range ends up to be in the editable section.
845 // XXX Looks like that this check wastes the time. Perhaps, we should
846 // implement a method which checks both two DOM points in the editor
849 startPoint
= GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock(
850 startPoint
, aEditSubAction
, aEditingHost
);
851 // XXX GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() may
852 // return point of editing host. Perhaps, we should change it and stop
853 // checking it here since this check may be expensive.
854 // XXX If the container is an element in the editing host but it points end of
855 // the container, this returns nullptr. Is it intentional?
856 if (!startPoint
.GetChildOrContainerIfDataNode() ||
857 !startPoint
.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
859 return NS_ERROR_FAILURE
;
862 GetPointAfterFollowingLineBreakOrAtFollowingBlock(endPoint
, aEditingHost
);
863 const EditorDOMPoint lastRawPoint
=
864 endPoint
.IsStartOfContainer() ? endPoint
: endPoint
.PreviousPoint();
865 // XXX GetPointAfterFollowingLineBreakOrAtFollowingBlock() may return point of
866 // editing host. Perhaps, we should change it and stop checking it here
867 // since this check may be expensive.
868 // XXX If the container is an element in the editing host but it points end of
869 // the container, this returns nullptr. Is it intentional?
870 if (!lastRawPoint
.GetChildOrContainerIfDataNode() ||
871 !lastRawPoint
.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
873 return NS_ERROR_FAILURE
;
876 nsresult rv
= aRange
.SetStartAndEnd(startPoint
.ToRawRangeBoundary(),
877 endPoint
.ToRawRangeBoundary());
879 return NS_ERROR_FAILURE
;
884 Result
<EditorDOMPoint
, nsresult
> AutoRangeArray::
885 SplitTextNodesAtEndBoundariesAndParentInlineElementsAtBoundaries(
886 HTMLEditor
& aHTMLEditor
) {
887 // FYI: The following code is originated in
888 // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#6971
890 // Split text nodes. This is necessary, since given ranges may end in text
891 // nodes in case where part of a pre-formatted elements needs to be moved.
892 EditorDOMPoint pointToPutCaret
;
893 IgnoredErrorResult ignoredError
;
894 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
895 EditorDOMPoint
atEnd(range
->EndRef());
896 if (NS_WARN_IF(!atEnd
.IsSet()) || !atEnd
.IsInTextNode()) {
900 if (!atEnd
.IsStartOfContainer() && !atEnd
.IsEndOfContainer()) {
901 // Split the text node.
902 Result
<SplitNodeResult
, nsresult
> splitAtEndResult
=
903 aHTMLEditor
.SplitNodeWithTransaction(atEnd
);
904 if (MOZ_UNLIKELY(splitAtEndResult
.isErr())) {
905 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
906 return splitAtEndResult
.propagateErr();
908 SplitNodeResult unwrappedSplitAtEndResult
= splitAtEndResult
.unwrap();
909 unwrappedSplitAtEndResult
.MoveCaretPointTo(
910 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
912 // Correct the range.
913 // The new end parent becomes the parent node of the text.
914 MOZ_ASSERT(!range
->IsInSelection());
915 range
->SetEnd(unwrappedSplitAtEndResult
.AtNextContent
<EditorRawDOMPoint
>()
916 .ToRawRangeBoundary(),
918 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
919 "nsRange::SetEnd() failed, but ignored");
920 ignoredError
.SuppressException();
924 // FYI: The following code is originated in
925 // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#7023
926 nsTArray
<OwningNonNull
<RangeItem
>> rangeItemArray
;
927 rangeItemArray
.AppendElements(mRanges
.Length());
929 // First register ranges for special editor gravity
930 for (OwningNonNull
<RangeItem
>& rangeItem
: rangeItemArray
) {
931 rangeItem
= new RangeItem();
932 rangeItem
->StoreRange(*mRanges
[0]);
933 aHTMLEditor
.RangeUpdaterRef().RegisterRangeItem(*rangeItem
);
934 // TODO: We should keep the array, and just update the ranges.
935 mRanges
.RemoveElementAt(0);
937 // Now bust up inlines.
939 for (OwningNonNull
<RangeItem
>& item
: Reversed(rangeItemArray
)) {
940 // MOZ_KnownLive because 'rangeItemArray' is guaranteed to keep it alive.
941 Result
<EditorDOMPoint
, nsresult
> splitParentsResult
=
942 aHTMLEditor
.SplitParentInlineElementsAtRangeEdges(MOZ_KnownLive(*item
));
943 if (MOZ_UNLIKELY(splitParentsResult
.isErr())) {
944 NS_WARNING("HTMLEditor::SplitParentInlineElementsAtRangeEdges() failed");
945 rv
= splitParentsResult
.unwrapErr();
948 if (splitParentsResult
.inspect().IsSet()) {
949 pointToPutCaret
= splitParentsResult
.unwrap();
952 // Then unregister the ranges
953 for (OwningNonNull
<RangeItem
>& item
: rangeItemArray
) {
954 aHTMLEditor
.RangeUpdaterRef().DropRangeItem(item
);
955 RefPtr
<nsRange
> range
= item
->GetRange();
957 mRanges
.AppendElement(std::move(range
));
961 // XXX Why do we ignore the other errors here??
962 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
963 return Err(NS_ERROR_EDITOR_DESTROYED
);
965 return pointToPutCaret
;
968 nsresult
AutoRangeArray::CollectEditTargetNodes(
969 const HTMLEditor
& aHTMLEditor
,
970 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
,
971 EditSubAction aEditSubAction
,
972 CollectNonEditableNodes aCollectNonEditableNodes
) const {
973 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
975 // FYI: This was moved from
976 // https://searchfox.org/mozilla-central/rev/4bce7d85ba4796dd03c5dcc7cfe8eee0e4c07b3b/editor/libeditor/HTMLEditSubActionHandler.cpp#7060
978 // Gather up a list of all the nodes
979 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
980 DOMSubtreeIterator iter
;
981 nsresult rv
= iter
.Init(*range
);
983 NS_WARNING("DOMSubtreeIterator::Init() failed");
986 if (aOutArrayOfContents
.IsEmpty()) {
987 iter
.AppendAllNodesToArray(aOutArrayOfContents
);
989 AutoTArray
<OwningNonNull
<nsIContent
>, 24> arrayOfTopChildren
;
990 iter
.AppendNodesToArray(
991 +[](nsINode
& aNode
, void* aArray
) -> bool {
993 return !static_cast<nsTArray
<OwningNonNull
<nsIContent
>>*>(aArray
)
996 arrayOfTopChildren
, &aOutArrayOfContents
);
997 aOutArrayOfContents
.AppendElements(std::move(arrayOfTopChildren
));
999 if (aCollectNonEditableNodes
== CollectNonEditableNodes::No
) {
1000 for (size_t i
: Reversed(IntegerRange(aOutArrayOfContents
.Length()))) {
1001 if (!EditorUtils::IsEditableContent(aOutArrayOfContents
[i
],
1002 EditorUtils::EditorType::HTML
)) {
1003 aOutArrayOfContents
.RemoveElementAt(i
);
1009 switch (aEditSubAction
) {
1010 case EditSubAction::eCreateOrRemoveBlock
: {
1011 // Certain operations should not act on li's and td's, but rather inside
1012 // them. Alter the list as needed.
1013 CollectChildrenOptions options
= {
1014 CollectChildrenOption::CollectListChildren
,
1015 CollectChildrenOption::CollectTableChildren
};
1016 if (aCollectNonEditableNodes
== CollectNonEditableNodes::No
) {
1017 options
+= CollectChildrenOption::IgnoreNonEditableChildren
;
1019 for (int32_t i
= aOutArrayOfContents
.Length() - 1; i
>= 0; i
--) {
1020 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[i
];
1021 if (HTMLEditUtils::IsListItem(content
)) {
1022 aOutArrayOfContents
.RemoveElementAt(i
);
1023 HTMLEditUtils::CollectChildren(*content
, aOutArrayOfContents
, i
,
1027 // Empty text node shouldn't be selected if unnecessary
1028 for (int32_t i
= aOutArrayOfContents
.Length() - 1; i
>= 0; i
--) {
1029 if (Text
* text
= aOutArrayOfContents
[i
]->GetAsText()) {
1030 // Don't select empty text except to empty block
1031 if (!HTMLEditUtils::IsVisibleTextNode(*text
)) {
1032 aOutArrayOfContents
.RemoveElementAt(i
);
1038 case EditSubAction::eCreateOrChangeList
: {
1039 // XXX aCollectNonEditableNodes is ignored here. Maybe a bug.
1040 CollectChildrenOptions options
= {
1041 CollectChildrenOption::CollectTableChildren
};
1042 for (size_t i
= aOutArrayOfContents
.Length(); i
> 0; i
--) {
1043 // Scan for table elements. If we find table elements other than
1044 // table, replace it with a list of any editable non-table content
1045 // because if a selection range starts from end in a table-cell and
1046 // ends at or starts from outside the `<table>`, we need to make
1047 // lists in each selected table-cells.
1048 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[i
- 1];
1049 if (HTMLEditUtils::IsAnyTableElementButNotTable(content
)) {
1050 aOutArrayOfContents
.RemoveElementAt(i
- 1);
1051 HTMLEditUtils::CollectChildren(content
, aOutArrayOfContents
, i
- 1,
1055 // If there is only one node in the array, and it is a `<div>`,
1056 // `<blockquote>` or a list element, then look inside of it until we
1057 // find inner list or content.
1058 if (aOutArrayOfContents
.Length() != 1) {
1061 Element
* deepestDivBlockquoteOrListElement
=
1062 HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
1063 aOutArrayOfContents
[0],
1064 {HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode
},
1065 nsGkAtoms::div
, nsGkAtoms::blockquote
, nsGkAtoms::ul
,
1066 nsGkAtoms::ol
, nsGkAtoms::dl
);
1067 if (!deepestDivBlockquoteOrListElement
) {
1070 if (deepestDivBlockquoteOrListElement
->IsAnyOfHTMLElements(
1071 nsGkAtoms::div
, nsGkAtoms::blockquote
)) {
1072 aOutArrayOfContents
.Clear();
1073 // XXX Before we're called, non-editable nodes are ignored. However,
1074 // we may append non-editable nodes here.
1075 HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement
,
1076 aOutArrayOfContents
, 0, {});
1079 aOutArrayOfContents
.ReplaceElementAt(
1080 0, OwningNonNull
<nsIContent
>(*deepestDivBlockquoteOrListElement
));
1083 case EditSubAction::eOutdent
:
1084 case EditSubAction::eIndent
:
1085 case EditSubAction::eSetPositionToAbsolute
: {
1086 // Indent/outdent already do something special for list items, but we
1087 // still need to make sure we don't act on table elements
1088 CollectChildrenOptions options
= {
1089 CollectChildrenOption::CollectListChildren
,
1090 CollectChildrenOption::CollectTableChildren
};
1091 if (aCollectNonEditableNodes
== CollectNonEditableNodes::No
) {
1092 options
+= CollectChildrenOption::IgnoreNonEditableChildren
;
1094 for (int32_t i
= aOutArrayOfContents
.Length() - 1; i
>= 0; i
--) {
1095 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[i
];
1096 if (HTMLEditUtils::IsAnyTableElementButNotTable(content
)) {
1097 aOutArrayOfContents
.RemoveElementAt(i
);
1098 HTMLEditUtils::CollectChildren(*content
, aOutArrayOfContents
, i
,
1108 // Outdent should look inside of divs.
1109 if (aEditSubAction
== EditSubAction::eOutdent
&&
1110 !aHTMLEditor
.IsCSSEnabled()) {
1111 CollectChildrenOptions options
= {};
1112 if (aCollectNonEditableNodes
== CollectNonEditableNodes::No
) {
1113 options
+= CollectChildrenOption::IgnoreNonEditableChildren
;
1115 for (int32_t i
= aOutArrayOfContents
.Length() - 1; i
>= 0; i
--) {
1116 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[i
];
1117 if (content
->IsHTMLElement(nsGkAtoms::div
)) {
1118 aOutArrayOfContents
.RemoveElementAt(i
);
1119 HTMLEditUtils::CollectChildren(*content
, aOutArrayOfContents
, i
,
1128 Element
* AutoRangeArray::GetClosestAncestorAnyListElementOfRange() const {
1129 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
1130 nsINode
* commonAncestorNode
= range
->GetClosestCommonInclusiveAncestor();
1131 if (MOZ_UNLIKELY(!commonAncestorNode
)) {
1134 for (Element
* element
:
1135 commonAncestorNode
->InclusiveAncestorsOfType
<Element
>()) {
1136 if (HTMLEditUtils::IsAnyListElement(element
)) {
1144 } // namespace mozilla