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.html;
16  
17  import java.io.InputStream;
18  import java.net.URL;
19  import java.util.Collections;
20  
21  import org.apache.commons.io.IOUtils;
22  import org.htmlunit.WebDriverTestCase;
23  import org.htmlunit.junit.annotation.Alerts;
24  import org.htmlunit.junit.annotation.BuggyWebDriver;
25  import org.htmlunit.junit.annotation.HtmlUnitNYI;
26  import org.htmlunit.util.MimeType;
27  import org.junit.jupiter.api.Test;
28  import org.openqa.selenium.By;
29  import org.openqa.selenium.WebDriver;
30  import org.openqa.selenium.WebElement;
31  import org.openqa.selenium.htmlunit.HtmlUnitDriver;
32  
33  /**
34   * Unit tests for {@link HtmlElement}.
35   *
36   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
37   * @author Denis N. Antonioli
38   * @author Daniel Gredler
39   * @author Ahmed Ashour
40   * @author Sudhan Moghe
41   * @author Frank Danek
42   * @author Ronald Brill
43   */
44  public class HtmlElement2Test extends WebDriverTestCase {
45  
46      /**
47       * @throws Exception if an error occurs
48       */
49      @Test
50      public void onpropertychange() throws Exception {
51          final String html = DOCTYPE_HTML
52              + "<html><head><script>\n"
53              + LOG_TITLE_FUNCTION
54              + "  function test() {\n"
55              + "    document.getElementById('input1').value = 'New Value';\n"
56              + "  }\n"
57              + "  function handler() {\n"
58              + "    log(event.propertyName);\n"
59              + "  }\n"
60              + "</script></head>\n"
61              + "<body onload='test()'>\n"
62              + "  <input id='input1' onpropertychange='handler()'>\n"
63              + "</body></html>";
64  
65          loadPageVerifyTitle2(html);
66      }
67  
68      /**
69       * @throws Exception if the test fails
70       */
71      @Test
72      @Alerts({"true", "true"})
73      public void duplicateId() throws Exception {
74          final String html = DOCTYPE_HTML
75              + "<html>\n"
76              + "<script>\n"
77              + LOG_TITLE_FUNCTION
78              + "  function test() {\n"
79              + "    var value = document.getElementById('duplicateID').innerHTML;\n"
80              + "    log(value.length > 10);\n"
81              + "    document.getElementById('duplicateID').style.display = 'block';\n"
82              + "    log(value === document.getElementById('duplicateID').innerHTML);\n"
83              + "  }\n"
84              + "</script>\n"
85              + "</head>\n"
86              + "<body onload='test()'>\n"
87              + "  <fieldset id='duplicateID'><span id='duplicateID'></span></fieldset>\n"
88              + "</body></html>";
89  
90          loadPageVerifyTitle2(html);
91      }
92  
93      /**
94       * @throws Exception if an error occurs
95       */
96      @Test
97      public void onpropertychange2() throws Exception {
98          final String html = DOCTYPE_HTML
99              + "<html><head><script>\n"
100             + LOG_TITLE_FUNCTION
101             + "  function test() {\n"
102             + "    document.getElementById('input1').value = 'New Value';\n"
103             + "  }\n"
104             + "  function handler() {\n"
105             + "    log(1);\n"
106             + "    document.getElementById('input1').dir='rtl';\n"
107             + "  }\n"
108             + "</script></head>\n"
109             + "<body onload='test()'>\n"
110             + "  <input id='input1' onpropertychange='handler()'>\n"
111             + "</body></html>";
112 
113         loadPageVerifyTitle2(html);
114     }
115 
116     /**
117      * Verifies that cloned node attributes have the same initial values, but changes can be made
118      * to the clone without affecting the original node, and that the id attribute is treated the
119      * same as all the other attributes. See bug #468.
120      * @throws Exception if an error occurs
121      */
122     @Test
123     @Alerts({"false", "true", "a", "a", "b", "b", "b", "c"})
124     public void clonedNodeAttributes() throws Exception {
125         final String html = DOCTYPE_HTML
126             + "<html><body id='a' title='b'><script>\n"
127             + LOG_TITLE_FUNCTION
128             + "var x = document.body.cloneNode(true);\n"
129             + "log(document.body == x);\n"
130             + "log(document.getElementById('a') == document.body);\n"
131             + "log(document.body.id);\n"
132             + "log(x.id);\n"
133             + "log(document.body.title);\n"
134             + "log(x.title);\n"
135             + "x.title = 'c';\n"
136             + "log(document.body.title);\n"
137             + "log(x.title);\n"
138             + "</script></body></html>";
139 
140         loadPageVerifyTitle2(html);
141     }
142 
143     /**
144      * Test attribute.text and attribute.xml added for XmlElement attributes
145      * are undefined for HtmlElement.
146      * @throws Exception if the test fails
147      */
148     @Test
149     @Alerts({"true", "undefined", "undefined"})
150     public void textAndXmlUndefined() throws Exception {
151         final String html = DOCTYPE_HTML
152             + "<html><head></head>\n"
153             + "<body>\n"
154             + "  <input type='text' id='textfield1' onfocus='log(1)'>\n"
155             + "  <script>\n"
156             + LOG_TITLE_FUNCTION
157             + "    var node = document.getElementById('textfield1');\n"
158             + "    log(node.attributes[0].nodeName.length > 0);\n"
159             + "    log(node.attributes[0].text);\n"
160             + "    log(node.attributes[0].xml);\n"
161             + "  </script>\n"
162             + "</body></html>";
163 
164         loadPageVerifyTitle2(html);
165     }
166 
167     /**
168      * @throws Exception if the test fails
169      */
170     @Test
171     @Alerts("something")
172     @BuggyWebDriver(FF = "",
173             FF_ESR = "")
174     @HtmlUnitNYI(CHROME = "initialsomething",
175             EDGE = "initialsomething",
176             FF = "initialsomething",
177             FF_ESR = "initialsomething")
178     //TODO: fails because of HTMLElement.getContentEditable doesn't detect DomElement.ATTRIBUTE_VALUE_EMPTY
179     // this could be a general attribute issue
180     public void contentEditable() throws Exception {
181         final String html = DOCTYPE_HTML
182             + "<html>\n"
183             + "<body contentEditable><p>initial</p></body>\n"
184             + "</html>";
185 
186         final WebDriver driver = loadPage2(html);
187         final WebElement body = driver.findElement(By.xpath("//body"));
188         body.clear();
189         body.sendKeys("something");
190         assertEquals(getExpectedAlerts()[0], body.getText());
191     }
192 
193     /**
194      * @throws Exception if an error occurs
195      */
196     @Test
197     // to test this manually you have to use an english keyboard layout
198     @Alerts("down: 16,0 down: 49,0 press: 33,33 up: 49,0 up: 16,0"
199             + " down: 16,0 down: 220,0 press: 124,124 up: 220,0 up: 16,0")
200     //https://github.com/SeleniumHQ/selenium/issues/639
201     @BuggyWebDriver(FF_ESR = "down: 49,0 press: 33,33 up: 49,0 down: 220,0 press: 124,124 up: 220,0",
202                 FF = "down: 49,0 press: 33,33 up: 49,0 down: 220,0 press: 124,124 up: 220,0")
203     public void shiftKeys() throws Exception {
204         final String html = DOCTYPE_HTML
205             + "<html><head><script>\n"
206             + "  function appendMessage(message) {\n"
207             + "    document.getElementById('result').innerHTML += message + ' ';\n"
208             + "  }\n"
209             + "</script></head>\n"
210             + "<body >\n"
211             + "  <input id='input1' onkeyup=\"appendMessage('up: ' + event.keyCode + ',' + event.charCode)\" "
212             + "onkeypress=\"appendMessage('press: ' + event.keyCode + ',' + event.charCode)\" "
213             + "onkeydown=\"appendMessage('down: ' + event.keyCode + ',' + event.charCode)\"><br>\n"
214             + "<p id='result'></p>\n"
215             + "</body></html>";
216 
217         final WebDriver driver = loadPage2(html);
218         final WebElement input = driver.findElement(By.id("input1"));
219         final WebElement result = driver.findElement(By.id("result"));
220         input.sendKeys("!|");
221         assertEquals(getExpectedAlerts()[0], result.getText());
222     }
223 
224     /**
225      * @throws Exception on test failure
226      */
227     @Test
228     @Alerts(DEFAULT = "[object HTMLInputElement] [object HTMLBodyElement]",
229             CHROME = "[object HTMLInputElement] onblur onfocusout [object HTMLBodyElement]",
230             EDGE = "[object HTMLInputElement] onblur onfocusout [object HTMLBodyElement]")
231     public void removeActiveElement() throws Exception {
232         final String html = DOCTYPE_HTML
233                 + "<html>\n"
234                 + "<head>\n"
235                 + "<script>\n"
236                 + "function test() {\n"
237                 + "  var elem = document.getElementById('text1');\n"
238                 + "  elem.focus();\n"
239                 + "  document.title += ' ' + document.activeElement;\n"
240                 + "  elem.parentNode.removeChild(elem);\n"
241                 + "  document.title += ' ' + document.activeElement;\n"
242                 + "}\n"
243                 + "</script>\n"
244                 + "</head>\n"
245                 + "<body onload='test()'>\n"
246                 + "<form name='form1'>\n"
247                 + "  <input id='text1' onblur='document.title += \" onblur\"' "
248                         + "onfocusout='document.title += \" onfocusout\"'>\n"
249                 + "</form>\n"
250                 + "</body></html>";
251 
252         final WebDriver driver = loadPage2(html);
253         assertTitle(driver, getExpectedAlerts()[0]);
254     }
255 
256     /**
257      * @throws Exception on test failure
258      */
259     @Test
260     @Alerts(DEFAULT = "[object HTMLInputElement] [object HTMLBodyElement]",
261             CHROME = "[object HTMLInputElement] onblur1 onfocusout1 [object HTMLBodyElement]",
262             EDGE = "[object HTMLInputElement] onblur1 onfocusout1 [object HTMLBodyElement]")
263     public void removeParentOfActiveElement() throws Exception {
264         final String html = DOCTYPE_HTML
265                 + "<html>\n"
266                 + "<head>\n"
267                 + "<script>\n"
268                 + "function test() {\n"
269                 + "  var elem = document.getElementById('text1');\n"
270                 + "  elem.focus();\n"
271                 + "  document.title += ' ' + document.activeElement;\n"
272 
273                 + "  var elem = document.getElementById('parent');\n"
274                 + "  elem.parentNode.removeChild(elem);\n"
275                 + "  document.title += ' ' + document.activeElement;\n"
276                 + "}\n"
277                 + "</script>\n"
278                 + "</head>\n"
279                 + "<body onload='test()'>\n"
280                 + "<form name='form1'>\n"
281                 + "  <div id='parent'>\n"
282                 + "    <input id='text1' onblur='document.title += \" onblur1\"' "
283                                 + "onfocusout='document.title += \" onfocusout1\"'>\n"
284                 + "    <input id='text2' onblur='document.title += \" onblur2\"' "
285                                 + "onfocusout='document.title += \" onfocusout2\"'>\n"
286                 + "  </div>\n"
287                 + "</form>\n"
288                 + "</body></html>";
289 
290         final WebDriver driver = loadPage2(html);
291         assertTitle(driver, getExpectedAlerts()[0]);
292     }
293 
294     /**
295      * Another nasty trick from one of these trackers.
296      *
297      * @throws Exception on test failure
298      */
299     @Test
300     @Alerts({"before appendChild;after appendChild;image onload;after removeChild;", "2"})
301     public void addRemove() throws Exception {
302         try (InputStream is = getClass().getClassLoader().getResourceAsStream("testfiles/tiny-jpg.img")) {
303             final byte[] directBytes = IOUtils.toByteArray(is);
304             final URL urlImage = new URL(URL_FIRST, "img.jpg");
305             getMockWebConnection().setResponse(urlImage, directBytes, 200, "ok", "image/jpg", Collections.emptyList());
306         }
307 
308         final String html = DOCTYPE_HTML
309                 + "<html>\n"
310                 + "<head>\n"
311                 + "<script>\n"
312                 + "function test() {\n"
313                 + "  var elem = document.createElement('img');\n"
314                 + "  elem.setAttribute('alt', '');\n"
315                 + "  elem.setAttribute('src', 'img.jpg');\n"
316                 + "  elem.style.display = 'none';\n"
317                 + "  elem.onload = function() {\n"
318                 + "      document.title += 'image onload;';"
319                 + "      document.body.removeChild(elem);\n"
320                 + "      document.title += 'after removeChild;';"
321                 + "    }\n"
322                 + "  document.title += 'before appendChild;';"
323                 + "  document.body.appendChild(elem);\n"
324                 + "  document.title += 'after appendChild;';"
325                 + "}\n"
326                 + "</script>\n"
327                 + "</head>\n"
328                 + "<body onload='test()'>\n"
329                 + "</body></html>";
330 
331         final int count = getMockWebConnection().getRequestCount();
332         final WebDriver driver = getWebDriver();
333         if (driver instanceof HtmlUnitDriver) {
334             ((HtmlUnitDriver) driver).setDownloadImages(true);
335         }
336         loadPage2(html);
337 
338         assertTitle(driver, getExpectedAlerts()[0]);
339         assertEquals(Integer.parseInt(getExpectedAlerts()[1]), getMockWebConnection().getRequestCount() - count);
340     }
341 
342     /**
343      * @throws Exception on test failure
344      */
345     @Test
346     public void keyPressEventWhenPreventsDefault() throws Exception {
347         final String html = DOCTYPE_HTML
348                 + "<html>\n"
349                 + "<body>\n"
350                 + "  <input id='suppress' onkeydown='event.preventDefault()' onkeypress='alert(\"press\")'>\n"
351                 + "</body></html>";
352 
353         final WebDriver driver = loadPage2(html);
354         driver.findElement(By.id("suppress")).sendKeys("s");
355         verifyAlerts(driver, getExpectedAlerts());
356     }
357 
358     /**
359      * @throws Exception on test failure
360      */
361     @Test
362     @Alerts("press")
363     public void keyUpEventWhenPreventsDefault() throws Exception {
364         final String html = DOCTYPE_HTML
365                 + "<html>\n"
366                 + "<body>\n"
367                 + "  <input id='suppress' onkeydown='event.preventDefault()' onkeyup='alert(\"press\")'>\n"
368                 + "</body></html>";
369 
370         final WebDriver driver = loadPage2(html);
371         driver.findElement(By.id("suppress")).sendKeys("s");
372         verifyAlerts(driver, getExpectedAlerts());
373     }
374 
375     /**
376      * @throws Exception if an error occurs
377      */
378     @Test
379     @Alerts({"[object HTMLHtmlElement]", "null"})
380     public void detach() throws Exception {
381         final String html = DOCTYPE_HTML
382             + "<html><head><script>\n"
383             + LOG_TITLE_FUNCTION
384             + "  function test() {\n"
385             + "    var xhr = new XMLHttpRequest();\n"
386             + "    xhr.onload = function () {\n"
387             + "        var xml = xhr.responseXML;\n"
388             + "        log(xml.documentElement);\n"
389             + "        xml.removeChild(xml.firstChild);\n"
390             + "        log(xml.documentElement);\n"
391             + "    }\n"
392             + "    xhr.open('GET', '" + URL_SECOND + "');\n"
393             + "    xhr.send();\n"
394             + "  }\n"
395             + "</script></head><body onload='test()'>\n"
396             + "</body></html>\n";
397 
398         final String xml = "<html xmlns=\"http://www.w3.org/1999/xhtml\"></html>";
399         getMockWebConnection().setResponse(URL_SECOND, xml, MimeType.APPLICATION_XML);
400         loadPage2(html);
401         verifyTitle2(DEFAULT_WAIT_TIME, getWebDriver(), getExpectedAlerts());
402     }
403 
404     /**
405      * @throws Exception if an error occurs
406      */
407     @Test
408     @Alerts("Hello-world")
409     public void typeAtEndOfEditableDiv() throws Exception {
410         final String html = DOCTYPE_HTML
411             + "<html><head><script>\n"
412             + "  function test() {\n"
413             + "    alert(document.getElementById('myInput').value);\n"
414             + "  }\n"
415             + "</script></head>\n"
416             + "<body>\n"
417             + "  <input id='myButton' type='button' onclick='test()'>\n"
418             + "  <div id='myInput' contenteditable='true'>Hello</div>\n"
419             + "</body></html>";
420 
421         final WebDriver driver = loadPage2(html);
422         final WebElement div = driver.findElement(By.id("myInput"));
423         div.sendKeys("-world");
424 
425         assertEquals(getExpectedAlerts()[0], div.getText());
426     }
427 
428     /**
429      * @throws Exception if an error occurs
430      */
431     @Test
432     @Alerts("Hello-world")
433     @BuggyWebDriver(FF = "Hello\n-world",
434             FF_ESR = "Hello\n-world")
435     public void typeAtEndOfEditableDivWithParagraphInside() throws Exception {
436         final String html = DOCTYPE_HTML
437             + "<html><head><script>\n"
438             + "  function test() {\n"
439             + "    alert(document.getElementById('myInput').value);\n"
440             + "  }\n"
441             + "</script></head>\n"
442             + "<body>\n"
443             + "  <input id='myButton' type='button' onclick='test()'>\n"
444             + "  <div id='myInput' contenteditable='true'><p>Hello</p></div>\n"
445             + "</body></html>";
446 
447         final WebDriver driver = loadPage2(html);
448         final WebElement div = driver.findElement(By.id("myInput"));
449         div.sendKeys("-world");
450 
451         assertEquals(getExpectedAlerts()[0], div.getText());
452     }
453 
454 
455     /**
456      * @throws Exception if an error occurs
457      */
458     @Test
459     @Alerts({"bottom", "bottom", "bottom", "", "bottom", "bottom"})
460     public void setGetStyle() throws Exception {
461         final String html = DOCTYPE_HTML
462             + "<html>\n"
463             + "<head>\n"
464             + "<script>\n"
465             + LOG_TITLE_FUNCTION
466             + "  function test() {\n"
467             + "    var d = document.createElement('div');\n"
468             + "    d.style.verticalAlign = 'bottom';\n"
469             + "    log(d.style.getPropertyValue('vertical-align'));\n"
470 
471             + "    d = document.getElementById('style-already-set');\n"
472             + "    log(d.style.getPropertyValue('vertical-align'));\n"
473             + "    document.body.removeChild(d);\n"
474             + "    log(d.style.getPropertyValue('vertical-align'));\n"
475 
476             + "    d = document.getElementById('style-unset');\n"
477             + "    log(d.style.getPropertyValue('vertical-align'));\n"
478             + "    d.style.verticalAlign = 'bottom';\n"
479             + "    log(d.style.getPropertyValue('vertical-align'));\n"
480             + "    document.body.removeChild(d);\n"
481             + "    log(d.style.getPropertyValue('vertical-align'));\n"
482 
483             + "  }\n"
484             + "</script>\n"
485             + "</head>\n"
486             + "<body onload='test()'>\n"
487             + "  <div id='style-already-set' style='vertical-align: bottom'></div>\n"
488             + "  <div id='style-unset'></div>"
489             + "</body>\n"
490             + "</html>";
491 
492         loadPageVerifyTitle2(html);
493     }
494 }