Let's say I'm on /page?id=1
Then I navigate to /page?id=2
And I make a change on that page, which implements a post and then redirects back to /page?id=2
In Firefox, I can hit the back button once and return to /page?id=1, but in Chrome and Safari on iPhone, I have to hit the back button twice because /page?id=2 is in the browser history twice. (And if I made multiple posts from id=2, I'd have to hit the back button that many times to finally return to id=1.)
In some ways, this seems like normal browser behavior, as each GET is simply pushed into the history, but since the URL is identical to the previous entry, this results in a poor user experience, which typically seems to be avoided by other web applications... and is naturally avoided in Firefox. Is this an unavoidable bug in Webkit browsers, or can I implement the PRG in a different way to avoid this?
btw- the behavior appears to be the same redirecting with 302 or 303.
UPDATE: I've mocked up some sample code... don't know if there's a platform like jsfiddle where I could upload this for you to see in action:
form.php:
id=<?=$_REQUEST['id']?>
<form action="submit.php" method="post">
<input type="hidden" name="id" value="<?=$_REQUEST['id']?>">
<input type="submit" value="submit">
</form>
submit.php:
<?php
header("Location: form.php?id=" . $_REQUEST['id']);
die($_REQUEST['id']);
?>
If I start on form.php?id=4 (just to put it in the browser history) and then go to form.php?id=5 and then hit submit (as though to execute a database change), in Firefox I get one entry in the history for each; in Chrome I get one entry for id=4 and then two entries for id=5. Why the difference in behavior? I think Firefox's behavior is better, since hitting back twice to get away from id=5 is counter-intuitive to the user.
Though it's not an explanation on what happens, I have a way around it that I use in all of my applications. First some code:
form.php
would look like this:
id=<?=$_REQUEST['id']?>
<form action="submit.php" target="iframe" method="post">
<input type="hidden" name="id" value="<?=$_REQUEST['id']?>">
<input type="submit" value="submit">
</form>
submit.php
like this:
<?php
header("Location: form.php?id=" . $_REQUEST['id']);
die($_REQUEST['id']);
?
<script>
window.parent.location.reload();
</script>
It's your documents with some extra stuff in the <form>
tag and a brand new <script>
tag.
I would also have an iframe
somewhere in the document like this:
<iframe name="iframe"></iframe>
So to explain. Instead of navigating to a new site and back again every time you need to make a change, you simply load the submit.php
in an iframe
in your existing document. Hence the target="iframe"
part.
Then when the iframe
has loaded, meaning the changes have been made, you reload the original page to reflect these changes, hence the window.parent.location.reload();
part. Since the page is just reloading, it won't put a second entry in your history.
I hope this helped you :)
We also experienced the same problem and even after researching for days I found no "easy" solution. The closest thing I found was this Webkit Bugzilla ticket, which by the looks of it doesn't seem to be very high priority. As you mentioned, IE and Firefox behave just fine.
Since we have our own back button in the application, we were able to solve the problem by using the session storage and checking it when the page is loaded. The TypeScript code is the following:
class DoubleHistoryWorkaround {
// key of the attribute we store the URL we where on when clicking on the back button
private static comingFromLabel = "comingFromURL";
// key of the attribute of the flag denoting whether we set a valid comingFromURL
private static comingFromFlag = "comingFromSet";
constructor() {
this.checkLocation();
}
/**
* Checks the URL we saved in the session and goes a further step back
* in the history if the first back button brought us to the same page again.
*/
private checkLocation() {
let doubleEntry : boolean;
// have we set a comingFromURL?
let comingFromSet = window.sessionStorage.getItem(DoubleHistoryWorkaround.comingFromFlag);
if (comingFromSet) {
// is the set comingFromURL the same as our current page?
let currentURL = window.location.href;
let comingFromURL = window.sessionStorage.getItem(DoubleHistoryWorkaround.comingFromLabel);
if (currentURL === comingFromURL) {
// double history entry detected
doubleEntry = true;
// before we skip we save our location ourselves, since we might still navigate
// to the same page again (in case of triple identical history entries)
DoubleHistoryWorkaround.saveLocation();
// skip this page
history.back();
}
}
// reset the location entry unless we just set it ourselves
if (!doubleEntry) {
this.resetLocation();
}
}
/**
* Saves the current location in the session storage.
*/
public static saveLocation() {
window.sessionStorage.setItem(DoubleHistoryWorkaround.comingFromFlag, "true");
window.sessionStorage.setItem(DoubleHistoryWorkaround.comingFromLabel, window.location.href);
}
/**
* Removes the set location from the session storage.
*/
private resetLocation() {
window.sessionStorage.setItem(DoubleHistoryWorkaround.comingFromFlag, "false");
window.sessionStorage.setItem(DoubleHistoryWorkaround.comingFromLabel, "");
}
}
We call DoubleHistoryWorkaround.saveLocation()
when we click our application's back button, setting the session entries that are checked by checkLocation()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With