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 "AutoClonedRangeArray.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 "WSRunScanner.h" // for WSRunScanner
16 #include "mozilla/CaretAssociationHint.h" // for CaretAssociationHint
17 #include "mozilla/IntegerRange.h" // for IntegerRange
18 #include "mozilla/OwningNonNull.h" // for OwningNonNull
19 #include "mozilla/PresShell.h" // for PresShell
20 #include "mozilla/dom/Document.h" // for dom::Document
21 #include "mozilla/dom/HTMLBRElement.h" // for dom HTMLBRElement
22 #include "mozilla/dom/Selection.h" // for dom::Selection
23 #include "mozilla/dom/Text.h" // for dom::Text
25 #include "gfxFontUtils.h" // for gfxFontUtils
26 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_*
27 #include "nsFrameSelection.h" // for nsFrameSelection
28 #include "nsIContent.h" // for nsIContent
29 #include "nsINode.h" // for nsINode
30 #include "nsRange.h" // for nsRange
31 #include "nsTextFragment.h" // for nsTextFragment
37 using EmptyCheckOption
= HTMLEditUtils::EmptyCheckOption
;
39 /******************************************************************************
40 * mozilla::AutoClonedRangeArray
41 *****************************************************************************/
43 template AutoClonedRangeArray::AutoClonedRangeArray(
44 const EditorDOMRange
& aRange
);
45 template AutoClonedRangeArray::AutoClonedRangeArray(
46 const EditorRawDOMRange
& aRange
);
47 template AutoClonedRangeArray::AutoClonedRangeArray(
48 const EditorDOMPoint
& aRange
);
49 template AutoClonedRangeArray::AutoClonedRangeArray(
50 const EditorRawDOMPoint
& aRange
);
52 AutoClonedRangeArray::AutoClonedRangeArray(const AutoClonedRangeArray
& aOther
)
53 : mAnchorFocusRange(aOther
.mAnchorFocusRange
),
54 mDirection(aOther
.mDirection
) {
55 mRanges
.SetCapacity(aOther
.mRanges
.Length());
56 for (const OwningNonNull
<nsRange
>& range
: aOther
.mRanges
) {
57 RefPtr
<nsRange
> clonedRange
= range
->CloneRange();
58 mRanges
.AppendElement(std::move(clonedRange
));
60 mAnchorFocusRange
= aOther
.mAnchorFocusRange
;
63 template <typename PointType
>
64 AutoClonedRangeArray::AutoClonedRangeArray(
65 const EditorDOMRangeBase
<PointType
>& aRange
) {
66 MOZ_ASSERT(aRange
.IsPositionedAndValid());
67 RefPtr
<nsRange
> range
= aRange
.CreateRange(IgnoreErrors());
68 if (NS_WARN_IF(!range
) || NS_WARN_IF(!range
->IsPositioned())) {
71 mRanges
.AppendElement(*range
);
72 mAnchorFocusRange
= std::move(range
);
75 template <typename PT
, typename CT
>
76 AutoClonedRangeArray::AutoClonedRangeArray(
77 const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
78 MOZ_ASSERT(aPoint
.IsSetAndValid());
79 RefPtr
<nsRange
> range
= aPoint
.CreateCollapsedRange(IgnoreErrors());
80 if (NS_WARN_IF(!range
) || NS_WARN_IF(!range
->IsPositioned())) {
83 mRanges
.AppendElement(*range
);
84 mAnchorFocusRange
= std::move(range
);
87 AutoClonedRangeArray::AutoClonedRangeArray(const nsRange
& aRange
) {
88 MOZ_ASSERT(aRange
.IsPositioned());
89 mRanges
.AppendElement(aRange
.CloneRange());
90 mAnchorFocusRange
= mRanges
[0];
94 bool AutoClonedRangeArray::IsEditableRange(const dom::AbstractRange
& aRange
,
95 const Element
& aEditingHost
) {
96 // TODO: Perhaps, we should check whether the start/end boundaries are
97 // first/last point of non-editable element.
98 // See https://github.com/w3c/editing/issues/283#issuecomment-788654850
99 EditorRawDOMPoint
atStart(aRange
.StartRef());
100 const bool isStartEditable
=
101 atStart
.IsInContentNode() &&
102 EditorUtils::IsEditableContent(*atStart
.ContainerAs
<nsIContent
>(),
103 EditorUtils::EditorType::HTML
) &&
104 !HTMLEditUtils::IsNonEditableReplacedContent(
105 *atStart
.ContainerAs
<nsIContent
>());
106 if (!isStartEditable
) {
110 if (aRange
.GetStartContainer() != aRange
.GetEndContainer()) {
111 EditorRawDOMPoint
atEnd(aRange
.EndRef());
112 const bool isEndEditable
=
113 atEnd
.IsInContentNode() &&
114 EditorUtils::IsEditableContent(*atEnd
.ContainerAs
<nsIContent
>(),
115 EditorUtils::EditorType::HTML
) &&
116 !HTMLEditUtils::IsNonEditableReplacedContent(
117 *atEnd
.ContainerAs
<nsIContent
>());
118 if (!isEndEditable
) {
122 // Now, both start and end points are editable, but if they are in
123 // different editing host, we cannot edit the range.
124 if (atStart
.ContainerAs
<nsIContent
>() != atEnd
.ContainerAs
<nsIContent
>() &&
125 atStart
.ContainerAs
<nsIContent
>()->GetEditingHost() !=
126 atEnd
.ContainerAs
<nsIContent
>()->GetEditingHost()) {
131 // HTMLEditor does not support modifying outside `<body>` element for now.
132 nsINode
* commonAncestor
= aRange
.GetClosestCommonInclusiveAncestor();
133 return commonAncestor
&& commonAncestor
->IsContent() &&
134 commonAncestor
->IsInclusiveDescendantOf(&aEditingHost
);
137 void AutoClonedRangeArray::EnsureOnlyEditableRanges(
138 const Element
& aEditingHost
) {
139 for (const size_t index
: Reversed(IntegerRange(mRanges
.Length()))) {
140 const OwningNonNull
<nsRange
>& range
= mRanges
[index
];
141 if (!AutoClonedRangeArray::IsEditableRange(range
, aEditingHost
)) {
142 mRanges
.RemoveElementAt(index
);
145 // Special handling for `inert` attribute. If anchor node is inert, the
146 // range should be treated as not editable.
147 nsIContent
* anchorContent
=
148 mDirection
== eDirNext
149 ? nsIContent::FromNode(range
->GetStartContainer())
150 : nsIContent::FromNode(range
->GetEndContainer());
151 if (anchorContent
&& HTMLEditUtils::ContentIsInert(*anchorContent
)) {
152 mRanges
.RemoveElementAt(index
);
155 // Additionally, if focus node is inert, the range should be collapsed to
157 nsIContent
* focusContent
=
158 mDirection
== eDirNext
159 ? nsIContent::FromNode(range
->GetEndContainer())
160 : nsIContent::FromNode(range
->GetStartContainer());
161 if (focusContent
&& focusContent
!= anchorContent
&&
162 HTMLEditUtils::ContentIsInert(*focusContent
)) {
163 range
->Collapse(mDirection
== eDirNext
);
166 mAnchorFocusRange
= mRanges
.IsEmpty() ? nullptr : mRanges
.LastElement().get();
169 void AutoClonedRangeArray::EnsureRangesInTextNode(const Text
& aTextNode
) {
170 auto GetOffsetInTextNode
= [&aTextNode
](const nsINode
* aNode
,
171 uint32_t aOffset
) -> uint32_t {
172 MOZ_DIAGNOSTIC_ASSERT(aNode
);
173 if (aNode
== &aTextNode
) {
176 const nsIContent
* anonymousDivElement
= aTextNode
.GetParent();
177 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement
);
178 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement
->IsHTMLElement(nsGkAtoms::div
));
179 MOZ_DIAGNOSTIC_ASSERT(anonymousDivElement
->GetFirstChild() == &aTextNode
);
180 if (aNode
== anonymousDivElement
&& aOffset
== 0u) {
181 return 0u; // Point before the text node so that use start of the text.
183 MOZ_DIAGNOSTIC_ASSERT(aNode
->IsInclusiveDescendantOf(anonymousDivElement
));
184 // Point after the text node so that use end of the text.
185 return aTextNode
.TextDataLength();
187 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
188 if (MOZ_LIKELY(range
->GetStartContainer() == &aTextNode
&&
189 range
->GetEndContainer() == &aTextNode
)) {
192 range
->SetStartAndEnd(
193 const_cast<Text
*>(&aTextNode
),
194 GetOffsetInTextNode(range
->GetStartContainer(), range
->StartOffset()),
195 const_cast<Text
*>(&aTextNode
),
196 GetOffsetInTextNode(range
->GetEndContainer(), range
->EndOffset()));
199 if (MOZ_UNLIKELY(mRanges
.Length() >= 2)) {
200 // For avoiding to handle same things in same range, we should drop and
201 // merge unnecessary ranges. Note that the ranges never overlap
202 // because selection ranges are not allowed it so that we need to check only
203 // end offset vs start offset of next one.
204 for (const size_t i
: Reversed(IntegerRange(mRanges
.Length() - 1u))) {
205 MOZ_ASSERT(mRanges
[i
]->EndOffset() < mRanges
[i
+ 1]->StartOffset());
206 // XXX Should we delete collapsed range unless the index is 0? Without
207 // Selection API, such situation cannot happen so that `TextEditor`
208 // may behave unexpectedly.
209 if (MOZ_UNLIKELY(mRanges
[i
]->EndOffset() >=
210 mRanges
[i
+ 1]->StartOffset())) {
211 const uint32_t newEndOffset
= mRanges
[i
+ 1]->EndOffset();
212 mRanges
.RemoveElementAt(i
+ 1);
213 if (MOZ_UNLIKELY(NS_WARN_IF(newEndOffset
> mRanges
[i
]->EndOffset()))) {
214 // So, this case shouldn't happen.
215 mRanges
[i
]->SetStartAndEnd(
216 const_cast<Text
*>(&aTextNode
), mRanges
[i
]->StartOffset(),
217 const_cast<Text
*>(&aTextNode
), newEndOffset
);
224 Result
<bool, nsresult
>
225 AutoClonedRangeArray::ShrinkRangesIfStartFromOrEndAfterAtomicContent(
226 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
227 IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent
) {
232 switch (aDirectionAndAmount
) {
233 case nsIEditor::eNext
:
234 case nsIEditor::eNextWord
:
235 case nsIEditor::ePrevious
:
236 case nsIEditor::ePreviousWord
:
242 bool changed
= false;
243 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
244 MOZ_ASSERT(!range
->IsInAnySelection(),
245 "Changing range in selection may cause running script");
246 Result
<bool, nsresult
> result
=
247 WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent(
249 if (result
.isErr()) {
251 "WSRunScanner::ShrinkRangeIfStartsFromOrEndsAfterAtomicContent() "
253 return Err(result
.inspectErr());
255 changed
|= result
.inspect();
258 if (mRanges
.Length() == 1 && aIfSelectingOnlyOneAtomicContent
==
259 IfSelectingOnlyOneAtomicContent::Collapse
) {
260 MOZ_ASSERT(mRanges
[0].get() == mAnchorFocusRange
.get());
261 if (mAnchorFocusRange
->GetStartContainer() ==
262 mAnchorFocusRange
->GetEndContainer() &&
263 mAnchorFocusRange
->GetChildAtStartOffset() &&
264 mAnchorFocusRange
->StartRef().GetNextSiblingOfChildAtOffset() ==
265 mAnchorFocusRange
->GetChildAtEndOffset()) {
266 mAnchorFocusRange
->Collapse(aDirectionAndAmount
== nsIEditor::eNext
||
267 aDirectionAndAmount
== nsIEditor::eNextWord
);
276 void AutoClonedRangeArray::
277 UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
278 EditorDOMPoint
& aStartPoint
, EditorDOMPoint
& aEndPoint
,
279 const Element
& aEditingHost
) {
280 // FYI: This was moved from
281 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6743
284 // The GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() and
285 // GetPointAfterFollowingLineBreakOrAtFollowingBlock() don't really do the
286 // right thing for collapsed ranges inside block elements that contain nothing
287 // but a solo <br>. It's easier/ to put a workaround here than to revamp
289 if (aStartPoint
!= aEndPoint
) {
293 if (!aStartPoint
.IsInContentNode()) {
297 // XXX Perhaps, this should be more careful. This may not select only one
298 // node because this just check whether the block is empty or not,
299 // and may not select in non-editable block. However, for inline
300 // editing host case, it's right to look for block element without
301 // editable state check. Now, this method is used for preparation for
302 // other things. So, cannot write test for this method behavior.
303 // So, perhaps, we should get rid of this method and each caller should
304 // handle its job better.
305 Element
* const maybeNonEditableBlockElement
=
306 HTMLEditUtils::GetInclusiveAncestorElement(
307 *aStartPoint
.ContainerAs
<nsIContent
>(),
308 HTMLEditUtils::ClosestBlockElement
,
309 BlockInlineCheck::UseComputedDisplayStyle
);
310 if (!maybeNonEditableBlockElement
) {
314 // Make sure we don't go higher than our root element in the content tree
315 if (aEditingHost
.IsInclusiveDescendantOf(maybeNonEditableBlockElement
)) {
319 if (HTMLEditUtils::IsEmptyNode(
320 *maybeNonEditableBlockElement
,
321 {EmptyCheckOption::TreatNonEditableContentAsInvisible
})) {
322 aStartPoint
.Set(maybeNonEditableBlockElement
, 0u);
323 aEndPoint
.SetToEndOf(maybeNonEditableBlockElement
);
328 * Get the point before the line containing aPointInLine.
330 * @return If the line starts after a `<br>` element, returns next
331 * sibling of the `<br>` element.
332 * If the line is first line of a block, returns point of
334 * NOTE: The result may be point of editing host. I.e., the container may be
335 * outside of editing host.
337 MOZ_NEVER_INLINE_DEBUG
static EditorDOMPoint
338 GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock(
339 const EditorDOMPoint
& aPointInLine
, EditSubAction aEditSubAction
,
340 BlockInlineCheck aBlockInlineCheck
, const Element
& aAncestorLimiter
) {
341 // FYI: This was moved from
342 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6447
344 if (NS_WARN_IF(!aPointInLine
.IsSet())) {
345 return EditorDOMPoint();
348 EditorDOMPoint
point(aPointInLine
);
349 // Start scanning from the container node if aPoint is in a text node.
350 // XXX Perhaps, IsInDataNode() must be expected.
351 if (point
.IsInTextNode()) {
352 if (!point
.GetContainer()->GetParentNode()) {
353 // Okay, can't promote any further
354 // XXX Why don't we return start of the text node?
357 // If there is a preformatted linefeed in the text node, let's return
358 // the point after it.
359 EditorDOMPoint atLastPreformattedNewLine
=
360 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode
<EditorDOMPoint
>(
362 if (atLastPreformattedNewLine
.IsSet()) {
363 return atLastPreformattedNewLine
.NextPoint();
365 point
.Set(point
.GetContainer());
368 // Look back through any further inline nodes that aren't across a <br>
369 // from us, and that are enclosed in the same block.
370 // I.e., looking for start of current hard line.
371 constexpr HTMLEditUtils::WalkTreeOptions
372 ignoreNonEditableNodeAndStopAtBlockBoundary
{
373 HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode
,
374 HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary
};
375 for (nsIContent
* previousEditableContent
= HTMLEditUtils::GetPreviousContent(
376 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
,
377 aBlockInlineCheck
, &aAncestorLimiter
);
378 previousEditableContent
&& previousEditableContent
->GetParentNode() &&
379 !HTMLEditUtils::IsVisibleBRElement(*previousEditableContent
) &&
380 !HTMLEditUtils::IsBlockElement(*previousEditableContent
,
382 previousEditableContent
= HTMLEditUtils::GetPreviousContent(
383 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
,
384 aBlockInlineCheck
, &aAncestorLimiter
)) {
385 EditorDOMPoint atLastPreformattedNewLine
=
386 HTMLEditUtils::GetPreviousPreformattedNewLineInTextNode
<EditorDOMPoint
>(
387 EditorRawDOMPoint::AtEndOf(*previousEditableContent
));
388 if (atLastPreformattedNewLine
.IsSet()) {
389 return atLastPreformattedNewLine
.NextPoint();
391 point
.Set(previousEditableContent
);
394 // Finding the real start for this point unless current line starts after
395 // <br> element. Look up the tree for as long as we are the first node in
396 // the container (typically, start of nearest block ancestor), and as long
397 // as we haven't hit the body node.
398 for (nsIContent
* nearContent
= HTMLEditUtils::GetPreviousContent(
399 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
,
400 aBlockInlineCheck
, &aAncestorLimiter
);
401 !nearContent
&& !point
.IsContainerHTMLElement(nsGkAtoms::body
) &&
402 point
.GetContainerParent();
403 nearContent
= HTMLEditUtils::GetPreviousContent(
404 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
,
405 aBlockInlineCheck
, &aAncestorLimiter
)) {
406 // Don't keep looking up if we have found a blockquote element to act on
407 // when we handle outdent.
408 // XXX Sounds like this is hacky. If possible, it should be check in
409 // outdent handler for consistency between edit sub-actions.
410 // We should check Chromium's behavior of outdent when Selection
411 // starts from `<blockquote>` and starts from first child of
413 if (aEditSubAction
== EditSubAction::eOutdent
&&
414 point
.IsContainerHTMLElement(nsGkAtoms::blockquote
)) {
418 // Don't walk past the editable section. Note that we need to check
419 // before walking up to a parent because we need to return the parent
420 // object, so the parent itself might not be in the editable area, but
421 // it's OK if we're not performing a block-level action.
422 bool blockLevelAction
=
423 aEditSubAction
== EditSubAction::eIndent
||
424 aEditSubAction
== EditSubAction::eOutdent
||
425 aEditSubAction
== EditSubAction::eSetOrClearAlignment
||
426 aEditSubAction
== EditSubAction::eCreateOrRemoveBlock
||
427 aEditSubAction
== EditSubAction::eFormatBlockForHTMLCommand
;
428 // XXX So, does this check whether the container is removable or not? It
429 // seems that here can be rewritten as obviously what here tries to
431 if (!point
.GetContainerParent()->IsInclusiveDescendantOf(
432 &aAncestorLimiter
) &&
434 !point
.GetContainer()->IsInclusiveDescendantOf(&aAncestorLimiter
))) {
438 // If we're formatting a block, we should reformat first ancestor format
440 if (aEditSubAction
== EditSubAction::eFormatBlockForHTMLCommand
&&
441 point
.IsContainerElement() &&
442 HTMLEditUtils::IsFormatElementForFormatBlockCommand(
443 *point
.ContainerAs
<Element
>())) {
444 point
.Set(point
.GetContainer());
448 point
.Set(point
.GetContainer());
454 * Get the point after the following line break or the block which breaks the
455 * line containing aPointInLine.
457 * @return If the line ends with a visible `<br>` element, returns
458 * the point after the `<br>` element.
459 * If the line ends with a preformatted linefeed, returns
460 * the point after the linefeed unless it's an invisible
461 * line break immediately before a block boundary.
462 * If the line ends with a block boundary, returns the
463 * point of the block.
465 MOZ_NEVER_INLINE_DEBUG
static EditorDOMPoint
466 GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock(
467 const EditorDOMPoint
& aPointInLine
, EditSubAction aEditSubAction
,
468 BlockInlineCheck aBlockInlineCheck
, const Element
& aAncestorLimiter
) {
469 // FYI: This was moved from
470 // https://searchfox.org/mozilla-central/rev/3419858c997f422e3e70020a46baae7f0ec6dacc/editor/libeditor/HTMLEditSubActionHandler.cpp#6541
472 if (NS_WARN_IF(!aPointInLine
.IsSet())) {
473 return EditorDOMPoint();
476 EditorDOMPoint
point(aPointInLine
);
477 // Start scanning from the container node if aPoint is in a text node.
478 // XXX Perhaps, IsInDataNode() must be expected.
479 if (point
.IsInTextNode()) {
480 if (NS_WARN_IF(!point
.GetContainer()->GetParentNode())) {
481 // Okay, can't promote any further
482 // XXX Why don't we return end of the text node?
485 EditorDOMPoint atNextPreformattedNewLine
=
486 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode
<
487 EditorDOMPoint
>(point
);
488 if (atNextPreformattedNewLine
.IsSet()) {
489 // If the linefeed is last character of the text node, it may be
490 // invisible if it's immediately before a block boundary. In such
491 // case, we should return the block boundary.
492 Element
* maybeNonEditableBlockElement
= nullptr;
493 if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
494 atNextPreformattedNewLine
, &maybeNonEditableBlockElement
) &&
495 maybeNonEditableBlockElement
) {
496 // If the block is a parent of the editing host, let's return end
498 if (maybeNonEditableBlockElement
== &aAncestorLimiter
||
499 !maybeNonEditableBlockElement
->IsInclusiveDescendantOf(
500 &aAncestorLimiter
)) {
501 return EditorDOMPoint::AtEndOf(aAncestorLimiter
);
503 // If it's invisible because of parent block boundary, return end
504 // of the block. Otherwise, i.e., it's followed by a child block,
505 // returns the point of the child block.
506 if (atNextPreformattedNewLine
.ContainerAs
<Text
>()
507 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement
)) {
508 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
510 return EditorDOMPoint(maybeNonEditableBlockElement
);
512 // Otherwise, return the point after the preformatted linefeed.
513 return atNextPreformattedNewLine
.NextPoint();
515 // want to be after the text node
516 point
.SetAfter(point
.GetContainer());
517 NS_WARNING_ASSERTION(point
.IsSet(), "Failed to set to after the text node");
520 // Look ahead through any further inline nodes that aren't across a <br> from
521 // us, and that are enclosed in the same block.
522 // XXX Currently, we stop block-extending when finding visible <br> element.
523 // This might be different from "block-extend" of execCommand spec.
524 // However, the spec is really unclear.
525 // XXX Probably, scanning only editable nodes is wrong for
526 // EditSubAction::eCreateOrRemoveBlock and
527 // EditSubAction::eFormatBlockForHTMLCommand because it might be better to
528 // wrap existing inline elements even if it's non-editable. For example,
529 // following examples with insertParagraph causes different result:
530 // * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
531 // * <div contenteditable>foo[]<b>bar</b></div>
532 // * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
533 // Only in the first case, after the caret position isn't wrapped with
534 // new <div> element.
535 constexpr HTMLEditUtils::WalkTreeOptions
536 ignoreNonEditableNodeAndStopAtBlockBoundary
{
537 HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode
,
538 HTMLEditUtils::WalkTreeOption::StopAtBlockBoundary
};
539 for (nsIContent
* nextEditableContent
= HTMLEditUtils::GetNextContent(
540 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
,
541 aBlockInlineCheck
, &aAncestorLimiter
);
542 nextEditableContent
&&
543 !HTMLEditUtils::IsBlockElement(*nextEditableContent
,
544 aBlockInlineCheck
) &&
545 nextEditableContent
->GetParent();
546 nextEditableContent
= HTMLEditUtils::GetNextContent(
547 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
,
548 aBlockInlineCheck
, &aAncestorLimiter
)) {
549 EditorDOMPoint atFirstPreformattedNewLine
=
550 HTMLEditUtils::GetInclusiveNextPreformattedNewLineInTextNode
<
551 EditorDOMPoint
>(EditorRawDOMPoint(nextEditableContent
, 0));
552 if (atFirstPreformattedNewLine
.IsSet()) {
553 // If the linefeed is last character of the text node, it may be
554 // invisible if it's immediately before a block boundary. In such
555 // case, we should return the block boundary.
556 Element
* maybeNonEditableBlockElement
= nullptr;
557 if (HTMLEditUtils::IsInvisiblePreformattedNewLine(
558 atFirstPreformattedNewLine
, &maybeNonEditableBlockElement
) &&
559 maybeNonEditableBlockElement
) {
560 // If the block is a parent of the editing host, let's return end
562 if (maybeNonEditableBlockElement
== &aAncestorLimiter
||
563 !maybeNonEditableBlockElement
->IsInclusiveDescendantOf(
564 &aAncestorLimiter
)) {
565 return EditorDOMPoint::AtEndOf(aAncestorLimiter
);
567 // If it's invisible because of parent block boundary, return end
568 // of the block. Otherwise, i.e., it's followed by a child block,
569 // returns the point of the child block.
570 if (atFirstPreformattedNewLine
.ContainerAs
<Text
>()
571 ->IsInclusiveDescendantOf(maybeNonEditableBlockElement
)) {
572 return EditorDOMPoint::AtEndOf(*maybeNonEditableBlockElement
);
574 return EditorDOMPoint(maybeNonEditableBlockElement
);
576 // Otherwise, return the point after the preformatted linefeed.
577 return atFirstPreformattedNewLine
.NextPoint();
579 point
.SetAfter(nextEditableContent
);
580 if (NS_WARN_IF(!point
.IsSet())) {
583 if (HTMLEditUtils::IsVisibleBRElement(*nextEditableContent
)) {
588 // Finding the real end for this point unless current line ends with a <br>
589 // element. Look up the tree for as long as we are the last node in the
590 // container (typically, block node), and as long as we haven't hit the body
592 for (nsIContent
* nearContent
= HTMLEditUtils::GetNextContent(
593 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
,
594 aBlockInlineCheck
, &aAncestorLimiter
);
595 !nearContent
&& !point
.IsContainerHTMLElement(nsGkAtoms::body
) &&
596 point
.GetContainerParent();
597 nearContent
= HTMLEditUtils::GetNextContent(
598 point
, ignoreNonEditableNodeAndStopAtBlockBoundary
,
599 aBlockInlineCheck
, &aAncestorLimiter
)) {
600 // Don't walk past the editable section. Note that we need to check before
601 // walking up to a parent because we need to return the parent object, so
602 // the parent itself might not be in the editable area, but it's OK.
603 // XXX Maybe returning parent of editing host is really error prone since
604 // everybody need to check whether the end point is in editing host
605 // when they touch there.
606 if (!point
.GetContainer()->IsInclusiveDescendantOf(&aAncestorLimiter
) &&
607 !point
.GetContainerParent()->IsInclusiveDescendantOf(
608 &aAncestorLimiter
)) {
612 // If we're formatting a block, we should reformat first ancestor format
614 if (aEditSubAction
== EditSubAction::eFormatBlockForHTMLCommand
&&
615 point
.IsContainerElement() &&
616 HTMLEditUtils::IsFormatElementForFormatBlockCommand(
617 *point
.ContainerAs
<Element
>())) {
618 point
.SetAfter(point
.GetContainer());
622 point
.SetAfter(point
.GetContainer());
623 if (NS_WARN_IF(!point
.IsSet())) {
630 void AutoClonedRangeArray::ExtendRangesToWrapLines(
631 EditSubAction aEditSubAction
, BlockInlineCheck aBlockInlineCheck
,
632 const Element
& aAncestorLimiter
) {
633 // FYI: This is originated in
634 // https://searchfox.org/mozilla-central/rev/1739f1301d658c9bff544a0a095ab11fca2e549d/editor/libeditor/HTMLEditSubActionHandler.cpp#6712
636 bool removeSomeRanges
= false;
637 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
638 // Remove non-positioned ranges.
639 if (MOZ_UNLIKELY(!range
->IsPositioned())) {
640 removeSomeRanges
= true;
643 // If the range is native anonymous subtrees, we must meet a bug of
644 // `Selection` so that we need to hack here.
645 if (MOZ_UNLIKELY(range
->GetStartContainer()->IsInNativeAnonymousSubtree() ||
646 range
->GetEndContainer()->IsInNativeAnonymousSubtree())) {
647 EditorRawDOMRange
rawRange(range
);
648 if (!rawRange
.EnsureNotInNativeAnonymousSubtree()) {
650 removeSomeRanges
= true;
654 range
->SetStartAndEnd(rawRange
.StartRef().ToRawRangeBoundary(),
655 rawRange
.EndRef().ToRawRangeBoundary())) ||
656 MOZ_UNLIKELY(!range
->IsPositioned())) {
658 removeSomeRanges
= true;
662 // Finally, extend the range.
663 if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
664 range
, aEditSubAction
, aBlockInlineCheck
, aAncestorLimiter
))) {
665 // If we failed to extend the range, we should use the original range
666 // as-is unless the range is broken at setting the range.
667 if (NS_WARN_IF(!range
->IsPositioned())) {
668 removeSomeRanges
= true;
672 if (removeSomeRanges
) {
673 for (const size_t i
: Reversed(IntegerRange(mRanges
.Length()))) {
674 if (!mRanges
[i
]->IsPositioned()) {
675 mRanges
.RemoveElementAt(i
);
678 if (!mAnchorFocusRange
|| !mAnchorFocusRange
->IsPositioned()) {
679 if (mRanges
.IsEmpty()) {
680 mAnchorFocusRange
= nullptr;
682 mAnchorFocusRange
= mRanges
.LastElement();
690 AutoClonedRangeArray::ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
691 nsRange
& aRange
, EditSubAction aEditSubAction
,
692 BlockInlineCheck aBlockInlineCheck
, const Element
& aEditingHost
) {
693 MOZ_DIAGNOSTIC_ASSERT(
694 !EditorRawDOMPoint(aRange
.StartRef()).IsInNativeAnonymousSubtree());
695 MOZ_DIAGNOSTIC_ASSERT(
696 !EditorRawDOMPoint(aRange
.EndRef()).IsInNativeAnonymousSubtree());
698 if (NS_WARN_IF(!aRange
.IsPositioned())) {
699 return NS_ERROR_INVALID_ARG
;
702 EditorDOMPoint
startPoint(aRange
.StartRef()), endPoint(aRange
.EndRef());
704 // If we're joining blocks, we call this for selecting a line to move.
705 // Therefore, we don't want to select the ancestor blocks in this case
706 // even if they are empty.
707 if (aEditSubAction
!= EditSubAction::eMergeBlockContents
) {
708 AutoClonedRangeArray::
709 UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
710 startPoint
, endPoint
, aEditingHost
);
713 // Make a new adjusted range to represent the appropriate block content.
714 // This is tricky. The basic idea is to push out the range endpoints to
715 // truly enclose the blocks that we will affect.
717 // Make sure that the new range ends up to be in the editable section.
718 // XXX Looks like that this check wastes the time. Perhaps, we should
719 // implement a method which checks both two DOM points in the editor
723 GetPointAtFirstContentOfLineOrParentHTMLBlockIfFirstContentOfBlock(
724 startPoint
, aEditSubAction
, aBlockInlineCheck
, aEditingHost
);
725 // XXX GetPointAtFirstContentOfLineOrParentBlockIfFirstContentOfBlock() may
726 // return point of editing host. Perhaps, we should change it and stop
727 // checking it here since this check may be expensive.
728 // XXX If the container is an element in the editing host but it points end of
729 // the container, this returns nullptr. Is it intentional?
730 if (!startPoint
.GetChildOrContainerIfDataNode() ||
731 !startPoint
.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
733 return NS_ERROR_FAILURE
;
735 endPoint
= GetPointAfterFollowingLineBreakOrAtFollowingHTMLBlock(
736 endPoint
, aEditSubAction
, aBlockInlineCheck
, aEditingHost
);
737 const EditorDOMPoint lastRawPoint
=
738 endPoint
.IsStartOfContainer() ? endPoint
: endPoint
.PreviousPoint();
739 // XXX GetPointAfterFollowingLineBreakOrAtFollowingBlock() may return point of
740 // editing host. Perhaps, we should change it and stop checking it here
741 // since this check may be expensive.
742 // XXX If the container is an element in the editing host but it points end of
743 // the container, this returns nullptr. Is it intentional?
744 if (!lastRawPoint
.GetChildOrContainerIfDataNode() ||
745 !lastRawPoint
.GetChildOrContainerIfDataNode()->IsInclusiveDescendantOf(
747 return NS_ERROR_FAILURE
;
750 nsresult rv
= aRange
.SetStartAndEnd(startPoint
.ToRawRangeBoundary(),
751 endPoint
.ToRawRangeBoundary());
753 return NS_ERROR_FAILURE
;
758 Result
<EditorDOMPoint
, nsresult
> AutoClonedRangeArray::
759 SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
760 HTMLEditor
& aHTMLEditor
, BlockInlineCheck aBlockInlineCheck
,
761 const Element
& aEditingHost
,
762 const nsIContent
* aAncestorLimiter
/* = nullptr */) {
763 // FYI: The following code is originated in
764 // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#6971
766 // Split text nodes. This is necessary, since given ranges may end in text
767 // nodes in case where part of a pre-formatted elements needs to be moved.
768 EditorDOMPoint pointToPutCaret
;
769 IgnoredErrorResult ignoredError
;
770 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
771 EditorDOMPoint
atEnd(range
->EndRef());
772 if (NS_WARN_IF(!atEnd
.IsSet()) || !atEnd
.IsInTextNode() ||
773 atEnd
.GetContainer() == aAncestorLimiter
) {
777 if (!atEnd
.IsStartOfContainer() && !atEnd
.IsEndOfContainer()) {
778 // Split the text node.
779 Result
<SplitNodeResult
, nsresult
> splitAtEndResult
=
780 aHTMLEditor
.SplitNodeWithTransaction(atEnd
);
781 if (MOZ_UNLIKELY(splitAtEndResult
.isErr())) {
782 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
783 return splitAtEndResult
.propagateErr();
785 SplitNodeResult unwrappedSplitAtEndResult
= splitAtEndResult
.unwrap();
786 unwrappedSplitAtEndResult
.MoveCaretPointTo(
787 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
789 // Correct the range.
790 // The new end parent becomes the parent node of the text.
791 MOZ_ASSERT(!range
->IsInAnySelection());
792 range
->SetEnd(unwrappedSplitAtEndResult
.AtNextContent
<EditorRawDOMPoint
>()
793 .ToRawRangeBoundary(),
795 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
796 "nsRange::SetEnd() failed, but ignored");
797 ignoredError
.SuppressException();
801 // FYI: The following code is originated in
802 // https://searchfox.org/mozilla-central/rev/c8e15e17bc6fd28f558c395c948a6251b38774ff/editor/libeditor/HTMLEditSubActionHandler.cpp#7023
803 AutoTArray
<OwningNonNull
<RangeItem
>, 8> rangeItemArray
;
804 rangeItemArray
.AppendElements(mRanges
.Length());
806 // First register ranges for special editor gravity
807 Maybe
<size_t> anchorFocusRangeIndex
;
808 for (const size_t index
: IntegerRange(rangeItemArray
.Length())) {
809 rangeItemArray
[index
] = new RangeItem();
810 rangeItemArray
[index
]->StoreRange(*mRanges
[index
]);
811 aHTMLEditor
.RangeUpdaterRef().RegisterRangeItem(*rangeItemArray
[index
]);
812 if (mRanges
[index
] == mAnchorFocusRange
) {
813 anchorFocusRangeIndex
= Some(index
);
816 // TODO: We should keep the array, and just update the ranges.
818 mAnchorFocusRange
= nullptr;
819 // Now bust up inlines.
821 for (const OwningNonNull
<RangeItem
>& item
: Reversed(rangeItemArray
)) {
822 // MOZ_KnownLive because 'rangeItemArray' is guaranteed to keep it alive.
823 Result
<EditorDOMPoint
, nsresult
> splitParentsResult
=
824 aHTMLEditor
.SplitInlineAncestorsAtRangeBoundaries(
825 MOZ_KnownLive(*item
), aBlockInlineCheck
, aEditingHost
,
827 if (MOZ_UNLIKELY(splitParentsResult
.isErr())) {
828 NS_WARNING("HTMLEditor::SplitInlineAncestorsAtRangeBoundaries() failed");
829 rv
= splitParentsResult
.unwrapErr();
832 if (splitParentsResult
.inspect().IsSet()) {
833 pointToPutCaret
= splitParentsResult
.unwrap();
836 // Then unregister the ranges
837 for (const size_t index
: IntegerRange(rangeItemArray
.Length())) {
838 aHTMLEditor
.RangeUpdaterRef().DropRangeItem(rangeItemArray
[index
]);
839 RefPtr
<nsRange
> range
= rangeItemArray
[index
]->GetRange();
840 if (range
&& range
->IsPositioned()) {
841 if (anchorFocusRangeIndex
.isSome() && index
== *anchorFocusRangeIndex
) {
842 mAnchorFocusRange
= range
;
844 mRanges
.AppendElement(std::move(range
));
847 if (!mAnchorFocusRange
&& !mRanges
.IsEmpty()) {
848 mAnchorFocusRange
= mRanges
.LastElement();
851 // XXX Why do we ignore the other errors here??
852 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
853 return Err(NS_ERROR_EDITOR_DESTROYED
);
855 return pointToPutCaret
;
858 nsresult
AutoClonedRangeArray::CollectEditTargetNodes(
859 const HTMLEditor
& aHTMLEditor
,
860 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
,
861 EditSubAction aEditSubAction
,
862 CollectNonEditableNodes aCollectNonEditableNodes
) const {
863 MOZ_ASSERT(aHTMLEditor
.IsEditActionDataAvailable());
865 // FYI: This was moved from
866 // https://searchfox.org/mozilla-central/rev/4bce7d85ba4796dd03c5dcc7cfe8eee0e4c07b3b/editor/libeditor/HTMLEditSubActionHandler.cpp#7060
868 // Gather up a list of all the nodes
869 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
870 DOMSubtreeIterator iter
;
871 nsresult rv
= iter
.Init(*range
);
873 NS_WARNING("DOMSubtreeIterator::Init() failed");
876 if (aOutArrayOfContents
.IsEmpty()) {
877 iter
.AppendAllNodesToArray(aOutArrayOfContents
);
879 AutoTArray
<OwningNonNull
<nsIContent
>, 24> arrayOfTopChildren
;
880 iter
.AppendNodesToArray(
881 +[](nsINode
& aNode
, void* aArray
) -> bool {
883 return !static_cast<nsTArray
<OwningNonNull
<nsIContent
>>*>(aArray
)
886 arrayOfTopChildren
, &aOutArrayOfContents
);
887 aOutArrayOfContents
.AppendElements(std::move(arrayOfTopChildren
));
889 if (aCollectNonEditableNodes
== CollectNonEditableNodes::No
) {
890 for (const size_t i
:
891 Reversed(IntegerRange(aOutArrayOfContents
.Length()))) {
892 if (!EditorUtils::IsEditableContent(aOutArrayOfContents
[i
],
893 EditorUtils::EditorType::HTML
)) {
894 aOutArrayOfContents
.RemoveElementAt(i
);
900 switch (aEditSubAction
) {
901 case EditSubAction::eCreateOrRemoveBlock
:
902 case EditSubAction::eFormatBlockForHTMLCommand
: {
903 // Certain operations should not act on li's and td's, but rather inside
904 // them. Alter the list as needed.
905 CollectChildrenOptions options
= {
906 CollectChildrenOption::CollectListChildren
,
907 CollectChildrenOption::CollectTableChildren
};
908 if (aCollectNonEditableNodes
== CollectNonEditableNodes::No
) {
909 options
+= CollectChildrenOption::IgnoreNonEditableChildren
;
911 if (aEditSubAction
== EditSubAction::eCreateOrRemoveBlock
) {
912 for (const size_t index
:
913 Reversed(IntegerRange(aOutArrayOfContents
.Length()))) {
914 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[index
];
915 if (HTMLEditUtils::IsListItem(content
)) {
916 aOutArrayOfContents
.RemoveElementAt(index
);
917 HTMLEditUtils::CollectChildren(*content
, aOutArrayOfContents
, index
,
922 // <dd> and <dt> are format blocks. Therefore, we should not handle
923 // their children directly. They should be replaced with new format
926 HTMLEditUtils::IsFormatTagForFormatBlockCommand(*nsGkAtoms::dt
));
928 HTMLEditUtils::IsFormatTagForFormatBlockCommand(*nsGkAtoms::dd
));
929 for (const size_t index
:
930 Reversed(IntegerRange(aOutArrayOfContents
.Length()))) {
931 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[index
];
932 MOZ_ASSERT_IF(HTMLEditUtils::IsListItem(content
),
933 content
->IsAnyOfHTMLElements(
934 nsGkAtoms::dd
, nsGkAtoms::dt
, nsGkAtoms::li
));
935 if (content
->IsHTMLElement(nsGkAtoms::li
)) {
936 aOutArrayOfContents
.RemoveElementAt(index
);
937 HTMLEditUtils::CollectChildren(*content
, aOutArrayOfContents
, index
,
942 // Empty text node shouldn't be selected if unnecessary
943 for (const size_t index
:
944 Reversed(IntegerRange(aOutArrayOfContents
.Length()))) {
945 if (const Text
* text
= aOutArrayOfContents
[index
]->GetAsText()) {
946 // Don't select empty text except to empty block
947 if (!HTMLEditUtils::IsVisibleTextNode(*text
)) {
948 aOutArrayOfContents
.RemoveElementAt(index
);
954 case EditSubAction::eCreateOrChangeList
: {
955 // XXX aCollectNonEditableNodes is ignored here. Maybe a bug.
956 CollectChildrenOptions options
= {
957 CollectChildrenOption::CollectTableChildren
};
958 for (const size_t index
:
959 Reversed(IntegerRange(aOutArrayOfContents
.Length()))) {
960 // Scan for table elements. If we find table elements other than
961 // table, replace it with a list of any editable non-table content
962 // because if a selection range starts from end in a table-cell and
963 // ends at or starts from outside the `<table>`, we need to make
964 // lists in each selected table-cells.
965 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[index
];
966 if (HTMLEditUtils::IsAnyTableElementButNotTable(content
)) {
967 aOutArrayOfContents
.RemoveElementAt(index
);
968 HTMLEditUtils::CollectChildren(content
, aOutArrayOfContents
, index
,
972 // If there is only one node in the array, and it is a `<div>`,
973 // `<blockquote>` or a list element, then look inside of it until we
974 // find inner list or content.
975 if (aOutArrayOfContents
.Length() != 1) {
978 Element
* deepestDivBlockquoteOrListElement
=
979 HTMLEditUtils::GetInclusiveDeepestFirstChildWhichHasOneChild(
980 aOutArrayOfContents
[0],
981 {HTMLEditUtils::WalkTreeOption::IgnoreNonEditableNode
},
982 BlockInlineCheck::Unused
, nsGkAtoms::div
, nsGkAtoms::blockquote
,
983 nsGkAtoms::ul
, nsGkAtoms::ol
, nsGkAtoms::dl
);
984 if (!deepestDivBlockquoteOrListElement
) {
987 if (deepestDivBlockquoteOrListElement
->IsAnyOfHTMLElements(
988 nsGkAtoms::div
, nsGkAtoms::blockquote
)) {
989 aOutArrayOfContents
.Clear();
990 // XXX Before we're called, non-editable nodes are ignored. However,
991 // we may append non-editable nodes here.
992 HTMLEditUtils::CollectChildren(*deepestDivBlockquoteOrListElement
,
993 aOutArrayOfContents
, 0, {});
996 aOutArrayOfContents
.ReplaceElementAt(
997 0, OwningNonNull
<nsIContent
>(*deepestDivBlockquoteOrListElement
));
1000 case EditSubAction::eOutdent
:
1001 case EditSubAction::eIndent
:
1002 case EditSubAction::eSetPositionToAbsolute
: {
1003 // Indent/outdent already do something special for list items, but we
1004 // still need to make sure we don't act on table elements
1005 CollectChildrenOptions options
= {
1006 CollectChildrenOption::CollectListChildren
,
1007 CollectChildrenOption::CollectTableChildren
};
1008 if (aCollectNonEditableNodes
== CollectNonEditableNodes::No
) {
1009 options
+= CollectChildrenOption::IgnoreNonEditableChildren
;
1011 for (const size_t index
:
1012 Reversed(IntegerRange(aOutArrayOfContents
.Length()))) {
1013 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[index
];
1014 if (HTMLEditUtils::IsAnyTableElementButNotTable(content
)) {
1015 aOutArrayOfContents
.RemoveElementAt(index
);
1016 HTMLEditUtils::CollectChildren(*content
, aOutArrayOfContents
, index
,
1026 // Outdent should look inside of divs.
1027 if (aEditSubAction
== EditSubAction::eOutdent
&&
1028 !aHTMLEditor
.IsCSSEnabled()) {
1029 CollectChildrenOptions options
= {};
1030 if (aCollectNonEditableNodes
== CollectNonEditableNodes::No
) {
1031 options
+= CollectChildrenOption::IgnoreNonEditableChildren
;
1033 for (const size_t index
:
1034 Reversed(IntegerRange(aOutArrayOfContents
.Length()))) {
1035 OwningNonNull
<nsIContent
> content
= aOutArrayOfContents
[index
];
1036 if (content
->IsHTMLElement(nsGkAtoms::div
)) {
1037 aOutArrayOfContents
.RemoveElementAt(index
);
1038 HTMLEditUtils::CollectChildren(*content
, aOutArrayOfContents
, index
,
1047 Element
* AutoClonedRangeArray::GetClosestAncestorAnyListElementOfRange() const {
1048 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
1049 nsINode
* commonAncestorNode
= range
->GetClosestCommonInclusiveAncestor();
1050 if (MOZ_UNLIKELY(!commonAncestorNode
)) {
1053 for (Element
* const element
:
1054 commonAncestorNode
->InclusiveAncestorsOfType
<Element
>()) {
1055 if (HTMLEditUtils::IsAnyListElement(element
)) {
1063 /******************************************************************************
1064 * mozilla::AutoClonedSelectionRangeArray
1065 *****************************************************************************/
1067 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
1068 const EditorDOMRange
& aRange
,
1069 const LimitersAndCaretData
& aLimitersAndCaretData
);
1070 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
1071 const EditorRawDOMRange
& aRange
,
1072 const LimitersAndCaretData
& aLimitersAndCaretData
);
1073 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
1074 const EditorDOMPoint
& aRange
,
1075 const LimitersAndCaretData
& aLimitersAndCaretData
);
1076 template AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
1077 const EditorRawDOMPoint
& aRange
,
1078 const LimitersAndCaretData
& aLimitersAndCaretData
);
1080 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
1081 const dom::Selection
& aSelection
) {
1082 Initialize(aSelection
);
1085 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
1086 const AutoClonedSelectionRangeArray
& aOther
)
1087 : AutoClonedRangeArray(aOther
),
1088 mLimitersAndCaretData(aOther
.mLimitersAndCaretData
) {}
1090 template <typename PointType
>
1091 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
1092 const EditorDOMRangeBase
<PointType
>& aRange
,
1093 const LimitersAndCaretData
& aLimitersAndCaretData
)
1094 : mLimitersAndCaretData(aLimitersAndCaretData
) {
1095 MOZ_ASSERT(aRange
.IsPositionedAndValid());
1096 RefPtr
<nsRange
> range
= aRange
.CreateRange(IgnoreErrors());
1097 if (NS_WARN_IF(!range
) || NS_WARN_IF(!range
->IsPositioned()) ||
1098 NS_WARN_IF(!RangeIsInLimiters(*range
))) {
1101 mRanges
.AppendElement(*range
);
1102 mAnchorFocusRange
= std::move(range
);
1105 template <typename PT
, typename CT
>
1106 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
1107 const EditorDOMPointBase
<PT
, CT
>& aPoint
,
1108 const LimitersAndCaretData
& aLimitersAndCaretData
)
1109 : mLimitersAndCaretData(aLimitersAndCaretData
) {
1110 MOZ_ASSERT(aPoint
.IsSetAndValid());
1111 if (NS_WARN_IF(!NodeIsInLimiters(aPoint
.GetContainer()))) {
1114 RefPtr
<nsRange
> range
= aPoint
.CreateCollapsedRange(IgnoreErrors());
1115 if (NS_WARN_IF(!range
) || NS_WARN_IF(!range
->IsPositioned())) {
1118 mRanges
.AppendElement(*range
);
1119 mAnchorFocusRange
= std::move(range
);
1120 SetNewCaretAssociationHint(aPoint
.ToRawRangeBoundary(),
1121 aPoint
.GetInterlinePosition());
1124 AutoClonedSelectionRangeArray::AutoClonedSelectionRangeArray(
1125 const nsRange
& aRange
, const LimitersAndCaretData
& aLimitersAndCaretData
)
1126 : mLimitersAndCaretData(aLimitersAndCaretData
) {
1127 MOZ_ASSERT(aRange
.IsPositioned());
1128 if (NS_WARN_IF(!RangeIsInLimiters(aRange
))) {
1131 mRanges
.AppendElement(aRange
.CloneRange());
1132 mAnchorFocusRange
= mRanges
[0];
1135 void AutoClonedSelectionRangeArray::SetNewCaretAssociationHint(
1136 const RawRangeBoundary
& aRawRangeBoundary
,
1137 InterlinePosition aInternlinePosition
) {
1138 if (aInternlinePosition
== Selection::InterlinePosition::Undefined
) {
1139 mLimitersAndCaretData
.mCaretAssociationHint
= ComputeCaretAssociationHint(
1140 mLimitersAndCaretData
.mCaretAssociationHint
,
1141 mLimitersAndCaretData
.mCaretBidiLevel
, aRawRangeBoundary
);
1143 SetInterlinePosition(aInternlinePosition
);
1147 bool AutoClonedSelectionRangeArray::SaveAndTrackRanges(
1148 HTMLEditor
& aHTMLEditor
) {
1149 if (mSavedRanges
.isSome()) {
1152 mSavedRanges
.emplace(*this);
1153 aHTMLEditor
.RangeUpdaterRef().RegisterSelectionState(mSavedRanges
.ref());
1154 mTrackingHTMLEditor
= &aHTMLEditor
;
1158 void AutoClonedSelectionRangeArray::ClearSavedRanges() {
1159 if (mSavedRanges
.isNothing()) {
1162 OwningNonNull
<HTMLEditor
> htmlEditor(std::move(mTrackingHTMLEditor
));
1163 MOZ_ASSERT(!mTrackingHTMLEditor
);
1164 htmlEditor
->RangeUpdaterRef().DropSelectionState(mSavedRanges
.ref());
1165 mSavedRanges
.reset();
1168 Result
<nsIEditor::EDirection
, nsresult
>
1169 AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor(
1170 const EditorBase
& aEditorBase
, nsIEditor::EDirection aDirectionAndAmount
) {
1171 MOZ_ASSERT(aEditorBase
.IsEditActionDataAvailable());
1172 MOZ_ASSERT(mAnchorFocusRange
);
1173 MOZ_ASSERT(mAnchorFocusRange
->IsPositioned());
1174 MOZ_ASSERT(mAnchorFocusRange
->StartRef().IsSet());
1175 MOZ_ASSERT(mAnchorFocusRange
->EndRef().IsSet());
1177 if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection(
1178 aDirectionAndAmount
, *this)) {
1179 return aDirectionAndAmount
;
1182 if (NS_WARN_IF(mRanges
.IsEmpty())) {
1183 return Err(NS_ERROR_FAILURE
);
1186 const RefPtr
<PresShell
> presShell
= aEditorBase
.GetPresShell();
1187 if (NS_WARN_IF(!presShell
)) {
1188 return Err(NS_ERROR_FAILURE
);
1191 const RefPtr
<Element
> editingHost
=
1192 aEditorBase
.IsHTMLEditor()
1193 ? aEditorBase
.AsHTMLEditor()->ComputeEditingHost()
1195 if (aEditorBase
.IsHTMLEditor() && NS_WARN_IF(!editingHost
)) {
1196 return Err(NS_ERROR_FAILURE
);
1199 Result
<RefPtr
<nsRange
>, nsresult
> result(NS_ERROR_UNEXPECTED
);
1200 const OwningNonNull
<nsRange
> anchorFocusRange
= *mAnchorFocusRange
;
1201 const LimitersAndCaretData limitersAndCaretData
= mLimitersAndCaretData
;
1202 const nsDirection rangeDirection
=
1203 mDirection
== eDirNext
? eDirNext
: eDirPrevious
;
1204 nsIEditor::EDirection directionAndAmountResult
= aDirectionAndAmount
;
1205 switch (aDirectionAndAmount
) {
1206 case nsIEditor::eNextWord
:
1207 result
= nsFrameSelection::CreateRangeExtendedToNextWordBoundary
<nsRange
>(
1208 *presShell
, limitersAndCaretData
, anchorFocusRange
, rangeDirection
);
1209 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
1210 return Err(NS_ERROR_EDITOR_DESTROYED
);
1212 NS_WARNING_ASSERTION(
1214 "nsFrameSelection::CreateRangeExtendedToNextWordBoundary() failed");
1215 // DeleteSelectionWithTransaction() doesn't handle these actions
1216 // because it's inside batching, so don't confuse it:
1217 directionAndAmountResult
= nsIEditor::eNone
;
1219 case nsIEditor::ePreviousWord
:
1221 nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary
<nsRange
>(
1222 *presShell
, limitersAndCaretData
, anchorFocusRange
,
1224 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
1225 return Err(NS_ERROR_EDITOR_DESTROYED
);
1227 NS_WARNING_ASSERTION(
1229 "nsFrameSelection::CreateRangeExtendedToPreviousWordBoundary() "
1231 // DeleteSelectionWithTransaction() doesn't handle these actions
1232 // because it's inside batching, so don't confuse it:
1233 directionAndAmountResult
= nsIEditor::eNone
;
1235 case nsIEditor::eNext
:
1237 nsFrameSelection::CreateRangeExtendedToNextGraphemeClusterBoundary
<
1238 nsRange
>(*presShell
, limitersAndCaretData
, anchorFocusRange
,
1240 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
1241 return Err(NS_ERROR_EDITOR_DESTROYED
);
1243 NS_WARNING_ASSERTION(result
.isOk(),
1244 "nsFrameSelection::"
1245 "CreateRangeExtendedToNextGraphemeClusterBoundary() "
1247 // Don't set directionAndAmount to eNone (see Bug 502259)
1249 case nsIEditor::ePrevious
: {
1250 // Only extend the selection where the selection is after a UTF-16
1251 // surrogate pair or a variation selector.
1252 // For other cases we don't want to do that, in order
1253 // to make sure that pressing backspace will only delete the last
1255 // XXX This is odd if the previous one is a sequence for a grapheme
1257 const auto atStartOfSelection
= GetFirstRangeStartPoint
<EditorDOMPoint
>();
1258 if (MOZ_UNLIKELY(NS_WARN_IF(!atStartOfSelection
.IsSet()))) {
1259 return Err(NS_ERROR_FAILURE
);
1262 // node might be anonymous DIV, so we find better text node
1263 const EditorDOMPoint insertionPoint
=
1264 aEditorBase
.IsTextEditor()
1265 ? aEditorBase
.AsTextEditor()->FindBetterInsertionPoint(
1267 : atStartOfSelection
.GetPointInTextNodeIfPointingAroundTextNode
<
1269 if (MOZ_UNLIKELY(!insertionPoint
.IsSet())) {
1271 "EditorBase::FindBetterInsertionPoint() failed, but ignored");
1272 return aDirectionAndAmount
;
1275 if (!insertionPoint
.IsInTextNode()) {
1276 return aDirectionAndAmount
;
1279 const nsTextFragment
* data
=
1280 &insertionPoint
.ContainerAs
<Text
>()->TextFragment();
1281 uint32_t offset
= insertionPoint
.Offset();
1283 data
->IsLowSurrogateFollowingHighSurrogateAt(offset
- 1)) &&
1285 gfxFontUtils::IsVarSelector(data
->CharAt(offset
- 1)))) {
1286 return aDirectionAndAmount
;
1288 // Different from the `eNext` case, we look for character boundary.
1289 // I'm not sure whether this inconsistency between "Delete" and
1290 // "Backspace" is intentional or not.
1291 result
= nsFrameSelection::CreateRangeExtendedToPreviousCharacterBoundary
<
1292 nsRange
>(*presShell
, limitersAndCaretData
, anchorFocusRange
,
1294 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
1295 return Err(NS_ERROR_EDITOR_DESTROYED
);
1297 NS_WARNING_ASSERTION(
1299 "nsFrameSelection::"
1300 "CreateRangeExtendedToPreviousGraphemeClusterBoundary() failed");
1303 case nsIEditor::eToBeginningOfLine
:
1305 nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak
<nsRange
>(
1306 *presShell
, limitersAndCaretData
, anchorFocusRange
,
1308 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
1309 return Err(NS_ERROR_EDITOR_DESTROYED
);
1311 NS_WARNING_ASSERTION(
1313 "nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak() "
1315 directionAndAmountResult
= nsIEditor::eNone
;
1317 case nsIEditor::eToEndOfLine
:
1319 nsFrameSelection::CreateRangeExtendedToNextHardLineBreak
<nsRange
>(
1320 *presShell
, limitersAndCaretData
, anchorFocusRange
,
1322 if (NS_WARN_IF(aEditorBase
.Destroyed())) {
1323 return Err(NS_ERROR_EDITOR_DESTROYED
);
1325 NS_WARNING_ASSERTION(
1327 "nsFrameSelection::CreateRangeExtendedToNextHardLineBreak() failed");
1328 directionAndAmountResult
= nsIEditor::eNext
;
1331 return aDirectionAndAmount
;
1334 if (result
.isErr()) {
1335 return Err(result
.inspectErr());
1337 RefPtr
<nsRange
> extendedRange(result
.unwrap().forget());
1338 if (!extendedRange
|| NS_WARN_IF(!extendedRange
->IsPositioned())) {
1339 NS_WARNING("Failed to extend the range, but ignored");
1340 return directionAndAmountResult
;
1343 // If the new range isn't editable, keep using the original range.
1344 if (aEditorBase
.IsHTMLEditor() &&
1345 !AutoClonedRangeArray::IsEditableRange(*extendedRange
, *editingHost
)) {
1346 return aDirectionAndAmount
;
1349 if (NS_WARN_IF(!mLimitersAndCaretData
.RangeInLimiters(*extendedRange
))) {
1350 NS_WARNING("A range was extended to outer of selection limiter");
1351 return Err(NS_ERROR_FAILURE
);
1354 // Swap focus/anchor range with the extended range.
1355 DebugOnly
<bool> found
= false;
1356 for (OwningNonNull
<nsRange
>& range
: mRanges
) {
1357 if (range
== mAnchorFocusRange
) {
1358 range
= *extendedRange
;
1364 mAnchorFocusRange
.swap(extendedRange
);
1365 return directionAndAmountResult
;
1368 } // namespace mozilla