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