1202 lines
43 KiB
JavaScript
1202 lines
43 KiB
JavaScript
/* 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/. */
|
|
|
|
/*
|
|
* This module is imported by code that uses the "download.xml" binding, and
|
|
* provides prototypes for objects that handle input and display information.
|
|
*/
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
|
|
DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
|
|
Downloads: "resource://gre/modules/Downloads.sys.mjs",
|
|
DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs",
|
|
FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
|
|
UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
|
|
});
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(
|
|
lazy,
|
|
"handlerSvc",
|
|
"@mozilla.org/uriloader/handler-service;1",
|
|
"nsIHandlerService"
|
|
);
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(
|
|
lazy,
|
|
"gReputationService",
|
|
"@mozilla.org/reputationservice/application-reputation-service;1",
|
|
Ci.nsIApplicationReputationService
|
|
);
|
|
|
|
import { Integration } from "resource://gre/modules/Integration.sys.mjs";
|
|
|
|
Integration.downloads.defineESModuleGetter(
|
|
lazy,
|
|
"DownloadIntegration",
|
|
"resource://gre/modules/DownloadIntegration.sys.mjs"
|
|
);
|
|
|
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
|
|
|
var gDownloadElementButtons = {
|
|
cancel: {
|
|
commandName: "downloadsCmd_cancel",
|
|
l10nId: "downloads-cmd-cancel",
|
|
descriptionL10nId: "downloads-cancel-download",
|
|
panelL10nId: "downloads-cmd-cancel-panel",
|
|
iconClass: "downloadIconCancel",
|
|
},
|
|
retry: {
|
|
commandName: "downloadsCmd_retry",
|
|
l10nId: "downloads-cmd-retry",
|
|
descriptionL10nId: "downloads-retry-download",
|
|
panelL10nId: "downloads-cmd-retry-panel",
|
|
iconClass: "downloadIconRetry",
|
|
},
|
|
show: {
|
|
commandName: "downloadsCmd_show",
|
|
l10nId: "downloads-cmd-show-button-2",
|
|
descriptionL10nId: "downloads-cmd-show-description-2",
|
|
panelL10nId: "downloads-cmd-show-panel-2",
|
|
iconClass: "downloadIconShow",
|
|
},
|
|
subviewOpenOrRemoveFile: {
|
|
commandName: "downloadsCmd_showBlockedInfo",
|
|
l10nId: "downloads-cmd-choose-open",
|
|
descriptionL10nId: "downloads-show-more-information",
|
|
panelL10nId: "downloads-cmd-choose-open-panel",
|
|
iconClass: "downloadIconSubviewArrow",
|
|
},
|
|
askOpenOrRemoveFile: {
|
|
commandName: "downloadsCmd_chooseOpen",
|
|
l10nId: "downloads-cmd-choose-open",
|
|
panelL10nId: "downloads-cmd-choose-open-panel",
|
|
iconClass: "downloadIconShow",
|
|
},
|
|
askRemoveFileOrAllow: {
|
|
commandName: "downloadsCmd_chooseUnblock",
|
|
l10nId: "downloads-cmd-choose-unblock",
|
|
panelL10nId: "downloads-cmd-choose-unblock-panel",
|
|
iconClass: "downloadIconShow",
|
|
},
|
|
removeFile: {
|
|
commandName: "downloadsCmd_confirmBlock",
|
|
l10nId: "downloads-cmd-remove-file",
|
|
panelL10nId: "downloads-cmd-remove-file-panel",
|
|
iconClass: "downloadIconCancel",
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Associates each document with a pre-built DOM fragment representing the
|
|
* download list item. This is then cloned to create each individual list item.
|
|
* This is stored on the document to prevent leaks that would occur if a single
|
|
* instance created by one document's DOMParser was stored globally.
|
|
*/
|
|
var gDownloadListItemFragments = new WeakMap();
|
|
|
|
export var DownloadsViewUI = {
|
|
/**
|
|
* Returns true if the given string is the name of a command that can be
|
|
* handled by the Downloads user interface, including standard commands.
|
|
*/
|
|
isCommandName(name) {
|
|
return name.startsWith("cmd_") || name.startsWith("downloadsCmd_");
|
|
},
|
|
|
|
/**
|
|
* Get source url of the download without'http' or'https' prefix.
|
|
*/
|
|
getStrippedUrl(download) {
|
|
return lazy.UrlbarUtils.stripPrefixAndTrim(download?.source?.url, {
|
|
stripHttp: true,
|
|
stripHttps: true,
|
|
})[0];
|
|
},
|
|
|
|
/**
|
|
* Returns the user-facing label for the given Download object. This is
|
|
* normally the leaf name of the download target file. In case this is a very
|
|
* old history download for which the target file is unknown, the download
|
|
* source URI is displayed.
|
|
*/
|
|
getDisplayName(download) {
|
|
if (
|
|
download.error?.reputationCheckVerdict ==
|
|
lazy.Downloads.Error.BLOCK_VERDICT_DOWNLOAD_SPAM
|
|
) {
|
|
let l10n = {
|
|
id: "downloads-blocked-from-url",
|
|
args: { url: DownloadsViewUI.getStrippedUrl(download) },
|
|
};
|
|
return { l10n };
|
|
}
|
|
return download.target.path
|
|
? PathUtils.filename(download.target.path)
|
|
: download.source.url;
|
|
},
|
|
|
|
/**
|
|
* Given a Download object, returns a string representing its file size with
|
|
* an appropriate measurement unit, for example "1.5 MB", or an empty string
|
|
* if the size is unknown.
|
|
*/
|
|
getSizeWithUnits(download) {
|
|
if (download.target.size === undefined) {
|
|
return "";
|
|
}
|
|
|
|
let [size, unit] = lazy.DownloadUtils.convertByteUnits(
|
|
download.target.size
|
|
);
|
|
return lazy.DownloadsCommon.strings.sizeWithUnits(size, unit);
|
|
},
|
|
|
|
/**
|
|
* Given a context menu and a download element on which it is invoked,
|
|
* update items in the context menu to reflect available options for
|
|
* that download element.
|
|
*/
|
|
updateContextMenuForElement(contextMenu, element) {
|
|
// Get the state and ensure only the appropriate items are displayed.
|
|
let state = parseInt(element.getAttribute("state"), 10);
|
|
|
|
const document = contextMenu.ownerDocument;
|
|
|
|
const {
|
|
DOWNLOAD_NOTSTARTED,
|
|
DOWNLOAD_DOWNLOADING,
|
|
DOWNLOAD_FINISHED,
|
|
DOWNLOAD_FAILED,
|
|
DOWNLOAD_CANCELED,
|
|
DOWNLOAD_PAUSED,
|
|
DOWNLOAD_BLOCKED_PARENTAL,
|
|
DOWNLOAD_DIRTY,
|
|
DOWNLOAD_BLOCKED_POLICY,
|
|
} = lazy.DownloadsCommon;
|
|
|
|
contextMenu.querySelector(".downloadPauseMenuItem").hidden =
|
|
state != DOWNLOAD_DOWNLOADING;
|
|
|
|
contextMenu.querySelector(".downloadResumeMenuItem").hidden =
|
|
state != DOWNLOAD_PAUSED;
|
|
|
|
// Only show "unblock" for blocked (dirty) items that have not been
|
|
// confirmed and have temporary data:
|
|
contextMenu.querySelector(".downloadUnblockMenuItem").hidden =
|
|
state != DOWNLOAD_DIRTY || !element.classList.contains("temporary-block");
|
|
|
|
// Can only remove finished/failed/canceled/blocked downloads.
|
|
contextMenu.querySelector(".downloadRemoveFromHistoryMenuItem").hidden = ![
|
|
DOWNLOAD_FINISHED,
|
|
DOWNLOAD_FAILED,
|
|
DOWNLOAD_CANCELED,
|
|
DOWNLOAD_BLOCKED_PARENTAL,
|
|
DOWNLOAD_DIRTY,
|
|
DOWNLOAD_BLOCKED_POLICY,
|
|
].includes(state);
|
|
|
|
// Can reveal downloads with data on the file system using the relevant OS
|
|
// tool (Explorer, Finder, appropriate Linux file system viewer):
|
|
contextMenu.querySelector(".downloadShowMenuItem").hidden =
|
|
![
|
|
DOWNLOAD_NOTSTARTED,
|
|
DOWNLOAD_DOWNLOADING,
|
|
DOWNLOAD_FINISHED,
|
|
DOWNLOAD_PAUSED,
|
|
].includes(state) ||
|
|
(state == DOWNLOAD_FINISHED && !element.hasAttribute("exists"));
|
|
|
|
// Show the separator if we're showing either unblock or reveal menu items.
|
|
contextMenu.querySelector(".downloadCommandsSeparator").hidden =
|
|
contextMenu.querySelector(".downloadUnblockMenuItem").hidden &&
|
|
contextMenu.querySelector(".downloadShowMenuItem").hidden;
|
|
|
|
let download = element._shell.download;
|
|
let mimeInfo = lazy.DownloadsCommon.getMimeInfo(download);
|
|
let { preferredAction, useSystemDefault, defaultDescription } = mimeInfo
|
|
? mimeInfo
|
|
: {};
|
|
|
|
// Hide the "Delete" item if there's no file data to delete.
|
|
contextMenu.querySelector(".downloadDeleteFileMenuItem").hidden =
|
|
download.deleted ||
|
|
!(download.target?.exists || download.target?.partFileExists);
|
|
|
|
// Hide the "Go To Download Page" item if there's no referrer. Ideally the
|
|
// Downloads API will require a referrer (see bug 1723712) to create a
|
|
// download, but this fallback will ensure any failures aren't user facing.
|
|
contextMenu.querySelector(".downloadOpenReferrerMenuItem").hidden =
|
|
!download.source.referrerInfo?.originalReferrer;
|
|
|
|
// Hide the "use system viewer" and "always use system viewer" items
|
|
// if the feature is disabled or this download doesn't support it:
|
|
let useSystemViewerItem = contextMenu.querySelector(
|
|
".downloadUseSystemDefaultMenuItem"
|
|
);
|
|
let alwaysUseSystemViewerItem = contextMenu.querySelector(
|
|
".downloadAlwaysUseSystemDefaultMenuItem"
|
|
);
|
|
let canViewInternally = element.hasAttribute("viewable-internally");
|
|
useSystemViewerItem.hidden =
|
|
!lazy.DownloadsCommon.openInSystemViewerItemEnabled ||
|
|
!canViewInternally ||
|
|
!download.target?.exists;
|
|
|
|
alwaysUseSystemViewerItem.hidden =
|
|
!lazy.DownloadsCommon.alwaysOpenInSystemViewerItemEnabled ||
|
|
!canViewInternally;
|
|
|
|
// Set menuitem labels to display the system viewer's name. Stop the l10n
|
|
// mutation observer temporarily since we're going to synchronously
|
|
// translate the elements to avoid translation delay. See bug 1737951 & bug
|
|
// 1746748. This can be simplified when they're resolved.
|
|
try {
|
|
document.l10n.pauseObserving();
|
|
// Handler descriptions longer than 40 characters will be skipped to avoid
|
|
// unreasonably stretching the context menu.
|
|
if (defaultDescription && defaultDescription.length < 40) {
|
|
document.l10n.setAttributes(
|
|
useSystemViewerItem,
|
|
"downloads-cmd-use-system-default-named",
|
|
{ handler: defaultDescription }
|
|
);
|
|
document.l10n.setAttributes(
|
|
alwaysUseSystemViewerItem,
|
|
"downloads-cmd-always-use-system-default-named",
|
|
{ handler: defaultDescription }
|
|
);
|
|
} else {
|
|
// In the unlikely event that defaultDescription is somehow missing/invalid,
|
|
// fall back to the static "Open In System Viewer" label.
|
|
document.l10n.setAttributes(
|
|
useSystemViewerItem,
|
|
"downloads-cmd-use-system-default"
|
|
);
|
|
document.l10n.setAttributes(
|
|
alwaysUseSystemViewerItem,
|
|
"downloads-cmd-always-use-system-default"
|
|
);
|
|
}
|
|
} finally {
|
|
document.l10n.resumeObserving();
|
|
}
|
|
document.l10n.translateElements([
|
|
useSystemViewerItem,
|
|
alwaysUseSystemViewerItem,
|
|
]);
|
|
|
|
// If non default mime-type or cannot be opened internally, display
|
|
// "always open similar files" item instead so that users can add a new
|
|
// mimetype to about:preferences table and set to open with system default.
|
|
let alwaysOpenSimilarFilesItem = contextMenu.querySelector(
|
|
".downloadAlwaysOpenSimilarFilesMenuItem"
|
|
);
|
|
|
|
/**
|
|
* In HelperAppDlg.sys.mjs, we determine whether or not an "always open..." checkbox
|
|
* should appear in the unknownContentType window. Here, we use similar checks to
|
|
* determine if we should show the "always open similar files" context menu item.
|
|
*
|
|
* Note that we also read the content type using mimeInfo to detect better and available
|
|
* mime types, given a file extension. Some sites default to "application/octet-stream",
|
|
* further limiting what file types can be added to about:preferences, even for file types
|
|
* that are in fact capable of being handled with a default application.
|
|
*
|
|
* There are also cases where download.contentType is undefined (ex. when opening
|
|
* the context menu on a previously downloaded item via download history).
|
|
* Using mimeInfo ensures that content type exists and prevents intermittence.
|
|
*/
|
|
//
|
|
let filename = PathUtils.filename(download.target.path);
|
|
|
|
let isExemptExecutableExtension =
|
|
Services.policies.isExemptExecutableExtension(
|
|
download.source.originalUrl || download.source.url,
|
|
filename?.split(".").at(-1)
|
|
);
|
|
|
|
let shouldNotRememberChoice =
|
|
!mimeInfo?.type ||
|
|
mimeInfo.type === "application/octet-stream" ||
|
|
mimeInfo.type === "application/x-msdownload" ||
|
|
mimeInfo.type === "application/x-msdos-program" ||
|
|
(lazy.gReputationService.isExecutable(filename) &&
|
|
!isExemptExecutableExtension) ||
|
|
(mimeInfo.type === "text/plain" &&
|
|
lazy.gReputationService.isBinary(download.target.path));
|
|
|
|
alwaysOpenSimilarFilesItem.hidden =
|
|
canViewInternally ||
|
|
state !== DOWNLOAD_FINISHED ||
|
|
shouldNotRememberChoice;
|
|
|
|
// Update checkbox for "always open..." options.
|
|
if (preferredAction === useSystemDefault) {
|
|
alwaysUseSystemViewerItem.setAttribute("checked", "true");
|
|
alwaysOpenSimilarFilesItem.setAttribute("checked", "true");
|
|
} else {
|
|
alwaysUseSystemViewerItem.removeAttribute("checked");
|
|
alwaysOpenSimilarFilesItem.removeAttribute("checked");
|
|
}
|
|
},
|
|
};
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
DownloadsViewUI,
|
|
"clearHistoryOnDelete",
|
|
"browser.download.clearHistoryOnDelete",
|
|
0
|
|
);
|
|
|
|
DownloadsViewUI.BaseView = class {
|
|
canClearDownloads(nodeContainer) {
|
|
// Downloads can be cleared if there's at least one removable download in
|
|
// the list (either a history download or a completed session download).
|
|
// Because history downloads are always removable and are listed after the
|
|
// session downloads, check from bottom to top.
|
|
for (let elt = nodeContainer.lastChild; elt; elt = elt.previousSibling) {
|
|
// Stopped, paused, and failed downloads with partial data are removed.
|
|
let download = elt._shell.download;
|
|
if (download.stopped && !(download.canceled && download.hasPartialData)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A download element shell is responsible for handling the commands and the
|
|
* displayed data for a single element that uses the "download.xml" binding.
|
|
*
|
|
* The information to display is obtained through the associated Download object
|
|
* from the JavaScript API for downloads, and commands are executed using a
|
|
* combination of Download methods and DownloadsCommon.sys.mjs helper functions.
|
|
*
|
|
* Specialized versions of this shell must be defined, and they are required to
|
|
* implement the "download" property or getter. Currently these objects are the
|
|
* HistoryDownloadElementShell and the DownloadsViewItem for the panel. The
|
|
* history view may use a HistoryDownload object in place of a Download object.
|
|
*/
|
|
DownloadsViewUI.DownloadElementShell = function () {};
|
|
|
|
DownloadsViewUI.DownloadElementShell.prototype = {
|
|
/**
|
|
* The richlistitem for the download, initialized by the derived object.
|
|
*/
|
|
element: null,
|
|
|
|
/**
|
|
* Manages the "active" state of the shell. By default all the shells are
|
|
* inactive, thus their UI is not updated. They must be activated when
|
|
* entering the visible area.
|
|
*/
|
|
ensureActive() {
|
|
if (!this._active) {
|
|
this._active = true;
|
|
this.connect();
|
|
this.onChanged();
|
|
}
|
|
},
|
|
get active() {
|
|
return !!this._active;
|
|
},
|
|
|
|
connect() {
|
|
let document = this.element.ownerDocument;
|
|
let downloadListItemFragment = gDownloadListItemFragments.get(document);
|
|
// When changing the markup within the fragment, please ensure that
|
|
// the functions within DownloadsView still operate correctly.
|
|
if (!downloadListItemFragment) {
|
|
let MozXULElement = document.defaultView.MozXULElement;
|
|
downloadListItemFragment = MozXULElement.parseXULToFragment(`
|
|
<hbox class="downloadMainArea" flex="1" align="center">
|
|
<image class="downloadTypeIcon"/>
|
|
<vbox class="downloadContainer" flex="1" pack="center">
|
|
<description class="downloadTarget" crop="center"/>
|
|
<description class="downloadDetails downloadDetailsNormal"
|
|
crop="end"/>
|
|
<description class="downloadDetails downloadDetailsHover"
|
|
crop="end"/>
|
|
<description class="downloadDetails downloadDetailsButtonHover"
|
|
crop="end"/>
|
|
</vbox>
|
|
<image class="downloadBlockedBadge" />
|
|
</hbox>
|
|
<button class="downloadButton"/>
|
|
`);
|
|
gDownloadListItemFragments.set(document, downloadListItemFragment);
|
|
}
|
|
this.element.setAttribute("active", true);
|
|
this.element.setAttribute("orient", "horizontal");
|
|
this.element.addEventListener("click", ev => {
|
|
ev.target.ownerGlobal.DownloadsView.onDownloadClick(ev);
|
|
});
|
|
this.element.appendChild(
|
|
document.importNode(downloadListItemFragment, true)
|
|
);
|
|
let downloadButton = this.element.querySelector(".downloadButton");
|
|
downloadButton.addEventListener("command", function (event) {
|
|
event.target.ownerGlobal.DownloadsView.onDownloadButton(event);
|
|
});
|
|
for (let [propertyName, selector] of [
|
|
["_downloadTypeIcon", ".downloadTypeIcon"],
|
|
["_downloadTarget", ".downloadTarget"],
|
|
["_downloadDetailsNormal", ".downloadDetailsNormal"],
|
|
["_downloadDetailsHover", ".downloadDetailsHover"],
|
|
["_downloadDetailsButtonHover", ".downloadDetailsButtonHover"],
|
|
["_downloadButton", ".downloadButton"],
|
|
]) {
|
|
this[propertyName] = this.element.querySelector(selector);
|
|
}
|
|
|
|
// HTML elements can be created directly without using parseXULToFragment.
|
|
let progress = (this._downloadProgress = document.createElementNS(
|
|
HTML_NS,
|
|
"progress"
|
|
));
|
|
progress.className = "downloadProgress";
|
|
progress.setAttribute("max", "100");
|
|
this._downloadTarget.insertAdjacentElement("afterend", progress);
|
|
},
|
|
|
|
/**
|
|
* URI string for the file type icon displayed in the download element.
|
|
*/
|
|
get image() {
|
|
if (!this.download.target.path) {
|
|
// Old history downloads may not have a target path.
|
|
return "moz-icon://.unknown?size=32";
|
|
}
|
|
|
|
// When a download that was previously in progress finishes successfully, it
|
|
// means that the target file now exists and we can extract its specific
|
|
// icon, for example from a Windows executable. To ensure that the icon is
|
|
// reloaded, however, we must change the URI used by the XUL image element,
|
|
// for example by adding a query parameter. This only works if we add one of
|
|
// the parameters explicitly supported by the nsIMozIconURI interface.
|
|
return (
|
|
"moz-icon://" +
|
|
this.download.target.path +
|
|
"?size=32" +
|
|
(this.download.succeeded ? "&state=normal" : "")
|
|
);
|
|
},
|
|
|
|
get browserWindow() {
|
|
return lazy.BrowserWindowTracker.getTopWindow();
|
|
},
|
|
|
|
/**
|
|
* Updates the display name and icon.
|
|
*
|
|
* @param displayName
|
|
* This is usually the full file name of the download without the path.
|
|
* @param icon
|
|
* URL of the icon to load, generally from the "image" property.
|
|
*/
|
|
showDisplayNameAndIcon(displayName, icon) {
|
|
if (displayName.l10n) {
|
|
let document = this.element.ownerDocument;
|
|
document.l10n.setAttributes(
|
|
this._downloadTarget,
|
|
displayName.l10n.id,
|
|
displayName.l10n.args
|
|
);
|
|
} else {
|
|
this._downloadTarget.setAttribute("value", displayName);
|
|
this._downloadTarget.setAttribute("tooltiptext", displayName);
|
|
}
|
|
this._downloadTypeIcon.setAttribute("src", icon);
|
|
},
|
|
|
|
/**
|
|
* Updates the displayed progress bar.
|
|
*
|
|
* @param mode
|
|
* Either "normal" or "undetermined".
|
|
* @param value
|
|
* Percentage of the progress bar to display, from 0 to 100.
|
|
* @param paused
|
|
* True to display the progress bar style for paused downloads.
|
|
*/
|
|
showProgress(mode, value, paused) {
|
|
if (mode == "undetermined") {
|
|
this._downloadProgress.removeAttribute("value");
|
|
} else {
|
|
this._downloadProgress.setAttribute("value", value);
|
|
}
|
|
this._downloadProgress.toggleAttribute("paused", !!paused);
|
|
},
|
|
|
|
/**
|
|
* Updates the full status line.
|
|
*
|
|
* @param status
|
|
* Status line of the Downloads Panel or the Downloads View.
|
|
* @param hoverStatus
|
|
* Label to show in the Downloads Panel when the mouse pointer is over
|
|
* the main area of the item. If not specified, this will be the same
|
|
* as the status line. This is ignored in the Downloads View. Type is
|
|
* either l10n object or string literal.
|
|
*/
|
|
showStatus(status, hoverStatus = status) {
|
|
let document = this.element.ownerDocument;
|
|
if (status?.l10n) {
|
|
document.l10n.setAttributes(
|
|
this._downloadDetailsNormal,
|
|
status.l10n.id,
|
|
status.l10n.args
|
|
);
|
|
} else {
|
|
this._downloadDetailsNormal.removeAttribute("data-l10n-id");
|
|
this._downloadDetailsNormal.setAttribute("value", status);
|
|
this._downloadDetailsNormal.setAttribute("tooltiptext", status);
|
|
}
|
|
if (hoverStatus?.l10n) {
|
|
document.l10n.setAttributes(
|
|
this._downloadDetailsHover,
|
|
hoverStatus.l10n.id,
|
|
hoverStatus.l10n.args
|
|
);
|
|
} else {
|
|
this._downloadDetailsHover.removeAttribute("data-l10n-id");
|
|
this._downloadDetailsHover.setAttribute("value", hoverStatus);
|
|
this._downloadDetailsHover.setAttribute("tooltiptext", hoverStatus);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Updates the status line combining the given state label with other labels.
|
|
*
|
|
* @param stateLabel
|
|
* Label representing the state of the download, for example "Failed".
|
|
* In the Downloads Panel, this is the only text displayed when the
|
|
* the mouse pointer is not over the main area of the item. In the
|
|
* Downloads View, this label is combined with the host and date, for
|
|
* example "Failed - example.com - 1:45 PM".
|
|
* @param hoverStatus
|
|
* Label to show in the Downloads Panel when the mouse pointer is over
|
|
* the main area of the item. If not specified, this will be the
|
|
* state label combined with the host and date. This is ignored in the
|
|
* Downloads View. Type is either l10n object or string literal.
|
|
*/
|
|
showStatusWithDetails(stateLabel, hoverStatus) {
|
|
if (stateLabel.l10n) {
|
|
this.showStatus(stateLabel, hoverStatus);
|
|
return;
|
|
}
|
|
let [displayHost] = lazy.DownloadUtils.getURIHost(this.download.source.url);
|
|
let [displayDate] = lazy.DownloadUtils.getReadableDates(
|
|
new Date(this.download.endTime)
|
|
);
|
|
|
|
let firstPart = lazy.DownloadsCommon.strings.statusSeparator(
|
|
stateLabel,
|
|
displayHost
|
|
);
|
|
let fullStatus = lazy.DownloadsCommon.strings.statusSeparator(
|
|
firstPart,
|
|
displayDate
|
|
);
|
|
|
|
if (!this.isPanel) {
|
|
this.showStatus(fullStatus);
|
|
} else {
|
|
this.showStatus(stateLabel, hoverStatus || fullStatus);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Updates the main action button and makes it visible.
|
|
*
|
|
* @param type
|
|
* One of the presets defined in gDownloadElementButtons.
|
|
*/
|
|
showButton(type) {
|
|
let { commandName, l10nId, descriptionL10nId, panelL10nId, iconClass } =
|
|
gDownloadElementButtons[type];
|
|
|
|
this.buttonCommandName = commandName;
|
|
let stringId = this.isPanel ? panelL10nId : l10nId;
|
|
let document = this.element.ownerDocument;
|
|
document.l10n.setAttributes(this._downloadButton, stringId);
|
|
if (this.isPanel && descriptionL10nId) {
|
|
document.l10n.setAttributes(
|
|
this._downloadDetailsButtonHover,
|
|
descriptionL10nId
|
|
);
|
|
}
|
|
this._downloadButton.setAttribute("class", "downloadButton " + iconClass);
|
|
this._downloadButton.removeAttribute("hidden");
|
|
},
|
|
|
|
hideButton() {
|
|
this._downloadButton.hidden = true;
|
|
},
|
|
|
|
lastEstimatedSecondsLeft: Infinity,
|
|
|
|
/**
|
|
* This is called when a major state change occurs in the download, but is not
|
|
* called for every progress update in order to improve performance.
|
|
*/
|
|
_updateState() {
|
|
this.showDisplayNameAndIcon(
|
|
DownloadsViewUI.getDisplayName(this.download),
|
|
this.image
|
|
);
|
|
this.element.setAttribute(
|
|
"state",
|
|
lazy.DownloadsCommon.stateOfDownload(this.download)
|
|
);
|
|
|
|
if (!this.download.stopped) {
|
|
// When the download becomes in progress, we make all the major changes to
|
|
// the user interface here. The _updateStateInner function takes care of
|
|
// displaying the right button type for all other state changes.
|
|
this.showButton("cancel");
|
|
|
|
// If there was a verdict set but the download is running we can assume
|
|
// that the verdict has been overruled and can be removed.
|
|
this.element.removeAttribute("verdict");
|
|
}
|
|
|
|
// Since state changed, reset the time left estimation.
|
|
this.lastEstimatedSecondsLeft = Infinity;
|
|
|
|
this._updateStateInner();
|
|
},
|
|
|
|
/**
|
|
* This is called for all changes in the download, including progress updates.
|
|
* For major state changes, _updateState is called first, but several elements
|
|
* are still updated here. When the download is in progress, this function
|
|
* takes a faster path with less element updates to improve performance.
|
|
*/
|
|
_updateStateInner() {
|
|
let progressPaused = false;
|
|
|
|
this.element.classList.toggle("openWhenFinished", !this.download.stopped);
|
|
|
|
if (!this.download.stopped) {
|
|
// The download is in progress, so we don't change the button state
|
|
// because the _updateState function already did it. We still need to
|
|
// update all elements that may change during the download.
|
|
let totalBytes = this.download.hasProgress
|
|
? this.download.totalBytes
|
|
: -1;
|
|
let [status, newEstimatedSecondsLeft] =
|
|
lazy.DownloadUtils.getDownloadStatus(
|
|
this.download.currentBytes,
|
|
totalBytes,
|
|
this.download.speed,
|
|
this.lastEstimatedSecondsLeft
|
|
);
|
|
this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
|
|
|
|
if (this.download.launchWhenSucceeded) {
|
|
status = lazy.DownloadUtils.getFormattedTimeStatus(
|
|
newEstimatedSecondsLeft
|
|
);
|
|
}
|
|
let hoverStatus = {
|
|
l10n: { id: "downloading-file-click-to-open" },
|
|
};
|
|
this.showStatus(status, hoverStatus);
|
|
} else {
|
|
let verdict = "";
|
|
|
|
// The download is not in progress, so we update the user interface based
|
|
// on other properties. The order in which we check the properties of the
|
|
// Download object is the same used by stateOfDownload.
|
|
if (this.download.deleted) {
|
|
this.showDeletedOrMissing();
|
|
} else if (this.download.succeeded) {
|
|
lazy.DownloadsCommon.log(
|
|
"_updateStateInner, target exists? ",
|
|
this.download.target.path,
|
|
this.download.target.exists
|
|
);
|
|
if (this.download.target.exists) {
|
|
// This is a completed download, and the target file still exists.
|
|
this.element.setAttribute("exists", "true");
|
|
|
|
this.element.toggleAttribute(
|
|
"viewable-internally",
|
|
lazy.DownloadIntegration.shouldViewDownloadInternally(
|
|
lazy.DownloadsCommon.getMimeInfo(this.download)?.type
|
|
)
|
|
);
|
|
|
|
let sizeWithUnits = DownloadsViewUI.getSizeWithUnits(this.download);
|
|
if (this.isPanel) {
|
|
// In the Downloads Panel, we show the file size after the state
|
|
// label, for example "Completed - 1.5 MB". When the pointer is over
|
|
// the main area of the item, this label is replaced with a
|
|
// description of the default action, which opens the file.
|
|
let status = lazy.DownloadsCommon.strings.stateCompleted;
|
|
if (sizeWithUnits) {
|
|
status = lazy.DownloadsCommon.strings.statusSeparator(
|
|
status,
|
|
sizeWithUnits
|
|
);
|
|
}
|
|
this.showStatus(status, { l10n: { id: "downloads-open-file" } });
|
|
} else {
|
|
// In the Downloads View, we show the file size in place of the
|
|
// state label, for example "1.5 MB - example.com - 1:45 PM".
|
|
this.showStatusWithDetails(
|
|
sizeWithUnits || lazy.DownloadsCommon.strings.sizeUnknown
|
|
);
|
|
}
|
|
this.showButton("show");
|
|
} else {
|
|
// This is a completed download, but the target file does not exist
|
|
// anymore, so the main action of opening the file is unavailable.
|
|
this.showDeletedOrMissing();
|
|
}
|
|
} else if (this.download.error) {
|
|
if (this.download.error.becauseBlockedByParentalControls) {
|
|
// This download was blocked permanently by parental controls.
|
|
this.showStatusWithDetails(
|
|
lazy.DownloadsCommon.strings.stateBlockedParentalControls
|
|
);
|
|
this.hideButton();
|
|
} else if (this.download.error.becauseBlockedByReputationCheck) {
|
|
verdict = this.download.error.reputationCheckVerdict;
|
|
let hover = "";
|
|
if (!this.download.hasBlockedData) {
|
|
// This download was blocked permanently by reputation check.
|
|
this.hideButton();
|
|
} else if (this.isPanel) {
|
|
// This download was blocked temporarily by reputation check. In the
|
|
// Downloads Panel, a subview can be used to remove the file or open
|
|
// the download anyways.
|
|
this.showButton("subviewOpenOrRemoveFile");
|
|
hover = { l10n: { id: "downloads-show-more-information" } };
|
|
} else {
|
|
// This download was blocked temporarily by reputation check. In the
|
|
// Downloads View, the interface depends on the threat severity.
|
|
switch (verdict) {
|
|
case lazy.Downloads.Error.BLOCK_VERDICT_UNCOMMON:
|
|
case lazy.Downloads.Error.BLOCK_VERDICT_INSECURE:
|
|
case lazy.Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
|
// Keep the option the user chose on the save dialogue
|
|
if (this.download.launchWhenSucceeded) {
|
|
this.showButton("askOpenOrRemoveFile");
|
|
} else {
|
|
this.showButton("askRemoveFileOrAllow");
|
|
}
|
|
break;
|
|
case lazy.Downloads.Error.BLOCK_VERDICT_DOWNLOAD_SPAM:
|
|
this.showButton("askRemoveFileOrAllow");
|
|
break;
|
|
default:
|
|
// Assume Downloads.Error.BLOCK_VERDICT_MALWARE
|
|
this.showButton("removeFile");
|
|
break;
|
|
}
|
|
}
|
|
this.showStatusWithDetails(this.rawBlockedTitleAndDetails[0], hover);
|
|
} else {
|
|
// This download failed without being blocked, and can be restarted.
|
|
this.showStatusWithDetails(
|
|
lazy.DownloadsCommon.strings.stateFailed,
|
|
this.download.error.localizedReason
|
|
);
|
|
this.showButton("retry");
|
|
}
|
|
} else if (this.download.canceled) {
|
|
if (this.download.hasPartialData) {
|
|
// This download was paused. The main action button will cancel the
|
|
// download, and in both the Downloads Panel and the Downlods View the
|
|
// status includes the size, for example "Paused - 1.1 MB".
|
|
let totalBytes = this.download.hasProgress
|
|
? this.download.totalBytes
|
|
: -1;
|
|
let transfer = lazy.DownloadUtils.getTransferTotal(
|
|
this.download.currentBytes,
|
|
totalBytes
|
|
);
|
|
this.showStatus(
|
|
lazy.DownloadsCommon.strings.statusSeparatorBeforeNumber(
|
|
lazy.DownloadsCommon.strings.statePaused,
|
|
transfer
|
|
)
|
|
);
|
|
this.showButton("cancel");
|
|
progressPaused = true;
|
|
} else {
|
|
// This download was canceled.
|
|
this.showStatusWithDetails(
|
|
lazy.DownloadsCommon.strings.stateCanceled
|
|
);
|
|
this.showButton("retry");
|
|
}
|
|
} else {
|
|
// This download was added to the global list before it started. While
|
|
// we still support this case, at the moment it can only be triggered by
|
|
// internally developed add-ons and regression tests, and should not
|
|
// happen unless there is a bug. This means the stateStarting string can
|
|
// probably be removed when converting the localization to Fluent.
|
|
this.showStatus(lazy.DownloadsCommon.strings.stateStarting);
|
|
this.showButton("cancel");
|
|
}
|
|
|
|
// These attributes are only set in this slower code path, because they
|
|
// are irrelevant for downloads that are in progress.
|
|
if (verdict) {
|
|
this.element.setAttribute("verdict", verdict);
|
|
} else {
|
|
this.element.removeAttribute("verdict");
|
|
}
|
|
|
|
this.element.classList.toggle(
|
|
"temporary-block",
|
|
!!this.download.hasBlockedData
|
|
);
|
|
}
|
|
|
|
// These attributes are set in all code paths, because they are relevant for
|
|
// downloads that are in progress and for other states.
|
|
if (this.download.hasProgress) {
|
|
this.showProgress("normal", this.download.progress, progressPaused);
|
|
} else {
|
|
this.showProgress("undetermined", 100, progressPaused);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns [title, [details1, details2]] for blocked downloads.
|
|
* The title or details could be raw strings or l10n objects.
|
|
*/
|
|
get rawBlockedTitleAndDetails() {
|
|
let s = lazy.DownloadsCommon.strings;
|
|
if (
|
|
!this.download.error ||
|
|
!this.download.error.becauseBlockedByReputationCheck
|
|
) {
|
|
return [null, null];
|
|
}
|
|
switch (this.download.error.reputationCheckVerdict) {
|
|
case lazy.Downloads.Error.BLOCK_VERDICT_UNCOMMON:
|
|
return [s.blockedUncommon2, [s.unblockTypeUncommon2, s.unblockTip2]];
|
|
case lazy.Downloads.Error.BLOCK_VERDICT_INSECURE:
|
|
return [
|
|
s.blockedPotentiallyInsecure,
|
|
[s.unblockInsecure2, s.unblockTip2],
|
|
];
|
|
case lazy.Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
|
|
return [
|
|
s.blockedPotentiallyUnwanted,
|
|
[s.unblockTypePotentiallyUnwanted2, s.unblockTip2],
|
|
];
|
|
case lazy.Downloads.Error.BLOCK_VERDICT_MALWARE:
|
|
return [s.blockedMalware, [s.unblockTypeMalware, s.unblockTip2]];
|
|
|
|
case lazy.Downloads.Error.BLOCK_VERDICT_DOWNLOAD_SPAM:
|
|
let title = {
|
|
id: "downloads-files-not-downloaded",
|
|
args: {
|
|
num: this.download.blockedDownloadsCount,
|
|
},
|
|
};
|
|
let details = {
|
|
id: "downloads-blocked-download-detailed-info",
|
|
args: { url: DownloadsViewUI.getStrippedUrl(this.download) },
|
|
};
|
|
return [{ l10n: title }, [{ l10n: details }, null]];
|
|
}
|
|
throw new Error(
|
|
"Unexpected reputationCheckVerdict: " +
|
|
this.download.error.reputationCheckVerdict
|
|
);
|
|
},
|
|
|
|
showDeletedOrMissing() {
|
|
this.element.removeAttribute("exists");
|
|
let label =
|
|
lazy.DownloadsCommon.strings[
|
|
this.download.deleted ? "fileDeleted" : "fileMovedOrMissing"
|
|
];
|
|
this.showStatusWithDetails(label, label);
|
|
this.hideButton();
|
|
},
|
|
|
|
/**
|
|
* Shows the appropriate unblock dialog based on the verdict, and executes the
|
|
* action selected by the user in the dialog, which may involve unblocking,
|
|
* opening or removing the file.
|
|
*
|
|
* @param window
|
|
* The window to which the dialog should be anchored.
|
|
* @param dialogType
|
|
* Can be "unblock", "chooseUnblock", or "chooseOpen".
|
|
*/
|
|
confirmUnblock(window, dialogType) {
|
|
lazy.DownloadsCommon.confirmUnblockDownload({
|
|
verdict: this.download.error.reputationCheckVerdict,
|
|
window,
|
|
dialogType,
|
|
})
|
|
.then(action => {
|
|
if (action == "open") {
|
|
return this.unblockAndOpenDownload();
|
|
} else if (action == "unblock") {
|
|
return this.download.unblock();
|
|
} else if (action == "confirmBlock") {
|
|
return this.download.confirmBlock();
|
|
}
|
|
return Promise.resolve();
|
|
})
|
|
.catch(console.error);
|
|
},
|
|
|
|
/**
|
|
* Unblocks the downloaded file and opens it.
|
|
*
|
|
* @return A promise that's resolved after the file has been opened.
|
|
*/
|
|
unblockAndOpenDownload() {
|
|
return this.download.unblock().then(() => this.downloadsCmd_open());
|
|
},
|
|
|
|
unblockAndSave() {
|
|
return this.download.unblock();
|
|
},
|
|
/**
|
|
* Returns the name of the default command to use for the current state of the
|
|
* download, when there is a double click or another default interaction. If
|
|
* there is no default command for the current state, returns an empty string.
|
|
* The commands are implemented as functions on this object or derived ones.
|
|
*/
|
|
get currentDefaultCommandName() {
|
|
switch (lazy.DownloadsCommon.stateOfDownload(this.download)) {
|
|
case lazy.DownloadsCommon.DOWNLOAD_NOTSTARTED:
|
|
return "downloadsCmd_cancel";
|
|
case lazy.DownloadsCommon.DOWNLOAD_FAILED:
|
|
case lazy.DownloadsCommon.DOWNLOAD_CANCELED:
|
|
return "downloadsCmd_retry";
|
|
case lazy.DownloadsCommon.DOWNLOAD_PAUSED:
|
|
return "downloadsCmd_pauseResume";
|
|
case lazy.DownloadsCommon.DOWNLOAD_FINISHED:
|
|
return "downloadsCmd_open";
|
|
case lazy.DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL:
|
|
return "downloadsCmd_openReferrer";
|
|
case lazy.DownloadsCommon.DOWNLOAD_DIRTY:
|
|
return "downloadsCmd_showBlockedInfo";
|
|
}
|
|
return "";
|
|
},
|
|
|
|
/**
|
|
* Returns true if the specified command can be invoked on the current item.
|
|
* The commands are implemented as functions on this object or derived ones.
|
|
*
|
|
* @param aCommand
|
|
* Name of the command to check, for example "downloadsCmd_retry".
|
|
*/
|
|
isCommandEnabled(aCommand) {
|
|
switch (aCommand) {
|
|
case "downloadsCmd_retry":
|
|
return this.download.canceled || !!this.download.error;
|
|
case "downloadsCmd_pauseResume":
|
|
return this.download.hasPartialData && !this.download.error;
|
|
case "downloadsCmd_openReferrer":
|
|
return (
|
|
!!this.download.source.referrerInfo &&
|
|
!!this.download.source.referrerInfo.originalReferrer
|
|
);
|
|
case "downloadsCmd_confirmBlock":
|
|
case "downloadsCmd_chooseUnblock":
|
|
case "downloadsCmd_chooseOpen":
|
|
case "downloadsCmd_unblock":
|
|
case "downloadsCmd_unblockAndSave":
|
|
case "downloadsCmd_unblockAndOpen":
|
|
return this.download.hasBlockedData;
|
|
case "downloadsCmd_cancel":
|
|
return this.download.hasPartialData || !this.download.stopped;
|
|
case "downloadsCmd_open":
|
|
case "downloadsCmd_open:current":
|
|
case "downloadsCmd_open:tab":
|
|
case "downloadsCmd_open:tabshifted":
|
|
case "downloadsCmd_open:window":
|
|
case "downloadsCmd_alwaysOpenSimilarFiles":
|
|
// This property is false if the download did not succeed.
|
|
return this.download.target.exists;
|
|
|
|
case "downloadsCmd_show":
|
|
case "downloadsCmd_deleteFile":
|
|
let { target } = this.download;
|
|
return (
|
|
!this.download.deleted && (target.exists || target.partFileExists)
|
|
);
|
|
|
|
case "downloadsCmd_delete":
|
|
case "cmd_delete":
|
|
// We don't want in-progress downloads to be removed accidentally.
|
|
return this.download.stopped;
|
|
case "downloadsCmd_openInSystemViewer":
|
|
case "downloadsCmd_alwaysOpenInSystemViewer":
|
|
return lazy.DownloadIntegration.shouldViewDownloadInternally(
|
|
lazy.DownloadsCommon.getMimeInfo(this.download)?.type
|
|
);
|
|
}
|
|
return DownloadsViewUI.isCommandName(aCommand) && !!this[aCommand];
|
|
},
|
|
|
|
doCommand(aCommand) {
|
|
// split off an optional command "modifier" into an argument,
|
|
// e.g. "downloadsCmd_open:window"
|
|
let [command, modifier] = aCommand.split(":");
|
|
if (DownloadsViewUI.isCommandName(command)) {
|
|
this[command](modifier);
|
|
}
|
|
},
|
|
|
|
onButton() {
|
|
this.doCommand(this.buttonCommandName);
|
|
},
|
|
|
|
downloadsCmd_cancel() {
|
|
// This is the correct way to avoid race conditions when cancelling.
|
|
this.download.cancel().catch(() => {});
|
|
this.download
|
|
.removePartialData()
|
|
.catch(console.error)
|
|
.finally(() => this.download.target.refresh());
|
|
},
|
|
|
|
downloadsCmd_confirmBlock() {
|
|
this.download.confirmBlock().catch(console.error);
|
|
},
|
|
|
|
downloadsCmd_open(openWhere = "tab") {
|
|
lazy.DownloadsCommon.openDownload(this.download, {
|
|
openWhere,
|
|
});
|
|
},
|
|
|
|
downloadsCmd_openReferrer() {
|
|
this.element.ownerGlobal.openURL(
|
|
this.download.source.referrerInfo.originalReferrer
|
|
);
|
|
},
|
|
|
|
downloadsCmd_pauseResume() {
|
|
if (this.download.stopped) {
|
|
this.download.start();
|
|
} else {
|
|
this.download.cancel();
|
|
}
|
|
},
|
|
|
|
downloadsCmd_show() {
|
|
let file = new lazy.FileUtils.File(this.download.target.path);
|
|
lazy.DownloadsCommon.showDownloadedFile(file);
|
|
},
|
|
|
|
downloadsCmd_retry() {
|
|
if (this.download.start) {
|
|
// Errors when retrying are already reported as download failures.
|
|
this.download.start().catch(() => {});
|
|
return;
|
|
}
|
|
|
|
let window = this.browserWindow || this.element.ownerGlobal;
|
|
let document = window.document;
|
|
|
|
// Do not suggest a file name if we don't know the original target.
|
|
let targetPath = this.download.target.path
|
|
? PathUtils.filename(this.download.target.path)
|
|
: null;
|
|
window.DownloadURL(this.download.source.url, targetPath, document);
|
|
},
|
|
|
|
downloadsCmd_delete() {
|
|
// Alias for the 'cmd_delete' command, because it may clash with another
|
|
// controller which causes unexpected behavior as different codepaths claim
|
|
// ownership.
|
|
this.cmd_delete();
|
|
},
|
|
|
|
cmd_delete() {
|
|
lazy.DownloadsCommon.deleteDownload(this.download).catch(console.error);
|
|
},
|
|
|
|
async downloadsCmd_deleteFile() {
|
|
// Remove the download from the session and history downloads, delete part files.
|
|
await lazy.DownloadsCommon.deleteDownloadFiles(
|
|
this.download,
|
|
DownloadsViewUI.clearHistoryOnDelete
|
|
);
|
|
},
|
|
|
|
downloadsCmd_openInSystemViewer() {
|
|
// For this interaction only, pass a flag to override the preferredAction for this
|
|
// mime-type and open using the system viewer
|
|
lazy.DownloadsCommon.openDownload(this.download, {
|
|
useSystemDefault: true,
|
|
}).catch(console.error);
|
|
},
|
|
|
|
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 = lazy.DownloadsCommon.getMimeInfo(this.download);
|
|
if (!mimeInfo) {
|
|
throw new Error(
|
|
"Can't open download with unknown mime-type in system viewer"
|
|
);
|
|
}
|
|
if (mimeInfo.preferredAction !== mimeInfo.useSystemDefault) {
|
|
// User has selected to open this mime-type with the system viewer from now on
|
|
lazy.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 {
|
|
lazy.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;
|
|
}
|
|
lazy.handlerSvc.store(mimeInfo);
|
|
lazy.DownloadsCommon.openDownload(this.download).catch(console.error);
|
|
},
|
|
|
|
downloadsCmd_alwaysOpenSimilarFiles() {
|
|
const mimeInfo = lazy.DownloadsCommon.getMimeInfo(this.download);
|
|
if (!mimeInfo) {
|
|
throw new Error("Can't open download with unknown mime-type");
|
|
}
|
|
|
|
// User has selected to always open this mime-type from now on and will add this
|
|
// mime-type to our preferences table with the system default option. Open the
|
|
// file immediately after selecting the menu item like alwaysOpenInSystemViewer.
|
|
if (mimeInfo.preferredAction !== mimeInfo.useSystemDefault) {
|
|
mimeInfo.preferredAction = mimeInfo.useSystemDefault;
|
|
lazy.handlerSvc.store(mimeInfo);
|
|
lazy.DownloadsCommon.openDownload(this.download).catch(console.error);
|
|
} else {
|
|
// Otherwise, if user unchecks this option after already enabling it from the
|
|
// context menu, resort to saveToDisk.
|
|
mimeInfo.preferredAction = mimeInfo.saveToDisk;
|
|
lazy.handlerSvc.store(mimeInfo);
|
|
}
|
|
},
|
|
};
|