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