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 #include "mozilla/LinkedList.h"
26 #include "mozilla/RangeBoundary.h"
27
28 namespace mozilla {
29 class ErrorResult;
30 namespace dom {
31 struct ClientRectsAndTexts;
32 class DocGroup;
33 class DocumentFragment;
34 class DOMRect;
35 class DOMRectList;
36 class InspectorFontFace;
37 class Selection;
38 } // namespace dom
39 } // namespace mozilla
40
41 class nsRange final : public nsIDOMRange,
42 public nsStubMutationObserver,
43 public nsWrapperCache,
44 // For linking together selection-associated ranges.
45 public mozilla::LinkedListElement<nsRange> {
46 typedef mozilla::ErrorResult ErrorResult;
47 typedef mozilla::dom::DocGroup DocGroup;
48 typedef mozilla::dom::DOMRect DOMRect;
49 typedef mozilla::dom::DOMRectList DOMRectList;
50 typedef mozilla::RangeBoundary RangeBoundary;
51 typedef mozilla::RawRangeBoundary RawRangeBoundary;
52
53 virtual ~nsRange();
54
55 public:
56 explicit nsRange(nsINode* aNode);
57
58 static nsresult CreateRange(nsIDOMNode* aStartContainer,
59 uint32_t aStartOffset, nsIDOMNode* aEndContainer,
60 uint32_t aEndOffset, nsRange** aRange);
61 static nsresult CreateRange(nsIDOMNode* aStartContainer,
62 uint32_t aStartOffset, nsIDOMNode* aEndContainer,
63 uint32_t aEndOffset, nsIDOMRange** aRange);
64 static nsresult CreateRange(nsINode* aStartContainer, uint32_t aStartOffset,
65 nsINode* aEndContainer, uint32_t aEndOffset,
66 nsRange** aRange);
67 static nsresult CreateRange(const RawRangeBoundary& aStart,
68 const RawRangeBoundary& aEnd, nsRange** aRange);
69
70 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsRange,nsIDOMRange)71 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsRange, nsIDOMRange)
72
73 nsrefcnt GetRefCount() const { return mRefCnt; }
74
75 // nsIDOMRange interface
76 NS_DECL_NSIDOMRANGE
77
GetRoot()78 nsINode* GetRoot() const { return mRoot; }
79
StartRef()80 const RangeBoundary& StartRef() const { return mStart; }
81
GetStartContainer()82 nsINode* GetStartContainer() const { return mStart.Container(); }
83
EndRef()84 const RangeBoundary& EndRef() const { return mEnd; }
85
GetEndContainer()86 nsINode* GetEndContainer() const { return mEnd.Container(); }
87
StartOffset()88 uint32_t StartOffset() const {
89 return static_cast<uint32_t>(mStart.Offset());
90 }
91
EndOffset()92 uint32_t EndOffset() const { return static_cast<uint32_t>(mEnd.Offset()); }
93
GetChildAtStartOffset()94 nsIContent* GetChildAtStartOffset() const {
95 return mStart.GetChildAtOffset();
96 }
97
GetChildAtEndOffset()98 nsIContent* GetChildAtEndOffset() const { return mEnd.GetChildAtOffset(); }
99
IsPositioned()100 bool IsPositioned() const { return mIsPositioned; }
101
SetMaySpanAnonymousSubtrees(bool aMaySpanAnonymousSubtrees)102 void SetMaySpanAnonymousSubtrees(bool aMaySpanAnonymousSubtrees) {
103 mMaySpanAnonymousSubtrees = aMaySpanAnonymousSubtrees;
104 }
105
106 /**
107 * Return true iff this range is part of a Selection object
108 * and isn't detached.
109 */
IsInSelection()110 bool IsInSelection() const { return !!mSelection; }
111
112 /**
113 * Called when the range is added/removed from a Selection.
114 */
115 void SetSelection(mozilla::dom::Selection* aSelection);
116
117 /**
118 * Return true if this range was generated.
119 * @see SetIsGenerated
120 */
IsGenerated()121 bool IsGenerated() const { return mIsGenerated; }
122
123 /**
124 * Mark this range as being generated or not.
125 * Currently it is used for marking ranges that are created when splitting up
126 * a range to exclude a -moz-user-select:none region.
127 * @see Selection::AddItem
128 * @see ExcludeNonSelectableNodes
129 */
SetIsGenerated(bool aIsGenerated)130 void SetIsGenerated(bool aIsGenerated) { mIsGenerated = aIsGenerated; }
131
132 nsINode* GetCommonAncestor() const;
133 void Reset();
134
135 /**
136 * SetStart() and SetEnd() sets start point or end point separately.
137 * However, this is expensive especially when it's a range of Selection.
138 * When you set both start and end of a range, you should use
139 * SetStartAndEnd() instead.
140 */
SetStart(nsINode * aContainer,uint32_t aOffset)141 nsresult SetStart(nsINode* aContainer, uint32_t aOffset) {
142 ErrorResult error;
143 SetStart(RawRangeBoundary(aContainer, aOffset), error);
144 return error.StealNSResult();
145 }
SetEnd(nsINode * aContainer,uint32_t aOffset)146 nsresult SetEnd(nsINode* aContainer, uint32_t aOffset) {
147 ErrorResult error;
148 SetEnd(RawRangeBoundary(aContainer, aOffset), error);
149 return error.StealNSResult();
150 }
151
152 already_AddRefed<nsRange> CloneRange() const;
153
154 /**
155 * SetStartAndEnd() works similar to call both SetStart() and SetEnd().
156 * Different from calls them separately, this does nothing if either
157 * the start point or the end point is invalid point.
158 * If the specified start point is after the end point, the range will be
159 * collapsed at the end point. Similarly, if they are in different root,
160 * the range will be collapsed at the end point.
161 */
SetStartAndEnd(nsINode * aStartContainer,uint32_t aStartOffset,nsINode * aEndContainer,uint32_t aEndOffset)162 nsresult SetStartAndEnd(nsINode* aStartContainer, uint32_t aStartOffset,
163 nsINode* aEndContainer, uint32_t aEndOffset) {
164 return SetStartAndEnd(RawRangeBoundary(aStartContainer, aStartOffset),
165 RawRangeBoundary(aEndContainer, aEndOffset));
166 }
167 nsresult SetStartAndEnd(const RawRangeBoundary& aStart,
168 const RawRangeBoundary& aEnd);
169
170 /**
171 * Adds all nodes between |aStartContent| and |aEndContent| to the range.
172 * The start offset will be set before |aStartContent|,
173 * while the end offset will be set immediately after |aEndContent|.
174 *
175 * Caller must guarantee both nodes are non null and
176 * children of |aContainer| and that |aEndContent| is after |aStartContent|.
177 */
178 void SelectNodesInContainer(nsINode* aContainer, nsIContent* aStartContent,
179 nsIContent* aEndContent);
180
181 /**
182 * CollapseTo() works similar to call both SetStart() and SetEnd() with
183 * same node and offset. This just calls SetStartAndParent() to set
184 * collapsed range at aContainer and aOffset.
185 */
CollapseTo(nsINode * aContainer,uint32_t aOffset)186 nsresult CollapseTo(nsINode* aContainer, uint32_t aOffset) {
187 return CollapseTo(RawRangeBoundary(aContainer, aOffset));
188 }
CollapseTo(const RawRangeBoundary & aPoint)189 nsresult CollapseTo(const RawRangeBoundary& aPoint) {
190 return SetStartAndEnd(aPoint, aPoint);
191 }
192
193 /**
194 * Retrieves node and offset for setting start or end of a range to
195 * before or after aNode.
196 */
GetContainerAndOffsetAfter(nsINode * aNode,uint32_t * aOffset)197 static nsINode* GetContainerAndOffsetAfter(nsINode* aNode,
198 uint32_t* aOffset) {
199 MOZ_ASSERT(aNode);
200 MOZ_ASSERT(aOffset);
201 *aOffset = 0;
202 nsINode* parentNode = aNode->GetParentNode();
203 if (!parentNode) {
204 return nullptr;
205 }
206 int32_t indexInParent = parentNode->ComputeIndexOf(aNode);
207 if (NS_WARN_IF(indexInParent < 0)) {
208 return nullptr;
209 }
210 *aOffset = static_cast<uint32_t>(indexInParent) + 1;
211 return parentNode;
212 }
GetContainerAndOffsetBefore(nsINode * aNode,uint32_t * aOffset)213 static nsINode* GetContainerAndOffsetBefore(nsINode* aNode,
214 uint32_t* aOffset) {
215 MOZ_ASSERT(aNode);
216 MOZ_ASSERT(aOffset);
217 *aOffset = 0;
218 nsINode* parentNode = aNode->GetParentNode();
219 if (!parentNode) {
220 return nullptr;
221 }
222 int32_t indexInParent = parentNode->ComputeIndexOf(aNode);
223 if (NS_WARN_IF(indexInParent < 0)) {
224 return nullptr;
225 }
226 *aOffset = static_cast<uint32_t>(indexInParent);
227 return parentNode;
228 }
229
230 // aMaxRanges is the maximum number of text ranges to record for each face
231 // (pass 0 to just get the list of faces, without recording exact ranges
232 // where each face was used).
233 nsresult GetUsedFontFaces(
234 nsTArray<nsAutoPtr<mozilla::dom::InspectorFontFace>>& aResult,
235 uint32_t aMaxRanges);
236
237 // nsIMutationObserver methods
238 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
239 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
240 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
241 NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
242 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
243
244 // WebIDL
245 static already_AddRefed<nsRange> Constructor(
246 const mozilla::dom::GlobalObject& global, mozilla::ErrorResult& aRv);
247
Collapsed()248 bool Collapsed() const {
249 return mIsPositioned && mStart.Container() == mEnd.Container() &&
250 mStart.Offset() == mEnd.Offset();
251 }
252 already_AddRefed<mozilla::dom::DocumentFragment> CreateContextualFragment(
253 const nsAString& aString, ErrorResult& aError);
254 already_AddRefed<mozilla::dom::DocumentFragment> CloneContents(
255 ErrorResult& aErr);
256 int16_t CompareBoundaryPoints(uint16_t aHow, nsRange& aOther,
257 ErrorResult& aErr);
ComparePoint(nsINode & aContainer,uint32_t aOffset,ErrorResult & aErr)258 int16_t ComparePoint(nsINode& aContainer, uint32_t aOffset,
259 ErrorResult& aErr) {
260 return ComparePoint(RawRangeBoundary(&aContainer, aOffset), aErr);
261 }
262 int16_t ComparePoint(const RawRangeBoundary& aPoint, ErrorResult& aErr);
263 void DeleteContents(ErrorResult& aRv);
264 already_AddRefed<mozilla::dom::DocumentFragment> ExtractContents(
265 ErrorResult& aErr);
266 nsINode* GetCommonAncestorContainer(ErrorResult& aRv) const;
267 nsINode* GetStartContainer(ErrorResult& aRv) const;
268 uint32_t GetStartOffset(ErrorResult& aRv) const;
269 nsINode* GetEndContainer(ErrorResult& aRv) const;
270 uint32_t GetEndOffset(ErrorResult& aRv) const;
271 void InsertNode(nsINode& aNode, ErrorResult& aErr);
272 bool IntersectsNode(nsINode& aNode, ErrorResult& aRv);
IsPointInRange(nsINode & aContainer,uint32_t aOffset,ErrorResult & aErr)273 bool IsPointInRange(nsINode& aContainer, uint32_t aOffset,
274 ErrorResult& aErr) {
275 return IsPointInRange(RawRangeBoundary(&aContainer, aOffset), aErr);
276 }
277 bool IsPointInRange(const RawRangeBoundary& aPoint, ErrorResult& aErr);
278
279 // *JS() methods are mapped to Range.*() of DOM.
280 // They may move focus only when the range represents normal selection.
281 // These methods shouldn't be used from internal.
282 void CollapseJS(bool aToStart);
283 void SelectNodeJS(nsINode& aNode, ErrorResult& aErr);
284 void SelectNodeContentsJS(nsINode& aNode, ErrorResult& aErr);
285 void SetEndJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
286 void SetEndAfterJS(nsINode& aNode, ErrorResult& aErr);
287 void SetEndBeforeJS(nsINode& aNode, ErrorResult& aErr);
288 void SetStartJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
289 void SetStartAfterJS(nsINode& aNode, ErrorResult& aErr);
290 void SetStartBeforeJS(nsINode& aNode, ErrorResult& aErr);
291
292 void SurroundContents(nsINode& aNode, ErrorResult& aErr);
293 already_AddRefed<DOMRect> GetBoundingClientRect(bool aClampToEdge = true,
294 bool aFlushLayout = true);
295 already_AddRefed<DOMRectList> GetClientRects(bool aClampToEdge = true,
296 bool aFlushLayout = true);
297 void GetClientRectsAndTexts(mozilla::dom::ClientRectsAndTexts& aResult,
298 ErrorResult& aErr);
299
300 // Following methods should be used for internal use instead of *JS().
301 void SelectNode(nsINode& aNode, ErrorResult& aErr);
302 void SelectNodeContents(nsINode& aNode, ErrorResult& aErr);
303 void SetEnd(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
304 void SetEnd(const RawRangeBoundary& aPoint, ErrorResult& aErr);
305 void SetEndAfter(nsINode& aNode, ErrorResult& aErr);
306 void SetEndBefore(nsINode& aNode, ErrorResult& aErr);
307 void SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aErr);
308 void SetStart(const RawRangeBoundary& aPoint, ErrorResult& aErr);
309 void SetStartAfter(nsINode& aNode, ErrorResult& aErr);
310 void SetStartBefore(nsINode& aNode, ErrorResult& aErr);
311
312 static void GetInnerTextNoFlush(mozilla::dom::DOMString& aValue,
313 mozilla::ErrorResult& aError,
314 nsIContent* aContainer);
315
GetParentObject()316 nsINode* GetParentObject() const { return mOwner; }
317 JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) final;
318 DocGroup* GetDocGroup() const;
319
320 private:
321 // no copy's or assigns
322 nsRange(const nsRange&);
323 nsRange& operator=(const nsRange&);
324
325 /**
326 * Cut or delete the range's contents.
327 *
328 * @param aFragment nsIDOMDocumentFragment containing the nodes.
329 * May be null to indicate the caller doesn't want a
330 * fragment.
331 */
332 nsresult CutContents(mozilla::dom::DocumentFragment** frag);
333
334 static nsresult CloneParentsBetween(nsINode* aAncestor, nsINode* aNode,
335 nsINode** aClosestAncestor,
336 nsINode** aFarthestAncestor);
337
338 public:
339 /**
340 * Compute the root node of aNode for initializing range classes.
341 * When aNode is in an anonymous subtree, this returns the shadow root or
342 * binding parent. Otherwise, the root node of the document or document
343 * fragment. If this returns nullptr, that means aNode can be neither the
344 * start container nor end container of any range.
345 */
ComputeRootNode(nsINode * aNode)346 static nsINode* ComputeRootNode(nsINode* aNode) {
347 return ComputeRootNode(aNode, false);
348 }
349
350 /**
351 * Return true if aStartContainer/aStartOffset and aEndContainer/aEndOffset
352 * are valid start and end points for a range. Otherwise, return false.
353 */
354 static bool IsValidPoints(nsINode* aStartContainer, uint32_t aStartOffset,
355 nsINode* aEndContainer, uint32_t aEndOffset);
356
357 /******************************************************************************
358 * Utility routine to detect if a content node starts before a range and/or
359 * ends after a range. If neither it is contained inside the range.
360 *
361 * XXX - callers responsibility to ensure node in same doc as range!
362 *
363 *****************************************************************************/
364 static nsresult CompareNodeToRange(nsINode* aNode, nsRange* aRange,
365 bool* outNodeBefore, bool* outNodeAfter);
366
367 /**
368 * Return true if any part of (aNode, aStartOffset) .. (aNode, aEndOffset)
369 * overlaps any nsRange in aNode's GetNextRangeCommonAncestor ranges (i.e.
370 * where aNode is a descendant of a range's common ancestor node).
371 * If a nsRange starts in (aNode, aEndOffset) or if it ends in
372 * (aNode, aStartOffset) then it is non-overlapping and the result is false
373 * for that nsRange. Collapsed ranges always counts as non-overlapping.
374 */
375 static bool IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
376 uint32_t aEndOffset);
377
378 /**
379 * This helper function gets rects and correlated text for the given range.
380 * @param aTextList optional where nullptr = don't retrieve text
381 */
382 static void CollectClientRectsAndText(
383 nsLayoutUtils::RectCallback* aCollector,
384 mozilla::dom::Sequence<nsString>* aTextList, nsRange* aRange,
385 nsINode* aStartContainer, uint32_t aStartOffset, nsINode* aEndContainer,
386 uint32_t aEndOffset, bool aClampToEdge, bool aFlushLayout);
387
388 /**
389 * Scan this range for -moz-user-select:none nodes and split it up into
390 * multiple ranges to exclude those nodes. The resulting ranges are put
391 * in aOutRanges. If no -moz-user-select:none node is found in the range
392 * then |this| is unmodified and is the only range in aOutRanges.
393 * Otherwise, |this| will be modified so that it ends before the first
394 * -moz-user-select:none node and additional ranges may also be created.
395 * If all nodes in the range are -moz-user-select:none then aOutRanges
396 * will be empty.
397 * @param aOutRanges the resulting set of ranges
398 */
399 void ExcludeNonSelectableNodes(nsTArray<RefPtr<nsRange>>* aOutRanges);
400
401 /**
402 * Notify the selection listeners after a range has been modified.
403 */
404 void NotifySelectionListenersAfterRangeSet();
405
406 typedef nsTHashtable<nsPtrHashKey<nsRange>> RangeHashTable;
407
408 protected:
409 void RegisterCommonAncestor(nsINode* aNode);
410 void UnregisterCommonAncestor(nsINode* aNode, bool aIsUnlinking);
IsValidBoundary(nsINode * aNode)411 nsINode* IsValidBoundary(nsINode* aNode) const {
412 return ComputeRootNode(aNode, mMaySpanAnonymousSubtrees);
413 }
414
415 /**
416 * XXX nsRange should accept 0 - UINT32_MAX as offset. However, users of
417 * nsRange treat offset as int32_t. Additionally, some other internal
418 * APIs like nsINode::ComputeIndexOf() use int32_t. Therefore,
419 * nsRange should accept only 0 - INT32_MAX as valid offset for now.
420 */
IsValidOffset(uint32_t aOffset)421 static bool IsValidOffset(uint32_t aOffset) { return aOffset <= INT32_MAX; }
422 static bool IsValidOffset(nsINode* aNode, uint32_t aOffset);
423
424 static nsINode* ComputeRootNode(nsINode* aNode,
425 bool aMaySpanAnonymousSubtrees);
426
427 // CharacterDataChanged set aNotInsertedYet to true to disable an assertion
428 // and suppress re-registering a range common ancestor node since
429 // the new text node of a splitText hasn't been inserted yet.
430 // CharacterDataChanged does the re-registering when needed.
431 void DoSetRange(const RawRangeBoundary& lowerBound,
432 const RawRangeBoundary& upperBound, nsINode* aRoot,
433 bool aNotInsertedYet = false);
434
435 /**
436 * For a range for which IsInSelection() is true, return the common ancestor
437 * for the range, which we had to compute when the common ancestor changed or
438 * IsInSelection became true, so we could register with it. That is, it's a
439 * faster version of GetCommonAncestor that only works for ranges in a
440 * Selection. The method will assert and the behavior is undefined if called
441 * on a range where IsInSelection() is false.
442 */
443 nsINode* GetRegisteredCommonAncestor();
444
445 // Helper to IsNodeSelected.
446 static bool IsNodeInSortedRanges(nsINode* aNode, uint32_t aStartOffset,
447 uint32_t aEndOffset,
448 const nsTArray<const nsRange*>& aRanges,
449 size_t aRangeStart, size_t aRangeEnd);
450
451 // Assume that this is guaranteed that this is held by the caller when
452 // this is used. (Note that we cannot use AutoRestore for mCalledByJS
453 // due to a bit field.)
454 class MOZ_RAII AutoCalledByJSRestore final {
455 private:
456 nsRange& mRange;
457 bool mOldValue;
458 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
459
460 public:
AutoCalledByJSRestore(nsRange & aRange MOZ_GUARD_OBJECT_NOTIFIER_PARAM)461 explicit AutoCalledByJSRestore(
462 nsRange& aRange MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
463 : mRange(aRange), mOldValue(aRange.mCalledByJS) {
464 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
465 }
~AutoCalledByJSRestore()466 ~AutoCalledByJSRestore() { mRange.mCalledByJS = mOldValue; }
SavedValue()467 bool SavedValue() const { return mOldValue; }
468 };
469
470 struct MOZ_STACK_CLASS AutoInvalidateSelection {
AutoInvalidateSelectionAutoInvalidateSelection471 explicit AutoInvalidateSelection(nsRange* aRange) : mRange(aRange) {
472 if (!mRange->IsInSelection() || sIsNested) {
473 return;
474 }
475 sIsNested = true;
476 mCommonAncestor = mRange->GetRegisteredCommonAncestor();
477 }
478 ~AutoInvalidateSelection();
479 nsRange* mRange;
480 RefPtr<nsINode> mCommonAncestor;
481 static bool sIsNested;
482 };
483
484 nsCOMPtr<nsIDocument> mOwner;
485 nsCOMPtr<nsINode> mRoot;
486 // mRegisteredCommonAncestor is only non-null when the range
487 // IsInSelection(). It's kept alive via mStartContainer/mEndContainer,
488 // because we update it any time those could become disconnected from it.
489 nsINode* MOZ_NON_OWNING_REF mRegisteredCommonAncestor;
490 RefPtr<mozilla::dom::Selection> mSelection;
491
492 // These raw pointers are used to remember a child that is about
493 // to be inserted between a CharacterData call and a subsequent
494 // ContentInserted or ContentAppended call. It is safe to store
495 // these refs because the caller is guaranteed to trigger both
496 // notifications while holding a strong reference to the new child.
497 nsIContent* MOZ_NON_OWNING_REF mNextStartRef;
498 nsIContent* MOZ_NON_OWNING_REF mNextEndRef;
499
500 RangeBoundary mStart;
501 RangeBoundary mEnd;
502
503 bool mIsPositioned : 1;
504 bool mMaySpanAnonymousSubtrees : 1;
505 bool mIsGenerated : 1;
506 bool mCalledByJS : 1;
507 };
508
ToCanonicalSupports(nsRange * aRange)509 inline nsISupports* ToCanonicalSupports(nsRange* aRange) {
510 return static_cast<nsIDOMRange*>(aRange);
511 }
512
ToSupports(nsRange * aRange)513 inline nsISupports* ToSupports(nsRange* aRange) {
514 return static_cast<nsIDOMRange*>(aRange);
515 }
516
517 #endif /* nsRange_h___ */
518