1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  * Implementation of the DOM Range object.
9  */
10 
11 #ifndef nsRange_h___
12 #define nsRange_h___
13 
14 #include "nsCOMPtr.h"
15 #include "mozilla/dom/AbstractRange.h"
16 #include "prmon.h"
17 #include "nsStubMutationObserver.h"
18 #include "nsWrapperCache.h"
19 #include "mozilla/Attributes.h"
20 #include "mozilla/ErrorResult.h"
21 #include "mozilla/LinkedList.h"
22 #include "mozilla/RangeBoundary.h"
23 #include "mozilla/WeakPtr.h"
24 
25 namespace mozilla {
26 class RectCallback;
27 namespace dom {
28 struct ClientRectsAndTexts;
29 class DocGroup;
30 class DocumentFragment;
31 class DOMRect;
32 class DOMRectList;
33 class InspectorFontFace;
34 class Selection;
35 }  // namespace dom
36 }  // namespace mozilla
37 
38 class nsRange final : public mozilla::dom::AbstractRange,
39                       public nsStubMutationObserver,
40                       // For linking together selection-associated ranges.
41                       public mozilla::LinkedListElement<nsRange> {
42   using ErrorResult = mozilla::ErrorResult;
43   using AbstractRange = mozilla::dom::AbstractRange;
44   using DocGroup = mozilla::dom::DocGroup;
45   using DOMRect = mozilla::dom::DOMRect;
46   using DOMRectList = mozilla::dom::DOMRectList;
47   using RangeBoundary = mozilla::RangeBoundary;
48   using RawRangeBoundary = mozilla::RawRangeBoundary;
49 
50   virtual ~nsRange();
51   explicit nsRange(nsINode* aNode);
52 
53  public:
54   /**
55    * The following Create() returns `nsRange` instance which is initialized
56    * only with aNode.  The result is never positioned.
57    */
58   static already_AddRefed<nsRange> Create(nsINode* aNode);
59 
60   /**
61    * The following Create() may return `nsRange` instance which is initialized
62    * with given range or points.  If it fails initializing new range with the
63    * arguments, returns `nullptr`.  `ErrorResult` is set to an error only
64    * when this returns `nullptr`.  The error code indicates the reason why
65    * it couldn't initialize the instance.
66    */
Create(const AbstractRange * aAbstractRange,ErrorResult & aRv)67   static already_AddRefed<nsRange> Create(const AbstractRange* aAbstractRange,
68                                           ErrorResult& aRv) {
69     return nsRange::Create(aAbstractRange->StartRef(), aAbstractRange->EndRef(),
70                            aRv);
71   }
Create(nsINode * aStartContainer,uint32_t aStartOffset,nsINode * aEndContainer,uint32_t aEndOffset,ErrorResult & aRv)72   static already_AddRefed<nsRange> Create(nsINode* aStartContainer,
73                                           uint32_t aStartOffset,
74                                           nsINode* aEndContainer,
75                                           uint32_t aEndOffset,
76                                           ErrorResult& aRv) {
77     return nsRange::Create(RawRangeBoundary(aStartContainer, aStartOffset),
78                            RawRangeBoundary(aEndContainer, aEndOffset), aRv);
79   }
80   template <typename SPT, typename SRT, typename EPT, typename ERT>
81   static already_AddRefed<nsRange> Create(
82       const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
83       const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
84       ErrorResult& aRv);
85 
86   NS_DECL_ISUPPORTS_INHERITED
87   NS_IMETHODIMP_(void) DeleteCycleCollectable(void) override;
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsRange,AbstractRange)88   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsRange, AbstractRange)
89 
90   nsrefcnt GetRefCount() const { return mRefCnt; }
91 
GetRoot()92   nsINode* GetRoot() const { return mRoot; }
93 
94   /**
95    * Return true iff this range is part of a Selection object
96    * and isn't detached.
97    */
IsInSelection()98   bool IsInSelection() const { return !!mSelection; }
99 
100   MOZ_CAN_RUN_SCRIPT void RegisterSelection(
101       mozilla::dom::Selection& aSelection);
102 
103   void UnregisterSelection();
104 
105   /**
106    * Returns pointer to a Selection if the range is associated with a Selection.
107    */
108   mozilla::dom::Selection* GetSelection() const;
109 
110   /**
111    * Return true if this range was generated.
112    * @see SetIsGenerated
113    */
IsGenerated()114   bool IsGenerated() const { return mIsGenerated; }
115 
116   /**
117    * Mark this range as being generated or not.
118    * Currently it is used for marking ranges that are created when splitting up
119    * a range to exclude a -moz-user-select:none region.
120    * @see Selection::AddRangesForSelectableNodes
121    * @see ExcludeNonSelectableNodes
122    */
SetIsGenerated(bool aIsGenerated)123   void SetIsGenerated(bool aIsGenerated) { mIsGenerated = aIsGenerated; }
124 
125   void Reset();
126 
127   /**
128    * SetStart() and SetEnd() sets start point or end point separately.
129    * However, this is expensive especially when it's a range of Selection.
130    * When you set both start and end of a range, you should use
131    * SetStartAndEnd() instead.
132    */
SetStart(nsINode * aContainer,uint32_t aOffset)133   nsresult SetStart(nsINode* aContainer, uint32_t aOffset) {
134     ErrorResult error;
135     SetStart(RawRangeBoundary(aContainer, aOffset), error);
136     return error.StealNSResult();
137   }
SetEnd(nsINode * aContainer,uint32_t aOffset)138   nsresult SetEnd(nsINode* aContainer, uint32_t aOffset) {
139     ErrorResult error;
140     SetEnd(RawRangeBoundary(aContainer, aOffset), error);
141     return error.StealNSResult();
142   }
143 
144   already_AddRefed<nsRange> CloneRange() const;
145 
146   /**
147    * SetStartAndEnd() works similar to call both SetStart() and SetEnd().
148    * Different from calls them separately, this does nothing if either
149    * the start point or the end point is invalid point.
150    * If the specified start point is after the end point, the range will be
151    * collapsed at the end point.  Similarly, if they are in different root,
152    * the range will be collapsed at the end point.
153    */
SetStartAndEnd(nsINode * aStartContainer,uint32_t aStartOffset,nsINode * aEndContainer,uint32_t aEndOffset)154   nsresult SetStartAndEnd(nsINode* aStartContainer, uint32_t aStartOffset,
155                           nsINode* aEndContainer, uint32_t aEndOffset) {
156     return SetStartAndEnd(RawRangeBoundary(aStartContainer, aStartOffset),
157                           RawRangeBoundary(aEndContainer, aEndOffset));
158   }
159   template <typename SPT, typename SRT, typename EPT, typename ERT>
SetStartAndEnd(const mozilla::RangeBoundaryBase<SPT,SRT> & aStartBoundary,const mozilla::RangeBoundaryBase<EPT,ERT> & aEndBoundary)160   nsresult SetStartAndEnd(
161       const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
162       const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
163     return AbstractRange::SetStartAndEndInternal(aStartBoundary, aEndBoundary,
164                                                  this);
165   }
166 
167   /**
168    * Adds all nodes between |aStartContent| and |aEndContent| to the range.
169    * The start offset will be set before |aStartContent|,
170    * while the end offset will be set immediately after |aEndContent|.
171    *
172    * Caller must guarantee both nodes are non null and
173    * children of |aContainer| and that |aEndContent| is after |aStartContent|.
174    */
175   void SelectNodesInContainer(nsINode* aContainer, nsIContent* aStartContent,
176                               nsIContent* aEndContent);
177 
178   /**
179    * CollapseTo() works similar to call both SetStart() and SetEnd() with
180    * same node and offset.  This just calls SetStartAndParent() to set
181    * collapsed range at aContainer and aOffset.
182    */
CollapseTo(nsINode * aContainer,uint32_t aOffset)183   nsresult CollapseTo(nsINode* aContainer, uint32_t aOffset) {
184     return CollapseTo(RawRangeBoundary(aContainer, aOffset));
185   }
CollapseTo(const RawRangeBoundary & aPoint)186   nsresult CollapseTo(const RawRangeBoundary& aPoint) {
187     return SetStartAndEnd(aPoint, aPoint);
188   }
189 
190   // aMaxRanges is the maximum number of text ranges to record for each face
191   // (pass 0 to just get the list of faces, without recording exact ranges
192   // where each face was used).
193   nsresult GetUsedFontFaces(
194       nsTArray<mozilla::UniquePtr<mozilla::dom::InspectorFontFace>>& aResult,
195       uint32_t aMaxRanges, bool aSkipCollapsedWhitespace);
196 
197   // nsIMutationObserver methods
198   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
199   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
200   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
201   NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
202   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
203 
204   // WebIDL
205   static already_AddRefed<nsRange> Constructor(
206       const mozilla::dom::GlobalObject& global, mozilla::ErrorResult& aRv);
207 
208   already_AddRefed<mozilla::dom::DocumentFragment> CreateContextualFragment(
209       const nsAString& aString, ErrorResult& aError) const;
210   already_AddRefed<mozilla::dom::DocumentFragment> CloneContents(
211       ErrorResult& aErr);
212   int16_t CompareBoundaryPoints(uint16_t aHow, const nsRange& aOtherRange,
213                                 ErrorResult& aRv);
214   int16_t ComparePoint(const nsINode& aContainer, uint32_t aOffset,
215                        ErrorResult& aRv) const;
216   void DeleteContents(ErrorResult& aRv);
217   already_AddRefed<mozilla::dom::DocumentFragment> ExtractContents(
218       ErrorResult& aErr);
GetCommonAncestorContainer(ErrorResult & aRv)219   nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const {
220     if (!mIsPositioned) {
221       aRv.Throw(NS_ERROR_NOT_INITIALIZED);
222       return nullptr;
223     }
224     return GetClosestCommonInclusiveAncestor();
225   }
226   void InsertNode(nsINode& aNode, ErrorResult& aErr);
227   bool IntersectsNode(nsINode& aNode, ErrorResult& aRv);
228   bool IsPointInRange(const nsINode& aContainer, uint32_t aOffset,
229                       ErrorResult& aRv) const;
230   void ToString(nsAString& aReturn, ErrorResult& aErr);
231   void Detach();
232 
233   // *JS() methods are mapped to Range.*() of DOM.
234   // They may move focus only when the range represents normal selection.
235   // These methods shouldn't be used from internal.
236   void CollapseJS(bool aToStart);
237   void SelectNodeJS(nsINode& aNode, ErrorResult& aErr);
238   void SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr);
239   void SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
240   void SetEndAfterJS(nsINode& aNode, ErrorResult& aErr);
241   void SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr);
242   void SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
243   void SetStartAfterJS(nsINode& aNode, ErrorResult& aErr);
244   void SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr);
245 
246   void SurroundContents(nsINode& aNode, ErrorResult& aErr);
247   already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true,
248                                                   bool aFlushLayout = true);
249   already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true,
250                                                bool aFlushLayout = true);
251   void GetClientRectsAndTexts(mozilla::dom::ClientRectsAndTexts& aResult,
252                               ErrorResult& aErr);
253 
254   // Following methods should be used for internal use instead of *JS().
255   void SelectNode(nsINode& aNode, ErrorResult& aErr);
256   void SelectNodeContents(nsINode& aNode, ErrorResult& aErr);
257   void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
258   void SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aErr);
259   void SetEndAfter(nsINode& aNode, ErrorResult& aErr);
260   void SetEndBefore(nsINode& aNode, ErrorResult& aErr);
261   void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
262   void SetStart(const RawRangeBoundary& aPoint, ErrorResult& aErr);
263   void SetStartAfter(nsINode& aNode, ErrorResult& aErr);
264   void SetStartBefore(nsINode& aNode, ErrorResult& aErr);
265   void Collapse(bool aToStart);
266 
267   static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue,
268                                   mozilla::ErrorResult& aError,
269                                   nsIContent* aContainer);
270 
271   virtual JSObject* WrapObject(JSContext* cx,
272                                JS::Handle<JSObject*> aGivenProto) final;
273   DocGroup* GetDocGroup() const;
274 
275  private:
276   // no copy's or assigns
277   nsRange(const nsRange&);
278   nsRange& operator=(const nsRange&);
279 
280   template <typename SPT, typename SRT, typename EPT, typename ERT>
281   static void AssertIfMismatchRootAndRangeBoundaries(
282       const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
283       const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
284       const nsINode* aRootNode, bool aNotInsertedYet = false);
285 
286   /**
287    * Cut or delete the range's contents.
288    *
289    * @param aFragment DocumentFragment containing the nodes.
290    *                  May be null to indicate the caller doesn't want a
291    *                  fragment.
292    * @param aRv The error if any.
293    */
294   void CutContents(mozilla::dom::DocumentFragment** aFragment,
295                    ErrorResult& aRv);
296 
297   static nsresult CloneParentsBetween(nsINode* aAncestor, nsINode* aNode,
298                                       nsINode** aClosestAncestor,
299                                       nsINode** aFarthestAncestor);
300 
301   /**
302    * Returns whether a node is safe to be accessed by the current caller.
303    */
304   bool CanAccess(const nsINode&) const;
305 
306   void AdjustNextRefsOnCharacterDataSplit(const nsIContent& aContent,
307                                           const CharacterDataChangeInfo& aInfo);
308 
309   struct RangeBoundariesAndRoot {
310     RawRangeBoundary mStart;
311     RawRangeBoundary mEnd;
312     nsINode* mRoot = nullptr;
313   };
314 
315   /**
316    * @param aContent Must be non-nullptr.
317    */
318   RangeBoundariesAndRoot DetermineNewRangeBoundariesAndRootOnCharacterDataMerge(
319       nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const;
320 
321   // @return true iff the range is positioned, aContainer belongs to the same
322   //         document as the range, aContainer is a DOCUMENT_TYPE_NODE and
323   //         aOffset doesn't exceed aContainer's length.
324   bool IsPointComparableToRange(const nsINode& aContainer, uint32_t aOffset,
325                                 ErrorResult& aErrorResult) const;
326 
327  public:
328   /**
329    * This helper function gets rects and correlated text for the given range.
330    * @param aTextList optional where nullptr = don't retrieve text
331    */
332   static void CollectClientRectsAndText(
333       mozilla::RectCallback* aCollector,
334       mozilla::dom::Sequence<nsString>* aTextList, nsRange* aRange,
335       nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer,
336       uint32_t aEndOffset, bool aClampToEdge, bool aFlushLayout);
337 
338   /**
339    * Scan this range for -moz-user-select:none nodes and split it up into
340    * multiple ranges to exclude those nodes.  The resulting ranges are put
341    * in aOutRanges.  If no -moz-user-select:none node is found in the range
342    * then |this| is unmodified and is the only range in aOutRanges.
343    * Otherwise, |this| will be modified so that it ends before the first
344    * -moz-user-select:none node and additional ranges may also be created.
345    * If all nodes in the range are -moz-user-select:none then aOutRanges
346    * will be empty.
347    * @param aOutRanges the resulting set of ranges
348    */
349   void ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges);
350 
351   /**
352    * Notify the selection listeners after a range has been modified.
353    */
354   MOZ_CAN_RUN_SCRIPT void NotifySelectionListenersAfterRangeSet();
355 
356  protected:
357   /**
358    * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
359    */
360   void RegisterClosestCommonInclusiveAncestor(nsINode* aNode);
361   /**
362    * https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
363    */
364   void UnregisterClosestCommonInclusiveAncestor(nsINode* aNode,
365                                                 bool aIsUnlinking);
366 
367   /**
368    * DoSetRange() is called when `AbstractRange::SetStartAndEndInternal()` sets
369    * mStart and mEnd, or some other internal methods modify `mStart` and/or
370    * `mEnd`.  Therefore, this shouldn't be a virtual method.
371    *
372    * @param aStartBoundary      Computed start point.  This must equals or be
373    *                            before aEndBoundary in the DOM tree order.
374    * @param aEndBoundary        Computed end point.
375    * @param aRootNode           The root node.
376    * @param aNotInsertedYet     true if this is called by CharacterDataChanged()
377    *                            to disable assertion and suppress re-registering
378    *                            a range common ancestor node since the new text
379    *                            node of a splitText hasn't been inserted yet.
380    *                            CharacterDataChanged() does the re-registering
381    *                            when needed.  Otherwise, false.
382    */
383   template <typename SPT, typename SRT, typename EPT, typename ERT>
384   MOZ_CAN_RUN_SCRIPT_BOUNDARY void DoSetRange(
385       const mozilla::RangeBoundaryBase<SPT, SRT>& aStartBoundary,
386       const mozilla::RangeBoundaryBase<EPT, ERT>& aEndBoundary,
387       nsINode* aRootNode, bool aNotInsertedYet = false);
388 
389   /**
390    * For a range for which IsInSelection() is true, return the closest common
391    * inclusive ancestor
392    * (https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor)
393    * for the range, which we had to compute when the common ancestor changed or
394    * IsInSelection became true, so we could register with it. That is, it's a
395    * faster version of GetClosestCommonInclusiveAncestor that only works for
396    * ranges in a Selection. The method will assert and the behavior is undefined
397    * if called on a range where IsInSelection() is false.
398    */
399   nsINode* GetRegisteredClosestCommonInclusiveAncestor();
400 
401   // Assume that this is guaranteed that this is held by the caller when
402   // this is used.  (Note that we cannot use AutoRestore for mCalledByJS
403   // due to a bit field.)
404   class MOZ_RAII AutoCalledByJSRestore final {
405    private:
406     nsRange& mRange;
407     bool mOldValue;
408 
409    public:
AutoCalledByJSRestore(nsRange & aRange)410     explicit AutoCalledByJSRestore(nsRange& aRange)
411         : mRange(aRange), mOldValue(aRange.mCalledByJS) {}
~AutoCalledByJSRestore()412     ~AutoCalledByJSRestore() { mRange.mCalledByJS = mOldValue; }
SavedValue()413     bool SavedValue() const { return mOldValue; }
414   };
415 
416   struct MOZ_STACK_CLASS AutoInvalidateSelection {
AutoInvalidateSelectionAutoInvalidateSelection417     explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) {
418       if (!mRange->IsInSelection() || sIsNested) {
419         return;
420       }
421       sIsNested = true;
422       mCommonAncestor = mRange->GetRegisteredClosestCommonInclusiveAncestor();
423     }
424     ~AutoInvalidateSelection();
425     nsRange* mRange;
426     RefPtr<nsINode> mCommonAncestor;
427     static bool sIsNested;
428   };
429 
430   bool MaybeInterruptLastRelease();
431 
432 #ifdef DEBUG
IsCleared()433   bool IsCleared() const {
434     return !mRoot && !mRegisteredClosestCommonInclusiveAncestor &&
435            !mSelection && !mNextStartRef && !mNextEndRef;
436   }
437 #endif  // #ifdef DEBUG
438 
439   nsCOMPtr<nsINode> mRoot;
440   // mRegisteredClosestCommonInclusiveAncestor is only non-null when the range
441   // IsInSelection().  It's kept alive via mStart/mEnd,
442   // because we update it any time those could become disconnected from it.
443   nsINode* MOZ_NON_OWNING_REF mRegisteredClosestCommonInclusiveAncestor;
444   mozilla::WeakPtr<mozilla::dom::Selection> mSelection;
445 
446   // These raw pointers are used to remember a child that is about
447   // to be inserted between a CharacterData call and a subsequent
448   // ContentInserted or ContentAppended call. It is safe to store
449   // these refs because the caller is guaranteed to trigger both
450   // notifications while holding a strong reference to the new child.
451   nsIContent* MOZ_NON_OWNING_REF mNextStartRef;
452   nsIContent* MOZ_NON_OWNING_REF mNextEndRef;
453 
454   static nsTArray<RefPtr<nsRange>>* sCachedRanges;
455 
456   friend class mozilla::dom::AbstractRange;
457 };
458 
459 #endif /* nsRange_h___ */
460