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 "inDeepTreeWalker.h"
8 #include "inLayoutUtils.h"
9
10 #include "BindingStyleRule.h"
11 #include "nsString.h"
12 #include "mozilla/dom/Document.h"
13 #include "nsServiceManagerUtils.h"
14 #include "nsIContent.h"
15 #include "ChildIterator.h"
16 #include "mozilla/dom/Element.h"
17 #include "mozilla/dom/InspectorUtils.h"
18 #include "mozilla/dom/NodeFilterBinding.h"
19
20 using mozilla::dom::InspectorUtils;
21
22 /*****************************************************************************
23 * This implementation does not currently operaate according to the W3C spec.
24 * In particular it does NOT handle DOM mutations during the walk. It also
25 * ignores whatToShow and the filter.
26 *****************************************************************************/
27
28 ////////////////////////////////////////////////////
29
inDeepTreeWalker()30 inDeepTreeWalker::inDeepTreeWalker()
31 : mShowAnonymousContent(false),
32 mShowSubDocuments(false),
33 mShowDocumentsAsNodes(false),
34 mCurrentIndex(-1),
35 mWhatToShow(mozilla::dom::NodeFilter_Binding::SHOW_ALL) {}
36
37 inDeepTreeWalker::~inDeepTreeWalker() = default;
38
NS_IMPL_ISUPPORTS(inDeepTreeWalker,inIDeepTreeWalker)39 NS_IMPL_ISUPPORTS(inDeepTreeWalker, inIDeepTreeWalker)
40
41 ////////////////////////////////////////////////////
42 // inIDeepTreeWalker
43
44 NS_IMETHODIMP
45 inDeepTreeWalker::GetShowAnonymousContent(bool* aShowAnonymousContent) {
46 *aShowAnonymousContent = mShowAnonymousContent;
47 return NS_OK;
48 }
49
50 NS_IMETHODIMP
SetShowAnonymousContent(bool aShowAnonymousContent)51 inDeepTreeWalker::SetShowAnonymousContent(bool aShowAnonymousContent) {
52 mShowAnonymousContent = aShowAnonymousContent;
53 return NS_OK;
54 }
55
56 NS_IMETHODIMP
GetShowSubDocuments(bool * aShowSubDocuments)57 inDeepTreeWalker::GetShowSubDocuments(bool* aShowSubDocuments) {
58 *aShowSubDocuments = mShowSubDocuments;
59 return NS_OK;
60 }
61
62 NS_IMETHODIMP
SetShowSubDocuments(bool aShowSubDocuments)63 inDeepTreeWalker::SetShowSubDocuments(bool aShowSubDocuments) {
64 mShowSubDocuments = aShowSubDocuments;
65 return NS_OK;
66 }
67
68 NS_IMETHODIMP
GetShowDocumentsAsNodes(bool * aShowDocumentsAsNodes)69 inDeepTreeWalker::GetShowDocumentsAsNodes(bool* aShowDocumentsAsNodes) {
70 *aShowDocumentsAsNodes = mShowDocumentsAsNodes;
71 return NS_OK;
72 }
73
74 NS_IMETHODIMP
SetShowDocumentsAsNodes(bool aShowDocumentsAsNodes)75 inDeepTreeWalker::SetShowDocumentsAsNodes(bool aShowDocumentsAsNodes) {
76 mShowDocumentsAsNodes = aShowDocumentsAsNodes;
77 return NS_OK;
78 }
79
80 NS_IMETHODIMP
Init(nsINode * aRoot,uint32_t aWhatToShow)81 inDeepTreeWalker::Init(nsINode* aRoot, uint32_t aWhatToShow) {
82 if (!aRoot) {
83 return NS_ERROR_INVALID_ARG;
84 }
85
86 mRoot = aRoot;
87 mCurrentNode = aRoot;
88 mWhatToShow = aWhatToShow;
89
90 return NS_OK;
91 }
92
93 ////////////////////////////////////////////////////
94
95 NS_IMETHODIMP
GetRoot(nsINode ** aRoot)96 inDeepTreeWalker::GetRoot(nsINode** aRoot) {
97 *aRoot = mRoot;
98 NS_IF_ADDREF(*aRoot);
99 return NS_OK;
100 }
101
102 NS_IMETHODIMP
GetWhatToShow(uint32_t * aWhatToShow)103 inDeepTreeWalker::GetWhatToShow(uint32_t* aWhatToShow) {
104 *aWhatToShow = mWhatToShow;
105 return NS_OK;
106 }
107
108 NS_IMETHODIMP
GetCurrentNode(nsINode ** aCurrentNode)109 inDeepTreeWalker::GetCurrentNode(nsINode** aCurrentNode) {
110 *aCurrentNode = mCurrentNode;
111 NS_IF_ADDREF(*aCurrentNode);
112 return NS_OK;
113 }
114
GetParent()115 already_AddRefed<nsINode> inDeepTreeWalker::GetParent() {
116 MOZ_ASSERT(mCurrentNode);
117
118 if (mCurrentNode == mRoot) {
119 return nullptr;
120 }
121
122 nsINode* parentNode =
123 InspectorUtils::GetParentForNode(*mCurrentNode, mShowAnonymousContent);
124
125 uint16_t nodeType = 0;
126 if (parentNode) {
127 nodeType = parentNode->NodeType();
128 }
129 // For compatibility reasons by default we skip the document nodes
130 // from the walk.
131 if (!mShowDocumentsAsNodes && nodeType == nsINode::DOCUMENT_NODE &&
132 parentNode != mRoot) {
133 parentNode =
134 InspectorUtils::GetParentForNode(*parentNode, mShowAnonymousContent);
135 }
136
137 return do_AddRef(parentNode);
138 }
139
GetChildren(nsINode * aParent,bool aShowAnonymousContent,bool aShowSubDocuments)140 static already_AddRefed<nsINodeList> GetChildren(nsINode* aParent,
141 bool aShowAnonymousContent,
142 bool aShowSubDocuments) {
143 MOZ_ASSERT(aParent);
144
145 nsCOMPtr<nsINodeList> ret;
146 if (aShowSubDocuments) {
147 mozilla::dom::Document* domdoc = inLayoutUtils::GetSubDocumentFor(aParent);
148 if (domdoc) {
149 aParent = domdoc;
150 }
151 }
152
153 nsCOMPtr<nsIContent> parentAsContent = do_QueryInterface(aParent);
154 if (parentAsContent && aShowAnonymousContent) {
155 ret = parentAsContent->GetChildren(nsIContent::eAllChildren);
156 } else {
157 // If it's not a content, then it's a document (or an attribute but we can
158 // ignore that case here). If aShowAnonymousContent is false we also want to
159 // fall back to ChildNodes so we can skip any native anon content that
160 // GetChildren would return.
161 ret = aParent->ChildNodes();
162 }
163 return ret.forget();
164 }
165
166 NS_IMETHODIMP
SetCurrentNode(nsINode * aCurrentNode)167 inDeepTreeWalker::SetCurrentNode(nsINode* aCurrentNode) {
168 // mCurrentNode can only be null if init either failed, or has not been
169 // called yet.
170 if (!mCurrentNode || !aCurrentNode) {
171 return NS_ERROR_FAILURE;
172 }
173
174 // If Document nodes are skipped by the walk, we should not allow
175 // one to set one as the current node either.
176 if (!mShowDocumentsAsNodes) {
177 if (aCurrentNode->NodeType() == nsINode::DOCUMENT_NODE) {
178 return NS_ERROR_FAILURE;
179 }
180 }
181
182 return SetCurrentNode(aCurrentNode, nullptr);
183 }
184
SetCurrentNode(nsINode * aCurrentNode,nsINodeList * aSiblings)185 nsresult inDeepTreeWalker::SetCurrentNode(nsINode* aCurrentNode,
186 nsINodeList* aSiblings) {
187 MOZ_ASSERT(aCurrentNode);
188
189 // We want to store the original state so in case of error
190 // we can restore that.
191 nsCOMPtr<nsINodeList> tmpSiblings = mSiblings;
192 nsCOMPtr<nsINode> tmpCurrent = mCurrentNode;
193 mSiblings = aSiblings;
194 mCurrentNode = aCurrentNode;
195
196 // If siblings were not passed in as argument we have to
197 // get them from the parent node of aCurrentNode.
198 // Note: in the mShowDoucmentsAsNodes case when a sub document
199 // is set as the current, we don't want to get the children
200 // from the iframe accidentally here, so let's just skip this
201 // part for document nodes, they should never have siblings.
202 if (!mSiblings) {
203 if (aCurrentNode->NodeType() != nsINode::DOCUMENT_NODE) {
204 nsCOMPtr<nsINode> parent = GetParent();
205 if (parent) {
206 mSiblings =
207 GetChildren(parent, mShowAnonymousContent, mShowSubDocuments);
208 }
209 }
210 }
211
212 if (mSiblings && mSiblings->Length()) {
213 // We cached all the siblings (if there are any) of the current node, but we
214 // still have to set the index too, to be able to iterate over them.
215 nsCOMPtr<nsIContent> currentAsContent = do_QueryInterface(mCurrentNode);
216 MOZ_ASSERT(currentAsContent);
217 int32_t index = mSiblings->IndexOf(currentAsContent);
218 if (index < 0) {
219 // If someone tries to set current node to some value that is not
220 // reachable otherwise, let's throw. (For example mShowAnonymousContent is
221 // false and some NAC was passed in)
222
223 // Restore state first.
224 mCurrentNode = tmpCurrent;
225 mSiblings = tmpSiblings;
226 return NS_ERROR_INVALID_ARG;
227 }
228 mCurrentIndex = index;
229 } else {
230 mCurrentIndex = -1;
231 }
232 return NS_OK;
233 }
234
235 NS_IMETHODIMP
ParentNode(nsINode ** _retval)236 inDeepTreeWalker::ParentNode(nsINode** _retval) {
237 *_retval = nullptr;
238 if (!mCurrentNode || mCurrentNode == mRoot) {
239 return NS_OK;
240 }
241
242 nsCOMPtr<nsINode> parent = GetParent();
243
244 if (!parent) {
245 return NS_OK;
246 }
247
248 nsresult rv = SetCurrentNode(parent);
249 NS_ENSURE_SUCCESS(rv, rv);
250
251 parent.forget(_retval);
252 return NS_OK;
253 }
254
255 // FirstChild and LastChild are very similar methods, this is the generic
256 // version for internal use. With aReverse = true it returns the LastChild.
EdgeChild(nsINode ** _retval,bool aFront)257 nsresult inDeepTreeWalker::EdgeChild(nsINode** _retval, bool aFront) {
258 if (!mCurrentNode) {
259 return NS_ERROR_FAILURE;
260 }
261
262 *_retval = nullptr;
263
264 nsCOMPtr<nsINode> echild;
265 if (mShowSubDocuments && mShowDocumentsAsNodes) {
266 // GetChildren below, will skip the document node from
267 // the walk. But if mShowDocumentsAsNodes is set to true
268 // we want to include the (sub)document itself too.
269 echild = inLayoutUtils::GetSubDocumentFor(mCurrentNode);
270 }
271
272 nsCOMPtr<nsINodeList> children;
273 if (!echild) {
274 children =
275 GetChildren(mCurrentNode, mShowAnonymousContent, mShowSubDocuments);
276 if (children && children->Length() > 0) {
277 echild = children->Item(aFront ? 0 : children->Length() - 1);
278 }
279 }
280
281 if (echild) {
282 nsresult rv = SetCurrentNode(echild, children);
283 NS_ENSURE_SUCCESS(rv, rv);
284 NS_ADDREF(*_retval = mCurrentNode);
285 }
286
287 return NS_OK;
288 }
289
290 NS_IMETHODIMP
FirstChild(nsINode ** _retval)291 inDeepTreeWalker::FirstChild(nsINode** _retval) {
292 return EdgeChild(_retval, /* aFront = */ true);
293 }
294
295 NS_IMETHODIMP
LastChild(nsINode ** _retval)296 inDeepTreeWalker::LastChild(nsINode** _retval) {
297 return EdgeChild(_retval, /* aFront = */ false);
298 }
299
300 NS_IMETHODIMP
PreviousSibling(nsINode ** _retval)301 inDeepTreeWalker::PreviousSibling(nsINode** _retval) {
302 *_retval = nullptr;
303 if (!mCurrentNode || !mSiblings || mCurrentIndex < 1) {
304 return NS_OK;
305 }
306
307 nsIContent* prev = mSiblings->Item(--mCurrentIndex);
308 mCurrentNode = prev;
309 NS_ADDREF(*_retval = mCurrentNode);
310 return NS_OK;
311 }
312
313 NS_IMETHODIMP
NextSibling(nsINode ** _retval)314 inDeepTreeWalker::NextSibling(nsINode** _retval) {
315 *_retval = nullptr;
316 if (!mCurrentNode || !mSiblings ||
317 mCurrentIndex + 1 >= (int32_t)mSiblings->Length()) {
318 return NS_OK;
319 }
320
321 nsIContent* next = mSiblings->Item(++mCurrentIndex);
322 mCurrentNode = next;
323 NS_ADDREF(*_retval = mCurrentNode);
324 return NS_OK;
325 }
326
327 NS_IMETHODIMP
PreviousNode(nsINode ** _retval)328 inDeepTreeWalker::PreviousNode(nsINode** _retval) {
329 if (!mCurrentNode || mCurrentNode == mRoot) {
330 // Nowhere to go from here
331 *_retval = nullptr;
332 return NS_OK;
333 }
334
335 nsCOMPtr<nsINode> node;
336 PreviousSibling(getter_AddRefs(node));
337
338 if (!node) {
339 return ParentNode(_retval);
340 }
341
342 // Now we're positioned at our previous sibling. But since the DOM tree
343 // traversal is depth-first, the previous node is its most deeply nested last
344 // child. Just loop until LastChild() returns null; since the LastChild()
345 // call that returns null won't affect our position, we will then be
346 // positioned at the correct node.
347 while (node) {
348 LastChild(getter_AddRefs(node));
349 }
350
351 NS_ADDREF(*_retval = mCurrentNode);
352 return NS_OK;
353 }
354
355 NS_IMETHODIMP
NextNode(nsINode ** _retval)356 inDeepTreeWalker::NextNode(nsINode** _retval) {
357 if (!mCurrentNode) {
358 return NS_OK;
359 }
360
361 // First try our kids
362 FirstChild(_retval);
363
364 if (*_retval) {
365 return NS_OK;
366 }
367
368 // Now keep trying next siblings up the parent chain, but if we
369 // discover there's nothing else restore our state.
370 #ifdef DEBUG
371 nsINode* origCurrentNode = mCurrentNode;
372 #endif
373 uint32_t lastChildCallsToMake = 0;
374 while (1) {
375 NextSibling(_retval);
376
377 if (*_retval) {
378 return NS_OK;
379 }
380
381 nsCOMPtr<nsINode> parent;
382 ParentNode(getter_AddRefs(parent));
383 if (!parent) {
384 // Nowhere else to go; we're done. Restore our state.
385 while (lastChildCallsToMake--) {
386 nsCOMPtr<nsINode> dummy;
387 LastChild(getter_AddRefs(dummy));
388 }
389 NS_ASSERTION(mCurrentNode == origCurrentNode,
390 "Didn't go back to the right node?");
391 *_retval = nullptr;
392 return NS_OK;
393 }
394 ++lastChildCallsToMake;
395 }
396
397 MOZ_ASSERT_UNREACHABLE("how did we get here?");
398 return NS_OK;
399 }
400