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