1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/tests/mochitest/whatwg/test_bug500328.html Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,771 @@ 1.4 +<!DOCTYPE HTML> 1.5 +<html> 1.6 +<!-- 1.7 +https://bugzilla.mozilla.org/show_bug.cgi?id=500328 1.8 +--> 1.9 +<head> 1.10 + <title>Test for Bug 500328</title> 1.11 + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> 1.12 + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> 1.13 + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 1.14 +</head> 1.15 +<body> 1.16 +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500328">Mozilla Bug 500328</a> 1.17 +<p id="display"></p> 1.18 +<div id="status"></div> 1.19 +<div id="content"> 1.20 + <iframe id="iframe"></iframe> 1.21 + <iframe id="iframe2"></iframe> 1.22 + <a id="link">link</a> 1.23 +</div> 1.24 +<pre id="test"> 1.25 +<script type="application/javascript;version=1.7"> 1.26 + 1.27 +/** Test for Bug 500328 **/ 1.28 + 1.29 +SimpleTest.waitForExplicitFinish(); 1.30 + 1.31 +var iframe = document.getElementById("iframe"); 1.32 +var iframeCw = iframe.contentWindow; 1.33 + 1.34 +var iframe2 = document.getElementById("iframe2"); 1.35 +var iframe2Cw = iframe2.contentWindow; 1.36 + 1.37 +const unvisitedColor = "rgb(0, 0, 238)"; 1.38 +const visitedColor = "rgb(85, 26, 139)"; 1.39 + 1.40 +var gCallbackOnIframeLoad = false; 1.41 +var gCallbackOnIframePageShow = false; 1.42 +var gCallbackOnPopState = false; 1.43 +var gNumPopStates = 0; 1.44 +var gLastPopStateEvent; 1.45 +var gLastScriptHistoryState; 1.46 + 1.47 +var gGen; 1.48 + 1.49 +function statusMsg(msg) { 1.50 + var msgElem = document.createElement("p"); 1.51 + msgElem.appendChild(document.createTextNode(msg)); 1.52 + 1.53 + document.getElementById("status").appendChild(msgElem); 1.54 +} 1.55 + 1.56 +function longWait() { 1.57 + function hitEventLoop(times, func) { 1.58 + if (times > 0) { 1.59 + setTimeout(hitEventLoop, 0, times - 1, func); 1.60 + } else { 1.61 + setTimeout(func, 0); 1.62 + } 1.63 + } 1.64 + hitEventLoop(100, function() { gGen.next(); }); 1.65 +} 1.66 + 1.67 +function shortWait() { 1.68 + setTimeout(function() { gGen.next(); }, 0); 1.69 +} 1.70 + 1.71 +function onChildPopState(e) { 1.72 + gNumPopStates++; 1.73 + gLastPopStateEvent = e; 1.74 + if (gCallbackOnPopState) { 1.75 + statusMsg("Popstate(" + JSON.stringify(e.state) + "). Calling gGen.next()."); 1.76 + gCallbackOnPopState = false; 1.77 + gGen.next(); 1.78 + } 1.79 + else { 1.80 + statusMsg("Popstate(" + JSON.stringify(e.state) + "). NOT calling gGen.next()."); 1.81 + } 1.82 +} 1.83 + 1.84 +function onChildScript(state) { 1.85 + gLastScriptHistoryState = state; 1.86 +} 1.87 + 1.88 +function getURLFromEvent(e) { 1.89 + try { 1.90 + var target = e.target; 1.91 + if ("contentWindow" in target) { 1.92 + return target.contentWindow.location.toString(); 1.93 + } 1.94 + if ("ownerDocument" in target && target.ownerDocument) { 1.95 + return target.ownerDocument.location.toString(); 1.96 + } 1.97 + if ("location" in target) { 1.98 + return target.location.toString(); 1.99 + } 1.100 + return target.toString(); 1.101 + } 1.102 + catch(ex) { 1.103 + return "<cross-site object>"; 1.104 + } 1.105 +} 1.106 + 1.107 +function onChildLoad(e) { 1.108 + if(gCallbackOnIframeLoad) { 1.109 + statusMsg("Got load for " + getURLFromEvent(e) + ". About to call gGen.next()."); 1.110 + gCallbackOnIframeLoad = false; 1.111 + gGen.next(); 1.112 + } 1.113 + else { 1.114 + statusMsg("Got load for " + getURLFromEvent(e) + ", but not calling gGen.next() because gCallbackOnIframeLoad was false."); 1.115 + } 1.116 +} 1.117 + 1.118 +function onChildPageShow(e) { 1.119 + if(gCallbackOnIframePageShow) { 1.120 + statusMsg("Got pageshow for " + getURLFromEvent(e) + ". About to call gGen.next()."); 1.121 + gCallbackOnIframePageShow = false; 1.122 + SimpleTest.executeSoon(function() { gGen.next(); }); 1.123 + } 1.124 + else { 1.125 + statusMsg("Got pageshow for " + getURLFromEvent(e) + ", but not calling gGen.next() because gCallbackOnIframePageShow was false."); 1.126 + } 1.127 +} 1.128 + 1.129 +function enableChildLoadCallback() { 1.130 + gCallbackOnIframeLoad = true; 1.131 +} 1.132 + 1.133 +function enableChildPageShowCallback() { 1.134 + gCallbackOnIframePageShow = true; 1.135 +} 1.136 + 1.137 +function enableChildPopStateCallback() { 1.138 + gCallbackOnPopState = true; 1.139 +} 1.140 + 1.141 +function clearPopStateCounter() { 1.142 + gNumPopStates = 0; 1.143 +} 1.144 + 1.145 +function noPopStateExpected(msg) { 1.146 + is(gNumPopStates, 0, msg); 1.147 + 1.148 + // Even if there's an error, set gNumPopStates to 0 so other tests don't 1.149 + // fail. 1.150 + gNumPopStates = 0; 1.151 +} 1.152 + 1.153 +function popstateExpected(msg) { 1.154 + is(gNumPopStates, 1, msg); 1.155 + gNumPopStates = 0; 1.156 +} 1.157 + 1.158 +function getColor(elem) { 1.159 + var utils = SpecialPowers.wrap(document).defaultView. 1.160 + QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor). 1.161 + getInterface(SpecialPowers.Ci.nsIDOMWindowUtils); 1.162 + return utils.getVisitedDependentComputedStyle(elem, "", "color"); 1.163 +} 1.164 + 1.165 +function getSHistory(theWindow) 1.166 +{ 1.167 + const Ci = SpecialPowers.Ci; 1.168 + var sh = SpecialPowers.wrap(theWindow.QueryInterface(Ci.nsIInterfaceRequestor)) 1.169 + .getInterface(Ci.nsIWebNavigation) 1.170 + .sessionHistory; 1.171 + if (!sh || sh == null) 1.172 + throw("Couldn't get shistory for window!"); 1.173 + 1.174 + return sh; 1.175 +} 1.176 + 1.177 +function getSHTitle(sh, offset) 1.178 +{ 1.179 + if (!offset) 1.180 + offset = 0; 1.181 + 1.182 + // False instructs the SHistory not to modify its current index. 1.183 + return sh.getEntryAtIndex(sh.index + offset, false).title; 1.184 +} 1.185 + 1.186 +// Tests that win's location ends with str 1.187 +function locationEndsWith(win, str) { 1.188 + var exp = new RegExp(str + "$"); 1.189 + ok(win.location.toString().match(exp), 1.190 + "Wrong window location. Expected it to end with " + 1.191 + str + ", but actuall was " + win.location); 1.192 +} 1.193 + 1.194 +function expectException(func, msg) { 1.195 + var failed = false; 1.196 + try { 1.197 + func(); 1.198 + } catch(ex) { 1.199 + failed = true; 1.200 + } 1.201 + 1.202 + ok(failed, msg + " succeeded, but should have failed."); 1.203 +} 1.204 + 1.205 +function runTest() { 1.206 + // We can't enable universal XPConnect privleges in this function, because 1.207 + // test 5 needs to be running at normal privleges in order to test the 1.208 + // same-origin policy. 1.209 + 1.210 + /** 1.211 + * PRELIMINARY: 1.212 + * 1. Clear the popstate counter 1.213 + */ 1.214 + 1.215 + clearPopStateCounter(); 1.216 + 1.217 + // The URL of file_bug500328_1.html on http://localhost:8888 1.218 + var innerLoc; 1.219 + 1.220 + // Now we can start the tests 1.221 + 1.222 + /** 1.223 + * TEST 1 tests basic pushState functionality 1.224 + */ 1.225 + enableChildLoadCallback(); 1.226 + iframeCw.location = "file_bug500328_1.html"; 1.227 + yield undefined; 1.228 + innerLoc = iframeCw.location.toString(); 1.229 + // No popstate during initial load. 1.230 + shortWait(); 1.231 + yield undefined; 1.232 + noPopStateExpected("No initial popstate."); 1.233 + is(JSON.stringify(gLastScriptHistoryState), "null", "null initial state."); 1.234 + statusMsg("Awake after first load."); 1.235 + 1.236 + // Make sure that the pushstate below doesn't trigger a hashchange. 1.237 + iframeCw.onhashchange = function() { 1.238 + ok(false, "Pushstate shouldn't trigger a hashchange."); 1.239 + }; 1.240 + 1.241 + var testObj1 = 42; 1.242 + var testObj2 = { x: 4.2 }; 1.243 + iframeCw.history.pushState(testObj1, "test 1"); 1.244 + is(JSON.stringify(iframeCw.history.state), JSON.stringify(testObj1), 1.245 + "correct state after pushState"); 1.246 + is(iframeCw.location.search, "", 1.247 + "First pushstate should leave us where we were."); 1.248 + 1.249 + iframeCw.history.pushState(testObj2, "test 1#foo", "?test1#foo"); 1.250 + is(JSON.stringify(iframeCw.history.state), JSON.stringify(testObj2), 1.251 + "correct state after pushState"); 1.252 + isnot(iframeCw.history.state, testObj2, 1.253 + "correct state object identity after pushState"); 1.254 + is(iframeCw.location.search, "?test1", 1.255 + "Second pushstate should push us to '?test1'."); 1.256 + is(iframeCw.location.hash, "#foo", 1.257 + "Second pushstate should push us to '#foo'"); 1.258 + shortWait(); 1.259 + yield undefined; 1.260 + 1.261 + // Let the hashchange event fire, if it's going to. 1.262 + longWait(); 1.263 + yield undefined; 1.264 + iframeCw.onhashchange = null; 1.265 + 1.266 + statusMsg("About to go back to page 1."); 1.267 + // We don't have to yield here because this back() and the resulting popstate 1.268 + // are completely synchronous. In fact, if we did yield, JS would throw an 1.269 + // error because we'd be calling gGen.next from within gGen.next. 1.270 + iframeCw.history.back(); 1.271 + 1.272 + statusMsg("Awake after going back to page 1."); 1.273 + popstateExpected("Going back to page 1 should trigger a popstate."); 1.274 + is(gLastPopStateEvent.isTrusted, true, 'Popstate event should be trusted.'); 1.275 + is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj1), 1.276 + "Wrong state object popped after going back to page 1."); 1.277 + ok(gLastPopStateEvent.state === iframeCw.history.state, 1.278 + "Wrong state object in document after going back to page 1."); 1.279 + ok(iframeCw.location.toString().match(/file_bug500328_1.html$/), 1.280 + "Going back to page 1 hould take us to original page."); 1.281 + 1.282 + iframeCw.history.back(); 1.283 + popstateExpected("Going back to page 0 should trigger a popstate."); 1.284 + ise(gLastPopStateEvent.state, null, 1.285 + "Going back to page 0 should pop a null state."); 1.286 + ise(iframeCw.history.state, null, 1.287 + "Going back to page 0 should pop a null state."); 1.288 + ise(iframeCw.location.search, "", 1.289 + "Going back to page 0 should clear the querystring."); 1.290 + 1.291 + iframeCw.history.forward(); 1.292 + popstateExpected("Going forward to page 1 should trigger a popstate."); 1.293 + is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj1), 1.294 + "Wrong state object popped after going forward to page 1."); 1.295 + ise(gLastPopStateEvent.state, iframeCw.history.state, 1.296 + "Wrong state object in document after going forward to page 1."); 1.297 + ok(iframeCw.location.toString().match(/file_bug500328_1.html$/), 1.298 + "Going forward to page 1 should leave us at original page."); 1.299 + 1.300 + statusMsg("About to go forward to page 2."); 1.301 + iframeCw.history.forward(); 1.302 + statusMsg("Awake after going forward to page 2."); 1.303 + popstateExpected("Going forward to page 2 should trigger a popstate."); 1.304 + is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj2), 1.305 + "Wrong state object popped after going forward to page 2."); 1.306 + ise(iframeCw.history.state, gLastPopStateEvent.state, 1.307 + "Wrong state object in document after going forward to page 2."); 1.308 + ok(iframeCw.location.toString().match(/file_bug500328_1.html\?test1#foo$/), 1.309 + "Going forward to page 2 took us to " + iframeCw.location.toString()); 1.310 + 1.311 + statusMsg("About to reload page 2."); 1.312 + iframeCw.location.reload(); 1.313 + enableChildLoadCallback(); 1.314 + yield undefined; 1.315 + statusMsg("Awake after reloading page 2."); 1.316 + noPopStateExpected("Reloading page 2 should not trigger popstate."); 1.317 + is(JSON.stringify(iframeCw.history.state), JSON.stringify(testObj2), 1.318 + "Wrong state object after reloading page 2."); 1.319 + is(JSON.stringify(gLastScriptHistoryState), JSON.stringify(testObj2), 1.320 + "Wrong state object while reloading page 2."); 1.321 + ok(iframeCw.location.toString().match(/file_bug500328_1.html\?test1#foo$/), 1.322 + "Reloading page 2 took us to " + iframeCw.location.toString()); 1.323 + 1.324 + // The iframe's current location is file_bug500328_1.html?test1#foo. 1.325 + // Clicking link1 should take us to file_bug500328_1.html?test1#1. 1.326 + 1.327 + enableChildPopStateCallback(); 1.328 + sendMouseEvent({type:'click'}, 'link-anchor1', iframeCw); 1.329 + yield undefined; 1.330 + popstateExpected("Clicking on link-anchor1 should trigger a popstate."); 1.331 + is(iframeCw.location.search, "?test1", 1.332 + "search should be ?test1 after clicking link."); 1.333 + is(iframeCw.location.hash, "#1", 1.334 + "hash should be #1 after clicking link."); 1.335 + ise(iframeCw.history.state, null, 1.336 + "Wrong state object in document after clicking link to hash '#1'."); 1.337 + 1.338 + /* 1.339 + * Reload file_bug500328_1.html; we're now going to test that link hrefs 1.340 + * and colors are updated correctly on push/popstates. 1.341 + */ 1.342 + 1.343 + iframe.onload = onChildLoad; 1.344 + enableChildLoadCallback(); 1.345 + iframeCw.location = "about:blank"; 1.346 + yield undefined; 1.347 + enableChildLoadCallback(); 1.348 + iframeCw.location = "file_bug500328_1.html"; 1.349 + yield undefined; 1.350 + noPopStateExpected("No popstate after re-loading file_bug500328_1.html"); 1.351 + statusMsg("Done loading file_bug500328_1.html for the second time."); 1.352 + 1.353 + var ifLink = iframeCw.document.getElementById("link-anchor1"); 1.354 + var rand = Date.now() + "-" + Math.random(); 1.355 + ifLink.href = rand; 1.356 + 1.357 + // Poll the document until the link has the correct color, or this test times 1.358 + // out. Unfortunately I can't come up with a more elegant way to do this. 1.359 + // We could listen to MozAfterPaint, but that doesn't guarantee that we'll 1.360 + // observe the new color. 1.361 + while (getColor(ifLink) != unvisitedColor) { 1.362 + // Dump so something shows up in the mochitest logs if we spin here. 1.363 + dump("ifLink has wrong initial color. Spinning...\n"); 1.364 + setTimeout(function() { gGen.next(); }, 0); 1.365 + yield undefined; 1.366 + } 1.367 + 1.368 + // Navigate iframe2 to dir/${rand} 1.369 + iframe2.onload = onChildLoad; 1.370 + enableChildLoadCallback(); 1.371 + iframe2Cw.location = "mytestdir/" + rand; 1.372 + yield undefined; 1.373 + 1.374 + // PushState the iframe into the mytestdir directory. This should cause 1.375 + // ifLink to turn purple, since we just visited mytestdir/${rand} in iframe2. 1.376 + iframeCw.history.pushState(null, "foo", "mytestdir/foo"); 1.377 + 1.378 + // Check that the link's color is now visitedColor 1.379 + while (getColor(ifLink) != visitedColor) { 1.380 + dump("ifLink has wrong color after pushstate. Spinning...\n"); 1.381 + setTimeout(function() { gGen.next(); }, 0); 1.382 + yield undefined; 1.383 + } 1.384 + 1.385 + ok(ifLink.href.match("mytestdir\\/" + rand + "$"), 1.386 + "inner frame's link should end with 'mytestdir/${rand}'"); 1.387 + 1.388 + // Navigate out of the mytestdir directory. This should cause ifLink to turn 1.389 + // blue again. 1.390 + iframeCw.history.pushState(null, "bar", "../file_bug500328_1.html"); 1.391 + 1.392 + // Check that the link's color is back to the unvisited color. 1.393 + while (getColor(ifLink) != unvisitedColor) { 1.394 + dump("ifLink has wrong color after pushstating out of dir. Spinning...\n"); 1.395 + setTimeout(function() { gGen.next(); }, 0); 1.396 + yield undefined; 1.397 + } 1.398 + 1.399 + ok(!ifLink.href.match("mytestdir"), 1.400 + "inner frame's link shouldn't contain 'mytestdir'."); 1.401 + 1.402 + /* 1.403 + * TEST 2 tests that pushstate's same-origin checks are correct. 1.404 + */ 1.405 + var filename = 'file_bug500328_2.html'; 1.406 + var dirname = document.location.pathname.replace(/[^\/]*$/, ''); 1.407 + statusMsg("Dirname is: " + dirname); 1.408 + iframeCw.location = filename; 1.409 + iframe.onload = onChildLoad; 1.410 + enableChildLoadCallback(); 1.411 + yield undefined; 1.412 + 1.413 + // This function tries to pushstate and replacestate to the given URL and 1.414 + // fails the test if the calls succeed. 1.415 + var tryBadPushAndReplaceState = function(url) { 1.416 + // XXX ex should be a SECURITY_ERR, not a plain Error. 1.417 + 1.418 + var hist = iframeCw.history; 1.419 + var url2 = url + dirname + filename; 1.420 + 1.421 + expectException(function() { hist.pushState({}, "foo", url); }, 1.422 + 'pushState to ' + url); 1.423 + 1.424 + expectException(function() { hist.pushState({}, "foo", url2); }, 1.425 + 'pushState to ' + url2); 1.426 + 1.427 + expectException(function() { hist.replaceState({}, "foo", url); }, 1.428 + 'replaceState to ' + url); 1.429 + 1.430 + expectException(function() { hist.replaceState({}, "foo", url2); }, 1.431 + 'replaceState to ' + url2); 1.432 + } 1.433 + 1.434 + // We're currently at http://example.com/[dirname]/[filename] 1.435 + tryBadPushAndReplaceState("https://mochi.test:8888"); 1.436 + tryBadPushAndReplaceState("http://foo.mochitest:8888"); 1.437 + tryBadPushAndReplaceState("http://mochi.test:1234"); 1.438 + tryBadPushAndReplaceState("http://mochi.test.a:8888"); 1.439 + tryBadPushAndReplaceState("http://mochi.tes:8888"); 1.440 + tryBadPushAndReplaceState("http://mmochi.test:8888"); 1.441 + tryBadPushAndReplaceState("http://me@mochi.test:8888"); 1.442 + 1.443 + /** 1.444 + * TEST 3 tests that the session history entries' titles are properly sync'ed 1.445 + * after push/pop states. 1.446 + * 1.447 + * We have to run this test in a popup rather than an iframe because only the 1.448 + * root docshell has a session history object. 1.449 + */ 1.450 + statusMsg("About to open popup."); 1.451 + var popup = window.open("file_bug500328_1.html", "popup0", 1.452 + "height=200,width=200,location=yes," + 1.453 + "menubar=yes,status=yes,toolbar=yes,dependent=yes"); 1.454 + 1.455 + enableChildLoadCallback(); 1.456 + var shistory = getSHistory(popup); 1.457 + yield undefined; 1.458 + shortWait(); 1.459 + yield undefined; 1.460 + noPopStateExpected("Shouldn't get popstate after opening window."); 1.461 + 1.462 + popup.history.pushState(null, "title 0"); 1.463 + ok(SpecialPowers.isBackButtonEnabled(popup), 1.464 + "Back button was not enabled after initial pushstate."); 1.465 + 1.466 + popup.document.title = "title 1"; 1.467 + 1.468 + // Yield to the event loop so listeners will be notified of the title change 1.469 + // and so that the hash change we trigger below generates a new session 1.470 + // history entry. 1.471 + shortWait(); 1.472 + yield undefined; 1.473 + 1.474 + // Check that the current session history entry's title has been updated to 1.475 + // reflect the new document title. 1.476 + is(getSHTitle(shistory), "title 1", "SHEntry title test 1"); 1.477 + 1.478 + // Change the page's hash to #1, which will trigger a popstate event. 1.479 + // We don't have to wait, because this happens synchronously. 1.480 + popup.location.hash = "#1"; 1.481 + popstateExpected("Didn't get popstate after changing hash."); 1.482 + 1.483 + popup.document.title = "title 2"; 1.484 + 1.485 + // Yield so listeners will be notified of the title change we just performed. 1.486 + shortWait(); 1.487 + yield undefined; 1.488 + 1.489 + is(getSHTitle(shistory), "title 2", "SHEntry title test 2"); 1.490 + 1.491 + // Go back. Happens synchronously. We should get a popstate. 1.492 + statusMsg("About to go back."); 1.493 + popup.history.go(-1); 1.494 + popstateExpected("Didn't get a popstate after going back."); 1.495 + 1.496 + // Even though we went back, we expect the SHEntry title to remain the same 1.497 + // because the document didn't change. 1.498 + is(getSHTitle(shistory), "title 2", "SHEntry title test 3"); 1.499 + 1.500 + popup.document.title = "Changed 1"; 1.501 + shortWait(); 1.502 + yield undefined; 1.503 + 1.504 + // This check is really a test of bug 509055. 1.505 + is(getSHTitle(shistory), "Changed 1", "SHEntry title test 4"); 1.506 + 1.507 + popup.close(); 1.508 + 1.509 + /** 1.510 + * TEST 4 tests replaceState and that we don't get double popstates on 1.511 + * window.open. It also stress-tests the system and its interaction with 1.512 + * bfcache by making many push/replace state calls. 1.513 + */ 1.514 + popup = window.open("file_bug500328_1.html", "popup1", 1.515 + "height=200,width=200,location=yes," + 1.516 + "menubar=yes,status=yes,toolbar=yes,dependent=yes"); 1.517 + 1.518 + // The initial about:blank load into the new window shouldn't result in us 1.519 + // seeing a popstate. Once file_bug500328_1.html is loaded, it'll overwrite 1.520 + // popup.onpopstate, and this assertion won't fire for that popstate and 1.521 + // others after. 1.522 + // 1.523 + // If we fired the popstate event asynchronously, we'd expect this assert to 1.524 + // fire. 1.525 + popup.onpopstate = function() { 1.526 + ok(false, "Initial load of popup shouldn't give us a popstate."); 1.527 + }; 1.528 + 1.529 + shistory = getSHistory(popup); 1.530 + 1.531 + enableChildLoadCallback(); 1.532 + yield undefined; 1.533 + statusMsg("Awake after loading content into popup."); 1.534 + 1.535 + popup.history.replaceState({n:1, ok:true}, "state 1", "good1.html"); 1.536 + locationEndsWith(popup, "good1.html"); 1.537 + 1.538 + // Even though we replaceState with title "state 1", the title should remain 1.539 + // "test 1" because we ignore the title argument in push/replaceState. 1.540 + // See bug 544535. 1.541 + is(getSHTitle(shistory), "test 1", "SHEntry title 'state 1'"); 1.542 + 1.543 + // Flush the event loop so our next load creates a new session history entry. 1.544 + shortWait(); 1.545 + yield undefined; 1.546 + 1.547 + enableChildLoadCallback(); 1.548 + popup.location = "file_bug500328_1.html"; 1.549 + yield undefined; 1.550 + 1.551 + // Flush the event loop so nsDocShell::OnNewURI runs and our load is recorded 1.552 + // properly. 1.553 + shortWait(); 1.554 + yield undefined; 1.555 + 1.556 + // Now go back and make sure everything is as it should be. 1.557 + enableChildPageShowCallback(); 1.558 + popup.history.back(); 1.559 + yield undefined; 1.560 + // Flush the event loop so the document's location is updated and any 1.561 + // popstates fire. 1.562 + shortWait(); 1.563 + yield undefined; 1.564 + noPopStateExpected("no popstate during initial load"); 1.565 + 1.566 + locationEndsWith(popup, "good1.html"); 1.567 + is(JSON.stringify(popup.history.state), '{"n":1,"ok":true}', 1.568 + "Wrong state popped after going back to initial state."); 1.569 + 1.570 + // We're back at state 0, which was replaceState-ed to state1.html. Let's do 1.571 + // some push/pop/replaces to make sure everything works OK when we involve 1.572 + // large numbers of SHEntries. 1.573 + for(var i = 2; i <= 30; i++) { 1.574 + if (i % 3 == 0) { 1.575 + popup.history.pushState({n:i, ok:true}, "state " + i, "good" + i + ".html"); 1.576 + } 1.577 + else { 1.578 + popup.history.pushState({n:i}, "state " + i, "state" + i + ".html"); 1.579 + for(var j = 0; j < i % 4; j++) { 1.580 + popup.history.replaceState({n:i, nn:j}, "state " + i + ", " + j); 1.581 + } 1.582 + popup.history.replaceState({n:i, ok:true}, "state " + i, "good" + i + ".html"); 1.583 + } 1.584 + } 1.585 + 1.586 + for(var i = 29; i >= 1; i--) { 1.587 + popup.history.back(); 1.588 + popstateExpected("Didn't get a popstate on iteration " + i); 1.589 + locationEndsWith(popup, "good" + i + ".html"); 1.590 + is(gLastPopStateEvent.state.n, i, "Bad counter on last popstate event."); 1.591 + ok(gLastPopStateEvent.state.ok, 1.592 + "Last popstate event should have 'ok' set to true."); 1.593 + } 1.594 + 1.595 + popup.close(); 1.596 + 1.597 + /** 1.598 + * TEST 5 tests misc security features and implementation details of 1.599 + * Push/ReplaceState 1.600 + */ 1.601 + 1.602 + /* 1.603 + * Test that we can't push/replace an object with a large (over 640k 1.604 + * characters) JSON representation. 1.605 + */ 1.606 + 1.607 + // (In case you're curious, this loop generates an object which serializes to 1.608 + // 694581 characters.) 1.609 + var bigObject = new Object(); 1.610 + for(var i = 0; i < 51200; i++) { 1.611 + bigObject[i] = i; 1.612 + } 1.613 + // statusMsg("Big object has size " + JSON.stringify(bigObject).length); 1.614 + 1.615 + // We shouldn't be able to pushstate this large object, due to space 1.616 + // constraints. 1.617 + expectException( 1.618 + function() { iframeCw.history.pushState(bigObject, "foo"); }, 1.619 + "pushState-ing large object"); 1.620 + 1.621 + expectException( 1.622 + function() { iframeCw.history.replaceState(bigObject, "foo"); }, 1.623 + "replaceState-ing large object"); 1.624 + 1.625 + /* 1.626 + * Make sure we can't push/replace state on an iframe of a different origin. 1.627 + * This will work if this function has requested Universal XPConnect 1.628 + * privileges, so any code which needs those privileges can't be in this 1.629 + * function. 1.630 + */ 1.631 + enableChildLoadCallback(); 1.632 + iframeCw.location = "http://example.com"; 1.633 + iframe.onload = onChildLoad; 1.634 + yield undefined; 1.635 + iframe.onload = null; 1.636 + 1.637 + expectException( 1.638 + function() { iframeCw.history.pushState({}, "foo"); }, 1.639 + "pushState-ing in a different origin"); 1.640 + 1.641 + expectException( 1.642 + function() { iframeCw.history.replaceState({}, "foo"); }, 1.643 + "replaceState-ing in a different origin"); 1.644 + 1.645 + /* 1.646 + * If we do the following: 1.647 + * * Start at page A. 1.648 + * * PushState to page B. 1.649 + * * Refresh. The server responds with a 404 1.650 + * * Go back. 1.651 + * Then at the end, page A should be displayed, not the 404 page. 1.652 + */ 1.653 + enableChildLoadCallback(); 1.654 + iframe.onload = onChildLoad; 1.655 + iframeCw.location = "about:blank"; 1.656 + yield undefined; 1.657 + iframe.onload = null; 1.658 + 1.659 + enableChildLoadCallback(); 1.660 + // navigate to http://mochi.test:8888/[...]/file_bug500328_1.html 1.661 + iframeCw.location = innerLoc; 1.662 + yield undefined; 1.663 + 1.664 + // PushState to a URL which doesn't exist 1.665 + iframeCw.history.pushState({}, "", rand); 1.666 + 1.667 + // Refresh. We'll end up a 404 page. 1.668 + iframe.onload = onChildLoad; 1.669 + enableChildLoadCallback(); 1.670 + iframeCw.location.reload(true); 1.671 + yield undefined; 1.672 + iframe.onload = null; 1.673 + 1.674 + // Since the last page was a 404, going back should actually show the 1.675 + // contents of the old page, instead of persisting the contents of the 404 1.676 + // page. 1.677 + enableChildPageShowCallback(); 1.678 + iframeCw.history.back(); 1.679 + yield undefined; 1.680 + 1.681 + // Make sure that we're actually showing the contents of 1.682 + // file_bug500328_1.html, as opposed to the 404 page. 1.683 + var identifierElem = iframeCw.document.getElementById("link-anchor1"); 1.684 + ok(identifierElem != undefined && identifierElem != null, 1.685 + "iframe didn't contain file_bug500328_1.html's contents."); 1.686 + 1.687 + /** 1.688 + * TEST 6 tests that the referrer is set properly after push/replace states. 1.689 + */ 1.690 + 1.691 + /* 1.692 + * First, a simple test: 1.693 + * * Load file_bug500328_1.html into iframe 1.694 + * * PushState to newpage1.html#foo 1.695 + * * Instruct the iframe to load file_bug500328_1.html into itself. 1.696 + * The referer should be newpage1.html, without the hash. 1.697 + * 1.698 + * This also tests that we can call pushState from within the onload handler. 1.699 + */ 1.700 + enableChildLoadCallback(); 1.701 + iframeCw.location = "file_bug500328_1.html"; 1.702 + yield undefined; 1.703 + 1.704 + // Run within the onload handler. This should work without issue. 1.705 + iframeCw.history.pushState(null, "", "newpage1.html"); 1.706 + 1.707 + // iframeCw.navigateTo() causes the iframe to set its location on our 1.708 + // behalf. We can't just set its location ourselves, because then *we* 1.709 + // become the referrer. 1.710 + enableChildLoadCallback(); 1.711 + iframeCw.navigateTo("file_bug500328_1.html"); 1.712 + yield undefined; 1.713 + 1.714 + ok(iframeCw.document.referrer.toString().match(/newpage1.html$/), 1.715 + "Wrong referrer after pushState. Expected newpage1.html, but was " + 1.716 + iframeCw.document.referrer); 1.717 + 1.718 + /* 1.719 + * We're back at file_bug500328_1.html. Now do the following: 1.720 + * * replaceState to newpage2.html#foo 1.721 + * * Click a link back to file_bug500328_1.html 1.722 + * The referrer should be newpage2.html, without the hash. 1.723 + */ 1.724 + iframeCw.history.replaceState(null, null, "newpage2.html#foo"); 1.725 + enableChildLoadCallback(); 1.726 + sendMouseEvent({type:'click'}, 'link-self', iframeCw); 1.727 + yield undefined; 1.728 + 1.729 + ok(iframeCw.document.referrer.toString().match(/newpage2.html$/), 1.730 + "Wrong referrer after replaceState. Expected newpage2.html, but was " + 1.731 + iframeCw.document.referrer); 1.732 + 1.733 + /* 1.734 + * Set up a cycle with the popstate event to make sure it's properly 1.735 + * collected. 1.736 + */ 1.737 + var evt = document.createEvent("popstateevent"); 1.738 + evt.initEvent("foo", false, false, evt); 1.739 + 1.740 + /* */ 1.741 + SimpleTest.finish(); 1.742 + statusMsg("********** Finished tests ***********"); 1.743 + while(true) 1.744 + { 1.745 + yield undefined; 1.746 + 1.747 + // I don't think this will actually make the mochitest fail, but there's 1.748 + // not much we can do about this. Realistically, though, regressions are 1.749 + // not likely to fire extra events -- this trap is here mostly to catch 1.750 + // errors made while wriring tests. 1.751 + ok(false, "Got extra event!"); 1.752 + } 1.753 + 1.754 + /* 1.755 + statusMsg("XXXXXXXXXXXXXX"); 1.756 + while(true) { 1.757 + yield undefined; 1.758 + statusMsg("Woken up."); 1.759 + } 1.760 + */ 1.761 +} 1.762 + 1.763 +// Important: Wait to start the tests until the page has loaded. Otherwise, 1.764 +// the test will occasionally fail when it begins running before the iframes 1.765 +// have finished their initial load of about:blank. 1.766 +window.addEventListener('load', function() { 1.767 + gGen = runTest(); 1.768 + gGen.next(); 1.769 +}, false); 1.770 + 1.771 +</script> 1.772 +</pre> 1.773 +</body> 1.774 +</html>