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 Range}.
25   *
26   * @author Marc Guillemot
27   * @author Ahmed Ashour
28   * @author James Phillpotts
29   * @author Frank Danek
30   * @author Ronald Brill
31   */
32  @RunWith(BrowserRunner.class)
33  public class RangeTest extends WebDriverTestCase {
34  
35      private static final String CONTENT_START = DOCTYPE_HTML
36          + "<html><head><title></title>\n"
37          + "<script>\n"
38          + LOG_TITLE_FUNCTION
39          + "function safeTagName(o) {\n"
40          + "  return o ? (o.tagName ? o.tagName : o) : undefined;\n"
41          + "}\n"
42          + "function alertRange(r) {\n"
43          + "  log(r.collapsed);\n"
44          + "  log(safeTagName(r.commonAncestorContainer));\n"
45          + "  log(safeTagName(r.startContainer));\n"
46          + "  log(r.startOffset);\n"
47          + "  log(safeTagName(r.endContainer));\n"
48          + "  log(r.endOffset);\n"
49          + "}\n"
50          + "function test() {\n"
51          + "  var r = document.createRange();\n";
52  
53      private static final String CONTENT_END = "\n}\n</script></head>\n"
54          + "<body onload='test()'>\n"
55          + "<div id='theDiv'>Hello, <span id='theSpan'>this is a test for"
56          + "<a id='theA' href='http://htmlunit.sf.net'>HtmlUnit</a> support"
57          + "</div>\n"
58          + "<p id='theP'>for Range</p>\n"
59          + "</body></html>";
60  
61      /**
62       * @throws Exception if the test fails
63       */
64      @Test
65      @Alerts({"true", "[object HTMLDocument]", "[object HTMLDocument]", "0", "[object HTMLDocument]", "0"})
66      public void emptyRange() throws Exception {
67          loadPageVerifyTitle2(CONTENT_START + "alertRange(r);" + CONTENT_END);
68      }
69  
70      /**
71       * @throws Exception if the test fails
72       */
73      @Test
74      @Alerts({"false", "BODY", "BODY", "1", "BODY", "2"})
75      public void selectNode() throws Exception {
76          final String script = "r.selectNode(document.getElementById('theDiv'));"
77              + "alertRange(r);";
78  
79          loadPageVerifyTitle2(CONTENT_START + script + CONTENT_END);
80      }
81  
82      /**
83       * @throws Exception if the test fails
84       */
85      @Test
86      @Alerts({"false", "DIV", "DIV", "0", "DIV", "2"})
87      public void selectNodeContents() throws Exception {
88          final String script = "r.selectNodeContents(document.getElementById('theDiv'));"
89              + "alertRange(r);";
90  
91          loadPageVerifyTitle2(CONTENT_START + script + CONTENT_END);
92      }
93  
94      /**
95       * @throws Exception if the test fails
96       */
97      @Test
98      @Alerts("<div id=\"myDiv2\"></div><div>harhar</div><div id=\"myDiv3\"></div>")
99      public void createContextualFragment() throws Exception {
100         final String html = DOCTYPE_HTML
101             + "<html><head>\n"
102             + "<script>\n"
103             + LOG_TITLE_FUNCTION
104             + "  function test() {\n"
105             + "    var element = document.getElementById('myDiv2');\n"
106             + "    var range = element.ownerDocument.createRange();\n"
107             + "    range.setStartAfter(element);\n"
108             + "    var fragment = range.createContextualFragment('<div>harhar</div>');\n"
109             + "    element.parentNode.insertBefore(fragment, element.nextSibling);\n"
110             + "    log(element.parentNode.innerHTML);\n"
111             + "  }\n"
112             + "</script></head><body onload='test()'>\n"
113             + "  <div id='myDiv'><div id='myDiv2'></div><div id='myDiv3'></div></div>\n"
114             + "</body></html>";
115 
116         loadPageVerifyTitle2(html);
117     }
118 
119     /**
120      * Same fragment should be parsed differently depending on the context.
121      * @throws Exception if the test fails
122      */
123     @Test
124     @Alerts({"[object Text]", "[object HTMLTableRowElement]"})
125     public void createContextualFragment2() throws Exception {
126         final String html = DOCTYPE_HTML
127             + "<html><body>\n"
128             + "<div id ='d'></div>\n"
129             + "<table><tr id='t'><td>old</td></tr></table>\n"
130             + "<script>\n"
131             + LOG_TITLE_FUNCTION
132             + "function test(id) {\n"
133             + "  var element = document.getElementById(id);\n"
134             + "  var range = element.ownerDocument.createRange();\n"
135             + "  range.selectNode(element);\n"
136             + "  var str = '<tr>  <td>new</td></tr>';\n" // space between <tr> and <td> is important!
137             + "  var fragment = range.createContextualFragment(str);\n"
138             + "  log(fragment.firstChild);\n"
139             + "}\n"
140             + "try {\n"
141             + "  test('d');\n"
142             + "  test('t');\n"
143             + "} catch(e) { logEx(e); }\n"
144             + "</script>\n"
145             + "</body></html>";
146 
147         loadPageVerifyTitle2(html);
148     }
149 
150     /**
151      * @throws Exception if the test fails
152      */
153     @Test
154     @Alerts("<div id=\"myDiv2\"></div>hello:<div id=\"myDiv3\"></div>")
155     public void createContextualStrangeCode() throws Exception {
156         final String html = DOCTYPE_HTML
157             + "<html><head>\n"
158             + "<script>\n"
159             + LOG_TITLE_FUNCTION
160             + "  function test() {\n"
161             + "    var element = document.getElementById('myDiv2');\n"
162             + "    var range = element.ownerDocument.createRange();\n"
163             + "    range.setStartAfter(element);\n"
164             + "    var fragment = range.createContextualFragment('hello:<world');\n"
165             + "    element.parentNode.insertBefore(fragment, element.nextSibling);\n"
166             + "    log(element.parentNode.innerHTML);\n"
167             + "  }\n"
168             + "</script></head><body onload='test()'>\n"
169             + "  <div id='myDiv'><div id='myDiv2'></div><div id='myDiv3'></div></div>\n"
170             + "</body></html>";
171 
172         loadPageVerifyTitle2(html);
173     }
174 
175     /**
176      * @throws Exception if the test fails
177      */
178     @Test
179     @Alerts({"qwerty", "tyxy", "[object DocumentFragment]", "[object HTMLSpanElement] [object Text]", "qwer",
180              "[object HTMLSpanElement]"})
181     public void extractContents() throws Exception {
182         final String html = DOCTYPE_HTML
183             + "<html><body><div id='d'>abc<span id='s'>qwerty</span>xyz</div>\n"
184             + "<script>\n"
185             + LOG_TITLE_FUNCTION
186             + "  var d = document.getElementById('d');\n"
187             + "  var s = document.getElementById('s');\n"
188             + "  var r = document.createRange();\n"
189             + "  r.setStart(s.firstChild, 4);\n"
190             + "  r.setEnd(d.childNodes[2], 2);\n"
191             + "  log(s.innerHTML);\n"
192             + "  log(r);\n"
193             + "  var fragment = r.extractContents();\n"
194             + "  log(fragment);\n"
195             + "  log(fragment.childNodes[0] + ' ' + fragment.childNodes[1]);\n"
196             + "  log(s.innerHTML);\n"
197             + "  log(document.getElementById('s'));\n"
198             + "</script></body></html>";
199         loadPageVerifyTitle2(html);
200     }
201 
202     /**
203      * @throws Exception if the test fails
204      */
205     @Test
206     @Alerts({"1 <p><b id=\"b\">text1<span id=\"s\">inner</span>text2</b></p>",
207              "2 text1",
208              "3 [object DocumentFragment]",
209              "4 1: [object HTMLParagraphElement]: <b id=\"b\">text1</b>",
210              "5 <p><b id=\"b\"><span id=\"s\">inner</span>text2</b></p>",
211              "6 1: [object HTMLParagraphElement]: <b id=\"b\"><span id=\"s\"></span>text2</b>",
212              "7 <p><b id=\"b\"><span id=\"s\">inner</span></b></p>"})
213     public void extractContents2() throws Exception {
214         final String html = DOCTYPE_HTML
215             + "<html><body><div id='d'><p><b id='b'>text1<span id='s'>inner</span>text2</b></p></div>\n"
216             + "<script>\n"
217             + LOG_TITLE_FUNCTION
218             + "  var d = document.getElementById('d');\n"
219             + "  var b = document.getElementById('b');\n"
220             + "  var s = document.getElementById('s');\n"
221             + "  var r = document.createRange();\n"
222             + "  r.setStart(d, 0);\n"
223             + "  r.setEnd(b, 1);\n"
224             + "  log('1 ' + d.innerHTML);\n"
225             + "  log('2 ' + r);\n"
226             + "  var f = r.extractContents();\n"
227             + "  log('3 ' + f);\n"
228             + "  log('4 ' + f.childNodes.length + ': ' + f.childNodes[0] + ': ' + f.childNodes[0].innerHTML);\n"
229             + "  log('5 ' + d.innerHTML);\n"
230             + "  var r2 = document.createRange();\n"
231             + "  r2.setStart(s, 1);\n"
232             + "  r2.setEnd(d, 1);\n"
233             + "  var f2 = r2.extractContents();\n"
234             + "  log('6 ' + f2.childNodes.length + ': ' + f2.childNodes[0] + ': ' + f2.childNodes[0].innerHTML);\n"
235             + "  log('7 ' + d.innerHTML);\n"
236             + "</script></body></html>";
237         loadPageVerifyTitle2(html);
238     }
239 
240     /**
241      * @throws Exception if the test fails
242      */
243     @Test
244     @Alerts({"0", "1", "2", "3"})
245     public void constants() throws Exception {
246         final String html = DOCTYPE_HTML
247               + "<html><body>\n"
248               + "<script>\n"
249               + LOG_TITLE_FUNCTION
250             + "  log(Range.START_TO_START);\n"
251             + "  log(Range.START_TO_END);\n"
252             + "  log(Range.END_TO_END);\n"
253             + "  log(Range.END_TO_START);\n"
254             + "</script></body></html>";
255         loadPageVerifyTitle2(html);
256     }
257 
258     /**
259      * @throws Exception if the test fails
260      */
261     @Test
262     @Alerts({"-1", "1", "1", "-1", "0"})
263     public void compareBoundaryPoints() throws Exception {
264         final String html = DOCTYPE_HTML
265             + "<html><body>\n"
266             + "<div id='d1'><div id='d2'></div></div>\n"
267             + "<script>\n"
268             + LOG_TITLE_FUNCTION
269             + "  var range = document.createRange();\n"
270             + "  range.selectNode(document.getElementById('d1'));\n"
271             + "  var sourceRange = document.createRange();\n"
272             + "  sourceRange.selectNode(document.getElementById('d2'));\n"
273             + "  log(range.compareBoundaryPoints(Range.START_TO_START, sourceRange));\n"
274             + "  log(range.compareBoundaryPoints(Range.START_TO_END, sourceRange));\n"
275             + "  log(range.compareBoundaryPoints(Range.END_TO_END, sourceRange));\n"
276             + "  log(range.compareBoundaryPoints(Range.END_TO_START, sourceRange));\n"
277             + "  log(range.compareBoundaryPoints(Range.START_TO_START, range));\n"
278             + "</script></body></html>";
279         loadPageVerifyTitle2(html);
280     }
281 
282     /**
283      * @throws Exception if an error occurs
284      */
285     @Test
286     @Alerts({"abcd", "bc", "null", "null", "ad", "bc"})
287     public void extractContents3() throws Exception {
288         final String html = DOCTYPE_HTML
289             + "<html><body><div id='d'><span id='a'>a</span><span id='b'>b</span>"
290             + "<span id='c'>c</span><span id='d'>d</span></div>\n"
291             + "<script>\n"
292             + LOG_TITLE_FUNCTION
293             + "  var d = document.getElementById('d');\n"
294             + "  var s = document.getElementById('s');\n"
295             + "  var r = document.createRange();\n"
296             + "  r.setStart(d, 1);\n"
297             + "  r.setEnd(d, 3);\n"
298             + "  log(d.textContent);\n"
299             + "  log(r.toString());\n"
300             + "  var x = r.extractContents();\n"
301             + "  log(document.getElementById('b'));\n"
302             + "  log(document.getElementById('c'));\n"
303             + "  log(d.textContent);\n"
304             + "  log(x.textContent);\n"
305             + "</script></body></html>";
306         loadPageVerifyTitle2(html);
307     }
308 
309     /**
310      * @throws Exception if an error occurs
311      */
312     @Test
313     @Alerts({"qwerty", "tyxy", "[object DocumentFragment]", "[object HTMLSpanElement] [object Text]",
314              "qwerty", "[object HTMLSpanElement]"})
315     public void cloneContents() throws Exception {
316         final String html = DOCTYPE_HTML
317             + "<html><body><div id='d'>abc<span id='s'>qwerty</span>xyz</div>\n"
318             + "<script>\n"
319             + LOG_TITLE_FUNCTION
320             + "  var d = document.getElementById('d');\n"
321             + "  var s = document.getElementById('s');\n"
322             + "  var r = document.createRange();\n"
323             + "  r.setStart(s.firstChild, 4);\n"
324             + "  r.setEnd(d.childNodes[2], 2);\n"
325             + "  log(s.innerHTML);\n"
326             + "  log(r);\n"
327             + "  var fragment = r.cloneContents();\n"
328             + "  log(fragment);\n"
329             + "  log(fragment.childNodes[0] + ' ' + fragment.childNodes[1]);\n"
330             + "  log(s.innerHTML);\n"
331             + "  log(document.getElementById('s'));\n"
332             + "</script></body></html>";
333         loadPageVerifyTitle2(html);
334     }
335 
336     /**
337      * @throws Exception if an error occurs
338      */
339     @Test
340     @Alerts({"qwerty", "bcqwertyxy", "null", "az"})
341     public void deleteContents() throws Exception {
342         final String html = DOCTYPE_HTML
343             + "<html><body><div id='d'>abc<span id='s'>qwerty</span>xyz</div>\n"
344             + "<script>\n"
345             + LOG_TITLE_FUNCTION
346             + "  var d = document.getElementById('d');\n"
347             + "  var s = document.getElementById('s');\n"
348             + "  var r = document.createRange();\n"
349             + "  r.setStart(d.firstChild, 1);\n"
350             + "  r.setEnd(d.childNodes[2], 2);\n"
351             + "  log(s.innerHTML);\n"
352             + "  log(r.toString());\n"
353             + "  r.deleteContents();\n"
354             + "  log(document.getElementById('s'));\n"
355             + "  log(d.textContent);\n"
356             + "</script></body></html>";
357         loadPageVerifyTitle2(html);
358     }
359 
360     /**
361      * @throws Exception if an error occurs
362      */
363     @Test
364     @Alerts({"abcd", "bc", "null", "null", "ad"})
365     public void deleteContents2() throws Exception {
366         final String html = DOCTYPE_HTML
367             + "<html><body><div id='d'><span id='a'>a</span><span id='b'>b</span><span id='c'>c</span>"
368             + "<span id='d'>d</span></div>\n"
369             + "<script>\n"
370             + LOG_TITLE_FUNCTION
371             + "  var d = document.getElementById('d');\n"
372             + "  var s = document.getElementById('s');\n"
373             + "  var r = document.createRange();\n"
374             + "  r.setStart(d, 1);\n"
375             + "  r.setEnd(d, 3);\n"
376             + "  log(d.textContent);\n"
377             + "  log(r.toString());\n"
378             + "  r.deleteContents();\n"
379             + "  log(document.getElementById('b'));\n"
380             + "  log(document.getElementById('c'));\n"
381             + "  log(d.textContent);\n"
382             + "</script></body></html>";
383         loadPageVerifyTitle2(html);
384     }
385 
386     /**
387      * @throws Exception if an error occurs
388      */
389     @Test
390     @Alerts("0")
391     public void getClientRectsEmpty() throws Exception {
392         final String html = DOCTYPE_HTML
393             + "<html>\n"
394             + "<body>\n"
395             + "  <div id='d'>a</div>\n"
396             + "<script>\n"
397             + LOG_TITLE_FUNCTION
398             + "  var d = document.getElementById('d');\n"
399             + "  var r = document.createRange();\n"
400             + "  log(r.getClientRects().length);\n"
401             + "</script>\n"
402             + "</body>\n"
403             + "</html>\n";
404         loadPageVerifyTitle2(html);
405     }
406 
407     /**
408      * @throws Exception if an error occurs
409      */
410     @Test
411     @Alerts("true")
412     public void getClientRectsMany() throws Exception {
413         final String html = DOCTYPE_HTML
414             + "<html><body><div id='d'><span id='a'>a</span><span id='b'>b</span><span id='c'>c</span>"
415             + "<span id='d'>d</span></div>\n"
416             + "<script>\n"
417             + LOG_TITLE_FUNCTION
418             + "  var d = document.getElementById('d');\n"
419             + "  var s = document.getElementById('s');\n"
420             + "  var r = document.createRange();\n"
421             + "  r.setStart(d, 1);\n"
422             + "  r.setEnd(d, 3);\n"
423             + "  log(r.getClientRects().length > 1);\n"
424             + "</script></body></html>";
425         loadPageVerifyTitle2(html);
426     }
427 
428     /**
429      * Test for a regression, getBoundingClientRect has detached
430      * all elements of the range from the document.
431      * @throws Exception if the test fails
432      */
433     @Test
434     @Alerts("[object HTMLBodyElement]")
435     public void getBoundingClientRectDoesNotChangeTheParent() throws Exception {
436         final String html = DOCTYPE_HTML
437             + "<html><head>\n"
438             + "<script>\n"
439             + LOG_TITLE_FUNCTION
440             + "function doTest() {\n"
441             + "  var range = document.createRange();\n"
442 
443             + "  var elem = document.createElement('boundtest');\n"
444             + "  document.body.appendChild(elem);\n"
445 
446             + "  range.selectNode(elem);\n"
447             + "  range.getBoundingClientRect();\n"
448 
449             + "  log(elem.parentNode);\n"
450             + "}\n"
451             + "</script>\n"
452             + "</head>\n"
453             + "<body onload='doTest()'>\n"
454             + "</body></html>";
455 
456         loadPageVerifyTitle2(html);
457     }
458 
459     /**
460      * Test for a regression, getClientRects has detached
461      * all elements of the range from the document.
462      * @throws Exception if the test fails
463      */
464     @Test
465     @Alerts("[object HTMLBodyElement]")
466     public void getClientRectsDoesNotChangeTheParent() throws Exception {
467         final String html = DOCTYPE_HTML
468             + "<html><head>\n"
469             + "<script>\n"
470             + LOG_TITLE_FUNCTION
471             + "function doTest() {\n"
472             + "  var range = document.createRange();\n"
473 
474             + "  var elem = document.createElement('boundtest');\n"
475             + "  document.body.appendChild(elem);\n"
476 
477             + "  range.selectNode(elem);\n"
478             + "  range.getClientRects();\n"
479 
480             + "  log(elem.parentNode);\n"
481             + "}\n"
482             + "</script>\n"
483             + "</head>\n"
484             + "<body onload='doTest()'>\n"
485             + "</body></html>";
486 
487         loadPageVerifyTitle2(html);
488     }
489 
490     /**
491      * @throws Exception if the test fails
492      */
493     @Test
494     @Alerts({"tyxy", "tyxy", "tyxy"})
495     public void testToString() throws Exception {
496         final String html = DOCTYPE_HTML
497               + "<html><body><div id='d'>abc<span id='s'>qwerty</span>xyz</div>\n"
498               + "<script>\n"
499               + LOG_TITLE_FUNCTION
500               + "  var d = document.getElementById('d');\n"
501               + "  var s = document.getElementById('s');\n"
502               + "  var r = document.createRange();\n"
503               + "  r.setStart(s.firstChild, 4);\n"
504               + "  r.setEnd(d.childNodes[2], 2);\n"
505               + "  log(r);\n"
506               + "  log('' + r);\n"
507               + "  log(r.toString());\n"
508               + "</script></body></html>";
509         loadPageVerifyTitle2(html);
510     }
511 }