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