You're going to have to use some kind of horrible browser sniffing here. For Firefox, I would go with your solution of storing the scroll position and restoring it.
I thought I had a good Webkit solution based on your description, but I just tried in Chrome 21, and it seems that Chrome scrolls first, then fires the popstate event, then fires the scroll event. But for reference, here's what I came up with:
function noScrollOnce(event) {
event.preventDefault();
document.removeEventListener('scroll', noScrollOnce);
}
window.onpopstate = function () {
document.addEventListener('scroll', noScrollOnce);
};
Black magic such as pretending the page is scrolling by moving an absolute
positioned element is ruled out by the screen repainting speed too.
So I'm 99% sure that the answer is that you can't, and you're going to have to use one of the compromises you've mentioned in the question. Both browsers scroll before JavaScript knows anything about it, so JavaScript can only react after the event. The only difference is that Firefox doesn't paint the screen until after the Javascript has fired, which is why there's a workable solution in Firefox but not in WebKit.