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