Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
let { MockFilePicker } = SpecialPowers;
MockFilePicker.init(window.browsingContext);
registerCleanupFunction(() => MockFilePicker.cleanup());
/**
* Check that the downloads panel opens when a download is spoofed.
*/
async function checkPanelOpens() {
info("Waiting for panel to open.");
let promise = promisePanelOpened();
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
is(
DownloadsPanel.isPanelShowing,
true,
"Panel state should indicate a preparation to be opened."
);
await promise;
is(DownloadsPanel.panel.state, "open", "Panel should be opened.");
DownloadsPanel.hidePanel();
}
/**
* Start a download and check that the downloads panel opens correctly according
* to the download parameter, openDownloadsListOnStart
* @param {boolean} [openDownloadsListOnStart]
* true (default) - open downloads panel when download starts
* false - no downloads panel; update indicator attention state
*/
async function downloadAndCheckPanel({ openDownloadsListOnStart = true } = {}) {
info("creating a download and setting it to in progress");
await task_addDownloads([
{
state: DownloadsCommon.DOWNLOAD_DOWNLOADING,
openDownloadsListOnStart,
},
]);
let publicList = await Downloads.getList(Downloads.PUBLIC);
let downloads = await publicList.getAll();
downloads[0].stopped = false;
// Make sure we remove that download at the end of the test.
let oldShowEventNotification = DownloadsIndicatorView.showEventNotification;
registerCleanupFunction(async () => {
for (let download of downloads) {
await publicList.remove(download);
}
DownloadsIndicatorView.showEventNotification = oldShowEventNotification;
});
// Instead of the panel opening, the download notification should be shown.
let promiseDownloadStartedNotification = new Promise(resolve => {
DownloadsIndicatorView.showEventNotification = aType => {
if (aType == "start") {
resolve();
}
};
});
DownloadsCommon.getData(window)._notifyDownloadEvent("start", {
openDownloadsListOnStart,
});
is(
DownloadsPanel.isPanelShowing,
false,
"Panel state should indicate it is not preparing to be opened"
);
info("waiting for download to start");
await promiseDownloadStartedNotification;
DownloadsIndicatorView.showEventNotification = oldShowEventNotification;
is(DownloadsPanel.panel.state, "closed", "Panel should be closed");
}
function clickCheckbox(checkbox) {
// Clicking a checkbox toggles its checkedness first.
if (checkbox.getAttribute("checked") == "true") {
checkbox.removeAttribute("checked");
} else {
checkbox.setAttribute("checked", "true");
}
// Then it runs the command and closes the popup.
checkbox.doCommand();
checkbox.parentElement.hidePopup();
}
/**
* Test that the downloads panel correctly opens or doesn't open based on
* whether the download triggered a dialog already. If askWhereToSave is true,
* we should get a file picker dialog. If preferredAction is alwaysAsk, we
* should get an unknown content type dialog. If neither of those is true, we
* should get no dialog at all, and expect the downloads panel to open.
* @param {boolean} [expectPanelToOpen] true - fail if panel doesn't open
* false (default) - fail if it opens
* @param {number} [preferredAction] Default download action:
* 0 (default) - save download to disk
* 1 - open UCT dialog first
* @param {boolean} [askWhereToSave] true - open file picker dialog
* false (default) - use download dir
*/
async function testDownloadsPanelAfterDialog({
expectPanelToOpen = false,
preferredAction,
askWhereToSave = false,
} = {}) {
const { saveToDisk, alwaysAsk } = Ci.nsIHandlerInfo;
if (![saveToDisk, alwaysAsk].includes(preferredAction)) {
preferredAction = saveToDisk;
}
const openUCT = preferredAction === alwaysAsk;
const TEST_PATH = getRootDirectory(gTestPath).replace(
);
const MimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
const HandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
Ci.nsIHandlerService
);
let publicList = await Downloads.getList(Downloads.PUBLIC);
for (let download of await publicList.getAll()) {
await publicList.remove(download);
}
// We need to test the changes from bug 1739348, where the helper app service
// sets a flag based on whether a file picker dialog was opened, and this flag
// determines whether the downloads panel will be opened as the download
// starts. We need to actually hit "Save" for the download to start, but we
// can't interact with the real file picker dialog. So this temporarily
// replaces it with a barebones component that plugs into the helper app
// service and tells it to start saving the file to the default path.
if (askWhereToSave) {
MockFilePicker.returnValue = MockFilePicker.returnOK;
MockFilePicker.showCallback = function (fp) {
// Get the default location from the helper app service.
let testFile = MockFilePicker.displayDirectory.clone();
testFile.append(fp.defaultString);
info("File picker download path: " + testFile.path);
MockFilePicker.setFiles([testFile]);
MockFilePicker.filterIndex = 0; // kSaveAsType_Complete
MockFilePicker.showCallback = null;
// Confirm that saving should proceed. The helper app service uses this
// value to determine whether to invoke launcher.saveDestinationAvailable
return MockFilePicker.returnOK;
};
}
await SpecialPowers.pushPrefEnv({
set: [
["browser.download.useDownloadDir", !askWhereToSave],
["browser.download.always_ask_before_handling_new_types", openUCT],
["security.dialog_enable_delay", 0],
],
});
// Configure the handler for the file according to parameters.
let mimeInfo = MimeSvc.getFromTypeAndExtension("text/plain", "txt");
let existed = HandlerSvc.exists(mimeInfo);
mimeInfo.alwaysAskBeforeHandling = openUCT;
mimeInfo.preferredAction = preferredAction;
HandlerSvc.store(mimeInfo);
registerCleanupFunction(async () => {
// Reset the handler to its original state.
if (existed) {
HandlerSvc.store(mimeInfo);
} else {
HandlerSvc.remove(mimeInfo);
}
await publicList.removeFinished();
BrowserTestUtils.removeTab(loadingTab);
});
let dialogWindowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
let downloadFinishedPromise = new Promise(resolve => {
publicList.addView({
onDownloadChanged(download) {
info("Download changed!");
if (download.succeeded || download.error) {
info("Download succeeded or failed.");
publicList.removeView(this);
resolve(download);
}
},
});
});
let panelOpenedPromise = expectPanelToOpen ? promisePanelOpened() : null;
// Open the tab that will trigger the download.
let loadingTab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: TEST_PATH + "foo.txt",
waitForLoad: false,
waitForStateStop: true,
});
// Wait for a UCT dialog if the handler was set up to open one.
if (openUCT) {
let dialogWindow = await dialogWindowPromise;
is(
dialogWindow.location.href,
"Should have seen the unknown content dialogWindow."
);
let doc = dialogWindow.document;
let dialog = doc.getElementById("unknownContentType");
let radio = doc.getElementById("save");
let button = dialog.getButton("accept");
await TestUtils.waitForCondition(
() => !button.disabled,
"Waiting for the UCT dialog's Accept button to be enabled."
);
ok(!radio.hidden, "The Save option should be visible");
// Make sure we aren't opening the file.
radio.click();
ok(radio.selected, "The Save option should be selected");
button.disabled = false;
dialog.acceptDialog();
}
info("Waiting for download to finish.");
let download = await downloadFinishedPromise;
ok(!download.error, "There should be no error.");
is(
DownloadsPanel.isPanelShowing,
expectPanelToOpen,
`Panel should${expectPanelToOpen ? " " : " not "}be showing.`
);
if (DownloadsPanel.isPanelShowing) {
await panelOpenedPromise;
let hiddenPromise = BrowserTestUtils.waitForPopupEvent(
DownloadsPanel.panel,
"hidden"
);
DownloadsPanel.hidePanel();
await hiddenPromise;
}
if (download?.target.exists) {
try {
info("Removing test file: " + download.target.path);
if (Services.appinfo.OS === "WINNT") {
await IOUtils.setPermissions(download.target.path, 0o600);
}
await IOUtils.remove(download.target.path);
} catch (ex) {
/* ignore */
}
}
for (let dl of await publicList.getAll()) {
await publicList.remove(dl);
}
BrowserTestUtils.removeTab(loadingTab);
}
/**
* Make sure the downloads panel opens automatically with a new download.
*/
add_task(async function test_downloads_panel_opens() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.download.always_ask_before_handling_new_types", false],
["browser.download.alwaysOpenPanel", true],
],
});
await checkPanelOpens();
});
add_task(async function test_customizemode_doesnt_wreck_things() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.download.always_ask_before_handling_new_types", false],
["browser.download.alwaysOpenPanel", true],
],
});
// Enter customize mode:
let customizationReadyPromise = BrowserTestUtils.waitForEvent(
gNavToolbox,
"customizationready"
);
gCustomizeMode.enter();
await customizationReadyPromise;
info("Try to open the panel (will not work, in customize mode)");
let promise = promisePanelOpened();
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
await new Promise(r => setTimeout(r, 100));
is(
DownloadsPanel.panel.state,
"closed",
"Should not start opening the panel."
);
let afterCustomizationPromise = BrowserTestUtils.waitForEvent(
gNavToolbox,
"aftercustomization"
);
gCustomizeMode.exit();
await afterCustomizationPromise;
// Avoid a failure on Linux where the window isn't active for some reason,
// which prevents the window's downloads panel from opening.
if (Services.focus.activeWindow != window) {
info("Main window is not active, trying to focus.");
await SimpleTest.promiseFocus(window);
is(Services.focus.activeWindow, window, "Main window should be active.");
}
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
await TestUtils.waitForCondition(
() => DownloadsPanel.isPanelShowing,
"Panel state should indicate a preparation to be opened"
);
await promise;
is(DownloadsPanel.panel.state, "open", "Panel should be opened");
DownloadsPanel.hidePanel();
});
/**
* Make sure the downloads panel _does not_ open automatically if we set the
* pref telling it not to do that.
*/
add_task(async function test_downloads_panel_opening_pref() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.download.always_ask_before_handling_new_types", false],
["browser.download.alwaysOpenPanel", false],
],
});
registerCleanupFunction(async () => {
await SpecialPowers.popPrefEnv();
});
await downloadAndCheckPanel();
await SpecialPowers.popPrefEnv();
});
/**
* Make sure the downloads panel _does not_ open automatically if we pass the
* parameter telling it not to do that to the download constructor.
*/
add_task(async function test_downloads_openDownloadsListOnStart_param() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.download.always_ask_before_handling_new_types", false],
["browser.download.alwaysOpenPanel", true],
],
});
registerCleanupFunction(async () => {
await SpecialPowers.popPrefEnv();
});
await downloadAndCheckPanel({ openDownloadsListOnStart: false });
await SpecialPowers.popPrefEnv();
});
/**
* Make sure the downloads panel _does not_ open automatically when an
* extension calls the browser.downloads.download API method while it is
* not handling user input, but that we do open it automatically when
* the same WebExtensions API is called while handling user input
* (See Bug 1759231)
*/
add_task(async function test_downloads_panel_on_webext_download_api() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.download.always_ask_before_handling_new_types", false],
["browser.download.alwaysOpenPanel", true],
],
});
registerCleanupFunction(async () => {
await SpecialPowers.popPrefEnv();
});
const extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["downloads"],
},
background() {
async function startDownload(downloadOptions) {
/* globals browser */
const downloadId = await browser.downloads.download(downloadOptions);
const downloadDone = new Promise(resolve => {
browser.downloads.onChanged.addListener(function listener(delta) {
browser.test.log(`downloads.onChanged = ${JSON.stringify(delta)}`);
if (
delta.id == downloadId &&
delta.state?.current !== "in_progress"
) {
browser.downloads.onChanged.removeListener(listener);
resolve();
}
});
});
browser.test.sendMessage("start-download:done");
await downloadDone;
await browser.downloads.removeFile(downloadId);
browser.test.sendMessage("removed-download-file");
}
browser.test.onMessage.addListener(
(msg, { withHandlingUserInput, downloadOptions }) => {
if (msg !== "start-download") {
browser.test.fail(`Got unexpected test message: ${msg}`);
return;
}
if (withHandlingUserInput) {
browser.test.withHandlingUserInput(() =>
startDownload(downloadOptions)
);
} else {
startDownload(downloadOptions);
}
}
);
},
});
await extension.startup();
startServer();
async function testExtensionDownloadCall({ withHandlingUserInput }) {
mustInterruptResponses();
let rnd = Math.random();
let url = httpUrl(`interruptible.txt?q=${rnd}`);
extension.sendMessage("start-download", {
withHandlingUserInput,
downloadOptions: { url },
});
await extension.awaitMessage("start-download:done");
let publicList = await Downloads.getList(Downloads.PUBLIC);
let downloads = await publicList.getAll();
let download = downloads.find(d => d.source.url === url);
is(download.source.url, url, "download has the expected url");
is(
download.openDownloadsListOnStart,
withHandlingUserInput,
`download panel should ${withHandlingUserInput ? "open" : "stay closed"}`
);
continueResponses();
await extension.awaitMessage("removed-download-file");
}
info(
"Test extension downloads.download API method call without handling user input"
);
await testExtensionDownloadCall({ withHandlingUserInput: true });
info(
"Test extension downloads.download API method call while handling user input"
);
await testExtensionDownloadCall({ withHandlingUserInput: false });
await extension.unload();
await SpecialPowers.popPrefEnv();
});
/**
* Make sure the downloads panel opens automatically with new download, only if
* no other downloads are in progress.
*/
add_task(async function test_downloads_panel_remains_closed() {
await SpecialPowers.pushPrefEnv({
set: [["browser.download.always_ask_before_handling_new_types", false]],
});
await task_addDownloads([
{ state: DownloadsCommon.DOWNLOAD_DOWNLOADING },
{ state: DownloadsCommon.DOWNLOAD_DOWNLOADING },
]);
let publicList = await Downloads.getList(Downloads.PUBLIC);
let downloads = await publicList.getAll();
info("setting 2 downloads to be in progress");
downloads[0].stopped = false;
downloads[1].stopped = false;
let oldShowEventNotification = DownloadsIndicatorView.showEventNotification;
registerCleanupFunction(async () => {
// Remove all downloads created during the test.
for (let download of downloads) {
await publicList.remove(download);
}
DownloadsIndicatorView.showEventNotification = oldShowEventNotification;
});
let promiseDownloadStartedNotification = new Promise(resolve => {
// Instead of downloads panel opening, download notification should be shown.
DownloadsIndicatorView.showEventNotification = aType => {
if (aType == "start") {
DownloadsIndicatorView.showEventNotification = oldShowEventNotification;
resolve();
}
};
});
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
is(
DownloadsPanel.isPanelShowing,
false,
"Panel state should NOT indicate a preparation to be opened"
);
await promiseDownloadStartedNotification;
is(DownloadsPanel.panel.state, "closed", "Panel should be closed");
for (let download of downloads) {
await publicList.remove(download);
}
is((await publicList.getAll()).length, 0, "Should have no downloads left.");
});
/**
* Make sure the downloads panel doesn't open if the window isn't in the
* foreground.
*/
add_task(async function test_downloads_panel_inactive_window() {
await SpecialPowers.pushPrefEnv({
set: [["browser.download.always_ask_before_handling_new_types", false]],
});
let oldShowEventNotification = DownloadsIndicatorView.showEventNotification;
registerCleanupFunction(async () => {
DownloadsIndicatorView.showEventNotification = oldShowEventNotification;
});
let promiseDownloadStartedNotification = new Promise(resolve => {
// Instead of downloads panel opening, download notification should be shown.
DownloadsIndicatorView.showEventNotification = aType => {
if (aType == "start") {
DownloadsIndicatorView.showEventNotification = oldShowEventNotification;
resolve();
}
};
});
let testRunnerWindow = Array.from(Services.wm.getEnumerator("")).find(
someWin => someWin != window
);
await SimpleTest.promiseFocus(testRunnerWindow);
DownloadsCommon.getData(window)._notifyDownloadEvent("start");
is(
DownloadsPanel.isPanelShowing,
false,
"Panel state should NOT indicate a preparation to be opened"
);
await promiseDownloadStartedNotification;
await SimpleTest.promiseFocus(window);
is(DownloadsPanel.panel.state, "closed", "Panel should be closed");
testRunnerWindow = null;
});
/**
* When right-clicking the downloads toolbar button, there should be a menuitem
* for toggling alwaysOpenPanel. Check that it works correctly.
*/
add_task(async function test_alwaysOpenPanel_menuitem() {
const alwaysOpenPanelPref = "browser.download.alwaysOpenPanel";
let checkbox = document.getElementById(
"toolbar-context-always-open-downloads-panel"
);
let button = document.getElementById("downloads-button");
Services.prefs.clearUserPref(alwaysOpenPanelPref);
await SpecialPowers.pushPrefEnv({
set: [["browser.download.autohideButton", false]],
});
registerCleanupFunction(async () => {
await SpecialPowers.popPrefEnv();
Services.prefs.clearUserPref(alwaysOpenPanelPref);
});
is(button.hidden, false, "Downloads button should not be hidden.");
info("Check context menu for downloads button.");
await openContextMenu(button);
is(checkbox.hidden, false, "Always Open checkbox is visible.");
is(checkbox.getAttribute("checked"), "true", "Always Open is enabled.");
info("Disable Always Open via context menu.");
clickCheckbox(checkbox);
is(
Services.prefs.getBoolPref(alwaysOpenPanelPref),
false,
"Always Open pref has been set to false."
);
await downloadAndCheckPanel();
await openContextMenu(button);
is(checkbox.hidden, false, "Always Open checkbox is visible.");
isnot(checkbox.getAttribute("checked"), "true", "Always Open is disabled.");
info("Enable Always Open via context menu");
clickCheckbox(checkbox);
is(
Services.prefs.getBoolPref(alwaysOpenPanelPref),
true,
"Pref has been set to true"
);
await checkPanelOpens();
});
/**
* Verify that the downloads panel opens if the download did not open a file
* picker or UCT dialog
*/
add_task(async function test_downloads_panel_after_no_dialogs() {
await testDownloadsPanelAfterDialog({ expectPanelToOpen: true });
ok(true, "Downloads panel opened because no dialogs were opened.");
});
/**
* Verify that the downloads panel doesn't open if the download opened an
* unknown content type dialog (e.g. action = always ask)
*/
add_task(async function test_downloads_panel_after_UCT_dialog() {
await testDownloadsPanelAfterDialog({
expectPanelToOpen: false,
preferredAction: Ci.nsIHandlerInfo.alwaysAsk,
});
ok(true, "Downloads panel suppressed after UCT dialog.");
});
/**
* Verify that the downloads panel doesn't open if the download opened a file
* picker dialog (e.g. useDownloadDir = false)
*/
add_task(async function test_downloads_panel_after_file_picker_dialog() {
await testDownloadsPanelAfterDialog({
expectPanelToOpen: false,
preferredAction: Ci.nsIHandlerInfo.saveToDisk,
askWhereToSave: true,
});
ok(true, "Downloads panel suppressed after file picker dialog.");
});
/**
* Verify that the downloads panel doesn't open if the download opened both
* dialogs (e.g. default action = always ask AND useDownloadDir = false)
*/
add_task(async function test_downloads_panel_after_both_dialogs() {
await testDownloadsPanelAfterDialog({
expectPanelToOpen: false,
preferredAction: Ci.nsIHandlerInfo.alwaysAsk,
askWhereToSave: true,
});
ok(true, "Downloads panel suppressed after UCT and file picker dialogs.");
});