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 #include "mozilla/RangeUtils.h"
8 
9 #include "mozilla/Assertions.h"
10 #include "mozilla/dom/AbstractRange.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/ShadowRoot.h"
13 #include "nsContentUtils.h"
14 
15 namespace mozilla {
16 
17 using namespace dom;
18 
19 template bool RangeUtils::IsValidPoints(const RangeBoundary& aStartBoundary,
20                                         const RangeBoundary& aEndBoundary);
21 template bool RangeUtils::IsValidPoints(const RangeBoundary& aStartBoundary,
22                                         const RawRangeBoundary& aEndBoundary);
23 template bool RangeUtils::IsValidPoints(const RawRangeBoundary& aStartBoundary,
24                                         const RangeBoundary& aEndBoundary);
25 template bool RangeUtils::IsValidPoints(const RawRangeBoundary& aStartBoundary,
26                                         const RawRangeBoundary& aEndBoundary);
27 
28 // static
ComputeRootNode(nsINode * aNode)29 nsINode* RangeUtils::ComputeRootNode(nsINode* aNode) {
30   if (!aNode) {
31     return nullptr;
32   }
33 
34   if (aNode->IsContent()) {
35     if (aNode->NodeInfo()->NameAtom() == nsGkAtoms::documentTypeNodeName) {
36       return nullptr;
37     }
38 
39     nsIContent* content = aNode->AsContent();
40 
41     // If the node is in a shadow tree then the ShadowRoot is the root.
42     //
43     // FIXME(emilio): Should this be after the NAC check below? We can have NAC
44     // inside Shadow DOM which will peek this path rather than the one below.
45     if (ShadowRoot* containingShadow = content->GetContainingShadow()) {
46       return containingShadow;
47     }
48 
49     // If the node is in NAC, then the NAC parent should be the root.
50     if (nsINode* root = content->GetClosestNativeAnonymousSubtreeRootParent()) {
51       return root;
52     }
53   }
54 
55   // Elements etc. must be in document or in document fragment,
56   // text nodes in document, in document fragment or in attribute.
57   if (nsINode* root = aNode->GetUncomposedDoc()) {
58     return root;
59   }
60 
61   NS_ASSERTION(!aNode->SubtreeRoot()->IsDocument(),
62                "GetUncomposedDoc should have returned a doc");
63 
64   // We allow this because of backward compatibility.
65   return aNode->SubtreeRoot();
66 }
67 
68 // static
69 template <typename SPT, typename SRT, typename EPT, typename ERT>
IsValidPoints(const RangeBoundaryBase<SPT,SRT> & aStartBoundary,const RangeBoundaryBase<EPT,ERT> & aEndBoundary)70 bool RangeUtils::IsValidPoints(
71     const RangeBoundaryBase<SPT, SRT>& aStartBoundary,
72     const RangeBoundaryBase<EPT, ERT>& aEndBoundary) {
73   // Use NS_WARN_IF() only for the cases where the arguments are unexpected.
74   if (NS_WARN_IF(!aStartBoundary.IsSetAndValid()) ||
75       NS_WARN_IF(!aEndBoundary.IsSetAndValid())) {
76     return false;
77   }
78 
79   // Otherwise, don't use NS_WARN_IF() for preventing to make console messy.
80   // Instead, check one by one since it is easier to catch the error reason
81   // with debugger.
82 
83   if (ComputeRootNode(aStartBoundary.Container()) !=
84       ComputeRootNode(aEndBoundary.Container())) {
85     return false;
86   }
87 
88   const Maybe<int32_t> order =
89       nsContentUtils::ComparePoints(aStartBoundary, aEndBoundary);
90   if (!order) {
91     MOZ_ASSERT_UNREACHABLE();
92     return false;
93   }
94 
95   return *order != 1;
96 }
97 
98 // static
IsNodeContainedInRange(nsINode & aNode,AbstractRange * aAbstractRange)99 Maybe<bool> RangeUtils::IsNodeContainedInRange(nsINode& aNode,
100                                                AbstractRange* aAbstractRange) {
101   bool nodeIsBeforeRange{false};
102   bool nodeIsAfterRange{false};
103 
104   const nsresult rv = CompareNodeToRange(&aNode, aAbstractRange,
105                                          &nodeIsBeforeRange, &nodeIsAfterRange);
106   if (NS_FAILED(rv)) {
107     return Nothing();
108   }
109 
110   return Some(!nodeIsBeforeRange && !nodeIsAfterRange);
111 }
112 
113 // Utility routine to detect if a content node is completely contained in a
114 // range If outNodeBefore is returned true, then the node starts before the
115 // range does. If outNodeAfter is returned true, then the node ends after the
116 // range does. Note that both of the above might be true. If neither are true,
117 // the node is contained inside of the range.
118 // XXX - callers responsibility to ensure node in same doc as range!
119 
120 // static
CompareNodeToRange(nsINode * aNode,AbstractRange * aAbstractRange,bool * aNodeIsBeforeRange,bool * aNodeIsAfterRange)121 nsresult RangeUtils::CompareNodeToRange(nsINode* aNode,
122                                         AbstractRange* aAbstractRange,
123                                         bool* aNodeIsBeforeRange,
124                                         bool* aNodeIsAfterRange) {
125   MOZ_ASSERT(aNodeIsBeforeRange);
126   MOZ_ASSERT(aNodeIsAfterRange);
127 
128   if (NS_WARN_IF(!aNode) || NS_WARN_IF(!aAbstractRange) ||
129       NS_WARN_IF(!aAbstractRange->IsPositioned())) {
130     return NS_ERROR_INVALID_ARG;
131   }
132 
133   // create a pair of dom points that expresses location of node:
134   //     NODE(start), NODE(end)
135   // Let incoming range be:
136   //    {RANGE(start), RANGE(end)}
137   // if (RANGE(start) <= NODE(start))  and (RANGE(end) => NODE(end))
138   // then the Node is contained (completely) by the Range.
139 
140   // gather up the dom point info
141   int32_t nodeStart, nodeEnd;
142   nsINode* parent = aNode->GetParentNode();
143   if (!parent) {
144     // can't make a parent/offset pair to represent start or
145     // end of the root node, because it has no parent.
146     // so instead represent it by (node,0) and (node,numChildren)
147     parent = aNode;
148     nodeStart = 0;
149     uint32_t childCount = aNode->GetChildCount();
150     MOZ_ASSERT(childCount <= INT32_MAX,
151                "There shouldn't be over INT32_MAX children");
152     nodeEnd = static_cast<int32_t>(childCount);
153   } else {
154     nodeStart = parent->ComputeIndexOf(aNode);
155     nodeEnd = nodeStart + 1;
156     MOZ_ASSERT(nodeStart < nodeEnd, "nodeStart shouldn't be INT32_MAX");
157   }
158 
159   // XXX nsContentUtils::ComparePoints() may be expensive.  If some callers
160   //     just want one of aNodeIsBeforeRange or aNodeIsAfterRange, we can
161   //     skip the other comparison.
162 
163   // In the ComparePoints calls below we use a container & offset instead of
164   // a range boundary because the range boundary constructor warns if you pass
165   // in a -1 offset and the ComputeIndexOf call above can return -1 if aNode
166   // is native anonymous content. ComparePoints has comments about offsets
167   // being -1 and it seems to deal with it, or at least we aren't aware of any
168   // problems arising because of it. We don't have a better idea how to get
169   // rid of the warning without much larger changes so we do this just to
170   // silence the warning. (Bug 1438996)
171 
172   // is RANGE(start) <= NODE(start) ?
173   Maybe<int32_t> order = nsContentUtils::ComparePoints(
174       aAbstractRange->StartRef().Container(),
175       *aAbstractRange->StartRef().Offset(
176           RangeBoundary::OffsetFilter::kValidOrInvalidOffsets),
177       parent, nodeStart);
178   if (NS_WARN_IF(!order)) {
179     return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
180   }
181   *aNodeIsBeforeRange = *order > 0;
182 
183   // is RANGE(end) >= NODE(end) ?
184   order = nsContentUtils::ComparePoints(
185       aAbstractRange->EndRef().Container(),
186       *aAbstractRange->EndRef().Offset(
187           RangeBoundary::OffsetFilter::kValidOrInvalidOffsets),
188       parent, nodeEnd);
189 
190   if (NS_WARN_IF(!order)) {
191     return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
192   }
193 
194   *aNodeIsAfterRange = *order < 0;
195 
196   return NS_OK;
197 }
198 
199 }  // namespace mozilla
200