View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.dom;
16  
17  import org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.BrowserRunner;
19  import org.htmlunit.junit.annotation.Alerts;
20  import org.junit.Test;
21  import org.junit.runner.RunWith;
22  
23  /**
24   * Tests for {@link TreeWalker}.
25   *
26   * @author <a href="mailto:mike@10gen.com">Mike Dirolf</a>
27   * @author Marc Guillemot
28   * @author Frank Danek
29   */
30  @RunWith(BrowserRunner.class)
31  public class TreeWalkerTest extends WebDriverTestCase {
32      private static final String CONTENT_START = DOCTYPE_HTML
33          + "<html><head><title></title>\n"
34          + "<script>\n"
35          + LOG_TITLE_FUNCTION
36          + "function safeTagName(o) {\n"
37          + "  return o ? o.tagName : undefined\n"
38          + "}\n"
39          + "function alertTreeWalker(tw) {\n"
40          + "  log(safeTagName(tw.root));\n"
41          + "  log(safeTagName(tw.currentNode));\n"
42          + "  log(tw.whatToShow);\n"
43          + "  log(tw.expandEntityReferences);\n"
44          + "}\n"
45          + "function test() {\n"
46          + "  try {\n";
47  
48      private static final String CONTENT_END = "\n  } catch(e) { logEx(e) }\n"
49          + "\n}\n</script></head>\n"
50          + "<body onload='test()'>\n"
51          + "<div id='theDiv'>Hello, <span id='theSpan'>this is a test for"
52          + "<a id='theA' href='http://htmlunit.sf.net'>HtmlUnit</a> support"
53          + "</div>\n"
54          + "<p id='theP'>for TreeWalker's</p>\n"
55          + "</body></html>";
56  
57      private void test(final String script) throws Exception {
58          final String html = CONTENT_START + script + CONTENT_END;
59  
60          loadPageVerifyTitle2(html);
61      }
62  
63      private static final String CONTENT_START2 =
64          "<html><head><title></title>\n"
65          + "<script>\n"
66          + LOG_TITLE_FUNCTION
67          + "function safeTagName(o) {\n"
68          + "  return o ? o.tagName : undefined\n"
69          + "}\n"
70          + "function test() {\n"
71          + "  try {\n";
72  
73      private static final String CONTENT_END2 = "\n  } catch(e) { logEx(e) }\n"
74          + "\n}\n</script></head>\n"
75          + "<body onload='test()'>\n"
76          + "<div id='theDiv'>Hello, <span id='theSpan'>this is a test for"
77          + "<a id='theA' href='http://htmlunit.sf.net'>HtmlUnit</a> support"
78          + "</div>\n"
79          + "<p id='theP'>for <br/>TreeWalkers<span>something</span>that is <a>important to me</a></p>\n"
80          + "<span>something <code>codey</code>goes <pre>  here</pre></span>\n"
81          + "</body></html>";
82  
83      private void test2(final String script) throws Exception {
84          final String html = CONTENT_START2 + script + CONTENT_END2;
85  
86          loadPageVerifyTitle2(html);
87      }
88  
89      /**
90       * @throws Exception if the test fails
91       */
92      @Test
93      @Alerts({"BODY", "BODY", "1", "undefined"})
94      public void getters1() throws Exception {
95          final String script = "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, false);"
96                              + "alertTreeWalker(tw);";
97  
98          test(script);
99      }
100 
101     /**
102      * @throws Exception if the test fails
103      */
104     @Test
105     @Alerts({"A", "A", "4294967295", "undefined"})
106     // The spec states it is an unsigned long.
107     public void getters2() throws Exception {
108         final String script = "var theA = document.getElementById('theA');\n"
109             + "var tw = document.createTreeWalker(theA, NodeFilter.SHOW_ALL, null, true);\n"
110             + "alertTreeWalker(tw);\n";
111 
112         test(script);
113     }
114 
115     /**
116      * @throws Exception if the test fails
117      */
118     @Test
119     @Alerts({"BODY", "DIV", "1", "undefined"})
120     public void firstChild() throws Exception {
121         final String script =
122             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
123             + "tw.firstChild();\n"
124             + "alertTreeWalker(tw);\n";
125 
126         test(script);
127     }
128 
129     /**
130      * @throws Exception if the test fails
131      */
132     @Test
133     @Alerts({"BODY", "SPAN", "1", "undefined"})
134     public void firstChild2() throws Exception {
135         final String script =
136             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
137             + "tw.currentNode = document.getElementById('theDiv');\n"
138             + "tw.firstChild();\n"
139             + "alertTreeWalker(tw);\n";
140 
141         test(script);
142     }
143 
144     /**
145      * @throws Exception if the test fails
146      */
147     @Test
148     @Alerts({"BODY", "P", "1", "undefined"})
149     public void lastChild() throws Exception {
150         final String script =
151             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
152             + "tw.lastChild();\n"
153             + "alertTreeWalker(tw);\n";
154 
155         test(script);
156     }
157 
158     /**
159      * @throws Exception if the test fails
160      */
161     @Test
162     @Alerts({"BODY", "SPAN", "1", "undefined"})
163     public void lastChild2() throws Exception {
164         final String script =
165             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
166             + "tw.currentNode = document.getElementById('theDiv');\n"
167             + "tw.lastChild();\n"
168             + "alertTreeWalker(tw);\n";
169 
170         test(script);
171     }
172 
173     /**
174      * @throws Exception if the test fails
175      */
176     @Test
177     @Alerts({"BODY", "BODY", "1", "undefined", "null"})
178     public void parentNode() throws Exception {
179         final String script =
180             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
181             + "tw.currentNode = document.getElementById('theDiv');\n"
182             + "tw.parentNode();\n"
183             + "alertTreeWalker(tw);\n"
184             + "log(tw.parentNode());";
185 
186         test(script);
187     }
188 
189     /**
190      * @throws Exception if the test fails
191      */
192     @Test
193     @Alerts({"BODY", "DIV", "1", "undefined"})
194     public void parentNode2() throws Exception {
195         final String script =
196             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
197             + "tw.currentNode = document.getElementById('theSpan');\n"
198             + "tw.parentNode();\n"
199             + "alertTreeWalker(tw);";
200 
201         test(script);
202     }
203 
204     /**
205      * @throws Exception if the test fails
206      */
207     @Test
208     @Alerts({"BODY", "P", "1", "undefined", "null"})
209     public void siblings() throws Exception {
210         final String script =
211             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
212             + "tw.currentNode = document.getElementById('theDiv');\n"
213             + "tw.nextSibling();\n"
214             + "alertTreeWalker(tw);\n"
215             + "log(tw.nextSibling());\n";
216 
217         test(script);
218     }
219 
220     /**
221      * @throws Exception if the test fails
222      */
223     @Test
224     @Alerts({"BODY", "DIV", "1", "undefined", "null"})
225     public void siblings2() throws Exception {
226         final String script1 =
227             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
228             + "tw.currentNode = document.getElementById('theP');\n"
229             + "tw.previousSibling();\n"
230             + "alertTreeWalker(tw);\n"
231             + "log(tw.previousSibling());\n";
232 
233         test(script1);
234     }
235 
236     /**
237      * @throws Exception if the test fails
238      */
239     @Test
240     @Alerts({"BODY", "DIV", "SPAN", "A", "P", "undefined", "P"})
241     public void next() throws Exception {
242         final String script =
243             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
244             + "log(safeTagName(tw.currentNode));\n"
245             + "log(safeTagName(tw.nextNode()));\n"
246             + "log(safeTagName(tw.nextNode()));\n"
247             + "log(safeTagName(tw.nextNode()));\n"
248             + "log(safeTagName(tw.nextNode()));\n"
249             + "log(safeTagName(tw.nextNode()));\n"
250             + "log(safeTagName(tw.currentNode));\n";
251 
252         test(script);
253     }
254 
255     /**
256      * @throws Exception if the test fails
257      */
258     @Test
259     @Alerts({"P", "A", "SPAN", "DIV", "BODY", "undefined", "BODY"})
260     public void previous() throws Exception {
261         final String script =
262             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
263             + "tw.currentNode = document.getElementById('theP');\n"
264             + "log(safeTagName(tw.currentNode));\n"
265             + "log(safeTagName(tw.previousNode()));\n"
266             + "log(safeTagName(tw.previousNode()));\n"
267             + "log(safeTagName(tw.previousNode()));\n"
268             + "log(safeTagName(tw.previousNode()));\n"
269             + "log(safeTagName(tw.previousNode()));\n"
270             + "log(safeTagName(tw.currentNode));\n";
271 
272         test(script);
273     }
274 
275     /**
276      * @throws Exception if the test fails
277      */
278     @Test
279     @Alerts({"DIV", "SPAN", "A", "undefined", "P", "BODY", "undefined", "SPAN", "undefined",
280                 "P", "SPAN", "CODE", "PRE", "undefined"})
281     public void walking() throws Exception {
282         final String script = "var tw = document.createTreeWalker(document.body, 1, null, true);\n"
283             + "log(safeTagName(tw.firstChild()));\n"
284             + "log(safeTagName(tw.firstChild()));\n"
285             + "log(safeTagName(tw.lastChild()));\n"
286             + "log(safeTagName(tw.lastChild()));\n"
287             + "log(safeTagName(tw.nextNode()));\n"
288             + "log(safeTagName(tw.parentNode()));\n"
289             + "log(safeTagName(tw.parentNode()));\n"
290             + "log(safeTagName(tw.lastChild()));\n"
291             + "log(safeTagName(tw.nextSibling()));\n"
292             + "log(safeTagName(tw.previousSibling()));\n"
293             + "log(safeTagName(tw.nextSibling()));\n"
294             + "log(safeTagName(tw.nextNode()));\n"
295             + "log(safeTagName(tw.nextNode()));\n"
296             + "log(safeTagName(tw.nextNode()));\n";
297 
298         test2(script);
299     }
300 
301     /**
302      * @throws Exception if the test fails
303      */
304     @Test
305     @Alerts({"TITLE", "SCRIPT", "HEAD", "HTML", "HEAD", "BODY", "undefined"})
306     public void walkingOutsideTheRoot() throws Exception {
307         final String script =
308             "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, null, true);\n"
309             + "tw.currentNode = document.firstChild.firstChild;\n"
310             + "log(safeTagName(tw.firstChild()));\n"
311             + "log(safeTagName(tw.nextNode()));\n"
312             + "log(safeTagName(tw.parentNode()));\n"
313             + "log(safeTagName(tw.previousNode()));\n"
314             + "log(safeTagName(tw.firstChild()));\n"
315             + "log(safeTagName(tw.nextSibling()));\n"
316             + "log(safeTagName(tw.previousSibling()));\n";
317 
318         test2(script);
319     }
320 
321     /**
322      * @throws Exception if the test fails
323      */
324     @Test
325     @Alerts("TypeError")
326     public void nullRoot() throws Exception {
327         final String script = "try {\n"
328             + "var tw = document.createTreeWalker(null, NodeFilter.SHOW_ELEMENT, null, true);\n"
329             + "} catch(e) { logEx(e); }\n";
330 
331         test2(script);
332     }
333 
334     /**
335      * @throws Exception if the test fails
336      */
337     @Test
338     @Alerts({"TITLE", "undefined", "HEAD", "HTML", "HEAD", "BODY", "undefined"})
339     public void simpleFilter() throws Exception {
340         final String script = "var noScripts = {\n"
341             + "  acceptNode: function(node) {\n"
342             + "    if (node.tagName == 'SCRIPT')\n"
343             + "      return NodeFilter.FILTER_REJECT;\n"
344             // using number rather that object field causes Rhino to pass a Double
345             + "    return 1; // NodeFilter.FILTER_ACCEPT \n"
346             + "  }\n"
347             + "}\n"
348             + "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, noScripts, true);\n"
349             + "tw.currentNode = document.firstChild.firstChild;\n"
350             + "log(safeTagName(tw.firstChild()));\n"
351             + "log(safeTagName(tw.nextSibling()));\n"
352             + "log(safeTagName(tw.parentNode()));\n"
353             + "log(safeTagName(tw.previousNode()));\n"
354             + "log(safeTagName(tw.firstChild()));\n"
355             + "log(safeTagName(tw.nextSibling()));\n"
356             + "log(safeTagName(tw.previousSibling()));\n";
357 
358         test2(script);
359     }
360 
361     /**
362      * @throws Exception if the test fails
363      */
364     @Test
365     @Alerts({"TITLE", "undefined", "HEAD", "HTML", "HEAD", "BODY", "undefined"})
366     public void simpleFilter_asAFunction() throws Exception {
367         final String script = "var noScripts = function(node) {\n"
368             + "  if (node.tagName == 'SCRIPT')\n"
369             + "    return NodeFilter.FILTER_REJECT;\n"
370             + "  return 1;\n"
371             + "}\n"
372             + "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, noScripts, true);\n"
373             + "tw.currentNode = document.firstChild.firstChild;\n"
374             + "log(safeTagName(tw.firstChild()));\n"
375             + "log(safeTagName(tw.nextSibling()));\n"
376             + "log(safeTagName(tw.parentNode()));\n"
377             + "log(safeTagName(tw.previousNode()));\n"
378             + "log(safeTagName(tw.firstChild()));\n"
379             + "log(safeTagName(tw.nextSibling()));\n"
380             + "log(safeTagName(tw.previousSibling()));\n";
381 
382         test2(script);
383     }
384 
385     /**
386      * @throws Exception if the test fails
387      */
388     @Test
389     @Alerts("TypeError")
390     public void emptyFilter() throws Exception {
391         final String script = "try {\n"
392             + "var tw = document.createTreeWalker(null, NodeFilter.SHOW_ELEMENT, {}, true);\n"
393             + "} catch(e) { logEx(e); }\n";
394 
395         test2(script);
396     }
397 
398     /**
399      * @throws Exception if the test fails
400      */
401     @Test
402     @Alerts({"P", "undefined"})
403     public void secondFilterReject() throws Exception {
404         final String script = ""
405             + "var noScripts = {\n"
406             + "  acceptNode: function(node) {\n"
407             + "    if (node.tagName == 'SPAN' || node.tagName == 'DIV') {\n"
408             + "      return NodeFilter.FILTER_REJECT;\n"
409             + "    }\n"
410             + "    return NodeFilter.FILTER_ACCEPT;\n"
411             + "  }\n"
412             + "}\n"
413             + "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, noScripts, true);\n"
414             + "log(safeTagName(tw.firstChild()));\n"
415             + "log(safeTagName(tw.nextSibling()));\n";
416 
417         test2(script);
418     }
419 
420     /**
421      * @throws Exception if the test fails
422      */
423     @Test
424     @Alerts({"A", "P", "CODE", "PRE", "undefined"})
425     public void secondFilterSkip() throws Exception {
426         final String script = "var noScripts = {acceptNode: function(node) {if (node.tagName == 'SPAN' ||"
427             + "node.tagName == 'DIV') return NodeFilter.FILTER_SKIP;"
428             + "return NodeFilter.FILTER_ACCEPT}};\n"
429             + "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, noScripts, true);\n"
430             + "log(safeTagName(tw.firstChild()));\n"
431             + "log(safeTagName(tw.nextSibling()));\n"
432             + "log(safeTagName(tw.nextSibling()));\n"
433             + "log(safeTagName(tw.nextSibling()));\n"
434             + "log(safeTagName(tw.nextSibling()));\n";
435 
436         test2(script);
437     }
438 
439     /**
440      * @throws Exception if the test fails
441      */
442     @Test
443     @Alerts({"P", "undefined"})
444     public void secondFilterRejectReverse() throws Exception {
445         final String script = "var noScripts = {acceptNode: function(node) {if (node.tagName == 'SPAN' ||"
446             + "node.tagName == 'DIV') return NodeFilter.FILTER_REJECT;"
447             + "return NodeFilter.FILTER_ACCEPT}};\n"
448             + "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, noScripts, true);\n"
449             + "log(safeTagName(tw.lastChild()));\n"
450             + "log(safeTagName(tw.previousSibling()));\n";
451 
452         test2(script);
453     }
454 
455     /**
456      * @throws Exception if the test fails
457      */
458     @Test
459     @Alerts({"PRE", "CODE", "P", "A", "undefined"})
460     public void secondFilterSkipReverse() throws Exception {
461         final String script = "var noScripts = {acceptNode: function(node) {if (node.tagName == 'SPAN' ||"
462             + "node.tagName == 'DIV') return NodeFilter.FILTER_SKIP; return NodeFilter.FILTER_ACCEPT}};\n"
463             + "var tw = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, noScripts, true);\n"
464             + "log(safeTagName(tw.lastChild()));\n"
465             + "log(safeTagName(tw.previousSibling()));\n"
466             + "log(safeTagName(tw.previousSibling()));\n"
467             + "log(safeTagName(tw.previousSibling()));\n"
468             + "log(safeTagName(tw.previousSibling()));";
469 
470         test2(script);
471     }
472 }