Bug 1776056 - Switch to the tab an animation is running and make sure the animation...
[gecko.git] / editor / libeditor / AutoRangeArray.h
blob67a9eb93b5300b8c5d4e442487767ea0aecaee90
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
30 namespace mozilla {
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 {
39 public:
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);
48 ~AutoRangeArray();
50 void Initialize(const dom::Selection& aSelection) {
51 ClearSavedRanges();
52 mDirection = aSelection.GetDirection();
53 mRanges.Clear();
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();
63 /**
64 * Check whether all ranges in content nodes or not. If the ranges is empty,
65 * this returns false.
67 [[nodiscard]] bool IsInContent() const {
68 if (mRanges.IsEmpty()) {
69 return false;
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())) {
76 return false;
79 return true;
82 /**
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);
89 /**
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);
96 /**
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.
124 [[nodiscard]] bool
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)) {
131 return true;
133 nsINode* endContainer = range->GetEndContainer();
134 if (startContainer == endContainer) {
135 continue;
137 if (endContainer && endContainer->IsInclusiveDescendantOf(&aContent)) {
138 return true;
141 return false;
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 {
148 return mRanges[0];
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());
157 return ranges;
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) {
176 mRanges.Clear();
177 if (!mAnchorFocusRange) {
178 mAnchorFocusRange = nsRange::Create(&aNode);
179 if (!mAnchorFocusRange) {
180 return NS_ERROR_FAILURE;
183 ErrorResult error;
184 mAnchorFocusRange->SelectNode(aNode, error);
185 if (error.Failed()) {
186 mAnchorFocusRange = nullptr;
187 return error.StealNSResult();
189 mRanges.AppendElement(*mAnchorFocusRange);
190 return NS_OK;
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
198 * calling this.
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) {
231 mRanges.Clear();
232 if (!mAnchorFocusRange) {
233 ErrorResult error;
234 mAnchorFocusRange = nsRange::Create(aPoint.ToRawRangeBoundary(),
235 aPoint.ToRawRangeBoundary(), error);
236 if (error.Failed()) {
237 mAnchorFocusRange = nullptr;
238 return error.StealNSResult();
240 } else {
241 nsresult rv = mAnchorFocusRange->CollapseTo(aPoint.ToRawRangeBoundary());
242 if (NS_FAILED(rv)) {
243 mAnchorFocusRange = nullptr;
244 return rv;
247 mRanges.AppendElement(*mAnchorFocusRange);
248 return NS_OK;
250 template <typename SPT, typename SCT, typename EPT, typename ECT>
251 nsresult SetStartAndEnd(const EditorDOMPointBase<SPT, SCT>& aStart,
252 const EditorDOMPointBase<EPT, ECT>& aEnd) {
253 mRanges.Clear();
254 if (!mAnchorFocusRange) {
255 ErrorResult error;
256 mAnchorFocusRange = nsRange::Create(aStart.ToRawRangeBoundary(),
257 aEnd.ToRawRangeBoundary(), error);
258 if (error.Failed()) {
259 mAnchorFocusRange = nullptr;
260 return error.StealNSResult();
262 } else {
263 nsresult rv = mAnchorFocusRange->SetStartAndEnd(
264 aStart.ToRawRangeBoundary(), aEnd.ToRawRangeBoundary());
265 if (NS_FAILED(rv)) {
266 mAnchorFocusRange = nullptr;
267 return rv;
270 mRanges.AppendElement(*mAnchorFocusRange);
271 return NS_OK;
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())) {
277 mRanges.Clear();
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()
304 ? AnchorRef()
305 .Offset(RangeBoundary::OffsetFilter::kValidOffsets)
306 .valueOr(0)
307 : 0;
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()
326 ? FocusRef()
327 .Offset(RangeBoundary::OffsetFilter::kValidOffsets)
328 .valueOr(0)
329 : 0;
331 [[nodiscard]] nsIContent* GetChildAtFocusOffset() const {
332 return FocusRef().IsSet() ? FocusRef().GetChildAtOffset() : nullptr;
335 void RemoveAllRanges() {
336 mRanges.Clear();
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()) {
351 return;
353 mSavedRanges->ApplyTo(*this);
354 ClearSavedRanges();
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),
369 error);
370 if (error.Failed()) {
371 return error.StealNSResult();
374 return NS_OK;
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()) {
396 return nullptr;
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)) {
409 return nullptr;
411 if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
412 *range, aEditSubAction, aEditingHost)) ||
413 MOZ_UNLIKELY(!range->IsPositioned())) {
414 return nullptr;
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
423 * expected.
425 * @param aHTMLEditor The HTMLEditor which will handle the splittings.
426 * @return A suggest point to put caret if succeeded, but it may be
427 * unset.
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
448 * range.
450 dom::Element* GetClosestAncestorAnyListElementOfRange() const;
452 private:
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