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;
16  
17  import static java.nio.charset.StandardCharsets.ISO_8859_1;
18  
19  import java.time.Duration;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.List;
23  
24  import org.htmlunit.WebDriverTestCase;
25  import org.htmlunit.junit.BrowserRunner;
26  import org.htmlunit.junit.annotation.Alerts;
27  import org.htmlunit.junit.annotation.HtmlUnitNYI;
28  import org.htmlunit.util.NameValuePair;
29  import org.junit.Test;
30  import org.junit.runner.RunWith;
31  import org.openqa.selenium.By;
32  import org.openqa.selenium.JavascriptExecutor;
33  import org.openqa.selenium.WebDriver;
34  
35  /**
36   * Tests for {@link History}.
37   *
38   * @author Marc Guillemot
39   * @author Ahmed Ashour
40   * @author Ronald Brill
41   * @author Adam Afeltowicz
42   * @author Carsten Steul
43   * @author Anton Demydenko
44   * @author Lai Quang Duong
45   */
46  @RunWith(BrowserRunner.class)
47  public class History2Test extends WebDriverTestCase {
48  
49      /**
50       * @throws Exception if an error occurs
51       */
52      @Test
53      @Alerts("here")
54      public void goShouldIgnoreOutOfBoundIndex() throws Exception {
55          final String html = DOCTYPE_HTML
56                  + "<html><body>\n"
57                  + "<script>\n"
58                  + LOG_TITLE_FUNCTION
59                  + "history.go(1);\n"
60                  + "log('here');\n"
61                  + "</script></body></html>";
62  
63          loadPageVerifyTitle2(html);
64          assertEquals(1, getMockWebConnection().getRequestCount());
65      }
66  
67      /**
68       * @throws Exception if an error occurs
69       */
70      @Test
71      @Alerts({"[object PopStateEvent]", "null"})
72      public void pushStateSimple() throws Exception {
73          final String html = DOCTYPE_HTML
74                  + "<html>\n"
75                  + "<head>\n"
76                  + "<script>\n"
77                  + LOG_TITLE_FUNCTION
78                  + "  function test() {\n"
79                  + "    if (!window.history.pushState) { log('no pushState'); return }\n"
80                  + "    var stateObj = { hi: 'there' };\n"
81                  + "    window.history.pushState(stateObj, 'page 2', 'bar.html');\n"
82                  + "  }\n"
83  
84                  + "  function popMe(event) {\n"
85                  + "    var e = event ? event : window.event;\n"
86                  + "    log(e);\n"
87                  + "    log(e.state);\n"
88                  + "  }\n"
89                  + "</script>\n"
90                  + "</head>\n"
91                  + "<body onpopstate='popMe(event)'>\n"
92                  + "  <button id=myId onclick='test()'>Click me</button>\n"
93                  + "</body></html>";
94  
95          final String[] expectedAlerts = getExpectedAlerts();
96          final WebDriver driver = loadPage2(html);
97          driver.findElement(By.id("myId")).click();
98  
99          if (expectedAlerts.length > 1) {
100             assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
101             driver.navigate().back();
102         }
103         verifyTitle2(driver, expectedAlerts);
104     }
105 
106     /**
107      * @throws Exception if an error occurs
108      */
109     @Test
110     @Alerts({"[object PopStateEvent]", "{\"hi\":\"there\"}",
111                 "[object PopStateEvent]", "{\"hi\":\"there\"}",
112                 "[object PopStateEvent]", "null",
113                 "[object PopStateEvent]", "null",
114                 "[object PopStateEvent]", "{\"hi\":\"there\"}",
115                 "[object PopStateEvent]", "{\"hi\":\"there\"}",
116                 "[object PopStateEvent]", "{\"hi2\":\"there2\"}",
117                 "[object PopStateEvent]", "{\"hi2\":\"there2\"}"})
118     public void pushState() throws Exception {
119         final String html = DOCTYPE_HTML
120                 + "<html>\n"
121                 + "<head>\n"
122                 + "<script>\n"
123                 + LOG_TITLE_FUNCTION
124                 + "  function test() {\n"
125                 + "    if (window.history.pushState) {\n"
126                 + "      var stateObj = { hi: 'there' };\n"
127                 + "      window.history.pushState(stateObj, 'page 2', 'bar.html');\n"
128                 + "    }\n"
129                 + "  }\n"
130 
131                 + "  function test2() {\n"
132                 + "    if (window.history.pushState) {\n"
133                 + "      var stateObj = { hi2: 'there2' };\n"
134                 + "      window.history.pushState(stateObj, 'page 3', 'bar2.html');\n"
135                 + "    }\n"
136                 + "  }\n"
137 
138                 + "  function popMe(event) {\n"
139                 + "    var e = event ? event : window.event;\n"
140                 + "    log(e);\n"
141                 + "    log(JSON.stringify(e.state));\n"
142                 + "  }\n"
143 
144                 + "  function setWindowName() {\n"
145                 + "    window.name = window.name + 'a\\u00a7';\n"
146                 + "  }\n"
147 
148                 + "  window.addEventListener('popstate', popMe);\n"
149                 + "</script>\n"
150                 + "</head>\n"
151                 + "<body onpopstate='popMe(event)' onload='setWindowName()' onbeforeunload='setWindowName()' "
152                                                                 + "onunload='setWindowName()'>\n"
153                 + "  <button id=myId onclick='test()'>Click me</button>\n"
154                 + "  <button id=myId2 onclick='test2()'>Click me</button>\n"
155                 + "</body></html>";
156 
157         final String[] expectedAlerts = getExpectedAlerts();
158         int i = 0;
159 
160         final WebDriver driver = loadPage2(html);
161         verifyWindowName2(driver, "a");
162 
163         final long start = (Long) ((JavascriptExecutor) driver).executeScript("return window.history.length");
164 
165         driver.findElement(By.id("myId")).click();
166         verifyWindowName2(driver, "a");
167         assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
168         assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
169 
170         driver.findElement(By.id("myId2")).click();
171         verifyWindowName2(driver, "a");
172         assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
173         assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
174 
175         driver.navigate().back();
176         i = i + 4;
177         verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, i));
178         verifyWindowName2(driver, "a");
179         assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
180         assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
181 
182         driver.navigate().back();
183         i = i + 4;
184         verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, i));
185         verifyWindowName2(driver, "a");
186         assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
187         assertEquals(URL_FIRST.toString(), driver.getCurrentUrl());
188 
189         driver.navigate().forward();
190         i = i + 4;
191         verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, i));
192         verifyWindowName2(driver, "a");
193         assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
194         assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
195 
196         driver.navigate().forward();
197         i = i + 4;
198         verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, i));
199         verifyWindowName2(driver, "a");
200         assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
201         assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
202 
203         assertEquals(1, getMockWebConnection().getRequestCount());
204 
205         // because we have changed the window name
206         releaseResources();
207         shutDownAll();
208     }
209 
210     /**
211      * @throws Exception if an error occurs
212      */
213     @Test
214     @Alerts({"[object PopStateEvent]", "{\"hi\":\"there\"}", "true",
215              "[object PopStateEvent]", "{\"hi\":\"there\"}", "true",
216              "[object PopStateEvent]", "null", "true",
217              "[object PopStateEvent]", "null", "true",
218              "[object PopStateEvent]", "{\"hi\":\"there\"}", "true",
219              "[object PopStateEvent]", "{\"hi\":\"there\"}", "true",
220              "[object PopStateEvent]", "{\"hi2\":\"there2\"}", "true",
221              "[object PopStateEvent]", "{\"hi2\":\"there2\"}", "true"})
222     public void pushStateClone() throws Exception {
223         final String html = DOCTYPE_HTML
224                 + "<html>\n"
225                 + "<head>\n"
226                 + "<script>\n"
227                 + LOG_TITLE_FUNCTION
228                 + "  function test() {\n"
229                 + "    if (window.history.pushState) {\n"
230                 + "      var stateObj = { hi: 'there' };\n"
231                 + "      window.history.pushState(stateObj, 'page 2', 'bar.html');\n"
232                 + "    }\n"
233                 + "  }\n"
234 
235                 + "  function test2() {\n"
236                 + "    if (window.history.pushState) {\n"
237                 + "      var stateObj = { hi2: 'there2' };\n"
238                 + "      window.history.pushState(stateObj, 'page 3', 'bar2.html');\n"
239                 + "    }\n"
240                 + "  }\n"
241 
242                 + "  function popMe(event) {\n"
243                 + "    var e = event ? event : window.event;\n"
244                 + "    log(e);\n"
245                 + "    log(JSON.stringify(e.state));\n"
246                 + "    log(e.state == history.state);\n"
247                 + "  }\n"
248 
249                 + "  function setWindowName() {\n"
250                 + "    window.name = window.name + 'a\\u00a7';\n"
251                 + "  }\n"
252 
253                 + "  window.addEventListener('popstate', popMe);\n"
254                 + "</script>\n"
255                 + "</head>\n"
256                 + "<body onpopstate='popMe(event)' onload='setWindowName()' onbeforeunload='setWindowName()' "
257                                                                 + "onunload='setWindowName()'>\n"
258                 + "  <button id=myId onclick='test()'>Click me</button>\n"
259                 + "  <button id=myId2 onclick='test2()'>Click me</button>\n"
260                 + "</body></html>";
261 
262         final String[] expectedAlerts = getExpectedAlerts();
263         int i = 0;
264         final WebDriver driver = loadPage2(html);
265         verifyWindowName2(driver, "a");
266 
267         final long start = (Long) ((JavascriptExecutor) driver).executeScript("return window.history.length");
268 
269         final long waitTime = 4 * DEFAULT_WAIT_TIME.toMillis();
270         if (expectedAlerts.length != 0) {
271             driver.findElement(By.id("myId")).click();
272             verifyWindowName2(driver, "a");
273             assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
274             assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
275 
276             driver.findElement(By.id("myId2")).click();
277             verifyWindowName2(driver, "a");
278             assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
279             assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
280 
281             driver.navigate().back();
282             i = 6;
283             verifyTitle2(Duration.ofMillis(waitTime), driver, Arrays.copyOfRange(expectedAlerts, 0, i));
284             verifyWindowName2(driver, "a");
285             assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
286             assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
287 
288             driver.navigate().back();
289             i = i + 6;
290             verifyTitle2(Duration.ofMillis(waitTime), driver, Arrays.copyOfRange(expectedAlerts, 0, i));
291             verifyWindowName2(driver, "a");
292             assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
293             assertEquals(URL_FIRST.toString(), driver.getCurrentUrl());
294 
295             driver.navigate().forward();
296             i = i + 6;
297             verifyTitle2(Duration.ofMillis(waitTime), driver, Arrays.copyOfRange(expectedAlerts, 0, i));
298             verifyWindowName2(driver, "a");
299             assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
300             assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
301 
302             driver.navigate().forward();
303             i = i + 6;
304             verifyTitle2(Duration.ofMillis(waitTime), driver, Arrays.copyOfRange(expectedAlerts, 0, i));
305             verifyWindowName2(driver, "a");
306             assertEquals(start + 2, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
307             assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
308         }
309 
310         assertEquals(1, getMockWebConnection().getRequestCount());
311 
312         // because we have changed the window name
313         releaseResources();
314         shutDownAll();
315     }
316 
317     /**
318      * @throws Exception if an error occurs
319      */
320     @Test
321     @Alerts({"true", "true"})
322     public void pushStateLocationHref() throws Exception {
323         final String html = DOCTYPE_HTML
324                 + "<html>\n"
325                 + "<head>\n"
326                 + "<script>\n"
327                 + LOG_TITLE_FUNCTION
328                 + "  function test() {\n"
329                 + "    if (!window.history.pushState) { log('no pushState'); return }\n"
330                 + "    try {\n"
331                 + "      var stateObj = { hi: 'there' };\n"
332                 + "      window.history.pushState(stateObj, 'page 2', 'bar.html');\n"
333                 + "      log(location.href.indexOf('bar.html') > -1);\n"
334                 + "    } catch(e) { logEx(e); }\n"
335                 + "  }\n"
336 
337                 + "  function test2() {\n"
338                 + "    if (!window.history.pushState) { log('no pushState'); return }\n"
339                 + "    try {\n"
340                 + "      var stateObj = { hi2: 'there2' };\n"
341                 + "      window.history.pushState(stateObj, 'page 3', 'bar2.html');\n"
342                 + "      log(location.href.indexOf('bar2.html') > -1);\n"
343                 + "    } catch(e) { logEx(e); }\n"
344                 + "  }\n"
345 
346                 + "</script>\n"
347                 + "</head>\n"
348                 + "<body>\n"
349                 + "  <button id=myId onclick='test()'>Click me</button>\n"
350                 + "  <button id=myId2 onclick='test2()'>Click me</button>\n"
351                 + "</body></html>";
352 
353         final String[] expectedAlerts = getExpectedAlerts();
354         final WebDriver driver = loadPage2(html);
355         driver.findElement(By.id("myId")).click();
356         verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, 1));
357 
358         assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
359         assertEquals(URL_FIRST + "bar.html", ((JavascriptExecutor) driver).executeScript("return location.href"));
360         driver.findElement(By.id("myId2")).click();
361         verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, 2));
362 
363         assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
364         assertEquals(URL_FIRST + "bar2.html", ((JavascriptExecutor) driver).executeScript("return location.href"));
365         driver.navigate().back();
366         assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
367         driver.navigate().back();
368         assertEquals(URL_FIRST.toString(), driver.getCurrentUrl());
369         driver.navigate().forward();
370         assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
371         driver.navigate().forward();
372         assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
373     }
374 
375     /**
376      * @throws Exception if an error occurs
377      */
378     @Test
379     @Alerts({"[object PopStateEvent]", "null",
380              "[object PopStateEvent]", "null",
381              "[object PopStateEvent]", "{\"hi2\":\"there2\"}",
382              "[object PopStateEvent]", "{\"hi2\":\"there2\"}"})
383     public void replaceState() throws Exception {
384         final String html = DOCTYPE_HTML
385                 + "<html>\n"
386                 + "<head>\n"
387                 + "<script>\n"
388                 + LOG_TITLE_FUNCTION
389                 + "  function test() {\n"
390                 + "    if (window.history.pushState) {\n"
391                 + "      var stateObj = { hi: 'there' };\n"
392                 + "      window.history.pushState(stateObj, 'page 2', 'bar.html');\n"
393                 + "    }\n"
394                 + "  }\n"
395 
396                 + "  function test2() {\n"
397                 + "    if (window.history.replaceState) {\n"
398                 + "      var stateObj = { hi2: 'there2' };\n"
399                 + "      window.history.replaceState(stateObj, 'page 3', 'bar2.html');\n"
400                 + "    }\n"
401                 + "  }\n"
402 
403                 + "  function popMe(event) {\n"
404                 + "    var e = event ? event : window.event;\n"
405                 + "    log(e);\n"
406                 + "    log(JSON.stringify(e.state));\n"
407                 + "  }\n"
408 
409                 + "  function setWindowName() {\n"
410                 + "    window.name = window.name + 'a\\u00a7';\n"
411                 + "  }\n"
412 
413                 + "  window.addEventListener('popstate', popMe);\n"
414                 + "</script>\n"
415                 + "</head>\n"
416                 + "<body onpopstate='popMe(event)' onload='setWindowName()' onbeforeunload='setWindowName()' "
417                                                         + "onunload='setWindowName()'>\n"
418                 + "  <button id='myId' onclick='test()'>Click me</button>\n"
419                 + "  <button id='myId2' onclick='test2()'>Click me</button>\n"
420                 + "</body></html>";
421 
422         final String[] expectedAlerts = getExpectedAlerts();
423         int i = 0;
424         final WebDriver driver = loadPage2(html);
425 
426         verifyWindowName2(driver, "a");
427         final long start = (Long) ((JavascriptExecutor) driver).executeScript("return window.history.length");
428 
429         if (expectedAlerts.length != 0) {
430             driver.findElement(By.id("myId")).click();
431             verifyWindowName2(driver, "a");
432             assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
433             assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
434 
435             driver.findElement(By.id("myId2")).click();
436             verifyWindowName2(driver, "a");
437             assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
438             assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
439 
440             driver.navigate().back();
441             i = i + 4;
442             verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, i));
443             verifyWindowName2(driver, "a");
444             assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
445             assertEquals(URL_FIRST.toString(), driver.getCurrentUrl());
446 
447             driver.navigate().forward();
448             i = i + 4;
449             verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, i));
450             verifyWindowName2(driver, "a");
451             assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
452             assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
453         }
454 
455         assertEquals(1, getMockWebConnection().getRequestCount());
456 
457         // because we have changed the window name
458         releaseResources();
459         shutDownAll();
460     }
461 
462     /**
463      * @throws Exception if an error occurs
464      */
465     @Test
466     @Alerts({"[object PopStateEvent]", "null", "true",
467              "[object PopStateEvent]", "null", "true",
468              "[object PopStateEvent]", "{\"hi2\":\"there2\"}", "true",
469              "[object PopStateEvent]", "{\"hi2\":\"there2\"}", "true"})
470     public void replaceStateClone() throws Exception {
471         final String html = DOCTYPE_HTML
472                 + "<html>\n"
473                 + "<head>\n"
474                 + "<script>\n"
475                 + LOG_TITLE_FUNCTION
476                 + "  function test() {\n"
477                 + "    if (window.history.pushState) {\n"
478                 + "      var stateObj = { hi: 'there' };\n"
479                 + "      window.history.pushState(stateObj, 'page 2', 'bar.html');\n"
480                 + "    }\n"
481                 + "  }\n"
482 
483                 + "  function test2() {\n"
484                 + "    if (window.history.replaceState) {\n"
485                 + "      var stateObj = { hi2: 'there2' };\n"
486                 + "      window.history.replaceState(stateObj, 'page 3', 'bar2.html');\n"
487                 + "    }\n"
488                 + "  }\n"
489 
490                 + "  function popMe(event) {\n"
491                 + "    var e = event ? event : window.event;\n"
492                 + "    log(e);\n"
493                 + "    log(JSON.stringify(e.state));\n"
494                 + "    log(e.state == history.state);\n"
495                 + "  }\n"
496 
497                 + "  function setWindowName() {\n"
498                 + "    window.name = window.name + 'a\\u00a7';\n"
499                 + "  }\n"
500 
501                 + "  window.addEventListener('popstate', popMe);\n"
502                 + "</script>\n"
503                 + "</head>\n"
504                 + "<body onpopstate='popMe(event)' onload='setWindowName()' onbeforeunload='setWindowName()' "
505                                                         + "onunload='setWindowName()'>\n"
506                 + "  <button id=myId onclick='test()'>Click me</button>\n"
507                 + "  <button id=myId2 onclick='test2()'>Click me</button>\n"
508                 + "</body></html>";
509 
510         final String[] expectedAlerts = getExpectedAlerts();
511         int i = 0;
512         final WebDriver driver = loadPage2(html);
513 
514         verifyWindowName2(driver, "a");
515         final long start = (Long) ((JavascriptExecutor) driver).executeScript("return window.history.length");
516 
517         if (expectedAlerts.length != 0) {
518             driver.findElement(By.id("myId")).click();
519             verifyWindowName2(driver, "a");
520             assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
521             assertEquals(URL_FIRST + "bar.html", driver.getCurrentUrl());
522 
523             driver.findElement(By.id("myId2")).click();
524             verifyWindowName2(driver, "a");
525             assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
526             assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
527 
528             driver.navigate().back();
529             i = i + 6;
530             verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, i));
531             verifyWindowName2(driver, "a");
532             assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
533             assertEquals(URL_FIRST.toString(), driver.getCurrentUrl());
534 
535             driver.navigate().forward();
536             i = i + 6;
537             verifyTitle2(driver, Arrays.copyOfRange(expectedAlerts, 0, i));
538             verifyWindowName2(driver, "a");
539             assertEquals(start + 1, ((JavascriptExecutor) driver).executeScript("return window.history.length"));
540             assertEquals(URL_FIRST + "bar2.html", driver.getCurrentUrl());
541         }
542 
543         assertEquals(1, getMockWebConnection().getRequestCount());
544 
545         // because we have changed the window name
546         releaseResources();
547         shutDownAll();
548     }
549 
550     /**
551      * @throws Exception if an error occurs
552      */
553     @Test
554     @Alerts(DEFAULT = {"href=§§URL§§", "href=§§URL§§?p=%C3%84"},
555             FF = {"href=§§URL§§", "href=§§URL§§?p=%C4"},
556             FF_ESR = {"href=§§URL§§", "href=§§URL§§?p=%C4"})
557     @HtmlUnitNYI(CHROME = {"href=§§URL§§", "href=§§URL§§?p=%C4"},
558             EDGE = {"href=§§URL§§", "href=§§URL§§?p=%C4"})
559     public void pushStateEncoding() throws Exception {
560         final String html = DOCTYPE_HTML
561                 + "<html>\n"
562                 + "<head>\n"
563                 + "<script>\n"
564                 + LOG_TITLE_FUNCTION
565                 + "  function test() {\n"
566                 + "    log('href=' + location.href);\n"
567                 + "    window.history.pushState(null, '', '" + URL_FIRST + "?p=\u00c4');\n"
568                 + "    log('href=' + location.href);\n"
569                 + "  }\n"
570                 + "</script>\n"
571                 + "</head>\n"
572                 + "<body onload='test()'>\n"
573                 + "</body></html>";
574 
575         expandExpectedAlertsVariables(URL_FIRST);
576         loadPageVerifyTitle2(html);
577     }
578 
579     /**
580      * @throws Exception if an error occurs
581      */
582     @Test
583     @Alerts({"href=§§URL§§", "hash=", "href=§§URL§§#foo", "hash=#foo", "onhashchange #proof"})
584     public void pushStateChangeHash() throws Exception {
585         final String html = DOCTYPE_HTML
586                 + "<html>\n"
587                 + "<head>\n"
588                 + "<script>\n"
589                 + LOG_TITLE_FUNCTION
590                 + "  function test() {\n"
591                 + "    log('href=' + location.href);\n"
592                 + "    log('hash=' + location.hash);\n"
593                 + "    window.history.pushState({ hi: 'there' }, '', '" + URL_FIRST + "#foo');\n"
594                 + "    log('href=' + location.href);\n"
595                 + "    log('hash=' + location.hash);\n"
596                 // to make sure we have the event handler registered
597                 + "    location.hash = 'proof';"
598                 + "  }\n"
599 
600                 + " function locationHashChanged(event) {\n"
601                 + "   log('onhashchange ' + location.hash);\n"
602                 + " }\n"
603                 + " window.onhashchange = locationHashChanged;\n"
604                 + "</script>\n"
605                 + "</head>\n"
606                 + "<body onload='test()'>\n"
607                 + "</body></html>";
608 
609         expandExpectedAlertsVariables(URL_FIRST);
610         loadPageVerifyTitle2(html);
611     }
612 
613     /**
614      * @throws Exception if the test fails
615      */
616     @Test
617     @Alerts({"href=§§URL§§", "href=§§URL§§"})
618     public void replaceStateNull() throws Exception {
619         final String html = DOCTYPE_HTML
620                 + "<html>\n"
621                 + "<head>\n"
622                 + "<script>\n"
623                 + LOG_TITLE_FUNCTION
624                 + "  function test() {\n"
625                 + "    log('href=' + location.href);\n"
626                 + "    window.history.replaceState(null, '', null);\n"
627                 + "    log('href=' + location.href);\n"
628                 + "  }\n"
629                 + "</script>\n"
630                 + "</head>\n"
631                 + "<body onload='test()'>\n"
632                 + "</body></html>";
633 
634         expandExpectedAlertsVariables(URL_FIRST);
635         loadPageVerifyTitle2(html);
636     }
637 
638     /**
639      * @throws Exception if the test fails
640      */
641     @Test
642     @Alerts({"href=§§URL§§", "href=§§URL§§"})
643     public void replaceStateUndefined() throws Exception {
644         final String html = DOCTYPE_HTML
645                 + "<html>\n"
646                 + "<head>\n"
647                 + "<script>\n"
648                 + LOG_TITLE_FUNCTION
649                 + "  function test() {\n"
650                 + "    log('href=' + location.href);\n"
651                 + "    window.history.replaceState(null, '', undefined);\n"
652                 + "    log('href=' + location.href);\n"
653                 + "  }\n"
654                 + "</script>\n"
655                 + "</head>\n"
656                 + "<body onload='test()'>\n"
657                 + "</body></html>";
658 
659         expandExpectedAlertsVariables(URL_FIRST);
660         loadPageVerifyTitle2(html);
661     }
662 
663     /**
664      * @throws Exception if the test fails
665      */
666     @Test
667     @Alerts({"href=§§URL§§", "href=§§URL§§undefined"})
668     public void replaceStateUndefinedString() throws Exception {
669         final String html = DOCTYPE_HTML
670                 + "<html>\n"
671                 + "<head>\n"
672                 + "<script>\n"
673                 + LOG_TITLE_FUNCTION
674                 + "  function test() {\n"
675                 + "    log('href=' + location.href);\n"
676                 + "    window.history.replaceState(null, '', 'undefined');\n"
677                 + "    log('href=' + location.href);\n"
678                 + "  }\n"
679                 + "</script>\n"
680                 + "</head>\n"
681                 + "<body onload='test()'>\n"
682                 + "</body></html>";
683 
684         expandExpectedAlertsVariables(URL_FIRST);
685         loadPageVerifyTitle2(html);
686     }
687 
688     /**
689      * @throws Exception if the test fails
690      */
691     @Test
692     @Alerts({"href=§§URL§§", "href=§§URL§§[object%20Object]"})
693     @HtmlUnitNYI(CHROME = {"href=§§URL§§", "href=§§URL§§%5Bobject%20Object%5D"},
694                  EDGE = {"href=§§URL§§", "href=§§URL§§%5Bobject%20Object%5D"},
695                  FF = {"href=§§URL§§", "href=§§URL§§%5Bobject%20Object%5D"},
696                  FF_ESR = {"href=§§URL§§", "href=§§URL§§%5Bobject%20Object%5D"})
697     public void replaceStateObj() throws Exception {
698         final String html = DOCTYPE_HTML
699                 + "<html>\n"
700                 + "<head>\n"
701                 + "<script>\n"
702                 + LOG_TITLE_FUNCTION
703                 + "  function test() {\n"
704                 + "    log('href=' + location.href);\n"
705                 + "    "
706                 + "    window.history.replaceState(null, '', { val: 'abcd' });\n"
707                 + "    log('href=' + location.href);\n"
708                 + "  }\n"
709                 + "</script>\n"
710                 + "</head>\n"
711                 + "<body onload='test()'>\n"
712                 + "</body></html>";
713 
714         expandExpectedAlertsVariables(URL_FIRST);
715         loadPageVerifyTitle2(html);
716     }
717 
718     /**
719      * @throws Exception if the test fails
720      */
721     @Test
722     @Alerts({"href=§§URL§§", "href=§§URL§§"})
723     public void pushStateNull() throws Exception {
724         final String html = DOCTYPE_HTML
725                 + "<html>\n"
726                 + "<head>\n"
727                 + "<script>\n"
728                 + LOG_TITLE_FUNCTION
729                 + "  function test() {\n"
730                 + "    log('href=' + location.href);\n"
731                 + "    window.history.pushState(null, '', null);\n"
732                 + "    log('href=' + location.href);\n"
733                 + "  }\n"
734                 + "</script>\n"
735                 + "</head>\n"
736                 + "<body onload='test()'>\n"
737                 + "</body></html>";
738 
739         expandExpectedAlertsVariables(URL_FIRST);
740         loadPageVerifyTitle2(html);
741     }
742 
743     /**
744      * @throws Exception if the test fails
745      */
746     @Test
747     @Alerts({"href=§§URL§§", "href=§§URL§§"})
748     public void pushStateUndefined() throws Exception {
749         final String html = DOCTYPE_HTML
750                 + "<html>\n"
751                 + "<head>\n"
752                 + "<script>\n"
753                 + LOG_TITLE_FUNCTION
754                 + "  function test() {\n"
755                 + "    log('href=' + location.href);\n"
756                 + "    window.history.pushState(null, '', undefined);\n"
757                 + "    log('href=' + location.href);\n"
758                 + "  }\n"
759                 + "</script>\n"
760                 + "</head>\n"
761                 + "<body onload='test()'>\n"
762                 + "</body></html>";
763 
764         expandExpectedAlertsVariables(URL_FIRST);
765         loadPageVerifyTitle2(html);
766     }
767 
768     /**
769      * @throws Exception if the test fails
770      */
771     @Test
772     @Alerts({"href=§§URL§§", "href=§§URL§§undefined"})
773     public void pushStateUndefinedString() throws Exception {
774         final String html = DOCTYPE_HTML
775                 + "<html>\n"
776                 + "<head>\n"
777                 + "<script>\n"
778                 + LOG_TITLE_FUNCTION
779                 + "  function test() {\n"
780                 + "    log('href=' + location.href);\n"
781                 + "    window.history.pushState(null, '', 'undefined');\n"
782                 + "    log('href=' + location.href);\n"
783                 + "  }\n"
784                 + "</script>\n"
785                 + "</head>\n"
786                 + "<body onload='test()'>\n"
787                 + "</body></html>";
788 
789         expandExpectedAlertsVariables(URL_FIRST);
790         loadPageVerifyTitle2(html);
791     }
792 
793     /**
794      * @throws Exception if the test fails
795      */
796     @Test
797     @Alerts({"href=§§URL§§", "href=§§URL§§[object%20Object]"})
798     @HtmlUnitNYI(CHROME = {"href=§§URL§§", "href=§§URL§§%5Bobject%20Object%5D"},
799             EDGE = {"href=§§URL§§", "href=§§URL§§%5Bobject%20Object%5D"},
800             FF = {"href=§§URL§§", "href=§§URL§§%5Bobject%20Object%5D"},
801             FF_ESR = {"href=§§URL§§", "href=§§URL§§%5Bobject%20Object%5D"})
802     public void pushStateObj() throws Exception {
803         final String html = DOCTYPE_HTML
804                 + "<html>\n"
805                 + "<head>\n"
806                 + "<script>\n"
807                 + LOG_TITLE_FUNCTION
808                 + "  function test() {\n"
809                 + "    log('href=' + location.href);\n"
810                 + "    window.history.pushState(null, '', { val: 'abcd' });\n"
811                 + "    log('href=' + location.href);\n"
812                 + "  }\n"
813                 + "</script>\n"
814                 + "</head>\n"
815                 + "<body onload='test()'>\n"
816                 + "</body></html>";
817 
818         expandExpectedAlertsVariables(URL_FIRST);
819         loadPageVerifyTitle2(html);
820     }
821 
822     /**
823      * @throws Exception if an error occurs
824      */
825     @Test
826     @Alerts({"href=§§URL§§", "hash=", "href=§§URL§§#foo", "hash=#foo", "onhashchange #proof"})
827     public void pushStateChangeHashNoStore() throws Exception {
828         final String html = DOCTYPE_HTML
829                 + "<html>\n"
830                 + "<head>\n"
831                 + "<script>\n"
832                 + LOG_TITLE_FUNCTION
833                 + "  function test() {\n"
834                 + "    log('href=' + location.href);\n"
835                 + "    log('hash=' + location.hash);\n"
836                 + "    window.history.pushState({ hi: 'there' }, '', '" + URL_FIRST + "#foo');\n"
837                 + "    log('href=' + location.href);\n"
838                 + "    log('hash=' + location.hash);\n"
839                 // to make sure we have the event handler registered
840                 + "    location.hash = 'proof';"
841                 + "  }\n"
842 
843                 + " function locationHashChanged(event) {\n"
844                 + "   log('onhashchange ' + location.hash);\n"
845                 + " }\n"
846                 + " window.onhashchange = locationHashChanged;\n"
847                 + "</script>\n"
848                 + "</head>\n"
849                 + "<body onload='test()'>\n"
850                 + "</body></html>";
851 
852         expandExpectedAlertsVariables(URL_FIRST);
853 
854         final List<NameValuePair> headers = new ArrayList<>();
855         headers.add(new NameValuePair("Cache-Control", "no-store"));
856         getMockWebConnection().setResponse(URL_FIRST, html, 200, "OK", "text/html;charset=ISO-8859-1",
857             ISO_8859_1, headers);
858 
859         final WebDriver driver = loadPage2(URL_FIRST, null);
860         verifyTitle2(driver, getExpectedAlerts());
861     }
862 
863     /**
864      * @throws Exception if an error occurs
865      */
866     @Test
867     @Alerts(DEFAULT = {"href=§§URL§§", "href=§§URL§§?p=%C3%84"},
868             FF = {"href=§§URL§§", "href=§§URL§§?p=%C4"},
869             FF_ESR = {"href=§§URL§§", "href=§§URL§§?p=%C4"})
870     @HtmlUnitNYI(CHROME = {"href=§§URL§§", "href=§§URL§§?p=%C4"},
871             EDGE = {"href=§§URL§§", "href=§§URL§§?p=%C4"})
872     public void replaceStateEncoding() throws Exception {
873         final String html = DOCTYPE_HTML
874                 + "<html>\n"
875                 + "<head>\n"
876                 + "<script>\n"
877                 + LOG_TITLE_FUNCTION
878                 + "  function test() {\n"
879                 + "    log('href=' + location.href);\n"
880                 + "    window.history.replaceState(null, '', '" + URL_FIRST + "?p=\u00c4');\n"
881                 + "    log('href=' + location.href);\n"
882                 + "  }\n"
883                 + "</script>\n"
884                 + "</head>\n"
885                 + "<body onload='test()'>\n"
886                 + "</body></html>";
887 
888         expandExpectedAlertsVariables(URL_FIRST);
889         loadPageVerifyTitle2(html);
890     }
891 
892     /**
893      * @throws Exception if an error occurs
894      */
895     @Test
896     @Alerts({"href=§§URL§§", "hash=", "href=§§URL§§#foo", "hash=#foo", "onhashchange #proof"})
897     public void replaceStateChangeHash() throws Exception {
898         final String html = DOCTYPE_HTML
899                 + "<html>\n"
900                 + "<head>\n"
901                 + "<script>\n"
902                 + LOG_TITLE_FUNCTION
903                 + "  function test() {\n"
904                 + "    log('href=' + location.href);\n"
905                 + "    log('hash=' + location.hash);\n"
906                 + "    window.history.replaceState({ hi: 'there' }, '', '" + URL_FIRST + "#foo');\n"
907                 + "    log('href=' + location.href);\n"
908                 + "    log('hash=' + location.hash);\n"
909 
910                 // to make sure we have the event handler registered
911                 + "    location.hash = 'proof';"
912                 + "  }\n"
913 
914                 + " function locationHashChanged(event) {\n"
915                 + "   log('onhashchange ' + location.hash);\n"
916                 + " }\n"
917                 + " window.onhashchange = locationHashChanged;\n"
918                 + "</script>\n"
919                 + "</head>\n"
920                 + "<body onload='test()'>\n"
921                 + "</body></html>";
922 
923         expandExpectedAlertsVariables(URL_FIRST);
924         loadPageVerifyTitle2(html);
925     }
926 
927     /**
928      * @throws Exception if an error occurs
929      */
930     @Test
931     @Alerts({"href=§§URL§§", "hash=", "href=§§URL§§#foo", "hash=#foo", "onhashchange #proof"})
932     public void replaceStateChangeHashNoStore() throws Exception {
933         final String html = DOCTYPE_HTML
934                 + "<html>\n"
935                 + "<head>\n"
936                 + "<script>\n"
937                 + LOG_TITLE_FUNCTION
938                 + "  function test() {\n"
939                 + "    log('href=' + location.href);\n"
940                 + "    log('hash=' + location.hash);\n"
941                 + "    window.history.replaceState({ hi: 'there' }, '', '" + URL_FIRST + "#foo');\n"
942                 + "    log('href=' + location.href);\n"
943                 + "    log('hash=' + location.hash);\n"
944 
945                 // to make sure we have the event handler registered
946                 + "    location.hash = 'proof';"
947                 + "  }\n"
948 
949                 + " function locationHashChanged(event) {\n"
950                 + "   log('onhashchange ' + location.hash);\n"
951                 + " }\n"
952                 + " window.onhashchange = locationHashChanged;\n"
953                 + "</script>\n"
954                 + "</head>\n"
955                 + "<body onload='test()'>\n"
956                 + "</body></html>";
957 
958         expandExpectedAlertsVariables(URL_FIRST);
959 
960         final List<NameValuePair> headers = new ArrayList<>();
961         headers.add(new NameValuePair("Cache-Control", "no-store"));
962         getMockWebConnection().setResponse(URL_FIRST, html, 200, "OK", "text/html;charset=ISO-8859-1",
963             ISO_8859_1, headers);
964 
965         final WebDriver driver = loadPage2(URL_FIRST, null);
966         verifyTitle2(driver, getExpectedAlerts());
967     }
968 
969     /**
970      * @throws Exception if an error occurs
971      */
972     @Test
973     public void length() throws Exception {
974         final String second = DOCTYPE_HTML
975                 + "<html>\n"
976                 + "<head></head>\n"
977                 + "<body>\n"
978                 + "<a name='length' href='' onclick='alert(history.length);return false;'>length</a><br>\n"
979                 + "<a name='x' href='#x'>x</a><br>\n"
980                 + "</body></html>\n";
981 
982         getMockWebConnection().setResponse(URL_SECOND, second);
983 
984         final String html = DOCTYPE_HTML
985                 + "<html>\n"
986                 + "<head></head>\n"
987                 + "<body>\n"
988                 + "<a name='length' href='' onclick='alert(history.length);return false;'>length</a><br>\n"
989                 + "<a name='b' href='" + URL_SECOND + "'>b</a><br>\n"
990                 + "</body></html>\n";
991 
992         final WebDriver driver = loadPage2(html);
993         driver.findElement(By.name("length")).click();
994 
995         // when testing with real browsers we are facing different offsets
996         final int start = Integer.parseInt(getCollectedAlerts(driver, 1).get(0));
997 
998         driver.findElement(By.name("b")).click();
999         driver.findElement(By.name("length")).click();
1000         assertEquals(new String[] {"" + (start + 1)}, getCollectedAlerts(driver, 1));
1001 
1002         driver.findElement(By.name("x")).click();
1003         driver.findElement(By.name("length")).click();
1004         assertEquals(new String[] {"" + (start + 2)}, getCollectedAlerts(driver, 1));
1005     }
1006 
1007     /**
1008      * History.previous was defined in old FF versions.
1009      *
1010      * @throws Exception if an error occurs
1011      */
1012     @Test
1013     @Alerts("undefined")
1014     public void previous() throws Exception {
1015         final String html = DOCTYPE_HTML
1016                 + "<html>\n"
1017                 + "<head>\n"
1018                 + "<script>\n"
1019                 + LOG_TITLE_FUNCTION
1020                 + "</script>\n"
1021                 + "</head>\n"
1022                 + "<body>\n"
1023                 + "<a name='itemZero' href='' onclick='log(history.previous); return false;'>item zero</a>\n"
1024                 + "</body></html>\n";
1025 
1026         final WebDriver driver = loadPage2(html);
1027         driver.findElement(By.name("itemZero")).click();
1028 
1029         verifyTitle2(driver, getExpectedAlerts());
1030     }
1031 
1032     /**
1033      * History.current was defined in old FF versions.
1034      *
1035      * @throws Exception if an error occurs
1036      */
1037     @Test
1038     @Alerts("undefined")
1039     public void current() throws Exception {
1040         final String html = DOCTYPE_HTML
1041                 + "<html>\n"
1042                 + "<head>\n"
1043                 + "<script>\n"
1044                 + LOG_TITLE_FUNCTION
1045                 + "</script>\n"
1046                 + "</head>\n"
1047                 + "<body>\n"
1048                 + "<a name='itemZero' href='' onclick='log(history.current); return false;'>item zero</a>\n"
1049                 + "</body></html>\n";
1050 
1051         final WebDriver driver = loadPage2(html);
1052         driver.findElement(By.name("itemZero")).click();
1053 
1054         verifyTitle2(driver, getExpectedAlerts());
1055     }
1056 
1057     /**
1058      * History.next was defined in old FF versions.
1059      *
1060      * @throws Exception if an error occurs
1061      */
1062     @Test
1063     @Alerts("undefined")
1064     public void next() throws Exception {
1065         final String html = DOCTYPE_HTML
1066                 + "<html>\n"
1067                 + "<head>\n"
1068                 + "<script>\n"
1069                 + LOG_TITLE_FUNCTION
1070                 + "</script>\n"
1071                 + "</head>\n"
1072                 + "<body>\n"
1073                 + "<a name='itemZero' href='' onclick='log(history.next); return false;'>item zero</a>\n"
1074                 + "</body></html>\n";
1075 
1076         final WebDriver driver = loadPage2(html);
1077         driver.findElement(By.name("itemZero")).click();
1078 
1079         verifyTitle2(driver, getExpectedAlerts());
1080     }
1081 
1082     /**
1083      * History.item was defined in old FF versions.
1084      *
1085      * @throws Exception if an error occurs
1086      */
1087     @Test
1088     @Alerts("undefined")
1089     public void item() throws Exception {
1090         final String html = DOCTYPE_HTML
1091                 + "<html>\n"
1092                 + "<head>\n"
1093                 + "<script>\n"
1094                 + LOG_TITLE_FUNCTION
1095                 + "</script>\n"
1096                 + "</head>\n"
1097                 + "<body>\n"
1098                 + "<a name='itemZero' href='' onclick='log(history.item); return false;'>item zero</a>\n"
1099                 + "</body></html>\n";
1100 
1101         final WebDriver driver = loadPage2(html);
1102         driver.findElement(By.name("itemZero")).click();
1103 
1104         verifyTitle2(driver, getExpectedAlerts());
1105     }
1106 
1107     /**
1108      * @throws Exception if an error occurs
1109      */
1110     @Test
1111     @Alerts({"false", "false", "false", "false", "false", "false"})
1112     public void byIndex() throws Exception {
1113         final String html = DOCTYPE_HTML
1114                 + "<html>\n"
1115                 + "<head>\n"
1116                 + "<script>\n"
1117                 + LOG_TITLE_FUNCTION
1118                 + "</script>\n"
1119                 + "</head>\n"
1120                 + "<body>\n"
1121                 + "<a name='hasNegativeOne' href='' onclick="
1122                     + "'log(\"-1\" in history);log(-1 in history);return false;'>has negative one</a><br>\n"
1123                 + "<a name='hasZero' href='' onclick="
1124                     + "'log(\"0\" in history);log(0 in history);return false;'>has zero</a><br>\n"
1125                 + "<a name='hasPositiveOne' href='' onclick="
1126                     + "'log(\"1\" in history);log(1 in history);return false;'>has positive one</a><br>\n"
1127                 + "</body></html>\n";
1128 
1129         final WebDriver driver = loadPage2(html);
1130         driver.findElement(By.name("hasNegativeOne")).click();
1131         verifyTitle2(driver, Arrays.copyOfRange(getExpectedAlerts(), 0, 2));
1132         driver.findElement(By.name("hasZero")).click();
1133         verifyTitle2(driver, Arrays.copyOfRange(getExpectedAlerts(), 0, 4));
1134         driver.findElement(By.name("hasPositiveOne")).click();
1135         verifyTitle2(driver, Arrays.copyOfRange(getExpectedAlerts(), 0, 6));
1136     }
1137 
1138     /**
1139      * @throws Exception if an error occurs
1140      */
1141     @Test
1142     @Alerts("undefined")
1143     public void arrayAccess() throws Exception {
1144         final String html = DOCTYPE_HTML
1145                 + "<html>\n"
1146                 + "<head>\n"
1147                 + "<script>\n"
1148                 + LOG_TITLE_FUNCTION
1149                 + "</script>\n"
1150                 + "</head>\n"
1151                 + "<body>\n"
1152                 + "<a name='arrayAccess' href='' onclick='log(history[0]);return false;'>array access</a><br>\n"
1153                 + "</body></html>\n";
1154 
1155         final WebDriver driver = loadPage2(html);
1156         driver.findElement(By.name("arrayAccess")).click();
1157 
1158         verifyTitle2(driver, getExpectedAlerts());
1159     }
1160 
1161     /**
1162      * @throws Exception if an error occurs
1163      */
1164     @Test
1165     @Alerts("null")
1166     public void state() throws Exception {
1167         final String html = DOCTYPE_HTML
1168                 + "<html><head><script>\n"
1169                 + LOG_TITLE_FUNCTION
1170                 + "  function test() {\n"
1171                 + "    log(history.state);\n"
1172                 + "  }\n"
1173                 + "</script></head>\n"
1174                 + "<body onload='test()'>\n"
1175                 + "</body></html>";
1176 
1177         loadPageVerifyTitle2(html);
1178     }
1179 
1180     /**
1181      * @throws Exception if an error occurs
1182      */
1183     @Test
1184     @Alerts({"back", "forward", "go", "length", "pushState", "replaceState",
1185              "scrollRestoration", "state"})
1186     public void properties() throws Exception {
1187         final String html = DOCTYPE_HTML
1188                 + "<html><head><script>\n"
1189                 + LOG_TITLE_FUNCTION
1190                 + "  function test() {\n"
1191                 + "    var props = [];\n"
1192                 + "    var i = 0;\n"
1193                 + "    for (prop in window.history) {\n"
1194                 + "      props[i++] = prop;\n"
1195                 + "    }\n"
1196                 + "    props.sort();\n"
1197                 + "    for (i = 0; i < props.length; i++) {\n"
1198                 + "      log(props[i]);\n"
1199                 + "    }\n"
1200                 + "  }\n"
1201                 + "</script></head>\n"
1202                 + "<body onload='test()'>\n"
1203                 + "</body></html>";
1204 
1205         loadPageVerifyTitle2(html);
1206     }
1207 
1208     /**
1209      * Test that a new page loads after history.pushState() is called.
1210      * @throws Exception if test fails
1211      */
1212     @Test
1213     public void loadPageAfterPushState() throws Exception {
1214         final String html = DOCTYPE_HTML
1215                 + "<html>\n"
1216                 + "<head>\n"
1217                 + "<title>page1</title>\n"
1218                 + "<script>\n"
1219                 + "  function pushState() {\n"
1220                 + "    window.history.pushState({'key':'value'});\n"
1221                 + "  }\n"
1222                 + "</script>\n"
1223                 + "</head>\n"
1224                 + "<body onload='pushState()'>\n"
1225                 + "</body></html>";
1226         final String html2 = DOCTYPE_HTML
1227                 + "<html>\n"
1228                 + "<head>\n"
1229                 + "<title>page2</title>\n"
1230                 + "</head>\n"
1231                 + "<body>\n"
1232                 + "</body></html>";
1233 
1234         final WebDriver driver = loadPage2(html);
1235         assertTitle(driver, "page1");
1236 
1237         loadPage2(html2, URL_SECOND);
1238         assertTitle(driver, "page2");
1239     }
1240 
1241     /**
1242      * @throws Exception if an error occurs
1243      */
1244     @Test
1245     @Alerts({"auto", "manual", "auto", "auto", "auto", "auto"})
1246     public void scrollRestoration() throws Exception {
1247         final String html = DOCTYPE_HTML
1248                 + "<html><head><script>\n"
1249                 + LOG_TITLE_FUNCTION
1250                 + "  function test() {\n"
1251                 + "    log(history.scrollRestoration);\n"
1252 
1253                 + "    history.scrollRestoration = 'manual';\n"
1254                 + "    log(history.scrollRestoration);\n"
1255 
1256                 + "    history.scrollRestoration = 'auto';\n"
1257                 + "    log(history.scrollRestoration);\n"
1258 
1259                 + "    history.scrollRestoration = 'MaNUaL';\n"
1260                 + "    log(history.scrollRestoration);\n"
1261 
1262                 + "    history.scrollRestoration = 'unknown';\n"
1263                 + "    log(history.scrollRestoration);\n"
1264 
1265                 + "    history.scrollRestoration = undefined;\n"
1266                 + "    log(history.scrollRestoration);\n"
1267 
1268                 + "  }\n"
1269                 + "</script></head>\n"
1270                 + "<body onload='test()'>\n"
1271                 + "</body></html>";
1272 
1273         loadPageVerifyTitle2(html);
1274     }
1275 
1276     /**
1277      * @throws Exception if an error occurs
1278      */
1279     @Test
1280     public void testHistoryBackAndForwarWithNoStoreCacheControlHeader() throws Exception {
1281         final String html = DOCTYPE_HTML
1282             + "<html><body>"
1283             + "<a id='startButton' href='" + URL_SECOND + "'>Start</a>\n"
1284             + "</body></html>";
1285         final String secondContent = DOCTYPE_HTML
1286             + "<html><head></head>\n"
1287             + "<body>\n"
1288             + "  <a id='nextButton' href='" + URL_THIRD + "'>Next</a>\n"
1289             + "  <a id='forwardButton' onclick='javascript:window.history.forward()'>Forward</a>\n"
1290             + "</body></html>";
1291         final String thirdContent = DOCTYPE_HTML
1292             + "<html><body>"
1293             + "<a id='backButton' onclick='javascript:window.history.back()'>Back</a>\n"
1294             + "</body></html>";
1295 
1296         final List<NameValuePair> headers = new ArrayList<>();
1297         headers.add(new NameValuePair("Cache-Control", "some-other-value, no-store"));
1298         getMockWebConnection().setResponse(URL_SECOND, secondContent, 200, "OK", "text/html;charset=ISO-8859-1",
1299             ISO_8859_1, headers);
1300         getMockWebConnection().setResponse(URL_THIRD, thirdContent, 200, "OK", "text/html;charset=ISO-8859-1",
1301             ISO_8859_1, headers);
1302 
1303         final WebDriver driver = loadPage2(html);
1304         driver.findElement(By.id("startButton")).click();
1305         driver.findElement(By.id("nextButton")).click();
1306         driver.findElement(By.id("backButton")).click();
1307 
1308         assertEquals(URL_SECOND.toString(), driver.getCurrentUrl());
1309         assertEquals(4, getMockWebConnection().getRequestCount());
1310 
1311         driver.findElement(By.id("forwardButton")).click();
1312         assertEquals(URL_THIRD.toString(), driver.getCurrentUrl());
1313         assertEquals(5, getMockWebConnection().getRequestCount());
1314     }
1315 }