Bug 391834, don't allow prompts in beforeunload, unload and pagehide events,r=smaug,patch mostly by gavin

This commit is contained in:
Neil Deakin 2012-08-13 15:05:34 -04:00
parent afbbe47319
commit 1a89394d0d
10 changed files with 211 additions and 15 deletions

View File

@ -1616,6 +1616,11 @@
evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
aTab.dispatchEvent(evt);
// Prevent this tab from showing further dialogs, since we're closing it
var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils);
windowUtils.preventFurtherDialogs();
// Remove the tab's filter and progress listener.
const filter = this.mTabFilters[aTab._tPos];
#ifdef MOZ_E10S_COMPAT

View File

@ -199,6 +199,7 @@ _BROWSER_FILES = \
browser_tabfocus.js \
browser_tabs_isActive.js \
browser_tabs_owner.js \
browser_unloaddialogs.js \
browser_urlbarAutoFillTrimURLs.js \
browser_urlbarCopying.js \
browser_urlbarEnter.js \

View File

@ -0,0 +1,134 @@
function notify(event)
{
if (event.target.location == "about:blank")
return;
var eventname = event.type;
if (eventname == "pagehide")
details.pagehides++;
else if (eventname == "beforeunload")
details.beforeunloads++;
else if (eventname == "unload")
details.unloads++;
}
var details;
var gUseFrame = false;
const windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
const TEST_BASE_URL = "data:text/html,<script>" +
"function note(event) { try { alert(event.type); } catch(ex) { return; } throw 'alert appeared'; }" +
"</script>" +
"<body onpagehide='note(event)' onbeforeunload='alert(event.type);' onunload='note(event)'>";
const TEST_URL = TEST_BASE_URL + "Test</body>";
const TEST_FRAME_URL = TEST_BASE_URL + "Frames</body>";
function test()
{
waitForExplicitFinish();
windowMediator.addListener(promptListener);
runTest();
}
function runTest()
{
details = {
testNumber : 0,
beforeunloads : 0,
pagehides : 0,
unloads : 0,
prompts : 0
};
var tab = gBrowser.addTab(TEST_URL);
gBrowser.selectedTab = tab;
tab.linkedBrowser.addEventListener("pageshow", shown, true);
tab.linkedBrowser.addEventListener("pagehide", notify, true);
tab.linkedBrowser.addEventListener("beforeunload", notify, true);
tab.linkedBrowser.addEventListener("unload", notify, true);
}
function shown(event)
{
if (details.testNumber == 0) {
var browser;
var iframe;
if (gUseFrame) {
iframe = event.target.createElement("iframe");
iframe.src = TEST_FRAME_URL;
event.target.documentElement.appendChild(iframe);
browser = iframe.contentWindow;
}
else {
browser = gBrowser.selectedTab.linkedBrowser;
details.testNumber = 1; // Move onto to the next step immediately
}
}
if (details.testNumber == 1) {
// Test going to another page
executeSoon(function () {
const urlToLoad = "data:text/html,<body>Another Page</body>";
if (gUseFrame) {
event.target.location = urlToLoad;
}
else {
gBrowser.selectedBrowser.loadURI(urlToLoad);
}
});
}
else if (details.testNumber == 2) {
is(details.pagehides, 1, "pagehides after next page")
is(details.beforeunloads, 1, "beforeunloads after next page")
is(details.unloads, 1, "unloads after next page")
is(details.prompts, 1, "prompts after next page")
executeSoon(function () gUseFrame ? gBrowser.goBack() : event.target.defaultView.back());
}
else if (details.testNumber == 3) {
is(details.pagehides, 2, "pagehides after back")
is(details.beforeunloads, 2, "beforeunloads after back")
// No cache, so frame is unloaded
is(details.unloads, gUseFrame ? 2 : 1, "unloads after back")
is(details.prompts, 1, "prompts after back")
// Test closing the tab
gBrowser.selectedBrowser.removeEventListener("pageshow", shown, true);
gBrowser.removeTab(gBrowser.selectedTab);
// When the frame is present, there is are two beforeunload and prompts,
// one for the frame and the other for the parent.
is(details.pagehides, 3, "pagehides after close")
is(details.beforeunloads, gUseFrame ? 4 : 3, "beforeunloads after close")
is(details.unloads, gUseFrame ? 3 : 2, "unloads after close")
is(details.prompts, gUseFrame ? 3 : 2, "prompts after close")
// Now run the test again using a child frame.
if (gUseFrame) {
windowMediator.removeListener(promptListener);
finish();
}
else {
gUseFrame = true;
runTest();
}
return;
}
details.testNumber++;
}
var promptListener = {
onWindowTitleChange: function () {},
onOpenWindow: function (win) {
details.prompts++;
let domWin = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
executeSoon(function () { domWin.close() });
},
onCloseWindow: function () {},
};

