2010-08-13 Mihai Parparita <mihaip@chromium.org>

        Reviewed by Dimitri Glazkov.

        Session history should skip over JS redirects
        https://bugs.webkit.org/show_bug.cgi?id=42861

        Lock the back/forward list for location changes and form submits that
        happen before the onload event fires that are not the result of user
        gestures.

        http/tests/history tests now pass, their expectations were updated
        accordingly. Other tests needed a setTimeout wrapper around location
        changes and form submits during onload, otherwise they would not
        generate history entries as expected anymore.

        * fast/css/target-fragment-match.html:
        * fast/dom/location-hash.html:
        * fast/dom/Geolocation/resources/cached-page-1.html:
        * fast/dom/Window/timer-resume-on-navigation-back.html:
        * fast/events/pageshow-pagehide-on-back-cached-with-frames-expected.txt:
        * fast/events/pageshow-pagehide-on-back-cached-with-frames.html:
        * fast/forms/button-state-restore.html:
        * fast/forms/state-restore-to-non-autocomplete-form.html:
        * fast/forms/state-restore-to-non-edited-controls.html:
        * fast/frames/resources/cached-page-1.html:
        * fast/frames/resources/cached-page-2.html:
        * fast/harness/resources/cached-page-1.html:
        * fast/harness/resources/cached-page-with-data-urls.html:
        * fast/history/gesture-before-onload-expected.txt: Added.
        * fast/history/gesture-before-onload.html: Added.
        * fast/history/history-length.html:
        * fast/history/resources/gesture-before-onload-target.html: Added.
        * fast/history/saves-state-after-fragment-nav.html:
        * fast/loader/input-element-page-cache-crash.html:
        * fast/loader/stateobjects/document-destroyed-navigate-back.html:
        * fast/loader/stateobjects/pushstate-clears-forward-history.html:
        * fast/loader/subframe-navigate-during-main-frame-load.html:
        * http/tests/history/back-to-post.php:
        * http/tests/history/redirect-js-document-location-before-load-expected.txt:
        * http/tests/history/redirect-js-form-submit-before-load-expected.txt:
        * http/tests/history/redirect-js-location-assign-before-load-expected.txt:
        * http/tests/history/redirect-js-location-before-load-expected.txt:
        * http/tests/history/redirect-js-location-href-before-load-expected.txt:
        * http/tests/loading/307-after-303-after-post-expected.txt:
        * http/tests/loading/redirect-methods-expected.txt:
        * http/tests/navigation/resources/back-send-referrer-helper.php:
        * http/tests/navigation/resources/document-location.js:
        (start):
        * http/tests/navigation/resources/submit-to-fragment.pl:
        * security/autocomplete-cleared-on-back.html:
        * storage/hash-change-with-xhr.js:
        (updateDatabase):
        (invokeBack):
        (runTest):
        (runTestsInner):

2010-08-13  Mihai Parparita  <mihaip@chromium.org>

        Reviewed by Dimitri Glazkov.

        Session history should skip over JS redirects
        https://bugs.webkit.org/show_bug.cgi?id=42861

        Lock the back/forward list for location changes and form submits that
        happen before the onload event fires that are not the result of user
        gestures.

        Made form submission (at the ScheduledFormSubmission level) more similar
        to ScheduledURLNavigation by having it call clientRedirected too, fixing
        a long-standing FIXME.

        Test: fast/history/gesture-before-onload-location-href.html,
        fast/history/gesture-before-onload-form-submit.html and updated
        expectations for http/tests/history tests that used to fail.

        * loader/FormSubmission.cpp:
        (WebCore::FormSubmission::requestURL):
        (WebCore::FormSubmission::populateFrameLoadRequest):
        * loader/FormSubmission.h:
        * loader/RedirectScheduler.cpp:
        (WebCore::ScheduledFormSubmission::ScheduledFormSubmission):
        (WebCore::ScheduledFormSubmission::fire):
        (WebCore::ScheduledFormSubmission::didStartTimer):
        (WebCore::ScheduledFormSubmission::didStopTimer):
        (WebCore::RedirectScheduler::scheduleRedirect):
        (WebCore::RedirectScheduler::mustLockBackForwardList):
        (WebCore::RedirectScheduler::scheduleLocationChange):
        (WebCore::RedirectScheduler::scheduleFormSubmission):
        * loader/RedirectScheduler.h:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@65340 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 79dc7d5f
