3

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.
juanmirocks
  • 4,786
  • 5
  • 46
  • 46
  • You're probably running afoul of anti-malware protection, since a "confirmation" before closing the tab is common on malicious sites. – ssube Nov 21 '16 at 19:37
  • @ssube you mean that Safari interprets my repeated confirmation attempts as malware behavior and therefore just ignores my logic ? -- Chrome and Firefox add to the confirmation pop-up a checkbox for the user to decide _"prevent this page from creating additional dialogues"_, which of course is perfectly sensible. -- Does Safari no offer such an option? – juanmirocks Nov 21 '16 at 19:42
  • I'm not sure how Safari behaves, but it sounds like a similar feature could be breaking your code. If you've ever seen a page repeatedly open dialogs to prevent the user from leaving, most browsers have some protection against that. If Chrome and FF do it well, there's a good chance that Safari does something shady and unfortunate. – ssube Nov 21 '16 at 19:45
  • On the other hand, safari will not stop asking me until I've given my first "OK" answer. Then it will never ask again... It doesn't even give me an option of preventing the dialogs when I keep clicking on "Cancel". – juanmirocks Nov 21 '16 at 20:00
  • It's several years later, and Safari 13 still has this weird quirk. – Ben in CA Mar 20 '20 at 18:18
  • So much for caniuse.com! The site states that Safari fully supports beforeunload event https://caniuse.com/?search=beforeunload%20event – smohadjer Jan 14 '21 at 11:41
  • 1
    2021 and this problem is still not solved. Well done Apple! – smohadjer Jan 14 '21 at 12:23
  • 1
    Anno 2022 and still an issue. – sn3p Sep 26 '22 at 16:16

2 Answers2

5

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

Community
  • 1
  • 1
Kaloyan Kosev
  • 12,483
  • 8
  • 59
  • 90
  • Thank you very much @Kaloyan. I will try the solution later today and report. And likely give you the bounty :-) – juanmirocks Dec 01 '16 at 09:33
  • 1
    Thanks @juanmirocks , I appreciate that :-) – Kaloyan Kosev Dec 01 '16 at 16:17
  • 1
    Thank you very much again. Your answer was very clear and very informative (bounty for you!). **Very unfortunately**, after trying the proposed solution, I still experience the same previous behavior with Safari. I tested both on `http` and `https`. More info, the event `pageshow` has always the property `persisted` set to `false`. As a matter of fact, I do not use the back/forward buttons at all. Rather, I navigate between pages with normal page links. So I don't know... --- Really appreciate if you can come up with other hypotheses :) – juanmirocks Dec 01 '16 at 19:21
  • Ah, @juanmirocks , I'm sorry to hear that. That was my best guess. Based on the info you've shared (and based on my experience) I don't see any other reason or anything else that could behave wired. So the issue might be hidden somewhere in the logic of your React app. Could you please try to isolate a problematic case (component) and share a jsfiddle code example? PS: I really appreciate that you released the bounty to me. Thank you! – Kaloyan Kosev Dec 01 '16 at 22:17
  • Thanks again. Considering that it works just fine on Chrome or Firefox, I doubt the problem lies in my React code logic. Though maybe ;P . Well, as of now this problem is not critical for my application so I'm going to stop my searches now. Definitely if I find a solution in the future I will let you know if you're curious. – juanmirocks Dec 02 '16 at 10:03
  • Sure, @juanmirocks, that would be great ;-) – Kaloyan Kosev Dec 02 '16 at 11:31
  • To solve the issue with page visibility just before the onpageshow istrigger you can use onbeforeunload event. During this event you can change body's opacity to 0. So yes, but page will be briefly visible, but with 0 opacity. (window.onbeforeunload = () => { document.body.opacity = 0; } ). This works with Safari 11. – Kamen Stoykov Sep 16 '18 at 14:50
1

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() 
    }
};
SergeyLebedev
  • 3,673
  • 15
  • 29
  • 1
    Thank you very much @Sergey. Your answer and extra offered solution likely solves my problem! I report later. – juanmirocks Dec 01 '16 at 09:34
  • Thank you very much again. Unfortunately this did not solve my problem. See: http://stackoverflow.com/questions/40727989/is-onbeforeunload-cached-on-safari-macos#comment69049563_40896361 -- Do you have any other ideas? Thanks :) – juanmirocks Dec 01 '16 at 19:22