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.htmlunit.junit.annotation.HtmlUnitNYI;
21  import org.junit.Test;
22  import org.junit.runner.RunWith;
23  
24  /**
25   * Unit tests for {@link Selection}.
26   *
27   * @author Daniel Gredler
28   * @author Marc Guillemot
29   * @author Ronald Brill
30   */
31  @RunWith(BrowserRunner.class)
32  public class SelectionTest extends WebDriverTestCase {
33  
34      /**
35       * @throws Exception if an error occurs
36       */
37      @Test
38      @Alerts("true")
39      public void equality_selection() throws Exception {
40          final String html = DOCTYPE_HTML
41                  + "<html>\n"
42                  + "<body>\n"
43                  + "<script>"
44                  + LOG_TITLE_FUNCTION
45                  + "log(document.selection==document.selection);\n"
46                  + "</script>\n"
47                  + "</body></html>";
48          loadPageVerifyTitle2(html);
49      }
50  
51      /**
52       * @throws Exception if an error occurs
53       */
54      @Test
55      @Alerts("true")
56      public void equality_getSelection() throws Exception {
57          final String html = DOCTYPE_HTML
58              + "<html><body>\n"
59              + "<script>\n"
60              + LOG_TITLE_FUNCTION
61              + "try {\n"
62              + "  log(window.getSelection()==window.getSelection());\n"
63              + "} catch(e) { logEx(e) }\n"
64              + "</script>\n"
65              + "</body></html>";
66          loadPageVerifyTitle2(html);
67      }
68  
69      /**
70       * @throws Exception if an error occurs
71       */
72      @Test
73      @Alerts({"0", "0", "0", "cdefg"})
74      public void inputSelectionsAreIndependent() throws Exception {
75          final String html = DOCTYPE_HTML
76              + "<html><body onload='test()'>\n"
77              + "<input id='i' value='abcdefghi'/>\n"
78              + "<script>\n"
79              + LOG_TITLE_FUNCTION
80              + "  function test() {\n"
81              + "    var i = document.getElementById('i');\n"
82              + "    var s = window.getSelection();\n"
83              + "    log(s.rangeCount);\n"
84              + "    i.selectionStart = 2;\n"
85              + "    log(s.rangeCount);\n"
86              + "    i.selectionEnd = 7;\n"
87              + "    log(s.rangeCount);\n"
88              + "    log(i.value.substring(i.selectionStart, i.selectionEnd));\n"
89              + "  }\n"
90              + "</script>\n"
91              + "</body></html>";
92          loadPageVerifyTitle2(html);
93      }
94  
95      /**
96       * @throws Exception if an error occurs
97       */
98      @Test
99      @Alerts({"1:null/0/null/0/true/None/0/",
100              "2:s2/0/s2/1/false/Range/1/xyz[xyz"})
101     public void selectAllChildren() throws Exception {
102         final String jsSnippet = ""
103             + "    alertSelection(selection);\n"
104             + "    selection.selectAllChildren(s2);\n"
105             + "    alertSelection(selection);\n";
106 
107         tester(jsSnippet);
108     }
109 
110     /**
111      * @throws Exception if an error occurs
112      */
113     @Test
114     @Alerts({"1:s2/0/s2/1/false/Range/1/xyz[xyz",
115              "2:s2/0/s3/1/false/Range/1/xyzfoo[xyzfoo",
116              "3:s2/0/s3/2/false/Range/1/xyzfoo---[xyzfoo---",
117              "4:s2/0/s3/3/false/Range/1/xyzfoo---foo[xyzfoo---foo"})
118     public void extend() throws Exception {
119         final String jsSnippet = ""
120             + "    selection.selectAllChildren(s2);\n"
121             + "    alertSelection(selection);\n"
122             + "    if (selection.extend) {\n"
123             + "      selection.extend(s3, 1);\n"
124             + "      alertSelection(selection);\n"
125             + "      selection.extend(s3, 2);\n"
126             + "      alertSelection(selection);\n"
127             + "      selection.extend(s3, 3);\n"
128             + "      alertSelection(selection);\n"
129             + "    } else { log('selection.extend not available'); }\n";
130 
131         tester(jsSnippet);
132     }
133 
134     /**
135      * @throws Exception if an error occurs
136      */
137     @Test
138     @Alerts({"1:s2/0/s2/1/false/Range/1/xyz[xyz",
139              "2:s2/0/s2/0/true/Caret/1/["})
140     public void collapseToStart() throws Exception {
141         final String jsSnippet = ""
142             + "    selection.selectAllChildren(s2);\n"
143             + "    alertSelection(selection);\n"
144             + "    selection.collapseToStart();\n"
145             + "    alertSelection(selection);\n";
146 
147         tester(jsSnippet);
148     }
149 
150     /**
151      * @throws Exception if an error occurs
152      */
153     @Test
154     @Alerts({"1:s2/0/s2/1/false/Range/1/xyz[xyz",
155              "2:s2/1/s2/1/true/Caret/1/["})
156     public void collapseToEnd() throws Exception {
157         final String jsSnippet = ""
158             + "    selection.selectAllChildren(s2);\n"
159             + "    alertSelection(selection);\n"
160             + "    selection.collapseToEnd();\n"
161             + "    alertSelection(selection);\n";
162 
163         tester(jsSnippet);
164     }
165 
166     /**
167      * @throws Exception if an error occurs
168      */
169     @Test
170     @Alerts({"1:s2/0/s2/1/false/Range/1/xyz[xyz",
171              "2:null/0/null/0/true/None/0/"})
172     public void empty() throws Exception {
173         final String jsSnippet = ""
174             + "    selection.selectAllChildren(s2);\n"
175             + "    alertSelection(selection);\n"
176             + "    selection.empty();\n"
177             + "    alertSelection(selection);\n";
178 
179         tester(jsSnippet);
180     }
181 
182     /**
183      * @throws Exception if an error occurs
184      */
185     @Test
186     @Alerts({"1:null/0/null/0/true/None/0/",
187              "2:null/0/null/0/true/None/0/",
188              "3:s2/1/s3/1/false/Range/1/foo[foo"})
189     public void addRange() throws Exception {
190         final String jsSnippet = ""
191             + "      alertSelection(selection);\n"
192 
193             + "      var range = document.createRange();\n"
194             + "      range.setStart(s2, 1);\n"
195             + "      range.setEnd(s3, 1);\n"
196             + "      alertSelection(selection);\n"
197 
198             + "      selection.addRange(range);\n"
199             + "      alertSelection(selection);\n";
200 
201         tester(jsSnippet);
202     }
203 
204     /**
205      * @throws Exception if an error occurs
206      */
207     @Test
208     @Alerts({"1:null/0/null/0/true/None/0/",
209              "2:s1/1/s3/1/false/Range/1/xyzfoo[xyzfoo",
210              "3:null/0/null/0/true/None/0/"})
211     public void removeAllRanges() throws Exception {
212         final String jsSnippet = ""
213             + "      alertSelection(selection);\n"
214 
215             + "      var range = document.createRange();\n"
216             + "      range.setStart(s1, 1);\n"
217             + "      range.setEnd(s3, 1);\n"
218             + "      selection.addRange(range);\n"
219             + "      alertSelection(selection);\n"
220 
221             + "      selection.removeAllRanges();\n"
222             + "      alertSelection(selection);\n";
223 
224         tester(jsSnippet);
225     }
226 
227     /**
228      * @throws Exception if an error occurs
229      */
230     @Test
231     @Alerts({"1:s1/1/s3/1/false/Range/1/xyzfoo[xyzfoo",
232              "2:null/0/null/0/true/None/0/"})
233     public void removeAllRanges2() throws Exception {
234         final String jsSnippet = ""
235             + "      var range = document.createRange();\n"
236             + "      range.setStart(s1, 1);\n"
237             + "      range.setEnd(s3, 1);\n"
238             + "      selection.addRange(range);\n"
239             + "      alertSelection(selection);\n"
240 
241             + "      selection.removeAllRanges();\n"
242             + "      alertSelection(selection);\n";
243 
244         tester(jsSnippet);
245     }
246 
247     /**
248      * @throws Exception if an error occurs
249      */
250     @Test
251     @Alerts(DEFAULT = {"1:null/0/null/0/true/None/0/",
252                        "2:s1/0/s1/1/false/Range/1/abc[abc",
253                        "3:null/0/null/0/true/None/0/"},
254             FF = {"1:null/0/null/0/true/None/0/",
255                   "2:s1/1/s3/1/false/Range/2/abcxyzfoo[abc[xyzfoo",
256                   "3:null/0/null/0/true/None/0/"},
257             FF_ESR = {"1:null/0/null/0/true/None/0/",
258                       "2:s1/1/s3/1/false/Range/2/abcxyzfoo[abc[xyzfoo",
259                       "3:null/0/null/0/true/None/0/"})
260     @HtmlUnitNYI(CHROME = {"1:null/0/null/0/true/None/0/",
261                            "2:s1/0/s1/1/false/Range/2/abcxyzfoo[abc[xyzfoo",
262                            "3:null/0/null/0/true/None/0/"},
263             EDGE = {"1:null/0/null/0/true/None/0/",
264                     "2:s1/0/s1/1/false/Range/2/abcxyzfoo[abc[xyzfoo",
265                     "3:null/0/null/0/true/None/0/"},
266             FF = {"1:null/0/null/0/true/None/0/",
267                   "2:s1/0/s1/1/false/Range/2/abcxyzfoo[abc[xyzfoo",
268                   "3:null/0/null/0/true/None/0/"},
269             FF_ESR = {"1:null/0/null/0/true/None/0/",
270                       "2:s1/0/s1/1/false/Range/2/abcxyzfoo[abc[xyzfoo",
271                       "3:null/0/null/0/true/None/0/"})
272     public void selectAllChildrenAddRange() throws Exception {
273         final String jsSnippet = ""
274             + "      alertSelection(selection);\n"
275 
276             + "      selection.selectAllChildren(s1);\n"
277             + "      var range = document.createRange();\n"
278             + "      range.setStart(s1, 1);\n"
279             + "      range.setEnd(s3, 1);\n"
280             + "      selection.addRange(range);\n"
281             + "      alertSelection(selection);\n"
282 
283             + "      selection.removeAllRanges();\n"
284             + "      alertSelection(selection);\n";
285 
286         tester(jsSnippet);
287     }
288 
289     /**
290      * @throws Exception if an error occurs
291      */
292     @Test
293     @Alerts({"1:null/0/null/0/true/None/0/",
294              "2:s1/0/s1/1/false/Range/1/abc[abc",
295              "3:null/0/null/0/true/None/0/"})
296     public void addRangeSelectAllChildren() throws Exception {
297         final String jsSnippet = ""
298             + "      alertSelection(selection);\n"
299 
300             + "      var range = document.createRange();\n"
301             + "      range.setStart(s1, 1);\n"
302             + "      range.setEnd(s3, 1);\n"
303             + "      selection.addRange(range);\n"
304             + "      selection.selectAllChildren(s1);\n"
305             + "      alertSelection(selection);\n"
306 
307             + "      selection.removeAllRanges();\n"
308             + "      alertSelection(selection);\n";
309 
310         tester(jsSnippet);
311     }
312 
313     /**
314      * @throws Exception if an error occurs
315      */
316     @Test
317     @Alerts(DEFAULT = {"1:null/0/null/0/true/None/0/",
318                        "2:s1/0/s1/1/false/Range/1/abc[abc",
319                        "3:s1/0/s1/1/false/Range/1/abc[abc",
320                        "4:null/0/null/0/true/None/0/"},
321             FF = {"1:null/0/null/0/true/None/0/",
322                   "2:s1/1/s2/1/false/Range/2/abcxyz[abc[xyz",
323                   "3:s2/1/s3/3/false/Range/3/abcxyzfoo---foo[abc[xyz[foo---foo",
324                   "4:null/0/null/0/true/None/0/"},
325             FF_ESR = {"1:null/0/null/0/true/None/0/",
326                       "2:s1/1/s2/1/false/Range/2/abcxyz[abc[xyz",
327                       "3:s2/1/s3/3/false/Range/3/abcxyzfoo---foo[abc[xyz[foo---foo",
328                       "4:null/0/null/0/true/None/0/"})
329     @HtmlUnitNYI(CHROME = {"1:null/0/null/0/true/None/0/",
330                            "2:s1/0/s1/1/false/Range/2/abcxyz[abc[xyz",
331                            "3:s2/1/s3/3/false/Range/3/abcxyzfoo---foo[abc[xyz[foo---foo",
332                            "4:null/0/null/0/true/None/0/"},
333             EDGE = {"1:null/0/null/0/true/None/0/",
334                     "2:s1/0/s1/1/false/Range/2/abcxyz[abc[xyz",
335                     "3:s2/1/s3/3/false/Range/3/abcxyzfoo---foo[abc[xyz[foo---foo",
336                     "4:null/0/null/0/true/None/0/"},
337             FF = {"1:null/0/null/0/true/None/0/",
338                   "2:s1/0/s1/1/false/Range/2/abcxyz[abc[xyz",
339                   "3:s2/1/s3/3/false/Range/3/abcxyzfoo---foo[abc[xyz[foo---foo",
340                   "4:null/0/null/0/true/None/0/"},
341             FF_ESR = {"1:null/0/null/0/true/None/0/",
342                       "2:s1/0/s1/1/false/Range/2/abcxyz[abc[xyz",
343                       "3:s2/1/s3/3/false/Range/3/abcxyzfoo---foo[abc[xyz[foo---foo",
344                       "4:null/0/null/0/true/None/0/"})
345     public void addRangeAddRange() throws Exception {
346         final String jsSnippet = ""
347             + "      alertSelection(selection);\n"
348 
349             + "      selection.selectAllChildren(s1);\n"
350             + "      var range = document.createRange();\n"
351             + "      range.setStart(s1, 1);\n"
352             + "      range.setEnd(s2, 1);\n"
353             + "      selection.addRange(range);\n"
354             + "      alertSelection(selection);\n"
355 
356             + "      var range = document.createRange();\n"
357             + "      range.setStart(s2, 1);\n"
358             + "      range.setEnd(s3, 3);\n"
359             + "      selection.addRange(range);\n"
360             + "      alertSelection(selection);\n"
361 
362             + "      selection.removeAllRanges();\n"
363             + "      alertSelection(selection);\n";
364 
365         tester(jsSnippet);
366     }
367 
368     /**
369      * Test selection's anchorNode and focusNode after call to removeRange. Surprisingly, this is
370      * not null.
371      * @throws Exception if an error occurs
372      */
373     @Test
374     @Alerts({"1:[object Text]/1/[object Text]/2/false/Range/1/yzfo[yzfo",
375              "2:null/0/null/0/true/None/0/",
376              "false", "true"})
377     public void aLittleBitOfEverything_removeRange() throws Exception {
378         final String jsSnippet = ""
379             + "    var range = document.createRange();\n"
380             + "    range.setStart(s2.firstChild, 1);\n"
381             + "    range.setEnd(s3.firstChild, 2);\n"
382             + "    selection.addRange(range);\n"
383             + "    alertSelection(selection);\n"
384             + "    selection.removeRange(range);\n"
385             + "    alertSelection(selection);\n"
386             + "    log(range.collapsed);\n"
387             + "    selection.addRange(range);\n"
388             + "    log(selection.getRangeAt(0) == selection.getRangeAt(0));\n";
389 
390         tester(jsSnippet);
391     }
392 
393     private void tester(final String jsSnippet) throws Exception {
394         final String html = DOCTYPE_HTML
395             + "<html>\n"
396             + "<body onload='test()'>\n"
397             + "  <span id='s1'>abc</span><span id='s2'>xyz</span><span id='s3'>foo<span>---</span>foo</span>\n"
398 
399             + "<script>\n"
400             + LOG_TITLE_FUNCTION
401             + "  var x = 1;\n"
402             + "  function test() {\n"
403             + "    var selection = window.getSelection();\n"
404             + "    var s1 = document.getElementById('s1');\n"
405             + "    var s2 = document.getElementById('s2');\n"
406             + "    var s3 = document.getElementById('s3');\n"
407             + "    try {\n"
408                         + jsSnippet
409             + "    } catch(e) { logEx(e); }\n"
410             + "  }\n"
411 
412             + "  function alertSelection(s) {\n"
413             + "    var anchorNode = (s.anchorNode && s.anchorNode.id ? s.anchorNode.id : s.anchorNode);\n"
414             + "    var focusNode = (s.focusNode && s.focusNode.id ? s.focusNode.id : s.focusNode);\n"
415             + "    var msg = (x++) + ':' + anchorNode + '/' + s.anchorOffset + '/' + focusNode + '/' +\n"
416             + "       s.focusOffset + '/' + s.isCollapsed + '/' + s.type + '/' + s.rangeCount + '/' + s;\n"
417             + "    for(var i = 0; i < s.rangeCount; i++) {\n"
418             + "      msg += '[' + s.getRangeAt(i);\n"
419             + "    }\n"
420             + "    log(msg);\n"
421             + "  }\n"
422             + "</script>\n"
423             + "</body></html>";
424         loadPageVerifyTitle2(html);
425     }
426 
427     /**
428      * @throws Exception if an error occurs
429      */
430     @Test
431     @Alerts(DEFAULT = {"", "null-0", "", "null-0", "", "null-0", "", "null-0"},
432             FF = {"", "null-0", "", "null-0", "null", "null"},
433             FF_ESR = {"", "null-0", "", "null-0", "null", "null"})
434     public void getSelection_display() throws Exception {
435         final String html = DOCTYPE_HTML
436             + "<html>\n"
437             + "<body onload='test()'>\n"
438             + "  <iframe id='frame1' src='about:blank'></iframe>\n"
439             + "  <iframe id='frame2' src='about:blank' style='display: none'></iframe>\n"
440             + "  <div style='display: none'>\n"
441             + "    <iframe id='frame3' src='about:blank'></iframe>\n"
442             + "  </div>\n"
443 
444             + "  <script>\n"
445             + LOG_TITLE_FUNCTION
446             + "    function sel(win) {\n"
447             + "      if (win.getSelection) {\n"
448             + "        var range = win.getSelection();\n"
449             + "        log(range);\n"
450             + "        if (range) {\n"
451             + "          log(range.anchorNode + '-' + range.rangeCount);\n"
452             + "        }\n"
453             + "      }\n"
454             + "    }\n"
455 
456             + "    function test() {\n"
457             + "      sel(window);\n"
458             + "      sel(document.getElementById('frame1').contentWindow);\n"
459             + "      sel(document.getElementById('frame2').contentWindow);\n"
460             + "      sel(document.getElementById('frame3').contentWindow);\n"
461             + "    }\n"
462             + "  </script>\n"
463             + "</body></html>";
464         loadPageVerifyTitle2(html);
465     }
466 
467     /**
468      * @throws Exception if an error occurs
469      */
470     @Test
471     @Alerts({"", "", ""})
472     public void testToString() throws Exception {
473         final String html = DOCTYPE_HTML
474             + "<html>\n"
475             + "<body onload='test()'>\n"
476             + "<input id='i' value='abcdefghi'/>\n"
477             + "<script>\n"
478             + LOG_TITLE_FUNCTION
479             + "  function test() {\n"
480             + "    var i = document.getElementById('i');\n"
481             + "    var s = window.getSelection();\n"
482             + "    log(s);\n"
483             + "    log('' + s);\n"
484             + "    log(s.toString());\n"
485             + "  }\n"
486             + "</script>\n"
487             + "</body></html>";
488         loadPageVerifyTitle2(html);
489     }
490 }