View File

@ -62,9 +62,6 @@ function testLeavePage() {
let dialogsAccepted = 0;
whenDialogOpened(function onDialogOpened(dialog) {
if (++dialogsAccepted < 3)
whenDialogOpened(onDialogOpened);
// Leave page
dialog.acceptDialog();
});

View File

@ -27,7 +27,7 @@ class nsDOMNavigationTiming;
[ptr] native nsIViewPtr(nsIView);
[ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming);
[scriptable, builtinclass, uuid(26b2380b-4a1a-46cd-b7d8-7600e41c1688)]
[scriptable, builtinclass, uuid(b9d92b8b-5623-4079-ae11-36bb341f322e)]
interface nsIContentViewer : nsISupports
{
@ -134,7 +134,7 @@ interface nsIContentViewer : nsISupports
*/
void clearHistoryEntry();
/*
/**
* Change the layout to view the document with page layout (like print preview), but
* dynamic and editable (like Galley layout).
*/
@ -146,7 +146,7 @@ interface nsIContentViewer : nsISupports
*/
readonly attribute nsISHEntry historyEntry;
/*
/**
* Indicates when we're in a state where content shouldn't be allowed to
* trigger a tab-modal prompt (as opposed to a window-modal prompt) because
* we're part way through some operation (eg beforeunload) that shouldn't be
@ -155,6 +155,11 @@ interface nsIContentViewer : nsISupports
*/
readonly attribute boolean isTabModalPromptAllowed;
/**
* Returns whether this content viewer is in a hidden state.
*/
readonly attribute boolean isHidden;
[noscript] readonly attribute nsIPresShellPtr presShell;
[noscript] readonly attribute nsPresContextPtr presContext;
// aDocument must not be null.

View File

@ -2287,6 +2287,22 @@ nsDOMWindowUtils::CheckAndClearPaintedState(nsIDOMElement* aElement, bool* aResu
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::PreventFurtherDialogs()
{
// Permanently disable further dialogs for this window.
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
static_cast<nsGlobalWindow*>(window.get())->PreventFurtherDialogs(true);
return NS_OK;
}
static nsresult
GetFileOrBlob(const nsAString& aName, const jsval& aBlobParts,
const jsval& aParameters, JSContext* aCx,

View File

@ -684,7 +684,8 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
mCleanedUp(false),
mCallCleanUpAfterModalDialogCloses(false),
mDialogAbuseCount(0),
mStopAbuseDialogs(false)
mStopAbuseDialogs(false),
mDialogsPermanentlyDisabled(false)
{
nsLayoutStatics::AddRef();
@ -2566,6 +2567,22 @@ nsGlobalWindow::DialogsAreBlocked(bool *aBeingAbused)
return true;
}
if (topWindow->mDialogsPermanentlyDisabled) {
return true;
}
// Dialogs are blocked if the content viewer is hidden
if (mDocShell) {
nsCOMPtr<nsIContentViewer> cv;
mDocShell->GetContentViewer(getter_AddRefs(cv));
bool isHidden;
cv->GetIsHidden(&isHidden);
if (isHidden) {
return true;
}
}
*aBeingAbused = topWindow->DialogsAreBeingAbused();
return topWindow->mStopAbuseDialogs && *aBeingAbused;
@ -2625,7 +2642,7 @@ nsGlobalWindow::ConfirmDialogIfNeeded()
"ScriptDialogPreventTitle", title);
promptSvc->Confirm(this, title.get(), label.get(), &disableDialog);
if (disableDialog) {
PreventFurtherDialogs();
PreventFurtherDialogs(false);
return false;
}
@ -2633,7 +2650,7 @@ nsGlobalWindow::ConfirmDialogIfNeeded()
}
void
nsGlobalWindow::PreventFurtherDialogs()
nsGlobalWindow::PreventFurtherDialogs(bool aPermanent)
{
nsGlobalWindow *topWindow = GetScriptableTop();
if (!topWindow) {
@ -2644,6 +2661,9 @@ nsGlobalWindow::PreventFurtherDialogs()
topWindow = topWindow->GetCurrentInnerWindowInternal();
if (topWindow) {
topWindow->mStopAbuseDialogs = true;
if (aPermanent) {
topWindow->mDialogsPermanentlyDisabled = true;
}
}
}
@ -4847,7 +4867,7 @@ nsGlobalWindow::Alert(const nsAString& aString)
rv = prompt->AlertCheck(title.get(), final.get(), label.get(),
&disallowDialog);
if (disallowDialog)
PreventFurtherDialogs();
PreventFurtherDialogs(false);
} else {
rv = prompt->Alert(title.get(), final.get());
}
@ -4914,7 +4934,7 @@ nsGlobalWindow::Confirm(const nsAString& aString, bool* aReturn)
rv = prompt->ConfirmCheck(title.get(), final.get(), label.get(),
&disallowDialog, aReturn);
if (disallowDialog)
PreventFurtherDialogs();
PreventFurtherDialogs(false);
} else {
rv = prompt->Confirm(title.get(), final.get(), aReturn);
}
@ -4990,7 +5010,7 @@ nsGlobalWindow::Prompt(const nsAString& aMessage, const nsAString& aInitial,
&inoutValue, label.get(), &disallowDialog, &ok);
if (disallowDialog) {
PreventFurtherDialogs();
PreventFurtherDialogs(false);
}
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -453,7 +453,7 @@ public:
bool ConfirmDialogIfNeeded();
// Prevent further dialogs in this (top level) window
void PreventFurtherDialogs();
void PreventFurtherDialogs(bool aPermanent);
virtual void SetHasAudioAvailableEventListeners();
@ -1059,6 +1059,11 @@ protected:
// mDialogAbuseCount gets reset.
bool mStopAbuseDialogs;
// This flag gets set when dialogs should be permanently disabled for this
// window (e.g. when we are closing the tab and therefore are guaranteed to be
// destroying this window).
bool mDialogsPermanentlyDisabled;
nsRefPtr<nsDOMMozURLProperty> mURLProperty;
nsTHashtable<nsPtrHashKey<nsDOMEventTargetHelper> > mEventTargetObjects;

View File

@ -39,7 +39,7 @@ interface nsIFile;
interface nsIDOMTouch;
interface nsIDOMClientRect;
[scriptable, uuid(97548ee0-9def-421a-ab2a-c6c98efa9a3c)]
[scriptable, uuid(5fc61d7b-a303-4f34-adfe-b7828675ba45)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -1183,4 +1183,10 @@ interface nsIDOMWindowUtils : nsISupports {
* The caller of this method must have UniversalXPConnect privileges.
*/
void setScrollPositionClampingScrollPortSize(in float aWidth, in float aHeight);
/**
* Prevent this window (and any child windows) from displaying any further
* dialogs (e.g. window.alert()).
*/
void preventFurtherDialogs();
};

View File

@ -4310,6 +4310,13 @@ DocumentViewerImpl::GetIsTabModalPromptAllowed(bool *aAllowed)
return NS_OK;
}
NS_IMETHODIMP
DocumentViewerImpl::GetIsHidden(bool *aHidden)
{
*aHidden = mHidden;
return NS_OK;
}
void
DocumentViewerImpl::DestroyPresShell()
{