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 "SelectionState.h" // for SelectionState
15 #include "mozilla/ErrorResult.h" // for ErrorResult
16 #include "mozilla/IntegerRange.h" // for IntegerRange
17 #include "mozilla/Maybe.h" // for Maybe
18 #include "mozilla/RangeBoundary.h" // for RangeBoundary
19 #include "mozilla/Result.h" // for Result<>
20 #include "mozilla/dom/Element.h" // for dom::Element
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 "nsDebug.h" // for NS_WARNING, etc
26 #include "nsDirection.h" // for nsDirection
27 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_*
28 #include "nsRange.h" // for nsRange
32 /******************************************************************************
33 * AutoRangeArray stores ranges which do no belong any `Selection`.
34 * So, different from `AutoSelectionRangeArray`, this can be used for
35 * ranges which may need to be modified before touching the DOM tree,
36 * but does not want to modify `Selection` for the performance.
37 *****************************************************************************/
38 class MOZ_STACK_CLASS AutoRangeArray final
{
40 explicit AutoRangeArray(const dom::Selection
& aSelection
);
41 template <typename PointType
>
42 explicit AutoRangeArray(const EditorDOMRangeBase
<PointType
>& aRange
);
43 template <typename PT
, typename CT
>
44 explicit AutoRangeArray(const EditorDOMPointBase
<PT
, CT
>& aPoint
);
45 // The copy constructor copies everything except saved ranges.
46 explicit AutoRangeArray(const AutoRangeArray
& aOther
);
50 void Initialize(const dom::Selection
& aSelection
) {
52 mDirection
= aSelection
.GetDirection();
54 for (const uint32_t i
: IntegerRange(aSelection
.RangeCount())) {
55 MOZ_ASSERT(aSelection
.GetRangeAt(i
));
56 mRanges
.AppendElement(aSelection
.GetRangeAt(i
)->CloneRange());
57 if (aSelection
.GetRangeAt(i
) == aSelection
.GetAnchorFocusRange()) {
58 mAnchorFocusRange
= mRanges
.LastElement();
64 * Check whether all ranges in content nodes or not. If the ranges is empty,
67 [[nodiscard
]] bool IsInContent() const {
68 if (mRanges
.IsEmpty()) {
71 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
72 if (MOZ_UNLIKELY(!range
->IsPositioned() || !range
->GetStartContainer() ||
73 !range
->GetStartContainer()->IsContent() ||
74 !range
->GetEndContainer() ||
75 !range
->GetEndContainer()->IsContent())) {
83 * EnsureOnlyEditableRanges() removes ranges which cannot modify.
84 * Note that this is designed only for `HTMLEditor` because this must not
85 * be required by `TextEditor`.
87 void EnsureOnlyEditableRanges(const dom::Element
& aEditingHost
);
90 * EnsureRangesInTextNode() is designed for TextEditor to guarantee that
91 * all ranges are in its text node which is first child of the anonymous <div>
92 * element and is first child.
94 void EnsureRangesInTextNode(const dom::Text
& aTextNode
);
97 * Extend ranges to wrap lines to handle block level edit actions such as
98 * updating the block parent or indent/outdent around the selection.
100 void ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
101 EditSubAction aEditSubAction
, const dom::Element
& aEditingHost
);
104 * Check whether the range is in aEditingHost and both containers of start and
105 * end boundaries of the range are editable.
107 [[nodiscard
]] static bool IsEditableRange(const dom::AbstractRange
& aRange
,
108 const dom::Element
& aEditingHost
);
111 * Check whether the first range is in aEditingHost and both containers of
112 * start and end boundaries of the first range are editable.
114 [[nodiscard
]] bool IsFirstRangeEditable(
115 const dom::Element
& aEditingHost
) const {
116 return IsEditableRange(FirstRangeRef(), aEditingHost
);
120 * IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf() returns true
121 * if at least one of the containers of the range boundaries is an inclusive
122 * descendant of aContent.
125 IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
126 const nsIContent
& aContent
) const {
127 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
128 nsINode
* startContainer
= range
->GetStartContainer();
129 if (startContainer
&&
130 startContainer
->IsInclusiveDescendantOf(&aContent
)) {
133 nsINode
* endContainer
= range
->GetEndContainer();
134 if (startContainer
== endContainer
) {
137 if (endContainer
&& endContainer
->IsInclusiveDescendantOf(&aContent
)) {
144 [[nodiscard
]] auto& Ranges() { return mRanges
; }
145 [[nodiscard
]] const auto& Ranges() const { return mRanges
; }
146 [[nodiscard
]] OwningNonNull
<nsRange
>& FirstRangeRef() { return mRanges
[0]; }
147 [[nodiscard
]] const OwningNonNull
<nsRange
>& FirstRangeRef() const {
151 template <template <typename
> typename StrongPtrType
>
152 [[nodiscard
]] AutoTArray
<StrongPtrType
<nsRange
>, 8> CloneRanges() const {
153 AutoTArray
<StrongPtrType
<nsRange
>, 8> ranges
;
154 for (const auto& range
: mRanges
) {
155 ranges
.AppendElement(range
->CloneRange());
160 template <typename EditorDOMPointType
>
161 [[nodiscard
]] EditorDOMPointType
GetFirstRangeStartPoint() const {
162 if (mRanges
.IsEmpty() || !mRanges
[0]->IsPositioned()) {
163 return EditorDOMPointType();
165 return EditorDOMPointType(mRanges
[0]->StartRef());
167 template <typename EditorDOMPointType
>
168 [[nodiscard
]] EditorDOMPointType
GetFirstRangeEndPoint() const {
169 if (mRanges
.IsEmpty() || !mRanges
[0]->IsPositioned()) {
170 return EditorDOMPointType();
172 return EditorDOMPointType(mRanges
[0]->EndRef());
175 nsresult
SelectNode(nsINode
& aNode
) {
177 if (!mAnchorFocusRange
) {
178 mAnchorFocusRange
= nsRange::Create(&aNode
);
179 if (!mAnchorFocusRange
) {
180 return NS_ERROR_FAILURE
;
184 mAnchorFocusRange
->SelectNode(aNode
, error
);
185 if (error
.Failed()) {
186 mAnchorFocusRange
= nullptr;
187 return error
.StealNSResult();
189 mRanges
.AppendElement(*mAnchorFocusRange
);
194 * ExtendAnchorFocusRangeFor() extends the anchor-focus range for deleting
195 * content for aDirectionAndAmount. The range won't be extended to outer of
196 * selection limiter. Note that if a range is extened, the range is
197 * recreated. Therefore, caller cannot cache pointer of any ranges before
200 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<nsIEditor::EDirection
, nsresult
>
201 ExtendAnchorFocusRangeFor(const EditorBase
& aEditorBase
,
202 nsIEditor::EDirection aDirectionAndAmount
);
205 * For compatiblity with the other browsers, we should shrink ranges to
206 * start from an atomic content and/or end after one instead of start
207 * from end of a preceding text node and end by start of a follwing text
208 * node. Returns true if this modifies a range.
210 enum class IfSelectingOnlyOneAtomicContent
{
211 Collapse
, // Collapse to the range selecting only one atomic content to
212 // start or after of it. Whether to collapse start or after
213 // it depends on aDirectionAndAmount. This is ignored if
214 // there are multiple ranges.
215 KeepSelecting
, // Won't collapse the range.
217 Result
<bool, nsresult
> ShrinkRangesIfStartFromOrEndAfterAtomicContent(
218 const HTMLEditor
& aHTMLEditor
, nsIEditor::EDirection aDirectionAndAmount
,
219 IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent
,
220 const dom::Element
* aEditingHost
);
223 * The following methods are same as `Selection`'s methods.
225 [[nodiscard
]] bool IsCollapsed() const {
226 return mRanges
.IsEmpty() ||
227 (mRanges
.Length() == 1 && mRanges
[0]->Collapsed());
229 template <typename PT
, typename CT
>
230 nsresult
Collapse(const EditorDOMPointBase
<PT
, CT
>& aPoint
) {
232 if (!mAnchorFocusRange
) {
234 mAnchorFocusRange
= nsRange::Create(aPoint
.ToRawRangeBoundary(),
235 aPoint
.ToRawRangeBoundary(), error
);
236 if (error
.Failed()) {
237 mAnchorFocusRange
= nullptr;
238 return error
.StealNSResult();
241 nsresult rv
= mAnchorFocusRange
->CollapseTo(aPoint
.ToRawRangeBoundary());
243 mAnchorFocusRange
= nullptr;
247 mRanges
.AppendElement(*mAnchorFocusRange
);
250 template <typename SPT
, typename SCT
, typename EPT
, typename ECT
>
251 nsresult
SetStartAndEnd(const EditorDOMPointBase
<SPT
, SCT
>& aStart
,
252 const EditorDOMPointBase
<EPT
, ECT
>& aEnd
) {
254 if (!mAnchorFocusRange
) {
256 mAnchorFocusRange
= nsRange::Create(aStart
.ToRawRangeBoundary(),
257 aEnd
.ToRawRangeBoundary(), error
);
258 if (error
.Failed()) {
259 mAnchorFocusRange
= nullptr;
260 return error
.StealNSResult();
263 nsresult rv
= mAnchorFocusRange
->SetStartAndEnd(
264 aStart
.ToRawRangeBoundary(), aEnd
.ToRawRangeBoundary());
266 mAnchorFocusRange
= nullptr;
270 mRanges
.AppendElement(*mAnchorFocusRange
);
273 template <typename SPT
, typename SCT
, typename EPT
, typename ECT
>
274 nsresult
SetBaseAndExtent(const EditorDOMPointBase
<SPT
, SCT
>& aAnchor
,
275 const EditorDOMPointBase
<EPT
, ECT
>& aFocus
) {
276 if (MOZ_UNLIKELY(!aAnchor
.IsSet()) || MOZ_UNLIKELY(!aFocus
.IsSet())) {
278 mAnchorFocusRange
= nullptr;
279 return NS_ERROR_INVALID_ARG
;
281 return aAnchor
.EqualsOrIsBefore(aFocus
) ? SetStartAndEnd(aAnchor
, aFocus
)
282 : SetStartAndEnd(aFocus
, aAnchor
);
284 [[nodiscard
]] const nsRange
* GetAnchorFocusRange() const {
285 return mAnchorFocusRange
;
287 [[nodiscard
]] nsDirection
GetDirection() const { return mDirection
; }
289 void SetDirection(nsDirection aDirection
) { mDirection
= aDirection
; }
291 [[nodiscard
]] const RangeBoundary
& AnchorRef() const {
292 if (!mAnchorFocusRange
) {
293 static RangeBoundary sEmptyRangeBoundary
;
294 return sEmptyRangeBoundary
;
296 return mDirection
== nsDirection::eDirNext
? mAnchorFocusRange
->StartRef()
297 : mAnchorFocusRange
->EndRef();
299 [[nodiscard
]] nsINode
* GetAnchorNode() const {
300 return AnchorRef().IsSet() ? AnchorRef().Container() : nullptr;
302 [[nodiscard
]] uint32_t GetAnchorOffset() const {
303 return AnchorRef().IsSet()
305 .Offset(RangeBoundary::OffsetFilter::kValidOffsets
)
309 [[nodiscard
]] nsIContent
* GetChildAtAnchorOffset() const {
310 return AnchorRef().IsSet() ? AnchorRef().GetChildAtOffset() : nullptr;
313 [[nodiscard
]] const RangeBoundary
& FocusRef() const {
314 if (!mAnchorFocusRange
) {
315 static RangeBoundary sEmptyRangeBoundary
;
316 return sEmptyRangeBoundary
;
318 return mDirection
== nsDirection::eDirNext
? mAnchorFocusRange
->EndRef()
319 : mAnchorFocusRange
->StartRef();
321 [[nodiscard
]] nsINode
* GetFocusNode() const {
322 return FocusRef().IsSet() ? FocusRef().Container() : nullptr;
324 [[nodiscard
]] uint32_t FocusOffset() const {
325 return FocusRef().IsSet()
327 .Offset(RangeBoundary::OffsetFilter::kValidOffsets
)
331 [[nodiscard
]] nsIContent
* GetChildAtFocusOffset() const {
332 return FocusRef().IsSet() ? FocusRef().GetChildAtOffset() : nullptr;
335 void RemoveAllRanges() {
337 mAnchorFocusRange
= nullptr;
338 mDirection
= nsDirection::eDirNext
;
342 * APIs to store ranges with only container node and offset in it, and track
343 * them with RangeUpdater.
345 [[nodiscard
]] bool SaveAndTrackRanges(HTMLEditor
& aHTMLEditor
);
346 [[nodiscard
]] bool HasSavedRanges() const { return mSavedRanges
.isSome(); }
347 void ClearSavedRanges();
348 void RestoreFromSavedRanges() {
349 MOZ_DIAGNOSTIC_ASSERT(mSavedRanges
.isSome());
350 if (mSavedRanges
.isNothing()) {
353 mSavedRanges
->ApplyTo(*this);
358 * Apply mRanges and mDirection to aSelection.
360 MOZ_CAN_RUN_SCRIPT nsresult
ApplyTo(dom::Selection
& aSelection
) {
361 dom::SelectionBatcher
selectionBatcher(aSelection
, __FUNCTION__
);
362 aSelection
.RemoveAllRanges(IgnoreErrors());
363 MOZ_ASSERT(!aSelection
.RangeCount());
364 aSelection
.SetDirection(mDirection
);
365 IgnoredErrorResult error
;
366 for (const OwningNonNull
<nsRange
>& range
: mRanges
) {
367 // MOZ_KnownLive(range) due to bug 1622253
368 aSelection
.AddRangeAndSelectFramesAndNotifyListeners(MOZ_KnownLive(range
),
370 if (error
.Failed()) {
371 return error
.StealNSResult();
378 * If the points are same (i.e., mean a collapsed range) and in an empty block
379 * element except the padding <br> element, this makes aStartPoint and
380 * aEndPoint contain the padding <br> element.
382 static void UpdatePointsToSelectAllChildrenIfCollapsedInEmptyBlockElement(
383 EditorDOMPoint
& aStartPoint
, EditorDOMPoint
& aEndPoint
,
384 const dom::Element
& aEditingHost
);
387 * CreateRangeExtendedToHardLineStartAndEnd() creates an nsRange instance
388 * which may be expanded to start/end of hard line at both edges of the given
389 * range. If this fails handling something, returns nullptr.
391 static already_AddRefed
<nsRange
>
392 CreateRangeWrappingStartAndEndLinesContainingBoundaries(
393 const EditorDOMRange
& aRange
, EditSubAction aEditSubAction
,
394 const dom::Element
& aEditingHost
) {
395 if (!aRange
.IsPositioned()) {
398 return CreateRangeWrappingStartAndEndLinesContainingBoundaries(
399 aRange
.StartRef(), aRange
.EndRef(), aEditSubAction
, aEditingHost
);
401 static already_AddRefed
<nsRange
>
402 CreateRangeWrappingStartAndEndLinesContainingBoundaries(
403 const EditorDOMPoint
& aStartPoint
, const EditorDOMPoint
& aEndPoint
,
404 EditSubAction aEditSubAction
, const dom::Element
& aEditingHost
) {
405 RefPtr
<nsRange
> range
=
406 nsRange::Create(aStartPoint
.ToRawRangeBoundary(),
407 aEndPoint
.ToRawRangeBoundary(), IgnoreErrors());
408 if (MOZ_UNLIKELY(!range
)) {
411 if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
412 *range
, aEditSubAction
, aEditingHost
)) ||
413 MOZ_UNLIKELY(!range
->IsPositioned())) {
416 return range
.forget();
420 * Splits text nodes if each range end is in middle of a text node, then,
421 * calls HTMLEditor::SplitParentInlineElementsAtRangeEdges(RangeItem&) for
422 * each range. Finally, updates ranges to keep edit target ranges as
425 * @param aHTMLEditor The HTMLEditor which will handle the splittings.
426 * @return A suggest point to put caret if succeeded, but it may be
429 [[nodiscard
]] MOZ_CAN_RUN_SCRIPT Result
<EditorDOMPoint
, nsresult
>
430 SplitTextNodesAtEndBoundariesAndParentInlineElementsAtBoundaries(
431 HTMLEditor
& aHTMLEditor
);
434 * CollectEditTargetNodes() collects edit target nodes the ranges.
435 * First, this collects all nodes in given ranges, then, modifies the
436 * result for specific edit sub-actions.
438 enum class CollectNonEditableNodes
{ No
, Yes
};
439 nsresult
CollectEditTargetNodes(
440 const HTMLEditor
& aHTMLEditor
,
441 nsTArray
<OwningNonNull
<nsIContent
>>& aOutArrayOfContents
,
442 EditSubAction aEditSubAction
,
443 CollectNonEditableNodes aCollectNonEditableNodes
) const;
446 * Retrieve a closest ancestor list element of a common ancestor of _A_ range
447 * of the ranges. This tries to retrieve it from the first range to the last
450 dom::Element
* GetClosestAncestorAnyListElementOfRange() const;
453 static nsresult
ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
454 nsRange
& aRange
, EditSubAction aEditSubAction
,
455 const dom::Element
& aEditingHost
);
457 AutoTArray
<mozilla::OwningNonNull
<nsRange
>, 8> mRanges
;
458 RefPtr
<nsRange
> mAnchorFocusRange
;
459 nsDirection mDirection
= nsDirection::eDirNext
;
460 Maybe
<SelectionState
> mSavedRanges
;
461 RefPtr
<HTMLEditor
> mTrackingHTMLEditor
;
464 } // namespace mozilla
466 #endif // #ifndef AutoRangeArray_h