Bug 1769033 - Add OpenBSD sandboxing support r=gaston
[gecko.git] / editor / libeditor / SelectionState.h
blob52b06a2bcaa3fd61f0dd08938bc994aa06f7a0f9
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/EditorForwards.h"
11 #include "mozilla/Maybe.h"
12 #include "mozilla/OwningNonNull.h"
13 #include "nsCOMPtr.h"
14 #include "nsDirection.h"
15 #include "nsINode.h"
16 #include "nsRange.h"
17 #include "nsTArray.h"
18 #include "nscore.h"
20 class nsCycleCollectionTraversalCallback;
21 class nsRange;
22 namespace mozilla {
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() const;
56 // Same as the API of dom::AbstractRange
57 [[nodiscard]] nsINode* GetRoot() const;
58 [[nodiscard]] bool Collapsed() const {
59 return mStartContainer == mEndContainer && mStartOffset == mEndOffset;
61 [[nodiscard]] bool IsPositioned() const {
62 return mStartContainer && mEndContainer;
64 [[nodiscard]] bool Equals(const RangeItem& aOther) const {
65 return mStartContainer == aOther.mStartContainer &&
66 mEndContainer == aOther.mEndContainer &&
67 mStartOffset == aOther.mStartOffset &&
68 mEndOffset == aOther.mEndOffset;
70 EditorDOMPoint StartPoint() const {
71 return EditorDOMPoint(mStartContainer, mStartOffset);
73 EditorDOMPoint EndPoint() const {
74 return EditorDOMPoint(mEndContainer, mEndOffset);
76 EditorRawDOMPoint StartRawPoint() const {
77 return EditorRawDOMPoint(mStartContainer, mStartOffset);
79 EditorRawDOMPoint EndRawPoint() const {
80 return EditorRawDOMPoint(mEndContainer, mEndOffset);
83 NS_INLINE_DECL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RangeItem)
84 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(RangeItem)
86 nsCOMPtr<nsINode> mStartContainer;
87 nsCOMPtr<nsINode> mEndContainer;
88 uint32_t mStartOffset;
89 uint32_t mEndOffset;
92 /**
93 * mozilla::SelectionState
95 * Class for recording selection info. Stores selection as collection of
96 * { {startnode, startoffset} , {endnode, endoffset} } tuples. Can't store
97 * ranges since dom gravity will possibly change the ranges.
100 class SelectionState final {
101 public:
103 * Same as the API as dom::Selection
105 [[nodiscard]] bool IsCollapsed() const {
106 if (mArray.Length() != 1) {
107 return false;
109 return mArray[0]->Collapsed();
112 void RemoveAllRanges() {
113 mArray.Clear();
114 mDirection = eDirNext;
117 [[nodiscard]] uint32_t RangeCount() const { return mArray.Length(); }
120 * Saving all ranges of aSelection.
122 void SaveSelection(dom::Selection& aSelection);
125 * Setting aSelection to have all ranges stored by this instance.
127 MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
128 RestoreSelection(dom::Selection& aSelection);
131 * HasOnlyCollapsedRange() returns true only when there is a positioned range
132 * which is collapsed. I.e., the selection represents a caret point.
134 [[nodiscard]] bool HasOnlyCollapsedRange() const {
135 if (mArray.Length() != 1) {
136 return false;
138 if (!mArray[0]->IsPositioned() || !mArray[0]->Collapsed()) {
139 return false;
141 return true;
145 * Equals() returns true only when there are same number of ranges and
146 * all their containers and offsets are exactly same. This won't check
147 * the validity of each range with the current DOM tree.
149 [[nodiscard]] bool Equals(const SelectionState& aOther) const;
152 * Returns common root node of all ranges' start and end containers.
153 * Some of them have different root nodes, this returns nullptr.
155 [[nodiscard]] nsINode* GetCommonRootNode() const {
156 nsINode* rootNode = nullptr;
157 for (const RefPtr<RangeItem>& rangeItem : mArray) {
158 nsINode* newRootNode = rangeItem->GetRoot();
159 if (!newRootNode || (rootNode && rootNode != newRootNode)) {
160 return nullptr;
162 rootNode = newRootNode;
164 return rootNode;
167 private:
168 CopyableAutoTArray<RefPtr<RangeItem>, 1> mArray;
169 nsDirection mDirection = eDirNext;
171 friend class RangeUpdater;
172 friend void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback&,
173 SelectionState&, const char*,
174 uint32_t);
175 friend void ImplCycleCollectionUnlink(SelectionState&);
178 inline void ImplCycleCollectionTraverse(
179 nsCycleCollectionTraversalCallback& aCallback, SelectionState& aField,
180 const char* aName, uint32_t aFlags = 0) {
181 ImplCycleCollectionTraverse(aCallback, aField.mArray, aName, aFlags);
184 inline void ImplCycleCollectionUnlink(SelectionState& aField) {
185 ImplCycleCollectionUnlink(aField.mArray);
188 class MOZ_STACK_CLASS RangeUpdater final {
189 public:
190 RangeUpdater();
192 void RegisterRangeItem(RangeItem& aRangeItem);
193 void DropRangeItem(RangeItem& aRangeItem);
194 void RegisterSelectionState(SelectionState& aSelectionState);
195 void DropSelectionState(SelectionState& aSelectionState);
197 // editor selection gravity routines. Note that we can't always depend on
198 // DOM Range gravity to do what we want to the "real" selection. For
199 // instance, if you move a node, that corresponds to deleting it and
200 // reinserting it. DOM Range gravity will promote the selection out of the
201 // node on deletion, which is not what you want if you know you are
202 // reinserting it.
203 template <typename PT, typename CT>
204 nsresult SelAdjCreateNode(const EditorDOMPointBase<PT, CT>& aPoint);
205 template <typename PT, typename CT>
206 nsresult SelAdjInsertNode(const EditorDOMPointBase<PT, CT>& aPoint);
207 void SelAdjDeleteNode(nsINode& aNode);
210 * SelAdjSplitNode() is called immediately after spliting aOriginalNode
211 * and inserted aNewContent into the DOM tree.
213 * @param aOriginalContent The node which was split.
214 * @param aSplitOffset The old offset in aOriginalContent at splitting
215 * it.
216 * @param aNewContent The new content node which was inserted into
217 * the DOM tree.
218 * @param aSplitNodeDirection Whether aNewNode was inserted before or after
219 * aOriginalContent.
221 nsresult SelAdjSplitNode(nsIContent& aOriginalContent, uint32_t aSplitOffset,
222 nsIContent& aNewContent,
223 SplitNodeDirection aSplitNodeDirection);
226 * SelAdjJoinNodes() is called immediately after joining aRemovedContent and
227 * the container of aStartOfRightContent.
229 * @param aStartOfRightContent The container is joined content node which
230 * now has all children or text data which were
231 * in aRemovedContent. And this points where
232 * the joined position.
233 * @param aRemovedContent The removed content.
234 * @param aOffsetOfRemovedContent The offset which aRemovedContent was in
235 * its ex-parent.
237 nsresult SelAdjJoinNodes(const EditorRawDOMPoint& aStartOfRightContent,
238 const nsIContent& aRemovedContent,
239 uint32_t aOffsetOfRemovedContent,
240 JoinNodesDirection aJoinNodesDirection);
241 void SelAdjInsertText(const dom::Text& aTextNode, uint32_t aOffset,
242 uint32_t aInsertedLength);
243 void SelAdjDeleteText(const dom::Text& aTextNode, uint32_t aOffset,
244 uint32_t aDeletedLength);
245 void SelAdjReplaceText(const dom::Text& aTextNode, uint32_t aOffset,
246 uint32_t aReplacedLength, uint32_t aInsertedLength);
247 // the following gravity routines need will/did sandwiches, because the other
248 // gravity routines will be called inside of these sandwiches, but should be
249 // ignored.
250 void WillReplaceContainer() {
251 // XXX Isn't this possible with mutation event listener?
252 NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
253 mLocked = true;
255 void DidReplaceContainer(const dom::Element& aRemovedElement,
256 dom::Element& aInsertedElement);
257 void WillRemoveContainer() {
258 // XXX Isn't this possible with mutation event listener?
259 NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
260 mLocked = true;
262 void DidRemoveContainer(const dom::Element& aRemovedElement,
263 nsINode& aRemovedElementContainerNode,
264 uint32_t aOldOffsetOfRemovedElement,
265 uint32_t aOldChildCountOfRemovedElement);
266 void WillInsertContainer() {
267 // XXX Isn't this possible with mutation event listener?
268 NS_WARNING_ASSERTION(!mLocked, "Has already been locked");
269 mLocked = true;
271 void DidInsertContainer() {
272 NS_WARNING_ASSERTION(mLocked, "Not locked");
273 mLocked = false;
275 void WillMoveNode() { mLocked = true; }
276 void DidMoveNode(const nsINode& aOldParent, uint32_t aOldOffset,
277 const nsINode& aNewParent, uint32_t aNewOffset);
279 private:
280 // TODO: A lot of loop in these methods check whether each item `nullptr` or
281 // not. We should make it not nullable later.
282 nsTArray<RefPtr<RangeItem>> mArray;
283 bool mLocked;
287 * Helper class for using SelectionState. Stack based class for doing
288 * preservation of dom points across editor actions.
291 class MOZ_STACK_CLASS AutoTrackDOMPoint final {
292 public:
293 AutoTrackDOMPoint() = delete;
294 AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, nsCOMPtr<nsINode>* aNode,
295 uint32_t* aOffset)
296 : mRangeUpdater(aRangeUpdater),
297 mNode(aNode),
298 mOffset(aOffset),
299 mRangeItem(do_AddRef(new RangeItem())) {
300 mRangeItem->mStartContainer = *mNode;
301 mRangeItem->mEndContainer = *mNode;
302 mRangeItem->mStartOffset = *mOffset;
303 mRangeItem->mEndOffset = *mOffset;
304 mRangeUpdater.RegisterRangeItem(mRangeItem);
307 AutoTrackDOMPoint(RangeUpdater& aRangeUpdater, EditorDOMPoint* aPoint)
308 : mRangeUpdater(aRangeUpdater),
309 mNode(nullptr),
310 mOffset(nullptr),
311 mPoint(Some(aPoint->IsSet() ? aPoint : nullptr)),
312 mRangeItem(do_AddRef(new RangeItem())) {
313 if (!aPoint->IsSet()) {
314 return; // Nothing should be tracked.
316 mRangeItem->mStartContainer = aPoint->GetContainer();
317 mRangeItem->mEndContainer = aPoint->GetContainer();
318 mRangeItem->mStartOffset = aPoint->Offset();
319 mRangeItem->mEndOffset = aPoint->Offset();
320 mRangeUpdater.RegisterRangeItem(mRangeItem);
323 ~AutoTrackDOMPoint() {
324 if (mPoint.isSome()) {
325 if (!mPoint.ref()) {
326 return; // We don't track anything.
328 mRangeUpdater.DropRangeItem(mRangeItem);
329 // Setting `mPoint` with invalid DOM point causes hitting `NS_ASSERTION()`
330 // and the number of times may be too many. (E.g., 1533913.html hits
331 // over 700 times!) We should just put warning instead.
332 if (NS_WARN_IF(!mRangeItem->mStartContainer)) {
333 mPoint.ref()->Clear();
334 return;
336 if (NS_WARN_IF(mRangeItem->mStartContainer->Length() <
337 mRangeItem->mStartOffset)) {
338 mPoint.ref()->SetToEndOf(mRangeItem->mStartContainer);
339 return;
341 mPoint.ref()->Set(mRangeItem->mStartContainer, mRangeItem->mStartOffset);
342 return;
344 mRangeUpdater.DropRangeItem(mRangeItem);
345 *mNode = mRangeItem->mStartContainer;
346 *mOffset = mRangeItem->mStartOffset;
349 private:
350 RangeUpdater& mRangeUpdater;
351 // Allow tracking nsINode until nsNode is gone
352 nsCOMPtr<nsINode>* mNode;
353 uint32_t* mOffset;
354 Maybe<EditorDOMPoint*> mPoint;
355 OwningNonNull<RangeItem> mRangeItem;
358 class MOZ_STACK_CLASS AutoTrackDOMRange final {
359 public:
360 AutoTrackDOMRange() = delete;
361 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, EditorDOMPoint* aStartPoint,
362 EditorDOMPoint* aEndPoint)
363 : mRangeRefPtr(nullptr), mRangeOwningNonNull(nullptr) {
364 mStartPointTracker.emplace(aRangeUpdater, aStartPoint);
365 mEndPointTracker.emplace(aRangeUpdater, aEndPoint);
367 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, EditorDOMRange* aRange)
368 : mRangeRefPtr(nullptr), mRangeOwningNonNull(nullptr) {
369 mStartPointTracker.emplace(
370 aRangeUpdater, const_cast<EditorDOMPoint*>(&aRange->StartRef()));
371 mEndPointTracker.emplace(aRangeUpdater,
372 const_cast<EditorDOMPoint*>(&aRange->EndRef()));
374 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, RefPtr<nsRange>* aRange)
375 : mStartPoint((*aRange)->StartRef()),
376 mEndPoint((*aRange)->EndRef()),
377 mRangeRefPtr(aRange),
378 mRangeOwningNonNull(nullptr) {
379 mStartPointTracker.emplace(aRangeUpdater, &mStartPoint);
380 mEndPointTracker.emplace(aRangeUpdater, &mEndPoint);
382 AutoTrackDOMRange(RangeUpdater& aRangeUpdater, OwningNonNull<nsRange>* aRange)
383 : mStartPoint((*aRange)->StartRef()),
384 mEndPoint((*aRange)->EndRef()),
385 mRangeRefPtr(nullptr),
386 mRangeOwningNonNull(aRange) {
387 mStartPointTracker.emplace(aRangeUpdater, &mStartPoint);
388 mEndPointTracker.emplace(aRangeUpdater, &mEndPoint);
390 ~AutoTrackDOMRange() {
391 if (!mRangeRefPtr && !mRangeOwningNonNull) {
392 // The destructor of the trackers will update automatically.
393 return;
395 // Otherwise, destroy them now.
396 mStartPointTracker.reset();
397 mEndPointTracker.reset();
398 if (mRangeRefPtr) {
399 (*mRangeRefPtr)
400 ->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(),
401 mEndPoint.ToRawRangeBoundary());
402 return;
404 if (mRangeOwningNonNull) {
405 (*mRangeOwningNonNull)
406 ->SetStartAndEnd(mStartPoint.ToRawRangeBoundary(),
407 mEndPoint.ToRawRangeBoundary());
408 return;
412 private:
413 Maybe<AutoTrackDOMPoint> mStartPointTracker;
414 Maybe<AutoTrackDOMPoint> mEndPointTracker;
415 EditorDOMPoint mStartPoint;
416 EditorDOMPoint mEndPoint;
417 RefPtr<nsRange>* mRangeRefPtr;
418 OwningNonNull<nsRange>* mRangeOwningNonNull;
422 * Another helper class for SelectionState. Stack based class for doing
423 * Will/DidReplaceContainer()
426 class MOZ_STACK_CLASS AutoReplaceContainerSelNotify final {
427 public:
428 AutoReplaceContainerSelNotify() = delete;
429 // FYI: Marked as `MOZ_CAN_RUN_SCRIPT` for avoiding to use strong pointers
430 // for the members.
431 MOZ_CAN_RUN_SCRIPT
432 AutoReplaceContainerSelNotify(RangeUpdater& aRangeUpdater,
433 dom::Element& aOriginalElement,
434 dom::Element& aNewElement)
435 : mRangeUpdater(aRangeUpdater),
436 mOriginalElement(aOriginalElement),
437 mNewElement(aNewElement) {
438 mRangeUpdater.WillReplaceContainer();
441 ~AutoReplaceContainerSelNotify() {
442 mRangeUpdater.DidReplaceContainer(mOriginalElement, mNewElement);
445 private:
446 RangeUpdater& mRangeUpdater;
447 dom::Element& mOriginalElement;
448 dom::Element& mNewElement;
452 * Another helper class for SelectionState. Stack based class for doing
453 * Will/DidRemoveContainer()
456 class MOZ_STACK_CLASS AutoRemoveContainerSelNotify final {
457 public:
458 AutoRemoveContainerSelNotify() = delete;
459 AutoRemoveContainerSelNotify(RangeUpdater& aRangeUpdater,
460 const EditorDOMPoint& aAtRemovingElement)
461 : mRangeUpdater(aRangeUpdater),
462 mRemovingElement(*aAtRemovingElement.GetChild()->AsElement()),
463 mParentNode(*aAtRemovingElement.GetContainer()),
464 mOffsetInParent(aAtRemovingElement.Offset()),
465 mChildCountOfRemovingElement(mRemovingElement->GetChildCount()) {
466 MOZ_ASSERT(aAtRemovingElement.IsSet());
467 mRangeUpdater.WillRemoveContainer();
470 ~AutoRemoveContainerSelNotify() {
471 mRangeUpdater.DidRemoveContainer(mRemovingElement, mParentNode,
472 mOffsetInParent,
473 mChildCountOfRemovingElement);
476 private:
477 RangeUpdater& mRangeUpdater;
478 OwningNonNull<dom::Element> mRemovingElement;
479 OwningNonNull<nsINode> mParentNode;
480 uint32_t mOffsetInParent;
481 uint32_t mChildCountOfRemovingElement;
485 * Another helper class for SelectionState. Stack based class for doing
486 * Will/DidInsertContainer()
487 * XXX The lock state isn't useful if the edit action is triggered from
488 * a mutation event listener so that looks like that we can remove
489 * this class.
492 class MOZ_STACK_CLASS AutoInsertContainerSelNotify final {
493 private:
494 RangeUpdater& mRangeUpdater;
496 public:
497 AutoInsertContainerSelNotify() = delete;
498 explicit AutoInsertContainerSelNotify(RangeUpdater& aRangeUpdater)
499 : mRangeUpdater(aRangeUpdater) {
500 mRangeUpdater.WillInsertContainer();
503 ~AutoInsertContainerSelNotify() { mRangeUpdater.DidInsertContainer(); }
507 * Another helper class for SelectionState. Stack based class for doing
508 * Will/DidMoveNode()
511 class MOZ_STACK_CLASS AutoMoveNodeSelNotify final {
512 public:
513 AutoMoveNodeSelNotify() = delete;
514 AutoMoveNodeSelNotify(RangeUpdater& aRangeUpdater,
515 const EditorDOMPoint& aOldPoint,
516 const EditorDOMPoint& aNewPoint)
517 : mRangeUpdater(aRangeUpdater),
518 mOldParent(*aOldPoint.GetContainer()),
519 mNewParent(*aNewPoint.GetContainer()),
520 mOldOffset(aOldPoint.Offset()),
521 mNewOffset(aNewPoint.Offset()) {
522 MOZ_ASSERT(aOldPoint.IsSet());
523 MOZ_ASSERT(aNewPoint.IsSet());
524 mRangeUpdater.WillMoveNode();
527 ~AutoMoveNodeSelNotify() {
528 mRangeUpdater.DidMoveNode(mOldParent, mOldOffset, mNewParent, mNewOffset);
531 template <typename EditorDOMPointType>
532 EditorDOMPointType ComputeInsertionPoint() const {
533 if (&mOldParent == &mNewParent && mOldOffset < mNewOffset) {
534 return EditorDOMPointType(&mNewParent, mNewOffset - 1);
536 return EditorDOMPointType(&mNewParent, mNewOffset);
539 private:
540 RangeUpdater& mRangeUpdater;
541 nsINode& mOldParent;
542 nsINode& mNewParent;
543 uint32_t mOldOffset;
544 uint32_t mNewOffset;
547 } // namespace mozilla
549 #endif // #ifndef mozilla_SelectionState_h