Bug 1639069 - Add download context menu items to 'Use' and 'Always use' the system viewer to open the download. r=jaws

Differential Revision: https://phabricator.services.mozilla.com/D79396
This commit is contained in:
Sam Foster 2020-06-25 22:19:37 +00:00
parent 1c447df70b
commit 394b51b9e3
13 changed files with 389 additions and 8 deletions

View file

@ -359,6 +359,13 @@ pref("browser.download.animateNotifications", true);
// This records whether or not the panel has been shown at least once. // This records whether or not the panel has been shown at least once.
pref("browser.download.panel.shown", false); pref("browser.download.panel.shown", false);
// This records whether or not to show the 'Open in system viewer' context menu item when appropriate
pref("browser.download.openInSystemViewerContextMenuItem", true);
// This records whether or not to show the 'Always open...' context menu item when appropriate
pref("browser.download.alwaysOpenInSystemViewerContextMenuItem", true);
// This controls whether the button is automatically shown/hidden depending // This controls whether the button is automatically shown/hidden depending
// on whether there are downloads to show. // on whether there are downloads to show.
pref("browser.download.autohideButton", true); pref("browser.download.autohideButton", true);

View file

@ -229,6 +229,8 @@ var PrefObserver = {
PrefObserver.register({ PrefObserver.register({
// prefName: defaultValue // prefName: defaultValue
animateNotifications: true, animateNotifications: true,
openInSystemViewerContextMenuItem: true,
alwaysOpenInSystemViewerContextMenuItem: true,
}); });
// DownloadsCommon // DownloadsCommon
@ -296,6 +298,20 @@ var DownloadsCommon = {
return PrefObserver.animateNotifications; return PrefObserver.animateNotifications;
}, },
/**
* Indicates whether or not to show the 'Open in system viewer' context menu item when appropriate
*/
get openInSystemViewerItemEnabled() {
return PrefObserver.openInSystemViewerContextMenuItem;
},
/**
* Indicates whether or not to show the 'Always open...' context menu item when appropriate
*/
get alwaysOpenInSystemViewerItemEnabled() {
return PrefObserver.alwaysOpenInSystemViewerContextMenuItem;
},
/** /**
* Get access to one of the DownloadsData, PrivateDownloadsData, or * Get access to one of the DownloadsData, PrivateDownloadsData, or
* HistoryDownloadsData objects, depending on the privacy status of the * HistoryDownloadsData objects, depending on the privacy status of the
@ -610,6 +626,9 @@ var DownloadsCommon = {
* @param options.openWhere * @param options.openWhere
* Optional string indicating how to handle opening a download target file URI. * Optional string indicating how to handle opening a download target file URI.
* One of "window", "tab", "tabshifted". * One of "window", "tab", "tabshifted".
* @param options.useSystemDefault
* Optional value indicating how to handle launching this download,
* this call only. Will override the associated mimeInfo.preferredAction
* @return {Promise} * @return {Promise}
* @resolves When the instruction to launch the file has been * @resolves When the instruction to launch the file has been
* successfully given to the operating system or handled internally * successfully given to the operating system or handled internally

View file

@ -297,6 +297,11 @@ class DownloadsSubview extends DownloadsViewUI.BaseView {
while (!button._shell) { while (!button._shell) {
button = button.parentNode; button = button.parentNode;
} }
let download = button._shell.download;
let { preferredAction, useSystemDefault } = DownloadsCommon.getMimeInfo(
download
);
menu.setAttribute("state", button.getAttribute("state")); menu.setAttribute("state", button.getAttribute("state"));
if (button.hasAttribute("exists")) { if (button.hasAttribute("exists")) {
menu.setAttribute("exists", button.getAttribute("exists")); menu.setAttribute("exists", button.getAttribute("exists"));
@ -307,6 +312,37 @@ class DownloadsSubview extends DownloadsViewUI.BaseView {
"temporary-block", "temporary-block",
button.classList.contains("temporary-block") button.classList.contains("temporary-block")
); );
// menu items are conditionally displayed via CSS based on an is-pdf attribute
DownloadsCommon.log(
"DownloadsSubview, updateContextMenu, download is pdf? ",
download.target.path,
button.hasAttribute("is-pdf")
);
if (button.hasAttribute("is-pdf")) {
menu.setAttribute("is-pdf", "true");
let alwaysUseSystemViewerItem = menu.querySelector(
".downloadAlwaysUseSystemDefaultMenuItem"
);
if (preferredAction === useSystemDefault) {
alwaysUseSystemViewerItem.setAttribute("checked", "true");
} else {
alwaysUseSystemViewerItem.removeAttribute("checked");
}
alwaysUseSystemViewerItem.toggleAttribute(
"enabled",
DownloadsCommon.alwaysOpenInSystemViewerItemEnabled
);
let useSystemViewerItem = menu.querySelector(
".downloadUseSystemDefaultMenuItem"
);
useSystemViewerItem.toggleAttribute(
"enabled",
DownloadsCommon.openInSystemViewerItemEnabled
);
} else {
menu.removeAttribute("is-pdf");
}
for (let menuitem of menu.getElementsByTagName("menuitem")) { for (let menuitem of menu.getElementsByTagName("menuitem")) {
let command = menuitem.getAttribute("command"); let command = menuitem.getAttribute("command");
if (!command) { if (!command) {

View file

@ -24,6 +24,13 @@ XPCOMUtils.defineLazyModuleGetters(this, {
OS: "resource://gre/modules/osfile.jsm", OS: "resource://gre/modules/osfile.jsm",
}); });
XPCOMUtils.defineLazyServiceGetter(
this,
"handlerSvc",
"@mozilla.org/uriloader/handler-service;1",
"nsIHandlerService"
);
const HTML_NS = "http://www.w3.org/1999/xhtml"; const HTML_NS = "http://www.w3.org/1999/xhtml";
var gDownloadElementButtons = { var gDownloadElementButtons = {
@ -457,9 +464,21 @@ DownloadsViewUI.DownloadElementShell.prototype = {
// on other properties. The order in which we check the properties of the // on other properties. The order in which we check the properties of the
// Download object is the same used by stateOfDownload. // Download object is the same used by stateOfDownload.
if (this.download.succeeded) { if (this.download.succeeded) {
DownloadsCommon.log(
"_updateStateInner, target exists? ",
this.download.target.path,
this.download.target.exists
);
if (this.download.target.exists) { if (this.download.target.exists) {
// This is a completed download, and the target file still exists. // This is a completed download, and the target file still exists.
this.element.setAttribute("exists", "true"); this.element.setAttribute("exists", "true");
const isPDF = DownloadsCommon.isFileOfType(
this.download,
"application/pdf"
);
this.element.toggleAttribute("is-pdf", isPDF);
let sizeWithUnits = DownloadsViewUI.getSizeWithUnits(this.download); let sizeWithUnits = DownloadsViewUI.getSizeWithUnits(this.download);
if (this.isPanel) { if (this.isPanel) {
// In the Downloads Panel, we show the file size after the state // In the Downloads Panel, we show the file size after the state
@ -728,6 +747,9 @@ DownloadsViewUI.DownloadElementShell.prototype = {
case "cmd_delete": case "cmd_delete":
// We don't want in-progress downloads to be removed accidentally. // We don't want in-progress downloads to be removed accidentally.
return this.download.stopped; return this.download.stopped;
case "downloadsCmd_openInSystemViewer":
case "downloadsCmd_alwaysOpenInSystemViewer":
return DownloadsCommon.isFileOfType(this.download, "application/pdf");
} }
return DownloadsViewUI.isCommandName(aCommand) && !!this[aCommand]; return DownloadsViewUI.isCommandName(aCommand) && !!this[aCommand];
}, },
@ -807,4 +829,39 @@ DownloadsViewUI.DownloadElementShell.prototype = {
cmd_delete() { cmd_delete() {
DownloadsCommon.deleteDownload(this.download).catch(Cu.reportError); DownloadsCommon.deleteDownload(this.download).catch(Cu.reportError);
}, },
downloadsCmd_openInSystemViewer() {
// For this interaction only, pass a flag to override the preferredAction for this
// mime-type and open using the system viewer
DownloadsCommon.openDownload(this.download, {
useSystemDefault: true,
}).catch(Cu.reportError);
},
downloadsCmd_alwaysOpenInSystemViewer() {
// this command toggles between setting preferredAction for this mime-type to open
// using the system viewer, or to open the file in browser.
const mimeInfo = DownloadsCommon.getMimeInfo(this.download);
if (mimeInfo.preferredAction !== mimeInfo.useSystemDefault) {
// User has selected to open this mime-type with the system viewer from now on
DownloadsCommon.log(
"downloadsCmd_alwaysOpenInSystemViewer command for download: ",
this.download,
"switching to use system default for " + mimeInfo.type
);
mimeInfo.preferredAction = mimeInfo.useSystemDefault;
mimeInfo.alwaysAskBeforeHandling = false;
} else {
DownloadsCommon.log(
"downloadsCmd_alwaysOpenInSystemViewer command for download: ",
this.download,
"currently uses system default, switching to handleInternally"
);
// User has selected to not open this mime-type with the system viewer
mimeInfo.preferredAction = mimeInfo.handleInternally;
mimeInfo.alwaysAskBeforeHandling = true;
}
handlerSvc.store(mimeInfo);
DownloadsCommon.openDownload(this.download).catch(Cu.reportError);
},
}; };

View file

@ -705,6 +705,9 @@ DownloadsPlacesView.prototype = {
// Set the state attribute so that only the appropriate items are displayed. // Set the state attribute so that only the appropriate items are displayed.
let contextMenu = document.getElementById("downloadsContextMenu"); let contextMenu = document.getElementById("downloadsContextMenu");
let download = element._shell.download; let download = element._shell.download;
let { preferredAction, useSystemDefault } = DownloadsCommon.getMimeInfo(
download
);
contextMenu.setAttribute( contextMenu.setAttribute(
"state", "state",
DownloadsCommon.stateOfDownload(download) DownloadsCommon.stateOfDownload(download)
@ -712,6 +715,31 @@ DownloadsPlacesView.prototype = {
contextMenu.setAttribute("exists", "true"); contextMenu.setAttribute("exists", "true");
contextMenu.classList.toggle("temporary-block", !!download.hasBlockedData); contextMenu.classList.toggle("temporary-block", !!download.hasBlockedData);
if (element.hasAttribute("is-pdf")) {
contextMenu.setAttribute("is-pdf", "true");
let alwaysUseSystemViewerItem = contextMenu.querySelector(
".downloadAlwaysUseSystemDefaultMenuItem"
);
if (preferredAction === useSystemDefault) {
alwaysUseSystemViewerItem.setAttribute("checked", "true");
} else {
alwaysUseSystemViewerItem.removeAttribute("checked");
}
alwaysUseSystemViewerItem.toggleAttribute(
"enabled",
DownloadsCommon.alwaysOpenInSystemViewerItemEnabled
);
let useSystemViewerItem = contextMenu.querySelector(
".downloadUseSystemDefaultMenuItem"
);
useSystemViewerItem.toggleAttribute(
"enabled",
DownloadsCommon.openInSystemViewerItemEnabled
);
} else {
contextMenu.removeAttribute("is-pdf");
}
if (!download.stopped) { if (!download.stopped) {
// The hasPartialData property of a download may change at any time after // The hasPartialData property of a download may change at any time after
// it has started, so ensure we update the related command now. // it has started, so ensure we update the related command now.

View file

@ -95,8 +95,14 @@
.download-state[state="1"]:not([exists]) .download-state[state="1"]:not([exists])
.downloadCommandsSeparator, .downloadCommandsSeparator,
.download-state[state="8"]:not(.temporary-block) .download-state[state="8"]:not(.temporary-block)
.downloadCommandsSeparator .downloadCommandsSeparator,
/* the system-viewer context menu items are only shown for certain mime-types
and can be individually enabled via prefs */
.download-state:not([is-pdf]) .downloadUseSystemDefaultMenuItem,
.download-state .downloadUseSystemDefaultMenuItem:not([enabled]),
.download-state .downloadAlwaysUseSystemDefaultMenuItem:not([enabled]),
.download-state:not([is-pdf]) .downloadAlwaysUseSystemDefaultMenuItem
{ {
display: none; display: none;
} }

