--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5004,16 +5004,20 @@ nsBrowserAccess.prototype = {
return browser.QueryInterface(Ci.nsIFrameLoaderOwner);
return null;
},
isTabContentWindow: function (aWindow) {
return gBrowser.browsers.some(browser => browser.contentWindow == aWindow);
},
+
+ canClose() {
+ return CanCloseWindow();
+ },
}
function getTogglableToolbars() {
let toolbarNodes = Array.slice(gNavToolbox.childNodes);
toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars);
toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname"));
return toolbarNodes;
}
@@ -6560,47 +6564,59 @@ var IndexedDBPromptHelper = {
// Set the timeoutId after the popup has been created, and use the long
// timeout value. If the user doesn't notice the popup after this amount of
// time then it is most likely not visible and we want to alert the page.
timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration);
}
};
+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()) {
TabView.hide();
return false;
}
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;
}
/**
* Checks if this is the last full *browser* window around. If it is, this will
* be communicated like quitting. Otherwise, we warn about closing multiple tabs.
* @returns true if closing can proceed, false if it got cancelled.
*/
function warnAboutClosingWindow() {
--- a/browser/base/content/chatWindow.xul
+++ b/browser/base/content/chatWindow.xul
@@ -126,16 +126,21 @@ chatBrowserAccess.prototype = {
openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aContext) {
let browser = this._openURIInNewTab(aURI, aWhere);
return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
},
isTabContentWindow: function (aWindow) {
return this.contentWindow == aWindow;
},
+
+ canClose() {
+ let {BrowserUtils} = Cu.import("resource://gre/modules/BrowserUtils.jsm", {});
+ return BrowserUtils.canCloseWindow(window);
+ },
};
</script>
#include browser-sets.inc
#ifdef XP_MACOSX
#include browser-menubar.inc
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -1,9 +1,9 @@
-// -*- 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/. */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
@@ -562,38 +562,27 @@ Sanitizer.prototype = {
get canClear()
{
return true;
}
},
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*() {
// NB: this closes all *browser* windows, not other windows like the library, about window,
// browser console, etc.
// Keep track of the time in case we get stuck in la-la-land because of onbeforeunload
// dialogs
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1151,35 +1151,39 @@
}
});
this.mCurrentTab.dispatchEvent(event);
this._tabAttrModified(oldTab, ["selected"]);
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);
if (this.isFindBarInitialized(oldTab)) {
let findBar = this.getFindBar(oldTab);
oldTab._findBarFocused = (!findBar.hidden &&
findBar._findField.getAttribute("focused") == "true");
}
@@ -2098,29 +2102,30 @@
<method name="removeTab">
<parameter name="aTab"/>
<parameter name="aParams"/>
<body>
<![CDATA[
if (aParams) {
var animate = aParams.animate;
var byMouse = aParams.byMouse;
+ var skipPermitUnload = aParams.skipPermitUnload;
}
// Handle requests for synchronously removing an already
// asynchronously closing tab.
if (!animate &&
aTab.closing) {
this._endRemoveTab(aTab);
return;
}
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)
this.tabContainer._lockTabSizing(aTab);
else
this.tabContainer._unlockTabSizing();
if (!animate /* the caller didn't opt in */ ||
@@ -2158,16 +2163,17 @@
false
</field>
<method name="_beginRemoveTab">
<parameter name="aTab"/>
<parameter name="aTabWillBeMoved"/>
<parameter name="aCloseWindowWithLastTab"/>
<parameter name="aCloseWindowFastpath"/>
+ <parameter name="aSkipPermitUnload"/>
<body>
<![CDATA[
if (aTab.closing ||
this._windowIsClosing)
return false;
var browser = this.getBrowserForTab(aTab);
@@ -2188,33 +2194,30 @@
// cancels the operation. We are finished here in both cases.
this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
return null;
}
newTab = true;
}
- if (!aTab._pendingPermitUnload && !aTabWillBeMoved) {
- let ds = browser.docShell;
- if (ds && ds.contentViewer) {
- // We need to block while calling permitUnload() because it
- // processes the event queue and may lead to another removeTab()
- // call before permitUnload() returns.
- aTab._pendingPermitUnload = true;
- let permitUnload = ds.contentViewer.permitUnload();
- delete aTab._pendingPermitUnload;
- // If we were closed during onbeforeunload, we return false now
- // so we don't (try to) close the same tab again. Of course, we
- // also stop if the unload was cancelled by the user:
- if (aTab.closing || !permitUnload) {
- // NB: deliberately keep the _closedDuringPermitUnload set to
- // true so we keep exiting early in case of multiple calls.
- return false;
- }
+ if (!aTab._pendingPermitUnload && !aTabWillBeMoved && !aSkipPermitUnload) {
+ // We need to block while calling permitUnload() because it
+ // processes the event queue and may lead to another removeTab()
+ // call before permitUnload() returns.
+ aTab._pendingPermitUnload = true;
+ let {permitUnload} = browser.permitUnload();
+ delete aTab._pendingPermitUnload;
+ // If we were closed during onbeforeunload, we return false now
+ // so we don't (try to) close the same tab again. Of course, we
+ // also stop if the unload was cancelled by the user:
+ if (aTab.closing || !permitUnload) {
+ // NB: deliberately keep the _closedDuringPermitUnload set to
+ // true so we keep exiting early in case of multiple calls.
+ return false;
}
}
aTab.closing = true;
this._removingTabs.push(aTab);
this._visibleTabs = null; // invalidate cache
// Invalidate hovered tab state tracking for this closing tab.
@@ -4022,23 +4025,29 @@
return;
let titleChanged = this.setTabTitle(tab);
if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
tab.setAttribute("titlechanged", "true");
break;
}
case "DOMWindowClose": {
if (this.tabs.length == 1) {
+ // We already did PermitUnload in the content process
+ // for this tab (the only one in the window). So we don't
+ // need to do it again for any tabs.
+ window.skipNextCanClose = true;
window.close();
return;
}
let tab = this.getTabForBrowser(browser);
if (tab) {
- this.removeTab(tab);
+ // Skip running PermitUnload since it already happened in
+ // the content process.
+ this.removeTab(tab, {skipPermitUnload: true});
}
break;
}
case "contextmenu": {
let spellInfo = aMessage.data.spellInfo;
if (spellInfo)
spellInfo.target = aMessage.target.messageManager;
let documentURIObject = makeURI(aMessage.data.docLocation,
@@ -4312,22 +4321,28 @@
</implementation>
<handlers>
<handler event="DOMWindowClose" phase="capturing">
<![CDATA[
if (!event.isTrusted)
return;
- if (this.tabs.length == 1)
+ if (this.tabs.length == 1) {
+ // We already did PermitUnload in nsGlobalWindow::Close
+ // for this tab. There are no other tabs we need to do
+ // PermitUnload for.
+ window.skipNextCanClose = true;
return;
+ }
var tab = this._getTabForContentWindow(event.target);
if (tab) {
- this.removeTab(tab);
+ // Skip running PermitUnload since it already happened.
+ this.removeTab(tab, {skipPermitUnload: true});
event.preventDefault();
}
]]>
</handler>
<handler event="DOMWillOpenModalDialog" phase="capturing">
<![CDATA[
if (!event.isTrusted)
return;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -144,17 +144,16 @@ skip-if = e10s # Bug 1101993 - times out
[browser_autocomplete_enter_race.js]
[browser_autocomplete_no_title.js]
[browser_autocomplete_autoselect.js]
[browser_autocomplete_oldschool_wrap.js]
[browser_autocomplete_tag_star_visibility.js]
[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)
[browser_bug304198.js]
[browser_bug321000.js]
skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
[browser_bug329212.js]
[browser_bug331772_xul_tooltiptext_in_html.js]
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7800,17 +7800,17 @@ nsDocShell::CreateAboutBlankContentViewe
// with about:blank. And also ensure we fire the unload events
// in the current document.
// Unload gets fired first for
// document loaded from the session history.
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.
return NS_ERROR_FAILURE;
}
mSavingOldViewer = aTryToSaveOldPresentation &&
CanSavePresentation(LOAD_NORMAL, nullptr, nullptr);
@@ -10138,17 +10138,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
bool timeBeforeUnload = aFileName.IsVoid();
if (mTiming && timeBeforeUnload) {
mTiming->NotifyBeforeUnload();
}
// Check if the page doesn't want to be unloaded. The javascript:
// 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
// load.
return NS_OK;
}
}
--- a/docshell/base/nsIContentViewer.idl
+++ b/docshell/base/nsIContentViewer.idl
@@ -26,66 +26,51 @@ class nsDOMNavigationTiming;
[ptr] native nsIWidgetPtr(nsIWidget);
[ref] native nsIntRectRef(nsIntRect);
[ptr] native nsIPresShellPtr(nsIPresShell);
[ptr] native nsPresContextPtr(nsPresContext);
[ptr] native nsViewPtr(nsView);
[ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming);
[ref] native nsIContentViewerTArray(nsTArray<nsCOMPtr<nsIContentViewer> >);
-[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,
[const] in nsIntRectRef aBounds);
attribute nsIDocShell container;
[noscript,notxpcom,nostdcall] void loadStart(in nsIDocument aDoc);
void loadComplete(in nsresult aStatus);
/**
* 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.
*/
readonly attribute boolean inPermitUnload;
/**
* As above, but this passes around the aShouldPrompt argument to keep
* 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.
* In this case, the corresponding docshell will not allow navigation.
*/
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);
/**
* All users of a content viewer are responsible for calling both
* close() and destroy(), in that order.
*
* close() should be called when the load of a new page for the next
* content viewer begins, and destroy() should be called when the next
--- a/docshell/test/browser/browser.ini
+++ b/docshell/test/browser/browser.ini
@@ -78,15 +78,14 @@ skip-if = e10s # Bug 1220927 - Test trie
[browser_bug673467.js]
[browser_bug852909.js]
[browser_bug92473.js]
[browser_uriFixupIntegration.js]
[browser_loadDisallowInherit.js]
[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]
[browser_timelineMarkers-03.js]
[browser_timelineMarkers-04.js]
[browser_timelineMarkers-05.js]
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7993,30 +7993,41 @@ public:
};
bool
nsGlobalWindow::CanClose()
{
MOZ_ASSERT(IsOuterWindow());
+ if (mIsChrome) {
+ nsCOMPtr<nsIBrowserDOMWindow> bwin;
+ nsIDOMChromeWindow* chromeWin = static_cast<nsGlobalChromeWindow*>(this);
+ chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
+
+ bool canClose = true;
+ if (bwin && NS_SUCCEEDED(bwin->CanClose(&canClose))) {
+ return canClose;
+ }
+ }
+
if (!mDocShell) {
return true;
}
// Ask the content viewer whether the toplevel window can close.
// If the content viewer returns false, it is responsible for calling
// Close() as soon as it is possible for the window to close.
// This allows us to not close the window while printing is happening.
nsCOMPtr<nsIContentViewer> cv;
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;
rv = cv->RequestWindowClose(&canClose);
if (NS_SUCCEEDED(rv) && !canClose)
return false;
}
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -1517,17 +1517,17 @@ nsHTMLDocument::Open(JSContext* cx,
// Stop current loads targeted at the window this document is in.
if (mScriptGlobalObject) {
nsCOMPtr<nsIContentViewer> cv;
shell->GetContentViewer(getter_AddRefs(cv));
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<nsIDocument> ret = this;
return ret.forget();
}
}
nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell));
--- a/dom/interfaces/base/nsIBrowserDOMWindow.idl
+++ b/dom/interfaces/base/nsIBrowserDOMWindow.idl
@@ -12,17 +12,17 @@ interface nsIFrameLoaderOwner;
[scriptable, uuid(e774db14-79ac-4156-a7a3-aa3fd0a22c10)]
interface nsIOpenURIInFrameParams : nsISupports
{
attribute DOMString referrer;
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
* nsIBrowserDOMWindow. It is intended to be attached to the chrome DOMWindow
* of a toplevel browser window (a XUL window). A DOMWindow that does not
* happen to be a browser chrome window will simply have no access to any such
* interface.
*/
@@ -94,11 +94,19 @@ interface nsIBrowserDOMWindow : nsISuppo
nsIFrameLoaderOwner openURIInFrame(in nsIURI aURI, in nsIOpenURIInFrameParams params,
in short aWhere, in short aContext);
/**
* @param aWindow the window to test.
* @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();
};
--- a/dom/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/jsurl/nsJSProtocolHandler.cpp
@@ -744,17 +744,17 @@ nsJSChannel::EvaluateScript()
NS_QueryNotificationCallbacks(mStreamChannel, docShell);
if (docShell) {
nsCOMPtr<nsIContentViewer> cv;
docShell->GetContentViewer(getter_AddRefs(cv));
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
// return from the javascript: URL...
mStatus = NS_ERROR_DOM_RETVAL_UNDEFINED;
}
}
}
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -399,17 +399,16 @@ protected:
#endif // NS_PRINTING
/* character set member data */
int32_t mHintCharsetSource;
nsCString mHintCharset;
nsCString mForceCharacterSet;
bool mIsPageMode;
- bool mCallerIsClosingWindow;
bool mInitializedForPrintPreview;
bool mHidden;
};
class nsPrintEventDispatcher
{
public:
explicit nsPrintEventDispatcher(nsIDocument* aTop) : mTop(aTop)
@@ -450,17 +449,16 @@ NS_NewContentViewer()
}
void nsDocumentViewer::PrepareToStartLoad()
{
mStopped = false;
mLoaded = false;
mAttachedToParent = false;
mDeferredWindowClose = false;
- mCallerIsClosingWindow = false;
#ifdef NS_PRINTING
mPrintIsPending = false;
mPrintDocIsFullyLoaded = false;
mClosingWhilePrinting = false;
// Make sure we have destroyed it and cleared the data member
if (mPrintEngine) {
@@ -1046,37 +1044,33 @@ nsDocumentViewer::LoadComplete(nsresult
mCachedPrintWebProgressListner = nullptr;
}
#endif
return rv;
}
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;
*aPermitUnload = true;
if (!mDocument
|| mInPermitUnload
- || mCallerIsClosingWindow
|| mInPermitUnloadPrompt) {
return NS_OK;
}
static bool sIsBeforeUnloadDisabled;
static bool sBeforeUnloadRequiresInteraction;
static bool sBeforeUnloadPrefsCached = false;
@@ -1242,26 +1236,22 @@ nsDocumentViewer::PermitUnloadInternal(b
nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(item));
if (docShell) {
nsCOMPtr<nsIContentViewer> cv;
docShell->GetContentViewer(getter_AddRefs(cv));
if (cv) {
- cv->PermitUnloadInternal(aCallerClosesWindow, aShouldPrompt,
- aPermitUnload);
+ cv->PermitUnloadInternal(aShouldPrompt, aPermitUnload);
}
}
}
}
- if (aCallerClosesWindow && *aPermitUnload)
- mCallerIsClosingWindow = true;
-
return NS_OK;
}
NS_IMETHODIMP
nsDocumentViewer::GetBeforeUnloadFiring(bool* aInEvent)
{
*aInEvent = mInPermitUnload;
return NS_OK;
@@ -1270,45 +1260,16 @@ nsDocumentViewer::GetBeforeUnloadFiring(
NS_IMETHODIMP
nsDocumentViewer::GetInPermitUnload(bool* aInEvent)
{
*aInEvent = mInPermitUnloadPrompt;
return NS_OK;
}
NS_IMETHODIMP
-nsDocumentViewer::ResetCloseWindow()
-{
- mCallerIsClosingWindow = false;
-
- nsCOMPtr<nsIDocShell> docShell(mContainer);
- if (docShell) {
- int32_t childCount;
- docShell->GetChildCount(&childCount);
-
- for (int32_t i = 0; i < childCount; ++i) {
- nsCOMPtr<nsIDocShellTreeItem> item;
- docShell->GetChildAt(i, getter_AddRefs(item));
-
- nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(item));
-
- if (docShell) {
- nsCOMPtr<nsIContentViewer> cv;
- docShell->GetContentViewer(getter_AddRefs(cv));
-
- if (cv) {
- cv->ResetCloseWindow();
- }
- }
- }
- }
- return NS_OK;
-}
-
-NS_IMETHODIMP
nsDocumentViewer::PageHide(bool aIsUnload)
{
AutoDontWarnAboutSyncXHR disableSyncXHRWarning;
mHidden = true;
if (!mDocument) {
return NS_ERROR_NULL_POINTER;
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -56,16 +56,19 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
if (AppConstants.MOZ_SAFE_BROWSING) {
XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
"resource://gre/modules/SafeBrowsing.jsm");
}
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
"resource://gre/modules/Sanitizer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
"resource://gre/modules/Prompt.jsm");
@@ -3486,16 +3489,20 @@ nsBrowserAccess.prototype = {
openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aContext) {
let browser = this._getBrowser(aURI, null, aWhere, aContext);
return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
},
isTabContentWindow: function(aWindow) {
return BrowserApp.getBrowserForWindow(aWindow) != null;
},
+
+ canClose() {
+ return BrowserUtils.canCloseWindow(window);
+ },
};
// track the last known screen size so that new tabs
// get created with the right size rather than being 1x1
var gScreenWidth = 1;
var gScreenHeight = 1;
--- a/services/fxaccounts/FxAccountsOAuthClient.jsm
+++ b/services/fxaccounts/FxAccountsOAuthClient.jsm
@@ -190,16 +190,35 @@ this.FxAccountsOAuthClient.prototype = {
err = new Error("OAuth flow failed. Keys were not returned");
} else {
result = {
code: data.code,
state: data.state
};
}
+ // 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) {
this.onError(err);
}
} else {
log.debug("OAuth flow completed.");
if (this.onComplete) {
@@ -209,35 +228,16 @@ this.FxAccountsOAuthClient.prototype = {
this.onComplete(result);
}
}
}
// 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;
}
}
};
this._channelCallback = listener.bind(this);
this._channel = new WebChannel(this._webChannelId, this._webChannelOrigin);
this._channel.listen(this._channelCallback);
--- a/toolkit/components/startup/tests/browser/browser.ini
+++ b/toolkit/components/startup/tests/browser/browser.ini
@@ -1,10 +1,8 @@
[DEFAULT]
support-files =
head.js
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]
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -572,16 +572,32 @@ var AutoCompletePopup = {
selectBy: function(reverse, page) {
this._index = sendSyncMessage("FormAutoComplete:SelectBy", {
reverse: reverse,
page: page
});
}
}
+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)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
var initData = sendSyncMessage("Browser:Init", {outerWindowID: outerWindowID});
if (initData.length) {
docShell.useGlobalHistory = initData[0].useGlobalHistory;
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -1077,18 +1077,18 @@
window.addEventListener("mousemove", this, true);
window.addEventListener("mousedown", this, true);
window.addEventListener("mouseup", this, true);
window.addEventListener("contextmenu", this, true);
window.addEventListener("keydown", this, true);
window.addEventListener("keypress", this, true);
window.addEventListener("keyup", this, true);
]]>
- </body>
- </method>
+ </body>
+ </method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body>
<![CDATA[
if (this._scrolling) {
switch(aEvent.type) {
case "mousemove": {
@@ -1231,16 +1231,40 @@
this._remoteFinder.swapBrowser(this);
if (aOtherBrowser._remoteFinder)
aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser);
}
]]>
</body>
</method>
+ <method name="getInPermitUnload">
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ if (!this.docShell || !this.docShell.contentViewer) {
+ aCallback(false);
+ return;
+ }
+ aCallback(this.docShell.contentViewer.inPermitUnload);
+ ]]>
+ </body>
+ </method>
+
+ <method name="permitUnload">
+ <body>
+ <![CDATA[
+ if (!this.docShell || !this.docShell.contentViewer) {
+ return true;
+ }
+ return {permitUnload: this.docShell.contentViewer.permitUnload(), timedOut: false};
+ ]]>
+ </body>
+ </method>
+
<!-- This will go away if the binding has been removed for some reason. -->
<field name="_alive">true</field>
</implementation>
<handlers>
<handler event="keypress" keycode="VK_F7" group="system">
<![CDATA[
if (event.defaultPrevented || !event.isTrusted)
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -239,16 +239,99 @@
let {frameLoader} = this.QueryInterface(Ci.nsIFrameLoaderOwner);
frameLoader.tabParent.setDocShellIsActiveAndForeground(isActive);
]]></body>
</method>
<field name="mDestroyed">false</field>
+ <field name="_permitUnloadId">0</field>
+
+ <method name="getInPermitUnload">
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ let id = this._permitUnloadId++;
+ let mm = this.messageManager;
+ mm.sendAsyncMessage("InPermitUnload", {id});
+ mm.addMessageListener("InPermitUnload", function listener(msg) {
+ if (msg.data.id != id) {
+ return;
+ }
+ aCallback(msg.data.inPermitUnload);
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="permitUnload">
+ <body>
+ <![CDATA[
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ const kTimeout = 5000;
+
+ let finished = false;
+ let responded = false;
+ let permitUnload;
+ let id = this._permitUnloadId++;
+ let mm = this.messageManager;
+
+ let msgListener = msg => {
+ 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};
+ ]]>
+ </body>
+ </method>
+
<constructor>
<![CDATA[
/*
* Don't try to send messages from this function. The message manager for
* the <browser> element may not be initialized yet.
*/
let jsm = "resource://gre/modules/RemoteWebNavigation.jsm";
--- a/toolkit/modules/BrowserUtils.jsm
+++ b/toolkit/modules/BrowserUtils.jsm
@@ -382,10 +382,26 @@ this.BrowserUtils = {
}
if (url && !url.host) {
url = null;
}
return { text: selectionStr, docSelectionIsCollapsed: collapsed,
linkURL: url ? url.spec : null, linkText: url ? linkText : "" };
- }
+ },
+
+ // Iterates through every docshell in the window and calls PermitUnload.
+ canCloseWindow(window) {
+ let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation);
+ let node = docShell.QueryInterface(Ci.nsIDocShellTreeItem);
+ for (let i = 0; i < node.childCount; ++i) {
+ let docShell = node.getChildAt(i).QueryInterface(Ci.nsIDocShell);
+ let contentViewer = docShell.contentViewer;
+ if (contentViewer && !contentViewer.permitUnload()) {
+ return false;
+ }
+ }
+
+ return true;
+ },
};