Is *onbeforeunload* cached on Safari (macOS)?

2019-05-22 23:37发布

问题:

I add an event lister on beforeunload as customary in my JS/ReactJS application. The function adds a confirmation dialog depending on an internal unSaved state.

Everything works fine on Chrome and Firefox (macOS / desktop).

On Safari, however:

  • the first time "I make use" of the event handler (I leave the page while unSaved==true), it works fine as expected,
  • yet, in subsequent attempts of leaving the page with unSaved==true, Safari does not ask for any confirmation at all.
  • when I go to a new tab (with the same previous URL), again the first time works, subsequent times do not...

What is weirder: I can see that my added event function is actually being called every time on safari since a test console.log is indeed being printed every time.

My only possible guess is that Safari is somehow caching my confirmation response for the tab? (?)

Any ideas for how to solve this?

My stack:

Safari: Version 10.0.1 (12602.2.14.0.7) macOS: 10.12.1 (16B2657) host: localhost protocols: tested on both, http and https

More info:

  • event pageshow has the property persisted always set to false. Therefore, the Page Cache by Safari (BFCache) should not be the cause of the problem.

回答1:

Problem

Your problem is caused by a browser feature called back-forward cache (BFCache).

It is supposed to save complete state of page when user navigates away. The idea is when user navigates back with back button page can be loaded from cache very quickly.

Chrome and Firefox considers event handlers on beforeunload event as a signal to not store page in back-forward cache, but Safari ignores such handlers.


Solution

I have found only 1 working solution, but it's a bit imperfect, hacky and ugly.

You can check the persisted property of the onpageshow event. It is set to false on initial page load. When page is loaded from back-forward cache it is set to true.

The onpageshow event is similar to the onload event, except that it occurs after the onload event when the page first loads. Also, the onpageshow event occurs every time the page is loaded, whereas the onload event does not occur when the page is loaded from the cache.

Important to note:

The solution is rather imperfect as the page is still shown briefly before reloading. This means, your page loads... and then - it's forced to start all over again. After all, it's not like it's triggering a reload after the page is fully loaded, but still.

And one more minor thing: of course, the solution is easily bypassed by disabling javascript.

So, use it on your own responsibility:

window.onpageshow = function(event) {
    if (event.persisted) {
        window.location.reload();
    }
};

Credits

  • Information about the Safari Page Cache and possible future changes to how unload events work
  • Mika Tuupola's answer on this question


回答2:

Seems to be Safari's back/forward cache issue. This cache (called also BFCache or Page Cache) stores current state of the entire web-page including JavaScript code state and restores it when user press browser's Back button. You can read about it here. Usually when beforeunload is used on loaded web-page browser turns BFCache off. But there may be something in your JavaScript code that stops this behavour. Or maybe you set unSaved state = false and then it gets cached? Try manually turn off back/forward cache by adding workaround on your page:

window.onpageshow = function(event) {
    if (event.persisted) {
        window.location.reload() 
    }
};