Bug 1890689 apply drift correction to input rate instead of output rate r=pehrsons
[gecko.git] / editor / libeditor / AutoRangeArray.h
blob3f3c4bf5b6ce323b917b334b5dd3e0f6ffcacbd4
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
31 namespace mozilla {
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 {
40 public:
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);
50 ~AutoRangeArray();
52 void Initialize(const dom::Selection& aSelection) {
53 ClearSavedRanges();
54 mDirection = aSelection.GetDirection();
55 mRanges.Clear();
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();
65 /**
66 * Check whether all ranges in content nodes or not. If the ranges is empty,
67 * this returns false.
69 [[nodiscard]] bool IsInContent() const {
70 if (mRanges.IsEmpty()) {
71 return false;
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())) {
78 return false;
81 return true;
84 /**
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);
91 /**
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);
98 /**
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.
127 [[nodiscard]] bool
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)) {
134 return true;
136 nsINode* endContainer = range->GetEndContainer();
137 if (startContainer == endContainer) {
138 continue;
140 if (endContainer && endContainer->IsInclusiveDescendantOf(&aContent)) {
141 return true;
144 return false;
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 {
151 return mRanges[0];
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());
160 return ranges;
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) {
179 mRanges.Clear();
180 if (!mAnchorFocusRange) {
181 mAnchorFocusRange = nsRange::Create(&aNode);
182 if (!mAnchorFocusRange) {
183 return NS_ERROR_FAILURE;
186 ErrorResult error;
187 mAnchorFocusRange->SelectNode(aNode, error);
188 if (error.Failed()) {
189 mAnchorFocusRange = nullptr;
190 return error.StealNSResult();
192 mRanges.AppendElement(*mAnchorFocusRange);
193 return NS_OK;
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
201 * calling this.
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) {
234 mRanges.Clear();
235 if (!mAnchorFocusRange) {
236 ErrorResult error;
237 mAnchorFocusRange = nsRange::Create(aPoint.ToRawRangeBoundary(),
238 aPoint.ToRawRangeBoundary(), error);
239 if (error.Failed()) {
240 mAnchorFocusRange = nullptr;
241 return error.StealNSResult();
243 } else {
244 nsresult rv = mAnchorFocusRange->CollapseTo(aPoint.ToRawRangeBoundary());
245 if (NS_FAILED(rv)) {
246 mAnchorFocusRange = nullptr;
247 return rv;
250 mRanges.AppendElement(*mAnchorFocusRange);
251 return NS_OK;
253 template <typename SPT, typename SCT, typename EPT, typename ECT>
254 nsresult SetStartAndEnd(const EditorDOMPointBase<SPT, SCT>& aStart,
255 const EditorDOMPointBase<EPT, ECT>& aEnd) {
256 mRanges.Clear();
257 if (!mAnchorFocusRange) {
258 ErrorResult error;
259 mAnchorFocusRange = nsRange::Create(aStart.ToRawRangeBoundary(),
260 aEnd.ToRawRangeBoundary(), error);
261 if (error.Failed()) {
262 mAnchorFocusRange = nullptr;
263 return error.StealNSResult();
265 } else {
266 nsresult rv = mAnchorFocusRange->SetStartAndEnd(
267 aStart.ToRawRangeBoundary(), aEnd.ToRawRangeBoundary());
268 if (NS_FAILED(rv)) {
269 mAnchorFocusRange = nullptr;
270 return rv;
273 mRanges.AppendElement(*mAnchorFocusRange);
274 return NS_OK;
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())) {
280 mRanges.Clear();
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()
307 ? AnchorRef()
308 .Offset(RangeBoundary::OffsetFilter::kValidOffsets)
309 .valueOr(0)
310 : 0;
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()
329 ? FocusRef()
330 .Offset(RangeBoundary::OffsetFilter::kValidOffsets)
331 .valueOr(0)
332 : 0;
334 [[nodiscard]] nsIContent* GetChildAtFocusOffset() const {
335 return FocusRef().IsSet() ? FocusRef().GetChildAtOffset() : nullptr;
338 void RemoveAllRanges() {
339 mRanges.Clear();
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()) {
354 return;
356 mSavedRanges->ApplyTo(*this);
357 ClearSavedRanges();
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),
372 error);
373 if (error.Failed()) {
374 return error.StealNSResult();
377 return NS_OK;
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()) {
399 return nullptr;
401 return CreateRangeWrappingStartAndEndLinesContainingBoundaries(
402 aRange.StartRef(), aRange.EndRef(), aEditSubAction, aBlockInlineCheck,
403 aEditingHost);
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)) {
414 return nullptr;
416 if (NS_FAILED(ExtendRangeToWrapStartAndEndLinesContainingBoundaries(
417 *range, aEditSubAction, aBlockInlineCheck, aEditingHost)) ||
418 MOZ_UNLIKELY(!range->IsPositioned())) {
419 return nullptr;
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
434 * split it.
435 * @return A suggest point to put caret if succeeded, but
436 * it may be unset.
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
459 * range.
461 dom::Element* GetClosestAncestorAnyListElementOfRange() const;
463 private:
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