diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 3b4da6bc52a..2a4eda272c8 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -5009,6 +5009,10 @@ nsBrowserAccess.prototype = { isTabContentWindow: function (aWindow) { return gBrowser.browsers.some(browser => browser.contentWindow == aWindow); }, + + canClose() { + return CanCloseWindow(); + }, } function getTogglableToolbars() { @@ -6565,6 +6569,26 @@ var IndexedDBPromptHelper = { } }; +function CanCloseWindow() +{ + // Avoid redundant calls to canClose from showing multiple + // PermitUnload dialogs. + if (window.skipNextCanClose) { + return true; + } + + for (let browser of gBrowser.browsers) { + let {permitUnload, timedOut} = browser.permitUnload(); + if (timedOut) { + return true; + } + if (!permitUnload) { + return false; + } + } + return true; +} + function WindowIsClosing() { if (TabView.isVisible()) { @@ -6575,27 +6599,19 @@ function WindowIsClosing() if (!closeWindow(false, warnAboutClosingWindow)) return false; - // Bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process - if (gMultiProcessBrowser) + // In theory we should exit here and the Window's internal Close + // method should trigger canClose on nsBrowserAccess. However, by + // that point it's too late to be able to show a prompt for + // PermitUnload. So we do it here, when we still can. + if (CanCloseWindow()) { + // This flag ensures that the later canClose call does nothing. + // It's only needed to make tests pass, since they detect the + // prompt even when it's not actually shown. + window.skipNextCanClose = true; return true; - - for (let browser of gBrowser.browsers) { - let ds = browser.docShell; - // Passing true to permitUnload indicates we plan on closing the window. - // This means that once unload is permitted, all further calls to - // permitUnload will be ignored. This avoids getting multiple prompts - // to unload the page. - if (ds.contentViewer && !ds.contentViewer.permitUnload(true)) { - // ... however, if the user aborts closing, we need to undo that, - // to ensure they get prompted again when we next try to close the window. - // We do this on the window's toplevel docshell instead of on the tab, so - // that all tabs we iterated before will get this reset. - window.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow(); - return false; - } } - return true; + return false; } /** diff --git a/browser/base/content/chatWindow.xul b/browser/base/content/chatWindow.xul index aab0b7005ef..61b87e2c910 100644 --- a/browser/base/content/chatWindow.xul +++ b/browser/base/content/chatWindow.xul @@ -131,6 +131,11 @@ chatBrowserAccess.prototype = { isTabContentWindow: function (aWindow) { return this.contentWindow == aWindow; }, + + canClose() { + let {BrowserUtils} = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}); + return BrowserUtils.canCloseWindow(window); + }, }; diff --git a/browser/base/content/sanitize.js b/browser/base/content/sanitize.js index 6110af47e92..f97ab580f73 100644 --- a/browser/base/content/sanitize.js +++ b/browser/base/content/sanitize.js @@ -1,4 +1,4 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 4 -*- +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -567,28 +567,17 @@ Sanitizer.prototype = { openWindows: { privateStateForNewWindow: "non-private", _canCloseWindow: function(aWindow) { - // Bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process - if (!aWindow.gMultiProcessBrowser) { - // Cargo-culted out of browser.js' WindowIsClosing because we don't care - // about TabView or the regular 'warn me before closing windows with N tabs' - // stuff here, and more importantly, we want to set aCallerClosesWindow to true - // when calling into permitUnload: - for (let browser of aWindow.gBrowser.browsers) { - let ds = browser.docShell; - // 'true' here means we will be closing the window soon, so please don't dispatch - // another onbeforeunload event when we do so. If unload is *not* permitted somewhere, - // we will reset the flag that this triggers everywhere so that we don't interfere - // with the browser after all: - if (ds.contentViewer && !ds.contentViewer.permitUnload(true)) { - return false; - } - } + if (aWindow.CanCloseWindow()) { + // We already showed PermitUnload for the window, so let's + // make sure we don't do it again when we actually close the + // window. + aWindow.skipNextCanClose = true; + return true; } - return true; }, _resetAllWindowClosures: function(aWindowList) { for (let win of aWindowList) { - win.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow(); + win.skipNextCanClose = false; } }, clear: Task.async(function*() { diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index bca8db37c9c..ad93bcb4e88 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -1156,25 +1156,29 @@ this._tabAttrModified(this.mCurrentTab, ["selected"]); if (oldBrowser != newBrowser && - oldBrowser.docShell && - oldBrowser.docShell.contentViewer.inPermitUnload) { - // Since the user is switching away from a tab that has - // a beforeunload prompt active, we remove the prompt. - // This prevents confusing user flows like the following: - // 1. User attempts to close Firefox - // 2. User switches tabs (ingoring a beforeunload prompt) - // 3. User returns to tab, presses "Leave page" - let promptBox = this.getTabModalPromptBox(oldBrowser); - let prompts = promptBox.listPrompts(); - // There might not be any prompts here if the tab was closed - // while in an onbeforeunload prompt, which will have - // destroyed aforementioned prompt already, so check there's - // something to remove, first: - if (prompts.length) { - // NB: This code assumes that the beforeunload prompt - // is the top-most prompt on the tab. - prompts[prompts.length - 1].abortPrompt(); - } + oldBrowser.getInPermitUnload) { + oldBrowser.getInPermitUnload(inPermitUnload => { + if (!inPermitUnload) { + return; + } + // Since the user is switching away from a tab that has + // a beforeunload prompt active, we remove the prompt. + // This prevents confusing user flows like the following: + // 1. User attempts to close Firefox + // 2. User switches tabs (ingoring a beforeunload prompt) + // 3. User returns to tab, presses "Leave page" + let promptBox = this.getTabModalPromptBox(oldBrowser); + let prompts = promptBox.listPrompts(); + // There might not be any prompts here if the tab was closed + // while in an onbeforeunload prompt, which will have + // destroyed aforementioned prompt already, so check there's + // something to remove, first: + if (prompts.length) { + // NB: This code assumes that the beforeunload prompt + // is the top-most prompt on the tab. + prompts[prompts.length - 1].abortPrompt(); + } + }); } oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused); @@ -2103,6 +2107,7 @@ if (aParams) { var animate = aParams.animate; var byMouse = aParams.byMouse; + var skipPermitUnload = aParams.skipPermitUnload; } // Handle requests for synchronously removing an already @@ -2115,7 +2120,7 @@ var isLastTab = (this.tabs.length - this._removingTabs.length == 1); - if (!this._beginRemoveTab(aTab, false, null, true)) + if (!this._beginRemoveTab(aTab, false, null, true, skipPermitUnload)) return; if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse) @@ -2163,6 +2168,7 @@ + diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini index b85f09e86d7..1e75dfe31c6 100644 --- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -149,7 +149,6 @@ skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir [browser_backButtonFitts.js] skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X [browser_beforeunload_duplicate_dialogs.js] -skip-if = e10s # bug 967873 means permitUnload doesn't work in e10s mode [browser_blob-channelname.js] [browser_bookmark_titles.js] skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341) diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index aec35dd88e9..66688848f6e 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -7805,7 +7805,7 @@ nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, mTiming->NotifyBeforeUnload(); bool okToUnload; - rv = mContentViewer->PermitUnload(false, &okToUnload); + rv = mContentViewer->PermitUnload(&okToUnload); if (NS_SUCCEEDED(rv) && !okToUnload) { // The user chose not to unload the page, interrupt the load. @@ -10143,7 +10143,7 @@ nsDocShell::InternalLoad(nsIURI* aURI, // protocol handler deals with this for javascript: URLs. if (!isJavaScript && aFileName.IsVoid() && mContentViewer) { bool okToUnload; - rv = mContentViewer->PermitUnload(false, &okToUnload); + rv = mContentViewer->PermitUnload(&okToUnload); if (NS_SUCCEEDED(rv) && !okToUnload) { // The user chose not to unload the page, interrupt the diff --git a/docshell/base/nsIContentViewer.idl b/docshell/base/nsIContentViewer.idl index 6d53ecd59db..6484b0a60d6 100644 --- a/docshell/base/nsIContentViewer.idl +++ b/docshell/base/nsIContentViewer.idl @@ -31,7 +31,7 @@ class nsDOMNavigationTiming; [ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming); [ref] native nsIContentViewerTArray(nsTArray >); -[scriptable, builtinclass, uuid(fbd04c99-e149-473f-8a68-44f53d82f98b)] +[scriptable, builtinclass, uuid(91b6c1f3-fc5f-43a9-88f4-9286bd19387f)] interface nsIContentViewer : nsISupports { [noscript] void init(in nsIWidgetPtr aParentWidget, @@ -45,12 +45,8 @@ interface nsIContentViewer : nsISupports /** * Checks if the document wants to prevent unloading by firing beforeunload on * the document, and if it does, prompts the user. The result is returned. - * - * @param aCallerClosesWindow indicates that the current caller will close the - * window. If the method returns true, all subsequent calls will be - * ignored. */ - boolean permitUnload([optional] in boolean aCallerClosesWindow); + boolean permitUnload(); /** * Exposes whether we're blocked in a call to permitUnload. @@ -62,8 +58,7 @@ interface nsIContentViewer : nsISupports * track of whether the user has responded to a prompt. * Used internally by the scriptable version to ensure we only prompt once. */ - [noscript,nostdcall] boolean permitUnloadInternal(in boolean aCallerClosesWindow, - inout boolean aShouldPrompt); + [noscript,nostdcall] boolean permitUnloadInternal(inout boolean aShouldPrompt); /** * Exposes whether we're in the process of firing the beforeunload event. @@ -71,16 +66,6 @@ interface nsIContentViewer : nsISupports */ readonly attribute boolean beforeUnloadFiring; - /** - * Works in tandem with permitUnload, if the caller decides not to close the - * window it indicated it will, it is the caller's responsibility to reset - * that with this method. - * - * @Note this method is only meant to be called on documents for which the - * caller has indicated that it will close the window. If that is not the case - * the behavior of this method is undefined. - */ - void resetCloseWindow(); void pageHide(in boolean isUnload); /** diff --git a/docshell/test/browser/browser.ini b/docshell/test/browser/browser.ini index b6215120f4b..5d94575cd8c 100644 --- a/docshell/test/browser/browser.ini +++ b/docshell/test/browser/browser.ini @@ -83,7 +83,6 @@ skip-if = e10s # Bug 1220927 - Test tries to do addSHistoryListener on content. [browser_loadURI.js] [browser_multiple_pushState.js] [browser_onbeforeunload_navigation.js] -skip-if = e10s [browser_search_notification.js] [browser_timelineMarkers-01.js] [browser_timelineMarkers-02.js] diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 66005461bcf..23b89048cd6 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -7998,6 +7998,17 @@ nsGlobalWindow::CanClose() { MOZ_ASSERT(IsOuterWindow()); + if (mIsChrome) { + nsCOMPtr bwin; + nsIDOMChromeWindow* chromeWin = static_cast(this); + chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin)); + + bool canClose = true; + if (bwin && NS_SUCCEEDED(bwin->CanClose(&canClose))) { + return canClose; + } + } + if (!mDocShell) { return true; } @@ -8011,7 +8022,7 @@ nsGlobalWindow::CanClose() mDocShell->GetContentViewer(getter_AddRefs(cv)); if (cv) { bool canClose; - nsresult rv = cv->PermitUnload(false, &canClose); + nsresult rv = cv->PermitUnload(&canClose); if (NS_SUCCEEDED(rv) && !canClose) return false; diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp index 6b936460170..f8c82189e8c 100644 --- a/dom/html/nsHTMLDocument.cpp +++ b/dom/html/nsHTMLDocument.cpp @@ -1522,7 +1522,7 @@ nsHTMLDocument::Open(JSContext* cx, if (cv) { bool okToUnload; - if (NS_SUCCEEDED(cv->PermitUnload(false, &okToUnload)) && !okToUnload) { + if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) { // We don't want to unload, so stop here, but don't throw an // exception. nsCOMPtr ret = this; diff --git a/dom/interfaces/base/nsIBrowserDOMWindow.idl b/dom/interfaces/base/nsIBrowserDOMWindow.idl index d0203f3ae9c..d94ff6e0ae0 100644 --- a/dom/interfaces/base/nsIBrowserDOMWindow.idl +++ b/dom/interfaces/base/nsIBrowserDOMWindow.idl @@ -17,7 +17,7 @@ interface nsIOpenURIInFrameParams : nsISupports attribute boolean isPrivate; }; -[scriptable, uuid(99f5a347-722c-4337-bd38-f14ec94801b3)] +[scriptable, uuid(31da1ce2-aec4-4c26-ac66-d622935c3bf4)] /** * The C++ source has access to the browser script source through @@ -99,6 +99,14 @@ interface nsIBrowserDOMWindow : nsISupports * @return whether the window is the main content window for any * currently open tab in this toplevel browser window. */ - boolean isTabContentWindow(in nsIDOMWindow aWindow); + boolean isTabContentWindow(in nsIDOMWindow aWindow); + + /** + * This function is responsible for calling + * nsIContentViewer::PermitUnload on each frame in the window. It + * returns true if closing the window is allowed. See canClose() in + * BrowserUtils.jsm for a simple implementation of this method. + */ + boolean canClose(); }; diff --git a/dom/jsurl/nsJSProtocolHandler.cpp b/dom/jsurl/nsJSProtocolHandler.cpp index 02eb109c60e..991fbe214ba 100644 --- a/dom/jsurl/nsJSProtocolHandler.cpp +++ b/dom/jsurl/nsJSProtocolHandler.cpp @@ -749,7 +749,7 @@ nsJSChannel::EvaluateScript() if (cv) { bool okToUnload; - if (NS_SUCCEEDED(cv->PermitUnload(false, &okToUnload)) && + if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) { // The user didn't want to unload the current // page, translate this into an undefined diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 30d4d179bda..a2c0e6f22d3 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -404,7 +404,6 @@ protected: nsCString mForceCharacterSet; bool mIsPageMode; - bool mCallerIsClosingWindow; bool mInitializedForPrintPreview; bool mHidden; }; @@ -455,7 +454,6 @@ void nsDocumentViewer::PrepareToStartLoad() mLoaded = false; mAttachedToParent = false; mDeferredWindowClose = false; - mCallerIsClosingWindow = false; #ifdef NS_PRINTING mPrintIsPending = false; @@ -1051,18 +1049,15 @@ nsDocumentViewer::LoadComplete(nsresult aStatus) } NS_IMETHODIMP -nsDocumentViewer::PermitUnload(bool aCallerClosesWindow, - bool *aPermitUnload) +nsDocumentViewer::PermitUnload(bool *aPermitUnload) { bool shouldPrompt = true; - return PermitUnloadInternal(aCallerClosesWindow, &shouldPrompt, - aPermitUnload); + return PermitUnloadInternal(&shouldPrompt, aPermitUnload); } nsresult -nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow, - bool *aShouldPrompt, +nsDocumentViewer::PermitUnloadInternal(bool *aShouldPrompt, bool *aPermitUnload) { AutoDontWarnAboutSyncXHR disableSyncXHRWarning; @@ -1071,7 +1066,6 @@ nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow, if (!mDocument || mInPermitUnload - || mCallerIsClosingWindow || mInPermitUnloadPrompt) { return NS_OK; } @@ -1247,16 +1241,12 @@ nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow, docShell->GetContentViewer(getter_AddRefs(cv)); if (cv) { - cv->PermitUnloadInternal(aCallerClosesWindow, aShouldPrompt, - aPermitUnload); + cv->PermitUnloadInternal(aShouldPrompt, aPermitUnload); } } } } - if (aCallerClosesWindow && *aPermitUnload) - mCallerIsClosingWindow = true; - return NS_OK; } @@ -1274,35 +1264,6 @@ nsDocumentViewer::GetInPermitUnload(bool* aInEvent) return NS_OK; } -NS_IMETHODIMP -nsDocumentViewer::ResetCloseWindow() -{ - mCallerIsClosingWindow = false; - - nsCOMPtr docShell(mContainer); - if (docShell) { - int32_t childCount; - docShell->GetChildCount(&childCount); - - for (int32_t i = 0; i < childCount; ++i) { - nsCOMPtr item; - docShell->GetChildAt(i, getter_AddRefs(item)); - - nsCOMPtr docShell(do_QueryInterface(item)); - - if (docShell) { - nsCOMPtr cv; - docShell->GetContentViewer(getter_AddRefs(cv)); - - if (cv) { - cv->ResetCloseWindow(); - } - } - } - } - return NS_OK; -} - NS_IMETHODIMP nsDocumentViewer::PageHide(bool aIsUnload) { diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 2c67be1e994..50cca76f2b7 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -61,6 +61,9 @@ if (AppConstants.MOZ_SAFE_BROWSING) { "resource://gre/modules/SafeBrowsing.jsm"); } +XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", + "resource://gre/modules/BrowserUtils.jsm"); + XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); @@ -3491,6 +3494,10 @@ nsBrowserAccess.prototype = { isTabContentWindow: function(aWindow) { return BrowserApp.getBrowserForWindow(aWindow) != null; }, + + canClose() { + return BrowserUtils.canCloseWindow(window); + }, }; diff --git a/services/fxaccounts/FxAccountsOAuthClient.jsm b/services/fxaccounts/FxAccountsOAuthClient.jsm index 097d6eddd0a..c59f1a86913 100644 --- a/services/fxaccounts/FxAccountsOAuthClient.jsm +++ b/services/fxaccounts/FxAccountsOAuthClient.jsm @@ -195,6 +195,25 @@ this.FxAccountsOAuthClient.prototype = { }; } + // if the message asked to close the tab + if (data.closeWindow && target) { + // for e10s reasons the best way is to use the TabBrowser to close the tab. + let tabbrowser = target.getTabBrowser(); + + if (tabbrowser) { + let tab = tabbrowser.getTabForBrowser(target); + + if (tab) { + tabbrowser.removeTab(tab); + log.debug("OAuth flow closed the tab."); + } else { + log.debug("OAuth flow failed to close the tab. Tab not found in TabBrowser."); + } + } else { + log.debug("OAuth flow failed to close the tab. TabBrowser not found."); + } + } + if (err) { log.debug(err.message); if (this.onError) { @@ -214,25 +233,6 @@ this.FxAccountsOAuthClient.prototype = { // onComplete will be called for this client only once // calling onComplete again will result in a failure of the OAuth flow this.tearDown(); - - // if the message asked to close the tab - if (data.closeWindow && target) { - // for e10s reasons the best way is to use the TabBrowser to close the tab. - let tabbrowser = target.getTabBrowser(); - - if (tabbrowser) { - let tab = tabbrowser.getTabForBrowser(target); - - if (tab) { - tabbrowser.removeTab(tab); - log.debug("OAuth flow closed the tab."); - } else { - log.debug("OAuth flow failed to close the tab. Tab not found in TabBrowser."); - } - } else { - log.debug("OAuth flow failed to close the tab. TabBrowser not found."); - } - } break; } } diff --git a/toolkit/components/startup/tests/browser/browser.ini b/toolkit/components/startup/tests/browser/browser.ini index 2204935bc90..0c02f73b688 100644 --- a/toolkit/components/startup/tests/browser/browser.ini +++ b/toolkit/components/startup/tests/browser/browser.ini @@ -4,7 +4,5 @@ support-files = beforeunload.html [browser_bug511456.js] -skip-if = e10s # Bug ?????? - test touches content (uses a WindowWatcher in the parent process to try and observe content created alerts etc) [browser_bug537449.js] -skip-if = e10s # Bug ?????? - test touches content (uses a WindowWatcher in the parent process to try and observe content created alerts etc) [browser_crash_detection.js] diff --git a/toolkit/content/browser-child.js b/toolkit/content/browser-child.js index 7d9c191056b..18c3930370e 100644 --- a/toolkit/content/browser-child.js +++ b/toolkit/content/browser-child.js @@ -577,6 +577,22 @@ var AutoCompletePopup = { } } +addMessageListener("InPermitUnload", msg => { + let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload; + sendAsyncMessage("InPermitUnload", {id: msg.data.id, inPermitUnload}); +}); + +addMessageListener("PermitUnload", msg => { + sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "start"}); + + let permitUnload = true; + if (docShell && docShell.contentViewer) { + permitUnload = docShell.contentViewer.permitUnload(); + } + + sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "end", permitUnload}); +}); + // We may not get any responses to Browser:Init if the browser element // is torn down too quickly. var outerWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor) diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml index 002876a1360..6d64b39acf1 100644 --- a/toolkit/content/widgets/browser.xml +++ b/toolkit/content/widgets/browser.xml @@ -1082,8 +1082,8 @@ window.addEventListener("keypress", this, true); window.addEventListener("keyup", this, true); ]]> - - + + @@ -1236,6 +1236,30 @@ + + + + + + + + + + + + + true diff --git a/toolkit/content/widgets/remote-browser.xml b/toolkit/content/widgets/remote-browser.xml index e146bf3d521..70f913e103e 100644 --- a/toolkit/content/widgets/remote-browser.xml +++ b/toolkit/content/widgets/remote-browser.xml @@ -244,6 +244,89 @@ false + 0 + + + + + + + + + + + { + if (msg.data.id != id) { + return; + } + if (msg.data.kind == "start") { + responded = true; + return; + } + done(msg.data.permitUnload); + }; + + let observer = subject => { + if (subject == mm) { + done(true); + } + }; + + function done(result) { + finished = true; + permitUnload = result; + mm.removeMessageListener("PermitUnload", msgListener); + Services.obs.removeObserver(observer, "message-manager-close"); + } + + mm.sendAsyncMessage("PermitUnload", {id}); + mm.addMessageListener("PermitUnload", msgListener); + Services.obs.addObserver(observer, "message-manager-close", false); + + let timedOut = false; + function timeout() { + if (!responded) { + timedOut = true; + } + + // Dispatch something to ensure that the main thread wakes up. + Services.tm.mainThread.dispatch(function() {}, Ci.nsIThread.DISPATCH_NORMAL); + } + + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT); + + while (!finished && !timedOut) { + Services.tm.currentThread.processNextEvent(true); + } + + return {permitUnload, timedOut}; + ]]> + + +