2010-08-13 Mihai Parparita <mihaip@chromium.org>
Reviewed by Dimitri Glazkov.
Session history should skip over JS redirects
https://bugs.webkit.org/show_bug.cgi?id=42861
Lock the back/forward list for location changes and form submits that
happen before the onload event fires that are not the result of user
gestures.
http/tests/history tests now pass, their expectations were updated
accordingly. Other tests needed a setTimeout wrapper around location
changes and form submits during onload, otherwise they would not
generate history entries as expected anymore.
* fast/css/target-fragment-match.html:
* fast/dom/location-hash.html:
* fast/dom/Geolocation/resources/cached-page-1.html:
* fast/dom/Window/timer-resume-on-navigation-back.html:
* fast/events/pageshow-pagehide-on-back-cached-with-frames-expected.txt:
* fast/events/pageshow-pagehide-on-back-cached-with-frames.html:
* fast/forms/button-state-restore.html:
* fast/forms/state-restore-to-non-autocomplete-form.html:
* fast/forms/state-restore-to-non-edited-controls.html:
* fast/frames/resources/cached-page-1.html:
* fast/frames/resources/cached-page-2.html:
* fast/harness/resources/cached-page-1.html:
* fast/harness/resources/cached-page-with-data-urls.html:
* fast/history/gesture-before-onload-expected.txt: Added.
* fast/history/gesture-before-onload.html: Added.
* fast/history/history-length.html:
* fast/history/resources/gesture-before-onload-target.html: Added.
* fast/history/saves-state-after-fragment-nav.html:
* fast/loader/input-element-page-cache-crash.html:
* fast/loader/stateobjects/document-destroyed-navigate-back.html:
* fast/loader/stateobjects/pushstate-clears-forward-history.html:
* fast/loader/subframe-navigate-during-main-frame-load.html:
* http/tests/history/back-to-post.php:
* http/tests/history/redirect-js-document-location-before-load-expected.txt:
* http/tests/history/redirect-js-form-submit-before-load-expected.txt:
* http/tests/history/redirect-js-location-assign-before-load-expected.txt:
* http/tests/history/redirect-js-location-before-load-expected.txt:
* http/tests/history/redirect-js-location-href-before-load-expected.txt:
* http/tests/loading/307-after-303-after-post-expected.txt:
* http/tests/loading/redirect-methods-expected.txt:
* http/tests/navigation/resources/back-send-referrer-helper.php:
* http/tests/navigation/resources/document-location.js:
(start):
* http/tests/navigation/resources/submit-to-fragment.pl:
* security/autocomplete-cleared-on-back.html:
* storage/hash-change-with-xhr.js:
(updateDatabase):
(invokeBack):
(runTest):
(runTestsInner):
2010-08-13 Sam Weinig <sam@webkit.org>
Rubber-stamped by Beth Dakin and Ban Bernstein.
......@@ -11,6 +11,12 @@ function test()
{
if (window.layoutTestController)
layoutTestController.waitUntilDone();
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(runTest, 0);
}
function runTest() {
window.location.hash ='#target-01';
document.body.offsetTop;
window.history.back(); // This queues up a navigation, so we need to delay the call to notifyDone.
......
......@@ -3,7 +3,8 @@ function loadNext() {
var geolocation = navigator.geolocation;
if (window.opener.reportPageOneOnload() == 1) {
window.opener.debug('resources/cached-page-1.html about to navigate to resources/cached-page-2.html')
location.href = 'cached-page-2.html';
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {location.href = 'cached-page-2.html';}, 0);
}
}
</script>
......
......@@ -18,9 +18,12 @@ function runTest() {
layoutTestController.waitUntilDone();
layoutTestController.overridePreference("WebKitUsesPageCachePreferenceKey", 1);
}
window.setTimeout("verify()", timeoutValue);
timestamp = new Date().getTime();
window.location.href = "data:text/html,<body onload='history.back()'></body>";
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {
window.setTimeout(verify, timeoutValue);
timestamp = new Date().getTime();
window.location.href = "data:text/html,<body onload='history.back()'></body>";
}, 0);
}
</script>
......
......@@ -87,8 +87,9 @@
numErrors = 0;
originalLocation = window.location.href;
originalHistoryLength = window.history.length;
step();
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(step, 0);
}
</script>
</head>
......
CONSOLE MESSAGE: line 20: ***Top level frame being parsed for the initial page load***
CONSOLE MESSAGE: line 20: Subsubframe window.onload
CONSOLE MESSAGE: line 20: Subsubframe window.onpageshow, target = [object HTMLDocument], persisted = false
CONSOLE MESSAGE: line 20: Subframe window.onload
CONSOLE MESSAGE: line 20: Subframe window.onpageshow, target = [object HTMLDocument], persisted = false
CONSOLE MESSAGE: line 20: Main frame window.onload
CONSOLE MESSAGE: line 20: Main frame window.onpageshow, target = [object HTMLDocument], persisted = false
CONSOLE MESSAGE: line 20: ***Navigating top-level frame to a page that will immediately navigate back to this one***
CONSOLE MESSAGE: line 20: Main frame window.onpagehide, target = [object HTMLDocument], persisted = true
CONSOLE MESSAGE: line 20: Subframe window.onpagehide, target = [object HTMLDocument], persisted = true
CONSOLE MESSAGE: line 20: Subsubframe window.onpagehide, target = [object HTMLDocument], persisted = true
CONSOLE MESSAGE: line 20: Subsubframe window.onpageshow, target = [object HTMLDocument], persisted = true
CONSOLE MESSAGE: line 20: Subframe window.onpageshow, target = [object HTMLDocument], persisted = true
CONSOLE MESSAGE: line 20: Main frame window.onpageshow, target = [object HTMLDocument], persisted = true
Test pageshow/pagehide event behavior when navigating away from a page with frames, putting the page in the page cache, then back to it.
***Top level frame being parsed for the initial page load***
Subsubframe window.onload
Subsubframe window.onpageshow, target = [object HTMLDocument], persisted = false
Subframe window.onload
Subframe window.onpageshow, target = [object HTMLDocument], persisted = false
Main frame window.onload
Main frame window.onpageshow, target = [object HTMLDocument], persisted = false
***Navigating top-level frame to a page that will immediately navigate back to this one***
Main frame window.onpagehide, target = [object HTMLDocument], persisted = true
Subframe window.onpagehide, target = [object HTMLDocument], persisted = true
Subsubframe window.onpagehide, target = [object HTMLDocument], persisted = true
Subsubframe window.onpageshow, target = [object HTMLDocument], persisted = true
Subframe window.onpageshow, target = [object HTMLDocument], persisted = true
Main frame window.onpageshow, target = [object HTMLDocument], persisted = true
......@@ -14,8 +14,10 @@ if (window.layoutTestController) {
function log(message)
{
var log = document.getElementById("log");
log.innerHTML += message + "\n";
// Logging to the console instead of the "log" DIV in the DOM because
// otherwise we trigger the assert mentioned at http://webkit.org/b/43152
// (since we log during pagehide)
console.log(message);
}
log("***Top level frame being parsed for the initial page load***");
......@@ -31,7 +33,8 @@ window.onpageshow = function(evt) {
layoutTestController.notifyDone();
} else {
log("***Navigating top-level frame to a page that will immediately navigate back to this one***");
window.location.href = "data:text/html,<script>history.back();</scr" + "ipt>";
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {window.location.href = "data:text/html,<script>history.back();</scr" + "ipt>";}, 0);
}
}
......
......@@ -31,7 +31,9 @@
input.value = "FAIL";
var form = document.getElementById("form");
form.submit();
// Submit form in a timeout to make sure that we create a new back/forward list item.
setTimeout(function() {form.submit();}, 0);
}
</script>
</head>
......
......@@ -38,7 +38,8 @@ if (!state.value) {
document.getElementById('input2').value = 'value2';
document.getElementById('textarea2').value = 'good';
document.getElementById('select2').value = 'BSD';
document.getElementById('form2').submit();
// Submit form in a timeout to make sure that we create a new back/forward list item.
setTimeout(function() {document.getElementById('form2').submit();}, 0);
} else {
// Second visit.
debug('Controls in the first form should have their default values:');
......
......@@ -37,7 +37,8 @@ if (!state.value) {
makeForm(parent, '1', '1', '1', '1', '1', '1', '1', '1');
document.getElementById('text1').value = 'edit';
document.getElementById('form1').submit();
// Submit form in a timeout to make sure that we create a new back/forward list item.
setTimeout(function() {document.getElementById('form1').submit();}, 0);
} else {
// Second visit.
makeForm(parent, '2', '2', '2', '2', '2', '2', '2', '2');
......
......@@ -17,7 +17,8 @@ function loadNext() {
intervalId = setInterval(endTest, 100);
window.opener.log("page-1, about to navigate to page-2.")
location.href = "cached-page-2.html";
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {location.href = "cached-page-2.html";}, 0);
}
// This unload handler exists just to make sure this page is not added
......
......@@ -19,7 +19,8 @@ function loadNext() {
intervalId = setInterval(goBack, 20);
window.opener.log("page-2, about to navigate to page-3.")
location.href = "cached-page-3.html";
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {location.href = "cached-page-3.html";}, 0);
}
</script>
<body onload="loadNext()">
......
......@@ -22,7 +22,8 @@ function loadNext()
intervalId = setInterval(check, 10);
window.opener.log("page-1, about to navigate to page-2.")
location.href = "cached-page-2.html";
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {location.href = "cached-page-2.html";}, 0);
}
</script>
<body onload="loadNext()"></body>
......@@ -18,7 +18,8 @@ function loadNext()
intervalId = setInterval(check, 10);
window.opener.log("page with data urls, about to navigate to page-2.")
location.href = "cached-page-2.html";
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {location.href = "cached-page-2.html";}, 0);
}
</script>
<body onload="loadNext()">
......
This page is the target of a redirect.
PASS: History item count should be 2 and is.
============== Back Forward List ==============
(file test):fast/history/gesture-before-onload-form-submit.html **nav target**
curr-> (file test):fast/history/resources/gesture-before-onload-target.html?#2 **nav target**
===============================================
<head>
</head>
<body onload="onload()">
<form action="resources/gesture-before-onload-target.html#2">
<input type="submit" id="nav-button" value="Navigate">
</form>
<div id="manual-explanation" style="display: none; color: blue">When running this test outside of DRT, please click on the "Navigate" button.</div>
</body>
<script>
if (window.layoutTestController) {
layoutTestController.clearBackForwardList();
layoutTestController.waitUntilDone();
}
if (window.eventSender) {
var navButtonNode = document.getElementById('nav-button');
eventSender.mouseMoveTo(
navButtonNode.offsetLeft + navButtonNode.offsetWidth / 2,
navButtonNode.offsetTop + navButtonNode.offsetHeight / 2);
eventSender.mouseDown();
eventSender.mouseUp();
} else {
document.getElementById('manual-explanation').style.display = '';
}
function onload() {
window.console.log('FAIL: Should not have reached onload before navigating away');
}
</script>
This page is the target of a redirect.
PASS: History item count should be 2 and is.
============== Back Forward List ==============
(file test):fast/history/gesture-before-onload-location-href.html **nav target**
curr-> (file test):fast/history/resources/gesture-before-onload-target.html#2 **nav target**
===============================================
<head>
</head>
<body onload="onload()">
<button id="nav-button" onclick="nav();">Navigate</button>
<div id="manual-explanation" style="display: none; color: blue">When running this test outside of DRT, please click on the "Navigate" button.</div>
</body>
<script>
if (window.layoutTestController) {
layoutTestController.clearBackForwardList();
layoutTestController.waitUntilDone();
}
if (window.eventSender) {
var navButtonNode = document.getElementById('nav-button');
eventSender.mouseMoveTo(
navButtonNode.offsetLeft + navButtonNode.offsetWidth / 2,
navButtonNode.offsetTop + navButtonNode.offsetHeight / 2);
eventSender.mouseDown();
eventSender.mouseUp();
} else {
document.getElementById('manual-explanation').style.display = '';
}
function nav() {
window.location.href = 'resources/gesture-before-onload-target.html#2';
}
function onload() {
window.console.log('FAIL: Should not have reached onload before navigating away');
}
</script>
......@@ -8,30 +8,34 @@ onload = function() {
layoutTestController.waitUntilDone();
}
}
// This test advances history by 2 pages, then navigates back one, and
// records history.length. We expect history.length to indicate the total
// length of session history. At the end of the test, it should be 2 greater
// than it was at the start of the test.
switch (sessionStorage.testStage++) {
case 0:
sessionStorage.initialLength = history.length;
location = "?a";
break;
case 1:
location = "?b";
break;
case 2:
history.back();
break;
case 3:
if (history.length == (sessionStorage.initialLength - 0) + 2)
document.body.innerHTML = "PASS";
else
document.body.innerHTML = "FAIL: initialLength=" + sessionStorage.initialLength + ", history.length=" + history.length;
if (window.layoutTestController)
layoutTestController.notifyDone();
break;
}
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {
// This test advances history by 2 pages, then navigates back one, and
// records history.length. We expect history.length to indicate the total
// length of session history. At the end of the test, it should be 2 greater
// than it was at the start of the test.
switch (sessionStorage.testStage++) {
case 0:
sessionStorage.initialLength = history.length;
location = "?a";
break;
case 1:
location = "?b";
break;
case 2:
history.back();
break;
case 3:
if (history.length == (sessionStorage.initialLength - 0) + 2)
document.body.innerHTML = "PASS";
else
document.body.innerHTML = "FAIL: initialLength=" + sessionStorage.initialLength + ", history.length=" + history.length;
if (window.layoutTestController)
layoutTestController.notifyDone();
break;
}
}, 0);
}
onunload = function() {
// disable page cache
......
<!-- This page accepts a query string specifying how many history items should
be present once it finishes loading. -->
<html>
<head>
<title>Redirect Target</title>
<script>
function log(s)
{
document.getElementById("console").appendChild(document.createTextNode(s + "\n"));
}
function testHistoryItemCount()
{
var expected = parseInt(location.hash.slice(1));
var actual = history.length;
if (actual === expected)
log("PASS: History item count should be " + expected + " and is.");
else
log("FAIL: History item count should be " + expected + " but instead is " + actual + ".");
}
window.addEventListener("load", function () {
if (window.layoutTestController) {
layoutTestController.dumpAsText();
testHistoryItemCount();
layoutTestController.dumpBackForwardList();
layoutTestController.notifyDone();
} else {
testHistoryItemCount();
}
}, false);
</script>
</head>
<body>
<p>This page is the target of a redirect.</p>
<pre id="console"></pre>
</body>
</html>
......@@ -30,8 +30,11 @@ function runTestStep() {
layoutTestController.dumpAsText();
layoutTestController.waitUntilDone();
}
navigateToHash(field);
navigateAwayAndBack();
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {
navigateToHash(field);
navigateAwayAndBack();
}, 0);
} else {
document.body.innerHTML = (field.value == '') ? 'FAIL' : 'PASS';
if (window.layoutTestController)
......
......@@ -13,8 +13,9 @@ function runTest()
var input = document.getElementById("testinput");
input.setAttribute("autocomplete", "on");
input.parentNode.removeChild(input);
window.location = "data:text/html,<script>history.back();</scrip" + "t>";
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {window.location = "data:text/html,<script>history.back();</scrip" + "t>";}, 0);
}
function pageHidden()
......
......@@ -33,9 +33,10 @@ function runThirdStageOfTest()
function loaded()
{
if (!sessionStorage.stage)
runFirstStageOfTest();
else if (sessionStorage.stage == 2)
if (!sessionStorage.stage) {
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(runFirstStageOfTest, 0);
} else if (sessionStorage.stage == 2)
runSecondStageOfTest();
else if (sessionStorage.stage == 3)
runThirdStageOfTest();
......
......@@ -8,22 +8,26 @@ onload = function() {
layoutTestController.waitUntilDone();
}
}
switch (sessionStorage.testStage++) {
case 0:
location = "?a";
break;
case 1:
location = "?b";
break;
case 2:
history.back();
break;
case 3:
history.pushState(null, null);
if (window.layoutTestController)
layoutTestController.notifyDone();
break;
}
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {
switch (sessionStorage.testStage++) {
case 0:
location = "?a";
break;
case 1:
location = "?b";
break;
case 2:
history.back();
break;
case 3:
history.pushState(null, null);
if (window.layoutTestController)
layoutTestController.notifyDone();
break;
}
}, 0);
}
onunload = function() {
// disable page cache
......
......@@ -9,8 +9,9 @@ function runTest()
layoutTestController.dumpBackForwardList();
layoutTestController.waitUntilDone();
}
location='resources/subframe-navigate-during-main-frame-load2.html';
// Location changes need to happen outside the onload handler to generate history entries.
setTimeout(function() {location='resources/subframe-navigate-during-main-frame-load2.html';}, 0);
}
</script>
......
......@@ -14,6 +14,13 @@ Getting an error page instead of login page navigating back in gmail.</p>
<input id="mysubmit" type="submit" name="Submit" value="Submit">
</form>
<script>
function submitForm()
{
// Submit form in a timeout to make sure that we create a new back/forward list item.
setTimeout(function() {document.forms[0].submit()}, 0);
}
if (window.layoutTestController) {
layoutTestController.dumpAsText();
layoutTestController.waitUntilDone();
......@@ -25,7 +32,7 @@ if (!window.layoutTestController)
if (document.location.search == "") {
window.name = ""; // Use window.name to communicate between steps.
document.forms[0].submit();
submitForm();
} else if (document.location.search == "?1") {
if (window.name == "finish") {
window.name = "";
......@@ -34,7 +41,7 @@ if (document.location.search == "") {
layoutTestController.notifyDone();
} else {
document.forms[0].action = "?2";
document.forms[0].submit();
submitForm();
}
} else {
// Test that going back to form submission result works.
......
......@@ -12,7 +12,7 @@ print <<HERE_DOC_END
<title>200 Refresh Redirect</title>
<script>
if (window.layoutTestController) {
layoutTestController.keepWebHistory();
layoutTestController.clearBackForwardList();
layoutTestController.waitUntilDone();
}
</script>
......
......@@ -12,7 +12,7 @@ print <<HERE_DOC_END
<title>200 Refresh Redirect</title>
<script>
if (window.layoutTestController) {
layoutTestController.keepWebHistory();
layoutTestController.clearBackForwardList();
layoutTestController.waitUntilDone();
}