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