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