Bug 1068660 - Add confirmation dialog to unblock downloads. r=Paolo r=MattN

This commit is contained in:
Alex Bardas 2014-10-14 11:19:00 +02:00
parent a121026943
commit e95da48b1b
11 changed files with 238 additions and 132 deletions

View File

@ -67,6 +67,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsLogger",
"resource:///modules/DownloadsLogger.jsm");
@ -143,6 +145,13 @@ PrefObserver.register({
* and provides shared methods for all the instances of the user interface.
*/
this.DownloadsCommon = {
/**
* Constants with the different types of unblock messages.
*/
BLOCK_VERDICT_MALWARE: "Malware",
BLOCK_VERDICT_POTENTIALLY_UNWANTED: "PotentiallyUnwanted",
BLOCK_VERDICT_UNCOMMON: "Uncommon",
log: function DC_log(...aMessageArgs) {
delete this.log;
this.log = function DC_log(...aMessageArgs) {
@ -511,7 +520,69 @@ this.DownloadsCommon = {
}
}
}
}
},
/**
* Displays an alert message box which asks the user if they want to
* unblock the downloaded file or not.
*
* @param aType
* The type of malware the downloaded file contains.
* @param aOwnerWindow
* The window with which this action is associated.
*
* @return True to unblock the file, false to keep the user safe and
* cancel the operation.
*/
confirmUnblockDownload: Task.async(function* DP_confirmUnblockDownload(aType, aOwnerWindow) {
let s = DownloadsCommon.strings;
let title = s.unblockHeader;
let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
(Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
let type = "";
let message = s.unblockTip;
let okButton = s.unblockButtonContinue;
let cancelButton = s.unblockButtonCancel;
switch (aType) {
case this.BLOCK_VERDICT_MALWARE:
type = s.unblockTypeMalware;
break;
case this.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
type = s.unblockTypePotentiallyUnwanted;
break;
case this.BLOCK_VERDICT_UNCOMMON:
type = s.unblockTypeUncommon;
break;
}
if (type) {
message = type + "\n\n" + message;
}
Services.ww.registerNotification(function onOpen(subj, topic) {
if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
// Make sure to listen for "DOMContentLoaded" because it is fired
// before the "load" event.
subj.addEventListener("DOMContentLoaded", function onLoad() {
subj.removeEventListener("DOMContentLoaded", onLoad);
if (subj.document.documentURI ==
"chrome://global/content/commonDialog.xul") {
Services.ww.unregisterNotification(onOpen);
let dialog = subj.document.getElementById("commonDialog");
if (dialog) {
// Change the dialog to use a warning icon.
dialog.classList.add("alert-dialog");
}
}
});
}
});
let rv = Services.prompt.confirmEx(aOwnerWindow, title, message, buttonFlags,
cancelButton, okButton, null, null, {});
return (rv == 1);
}),
};
/**

View File

@ -7,3 +7,4 @@ skip-if = buildapp == "mulet" || e10s
skip-if = os == "linux" # Bug 949434
[browser_overflow_anchor.js]
skip-if = os == "linux" # Bug 952422
[browser_confirm_unblock_download.js]

View File

@ -3,12 +3,15 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
registerCleanupFunction(function*() {
yield task_resetState();
});
/**
* Make sure the downloads panel can display items in the right order and
* contains the expected data.
*/
function test_task()
{
add_task(function* test_basic_functionality() {
// Display one of each download state.
const DownloadData = [
{ state: nsIDM.DOWNLOAD_NOTSTARTED },
@ -18,40 +21,35 @@ function test_task()
{ state: nsIDM.DOWNLOAD_CANCELED },
];
try {
// Wait for focus first
yield promiseFocus();
// Wait for focus first
yield promiseFocus();
// Ensure that state is reset in case previous tests didn't finish.
yield task_resetState();
// Ensure that state is reset in case previous tests didn't finish.
yield task_resetState();
// For testing purposes, show all the download items at once.
var originalCountLimit = DownloadsView.kItemCountLimit;
DownloadsView.kItemCountLimit = DownloadData.length;
registerCleanupFunction(function () {
DownloadsView.kItemCountLimit = originalCountLimit;
});
// For testing purposes, show all the download items at once.
var originalCountLimit = DownloadsView.kItemCountLimit;
DownloadsView.kItemCountLimit = DownloadData.length;
registerCleanupFunction(function () {
DownloadsView.kItemCountLimit = originalCountLimit;
});
// Populate the downloads database with the data required by this test.
yield task_addDownloads(DownloadData);
// Populate the downloads database with the data required by this test.
yield task_addDownloads(DownloadData);
// Open the user interface and wait for data to be fully loaded.
yield task_openPanel();
// Open the user interface and wait for data to be fully loaded.
yield task_openPanel();
// Test item data and count. This also tests the ordering of the display.
let richlistbox = document.getElementById("downloadsListBox");
/* disabled for failing intermittently (bug 767828)
// Test item data and count. This also tests the ordering of the display.
let richlistbox = document.getElementById("downloadsListBox");
/* disabled for failing intermittently (bug 767828)
is(richlistbox.children.length, DownloadData.length,
"There is the correct number of richlistitems");
*/
let itemCount = richlistbox.children.length;
for (let i = 0; i < itemCount; i++) {
let element = richlistbox.children[itemCount - i - 1];
let dataItem = new DownloadsViewItemController(element).dataItem;
is(dataItem.state, DownloadData[i].state, "Download states match up");
}
} finally {
// Clean up when the test finishes.
yield task_resetState();
*/
let itemCount = richlistbox.children.length;
for (let i = 0; i < itemCount; i++) {
let element = richlistbox.children[itemCount - i - 1];
let dataItem = new DownloadsViewItemController(element).dataItem;
is(dataItem.state, DownloadData[i].state, "Download states match up");
}
}
});

View File

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests the dialog which allows the user to unblock a downloaded file.
registerCleanupFunction(() => {});
function addDialogOpenObserver(buttonAction) {
Services.ww.registerNotification(function onOpen(subj, topic, data) {
if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
// The test listens for the "load" event which guarantees that the alert
// class has already been added (it is added when "DOMContentLoaded" is
// fired).
subj.addEventListener("load", function onLoad() {
subj.removeEventListener("load", onLoad);
if (subj.document.documentURI ==
"chrome://global/content/commonDialog.xul") {
Services.ww.unregisterNotification(onOpen);
let dialog = subj.document.getElementById("commonDialog");
ok(dialog.classList.contains("alert-dialog"),
"The dialog element should contain an alert class.");
let doc = subj.document.documentElement;
doc.getButton(buttonAction).click();
}
});
}
});
}
add_task(function* test_confirm_unblock_dialog_unblock() {
addDialogOpenObserver("cancel");
let result = yield DownloadsCommon.confirmUnblockDownload(DownloadsCommon.UNBLOCK_MALWARE,
window);
ok(result, "Should return true when the user clicks on `Unblock` button.");
});
add_task(function* test_confirm_unblock_dialog_keep_safe() {
addDialogOpenObserver("accept");
let result = yield DownloadsCommon.confirmUnblockDownload(DownloadsCommon.UNBLOCK_MALWARE,
window);
ok(!result, "Should return false when the user clicks on `Keep me safe` button.");
});

