Closed Bug 1191973 Opened 8 years ago Closed 8 years ago

Firefox window.onbeforeunload after switching tabs causes strange bug


(Firefox :: General, defect)

39 Branch
Not set



Firefox 43
Tracking Status
firefox39 --- affected
firefox40 --- affected
firefox41 --- affected
firefox42 --- affected
firefox43 --- fixed
firefox-esr31 --- affected
firefox-esr38 --- affected


(Reporter: joe, Unassigned)



(Keywords: regression, reproducible, testcase)


(2 files)

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:39.0) Gecko/20100101 Firefox/39.0
Build ID: 20150630154324

Steps to reproduce:

I am using Firefox 39.0, and this happens on both Windows and Mac. When using a page with window.onbeforeunload to ask a user to confirm leaving before saving changes if the user does not click either "Stay on Page" or "Leave Page" buttons and instead switches to another tab and then comes back to the original tab then any setTimeout() functions appear to stop working

Try this code in Firefox 39.0:

    <!DOCTYPE html><html><head><title>Firefox Bug??</title></head>
    <a href="#click" onclick="clicker()">Click Me</a>
    window.onbeforeunload = function() {return "Are you sure you wish to reload?";}
    function clicker(){
    	alert('This is an alert that appears straight away!');
    	setTimeout(function(){alert('This is an alert that appears after 1 second!');},1000);

By default clicking the link will display two alerts. The first appears after 1 second.

To see the bug do these exact steps:

 1. Run the page in Firefox 39.0
 2. Hit the refresh button and a confirmation box will display asking you to leave
 3. DO NOT press either of the buttons "Stay on Page" or "Leave Page" but instead click on another tab
 4. Come back to the original tab and notice how the confirmation box has gone.
 5. Try clicking on the link and notice the first alert() fires but the second alert() does not - the setTimeout() function no longer fires!

Actual results:

After the conformation box vanished the setTimeout() function no longer works... this means any jQuery animations etc stop working as well!

Expected results:

Well I guess first off the confirmation box should still be there... but even if it's not this should not stop setTimeout() from working.

Here is a page demonstrating it with jQuery:

Regreesed by: Bug 616853
Blocks: 616853
Component: Untriaged → General
Ever confirmed: true
Flags: needinfo?(ttaubert)
Attached file reporter's testcase
The following errors are shown in Browser console:

NotFoundError: Node was not found tabbrowser.xml:495:0

NS_ERROR_NOT_AVAILABLE: prompt aborted by user nsPrompter.js:427:0
Curiously, this seems to work as expected with E10S. At least, switching tabs no longer causes the prompt to disappear. (And note that those errors show up upon switching tabs.)

I think that's the actual bug - I suspect we're stuck in the mode where we're suppressing various things because we're in onbeforeunload, but something else has caused the prompt to be torn down.
Hmm, from tabbrowser.xml::updateCurrentBrowser()...

1188               if (oldBrowser != newBrowser &&
1189                   oldBrowser.docShell &&
1190                   oldBrowser.docShell.contentViewer.inPermitUnload) {
1191                 // Since the user is switching away from a tab that has
1192                 // a beforeunload prompt active, we remove the prompt.
1193                 // This prevents confusing user flows like the following:
1194                 //   1. User attempts to close Firefox
1195                 //   2. User switches tabs (ingoring a beforeunload prompt)
1196                 //   3. User returns to tab, presses "Leave page"
1197                 let promptBox = this.getTabModalPromptBox(oldBrowser);
1198                 let prompts = promptBox.listPrompts();
1199                 // There might not be any prompts here if the tab was closed
1200                 // while in an onbeforeunload prompt, which will have
1201                 // destroyed aforementioned prompt already, so check there's
1202                 // something to remove, first:
1203                 if (prompts.length) {
1204                   // NB: This code assumes that the beforeunload prompt
1205                   //     is the top-most prompt on the tab.
1206                   promptBox.removePrompt(prompts[prompts.length - 1]);
1207                 }
1208               }

So we remove the prompt, but that only removes the tabprompt overlay. Seems like there should actually be a call to prompts[prompts.length - 1].abortPrompt(), which would tear down the actual prompt call. [Assuming abortPrompt() is effectively triggering the "Stay on Page", ISTR there are cases where it doesn't know what the right button action is for the aborting a prompt. But in any case, I think that's what needs to happen here.]

This also means that my comment 4 seems wrong (we do want to remove the prompt), and there's actually an E10S bug with the prompt not being removed with switching tabs, I think. :(
e10s doesn't support onbeforeunload yet (like at all), which explains the behaviour there.
Flags: needinfo?(gijskruitbosch+bugs)
Flags: needinfo?(ttaubert)
Bug 1191973 - abortPrompt when removing it because the user switched tabs, r?dolske
Attachment #8648049 - Flags: review?(dolske)
nsDocumentViewer turns the exception into NS_OK, and returns false, but I don't know why the exception still shows up in the console... maybe because it went uncaught in JS land? Anyway, does this look right?
Flags: needinfo?(gijskruitbosch+bugs)
Comment on attachment 8648049 [details]
MozReview Request: Bug 1191973 - abortPrompt when removing it because the user switched tabs, r?dolske

Ship It!
Attachment #8648049 - Flags: review?(dolske) → review+
Closed: 8 years ago
Resolution: --- → FIXED
Target Milestone: --- → Firefox 43
You need to log in before you can comment on or make changes to this bug.