Line data Source code
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 nsIDOMRange object.
9 : */
10 :
11 : #ifndef nsRange_h___
12 : #define nsRange_h___
13 :
14 : #include "nsIDOMRange.h"
15 : #include "nsCOMPtr.h"
16 : #include "nsINode.h"
17 : #include "nsIDocument.h"
18 : #include "nsIDOMNode.h"
19 : #include "nsLayoutUtils.h"
20 : #include "prmon.h"
21 : #include "nsStubMutationObserver.h"
22 : #include "nsWrapperCache.h"
23 : #include "mozilla/Attributes.h"
24 : #include "mozilla/GuardObjects.h"
25 :
26 : namespace mozilla {
27 : class ErrorResult;
28 : namespace dom {
29 : struct ClientRectsAndTexts;
30 : class DocumentFragment;
31 : class DOMRect;
32 : class DOMRectList;
33 : class Selection;
34 : } // namespace dom
35 : } // namespace mozilla
36 :
37 : class nsRange final : public nsIDOMRange,
38 : public nsStubMutationObserver,
39 : public nsWrapperCache
40 : {
41 : typedef mozilla::ErrorResult ErrorResult;
42 : typedef mozilla::dom::DOMRect DOMRect;
43 : typedef mozilla::dom::DOMRectList DOMRectList;
44 :
45 : virtual ~nsRange();
46 :
47 : public:
48 : explicit nsRange(nsINode* aNode);
49 :
50 : static nsresult CreateRange(nsIDOMNode* aStartContainer, int32_t aStartOffset,
51 : nsIDOMNode* aEndContainer, int32_t aEndOffset,
52 : nsRange** aRange);
53 : static nsresult CreateRange(nsIDOMNode* aStartContainer, int32_t aStartOffset,
54 : nsIDOMNode* aEndContainer, int32_t aEndOffset,
55 : nsIDOMRange** aRange);
56 : static nsresult CreateRange(nsINode* aStartContainer, int32_t aStartOffset,
57 : nsINode* aEndContainer, int32_t aEndOffset,
58 : nsRange** aRange);
59 :
60 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
61 257 : NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsRange, nsIDOMRange)
62 :
63 4 : nsrefcnt GetRefCount() const
64 : {
65 4 : return mRefCnt;
66 : }
67 :
68 : // nsIDOMRange interface
69 : NS_DECL_NSIDOMRANGE
70 :
71 10 : nsINode* GetRoot() const
72 : {
73 10 : return mRoot;
74 : }
75 :
76 58 : nsINode* GetStartContainer() const
77 : {
78 58 : return mStartContainer;
79 : }
80 :
81 58 : nsINode* GetEndContainer() const
82 : {
83 58 : return mEndContainer;
84 : }
85 :
86 35 : int32_t StartOffset() const
87 : {
88 35 : return mStartOffset;
89 : }
90 :
91 35 : int32_t EndOffset() const
92 : {
93 35 : return mEndOffset;
94 : }
95 :
96 66 : bool IsPositioned() const
97 : {
98 66 : return mIsPositioned;
99 : }
100 :
101 0 : void SetMaySpanAnonymousSubtrees(bool aMaySpanAnonymousSubtrees)
102 : {
103 0 : mMaySpanAnonymousSubtrees = aMaySpanAnonymousSubtrees;
104 0 : }
105 :
106 : /**
107 : * Return true iff this range is part of a Selection object
108 : * and isn't detached.
109 : */
110 76 : bool IsInSelection() const
111 : {
112 76 : return !!mSelection;
113 : }
114 :
115 : /**
116 : * Called when the range is added/removed from a Selection.
117 : */
118 : void SetSelection(mozilla::dom::Selection* aSelection);
119 :
120 : /**
121 : * Return true if this range was generated.
122 : * @see SetIsGenerated
123 : */
124 0 : bool IsGenerated() const
125 : {
126 0 : return mIsGenerated;
127 : }
128 :
129 : /**
130 : * Mark this range as being generated or not.
131 : * Currently it is used for marking ranges that are created when splitting up
132 : * a range to exclude a -moz-user-select:none region.
133 : * @see Selection::AddItem
134 : * @see ExcludeNonSelectableNodes
135 : */
136 0 : void SetIsGenerated(bool aIsGenerated)
137 : {
138 0 : mIsGenerated = aIsGenerated;
139 0 : }
140 :
141 : nsINode* GetCommonAncestor() const;
142 : void Reset();
143 :
144 : /**
145 : * SetStart() and SetEnd() sets start point or end point separately.
146 : * However, this is expensive especially when it's a range of Selection.
147 : * When you set both start and end of a range, you should use
148 : * SetStartAndEnd() instead.
149 : */
150 : nsresult SetStart(nsINode* aContainer, int32_t aOffset);
151 : nsresult SetEnd(nsINode* aContainer, int32_t aOffset);
152 :
153 : already_AddRefed<nsRange> CloneRange() const;
154 :
155 : /**
156 : * SetStartAndEnd() works similar to call both SetStart() and SetEnd().
157 : * Different from calls them separately, this does nothing if either
158 : * the start point or the end point is invalid point.
159 : * If the specified start point is after the end point, the range will be
160 : * collapsed at the end point. Similarly, if they are in different root,
161 : * the range will be collapsed at the end point.
162 : */
163 : nsresult SetStartAndEnd(nsINode* aStartContainer, int32_t aStartOffset,
164 : nsINode* aEndContainer, int32_t aEndOffset);
165 :
166 : /**
167 : * CollapseTo() works similar to call both SetStart() and SetEnd() with
168 : * same node and offset. This just calls SetStartAndParent() to set
169 : * collapsed range at aContainer and aOffset.
170 : */
171 9 : nsresult CollapseTo(nsINode* aContainer, int32_t aOffset)
172 : {
173 9 : return SetStartAndEnd(aContainer, aOffset, aContainer, aOffset);
174 : }
175 :
176 : /**
177 : * Retrieves node and offset for setting start or end of a range to
178 : * before or after aNode.
179 : */
180 0 : static nsINode* GetContainerAndOffsetAfter(nsINode* aNode, int32_t* aOffset)
181 : {
182 0 : MOZ_ASSERT(aNode);
183 0 : MOZ_ASSERT(aOffset);
184 0 : nsINode* parentNode = aNode->GetParentNode();
185 0 : *aOffset = parentNode ? parentNode->IndexOf(aNode) : -1;
186 0 : if (*aOffset >= 0) {
187 0 : (*aOffset)++;
188 : }
189 0 : return parentNode;
190 : }
191 0 : static nsINode* GetContainerAndOffsetBefore(nsINode* aNode, int32_t* aOffset)
192 : {
193 0 : MOZ_ASSERT(aNode);
194 0 : MOZ_ASSERT(aOffset);
195 0 : nsINode* parentNode = aNode->GetParentNode();
196 0 : *aOffset = parentNode ? parentNode->IndexOf(aNode) : -1;
197 0 : return parentNode;
198 : }
199 :
200 : NS_IMETHOD GetUsedFontFaces(nsIDOMFontFaceList** aResult);
201 :
202 : // nsIMutationObserver methods
203 : NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
204 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
205 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
206 : NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
207 : NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
208 :
209 : // WebIDL
210 : static already_AddRefed<nsRange>
211 : Constructor(const mozilla::dom::GlobalObject& global,
212 : mozilla::ErrorResult& aRv);
213 :
214 46 : bool Collapsed() const
215 : {
216 92 : return mIsPositioned && mStartContainer == mEndContainer &&
217 92 : mStartOffset == mEndOffset;
218 : }
219 : already_AddRefed<mozilla::dom::DocumentFragment>
220 : CreateContextualFragment(const nsAString& aString, ErrorResult& aError);
221 : already_AddRefed<mozilla::dom::DocumentFragment>
222 : CloneContents(ErrorResult& aErr);
223 : int16_t CompareBoundaryPoints(uint16_t aHow, nsRange& aOther,
224 : ErrorResult& aErr);
225 : int16_t ComparePoint(nsINode& aContainer, uint32_t aOffset,
226 : ErrorResult& aErr);
227 : void DeleteContents(ErrorResult& aRv);
228 : already_AddRefed<mozilla::dom::DocumentFragment>
229 : ExtractContents(ErrorResult& aErr);
230 : nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const;
231 : nsINode* GetStartContainer(ErrorResult& aRv) const;
232 : uint32_t GetStartOffset(ErrorResult& aRv) const;
233 : nsINode* GetEndContainer(ErrorResult& aRv) const;
234 : uint32_t GetEndOffset(ErrorResult& aRv) const;
235 : void InsertNode(nsINode& aNode, ErrorResult& aErr);
236 : bool IntersectsNode(nsINode& aNode, ErrorResult& aRv);
237 : bool IsPointInRange(nsINode& aContainer, uint32_t aOffset, ErrorResult& aErr);
238 :
239 : // *JS() methods are mapped to Range.*() of DOM.
240 : // They may move focus only when the range represents normal selection.
241 : // These methods shouldn't be used from internal.
242 : void CollapseJS(bool aToStart);
243 : void SelectNodeJS(nsINode& aNode, ErrorResult& aErr);
244 : void SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr);
245 : void SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
246 : void SetEndAfterJS(nsINode& aNode, ErrorResult& aErr);
247 : void SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr);
248 : void SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
249 : void SetStartAfterJS(nsINode& aNode, ErrorResult& aErr);
250 : void SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr);
251 :
252 : void SurroundContents(nsINode& aNode, ErrorResult& aErr);
253 : already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true,
254 : bool aFlushLayout = true);
255 : already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true,
256 : bool aFlushLayout = true);
257 : void GetClientRectsAndTexts(
258 : mozilla::dom::ClientRectsAndTexts& aResult,
259 : ErrorResult& aErr);
260 :
261 : // Following methods should be used for internal use instead of *JS().
262 : void SelectNode(nsINode& aNode, ErrorResult& aErr);
263 : void SelectNodeContents(nsINode& aNode, ErrorResult& aErr);
264 : void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
265 : void SetEndAfter(nsINode& aNode, ErrorResult& aErr);
266 : void SetEndBefore(nsINode& aNode, ErrorResult& aErr);
267 : void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
268 : void SetStartAfter(nsINode& aNode, ErrorResult& aErr);
269 : void SetStartBefore(nsINode& aNode, ErrorResult& aErr);
270 :
271 : static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue,
272 : mozilla::ErrorResult& aError,
273 : nsIContent* aStartContainer,
274 : uint32_t aStartOffset,
275 : nsIContent* aEndContainer,
276 : uint32_t aEndOffset);
277 :
278 2 : nsINode* GetParentObject() const { return mOwner; }
279 : virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override final;
280 :
281 : private:
282 : // no copy's or assigns
283 : nsRange(const nsRange&);
284 : nsRange& operator=(const nsRange&);
285 :
286 : /**
287 : * Cut or delete the range's contents.
288 : *
289 : * @param aFragment nsIDOMDocumentFragment containing the nodes.
290 : * May be null to indicate the caller doesn't want a fragment.
291 : */
292 : nsresult CutContents(mozilla::dom::DocumentFragment** frag);
293 :
294 : static nsresult CloneParentsBetween(nsINode* aAncestor,
295 : nsINode* aNode,
296 : nsINode** aClosestAncestor,
297 : nsINode** aFarthestAncestor);
298 :
299 : public:
300 : /******************************************************************************
301 : * Utility routine to detect if a content node starts before a range and/or
302 : * ends after a range. If neither it is contained inside the range.
303 : *
304 : * XXX - callers responsibility to ensure node in same doc as range!
305 : *
306 : *****************************************************************************/
307 : static nsresult CompareNodeToRange(nsINode* aNode, nsRange* aRange,
308 : bool *outNodeBefore,
309 : bool *outNodeAfter);
310 :
311 : /**
312 : * Return true if any part of (aNode, aStartOffset) .. (aNode, aEndOffset)
313 : * overlaps any nsRange in aNode's GetNextRangeCommonAncestor ranges (i.e.
314 : * where aNode is a descendant of a range's common ancestor node).
315 : * If a nsRange starts in (aNode, aEndOffset) or if it ends in
316 : * (aNode, aStartOffset) then it is non-overlapping and the result is false
317 : * for that nsRange. Collapsed ranges always counts as non-overlapping.
318 : */
319 : static bool IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
320 : uint32_t aEndOffset);
321 :
322 : /**
323 : * This helper function gets rects and correlated text for the given range.
324 : * @param aTextList optional where nullptr = don't retrieve text
325 : */
326 : static void CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
327 : mozilla::dom::Sequence<nsString>* aTextList,
328 : nsRange* aRange,
329 : nsINode* aStartContainer,
330 : int32_t aStartOffset,
331 : nsINode* aEndContainer,
332 : int32_t aEndOffset,
333 : bool aClampToEdge, bool aFlushLayout);
334 :
335 : /**
336 : * Scan this range for -moz-user-select:none nodes and split it up into
337 : * multiple ranges to exclude those nodes. The resulting ranges are put
338 : * in aOutRanges. If no -moz-user-select:none node is found in the range
339 : * then |this| is unmodified and is the only range in aOutRanges.
340 : * Otherwise, |this| will be modified so that it ends before the first
341 : * -moz-user-select:none node and additional ranges may also be created.
342 : * If all nodes in the range are -moz-user-select:none then aOutRanges
343 : * will be empty.
344 : * @param aOutRanges the resulting set of ranges
345 : */
346 : void ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges);
347 :
348 : typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable;
349 : protected:
350 : void RegisterCommonAncestor(nsINode* aNode);
351 : void UnregisterCommonAncestor(nsINode* aNode);
352 : nsINode* IsValidBoundary(nsINode* aNode);
353 : static bool IsValidOffset(nsINode* aNode, int32_t aOffset);
354 :
355 : // CharacterDataChanged set aNotInsertedYet to true to disable an assertion
356 : // and suppress re-registering a range common ancestor node since
357 : // the new text node of a splitText hasn't been inserted yet.
358 : // CharacterDataChanged does the re-registering when needed.
359 : void DoSetRange(nsINode* aStartN, int32_t aStartOffset,
360 : nsINode* aEndN, int32_t aEndOffset,
361 : nsINode* aRoot, bool aNotInsertedYet = false);
362 :
363 : /**
364 : * For a range for which IsInSelection() is true, return the common
365 : * ancestor for the range. This method uses the selection bits and
366 : * nsGkAtoms::range property on the nodes to quickly find the ancestor.
367 : * That is, it's a faster version of GetCommonAncestor that only works
368 : * for ranges in a Selection. The method will assert and the behavior
369 : * is undefined if called on a range where IsInSelection() is false.
370 : */
371 : nsINode* GetRegisteredCommonAncestor();
372 :
373 : // Helper to IsNodeSelected.
374 : static bool IsNodeInSortedRanges(nsINode* aNode,
375 : uint32_t aStartOffset,
376 : uint32_t aEndOffset,
377 : const nsTArray<const nsRange*>& aRanges,
378 : size_t aRangeStart,
379 : size_t aRangeEnd);
380 :
381 : // Assume that this is guaranteed that this is held by the caller when
382 : // this is used. (Note that we cannot use AutoRestore for mCalledByJS
383 : // due to a bit field.)
384 : class MOZ_RAII AutoCalledByJSRestore final
385 : {
386 : private:
387 : nsRange& mRange;
388 : bool mOldValue;
389 : MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
390 :
391 : public:
392 4 : explicit AutoCalledByJSRestore(nsRange& aRange
393 : MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
394 4 : : mRange(aRange)
395 4 : , mOldValue(aRange.mCalledByJS)
396 : {
397 4 : MOZ_GUARD_OBJECT_NOTIFIER_INIT;
398 4 : }
399 4 : ~AutoCalledByJSRestore()
400 4 : {
401 4 : mRange.mCalledByJS = mOldValue;
402 4 : }
403 0 : bool SavedValue() const { return mOldValue; }
404 : };
405 :
406 : struct MOZ_STACK_CLASS AutoInvalidateSelection
407 : {
408 6 : explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange)
409 : {
410 : #ifdef DEBUG
411 6 : mWasInSelection = mRange->IsInSelection();
412 : #endif
413 6 : if (!mRange->IsInSelection() || mIsNested) {
414 6 : return;
415 : }
416 0 : mIsNested = true;
417 0 : mCommonAncestor = mRange->GetRegisteredCommonAncestor();
418 : }
419 : ~AutoInvalidateSelection();
420 : nsRange* mRange;
421 : RefPtr<nsINode> mCommonAncestor;
422 : #ifdef DEBUG
423 : bool mWasInSelection;
424 : #endif
425 : static bool mIsNested;
426 : };
427 :
428 : nsCOMPtr<nsIDocument> mOwner;
429 : nsCOMPtr<nsINode> mRoot;
430 : nsCOMPtr<nsINode> mStartContainer;
431 : nsCOMPtr<nsINode> mEndContainer;
432 : RefPtr<mozilla::dom::Selection> mSelection;
433 : int32_t mStartOffset;
434 : int32_t mEndOffset;
435 :
436 : bool mIsPositioned : 1;
437 : bool mMaySpanAnonymousSubtrees : 1;
438 : bool mIsGenerated : 1;
439 : bool mStartOffsetWasIncremented : 1;
440 : bool mEndOffsetWasIncremented : 1;
441 : bool mCalledByJS : 1;
442 : #ifdef DEBUG
443 : int32_t mAssertNextInsertOrAppendIndex;
444 : nsINode* mAssertNextInsertOrAppendNode;
445 : #endif
446 : };
447 :
448 : inline nsISupports*
449 : ToCanonicalSupports(nsRange* aRange)
450 : {
451 : return static_cast<nsIDOMRange*>(aRange);
452 : }
453 :
454 : inline nsISupports*
455 2 : ToSupports(nsRange* aRange)
456 : {
457 2 : return static_cast<nsIDOMRange*>(aRange);
458 : }
459 :
460 : #endif /* nsRange_h___ */
|