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