View File

@ -8,58 +8,50 @@
* download it notices. All subsequent downloads, even across sessions, should
* not open the panel automatically.
*/
function test_task()
{
add_task(function* test_first_download_panel() {
// Clear the download panel has shown preference first as this test is used to
// verify this preference's behaviour.
let oldPrefValue = true;
try {
oldPrefValue = Services.prefs.getBoolPref("browser.download.panel.shown");
} catch(ex) { }
let oldPrefValue = Services.prefs.getBoolPref("browser.download.panel.shown");
Services.prefs.setBoolPref("browser.download.panel.shown", false);
try {
// Ensure that state is reset in case previous tests didn't finish.
yield task_resetState();
// With this set to false, we should automatically open the panel the first
// time a download is started.
DownloadsCommon.getData(window).panelHasShownBefore = false;
let promise = promisePanelOpened();
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
yield promise;
// If we got here, that means the panel opened.
DownloadsPanel.hidePanel();
ok(DownloadsCommon.getData(window).panelHasShownBefore,
"Should have recorded that the panel was opened on a download.")
// Next, make sure that if we start another download, we don't open the
// panel automatically.
let originalOnPopupShown = DownloadsPanel.onPopupShown;
DownloadsPanel.onPopupShown = function () {
originalOnPopupShown.apply(this, arguments);
ok(false, "Should not have opened the downloads panel.");
};
try {
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
// Wait 2 seconds to ensure that the panel does not open.
let deferTimeout = Promise.defer();
setTimeout(deferTimeout.resolve, 2000);
yield deferTimeout.promise;
} finally {
DownloadsPanel.onPopupShown = originalOnPopupShown;
}
} finally {
registerCleanupFunction(function*() {
// Clean up when the test finishes.
yield task_resetState();
// Set the preference instead of clearing it afterwards to ensure the
// right value is used no matter what the default was. This ensures the
// panel doesn't appear and affect other tests.
Services.prefs.setBoolPref("browser.download.panel.shown", oldPrefValue);
}
}
});
// Ensure that state is reset in case previous tests didn't finish.
yield task_resetState();
// With this set to false, we should automatically open the panel the first
// time a download is started.
DownloadsCommon.getData(window).panelHasShownBefore = false;
let promise = promisePanelOpened();
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
yield promise;
// If we got here, that means the panel opened.
DownloadsPanel.hidePanel();
ok(DownloadsCommon.getData(window).panelHasShownBefore,
"Should have recorded that the panel was opened on a download.")
// Next, make sure that if we start another download, we don't open the
// panel automatically.
let originalOnPopupShown = DownloadsPanel.onPopupShown;
DownloadsPanel.onPopupShown = function () {
originalOnPopupShown.apply(this, arguments);
ok(false, "Should not have opened the downloads panel.");
};
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
// Wait 2 seconds to ensure that the panel does not open.
yield new Promise(resolve => setTimeout(resolve, 2000));
DownloadsPanel.onPopupShown = originalOnPopupShown;
});

