Back out bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process

This commit is contained in:
Bill McCloskey 2015-10-07 11:07:44 -07:00
parent 84239860a9
commit 9ca8b13cfa
17 changed files with 167 additions and 303 deletions

View File

@ -4970,10 +4970,6 @@ nsBrowserAccess.prototype = {
isTabContentWindow: function (aWindow) {
return gBrowser.browsers.some(browser => browser.contentWindow == aWindow);
},
canClose() {
return CanCloseWindow();
},
}
function getTogglableToolbars() {
@ -6439,26 +6435,6 @@ 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()) {
@ -6469,19 +6445,27 @@ function WindowIsClosing()
if (!closeWindow(false, warnAboutClosingWindow))
return false;
// 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;
// Bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process
if (gMultiProcessBrowser)
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 false;
return true;
}
/**

View File

@ -131,11 +131,6 @@ chatBrowserAccess.prototype = {
isTabContentWindow: function (aWindow) {
return this.contentWindow == aWindow;
},
canClose() {
let {BrowserUtils} = Cu.import("resource://gre/modules/BrowserUtils.jsm", {});
return BrowserUtils.canCloseWindow(window);
},
};
</script>

View File

@ -1,4 +1,4 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
/* 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,17 +567,28 @@ Sanitizer.prototype = {
openWindows: {
privateStateForNewWindow: "non-private",
_canCloseWindow: function(aWindow) {
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;
// 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;
}
}
}
return true;
},
_resetAllWindowClosures: function(aWindowList) {
for (let win of aWindowList) {
win.skipNextCanClose = false;
win.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow();
}
},
clear: Task.async(function*() {

View File

@ -1190,29 +1190,25 @@
this._tabAttrModified(this.mCurrentTab, ["selected"]);
if (oldBrowser != newBrowser &&
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.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._urlbarFocused = (gURLBar && gURLBar.focused);
@ -2135,7 +2131,6 @@
if (aParams) {
var animate = aParams.animate;
var byMouse = aParams.byMouse;
var skipPermitUnload = aParams.skipPermitUnload;
}
// Handle requests for synchronously removing an already
@ -2148,7 +2143,7 @@
var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
if (!this._beginRemoveTab(aTab, false, null, true, skipPermitUnload))
if (!this._beginRemoveTab(aTab, false, null, true))
return;
if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
@ -2196,7 +2191,6 @@
<parameter name="aTabWillBeMoved"/>
<parameter name="aCloseWindowWithLastTab"/>
<parameter name="aCloseWindowFastpath"/>
<parameter name="aSkipPermitUnload"/>
<body>
<![CDATA[
if (aTab.closing ||
@ -2227,20 +2221,23 @@
newTab = true;
}
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;
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;
}
}
}
@ -4058,19 +4055,13 @@
}
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) {
// Skip running PermitUnload since it already happened in
// the content process.
this.removeTab(tab, {skipPermitUnload: true});
this.removeTab(tab);
}
break;
}
@ -4354,18 +4345,12 @@
if (!event.isTrusted)
return;
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;
if (this.tabs.length == 1)
return;
}
var tab = this._getTabForContentWindow(event.target);
if (tab) {
// Skip running PermitUnload since it already happened.
this.removeTab(tab, {skipPermitUnload: true});
this.removeTab(tab);
event.preventDefault();
}
]]>

View File

@ -7772,7 +7772,7 @@ nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal,
mTiming->NotifyBeforeUnload();
bool okToUnload;
rv = mContentViewer->PermitUnload(&okToUnload);
rv = mContentViewer->PermitUnload(false, &okToUnload);
if (NS_SUCCEEDED(rv) && !okToUnload) {
// The user chose not to unload the page, interrupt the load.
@ -10111,7 +10111,7 @@ nsDocShell::InternalLoad(nsIURI* aURI,
// protocol handler deals with this for javascript: URLs.
if (!isJavaScript && aFileName.IsVoid() && mContentViewer) {
bool okToUnload;
rv = mContentViewer->PermitUnload(&okToUnload);
rv = mContentViewer->PermitUnload(false, &okToUnload);
if (NS_SUCCEEDED(rv) && !okToUnload) {
// The user chose not to unload the page, interrupt the

View File

@ -31,7 +31,7 @@ class nsDOMNavigationTiming;
[ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming);
[ref] native nsIContentViewerTArray(nsTArray<nsCOMPtr<nsIContentViewer> >);
[scriptable, builtinclass, uuid(91b6c1f3-fc5f-43a9-88f4-9286bd19387f)]
[scriptable, builtinclass, uuid(702e0a92-7d63-490e-b5ee-d247e6bd4588)]
interface nsIContentViewer : nsISupports
{
@ -46,8 +46,12 @@ 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();
boolean permitUnload([optional] in boolean aCallerClosesWindow);
/**
* Exposes whether we're blocked in a call to permitUnload.
@ -59,7 +63,8 @@ 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(inout boolean aShouldPrompt);
[noscript,nostdcall] boolean permitUnloadInternal(in boolean aCallerClosesWindow,
inout boolean aShouldPrompt);
/**
* Exposes whether we're in the process of firing the beforeunload event.
@ -67,6 +72,16 @@ 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);
/**

View File

@ -8752,17 +8752,6 @@ 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;
}
@ -8776,7 +8765,7 @@ nsGlobalWindow::CanClose()
mDocShell->GetContentViewer(getter_AddRefs(cv));
if (cv) {
bool canClose;
nsresult rv = cv->PermitUnload(&canClose);
nsresult rv = cv->PermitUnload(false, &canClose);
if (NS_SUCCEEDED(rv) && !canClose)
return false;

View File

@ -1519,7 +1519,7 @@ nsHTMLDocument::Open(JSContext* cx,
if (cv) {
bool okToUnload;
if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) {
if (NS_SUCCEEDED(cv->PermitUnload(false, &okToUnload)) && !okToUnload) {
// We don't want to unload, so stop here, but don't throw an
// exception.
nsCOMPtr<nsIDocument> ret = this;

View File

@ -17,7 +17,7 @@ interface nsIOpenURIInFrameParams : nsISupports
attribute boolean isPrivate;
};
[scriptable, uuid(31da1ce2-aec4-4c26-ac66-d622935c3bf4)]
[scriptable, uuid(99f5a347-722c-4337-bd38-f14ec94801b3)]
/**
* The C++ source has access to the browser script source through
@ -99,14 +99,6 @@ 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);
/**
* 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();
boolean isTabContentWindow(in nsIDOMWindow aWindow);
};

View File

@ -749,7 +749,7 @@ nsJSChannel::EvaluateScript()
if (cv) {
bool okToUnload;
if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) &&
if (NS_SUCCEEDED(cv->PermitUnload(false, &okToUnload)) &&
!okToUnload) {
// The user didn't want to unload the current
// page, translate this into an undefined

View File

@ -410,6 +410,7 @@ protected:
nsCString mForceCharacterSet;
bool mIsPageMode;
bool mCallerIsClosingWindow;
bool mInitializedForPrintPreview;
bool mHidden;
};
@ -460,6 +461,7 @@ void nsDocumentViewer::PrepareToStartLoad()
mLoaded = false;
mAttachedToParent = false;
mDeferredWindowClose = false;
mCallerIsClosingWindow = false;
#ifdef NS_PRINTING
mPrintIsPending = false;
@ -1047,15 +1049,18 @@ nsDocumentViewer::LoadComplete(nsresult aStatus)
}
NS_IMETHODIMP
nsDocumentViewer::PermitUnload(bool *aPermitUnload)
nsDocumentViewer::PermitUnload(bool aCallerClosesWindow,
bool *aPermitUnload)
{
bool shouldPrompt = true;
return PermitUnloadInternal(&shouldPrompt, aPermitUnload);
return PermitUnloadInternal(aCallerClosesWindow, &shouldPrompt,
aPermitUnload);
}
nsresult
nsDocumentViewer::PermitUnloadInternal(bool *aShouldPrompt,
nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow,
bool *aShouldPrompt,
bool *aPermitUnload)
{
AutoDontWarnAboutSyncXHR disableSyncXHRWarning;
@ -1064,6 +1069,7 @@ nsDocumentViewer::PermitUnloadInternal(bool *aShouldPrompt,
if (!mDocument
|| mInPermitUnload
|| mCallerIsClosingWindow
|| mInPermitUnloadPrompt) {
return NS_OK;
}
@ -1239,12 +1245,16 @@ nsDocumentViewer::PermitUnloadInternal(bool *aShouldPrompt,
docShell->GetContentViewer(getter_AddRefs(cv));
if (cv) {
cv->PermitUnloadInternal(aShouldPrompt, aPermitUnload);
cv->PermitUnloadInternal(aCallerClosesWindow, aShouldPrompt,
aPermitUnload);
}
}
}
}
if (aCallerClosesWindow && *aPermitUnload)
mCallerIsClosingWindow = true;
return NS_OK;
}
@ -1262,6 +1272,35 @@ nsDocumentViewer::GetInPermitUnload(bool* aInEvent)
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)
{

View File

@ -61,9 +61,6 @@ 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");
@ -3439,10 +3436,6 @@ nsBrowserAccess.prototype = {
isTabContentWindow: function(aWindow) {
return BrowserApp.getBrowserForWindow(aWindow) != null;
},
canClose() {
return BrowserUtils.canCloseWindow(window);
},
};

View File

@ -195,25 +195,6 @@ 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) {
@ -233,6 +214,25 @@ 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;
}
}

View File

@ -577,22 +577,6 @@ 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)

View File

@ -1073,8 +1073,8 @@
window.addEventListener("keypress", this, true);
window.addEventListener("keyup", this, true);
]]>
</body>
</method>
</body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
@ -1227,30 +1227,6 @@
</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>

View File

@ -234,89 +234,6 @@
<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[
/*

View File

@ -401,21 +401,5 @@ this.BrowserUtils = {
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;
},
}
};