Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | <!DOCTYPE HTML> |
michael@0 | 2 | <html> |
michael@0 | 3 | <!-- |
michael@0 | 4 | https://bugzilla.mozilla.org/show_bug.cgi?id=500328 |
michael@0 | 5 | --> |
michael@0 | 6 | <head> |
michael@0 | 7 | <title>Test for Bug 500328</title> |
michael@0 | 8 | <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> |
michael@0 | 9 | <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> |
michael@0 | 10 | <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> |
michael@0 | 11 | </head> |
michael@0 | 12 | <body> |
michael@0 | 13 | <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=500328">Mozilla Bug 500328</a> |
michael@0 | 14 | <p id="display"></p> |
michael@0 | 15 | <div id="status"></div> |
michael@0 | 16 | <div id="content"> |
michael@0 | 17 | <iframe id="iframe"></iframe> |
michael@0 | 18 | <iframe id="iframe2"></iframe> |
michael@0 | 19 | <a id="link">link</a> |
michael@0 | 20 | </div> |
michael@0 | 21 | <pre id="test"> |
michael@0 | 22 | <script type="application/javascript;version=1.7"> |
michael@0 | 23 | |
michael@0 | 24 | /** Test for Bug 500328 **/ |
michael@0 | 25 | |
michael@0 | 26 | SimpleTest.waitForExplicitFinish(); |
michael@0 | 27 | |
michael@0 | 28 | var iframe = document.getElementById("iframe"); |
michael@0 | 29 | var iframeCw = iframe.contentWindow; |
michael@0 | 30 | |
michael@0 | 31 | var iframe2 = document.getElementById("iframe2"); |
michael@0 | 32 | var iframe2Cw = iframe2.contentWindow; |
michael@0 | 33 | |
michael@0 | 34 | const unvisitedColor = "rgb(0, 0, 238)"; |
michael@0 | 35 | const visitedColor = "rgb(85, 26, 139)"; |
michael@0 | 36 | |
michael@0 | 37 | var gCallbackOnIframeLoad = false; |
michael@0 | 38 | var gCallbackOnIframePageShow = false; |
michael@0 | 39 | var gCallbackOnPopState = false; |
michael@0 | 40 | var gNumPopStates = 0; |
michael@0 | 41 | var gLastPopStateEvent; |
michael@0 | 42 | var gLastScriptHistoryState; |
michael@0 | 43 | |
michael@0 | 44 | var gGen; |
michael@0 | 45 | |
michael@0 | 46 | function statusMsg(msg) { |
michael@0 | 47 | var msgElem = document.createElement("p"); |
michael@0 | 48 | msgElem.appendChild(document.createTextNode(msg)); |
michael@0 | 49 | |
michael@0 | 50 | document.getElementById("status").appendChild(msgElem); |
michael@0 | 51 | } |
michael@0 | 52 | |
michael@0 | 53 | function longWait() { |
michael@0 | 54 | function hitEventLoop(times, func) { |
michael@0 | 55 | if (times > 0) { |
michael@0 | 56 | setTimeout(hitEventLoop, 0, times - 1, func); |
michael@0 | 57 | } else { |
michael@0 | 58 | setTimeout(func, 0); |
michael@0 | 59 | } |
michael@0 | 60 | } |
michael@0 | 61 | hitEventLoop(100, function() { gGen.next(); }); |
michael@0 | 62 | } |
michael@0 | 63 | |
michael@0 | 64 | function shortWait() { |
michael@0 | 65 | setTimeout(function() { gGen.next(); }, 0); |
michael@0 | 66 | } |
michael@0 | 67 | |
michael@0 | 68 | function onChildPopState(e) { |
michael@0 | 69 | gNumPopStates++; |
michael@0 | 70 | gLastPopStateEvent = e; |
michael@0 | 71 | if (gCallbackOnPopState) { |
michael@0 | 72 | statusMsg("Popstate(" + JSON.stringify(e.state) + "). Calling gGen.next()."); |
michael@0 | 73 | gCallbackOnPopState = false; |
michael@0 | 74 | gGen.next(); |
michael@0 | 75 | } |
michael@0 | 76 | else { |
michael@0 | 77 | statusMsg("Popstate(" + JSON.stringify(e.state) + "). NOT calling gGen.next()."); |
michael@0 | 78 | } |
michael@0 | 79 | } |
michael@0 | 80 | |
michael@0 | 81 | function onChildScript(state) { |
michael@0 | 82 | gLastScriptHistoryState = state; |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | function getURLFromEvent(e) { |
michael@0 | 86 | try { |
michael@0 | 87 | var target = e.target; |
michael@0 | 88 | if ("contentWindow" in target) { |
michael@0 | 89 | return target.contentWindow.location.toString(); |
michael@0 | 90 | } |
michael@0 | 91 | if ("ownerDocument" in target && target.ownerDocument) { |
michael@0 | 92 | return target.ownerDocument.location.toString(); |
michael@0 | 93 | } |
michael@0 | 94 | if ("location" in target) { |
michael@0 | 95 | return target.location.toString(); |
michael@0 | 96 | } |
michael@0 | 97 | return target.toString(); |
michael@0 | 98 | } |
michael@0 | 99 | catch(ex) { |
michael@0 | 100 | return "<cross-site object>"; |
michael@0 | 101 | } |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | function onChildLoad(e) { |
michael@0 | 105 | if(gCallbackOnIframeLoad) { |
michael@0 | 106 | statusMsg("Got load for " + getURLFromEvent(e) + ". About to call gGen.next()."); |
michael@0 | 107 | gCallbackOnIframeLoad = false; |
michael@0 | 108 | gGen.next(); |
michael@0 | 109 | } |
michael@0 | 110 | else { |
michael@0 | 111 | statusMsg("Got load for " + getURLFromEvent(e) + ", but not calling gGen.next() because gCallbackOnIframeLoad was false."); |
michael@0 | 112 | } |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | function onChildPageShow(e) { |
michael@0 | 116 | if(gCallbackOnIframePageShow) { |
michael@0 | 117 | statusMsg("Got pageshow for " + getURLFromEvent(e) + ". About to call gGen.next()."); |
michael@0 | 118 | gCallbackOnIframePageShow = false; |
michael@0 | 119 | SimpleTest.executeSoon(function() { gGen.next(); }); |
michael@0 | 120 | } |
michael@0 | 121 | else { |
michael@0 | 122 | statusMsg("Got pageshow for " + getURLFromEvent(e) + ", but not calling gGen.next() because gCallbackOnIframePageShow was false."); |
michael@0 | 123 | } |
michael@0 | 124 | } |
michael@0 | 125 | |
michael@0 | 126 | function enableChildLoadCallback() { |
michael@0 | 127 | gCallbackOnIframeLoad = true; |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | function enableChildPageShowCallback() { |
michael@0 | 131 | gCallbackOnIframePageShow = true; |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | function enableChildPopStateCallback() { |
michael@0 | 135 | gCallbackOnPopState = true; |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | function clearPopStateCounter() { |
michael@0 | 139 | gNumPopStates = 0; |
michael@0 | 140 | } |
michael@0 | 141 | |
michael@0 | 142 | function noPopStateExpected(msg) { |
michael@0 | 143 | is(gNumPopStates, 0, msg); |
michael@0 | 144 | |
michael@0 | 145 | // Even if there's an error, set gNumPopStates to 0 so other tests don't |
michael@0 | 146 | // fail. |
michael@0 | 147 | gNumPopStates = 0; |
michael@0 | 148 | } |
michael@0 | 149 | |
michael@0 | 150 | function popstateExpected(msg) { |
michael@0 | 151 | is(gNumPopStates, 1, msg); |
michael@0 | 152 | gNumPopStates = 0; |
michael@0 | 153 | } |
michael@0 | 154 | |
michael@0 | 155 | function getColor(elem) { |
michael@0 | 156 | var utils = SpecialPowers.wrap(document).defaultView. |
michael@0 | 157 | QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor). |
michael@0 | 158 | getInterface(SpecialPowers.Ci.nsIDOMWindowUtils); |
michael@0 | 159 | return utils.getVisitedDependentComputedStyle(elem, "", "color"); |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | function getSHistory(theWindow) |
michael@0 | 163 | { |
michael@0 | 164 | const Ci = SpecialPowers.Ci; |
michael@0 | 165 | var sh = SpecialPowers.wrap(theWindow.QueryInterface(Ci.nsIInterfaceRequestor)) |
michael@0 | 166 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 167 | .sessionHistory; |
michael@0 | 168 | if (!sh || sh == null) |
michael@0 | 169 | throw("Couldn't get shistory for window!"); |
michael@0 | 170 | |
michael@0 | 171 | return sh; |
michael@0 | 172 | } |
michael@0 | 173 | |
michael@0 | 174 | function getSHTitle(sh, offset) |
michael@0 | 175 | { |
michael@0 | 176 | if (!offset) |
michael@0 | 177 | offset = 0; |
michael@0 | 178 | |
michael@0 | 179 | // False instructs the SHistory not to modify its current index. |
michael@0 | 180 | return sh.getEntryAtIndex(sh.index + offset, false).title; |
michael@0 | 181 | } |
michael@0 | 182 | |
michael@0 | 183 | // Tests that win's location ends with str |
michael@0 | 184 | function locationEndsWith(win, str) { |
michael@0 | 185 | var exp = new RegExp(str + "$"); |
michael@0 | 186 | ok(win.location.toString().match(exp), |
michael@0 | 187 | "Wrong window location. Expected it to end with " + |
michael@0 | 188 | str + ", but actuall was " + win.location); |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | function expectException(func, msg) { |
michael@0 | 192 | var failed = false; |
michael@0 | 193 | try { |
michael@0 | 194 | func(); |
michael@0 | 195 | } catch(ex) { |
michael@0 | 196 | failed = true; |
michael@0 | 197 | } |
michael@0 | 198 | |
michael@0 | 199 | ok(failed, msg + " succeeded, but should have failed."); |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | function runTest() { |
michael@0 | 203 | // We can't enable universal XPConnect privleges in this function, because |
michael@0 | 204 | // test 5 needs to be running at normal privleges in order to test the |
michael@0 | 205 | // same-origin policy. |
michael@0 | 206 | |
michael@0 | 207 | /** |
michael@0 | 208 | * PRELIMINARY: |
michael@0 | 209 | * 1. Clear the popstate counter |
michael@0 | 210 | */ |
michael@0 | 211 | |
michael@0 | 212 | clearPopStateCounter(); |
michael@0 | 213 | |
michael@0 | 214 | // The URL of file_bug500328_1.html on http://localhost:8888 |
michael@0 | 215 | var innerLoc; |
michael@0 | 216 | |
michael@0 | 217 | // Now we can start the tests |
michael@0 | 218 | |
michael@0 | 219 | /** |
michael@0 | 220 | * TEST 1 tests basic pushState functionality |
michael@0 | 221 | */ |
michael@0 | 222 | enableChildLoadCallback(); |
michael@0 | 223 | iframeCw.location = "file_bug500328_1.html"; |
michael@0 | 224 | yield undefined; |
michael@0 | 225 | innerLoc = iframeCw.location.toString(); |
michael@0 | 226 | // No popstate during initial load. |
michael@0 | 227 | shortWait(); |
michael@0 | 228 | yield undefined; |
michael@0 | 229 | noPopStateExpected("No initial popstate."); |
michael@0 | 230 | is(JSON.stringify(gLastScriptHistoryState), "null", "null initial state."); |
michael@0 | 231 | statusMsg("Awake after first load."); |
michael@0 | 232 | |
michael@0 | 233 | // Make sure that the pushstate below doesn't trigger a hashchange. |
michael@0 | 234 | iframeCw.onhashchange = function() { |
michael@0 | 235 | ok(false, "Pushstate shouldn't trigger a hashchange."); |
michael@0 | 236 | }; |
michael@0 | 237 | |
michael@0 | 238 | var testObj1 = 42; |
michael@0 | 239 | var testObj2 = { x: 4.2 }; |
michael@0 | 240 | iframeCw.history.pushState(testObj1, "test 1"); |
michael@0 | 241 | is(JSON.stringify(iframeCw.history.state), JSON.stringify(testObj1), |
michael@0 | 242 | "correct state after pushState"); |
michael@0 | 243 | is(iframeCw.location.search, "", |
michael@0 | 244 | "First pushstate should leave us where we were."); |
michael@0 | 245 | |
michael@0 | 246 | iframeCw.history.pushState(testObj2, "test 1#foo", "?test1#foo"); |
michael@0 | 247 | is(JSON.stringify(iframeCw.history.state), JSON.stringify(testObj2), |
michael@0 | 248 | "correct state after pushState"); |
michael@0 | 249 | isnot(iframeCw.history.state, testObj2, |
michael@0 | 250 | "correct state object identity after pushState"); |
michael@0 | 251 | is(iframeCw.location.search, "?test1", |
michael@0 | 252 | "Second pushstate should push us to '?test1'."); |
michael@0 | 253 | is(iframeCw.location.hash, "#foo", |
michael@0 | 254 | "Second pushstate should push us to '#foo'"); |
michael@0 | 255 | shortWait(); |
michael@0 | 256 | yield undefined; |
michael@0 | 257 | |
michael@0 | 258 | // Let the hashchange event fire, if it's going to. |
michael@0 | 259 | longWait(); |
michael@0 | 260 | yield undefined; |
michael@0 | 261 | iframeCw.onhashchange = null; |
michael@0 | 262 | |
michael@0 | 263 | statusMsg("About to go back to page 1."); |
michael@0 | 264 | // We don't have to yield here because this back() and the resulting popstate |
michael@0 | 265 | // are completely synchronous. In fact, if we did yield, JS would throw an |
michael@0 | 266 | // error because we'd be calling gGen.next from within gGen.next. |
michael@0 | 267 | iframeCw.history.back(); |
michael@0 | 268 | |
michael@0 | 269 | statusMsg("Awake after going back to page 1."); |
michael@0 | 270 | popstateExpected("Going back to page 1 should trigger a popstate."); |
michael@0 | 271 | is(gLastPopStateEvent.isTrusted, true, 'Popstate event should be trusted.'); |
michael@0 | 272 | is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj1), |
michael@0 | 273 | "Wrong state object popped after going back to page 1."); |
michael@0 | 274 | ok(gLastPopStateEvent.state === iframeCw.history.state, |
michael@0 | 275 | "Wrong state object in document after going back to page 1."); |
michael@0 | 276 | ok(iframeCw.location.toString().match(/file_bug500328_1.html$/), |
michael@0 | 277 | "Going back to page 1 hould take us to original page."); |
michael@0 | 278 | |
michael@0 | 279 | iframeCw.history.back(); |
michael@0 | 280 | popstateExpected("Going back to page 0 should trigger a popstate."); |
michael@0 | 281 | ise(gLastPopStateEvent.state, null, |
michael@0 | 282 | "Going back to page 0 should pop a null state."); |
michael@0 | 283 | ise(iframeCw.history.state, null, |
michael@0 | 284 | "Going back to page 0 should pop a null state."); |
michael@0 | 285 | ise(iframeCw.location.search, "", |
michael@0 | 286 | "Going back to page 0 should clear the querystring."); |
michael@0 | 287 | |
michael@0 | 288 | iframeCw.history.forward(); |
michael@0 | 289 | popstateExpected("Going forward to page 1 should trigger a popstate."); |
michael@0 | 290 | is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj1), |
michael@0 | 291 | "Wrong state object popped after going forward to page 1."); |
michael@0 | 292 | ise(gLastPopStateEvent.state, iframeCw.history.state, |
michael@0 | 293 | "Wrong state object in document after going forward to page 1."); |
michael@0 | 294 | ok(iframeCw.location.toString().match(/file_bug500328_1.html$/), |
michael@0 | 295 | "Going forward to page 1 should leave us at original page."); |
michael@0 | 296 | |
michael@0 | 297 | statusMsg("About to go forward to page 2."); |
michael@0 | 298 | iframeCw.history.forward(); |
michael@0 | 299 | statusMsg("Awake after going forward to page 2."); |
michael@0 | 300 | popstateExpected("Going forward to page 2 should trigger a popstate."); |
michael@0 | 301 | is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj2), |
michael@0 | 302 | "Wrong state object popped after going forward to page 2."); |
michael@0 | 303 | ise(iframeCw.history.state, gLastPopStateEvent.state, |
michael@0 | 304 | "Wrong state object in document after going forward to page 2."); |
michael@0 | 305 | ok(iframeCw.location.toString().match(/file_bug500328_1.html\?test1#foo$/), |
michael@0 | 306 | "Going forward to page 2 took us to " + iframeCw.location.toString()); |
michael@0 | 307 | |
michael@0 | 308 | statusMsg("About to reload page 2."); |
michael@0 | 309 | iframeCw.location.reload(); |
michael@0 | 310 | enableChildLoadCallback(); |
michael@0 | 311 | yield undefined; |
michael@0 | 312 | statusMsg("Awake after reloading page 2."); |
michael@0 | 313 | noPopStateExpected("Reloading page 2 should not trigger popstate."); |
michael@0 | 314 | is(JSON.stringify(iframeCw.history.state), JSON.stringify(testObj2), |
michael@0 | 315 | "Wrong state object after reloading page 2."); |
michael@0 | 316 | is(JSON.stringify(gLastScriptHistoryState), JSON.stringify(testObj2), |
michael@0 | 317 | "Wrong state object while reloading page 2."); |
michael@0 | 318 | ok(iframeCw.location.toString().match(/file_bug500328_1.html\?test1#foo$/), |
michael@0 | 319 | "Reloading page 2 took us to " + iframeCw.location.toString()); |
michael@0 | 320 | |
michael@0 | 321 | // The iframe's current location is file_bug500328_1.html?test1#foo. |
michael@0 | 322 | // Clicking link1 should take us to file_bug500328_1.html?test1#1. |
michael@0 | 323 | |
michael@0 | 324 | enableChildPopStateCallback(); |
michael@0 | 325 | sendMouseEvent({type:'click'}, 'link-anchor1', iframeCw); |
michael@0 | 326 | yield undefined; |
michael@0 | 327 | popstateExpected("Clicking on link-anchor1 should trigger a popstate."); |
michael@0 | 328 | is(iframeCw.location.search, "?test1", |
michael@0 | 329 | "search should be ?test1 after clicking link."); |
michael@0 | 330 | is(iframeCw.location.hash, "#1", |
michael@0 | 331 | "hash should be #1 after clicking link."); |
michael@0 | 332 | ise(iframeCw.history.state, null, |
michael@0 | 333 | "Wrong state object in document after clicking link to hash '#1'."); |
michael@0 | 334 | |
michael@0 | 335 | /* |
michael@0 | 336 | * Reload file_bug500328_1.html; we're now going to test that link hrefs |
michael@0 | 337 | * and colors are updated correctly on push/popstates. |
michael@0 | 338 | */ |
michael@0 | 339 | |
michael@0 | 340 | iframe.onload = onChildLoad; |
michael@0 | 341 | enableChildLoadCallback(); |
michael@0 | 342 | iframeCw.location = "about:blank"; |
michael@0 | 343 | yield undefined; |
michael@0 | 344 | enableChildLoadCallback(); |
michael@0 | 345 | iframeCw.location = "file_bug500328_1.html"; |
michael@0 | 346 | yield undefined; |
michael@0 | 347 | noPopStateExpected("No popstate after re-loading file_bug500328_1.html"); |
michael@0 | 348 | statusMsg("Done loading file_bug500328_1.html for the second time."); |
michael@0 | 349 | |
michael@0 | 350 | var ifLink = iframeCw.document.getElementById("link-anchor1"); |
michael@0 | 351 | var rand = Date.now() + "-" + Math.random(); |
michael@0 | 352 | ifLink.href = rand; |
michael@0 | 353 | |
michael@0 | 354 | // Poll the document until the link has the correct color, or this test times |
michael@0 | 355 | // out. Unfortunately I can't come up with a more elegant way to do this. |
michael@0 | 356 | // We could listen to MozAfterPaint, but that doesn't guarantee that we'll |
michael@0 | 357 | // observe the new color. |
michael@0 | 358 | while (getColor(ifLink) != unvisitedColor) { |
michael@0 | 359 | // Dump so something shows up in the mochitest logs if we spin here. |
michael@0 | 360 | dump("ifLink has wrong initial color. Spinning...\n"); |
michael@0 | 361 | setTimeout(function() { gGen.next(); }, 0); |
michael@0 | 362 | yield undefined; |
michael@0 | 363 | } |
michael@0 | 364 | |
michael@0 | 365 | // Navigate iframe2 to dir/${rand} |
michael@0 | 366 | iframe2.onload = onChildLoad; |
michael@0 | 367 | enableChildLoadCallback(); |
michael@0 | 368 | iframe2Cw.location = "mytestdir/" + rand; |
michael@0 | 369 | yield undefined; |
michael@0 | 370 | |
michael@0 | 371 | // PushState the iframe into the mytestdir directory. This should cause |
michael@0 | 372 | // ifLink to turn purple, since we just visited mytestdir/${rand} in iframe2. |
michael@0 | 373 | iframeCw.history.pushState(null, "foo", "mytestdir/foo"); |
michael@0 | 374 | |
michael@0 | 375 | // Check that the link's color is now visitedColor |
michael@0 | 376 | while (getColor(ifLink) != visitedColor) { |
michael@0 | 377 | dump("ifLink has wrong color after pushstate. Spinning...\n"); |
michael@0 | 378 | setTimeout(function() { gGen.next(); }, 0); |
michael@0 | 379 | yield undefined; |
michael@0 | 380 | } |
michael@0 | 381 | |
michael@0 | 382 | ok(ifLink.href.match("mytestdir\\/" + rand + "$"), |
michael@0 | 383 | "inner frame's link should end with 'mytestdir/${rand}'"); |
michael@0 | 384 | |
michael@0 | 385 | // Navigate out of the mytestdir directory. This should cause ifLink to turn |
michael@0 | 386 | // blue again. |
michael@0 | 387 | iframeCw.history.pushState(null, "bar", "../file_bug500328_1.html"); |
michael@0 | 388 | |
michael@0 | 389 | // Check that the link's color is back to the unvisited color. |
michael@0 | 390 | while (getColor(ifLink) != unvisitedColor) { |
michael@0 | 391 | dump("ifLink has wrong color after pushstating out of dir. Spinning...\n"); |
michael@0 | 392 | setTimeout(function() { gGen.next(); }, 0); |
michael@0 | 393 | yield undefined; |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | ok(!ifLink.href.match("mytestdir"), |
michael@0 | 397 | "inner frame's link shouldn't contain 'mytestdir'."); |
michael@0 | 398 | |
michael@0 | 399 | /* |
michael@0 | 400 | * TEST 2 tests that pushstate's same-origin checks are correct. |
michael@0 | 401 | */ |
michael@0 | 402 | var filename = 'file_bug500328_2.html'; |
michael@0 | 403 | var dirname = document.location.pathname.replace(/[^\/]*$/, ''); |
michael@0 | 404 | statusMsg("Dirname is: " + dirname); |
michael@0 | 405 | iframeCw.location = filename; |
michael@0 | 406 | iframe.onload = onChildLoad; |
michael@0 | 407 | enableChildLoadCallback(); |
michael@0 | 408 | yield undefined; |
michael@0 | 409 | |
michael@0 | 410 | // This function tries to pushstate and replacestate to the given URL and |
michael@0 | 411 | // fails the test if the calls succeed. |
michael@0 | 412 | var tryBadPushAndReplaceState = function(url) { |
michael@0 | 413 | // XXX ex should be a SECURITY_ERR, not a plain Error. |
michael@0 | 414 | |
michael@0 | 415 | var hist = iframeCw.history; |
michael@0 | 416 | var url2 = url + dirname + filename; |
michael@0 | 417 | |
michael@0 | 418 | expectException(function() { hist.pushState({}, "foo", url); }, |
michael@0 | 419 | 'pushState to ' + url); |
michael@0 | 420 | |
michael@0 | 421 | expectException(function() { hist.pushState({}, "foo", url2); }, |
michael@0 | 422 | 'pushState to ' + url2); |
michael@0 | 423 | |
michael@0 | 424 | expectException(function() { hist.replaceState({}, "foo", url); }, |
michael@0 | 425 | 'replaceState to ' + url); |
michael@0 | 426 | |
michael@0 | 427 | expectException(function() { hist.replaceState({}, "foo", url2); }, |
michael@0 | 428 | 'replaceState to ' + url2); |
michael@0 | 429 | } |
michael@0 | 430 | |
michael@0 | 431 | // We're currently at http://example.com/[dirname]/[filename] |
michael@0 | 432 | tryBadPushAndReplaceState("https://mochi.test:8888"); |
michael@0 | 433 | tryBadPushAndReplaceState("http://foo.mochitest:8888"); |
michael@0 | 434 | tryBadPushAndReplaceState("http://mochi.test:1234"); |
michael@0 | 435 | tryBadPushAndReplaceState("http://mochi.test.a:8888"); |
michael@0 | 436 | tryBadPushAndReplaceState("http://mochi.tes:8888"); |
michael@0 | 437 | tryBadPushAndReplaceState("http://mmochi.test:8888"); |
michael@0 | 438 | tryBadPushAndReplaceState("http://me@mochi.test:8888"); |
michael@0 | 439 | |
michael@0 | 440 | /** |
michael@0 | 441 | * TEST 3 tests that the session history entries' titles are properly sync'ed |
michael@0 | 442 | * after push/pop states. |
michael@0 | 443 | * |
michael@0 | 444 | * We have to run this test in a popup rather than an iframe because only the |
michael@0 | 445 | * root docshell has a session history object. |
michael@0 | 446 | */ |
michael@0 | 447 | statusMsg("About to open popup."); |
michael@0 | 448 | var popup = window.open("file_bug500328_1.html", "popup0", |
michael@0 | 449 | "height=200,width=200,location=yes," + |
michael@0 | 450 | "menubar=yes,status=yes,toolbar=yes,dependent=yes"); |
michael@0 | 451 | |
michael@0 | 452 | enableChildLoadCallback(); |
michael@0 | 453 | var shistory = getSHistory(popup); |
michael@0 | 454 | yield undefined; |
michael@0 | 455 | shortWait(); |
michael@0 | 456 | yield undefined; |
michael@0 | 457 | noPopStateExpected("Shouldn't get popstate after opening window."); |
michael@0 | 458 | |
michael@0 | 459 | popup.history.pushState(null, "title 0"); |
michael@0 | 460 | ok(SpecialPowers.isBackButtonEnabled(popup), |
michael@0 | 461 | "Back button was not enabled after initial pushstate."); |
michael@0 | 462 | |
michael@0 | 463 | popup.document.title = "title 1"; |
michael@0 | 464 | |
michael@0 | 465 | // Yield to the event loop so listeners will be notified of the title change |
michael@0 | 466 | // and so that the hash change we trigger below generates a new session |
michael@0 | 467 | // history entry. |
michael@0 | 468 | shortWait(); |
michael@0 | 469 | yield undefined; |
michael@0 | 470 | |
michael@0 | 471 | // Check that the current session history entry's title has been updated to |
michael@0 | 472 | // reflect the new document title. |
michael@0 | 473 | is(getSHTitle(shistory), "title 1", "SHEntry title test 1"); |
michael@0 | 474 | |
michael@0 | 475 | // Change the page's hash to #1, which will trigger a popstate event. |
michael@0 | 476 | // We don't have to wait, because this happens synchronously. |
michael@0 | 477 | popup.location.hash = "#1"; |
michael@0 | 478 | popstateExpected("Didn't get popstate after changing hash."); |
michael@0 | 479 | |
michael@0 | 480 | popup.document.title = "title 2"; |
michael@0 | 481 | |
michael@0 | 482 | // Yield so listeners will be notified of the title change we just performed. |
michael@0 | 483 | shortWait(); |
michael@0 | 484 | yield undefined; |
michael@0 | 485 | |
michael@0 | 486 | is(getSHTitle(shistory), "title 2", "SHEntry title test 2"); |
michael@0 | 487 | |
michael@0 | 488 | // Go back. Happens synchronously. We should get a popstate. |
michael@0 | 489 | statusMsg("About to go back."); |
michael@0 | 490 | popup.history.go(-1); |
michael@0 | 491 | popstateExpected("Didn't get a popstate after going back."); |
michael@0 | 492 | |
michael@0 | 493 | // Even though we went back, we expect the SHEntry title to remain the same |
michael@0 | 494 | // because the document didn't change. |
michael@0 | 495 | is(getSHTitle(shistory), "title 2", "SHEntry title test 3"); |
michael@0 | 496 | |
michael@0 | 497 | popup.document.title = "Changed 1"; |
michael@0 | 498 | shortWait(); |
michael@0 | 499 | yield undefined; |
michael@0 | 500 | |
michael@0 | 501 | // This check is really a test of bug 509055. |
michael@0 | 502 | is(getSHTitle(shistory), "Changed 1", "SHEntry title test 4"); |
michael@0 | 503 | |
michael@0 | 504 | popup.close(); |
michael@0 | 505 | |
michael@0 | 506 | /** |
michael@0 | 507 | * TEST 4 tests replaceState and that we don't get double popstates on |
michael@0 | 508 | * window.open. It also stress-tests the system and its interaction with |
michael@0 | 509 | * bfcache by making many push/replace state calls. |
michael@0 | 510 | */ |
michael@0 | 511 | popup = window.open("file_bug500328_1.html", "popup1", |
michael@0 | 512 | "height=200,width=200,location=yes," + |
michael@0 | 513 | "menubar=yes,status=yes,toolbar=yes,dependent=yes"); |
michael@0 | 514 | |
michael@0 | 515 | // The initial about:blank load into the new window shouldn't result in us |
michael@0 | 516 | // seeing a popstate. Once file_bug500328_1.html is loaded, it'll overwrite |
michael@0 | 517 | // popup.onpopstate, and this assertion won't fire for that popstate and |
michael@0 | 518 | // others after. |
michael@0 | 519 | // |
michael@0 | 520 | // If we fired the popstate event asynchronously, we'd expect this assert to |
michael@0 | 521 | // fire. |
michael@0 | 522 | popup.onpopstate = function() { |
michael@0 | 523 | ok(false, "Initial load of popup shouldn't give us a popstate."); |
michael@0 | 524 | }; |
michael@0 | 525 | |
michael@0 | 526 | shistory = getSHistory(popup); |
michael@0 | 527 | |
michael@0 | 528 | enableChildLoadCallback(); |
michael@0 | 529 | yield undefined; |
michael@0 | 530 | statusMsg("Awake after loading content into popup."); |
michael@0 | 531 | |
michael@0 | 532 | popup.history.replaceState({n:1, ok:true}, "state 1", "good1.html"); |
michael@0 | 533 | locationEndsWith(popup, "good1.html"); |
michael@0 | 534 | |
michael@0 | 535 | // Even though we replaceState with title "state 1", the title should remain |
michael@0 | 536 | // "test 1" because we ignore the title argument in push/replaceState. |
michael@0 | 537 | // See bug 544535. |
michael@0 | 538 | is(getSHTitle(shistory), "test 1", "SHEntry title 'state 1'"); |
michael@0 | 539 | |
michael@0 | 540 | // Flush the event loop so our next load creates a new session history entry. |
michael@0 | 541 | shortWait(); |
michael@0 | 542 | yield undefined; |
michael@0 | 543 | |
michael@0 | 544 | enableChildLoadCallback(); |
michael@0 | 545 | popup.location = "file_bug500328_1.html"; |
michael@0 | 546 | yield undefined; |
michael@0 | 547 | |
michael@0 | 548 | // Flush the event loop so nsDocShell::OnNewURI runs and our load is recorded |
michael@0 | 549 | // properly. |
michael@0 | 550 | shortWait(); |
michael@0 | 551 | yield undefined; |
michael@0 | 552 | |
michael@0 | 553 | // Now go back and make sure everything is as it should be. |
michael@0 | 554 | enableChildPageShowCallback(); |
michael@0 | 555 | popup.history.back(); |
michael@0 | 556 | yield undefined; |
michael@0 | 557 | // Flush the event loop so the document's location is updated and any |
michael@0 | 558 | // popstates fire. |
michael@0 | 559 | shortWait(); |
michael@0 | 560 | yield undefined; |
michael@0 | 561 | noPopStateExpected("no popstate during initial load"); |
michael@0 | 562 | |
michael@0 | 563 | locationEndsWith(popup, "good1.html"); |
michael@0 | 564 | is(JSON.stringify(popup.history.state), '{"n":1,"ok":true}', |
michael@0 | 565 | "Wrong state popped after going back to initial state."); |
michael@0 | 566 | |
michael@0 | 567 | // We're back at state 0, which was replaceState-ed to state1.html. Let's do |
michael@0 | 568 | // some push/pop/replaces to make sure everything works OK when we involve |
michael@0 | 569 | // large numbers of SHEntries. |
michael@0 | 570 | for(var i = 2; i <= 30; i++) { |
michael@0 | 571 | if (i % 3 == 0) { |
michael@0 | 572 | popup.history.pushState({n:i, ok:true}, "state " + i, "good" + i + ".html"); |
michael@0 | 573 | } |
michael@0 | 574 | else { |
michael@0 | 575 | popup.history.pushState({n:i}, "state " + i, "state" + i + ".html"); |
michael@0 | 576 | for(var j = 0; j < i % 4; j++) { |
michael@0 | 577 | popup.history.replaceState({n:i, nn:j}, "state " + i + ", " + j); |
michael@0 | 578 | } |
michael@0 | 579 | popup.history.replaceState({n:i, ok:true}, "state " + i, "good" + i + ".html"); |
michael@0 | 580 | } |
michael@0 | 581 | } |
michael@0 | 582 | |
michael@0 | 583 | for(var i = 29; i >= 1; i--) { |
michael@0 | 584 | popup.history.back(); |
michael@0 | 585 | popstateExpected("Didn't get a popstate on iteration " + i); |
michael@0 | 586 | locationEndsWith(popup, "good" + i + ".html"); |
michael@0 | 587 | is(gLastPopStateEvent.state.n, i, "Bad counter on last popstate event."); |
michael@0 | 588 | ok(gLastPopStateEvent.state.ok, |
michael@0 | 589 | "Last popstate event should have 'ok' set to true."); |
michael@0 | 590 | } |
michael@0 | 591 | |
michael@0 | 592 | popup.close(); |
michael@0 | 593 | |
michael@0 | 594 | /** |
michael@0 | 595 | * TEST 5 tests misc security features and implementation details of |
michael@0 | 596 | * Push/ReplaceState |
michael@0 | 597 | */ |
michael@0 | 598 | |
michael@0 | 599 | /* |
michael@0 | 600 | * Test that we can't push/replace an object with a large (over 640k |
michael@0 | 601 | * characters) JSON representation. |
michael@0 | 602 | */ |
michael@0 | 603 | |
michael@0 | 604 | // (In case you're curious, this loop generates an object which serializes to |
michael@0 | 605 | // 694581 characters.) |
michael@0 | 606 | var bigObject = new Object(); |
michael@0 | 607 | for(var i = 0; i < 51200; i++) { |
michael@0 | 608 | bigObject[i] = i; |
michael@0 | 609 | } |
michael@0 | 610 | // statusMsg("Big object has size " + JSON.stringify(bigObject).length); |
michael@0 | 611 | |
michael@0 | 612 | // We shouldn't be able to pushstate this large object, due to space |
michael@0 | 613 | // constraints. |
michael@0 | 614 | expectException( |
michael@0 | 615 | function() { iframeCw.history.pushState(bigObject, "foo"); }, |
michael@0 | 616 | "pushState-ing large object"); |
michael@0 | 617 | |
michael@0 | 618 | expectException( |
michael@0 | 619 | function() { iframeCw.history.replaceState(bigObject, "foo"); }, |
michael@0 | 620 | "replaceState-ing large object"); |
michael@0 | 621 | |
michael@0 | 622 | /* |
michael@0 | 623 | * Make sure we can't push/replace state on an iframe of a different origin. |
michael@0 | 624 | * This will work if this function has requested Universal XPConnect |
michael@0 | 625 | * privileges, so any code which needs those privileges can't be in this |
michael@0 | 626 | * function. |
michael@0 | 627 | */ |
michael@0 | 628 | enableChildLoadCallback(); |
michael@0 | 629 | iframeCw.location = "http://example.com"; |
michael@0 | 630 | iframe.onload = onChildLoad; |
michael@0 | 631 | yield undefined; |
michael@0 | 632 | iframe.onload = null; |
michael@0 | 633 | |
michael@0 | 634 | expectException( |
michael@0 | 635 | function() { iframeCw.history.pushState({}, "foo"); }, |
michael@0 | 636 | "pushState-ing in a different origin"); |
michael@0 | 637 | |
michael@0 | 638 | expectException( |
michael@0 | 639 | function() { iframeCw.history.replaceState({}, "foo"); }, |
michael@0 | 640 | "replaceState-ing in a different origin"); |
michael@0 | 641 | |
michael@0 | 642 | /* |
michael@0 | 643 | * If we do the following: |
michael@0 | 644 | * * Start at page A. |
michael@0 | 645 | * * PushState to page B. |
michael@0 | 646 | * * Refresh. The server responds with a 404 |
michael@0 | 647 | * * Go back. |
michael@0 | 648 | * Then at the end, page A should be displayed, not the 404 page. |
michael@0 | 649 | */ |
michael@0 | 650 | enableChildLoadCallback(); |
michael@0 | 651 | iframe.onload = onChildLoad; |
michael@0 | 652 | iframeCw.location = "about:blank"; |
michael@0 | 653 | yield undefined; |
michael@0 | 654 | iframe.onload = null; |
michael@0 | 655 | |
michael@0 | 656 | enableChildLoadCallback(); |
michael@0 | 657 | // navigate to http://mochi.test:8888/[...]/file_bug500328_1.html |
michael@0 | 658 | iframeCw.location = innerLoc; |
michael@0 | 659 | yield undefined; |
michael@0 | 660 | |
michael@0 | 661 | // PushState to a URL which doesn't exist |
michael@0 | 662 | iframeCw.history.pushState({}, "", rand); |
michael@0 | 663 | |
michael@0 | 664 | // Refresh. We'll end up a 404 page. |
michael@0 | 665 | iframe.onload = onChildLoad; |
michael@0 | 666 | enableChildLoadCallback(); |
michael@0 | 667 | iframeCw.location.reload(true); |
michael@0 | 668 | yield undefined; |
michael@0 | 669 | iframe.onload = null; |
michael@0 | 670 | |
michael@0 | 671 | // Since the last page was a 404, going back should actually show the |
michael@0 | 672 | // contents of the old page, instead of persisting the contents of the 404 |
michael@0 | 673 | // page. |
michael@0 | 674 | enableChildPageShowCallback(); |
michael@0 | 675 | iframeCw.history.back(); |
michael@0 | 676 | yield undefined; |
michael@0 | 677 | |
michael@0 | 678 | // Make sure that we're actually showing the contents of |
michael@0 | 679 | // file_bug500328_1.html, as opposed to the 404 page. |
michael@0 | 680 | var identifierElem = iframeCw.document.getElementById("link-anchor1"); |
michael@0 | 681 | ok(identifierElem != undefined && identifierElem != null, |
michael@0 | 682 | "iframe didn't contain file_bug500328_1.html's contents."); |
michael@0 | 683 | |
michael@0 | 684 | /** |
michael@0 | 685 | * TEST 6 tests that the referrer is set properly after push/replace states. |
michael@0 | 686 | */ |
michael@0 | 687 | |
michael@0 | 688 | /* |
michael@0 | 689 | * First, a simple test: |
michael@0 | 690 | * * Load file_bug500328_1.html into iframe |
michael@0 | 691 | * * PushState to newpage1.html#foo |
michael@0 | 692 | * * Instruct the iframe to load file_bug500328_1.html into itself. |
michael@0 | 693 | * The referer should be newpage1.html, without the hash. |
michael@0 | 694 | * |
michael@0 | 695 | * This also tests that we can call pushState from within the onload handler. |
michael@0 | 696 | */ |
michael@0 | 697 | enableChildLoadCallback(); |
michael@0 | 698 | iframeCw.location = "file_bug500328_1.html"; |
michael@0 | 699 | yield undefined; |
michael@0 | 700 | |
michael@0 | 701 | // Run within the onload handler. This should work without issue. |
michael@0 | 702 | iframeCw.history.pushState(null, "", "newpage1.html"); |
michael@0 | 703 | |
michael@0 | 704 | // iframeCw.navigateTo() causes the iframe to set its location on our |
michael@0 | 705 | // behalf. We can't just set its location ourselves, because then *we* |
michael@0 | 706 | // become the referrer. |
michael@0 | 707 | enableChildLoadCallback(); |
michael@0 | 708 | iframeCw.navigateTo("file_bug500328_1.html"); |
michael@0 | 709 | yield undefined; |
michael@0 | 710 | |
michael@0 | 711 | ok(iframeCw.document.referrer.toString().match(/newpage1.html$/), |
michael@0 | 712 | "Wrong referrer after pushState. Expected newpage1.html, but was " + |
michael@0 | 713 | iframeCw.document.referrer); |
michael@0 | 714 | |
michael@0 | 715 | /* |
michael@0 | 716 | * We're back at file_bug500328_1.html. Now do the following: |
michael@0 | 717 | * * replaceState to newpage2.html#foo |
michael@0 | 718 | * * Click a link back to file_bug500328_1.html |
michael@0 | 719 | * The referrer should be newpage2.html, without the hash. |
michael@0 | 720 | */ |
michael@0 | 721 | iframeCw.history.replaceState(null, null, "newpage2.html#foo"); |
michael@0 | 722 | enableChildLoadCallback(); |
michael@0 | 723 | sendMouseEvent({type:'click'}, 'link-self', iframeCw); |
michael@0 | 724 | yield undefined; |
michael@0 | 725 | |
michael@0 | 726 | ok(iframeCw.document.referrer.toString().match(/newpage2.html$/), |
michael@0 | 727 | "Wrong referrer after replaceState. Expected newpage2.html, but was " + |
michael@0 | 728 | iframeCw.document.referrer); |
michael@0 | 729 | |
michael@0 | 730 | /* |
michael@0 | 731 | * Set up a cycle with the popstate event to make sure it's properly |
michael@0 | 732 | * collected. |
michael@0 | 733 | */ |
michael@0 | 734 | var evt = document.createEvent("popstateevent"); |
michael@0 | 735 | evt.initEvent("foo", false, false, evt); |
michael@0 | 736 | |
michael@0 | 737 | /* */ |
michael@0 | 738 | SimpleTest.finish(); |
michael@0 | 739 | statusMsg("********** Finished tests ***********"); |
michael@0 | 740 | while(true) |
michael@0 | 741 | { |
michael@0 | 742 | yield undefined; |
michael@0 | 743 | |
michael@0 | 744 | // I don't think this will actually make the mochitest fail, but there's |
michael@0 | 745 | // not much we can do about this. Realistically, though, regressions are |
michael@0 | 746 | // not likely to fire extra events -- this trap is here mostly to catch |
michael@0 | 747 | // errors made while wriring tests. |
michael@0 | 748 | ok(false, "Got extra event!"); |
michael@0 | 749 | } |
michael@0 | 750 | |
michael@0 | 751 | /* |
michael@0 | 752 | statusMsg("XXXXXXXXXXXXXX"); |
michael@0 | 753 | while(true) { |
michael@0 | 754 | yield undefined; |
michael@0 | 755 | statusMsg("Woken up."); |
michael@0 | 756 | } |
michael@0 | 757 | */ |
michael@0 | 758 | } |
michael@0 | 759 | |
michael@0 | 760 | // Important: Wait to start the tests until the page has loaded. Otherwise, |
michael@0 | 761 | // the test will occasionally fail when it begins running before the iframes |
michael@0 | 762 | // have finished their initial load of about:blank. |
michael@0 | 763 | window.addEventListener('load', function() { |
michael@0 | 764 | gGen = runTest(); |
michael@0 | 765 | gGen.next(); |
michael@0 | 766 | }, false); |
michael@0 | 767 | |
michael@0 | 768 | </script> |
michael@0 | 769 | </pre> |
michael@0 | 770 | </body> |
michael@0 | 771 | </html> |