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 #ifndef AutoRangeArray_h
7 #define AutoRangeArray_h
9 #include "EditAction.h" // for EditSubAction
10 #include "EditorBase.h" // for EditorBase
11 #include "EditorDOMPoint.h" // for EditorDOMPoint, EditorDOMRange, etc
12 #include "EditorForwards.h"
13 #include "HTMLEditHelpers.h" // for BlockInlineCheck
14 #include "SelectionState.h" // for SelectionState
16 #include "mozilla/ErrorResult.h" // for ErrorResult
17 #include "mozilla/IntegerRange.h" // for IntegerRange
18 #include "mozilla/Maybe.h" // for Maybe
19 #include "mozilla/RangeBoundary.h" // for RangeBoundary
20 #include "mozilla/Result.h" // for Result<>
21 #include "mozilla/dom/Element.h" // for dom::Element
22 #include "mozilla/dom/HTMLBRElement.h" // for dom::HTMLBRElement
23 #include "mozilla/dom/Selection.h" // for dom::Selection
24 #include "mozilla/dom/Text.h" // for dom::Text
26 #include "nsDebug.h" // for NS_WARNING, etc
27 #include "nsDirection.h" // for nsDirection
28 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_*
29 #include "nsRange.h" // for nsRange
33 /******************************************************************************
34 * AutoRangeArray stores ranges which do no belong any `Selection`.
35 * So, different from `AutoSelectionRangeArray`, this can be used for
36 * ranges which may need to be modified before touching the DOM tree,
37 * but does not want to modify `Selection` for the performance.
38 *****************************************************************************/
39 class MOZ_STACK_CLASS AutoRangeArray final
{
41 explicit AutoRangeArray(const dom::Selection
& aSelection
);
42 template <typename PointType
>
43 explicit AutoRangeArray(const EditorDOMRangeBase
<PointType
>& aRange
);
44 template <typename PT
, typename CT
>
45 explicit AutoRangeArray(const EditorDOMPointBase
<PT
, CT
>& aPoint
);
46 explicit AutoRangeArray(nsRange
& aRange
);
47 // The copy constructor copies everything except saved ranges.
48 explicit AutoRangeArray(const AutoRangeArray
& aOther
);
52 void Initialize(const dom::Selection
& aSelection
) {
54 mDirection
= aSelection
.GetDirection();
56 for (const uint32_t i
: IntegerRange(aSelection
.RangeCount())) {
57 MOZ_ASSERT(aSelection
.GetRangeAt(i
));
58 mRanges
.AppendElement(aSelection
.GetRangeAt(i
)->CloneRange());
59 if (aSelection
.GetRangeAt(i
) == aSelection
.GetAnchorFocusRange()) {
60 mAnchorFocusRange
= mRanges
.LastElement();
66 * Check whether all ranges in content nodes or not. If the ranges is empty,
69 [[nodiscard
]] bool IsInContent() const {
70 if (mRanges
.IsEmpty()) {
73 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
74 if (MOZ_UNLIKELY(!range
->IsPositioned() || !range
->GetStartContainer() ||
75 !range
->GetStartContainer()->IsContent() ||
76 !range
->GetEndContainer() ||
77 !range
->GetEndContainer()->IsContent())) {
85 * EnsureOnlyEditableRanges() removes ranges which cannot modify.
86 * Note that this is designed only for `HTMLEditor` because this must not
87 * be required by `TextEditor`.
89 void EnsureOnlyEditableRanges(const dom::Element
& aEditingHost
);
92 * EnsureRangesInTextNode() is designed for TextEditor to guarantee that
93 * all ranges are in its text node which is first child of the anonymous <div>
94 * element and is first child.
96 void EnsureRangesInTextNode(const dom::Text
& aTextNode
);
99 * Extend ranges to make each range select starting from a line start edge and
100 * ending after a line end edge to handle per line edit sub-actions.
102 void ExtendRangesToWrapLines(EditSubAction aEditSubAction
,
103 BlockInlineCheck aBlockInlineCheck
,
104 const dom::Element
& aEditingHost
);
107 * Check whether the range is in aEditingHost and both containers of start and
108 * end boundaries of the range are editable.
110 [[nodiscard
]] static bool IsEditableRange(const dom::AbstractRange
& aRange
,
111 const dom::Element
& aEditingHost
);
114 * Check whether the first range is in aEditingHost and both containers of
115 * start and end boundaries of the first range are editable.
117 [[nodiscard
]] bool IsFirstRangeEditable(
118 const dom::Element
& aEditingHost
) const {
119 return IsEditableRange(FirstRangeRef(), aEditingHost
);
123 * IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf() returns true
124 * if at least one of the containers of the range boundaries is an inclusive
125 * descendant of aContent.
128 IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
129 const nsIContent
& aContent
) const {
130 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
131 nsINode
* startContainer
= range
->GetStartContainer();
132 if (startContainer
&&
133 startContainer
->IsInclusiveDescendantOf(&aContent
)) {
136 nsINode
* endContainer
= range
->GetEndContainer();
137 if (startContainer
== endContainer
) {
140 if (endContainer
&& endContainer
->IsInclusiveDescendantOf(&aContent
)) {
147 [[nodiscard
]] auto& Ranges() { return mRanges
; }
148 [[nodiscard
]] const auto& Ranges() const { return mRanges
; }
149 [[nodiscard
]] OwningNonNull
<nsRange
>& FirstRangeRef() { return mRanges
[0]; }
150 [[nodiscard
]] const OwningNonNull
<nsRange
>& FirstRangeRef() const {
154 template <template <typename
> typename StrongPtrType
>
155 [[nodiscard
]] AutoTArray
<StrongPtrType
<nsRange
>, 8> CloneRanges() const {
156 AutoTArray
<StrongPtrType
<nsRange
>, 8> ranges
;
157 for (const auto& range
: mRanges
) {
158 ranges
.AppendElement(range
->CloneRange());
163 template <typename EditorDOMPointType
>
164 [[nodiscard
]] EditorDOMPointType
GetFirstRangeStartPoint() const {
165 if (mRanges
.IsEmpty() || !mRanges
[0]->IsPositioned()) {
166 return EditorDOMPointType();
168 return EditorDOMPointType(mRanges
[0]->StartRef());
170 template <typename EditorDOMPointType
>
171 [[nodiscard
]] EditorDOMPointType
GetFirstRangeEndPoint() const {
172 if (mRanges
.IsEmpty() || !mRanges
[0]->IsPositioned()) {
173 return EditorDOMPointType();
175 return EditorDOMPointType(mRanges
[0]->EndRef());
178 nsresult
SelectNode(nsINode
& aNode
) {
180 if (!mAnchorFocusRange
) {
181 mAnchorFocusRange
= nsRange::Create(&aNode
);
182 if (!mAnchorFocusRange
) {
183 return NS_ERROR_FAILURE
;
187 mAnchorFocusRange
->SelectNode(aNode
, error
);
188 if (error
.Failed()) {
189 mAnchorFocusRange
= nullptr;
190 return error
.StealNSResult();
192 mRanges
.AppendElement(*mAnchorFocusRange
);
197 * ExtendAnchorFocusRangeFor() extends the anchor-focus range for deleting
198 * content for aDirectionAndAmount. The range won't be extended to outer of
199 * selection limiter. Note that if a range is extened, the range is
200 * recreated. Therefore, caller cannot cache pointer of any ranges before
203 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<nsIEditor::EDirection
, nsresult
>
204 ExtendAnchorFocusRangeFor(const EditorBase
& aEditorBase
,
205 nsIEditor::EDirection aDirectionAndAmount
);
208 * For compatiblity with the other browsers, we should shrink ranges to
209 * start from an atomic content and/or end after one instead of start
210 * from end of a preceding text node and end by start of a follwing text
211 * node. Returns true if this modifies a range.
213 enum class IfSelectingOnlyOneAtomicContent
{
214 Collapse
, // Collapse to the range selecting only one atomic content to
215 // start or after of it. Whether to collapse start or after
216 // it depends on aDirectionAndAmount. This is ignored if
217 // there are multiple ranges.
218 KeepSelecting
, // Won't collapse the range.
220 Result
<bool, nsresult
> ShrinkRangesIfStartFromOrEndAfterAtomicContent(
221 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
222 IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent
,
223 const dom::Element
* aEditingHost
);
226 * The following methods are same as `Selection`'s methods.
228 [[nodiscard
]] bool IsCollapsed() const {
229 return mRanges
.IsEmpty() ||
230 (mRanges
.Length() == 1 && mRanges
[0]->Collapsed());
232 template <typename PT
, typename CT
>
233 nsresult
Collapse(const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
235 if (!mAnchorFocusRange
) {
237 mAnchorFocusRange
= nsRange::Create(aPoint
.ToRawRangeBoundary(),
238 aPoint
.ToRawRangeBoundary(), error
);
239 if (error
.Failed()) {
240 mAnchorFocusRange
= nullptr;
241 return error
.StealNSResult();
244 nsresult rv
= mAnchorFocusRange
->CollapseTo(aPoint
.ToRawRangeBoundary());
246 mAnchorFocusRange
= nullptr;
250 mRanges
.AppendElement(*mAnchorFocusRange
);
253 template <typename SPT
, typename SCT
, typename EPT
, typename ECT
>
254 nsresult
SetStartAndEnd(const EditorDOMPointBase
<SPT
, SCT
>& aStart
,
255 const EditorDOMPointBase
<EPT
, ECT
>& aEnd
) {
257 if (!mAnchorFocusRange
) {
259 mAnchorFocusRange
= nsRange::Create(aStart
.ToRawRangeBoundary(),
260 aEnd
.ToRawRangeBoundary(), error
);
261 if (error
.Failed()) {
262 mAnchorFocusRange
= nullptr;
263 return error
.StealNSResult();
266 nsresult rv
= mAnchorFocusRange
->SetStartAndEnd(
267 aStart
.ToRawRangeBoundary(), aEnd
.ToRawRangeBoundary());
269 mAnchorFocusRange
= nullptr;
273 mRanges
.AppendElement(*mAnchorFocusRange
);
276 template <typename SPT
, typename SCT
, typename EPT
, typename ECT
>
277 nsresult
SetBaseAndExtent(const EditorDOMPointBase
<SPT
, SCT
>& aAnchor
,
278 const EditorDOMPointBase
<EPT
, ECT
>& aFocus
) {
279 if (MOZ_UNLIKELY(!aAnchor
.IsSet()) || MOZ_UNLIKELY(!aFocus
.IsSet())) {
281 mAnchorFocusRange
= nullptr;
282 return NS_ERROR_INVALID_ARG
;
284 return aAnchor
.EqualsOrIsBefore(aFocus
) ? SetStartAndEnd(aAnchor
, aFocus
)
285 : SetStartAndEnd(aFocus
, aAnchor
);
287 [[nodiscard
]] const nsRange
* GetAnchorFocusRange() const {
288 return mAnchorFocusRange
;
290 [[nodiscard
]] nsDirection
GetDirection() const { return mDirection
; }
292 void SetDirection(nsDirection aDirection
) { mDirection
= aDirection
; }
294 [[nodiscard
]] const RangeBoundary
& AnchorRef() const {
295 if (!mAnchorFocusRange
) {
296 static RangeBoundary sEmptyRangeBoundary
;
297 return sEmptyRangeBoundary
;
299 return mDirection
== nsDirection::eDirNext
? mAnchorFocusRange
->StartRef()
300 : mAnchorFocusRange
->EndRef();
302 [[nodiscard
]] nsINode
* GetAnchorNode() const {
303 return AnchorRef().IsSet() ? AnchorRef().Container() : nullptr;
305 [[nodiscard
]] uint32_t GetAnchorOffset() const {
306 return AnchorRef().IsSet()
308 .Offset(RangeBoundary::OffsetFilter::kValidOffsets
)
312 [[nodiscard
]] nsIContent
* GetChildAtAnchorOffset() const {
313 return AnchorRef().IsSet() ? AnchorRef().GetChildAtOffset() : nullptr;
316 [[nodiscard
]] const RangeBoundary
& FocusRef() const {
317 if (!mAnchorFocusRange
) {
318 static RangeBoundary sEmptyRangeBoundary
;
319 return sEmptyRangeBoundary
;
321 return mDirection
== nsDirection::eDirNext
? mAnchorFocusRange
->EndRef()
322 : mAnchorFocusRange
->StartRef();
324 [[nodiscard
]] nsINode
* GetFocusNode() const {
325 return FocusRef().IsSet() ? FocusRef().Container() : nullptr;
327 [[nodiscard
]] uint32_t FocusOffset() const {
328 return FocusRef().IsSet()
330 .Offset(RangeBoundary::OffsetFilter::kValidOffsets
)
334 [[nodiscard
]] nsIContent
* GetChildAtFocusOffset() const {
335 return FocusRef().IsSet() ? FocusRef().GetChildAtOffset() : nullptr;
338 void RemoveAllRanges() {
340 mAnchorFocusRange
= nullptr;
341 mDirection
= nsDirection::eDirNext
;
345 * APIs to store ranges with only container node and offset in it, and track
346 * them with RangeUpdater.
348 [[nodiscard
]] bool SaveAndTrackRanges(HTMLEditor
& aHTMLEditor
);
349 [[nodiscard
]] bool HasSavedRanges() const { return mSavedRanges
.isSome(); }
350 void ClearSavedRanges();
351 void RestoreFromSavedRanges() {
352 MOZ_DIAGNOSTIC_ASSERT(mSavedRanges
.isSome());
353 if (mSavedRanges
.isNothing()) {
356 mSavedRanges
->ApplyTo(*this);
361 * Apply mRanges and mDirection to aSelection.
363 MOZ_CAN_RUN_SCRIPT nsresult
ApplyTo(dom::Selection
& aSelection
) {
364 dom::SelectionBatcher
selectionBatcher(aSelection
, __FUNCTION__
);
365 aSelection
.RemoveAllRanges(IgnoreErrors());
366 MOZ_ASSERT(!aSelection
.RangeCount());
367 aSelection
.SetDirection(mDirection
);
368 IgnoredErrorResult error
;
369 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
370 // MOZ_KnownLive(range) due to bug 1622253
371 aSelection
.AddRangeAndSelectFramesAndNotifyListeners(MOZ_KnownLive(range
),
373 if (error
.Failed()) {
374 return error
.StealNSResult();
381 * If the points are same (i.e., mean a collapsed range) and in an empty block
382 * element except the padding <br> element, this makes aStartPoint and
383 * aEndPoint contain the padding <br> element.
385 static void UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
386 EditorDOMPoint
& aStartPoint
, EditorDOMPoint
& aEndPoint
,
387 const dom::Element
& aEditingHost
);
390 * CreateRangeExtendedToHardLineStartAndEnd() creates an nsRange instance
391 * which may be expanded to start/end of hard line at both edges of the given
392 * range. If this fails handling something, returns nullptr.
394 static already_AddRefed
<nsRange
>
395 CreateRangeWrappingStartAndEndLinesContainingBoundaries(
396 const EditorDOMRange
& aRange
, EditSubAction aEditSubAction
,
397 BlockInlineCheck aBlockInlineCheck
, const dom::Element
& aEditingHost
) {
398 if (!aRange
.IsPositioned()) {
401 return CreateRangeWrappingStartAndEndLinesContainingBoundaries(
402 aRange
.StartRef(), aRange
.EndRef(), aEditSubAction
, aBlockInlineCheck
,
405 static already_AddRefed
<nsRange
>
406 CreateRangeWrappingStartAndEndLinesContainingBoundaries(
407 const EditorDOMPoint
& aStartPoint
, const EditorDOMPoint
& aEndPoint
,
408 EditSubAction aEditSubAction
, BlockInlineCheck aBlockInlineCheck
,
409 const dom::Element
& aEditingHost
) {
410 RefPtr
<nsRange
> range
=
411 nsRange::Create(aStartPoint
.ToRawRangeBoundary(),
412 aEndPoint
.ToRawRangeBoundary(), IgnoreErrors());
413 if (MOZ_UNLIKELY(!range
)) {
416 if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
417 *range
, aEditSubAction
, aBlockInlineCheck
, aEditingHost
)) ||
418 MOZ_UNLIKELY(!range
->IsPositioned())) {
421 return range
.forget();
425 * Splits text nodes if each range end is in middle of a text node, then,
426 * calls HTMLEditor::SplitParentInlineElementsAtRangeBoundaries() for each
427 * range. Finally, updates ranges to keep edit target ranges as expected.
429 * @param aHTMLEditor The HTMLEditor which will handle the splittings.
430 * @param aBlockInlineCheck Considering block vs inline with whether the
431 * computed style or the HTML default style.
432 * @param aElement The editing host.
433 * @param aAncestorLimiter A content node which you don't want this to
435 * @return A suggest point to put caret if succeeded, but
438 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditorDOMPoint
, nsresult
>
439 SplitTextAtEndBoundariesAndInlineAncestorsAtBothBoundaries(
440 HTMLEditor
& aHTMLEditor
, BlockInlineCheck aBlockInlineCheck
,
441 const dom::Element
& aEditingHost
,
442 const nsIContent
* aAncestorLimiter
= nullptr);
445 * CollectEditTargetNodes() collects edit target nodes the ranges.
446 * First, this collects all nodes in given ranges, then, modifies the
447 * result for specific edit sub-actions.
449 enum class CollectNonEditableNodes
{ No
, Yes
};
450 nsresult
CollectEditTargetNodes(
451 const HTMLEditor
& aHTMLEditor
,
452 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
,
453 EditSubAction aEditSubAction
,
454 CollectNonEditableNodes aCollectNonEditableNodes
) const;
457 * Retrieve a closest ancestor list element of a common ancestor of _A_ range
458 * of the ranges. This tries to retrieve it from the first range to the last
461 dom::Element
* GetClosestAncestorAnyListElementOfRange() const;
464 static nsresult
ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
465 nsRange
& aRange
, EditSubAction aEditSubAction
,
466 BlockInlineCheck aBlockInlineCheck
, const dom::Element
& aEditingHost
);
468 AutoTArray
<mozilla::OwningNonNull
<nsRange
>, 8> mRanges
;
469 RefPtr
<nsRange
> mAnchorFocusRange
;
470 nsDirection mDirection
= nsDirection::eDirNext
;
471 Maybe
<SelectionState
> mSavedRanges
;
472 RefPtr
<HTMLEditor
> mTrackingHTMLEditor
;
475 } // namespace mozilla
477 #endif // #ifndef AutoRangeArray_h