View file

@ -937,6 +937,11 @@ var DownloadsView = {
DownloadsViewController.updateCommands(); DownloadsViewController.updateCommands();
let download = element._shell.download;
let { preferredAction, useSystemDefault } = DownloadsCommon.getMimeInfo(
download
);
// Set the state attribute so that only the appropriate items are displayed. // Set the state attribute so that only the appropriate items are displayed.
let contextMenu = document.getElementById("downloadsContextMenu"); let contextMenu = document.getElementById("downloadsContextMenu");
contextMenu.setAttribute("state", element.getAttribute("state")); contextMenu.setAttribute("state", element.getAttribute("state"));
@ -949,6 +954,30 @@ var DownloadsView = {
"temporary-block", "temporary-block",
element.classList.contains("temporary-block") element.classList.contains("temporary-block")
); );
if (element.hasAttribute("is-pdf")) {
contextMenu.setAttribute("is-pdf", "true");
let alwaysUseSystemViewerItem = contextMenu.querySelector(
".downloadAlwaysUseSystemDefaultMenuItem"
);
if (preferredAction === useSystemDefault) {
alwaysUseSystemViewerItem.setAttribute("checked", "true");
} else {
alwaysUseSystemViewerItem.removeAttribute("checked");
}
alwaysUseSystemViewerItem.toggleAttribute(
"enabled",
DownloadsCommon.alwaysOpenInSystemViewerItemEnabled
);
let useSystemViewerItem = contextMenu.querySelector(
".downloadUseSystemDefaultMenuItem"
);
useSystemViewerItem.toggleAttribute(
"enabled",
DownloadsCommon.openInSystemViewerItemEnabled
);
} else {
contextMenu.removeAttribute("is-pdf");
}
}, },
onDownloadDragStart(aEvent) { onDownloadDragStart(aEvent) {
@ -1093,6 +1122,22 @@ class DownloadsViewItem extends DownloadsViewUI.DownloadElementShell {
DownloadsPanel.hidePanel(); DownloadsPanel.hidePanel();
} }
downloadsCmd_openInSystemViewer() {
super.downloadsCmd_openInSystemViewer();
// We explicitly close the panel here to give the user the feedback that
// their click has been received, and we're handling the action.
DownloadsPanel.hidePanel();
}
downloadsCmd_alwaysOpenInSystemViewer() {
super.downloadsCmd_alwaysOpenInSystemViewer();
// We explicitly close the panel here to give the user the feedback that
// their click has been received, and we're handling the action.
DownloadsPanel.hidePanel();
}
downloadsCmd_show() { downloadsCmd_show() {
let file = new FileUtils.File(this.download.target.path); let file = new FileUtils.File(this.download.target.path);
DownloadsCommon.showDownloadedFile(file); DownloadsCommon.showDownloadedFile(file);

View file

@ -22,4 +22,6 @@
<command id="downloadsCmd_retry"/> <command id="downloadsCmd_retry"/>
<command id="downloadsCmd_openReferrer"/> <command id="downloadsCmd_openReferrer"/>
<command id="downloadsCmd_clearDownloads"/> <command id="downloadsCmd_clearDownloads"/>
<command id="downloadsCmd_openInSystemViewer"/>
<command id="downloadsCmd_alwaysOpenInSystemViewer"/>
</commandset> </commandset>

View file

@ -17,6 +17,13 @@
<menuitem command="downloadsCmd_unblock" <menuitem command="downloadsCmd_unblock"
class="downloadUnblockMenuItem" class="downloadUnblockMenuItem"
data-l10n-id="downloads-cmd-unblock"/> data-l10n-id="downloads-cmd-unblock"/>
<menuitem command="downloadsCmd_openInSystemViewer"
class="downloadUseSystemDefaultMenuItem"
data-l10n-id="downloads-cmd-use-system-default"/>
<menuitem command="downloadsCmd_alwaysOpenInSystemViewer"
type="checkbox"
class="downloadAlwaysUseSystemDefaultMenuItem"
data-l10n-id="downloads-cmd-always-use-system-default"/>
<menuitem command="downloadsCmd_show" <menuitem command="downloadsCmd_show"
class="downloadShowMenuItem" class="downloadShowMenuItem"
#ifdef XP_MACOSX #ifdef XP_MACOSX

View file

@ -38,6 +38,10 @@
oncommand="goDoCommand('downloadsCmd_copyLocation')"/> oncommand="goDoCommand('downloadsCmd_copyLocation')"/>
<command id="downloadsCmd_clearList" <command id="downloadsCmd_clearList"
oncommand="goDoCommand('downloadsCmd_clearList')"/> oncommand="goDoCommand('downloadsCmd_clearList')"/>
<command id="downloadsCmd_openInSystemViewer"
oncommand="goDoCommand('downloadsCmd_openInSystemViewer')"/>
<command id="downloadsCmd_alwaysOpenInSystemViewer"
oncommand="goDoCommand('downloadsCmd_alwaysOpenInSystemViewer')"/>
</commandset> </commandset>
<!-- The panel has level="top" to ensure that it is never hidden by the <!-- The panel has level="top" to ensure that it is never hidden by the
@ -75,6 +79,13 @@
<menuitem command="downloadsCmd_unblock" <menuitem command="downloadsCmd_unblock"
class="downloadUnblockMenuItem" class="downloadUnblockMenuItem"
data-l10n-id="downloads-cmd-unblock"/> data-l10n-id="downloads-cmd-unblock"/>
<menuitem command="downloadsCmd_openInSystemViewer"
class="downloadUseSystemDefaultMenuItem"
data-l10n-id="downloads-cmd-use-system-default"/>
<menuitem command="downloadsCmd_alwaysOpenInSystemViewer"
type="checkbox"
class="downloadAlwaysUseSystemDefaultMenuItem"
data-l10n-id="downloads-cmd-always-use-system-default"/>
<menuitem command="downloadsCmd_show" <menuitem command="downloadsCmd_show"
class="downloadShowMenuItem" class="downloadShowMenuItem"
#ifdef XP_MACOSX #ifdef XP_MACOSX

View file

@ -29,6 +29,26 @@ const TestCases = [
tabSelected: true, tabSelected: true,
}, },
}, },
{
name: "Download panel, system viewer menu items prefd off",
whichUI: "downloadPanel",
itemSelector: "#downloadsListBox richlistitem .downloadMainArea",
async userEvents(itemTarget, win) {
EventUtils.synthesizeMouseAtCenter(itemTarget, {}, win);
},
prefs: [
["browser.download.openInSystemViewerContextMenuItem", false],
["browser.download.alwaysOpenInSystemViewerContextMenuItem", false],
],
expected: {
downloadCount: 1,
newWindow: false,
opensTab: true,
tabSelected: true,
useSystemMenuItemDisabled: true,
alwaysMenuItemDisabled: true,
},
},
{ {
name: "Download panel, open from keyboard", name: "Download panel, open from keyboard",
whichUI: "downloadPanel", whichUI: "downloadPanel",
@ -109,6 +129,26 @@ const TestCases = [
tabSelected: true, tabSelected: true,
}, },
}, },
{
name: "Library all downloads dialog, system viewer menu items prefd off",
whichUI: "allDownloads",
async userEvents(itemTarget, win) {
// double click
await triggerDblclickOn(itemTarget, {}, win);
},
prefs: [
["browser.download.openInSystemViewerContextMenuItem", false],
["browser.download.alwaysOpenInSystemViewerContextMenuItem", false],
],
expected: {
downloadCount: 1,
newWindow: false,
opensTab: true,
tabSelected: true,
useSystemMenuItemDisabled: true,
alwaysMenuItemDisabled: true,
},
},
{ {
name: "Library all downloads dialog, open from keyboard", name: "Library all downloads dialog, open from keyboard",
whichUI: "allDownloads", whichUI: "allDownloads",
@ -189,6 +229,28 @@ const TestCases = [
tabSelected: true, tabSelected: true,
}, },
}, },
{
name: "about:downloads, system viewer menu items prefd off",
whichUI: "aboutDownloads",
itemSelector: "#downloadsRichListBox richlistitem .downloadContainer",
async userEvents(itemSelector, win) {
let browser = win.gBrowser.selectedBrowser;
is(browser.currentURI.spec, "about:downloads");
await contentTriggerDblclickOn(itemSelector, {}, browser);
},
prefs: [
["browser.download.openInSystemViewerContextMenuItem", false],
["browser.download.alwaysOpenInSystemViewerContextMenuItem", false],
],
expected: {
downloadCount: 1,
newWindow: false,
opensTab: true,
tabSelected: true,
useSystemMenuItemDisabled: true,
alwaysMenuItemDisabled: true,
},
},
{ {
name: "about:downloads, open in new window", name: "about:downloads, open in new window",
whichUI: "aboutDownloads", whichUI: "aboutDownloads",
@ -306,6 +368,66 @@ function contentTriggerDblclickOn(selector, eventModifiers = {}, browser) {
); );
} }
async function openContextMenu(itemElement, win = window) {
let popupShownPromise = BrowserTestUtils.waitForEvent(
itemElement.ownerDocument,
"popupshown"
);
EventUtils.synthesizeMouseAtCenter(
itemElement,
{
type: "contextmenu",
button: 2,
},
win
);
let { target } = await popupShownPromise;
return target;
}
async function verifyContextMenu(contextMenu, expected = {}) {
info("verifyContextMenu with expected: " + JSON.stringify(expected, null, 2));
let alwaysMenuItem = contextMenu.querySelector(
".downloadAlwaysUseSystemDefaultMenuItem"
);
let useSystemMenuItem = contextMenu.querySelector(
".downloadUseSystemDefaultMenuItem"
);
await TestUtils.waitForCondition(
() => BrowserTestUtils.is_visible(contextMenu),
"The context menu is visible"
);
await TestUtils.waitForTick();
is(
BrowserTestUtils.is_hidden(useSystemMenuItem),
expected.useSystemMenuItemDisabled,
`The 'Use system viewer' menu item was ${
expected.useSystemMenuItemDisabled ? "hidden" : "visible"
}`
);
is(
BrowserTestUtils.is_hidden(alwaysMenuItem),
expected.alwaysMenuItemDisabled,
`The 'Use system viewer' menu item was ${
expected.alwaysMenuItemDisabled ? "hidden" : "visible"
}`
);
if (!expected.useSystemMenuItemDisabled && expected.alwaysChecked) {
is(
alwaysMenuItem.getAttribute("checked"),
"true",
"The 'Always...' menu item is checked"
);
} else if (!expected.useSystemMenuItemDisabled) {
ok(
!alwaysMenuItem.hasAttribute("checked"),
"The 'Always...' menu item not checked"
);
}
}
async function createDownloadedFile(pathname, contents) { async function createDownloadedFile(pathname, contents) {
let encoder = new TextEncoder(); let encoder = new TextEncoder();
let file = new FileUtils.File(pathname); let file = new FileUtils.File(pathname);
@ -352,7 +474,7 @@ async function addPDFDownload(itemData) {
return download; return download;
} }
async function testSetup(testData = {}) { async function testSetup() {
// remove download files, empty out collections // remove download files, empty out collections
let downloadList = await Downloads.getList(Downloads.ALL); let downloadList = await Downloads.getList(Downloads.ALL);
let downloadCount = (await downloadList.getAll()).length; let downloadCount = (await downloadList.getAll()).length;
@ -379,6 +501,7 @@ async function testOpenPDFPreview({
whichUI, whichUI,
itemSelector, itemSelector,
expected, expected,
prefs = [],
userEvents, userEvents,
isPrivate, isPrivate,
}) { }) {
@ -386,6 +509,11 @@ async function testOpenPDFPreview({
// Wait for focus first // Wait for focus first
await promiseFocus(); await promiseFocus();
await testSetup(); await testSetup();
if (prefs.length) {
await SpecialPowers.pushPrefEnv({
set: prefs,
});
}
// Populate downloads database with the data required by this test. // Populate downloads database with the data required by this test.
info("Adding download objects"); info("Adding download objects");
@ -454,12 +582,16 @@ async function testOpenPDFPreview({
} }
let itemTarget; let itemTarget;
let contextMenu;
switch (whichUI) { switch (whichUI) {
case "downloadPanel": case "downloadPanel":
info("Opening download panel"); info("Opening download panel");
await openDownloadPanel(expected.downloadCount); await openDownloadPanel(expected.downloadCount);
info("/Opening download panel"); info("/Opening download panel");
itemTarget = document.querySelector(itemSelector); itemTarget = document.querySelector(itemSelector);
contextMenu = uiWindow.document.querySelector("#downloadsContextMenu");
break; break;
case "allDownloads": case "allDownloads":
// we'll be interacting with the library dialog // we'll be interacting with the library dialog
@ -467,11 +599,17 @@ async function testOpenPDFPreview({
let listbox = uiWindow.document.getElementById("downloadsRichListBox"); let listbox = uiWindow.document.getElementById("downloadsRichListBox");
ok(listbox, "download list box present"); ok(listbox, "download list box present");
// wait for the expected number of items in the view // wait for the expected number of items in the view,
await TestUtils.waitForCondition( // and for the first item to be visible && clickable
() => listbox.itemChildren.length == expected.downloadCount await TestUtils.waitForCondition(() => {
); return (
listbox.itemChildren.length == expected.downloadCount &&
BrowserTestUtils.is_visible(listbox.itemChildren[0])
);
});
itemTarget = listbox.itemChildren[0]; itemTarget = listbox.itemChildren[0];
contextMenu = uiWindow.document.querySelector("#downloadsContextMenu");
break; break;
case "aboutDownloads": case "aboutDownloads":
info("Preparing about:downloads browser window"); info("Preparing about:downloads browser window");
@ -537,6 +675,21 @@ async function testOpenPDFPreview({
break; break;
} }
if (contextMenu) {
info("trigger the contextmenu");
await openContextMenu(itemTarget || itemSelector, uiWindow);
info("context menu should be open, verify its menu items");
let expectedValues = {
useSystemMenuItemDisabled: false,
alwaysMenuItemDisabled: false,
...expected,
};
await verifyContextMenu(contextMenu, expectedValues);
contextMenu.hidePopup();
} else {
todo(contextMenu, "No context menu checks for test: " + name);
}
info("Executing user events"); info("Executing user events");
await userEvents(itemTarget || itemSelector, uiWindow); await userEvents(itemTarget || itemSelector, uiWindow);
@ -594,6 +747,9 @@ async function testOpenPDFPreview({
await lastPBContextExitedPromise; await lastPBContextExitedPromise;
}); });
await downloadList.removeFinished(); await downloadList.removeFinished();
if (prefs.length) {
await SpecialPowers.popPrefEnv();
}
} }
// register the tests // register the tests

View file

@ -726,6 +726,9 @@ Download.prototype = {
* @param options.openWhere Optional string indicating how to open when handling * @param options.openWhere Optional string indicating how to open when handling
* download by opening the target file URI. * download by opening the target file URI.
* One of "window", "tab", "tabshifted" * One of "window", "tab", "tabshifted"
* @param options.useSystemDefault
* Optional value indicating how to handle launching this download,
* this time only. Will override the associated mimeInfo.preferredAction
* @return {Promise} * @return {Promise}
* @resolves When the instruction to launch the file has been * @resolves When the instruction to launch the file has been
* successfully given to the operating system. Note that * successfully given to the operating system. Note that

View file

@ -712,6 +712,9 @@ var DownloadIntegration = {
* @param options.openWhere Optional string indicating how to open when handling * @param options.openWhere Optional string indicating how to open when handling
* download by opening the target file URI. * download by opening the target file URI.
* One of "window", "tab", "tabshifted" * One of "window", "tab", "tabshifted"
* @param options.useSystemDefault
* Optional value indicating how to handle launching this download,
* this time only. Will override the associated mimeInfo.preferredAction
* *
* @return {Promise} * @return {Promise}
* @resolves When the instruction to launch the file has been * @resolves When the instruction to launch the file has been
@ -721,7 +724,7 @@ var DownloadIntegration = {
* @rejects JavaScript exception if there was an error trying to launch * @rejects JavaScript exception if there was an error trying to launch
* the file. * the file.
*/ */
async launchDownload(aDownload, { openWhere }) { async launchDownload(aDownload, { openWhere, useSystemDefault = null }) {
let file = new FileUtils.File(aDownload.target.path); let file = new FileUtils.File(aDownload.target.path);
// In case of a double extension, like ".tar.gz", we only // In case of a double extension, like ".tar.gz", we only
@ -794,7 +797,8 @@ var DownloadIntegration = {
const PDF_CONTENT_TYPE = "application/pdf"; const PDF_CONTENT_TYPE = "application/pdf";
if ( if (
aDownload.handleInternally || aDownload.handleInternally ||
(mimeInfo && (!useSystemDefault && // No explicit instruction was passed to launch this download using the default system viewer.
mimeInfo &&
(mimeInfo.type == PDF_CONTENT_TYPE || (mimeInfo.type == PDF_CONTENT_TYPE ||
fileExtension?.toLowerCase() == "pdf") && fileExtension?.toLowerCase() == "pdf") &&
!mimeInfo.alwaysAskBeforeHandling && !mimeInfo.alwaysAskBeforeHandling &&