Bug 1735777 [wpt PR 31236] - webrtc wpt: fix invalid unicode indexOf method call...
[gecko.git] / editor / libeditor / SelectionState.h
blobd5d415f4cc630cae4af6a24205170fa91a6548cb
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 mozilla_SelectionState_h
7 #define mozilla_SelectionState_h
9 #include "mozilla/EditorDOMPoint.h"
10 #include "mozilla/Maybe.h"
11 #include "mozilla/OwningNonNull.h"
12 #include "nsCOMPtr.h"
13 #include "nsDirection.h"
14 #include "nsINode.h"
15 #include "nsRange.h"
16 #include "nsTArray.h"
17 #include "nscore.h"
19 class nsCycleCollectionTraversalCallback;
20 class nsRange;
21 namespace mozilla {
22 class RangeUpdater;
23 namespace dom {
24 class Element;
25 class Selection;
26 class Text;
27 } // namespace dom
29 /**
30 * A helper struct for saving/setting ranges.
32 struct RangeItem final {
33 RangeItem() : mStartOffset(0), mEndOffset(0) {}
35 private:
36 // Private destructor, to discourage deletion outside of Release():
37 ~RangeItem() = default;
39 public:
40 void StoreRange(const nsRange& aRange);
41 void StoreRange(const EditorRawDOMPoint& aStartPoint,
42 const EditorRawDOMPoint& aEndPoint) {
43 MOZ_ASSERT(aStartPoint.IsSet());
44 MOZ_ASSERT(aEndPoint.IsSet());
45 mStartContainer = aStartPoint.GetContainer();
46 mStartOffset = aStartPoint.Offset();
47 mEndContainer = aEndPoint.GetContainer();
48 mEndOffset = aEndPoint.Offset();
50 void Clear() {
51 mStartContainer = mEndContainer = nullptr;
52 mStartOffset = mEndOffset = 0;
54 already_AddRefed<nsRange> GetRange();
55 bool IsCollapsed() const {
56 return mStartContainer == mEndContainer && mStartOffset == mEndOffset;
58 bool IsSet() const { return mStartContainer && mEndContainer; }
59 EditorDOMPoint StartPoint() const {
60 return EditorDOMPoint(mStartContainer, mStartOffset);
62 EditorDOMPoint EndPoint() const {
63 return EditorDOMPoint(mEndContainer, mEndOffset);
65 EditorRawDOMPoint StartRawPoint() const {
66 return EditorRawDOMPoint(mStartContainer, mStartOffset);
68 EditorRawDOMPoint EndRawPoint() const {
69 return EditorRawDOMPoint(mEndContainer, mEndOffset);
72 NS_INLINE_DECL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RangeItem)
73 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(RangeItem)
75 nsCOMPtr<nsINode> mStartContainer;
76 nsCOMPtr<nsINode> mEndContainer;
77 uint32_t mStartOffset;
78 uint32_t mEndOffset;
81 /**
82 * mozilla::SelectionState
84 * Class for recording selection info. Stores selection as collection of
85 * { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store
86 * ranges since dom gravity will possibly change the ranges.
89 class SelectionState final {
90 public:
91 SelectionState();
92 ~SelectionState() { Clear(); }
94 void SaveSelection(dom::Selection& aSelection);
95 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
96 RestoreSelection(dom::Selection& aSelection);
97 bool IsCollapsed() const;
98 bool Equals(SelectionState& aOther) const;
99 void Clear();
100 bool IsEmpty() const;
102 private:
103 CopyableAutoTArray<RefPtr<RangeItem>, 1> mArray;
104 nsDirection mDirection;
106 friend class RangeUpdater;
107 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
108 SelectionState&, const char*,
109 uint32_t);
110 friend void ImplCycleCollectionUnlink(SelectionState&);
113 inline void ImplCycleCollectionTraverse(
114 nsCycleCollectionTraversalCallback& aCallback, SelectionState& aField,
115 const char* aName, uint32_t aFlags = 0) {
116 ImplCycleCollectionTraverse(aCallback, aField.mArray, aName, aFlags);
119 inline void ImplCycleCollectionUnlink(SelectionState& aField) {
120 ImplCycleCollectionUnlink(aField.mArray);
123 class MOZ_STACK_CLASS RangeUpdater final {
124 public:
125 RangeUpdater();
127 void RegisterRangeItem(RangeItem& aRangeItem);
128 void DropRangeItem(RangeItem& aRangeItem);
129 void RegisterSelectionState(SelectionState& aSelectionState);
130 void DropSelectionState(SelectionState& aSelectionState);
132 // editor selection gravity routines. Note that we can't always depend on
133 // DOM Range gravity to do what we want to the "real" selection. For
134 // instance, if you move a node, that corresponds to deleting it and
135 // reinserting it. DOM Range gravity will promote the selection out of the
136 // node on deletion, which is not what you want if you know you are
137 // reinserting it.
138 template <typename PT, typename CT>
139 nsresult SelAdjCreateNode(const EditorDOMPointBase<PT, CT>& aPoint);
140 template <typename PT, typename CT>
141 nsresult SelAdjInsertNode(const EditorDOMPointBase<PT, CT>& aPoint);
142 void SelAdjDeleteNode(nsINode& aNode);
143 nsresult SelAdjSplitNode(nsIContent& aRightNode, nsIContent& aNewLeftNode);
144 nsresult SelAdjJoinNodes(nsINode& aLeftNode, nsINode& aRightNode,
145 nsINode& aParent, uint32_t aOffset,
146 uint32_t aOldLeftNodeLength);
147 void SelAdjInsertText(const dom::Text& aTextNode, uint32_t aOffset,
148 uint32_t aInsertedLength);
149 void SelAdjDeleteText(const dom::Text& aTextNode, uint32_t aOffset,
150 uint32_t aDeletedLength);
151 void SelAdjReplaceText(const dom::Text& aTextNode, uint32_t aOffset,
152 uint32_t aReplacedLength, uint32_t aInsertedLength);
153 // the following gravity routines need will/did sandwiches, because the other
154 // gravity routines will be called inside of these sandwiches, but should be
155 // ignored.
156 void WillReplaceContainer() {
157 // XXX Isn't this possible with mutation event listener?
158 NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
159 mLocked = true;
161 void DidReplaceContainer(const dom::Element& aRemovedElement,
162 dom::Element& aInsertedElement);
163 void WillRemoveContainer() {
164 // XXX Isn't this possible with mutation event listener?
165 NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
166 mLocked = true;
168 void DidRemoveContainer(const dom::Element& aRemovedElement,
169 nsINode& aRemovedElementContainerNode,
170 uint32_t aOldOffsetOfRemovedElement,
171 uint32_t aOldChildCountOfRemovedElement);
172 void WillInsertContainer() {
173 // XXX Isn't this possible with mutation event listener?
174 NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
175 mLocked = true;
177 void DidInsertContainer() {
178 NS_WARNING_ASSERTION(mLocked, "Not locked");
179 mLocked = false;
181 void WillMoveNode() { mLocked = true; }
182 void DidMoveNode(const nsINode& aOldParent, uint32_t aOldOffset,
183 const nsINode& aNewParent, uint32_t aNewOffset);
185 private:
186 // TODO: A lot of loop in these methods check whether each item `nullptr` or
187 // not. We should make it not nullable later.
188 nsTArray<RefPtr<RangeItem>> mArray;
189 bool mLocked;
193 * Helper class for using SelectionState. Stack based class for doing
194 * preservation of dom points across editor actions.
197 class MOZ_STACK_CLASS AutoTrackDOMPoint final {
198 public:
199 AutoTrackDOMPoint() = delete;
200 AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, nsCOMPtr<nsINode>* aNode,
201 uint32_t* aOffset)
202 : mRangeUpdater(aRangeUpdater),
203 mNode(aNode),
204 mOffset(aOffset),
205 mPoint(nullptr),
206 mRangeItem(do_AddRef(new RangeItem())) {
207 mRangeItem->mStartContainer = *mNode;
208 mRangeItem->mEndContainer = *mNode;
209 mRangeItem->mStartOffset = *mOffset;
210 mRangeItem->mEndOffset = *mOffset;
211 mRangeUpdater.RegisterRangeItem(mRangeItem);
214 AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, EditorDOMPoint* aPoint)
215 : mRangeUpdater(aRangeUpdater),
216 mNode(nullptr),
217 mOffset(nullptr),
218 mPoint(aPoint),
219 mRangeItem(do_AddRef(new RangeItem())) {
220 mRangeItem->mStartContainer = mPoint->GetContainer();
221 mRangeItem->mEndContainer = mPoint->GetContainer();
222 mRangeItem->mStartOffset = mPoint->Offset();
223 mRangeItem->mEndOffset = mPoint->Offset();
224 mRangeUpdater.RegisterRangeItem(mRangeItem);
227 ~AutoTrackDOMPoint() {
228 mRangeUpdater.DropRangeItem(mRangeItem);
229 if (mPoint) {
230 // Setting `mPoint` with invalid DOM point causes hitting `NS_ASSERTION()`
231 // and the number of times may be too many. (E.g., 1533913.html hits
232 // over 700 times!) We should just put warning instead.
233 if (NS_WARN_IF(!mRangeItem->mStartContainer)) {
234 mPoint->Clear();
235 return;
237 if (NS_WARN_IF(mRangeItem->mStartContainer->Length() <
238 mRangeItem->mStartOffset)) {
239 mPoint->SetToEndOf(mRangeItem->mStartContainer);
240 return;
242 mPoint->Set(mRangeItem->mStartContainer, mRangeItem->mStartOffset);
243 return;
245 *mNode = mRangeItem->mStartContainer;
246 *mOffset = mRangeItem->mStartOffset;
249 private:
250 RangeUpdater& mRangeUpdater;
251 // Allow tracking nsINode until nsNode is gone
252 nsCOMPtr<nsINode>* mNode;
253 uint32_t* mOffset;
254 EditorDOMPoint* mPoint;
255 OwningNonNull<RangeItem> mRangeItem;
258 class MOZ_STACK_CLASS AutoTrackDOMRange final {
259 public:
260 AutoTrackDOMRange() = delete;
261 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, EditorDOMPoint* aStartPoint,
262 EditorDOMPoint* aEndPoint)
263 : mRangeRefPtr(nullptr), mRangeOwningNonNull(nullptr) {
264 mStartPointTracker.emplace(aRangeUpdater, aStartPoint);
265 mEndPointTracker.emplace(aRangeUpdater, aEndPoint);
267 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, EditorDOMRange* aRange)
268 : mRangeRefPtr(nullptr), mRangeOwningNonNull(nullptr) {
269 mStartPointTracker.emplace(
270 aRangeUpdater, const_cast<EditorDOMPoint*>(&aRange->StartRef()));
271 mEndPointTracker.emplace(aRangeUpdater,
272 const_cast<EditorDOMPoint*>(&aRange->EndRef()));
274 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, RefPtr<nsRange>* aRange)
275 : mStartPoint((*aRange)->StartRef()),
276 mEndPoint((*aRange)->EndRef()),
277 mRangeRefPtr(aRange),
278 mRangeOwningNonNull(nullptr) {
279 mStartPointTracker.emplace(aRangeUpdater, &mStartPoint);
280 mEndPointTracker.emplace(aRangeUpdater, &mEndPoint);
282 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, OwningNonNull<nsRange>* aRange)
283 : mStartPoint((*aRange)->StartRef()),
284 mEndPoint((*aRange)->EndRef()),
285 mRangeRefPtr(nullptr),
286 mRangeOwningNonNull(aRange) {
287 mStartPointTracker.emplace(aRangeUpdater, &mStartPoint);
288 mEndPointTracker.emplace(aRangeUpdater, &mEndPoint);
290 ~AutoTrackDOMRange() {
291 if (!mRangeRefPtr && !mRangeOwningNonNull) {
292 // The destructor of the trackers will update automatically.
293 return;
295 // Otherwise, destroy them now.
296 mStartPointTracker.reset();
297 mEndPointTracker.reset();
298 if (mRangeRefPtr) {
299 (*mRangeRefPtr)
300 ->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(),
301 mEndPoint.ToRawRangeBoundary());
302 return;
304 if (mRangeOwningNonNull) {
305 (*mRangeOwningNonNull)
306 ->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(),
307 mEndPoint.ToRawRangeBoundary());
308 return;
312 private:
313 Maybe<AutoTrackDOMPoint> mStartPointTracker;
314 Maybe<AutoTrackDOMPoint> mEndPointTracker;
315 EditorDOMPoint mStartPoint;
316 EditorDOMPoint mEndPoint;
317 RefPtr<nsRange>* mRangeRefPtr;
318 OwningNonNull<nsRange>* mRangeOwningNonNull;
322 * Another helper class for SelectionState. Stack based class for doing
323 * Will/DidReplaceContainer()
326 class MOZ_STACK_CLASS AutoReplaceContainerSelNotify final {
327 public:
328 AutoReplaceContainerSelNotify() = delete;
329 // FYI: Marked as `MOZ_CAN_RUN_SCRIPT` for avoiding to use strong pointers
330 // for the members.
331 MOZ_CAN_RUN_SCRIPT
332 AutoReplaceContainerSelNotify(RangeUpdater& aRangeUpdater,
333 dom::Element& aOriginalElement,
334 dom::Element& aNewElement)
335 : mRangeUpdater(aRangeUpdater),
336 mOriginalElement(aOriginalElement),
337 mNewElement(aNewElement) {
338 mRangeUpdater.WillReplaceContainer();
341 ~AutoReplaceContainerSelNotify() {
342 mRangeUpdater.DidReplaceContainer(mOriginalElement, mNewElement);
345 private:
346 RangeUpdater& mRangeUpdater;
347 dom::Element& mOriginalElement;
348 dom::Element& mNewElement;
352 * Another helper class for SelectionState. Stack based class for doing
353 * Will/DidRemoveContainer()
356 class MOZ_STACK_CLASS AutoRemoveContainerSelNotify final {
357 public:
358 AutoRemoveContainerSelNotify() = delete;
359 AutoRemoveContainerSelNotify(RangeUpdater& aRangeUpdater,
360 const EditorDOMPoint& aAtRemovingElement)
361 : mRangeUpdater(aRangeUpdater),
362 mRemovingElement(*aAtRemovingElement.GetChild()->AsElement()),
363 mParentNode(*aAtRemovingElement.GetContainer()),
364 mOffsetInParent(aAtRemovingElement.Offset()),
365 mChildCountOfRemovingElement(mRemovingElement->GetChildCount()) {
366 MOZ_ASSERT(aAtRemovingElement.IsSet());
367 mRangeUpdater.WillRemoveContainer();
370 ~AutoRemoveContainerSelNotify() {
371 mRangeUpdater.DidRemoveContainer(mRemovingElement, mParentNode,
372 mOffsetInParent,
373 mChildCountOfRemovingElement);
376 private:
377 RangeUpdater& mRangeUpdater;
378 OwningNonNull<dom::Element> mRemovingElement;
379 OwningNonNull<nsINode> mParentNode;
380 uint32_t mOffsetInParent;
381 uint32_t mChildCountOfRemovingElement;
385 * Another helper class for SelectionState. Stack based class for doing
386 * Will/DidInsertContainer()
387 * XXX The lock state isn't useful if the edit action is triggered from
388 * a mutation event listener so that looks like that we can remove
389 * this class.
392 class MOZ_STACK_CLASS AutoInsertContainerSelNotify final {
393 private:
394 RangeUpdater& mRangeUpdater;
396 public:
397 AutoInsertContainerSelNotify() = delete;
398 explicit AutoInsertContainerSelNotify(RangeUpdater& aRangeUpdater)
399 : mRangeUpdater(aRangeUpdater) {
400 mRangeUpdater.WillInsertContainer();
403 ~AutoInsertContainerSelNotify() { mRangeUpdater.DidInsertContainer(); }
407 * Another helper class for SelectionState. Stack based class for doing
408 * Will/DidMoveNode()
411 class MOZ_STACK_CLASS AutoMoveNodeSelNotify final {
412 public:
413 AutoMoveNodeSelNotify() = delete;
414 AutoMoveNodeSelNotify(RangeUpdater& aRangeUpdater,
415 const EditorDOMPoint& aOldPoint,
416 const EditorDOMPoint& aNewPoint)
417 : mRangeUpdater(aRangeUpdater),
418 mOldParent(*aOldPoint.GetContainer()),
419 mNewParent(*aNewPoint.GetContainer()),
420 mOldOffset(aOldPoint.Offset()),
421 mNewOffset(aNewPoint.Offset()) {
422 MOZ_ASSERT(aOldPoint.IsSet());
423 MOZ_ASSERT(aNewPoint.IsSet());
424 mRangeUpdater.WillMoveNode();
427 ~AutoMoveNodeSelNotify() {
428 mRangeUpdater.DidMoveNode(mOldParent, mOldOffset, mNewParent, mNewOffset);
431 EditorRawDOMPoint ComputeInsertionPoint() const {
432 if (&mOldParent == &mNewParent && mOldOffset < mNewOffset) {
433 return EditorRawDOMPoint(&mNewParent, mNewOffset - 1);
435 return EditorRawDOMPoint(&mNewParent, mNewOffset);
438 private:
439 RangeUpdater& mRangeUpdater;
440 nsINode& mOldParent;
441 nsINode& mNewParent;
442 uint32_t mOldOffset;
443 uint32_t mNewOffset;
446 } // namespace mozilla
448 #endif // #ifndef mozilla_SelectionState_h