View File

@ -1,69 +1,69 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
registerCleanupFunction(function*() {
// Clean up when the test finishes.
yield task_resetState();
});
/**
* Make sure the downloads button and indicator overflows into the nav-bar
* chevron properly, and then when those buttons are clicked in the overflow
* panel that the downloads panel anchors to the chevron.
*/
function test_task() {
try {
// Ensure that state is reset in case previous tests didn't finish.
yield task_resetState();
add_task(function* test_overflow_anchor() {
// Ensure that state is reset in case previous tests didn't finish.
yield task_resetState();
// Record the original width of the window so we can put it back when
// this test finishes.
let oldWidth = window.outerWidth;
// Record the original width of the window so we can put it back when
// this test finishes.
let oldWidth = window.outerWidth;
// The downloads button should not be overflowed to begin with.
let button = CustomizableUI.getWidget("downloads-button")
.forWindow(window);
ok(!button.overflowed, "Downloads button should not be overflowed.");
// The downloads button should not be overflowed to begin with.
let button = CustomizableUI.getWidget("downloads-button")
.forWindow(window);
ok(!button.overflowed, "Downloads button should not be overflowed.");
// Hack - we lock the size of the default flex-y items in the nav-bar,
// namely, the URL and search inputs. That way we can resize the
// window without worrying about them flexing.
const kFlexyItems = ["urlbar-container", "search-container"];
registerCleanupFunction(() => unlockWidth(kFlexyItems));
lockWidth(kFlexyItems);
// Hack - we lock the size of the default flex-y items in the nav-bar,
// namely, the URL and search inputs. That way we can resize the
// window without worrying about them flexing.
const kFlexyItems = ["urlbar-container", "search-container"];
registerCleanupFunction(() => unlockWidth(kFlexyItems));
lockWidth(kFlexyItems);
// Resize the window to half of its original size. That should
// be enough to overflow the downloads button.
window.resizeTo(oldWidth / 2, window.outerHeight);
yield waitForOverflowed(button, true);
// Resize the window to half of its original size. That should
// be enough to overflow the downloads button.
window.resizeTo(oldWidth / 2, window.outerHeight);
yield waitForOverflowed(button, true);
let promise = promisePanelOpened();
button.node.doCommand();
yield promise;
let promise = promisePanelOpened();
button.node.doCommand();
yield promise;
let panel = DownloadsPanel.panel;
let chevron = document.getElementById("nav-bar-overflow-button");
is(panel.anchorNode, chevron, "Panel should be anchored to the chevron.");
let panel = DownloadsPanel.panel;
let chevron = document.getElementById("nav-bar-overflow-button");
is(panel.anchorNode, chevron, "Panel should be anchored to the chevron.");
DownloadsPanel.hidePanel();
DownloadsPanel.hidePanel();
// Unlock the widths on the flex-y items.
unlockWidth(kFlexyItems);
// Unlock the widths on the flex-y items.
unlockWidth(kFlexyItems);
// Put the window back to its original dimensions.
window.resizeTo(oldWidth, window.outerHeight);
// Put the window back to its original dimensions.
window.resizeTo(oldWidth, window.outerHeight);
// The downloads button should eventually be un-overflowed.
yield waitForOverflowed(button, false);
// The downloads button should eventually be un-overflowed.
yield waitForOverflowed(button, false);
// Now try opening the panel again.
promise = promisePanelOpened();
button.node.doCommand();
yield promise;
// Now try opening the panel again.
promise = promisePanelOpened();
button.node.doCommand();
yield promise;
is(panel.anchorNode.id, "downloads-indicator-anchor");
is(panel.anchorNode.id, "downloads-indicator-anchor");
DownloadsPanel.hidePanel();
} finally {
// Clean up when the test finishes.
yield task_resetState();
}
}
DownloadsPanel.hidePanel();
});
/**
* For some node IDs, finds the nodes and sets their min-width's to their

View File

@ -28,15 +28,6 @@ registerCleanupFunction(function () {
gTestTargetFile.remove(false);
});
////////////////////////////////////////////////////////////////////////////////
//// Infrastructure
function test()
{
waitForExplicitFinish();
Task.spawn(test_task).then(null, ex => ok(false, ex)).then(finish);
}
////////////////////////////////////////////////////////////////////////////////
//// Asynchronous support subroutines

View File

@ -13,7 +13,6 @@
<dialog id="commonDialog"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
aria-describedby="info.body"
onload="commonDialogOnLoad();"
onunload="commonDialogOnUnload();"
ondialogaccept="Dialog.onButton0(); return true;"
ondialogcancel="Dialog.onButton1(); return true;"
@ -23,6 +22,11 @@
<script type="application/javascript" src="chrome://global/content/commonDialog.js"/>
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript">
document.addEventListener("DOMContentLoaded", function() {
commonDialogOnLoad();
});
</script>
<commandset id="selectEditMenuItems">
<command id="cmd_copy" oncommand="goDoCommand('cmd_copy')" disabled="true"/>

View File

@ -60,6 +60,7 @@ window.dialog {
list-style-image: url("moz-icon://stock/gtk-dialog-info?size=dialog");
}
.alert-dialog #info\.icon,
.alert-icon {
list-style-image: url("moz-icon://stock/gtk-dialog-warning?size=dialog");
}

View File

@ -78,6 +78,7 @@ window.dialog {
list-style-image: url("chrome://global/skin/icons/information-64.png");
}
.alert-dialog #info\.icon,
.alert-icon {
list-style-image: url("chrome://global/skin/icons/warning-64.png");
}

View File

@ -56,6 +56,7 @@ window.dialog {
list-style-image: url("chrome://global/skin/icons/information-32.png");
}
.alert-dialog #info\.icon,
.alert-icon {
list-style-image: url("chrome://global/skin/icons/Warning.png");
}