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.event;
16  
17  import org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.annotation.Alerts;
19  import org.htmlunit.junit.annotation.BuggyWebDriver;
20  import org.htmlunit.junit.annotation.HtmlUnitNYI;
21  import org.junit.jupiter.api.Test;
22  import org.openqa.selenium.By;
23  import org.openqa.selenium.WebDriver;
24  import org.openqa.selenium.WebElement;
25  
26  /**
27   * Tests that the events triggered in the right order.
28   *
29   * @author Ronald Brill
30   * @author Frank Danek
31   */
32  public class Event2Test extends WebDriverTestCase {
33  
34      /**
35       * Test event order for clicking on a select option.
36       * @throws Exception if the test fails
37       */
38      @Test
39      @Alerts(DEFAULT = "[object Event] change b:true c:false [select] [-] "
40                  + "[object PointerEvent] click b:true c:true [select] [1]",
41              FF = "[object Event] change b:true c:false [select] [-] "
42                  + "[object PointerEvent] click b:true c:true [clickMe] [1]",
43              FF_ESR = "[object Event] change b:true c:false [select] [-] "
44                  + "[object PointerEvent] click b:true c:true [clickMe] [1]")
45      @BuggyWebDriver(DEFAULT = "[object Event] change b:true c:false [select] [-] "
46                  + "[object MouseEvent] click b:true c:true [select] [1]",
47              FF = "[object Event] change b:true c:true [select] [-] "
48                  + "[object Event] click b:true c:true [select] [-]",
49              FF_ESR = "[object Event] change b:true c:true [select] [-] "
50                  + "[object Event] click b:true c:true [select] [-]")
51      @HtmlUnitNYI(CHROME = "[object Event] change b:true c:false [select] [-]"
52                  + " [object PointerEvent] click b:true c:true [clickMe] [1]",
53              EDGE = "[object Event] change b:true c:false [select] [-]"
54                  + " [object PointerEvent] click b:true c:true [clickMe] [1]")
55      public void optionClick() throws Exception {
56          final String firstSnippet = "       <select name='select' id='select' size='2'\n";
57          final String secondSnippet = ">\n"
58                  + "               <option id='o_id1' value='o_value1'>option1</option>\n"
59                  + "               <option id='clickMe' value='o_value2'>option2</option>\n"
60                  + "               <option id='o_id3' value='o_value3'>option3</option>\n"
61                  + "       </select>\n";
62  
63          testClickEvents(firstSnippet, secondSnippet);
64      }
65  
66      /**
67       * Test event order for clicking on a select option.
68       * @throws Exception if the test fails
69       */
70      @Test
71      @Alerts("[object PointerEvent] click b:true c:true [clickMe] [1]")
72      @BuggyWebDriver(CHROME = "",
73                      EDGE = "",
74                      FF = "",
75                      FF_ESR = "")
76      // ChromeDriver does not generate a "[object MouseEvent] click b:true c:true [clickMe] [1]" but it occurs manually
77      public void optionClick2() throws Exception {
78          final String firstSnippet = "       <select name='select' id='select' size='2'>\n"
79                  + "               <option id='o_id1' value='o_value1'>option1</option>\n"
80                  + "               <option id='clickMe' value='o_value2'\n";
81          final String secondSnippet = ">option2</option>\n"
82                  + "               <option id='o_id3' value='o_value3'>option3</option>\n"
83                  + "       </select>\n";
84  
85          testClickEvents(firstSnippet, secondSnippet);
86      }
87  
88      /**
89       * Test event order for clicking on a radio button.
90       * @throws Exception if the test fails
91       */
92      @Test
93      @Alerts("[object PointerEvent] click b:true c:true [radio] [1]"
94                  + " [object Event] change b:true c:false [radio] [-]")
95      public void radioClick() throws Exception {
96          final String firstSnippet = "       <input type='radio' name='radio' id='clickMe' value='2'\n";
97          final String secondSnippet = ">Radio\n";
98  
99          testClickEvents(firstSnippet, secondSnippet);
100     }
101 
102     /**
103      * Test event order for clicking on a check box.
104      * @throws Exception if the test fails
105      */
106     @Test
107     @Alerts("[object PointerEvent] click b:true c:true [checkbox] [1]"
108                 + " [object Event] change b:true c:false [checkbox] [-]")
109     public void checkboxClick() throws Exception {
110         final String firstSnippet = "       <input type='checkbox' name='checkbox' id='clickMe' value='2'\n";
111         final String secondSnippet = ">Checkbox\n";
112 
113         testClickEvents(firstSnippet, secondSnippet);
114     }
115 
116     /**
117      * Test event order for clicking on an entry field.
118      * @throws Exception if the test fails
119      */
120     @Test
121     @Alerts("[object PointerEvent] click b:true c:true [clickMe] [1]")
122     public void inputTextClick() throws Exception {
123         final String firstSnippet = "       <input type='text' name='clickMe' id='clickMe' size='2'\n";
124         final String secondSnippet = ">\n";
125 
126         testClickEvents(firstSnippet, secondSnippet);
127     }
128 
129     /**
130      * Test event order for clicking on a password field.
131      * @throws Exception if the test fails
132      */
133     @Test
134     @Alerts("[object PointerEvent] click b:true c:true [clickMe] [1]")
135     public void inputPasswordClick() throws Exception {
136         final String firstSnippet = "       <input type='password' name='clickMe' id='clickMe' size='2'\n";
137         final String secondSnippet = ">\n";
138 
139         testClickEvents(firstSnippet, secondSnippet);
140     }
141 
142     /**
143      * Test event order for clicking on a text area.
144      * @throws Exception if the test fails
145      */
146     @Test
147     @Alerts("[object PointerEvent] click b:true c:true [clickMe] [1]")
148     public void textareaClick() throws Exception {
149         final String firstSnippet = "       <textarea name='clickMe' id='clickMe' size='2'\n";
150         final String secondSnippet = "></textarea>\n";
151 
152         testClickEvents(firstSnippet, secondSnippet);
153     }
154 
155     /**
156      * Test event order for clicking on a submit button.
157      * @throws Exception if the test fails
158      */
159     @Test
160     @Alerts("")
161     public void submitClick() throws Exception {
162         final String firstSnippet = "       <input type='submit' name='clickMe' id='clickMe'\n";
163         final String secondSnippet = ">\n";
164 
165         testClickEvents(firstSnippet, secondSnippet);
166     }
167 
168     /**
169      * Test event order for clicking on a reset button.
170      * @throws Exception if the test fails
171      */
172     @Test
173     @Alerts("[object PointerEvent] click b:true c:true [clickMe] [1]")
174     public void resetClick() throws Exception {
175         final String firstSnippet = "       <input type='reset' name='clickMe' id='clickMe'\n";
176         final String secondSnippet = ">\n";
177 
178         testClickEvents(firstSnippet, secondSnippet);
179     }
180 
181     /**
182      * Test event order for clicking on a reset button.
183      * @throws Exception if the test fails
184      */
185     @Test
186     @Alerts("[object PointerEvent] click b:true c:true [clickMe] [1]")
187     public void buttonClick() throws Exception {
188         final String firstSnippet = "       <input type='button' name='clickMe' id='clickMe'\n";
189         final String secondSnippet = ">\n";
190 
191         testClickEvents(firstSnippet, secondSnippet);
192     }
193 
194     /**
195      * Test event order for clicking on an anchor.
196      * @throws Exception if the test fails
197      */
198     @Test
199     @Alerts("[object PointerEvent] click b:true c:true [clickMe] [1]")
200     public void anchorClick() throws Exception {
201         final String firstSnippet = "       <a href='#' name='clickMe' id='clickMe'\n";
202         final String secondSnippet = ">anchor</a>\n";
203 
204         testClickEvents(firstSnippet, secondSnippet);
205     }
206 
207     private void testClickEvents(final String firstSnippet, final String secondSnippet) throws Exception {
208         final String html = DOCTYPE_HTML
209                 + "<html>\n"
210                 + "<head>\n"
211                 + "  <title></title>\n"
212                 + "  <script type='text/javascript'>\n"
213                 + "  <!--\n"
214                 + "    function dumpEvent(event) {\n"
215                 + "      var msg = event;\n"
216                 + "      msg = msg + ' ' + event.type;\n"
217                 + "      msg = msg + ' b:' + event.bubbles;\n"
218                 + "      msg = msg + ' c:' + event.cancelable;\n"
219                 + "\n"
220                 + "      // target\n"
221                 + "      var eTarget;\n"
222                 + "      if (event.target) {\n"
223                 + "        eTarget = event.target;\n"
224                 + "      } else if (event.srcElement) {\n"
225                 + "        eTarget = event.srcElement;\n"
226                 + "      }\n"
227                 + "      // defeat Safari bug\n"
228                 + "      if (eTarget.nodeType == 3) {\n"
229                 + "        eTarget = eTarget.parentNode;\n"
230                 + "      }\n"
231                 + "\n"
232                 + "      if (eTarget.name) {\n"
233                 + "        msg = msg + ' [' + eTarget.name + ']';\n"
234                 + "      } else {\n"
235                 + "        msg = msg + ' [' + eTarget.id + ']';\n"
236                 + "      }\n"
237                 + "\n"
238                 + "      // key code\n"
239                 + "      var eCode;\n"
240                 + "      if (event.keyCode) {\n"
241                 + "        eCode = event.keyCode;\n"
242                 + "      } else if (event.which) {\n"
243                 + "        eCode = event.which;\n"
244                 + "      } else if (event.button) {\n"
245                 + "        eCode = event.button;\n"
246                 + "      }\n"
247                 + "      if (eCode) {\n"
248                 + "        var char = String.fromCharCode(eCode);\n"
249                 + "        msg = msg + ' [' + eCode + ']';\n"
250                 + "      } else {\n"
251                 + "        msg = msg + ' [-]';\n"
252                 + "      }\n"
253                 + "\n"
254                 + "      document.title += ' ' + msg;\n"
255                 + "    }\n"
256                 + "  //-->\n"
257                 + "  </script>\n"
258                 + "</head>\n"
259                 + "  <form id='form' name='form' action='#'>\n"
260                 + "    <input type='text' id='start' name='startText'/>\n"
261                 + "\n"
262                 + firstSnippet
263                 + "      onclick='dumpEvent(event);'\n"
264 //                + "      ondblclick='dumpEvent(event);'\n"
265 //                + "      oncontextmenu='dumpEvent(event);'\n"
266 //                + "      onfocus='dumpEvent(event);'\n"
267 //                + "      onblur='dumpEvent(event);'\n"
268 //                + "      onmousedown = 'dumpEvent(event);'\n"
269 //                + "      onmouseup = 'dumpEvent(event);'\n"
270 //                + "      onkeydown = 'dumpEvent(event);'\n"
271 //                + "      onkeyup = 'dumpEvent(event);'\n"
272 //                + "      onkeypress = 'dumpEvent(event);'\n"
273 //                + "      onselect = 'dumpEvent(event);'\n"
274                 + "      onchange = 'dumpEvent(event);'"
275                 + secondSnippet
276                 + "   </form>\n"
277                 + "</body>\n"
278                 + "</html>\n";
279 
280         final WebDriver driver = loadPage2(html);
281         driver.findElement(By.id("clickMe")).click();
282 
283         assertTitle(driver, getExpectedAlerts()[0]);
284     }
285 
286     /**
287      * Test event order for typing into an entry field.
288      * @throws Exception if the test fails
289      */
290     @Test
291     @Alerts("[object KeyboardEvent] keydown b:true c:true [typeHere] [65] "
292             + "[object KeyboardEvent] keypress b:true c:true [typeHere] [97] "
293             + "[object KeyboardEvent] keyup b:true c:true [typeHere] [65]")
294     public void inputTextType() throws Exception {
295         final String firstSnippet = "       <input type='text' id='typeHere'\n";
296         final String secondSnippet = "/>\n";
297 
298         testTypeEvents(firstSnippet, secondSnippet);
299     }
300 
301     /**
302      * Test event order for typing into a password field.
303      * @throws Exception if the test fails
304      */
305     @Test
306     @Alerts("[object KeyboardEvent] keydown b:true c:true [typeHere] [65] "
307             + "[object KeyboardEvent] keypress b:true c:true [typeHere] [97] "
308             + "[object KeyboardEvent] keyup b:true c:true [typeHere] [65]")
309     public void inputPasswordType() throws Exception {
310         final String firstSnippet = "       <input type='password' id='typeHere'\n";
311         final String secondSnippet = "/>\n";
312 
313         testTypeEvents(firstSnippet, secondSnippet);
314     }
315 
316     /**
317      * Test event order for typing into a password field.
318      * @throws Exception if the test fails
319      */
320     @Test
321     @Alerts("[object KeyboardEvent] keydown b:true c:true [typeHere] [65] "
322             + "[object KeyboardEvent] keypress b:true c:true [typeHere] [97] "
323             + "[object KeyboardEvent] keyup b:true c:true [typeHere] [65]")
324     public void textAreaType() throws Exception {
325         final String firstSnippet = "       <textarea id='typeHere' rows='4' cols='2'\n";
326         final String secondSnippet = "></textarea >\n";
327 
328         testTypeEvents(firstSnippet, secondSnippet);
329     }
330 
331     private void testTypeEvents(final String firstSnippet, final String secondSnippet) throws Exception {
332         final String html = DOCTYPE_HTML
333                 + "<html>\n"
334                 + "<head>\n"
335                 + "  <script type='text/javascript'>\n"
336                 + "  <!--\n"
337                 + "    function dumpEvent(event) {\n"
338                 + "      var msg = event;\n"
339                 + "      msg = msg + ' ' + event.type;\n"
340                 + "      msg = msg + ' b:' + event.bubbles;\n"
341                 + "      msg = msg + ' c:' + event.cancelable;\n"
342                 + "\n"
343                 + "      // target\n"
344                 + "      var eTarget;\n"
345                 + "      if (event.target) {\n"
346                 + "        eTarget = event.target;\n"
347                 + "      } else if (event.srcElement) {\n"
348                 + "        eTarget = event.srcElement;\n"
349                 + "      }\n"
350                 + "      // defeat Safari bug\n"
351                 + "      if (eTarget.nodeType == 3) {\n"
352                 + "        eTarget = eTarget.parentNode;\n"
353                 + "      }\n"
354                 + "\n"
355                 + "      if (eTarget.name) {\n"
356                 + "        msg = msg + ' [' + eTarget.name + ']';\n"
357                 + "      } else {\n"
358                 + "        msg = msg + ' [' + eTarget.id + ']';\n"
359                 + "      }\n"
360                 + "\n"
361                 + "      // key code\n"
362                 + "      var eCode;\n"
363                 + "      if (event.keyCode) {\n"
364                 + "        eCode = event.keyCode;\n"
365                 + "      } else if (event.which) {\n"
366                 + "        eCode = event.which;\n"
367                 + "      } else if (event.button) {\n"
368                 + "        eCode = event.button;\n"
369                 + "      }\n"
370                 + "      if (eCode) {\n"
371                 + "        var char = String.fromCharCode(eCode);\n"
372                 + "        msg = msg + ' [' + eCode + ']';\n"
373                 + "      } else {\n"
374                 + "        msg = msg + ' [-]';\n"
375                 + "      }\n"
376                 + "\n"
377                 + "      document.title += ' ' + msg;\n"
378                 + "    }\n"
379                 + "  //-->\n"
380                 + "  </script>\n"
381                 + "</head>\n"
382                 + "  <form id='form' name='form' action='#'>\n"
383                 + "    <input type='text' id='start' name='startText'/>\n"
384                 + "\n"
385                 + firstSnippet
386                 + "    onclick='dumpEvent(event);'\n"
387 //                + "    ondblclick='dumpEvent(event);'\n"
388 //                + "    oncontextmenu='dumpEvent(event);'\n"
389 //                + "    onfocus='dumpEvent(event);'\n"
390 //                + "    onblur='dumpEvent(event);'\n"
391 //                + "    onmousedown = 'dumpEvent(event);'\n"
392 //                + "    onmouseup = 'dumpEvent(event);'\n"
393                 + "    onkeydown = 'dumpEvent(event);'\n"
394                 + "    onkeyup = 'dumpEvent(event);'\n"
395                 + "    onkeypress = 'dumpEvent(event);'\n"
396 //                + "    onselect = 'dumpEvent(event);'\n"
397                 + "    onchange = 'dumpEvent(event);'"
398                 + secondSnippet
399                 + "   </form>\n"
400                 + "</body>\n"
401                 + "</html>\n";
402 
403         final WebDriver driver = loadPage2(html);
404         driver.findElement(By.id("typeHere")).sendKeys("a");
405 
406         assertTitle(driver, getExpectedAlerts()[0]);
407     }
408 
409     /**
410      * Tests that event fires on key press.
411      * @throws Exception if the test fails
412      */
413     @Test
414     @Alerts({"pass", "fail:66", "fail:undefined"})
415     public void eventOnKeyDown() throws Exception {
416         final String html = DOCTYPE_HTML
417             + "<html><head>"
418             + "<script>\n"
419             + LOG_TITLE_FUNCTION
420             + "</script>"
421             + "</head>\n"
422             + "<body>\n"
423             + "  <button type='button' id='clickId'>Click Me</button>\n"
424             + "  <script>\n"
425             + "    function handler(_e) {\n"
426             + "      var e = _e ? _e : window.event;\n"
427             + "      if (e.keyCode == 65)\n"
428             + "        log('pass');\n"
429             + "      else\n"
430             + "        log('fail:' + e.keyCode);\n"
431             + "    }\n"
432             + "    document.getElementById('clickId').onkeydown = handler;\n"
433             + "    document.getElementById('clickId').onclick = handler;\n"
434             + "  </script>\n"
435             + "</body></html>";
436 
437         final WebDriver driver = loadPage2(html);
438         final WebElement element = driver.findElement(By.id("clickId"));
439         element.sendKeys("a");
440         verifyTitle2(driver, getExpectedAlerts()[0]);
441 
442         element.sendKeys("b");
443         verifyTitle2(driver, getExpectedAlerts()[0], getExpectedAlerts()[1]);
444 
445         element.click();
446         verifyTitle2(driver, getExpectedAlerts());
447     }
448 
449     /**
450      * Verifies that in IE, the <tt>shiftKey</tt>, <tt>ctrlKey</tt> and <tt>altKey</tt>
451      * event attributes are defined for all events, but <tt>metaKey</tt> is not defined
452      * for any events.
453      * Verifies that in FF, the <tt>shiftKey</tt>, <tt>ctrlKey</tt>, <tt>altKey</tt> and
454      * <tt>metaKey</tt> attributes are defined for mouse events only.
455      * @throws Exception if an error occurs
456      */
457     @Test
458     @Alerts({"object", "undefined", "undefined", "undefined", "undefined",
459              "object", "false", "false", "false", "false"})
460     public void keys() throws Exception {
461         final String html = DOCTYPE_HTML
462             + "<html><body onload='test(event)'><script>\n"
463             + LOG_TITLE_FUNCTION
464             + "  function test(e) {\n"
465             + "    log(typeof e);\n"
466             + "    log(e.shiftKey);\n"
467             + "    log(e.ctrlKey);\n"
468             + "    log(e.altKey);\n"
469             + "    log(e.metaKey);\n"
470             + "  }\n"
471             + "</script>\n"
472             + "<div id='div' onclick='test(event)'>abc</div>\n"
473             + "</body></html>";
474 
475         final String[] alerts = getExpectedAlerts();
476         int i = 0;
477 
478         final WebDriver driver = loadPage2(html);
479         verifyTitle2(driver, alerts[i++], alerts[i++], alerts[i++], alerts[i++], alerts[i++]);
480 
481         final WebElement element = driver.findElement(By.id("div"));
482         element.click();
483         verifyTitle2(driver, alerts);
484     }
485 
486     /**
487      * @throws Exception if an error occurs
488      */
489     @Test
490     public void preventDefault() throws Exception {
491         final String html = DOCTYPE_HTML
492             + "<html><head>\n"
493             + "<script>\n"
494             + "function block(e) {\n"
495             + "  if (e && e.preventDefault)\n"
496             + "    e.preventDefault();\n"
497             + "  else\n"
498             + "    return false;\n"
499             + "}\n"
500             + "\n"
501             + "function test() {\n"
502             + "  document.getElementById('myForm').onsubmit = block;\n"
503             + "}\n"
504             + "</script>\n"
505             + "</head><body onload='test()'>\n"
506             + "<form id='myForm' action='doesnt_exist.html'>\n"
507             + "  <input type='submit' id='mySubmit' value='Continue'></p>\n"
508             + "</form>\n"
509             + "</body></html>";
510 
511         final WebDriver driver = loadPage2(html);
512         driver.findElement(By.id("mySubmit"));
513         assertEquals(URL_FIRST.toExternalForm(), driver.getCurrentUrl());
514     }
515 
516     /**
517      * Test DOMContentLoaded event.
518      * @throws Exception if the test fails
519      */
520     @Test
521     @Alerts({"DOMContentLoaded type=DOMContentLoaded", "onLoad"})
522     public void dOMContentLoaded() throws Exception {
523         final String html = DOCTYPE_HTML
524             + "<html>\n"
525             + "<head>\n"
526             + "<script>\n"
527             + LOG_TITLE_FUNCTION
528             + "  document.addEventListener('DOMContentLoaded', onDCL, false);\n"
529             + "  function onDCL(e) {\n"
530             + "    log('DOMContentLoaded type=' + e.type);\n"
531             + "  }\n"
532             + "</script>\n"
533             + "</head>\n"
534             + "<body onload='log(\"onLoad\")'>\n"
535             + "</body></html>";
536 
537         loadPageVerifyTitle2(html);
538     }
539 
540     /**
541      * @throws Exception if the test fails
542      */
543     @Test
544     @Alerts({"false", "not canceled", "true", "canceled", "true"})
545     public void testPreventDefault() throws Exception {
546         final String html = DOCTYPE_HTML
547             + "<html>\n"
548             + "<head>\n"
549             + "<script>\n"
550             + LOG_TITLE_FUNCTION
551             + "  function preventDef(event) {\n"
552             + "    event.preventDefault();\n"
553             + "  }\n"
554 
555             + "  function addHandler() {\n"
556             + "    document.getElementById('checkbox').addEventListener('click', preventDef, false);\n"
557             + "  }\n"
558 
559             + "  function simulateClick() {\n"
560             + "    var evt = document.createEvent('MouseEvents');\n"
561             + "    evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0,"
562                         + " false, false, false, false, 0, null);\n"
563             + "    var cb = document.getElementById('checkbox');\n"
564             + "    var canceled = !cb.dispatchEvent(evt);\n"
565             + "    if(canceled) {\n"
566             + "      // A handler called preventDefault\n"
567             + "      log('canceled');\n"
568             + "    } else {\n"
569             + "      // None of the handlers called preventDefault\n"
570             + "      log('not canceled');\n"
571             + "    }\n"
572             + "  }\n"
573 
574             + "  function test() {\n"
575             + "    log(document.getElementById('checkbox').checked);\n"
576             + "    simulateClick();\n"
577             + "    log(document.getElementById('checkbox').checked);\n"
578             + "    addHandler();\n"
579             + "    simulateClick();\n"
580             + "    log(document.getElementById('checkbox').checked);\n"
581             + "  }\n"
582             + "</script>\n"
583             + "</head>\n"
584             + "<body onload='test()'>\n"
585             + "  <input type='checkbox' id='checkbox'/><label for='checkbox'>Checkbox</label>\n"
586             + "</body></html>";
587 
588         loadPageVerifyTitle2(html);
589     }
590 
591     /**
592      * Test event transmission to event handler.
593      * @throws Exception if the test fails
594      */
595     @Test
596     @Alerts({"false", "false", "SPAN"})
597     public void eventTransmission() throws Exception {
598         final String html = DOCTYPE_HTML
599             + "<html>\n"
600             + "<body>\n"
601             + "  <span id='clickMe'>foo</span>\n"
602             + "  <script>\n"
603             + LOG_TITLE_FUNCTION
604             + "    function handler(e) {\n"
605             + "      log(e == null);\n"
606             + "      log(window.event == null);\n"
607             + "      var theEvent = (e != null) ? e : window.event;\n"
608             + "      var target = theEvent.target ? theEvent.target : theEvent.srcElement;\n"
609             + "      log(target.tagName);\n"
610             + "    }\n"
611             + "    document.getElementById('clickMe').onclick = handler;\n"
612             + "</script>\n"
613             + "</body></html>";
614 
615         final WebDriver driver = loadPage2(html);
616         driver.findElement(By.id("clickMe")).click();
617 
618         verifyTitle2(driver, getExpectedAlerts());
619     }
620 
621     /**
622      * @throws Exception if an error occurs
623      */
624     @Test
625     @Alerts({"capturing", "at target", "bubbling"})
626     public void eventPhase() throws Exception {
627         final String html = DOCTYPE_HTML
628             + "<html>\n"
629             + "<head><script>\n"
630             + LOG_TITLE_FUNCTION
631             + "  function init() {\n"
632             + "    var form = document.forms[0];\n"
633             + "    form.addEventListener('click', alertPhase, true);\n"
634             + "    form.addEventListener('click', alertPhase, false);\n"
635             + "  }\n"
636 
637             + "  function alertPhase(e) {\n"
638             + "    switch (e.eventPhase) {\n"
639             + "      case 1: log('capturing'); break;\n"
640             + "      case 2: log('at target'); break;\n"
641             + "      case 3: log('bubbling'); break;\n"
642             + "      default: log('unknown');\n"
643             + "    }\n"
644             + "  }\n"
645             + "</script></head>\n"
646             + "<body onload='init()'>\n"
647             + "  <form>\n"
648             + "    <input type='button' onclick='alertPhase(event)' id='b'>\n"
649             + "  </form>\n"
650             + "</body></html>";
651 
652         final WebDriver driver = loadPage2(html);
653         driver.findElement(By.id("b")).click();
654 
655         verifyTitle2(driver, getExpectedAlerts());
656     }
657 
658     /**
659      * Test for event capturing and bubbling in FF.
660      * @throws Exception if the test fails
661      */
662     @Test
663     @Alerts({"window capturing", "div capturing", "span capturing",
664                 "span bubbling", "div", "div bubbling", "window bubbling"})
665     public void eventCapturingAndBubbling() throws Exception {
666         final String html = DOCTYPE_HTML
667             + "<html>\n"
668             + "<head>\n"
669             + "<script>\n"
670             + LOG_TITLE_FUNCTION
671             + "  function t(_s) {\n"
672             + "    return function() { log(_s) };\n"
673             + "  }\n"
674 
675             + "  function init() {\n"
676             + "    window.addEventListener('click', t('window capturing'), true);\n"
677             + "    window.addEventListener('click', t('window bubbling'), false);\n"
678             + "    var oDiv = document.getElementById('theDiv');\n"
679             + "    oDiv.addEventListener('click', t('div capturing'), true);\n"
680             + "    oDiv.addEventListener('click', t('div bubbling'), false);\n"
681             + "    var oSpan = document.getElementById('theSpan');\n"
682             + "    oSpan.addEventListener('click', t('span capturing'), true);\n"
683             + "    oSpan.addEventListener('click', t('span bubbling'), false);\n"
684             + "  }\n"
685             + "</script>\n"
686             + "</head>\n"
687             + "<body onload='init()'>\n"
688             + "  <div onclick=\"log('div')\" id='theDiv'>\n"
689             + "    <span id='theSpan'>blabla</span>\n"
690             + "  </div>\n"
691             + "</body></html>";
692 
693         final WebDriver driver = loadPage2(html);
694         driver.findElement(By.id("theSpan")).click();
695 
696         verifyTitle2(driver, getExpectedAlerts());
697     }
698 
699     /**
700      * Test for event capturing and bubbling.
701      * @throws Exception if the test fails
702      */
703     @Test
704     @Alerts({"window capturing", "div capturing", "span capturing", "div", "window capturing", "false",
705                 "true"})
706     public void stopPropagation() throws Exception {
707         stopPropagation("stopPropagation()");
708     }
709 
710     /**
711      * Test for event capturing and bubbling in FF.
712      * @throws Exception if the test fails
713      */
714     @Test
715     @Alerts({"window capturing", "div capturing", "span capturing", "div", "window capturing", "false", "true"})
716     public void stopPropagationCancelBubble() throws Exception {
717         stopPropagation("cancelBubble=true");
718     }
719 
720     private void stopPropagation(final String cancelMethod) throws Exception {
721         final String html = DOCTYPE_HTML
722             + "<html>\n"
723             + "<head>\n"
724             + "<script>\n"
725             + LOG_TITLE_FUNCTION
726             + "  var counter = 0;\n"
727             + "  function t(_s) {\n"
728             + "    return function(e) {\n"
729             + "      log(_s); counter++;\n"
730             + "      if (counter >= 4) {\n"
731             + "        log(e.cancelBubble);\n"
732             + "        e." + cancelMethod + ";\n"
733             + "        log(e.cancelBubble);\n"
734             + "      }\n"
735             + "    };\n"
736             + "  }\n"
737             + "  function init() {\n"
738             + "    window.addEventListener('click', t('window capturing'), true);\n"
739             + "    var oDiv = document.getElementById('theDiv');\n"
740             + "    oDiv.addEventListener('click', t('div capturing'), true);\n"
741             + "    var oSpan = document.getElementById('theSpan');\n"
742             + "    oSpan.addEventListener('click', t('span capturing'), true);\n"
743             + "  }\n"
744             + "</script>\n"
745             + "</head>\n"
746             + "<body onload='init()'>\n"
747             + "  <div onclick=\"log('div')\" id='theDiv'>\n"
748             + "    <span id='theSpan'>blabla</span>\n"
749             + "  </div>\n"
750             + "</body></html>";
751 
752         final String[] alerts = getExpectedAlerts();
753         int i = 0;
754 
755         final WebDriver driver = loadPage2(html);
756         driver.findElement(By.id("theSpan")).click();
757         verifyTitle2(driver, alerts[i++], alerts[i++], alerts[i++], alerts[i++]);
758 
759         driver.findElement(By.id("theSpan")).click();
760         verifyTitle2(driver, alerts);
761     }
762 
763     /**
764      * @throws Exception if an error occurs
765      */
766     @Test
767     @Alerts({"w", "w 2", "d", "d 2", "s", "s 2", "w", "w 2"})
768     public void stopPropagation_WithMultipleEventHandlers() throws Exception {
769         final String html = DOCTYPE_HTML
770             + "<html>\n"
771             + "<head>\n"
772             + "<script>\n"
773             + LOG_TITLE_FUNCTION
774             + "  var counter = 0;\n"
775             + "  function t(_s) {\n"
776             + "    return function(e) { log(_s); counter++; if (counter >= 5) e.stopPropagation(); };\n"
777             + "  }\n"
778             + "  function init() {\n"
779             + "    window.addEventListener('click', t('w'), true);\n"
780             + "    window.addEventListener('click', t('w 2'), true);\n"
781             + "    var oDiv = document.getElementById('theDiv');\n"
782             + "    oDiv.addEventListener('click', t('d'), true);\n"
783             + "    oDiv.addEventListener('click', t('d 2'), true);\n"
784             + "    var oSpan = document.getElementById('theSpan');\n"
785             + "    oSpan.addEventListener('click', t('s'), true);\n"
786             + "    oSpan.addEventListener('click', t('s 2'), true);\n"
787             + "  }\n"
788             + "</script>\n"
789             + "</head><body onload='init()'>\n"
790             + "<div id='theDiv'>\n"
791             + "<span id='theSpan'>blabla</span>\n"
792             + "</div>\n"
793             + "</body></html>";
794 
795         final String[] alerts = getExpectedAlerts();
796         int i = 0;
797 
798         final WebDriver driver = loadPage2(html);
799         driver.findElement(By.id("theSpan")).click();
800         verifyTitle2(driver, alerts[i++], alerts[i++], alerts[i++], alerts[i++], alerts[i++], alerts[i++]);
801 
802         driver.findElement(By.id("theSpan")).click();
803         verifyTitle2(driver, alerts);
804     }
805 
806     /**
807      * Test for event capturing and bubbling.
808      * @throws Exception if the test fails
809      */
810     @Test
811     @Alerts({"window capturing", "div capturing", "span capturing", "div", "window capturing", "false",
812                 "true"})
813     public void stopImmediatePropagation() throws Exception {
814         stopPropagation("stopImmediatePropagation()");
815     }
816 
817     /**
818      * @throws Exception if an error occurs
819      */
820     @Test
821     @Alerts({"w", "w 2", "d", "d 2", "s", "w"})
822     public void stopImmediatePropagation_WithMultipleEventHandlers() throws Exception {
823         final String html = DOCTYPE_HTML
824             + "<html>\n"
825             + "<head>\n"
826             + "<script>\n"
827             + LOG_TITLE_FUNCTION
828             + "  var counter = 0;\n"
829             + "  function t(_s) {\n"
830             + "    return function(e) { log(_s); counter++; if (counter >= 5) e.stopImmediatePropagation(); };\n"
831             + "  }\n"
832             + "  function init() {\n"
833             + "    window.addEventListener('click', t('w'), true);\n"
834             + "    window.addEventListener('click', t('w 2'), true);\n"
835             + "    var oDiv = document.getElementById('theDiv');\n"
836             + "    oDiv.addEventListener('click', t('d'), true);\n"
837             + "    oDiv.addEventListener('click', t('d 2'), true);\n"
838             + "    var oSpan = document.getElementById('theSpan');\n"
839             + "    oSpan.addEventListener('click', t('s'), true);\n"
840             + "    oSpan.addEventListener('click', t('s 2'), true);\n"
841             + "  }\n"
842             + "</script>\n"
843             + "</head><body onload='init()'>\n"
844             + "<div id='theDiv'>\n"
845             + "<span id='theSpan'>blabla</span>\n"
846             + "</div>\n"
847             + "</body></html>";
848 
849         final String[] alerts = getExpectedAlerts();
850         int i = 0;
851 
852         final WebDriver driver = loadPage2(html);
853         driver.findElement(By.id("theSpan")).click();
854         verifyTitle2(driver, alerts[i++], alerts[i++], alerts[i++], alerts[i++], alerts[i++]);
855 
856         driver.findElement(By.id("theSpan")).click();
857         verifyTitle2(driver, alerts);
858     }
859 
860     /**
861      * Test event order for clicking on a select option.
862      * @throws Exception if the test fails
863      */
864     @Test
865     @Alerts("[object Event]")
866     public void windowEvent() throws Exception {
867         final String html = DOCTYPE_HTML
868                 + "<html>\n"
869                 + "<head>\n"
870                 + "<script>\n"
871                 + LOG_TITLE_FUNCTION
872                 + "  function test() {\n"
873                 + "    log(window.event);\n"
874                 + "  }\n"
875                 + "</script>\n"
876                 + "</head><body onload='test()'>\n"
877                 + "</body></html>";
878 
879         loadPageVerifyTitle2(html);
880     }
881 
882     /**
883      * @throws Exception if an error occurs
884      */
885     @Test
886     @Alerts({"anchor onclick prevented=false",
887                 "document onclick prevented=false",
888                 "window onclick prevented=true"})
889     public void returnPriority() throws Exception {
890         final String html = DOCTYPE_HTML
891                 + "<html><head>\n"
892                 + "<script>\n"
893                 + "  function log(msg) {\n"
894                 + "    window.document.title += msg + ';';\n"
895                 + "  }\n"
896                 + "</script>\n"
897                 + "</head>\n"
898                 + "<body>\n"
899                 + "<a id='tester' href='javascript:log(\"FIRED\")'>test: onclick return value</a>\n"
900                 + "<script>\n"
901                 + "  tester.onclick = function (event) { "
902                                     + "log('anchor onclick prevented=' + event.defaultPrevented); return true }\n"
903                 + "  document.onclick = function (event) { "
904                                     + "log('document onclick prevented=' + event.defaultPrevented); return false }\n"
905                 + "  window.onclick = function (event) { "
906                                     + "log('window onclick prevented=' + event.defaultPrevented); return true; }\n"
907                 + "</script>\n"
908                 + "</body></html>";
909 
910         final WebDriver driver = loadPage2(html);
911         driver.findElement(By.id("tester")).click();
912 
913         final String text = driver.getTitle().trim().replaceAll(";", "\n").trim();
914         assertEquals(String.join("\n", getExpectedAlerts()), text);
915     }
916 
917     /**
918      * BeforeUnloadEvent's returnValue behaves differently to other events.
919      * @throws Exception if an error occurs
920      */
921     @Test
922     @Alerts({"nullwindow at beforeunload rv=",
923              "window onbeforeunload rv=1",
924              "window at beforeunload rv=1"})
925     public void returnPriority2() throws Exception {
926         final String html = DOCTYPE_HTML
927                 + "<html><head>\n"
928                 + "<script>\n"
929                 + "  output = '';\n"
930                 + "  function log(msg) {\n"
931                 + "    var output = localStorage.getItem('output');\n"
932                 + "    output += msg + ';';\n"
933                 + "    localStorage.setItem('output', output);\n"
934                 + "  }\n"
935                 + "  function unload1(event) {\n"
936                 + "    log('window at beforeunload rv=' + event.returnValue);\n"
937                 + "    event.returnValue='1';\n"
938                 + "  }\n"
939                 + "  function unload2(event) {\n"
940                 + "    log('window onbeforeunload rv=' + event.returnValue);\n"
941                 + "    return '2';\n"
942                 + "  }\n"
943                 + "  function unload3(event) {\n"
944                 + "    log('window at beforeunload rv=' + event.returnValue);\n"
945                 + "    event.returnValue='3';\n"
946                 + "  }\n"
947                 + "</script>\n"
948                 + "</head>\n"
949                 + "<body>\n"
950                 + "<button id='tester' onclick='window.location.reload()'>test: onbeforeunload return value</button>\n"
951 
952                 + "<button id='getResult' "
953                             + "onclick='window.removeEventListener(\"beforeunload\", unload1);"
954                              + "window.onbeforeunload = undefined;"
955                             + "window.removeEventListener(\"beforeunload\", unload3);"
956                             + "window.document.title=localStorage.getItem(\"output\")'>get result</button>\n"
957                 + "<script>\n"
958                 + "  window.addEventListener('beforeunload', unload1);\n"
959                 + "  window.onbeforeunload = unload2\n"
960                 + "  window.addEventListener('beforeunload', unload3);\n"
961                 + "</script>\n"
962                 + "</body></html>";
963 
964         final WebDriver driver = loadPage2(html);
965         driver.findElement(By.id("tester")).click();
966         Thread.sleep(200);
967         driver.switchTo().alert().accept();
968         Thread.sleep(200);
969 
970         driver.findElement(By.id("getResult")).click();
971         final String text = driver.getTitle().trim().replaceAll(";", "\n").trim();
972         assertEquals(String.join("\n", getExpectedAlerts()), text);
973     }
974 }