Bug 1749784 - Overhaul DownloadsIndicatorDataCtor to track each download's attention state individually. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D135857
This commit is contained in:
@@ -158,6 +158,12 @@ var DownloadsCommon = {
|
||||
ATTENTION_WARNING: "warning",
|
||||
ATTENTION_SEVERE: "severe",
|
||||
|
||||
// Bit flags for the attentionSuppressed property.
|
||||
SUPPRESS_NONE: 0,
|
||||
SUPPRESS_PANEL_OPEN: 1,
|
||||
SUPPRESS_ALL_DOWNLOADS_OPEN: 2,
|
||||
SUPPRESS_CONTENT_AREA_DOWNLOADS_OPEN: 4,
|
||||
|
||||
/**
|
||||
* Returns an object whose keys are the string names from the downloads string
|
||||
* bundle, and whose values are either the translated strings or functions
|
||||
@@ -792,7 +798,7 @@ function DownloadsDataCtor({ isPrivate, isHistory, maxHistoryResults } = {}) {
|
||||
this._isPrivate = !!isPrivate;
|
||||
|
||||
// Contains all the available Download objects and their integer state.
|
||||
this.oldDownloadStates = new Map();
|
||||
this._oldDownloadStates = new WeakMap();
|
||||
|
||||
// For the history downloads list we don't need to register this as a view,
|
||||
// but we have to ensure that the DownloadsData object is initialized before
|
||||
@@ -844,15 +850,15 @@ DownloadsDataCtor.prototype = {
|
||||
* Iterator for all the available Download objects. This is empty until the
|
||||
* data has been loaded using the JavaScript API for downloads.
|
||||
*/
|
||||
get downloads() {
|
||||
return this.oldDownloadStates.keys();
|
||||
get _downloads() {
|
||||
return ChromeUtils.nondeterministicGetWeakMapKeys(this._oldDownloadStates);
|
||||
},
|
||||
|
||||
/**
|
||||
* True if there are finished downloads that can be removed from the list.
|
||||
*/
|
||||
get canRemoveFinished() {
|
||||
for (let download of this.downloads) {
|
||||
for (let download of this._downloads) {
|
||||
// Stopped, paused, and failed downloads with partial data are removed.
|
||||
if (download.stopped && !(download.canceled && download.hasPartialData)) {
|
||||
return true;
|
||||
@@ -869,10 +875,6 @@ DownloadsDataCtor.prototype = {
|
||||
Downloads.getList(this._isPrivate ? Downloads.PRIVATE : Downloads.PUBLIC)
|
||||
.then(list => list.removeFinished())
|
||||
.catch(Cu.reportError);
|
||||
let indicatorData = this._isPrivate
|
||||
? PrivateDownloadsIndicatorData
|
||||
: DownloadsIndicatorData;
|
||||
indicatorData.attention = DownloadsCommon.ATTENTION_NONE;
|
||||
},
|
||||
|
||||
// Integration with the asynchronous Downloads back-end
|
||||
@@ -884,7 +886,7 @@ DownloadsDataCtor.prototype = {
|
||||
// for which the end time is stored differently, as a Places annotation.
|
||||
download.endTime = Date.now();
|
||||
|
||||
this.oldDownloadStates.set(
|
||||
this._oldDownloadStates.set(
|
||||
download,
|
||||
DownloadsCommon.stateOfDownload(download)
|
||||
);
|
||||
@@ -894,9 +896,9 @@ DownloadsDataCtor.prototype = {
|
||||
},
|
||||
|
||||
onDownloadChanged(download) {
|
||||
let oldState = this.oldDownloadStates.get(download);
|
||||
let oldState = this._oldDownloadStates.get(download);
|
||||
let newState = DownloadsCommon.stateOfDownload(download);
|
||||
this.oldDownloadStates.set(download, newState);
|
||||
this._oldDownloadStates.set(download, newState);
|
||||
|
||||
if (oldState != newState) {
|
||||
if (
|
||||
@@ -927,7 +929,7 @@ DownloadsDataCtor.prototype = {
|
||||
},
|
||||
|
||||
onDownloadRemoved(download) {
|
||||
this.oldDownloadStates.delete(download);
|
||||
this._oldDownloadStates.delete(download);
|
||||
},
|
||||
|
||||
// Registration of views
|
||||
@@ -999,7 +1001,7 @@ DownloadsDataCtor.prototype = {
|
||||
Services.prefs.getBoolPref(
|
||||
"browser.download.improvements_to_download_panel"
|
||||
) &&
|
||||
DownloadsCommon.summarizeDownloads(this.downloads).numDownloading <= 1 &&
|
||||
DownloadsCommon.summarizeDownloads(this._downloads).numDownloading <= 1 &&
|
||||
gAlwaysOpenPanel;
|
||||
|
||||
if (
|
||||
@@ -1219,7 +1221,7 @@ const DownloadsViewPrototype = {
|
||||
* @note Subclasses should override this.
|
||||
*/
|
||||
onDownloadRemoved(download) {
|
||||
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
|
||||
this._oldDownloadStates.delete(download);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1275,6 +1277,27 @@ function DownloadsIndicatorDataCtor(aPrivate) {
|
||||
DownloadsIndicatorDataCtor.prototype = {
|
||||
__proto__: DownloadsViewPrototype,
|
||||
|
||||
/**
|
||||
* Map of the relative severities of different attention states.
|
||||
* Used in sorting the map of active downloads' attention states
|
||||
* to determine the attention state to be displayed.
|
||||
*/
|
||||
_attentionPriority: new Map([
|
||||
[DownloadsCommon.ATTENTION_NONE, 0],
|
||||
[DownloadsCommon.ATTENTION_SUCCESS, 1],
|
||||
[DownloadsCommon.ATTENTION_INFO, 2],
|
||||
[DownloadsCommon.ATTENTION_WARNING, 3],
|
||||
[DownloadsCommon.ATTENTION_SEVERE, 4],
|
||||
]),
|
||||
|
||||
/**
|
||||
* Iterator for all the available Download objects. This is empty until the
|
||||
* data has been loaded using the JavaScript API for downloads.
|
||||
*/
|
||||
get _downloads() {
|
||||
return ChromeUtils.nondeterministicGetWeakMapKeys(this._oldDownloadStates);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an object previously added using addView.
|
||||
*
|
||||
@@ -1296,6 +1319,10 @@ DownloadsIndicatorDataCtor.prototype = {
|
||||
},
|
||||
|
||||
onDownloadStateChanged(download) {
|
||||
if (this._attentionSuppressed !== DownloadsCommon.SUPPRESS_NONE) {
|
||||
return;
|
||||
}
|
||||
let attention;
|
||||
if (
|
||||
!download.succeeded &&
|
||||
download.error &&
|
||||
@@ -1303,46 +1330,30 @@ DownloadsIndicatorDataCtor.prototype = {
|
||||
) {
|
||||
switch (download.error.reputationCheckVerdict) {
|
||||
case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
|
||||
// Existing higher level attention indication trumps ATTENTION_INFO.
|
||||
if (
|
||||
this._attention != DownloadsCommon.ATTENTION_SEVERE &&
|
||||
this._attention != DownloadsCommon.ATTENTION_WARNING
|
||||
) {
|
||||
this.attention = DownloadsCommon.ATTENTION_INFO;
|
||||
}
|
||||
attention = DownloadsCommon.ATTENTION_INFO;
|
||||
break;
|
||||
case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED: // fall-through
|
||||
case Downloads.Error.BLOCK_VERDICT_INSECURE:
|
||||
case Downloads.Error.BLOCK_VERDICT_DOWNLOAD_SPAM:
|
||||
// Existing higher level attention indication trumps ATTENTION_WARNING.
|
||||
if (this._attention != DownloadsCommon.ATTENTION_SEVERE) {
|
||||
this.attention = DownloadsCommon.ATTENTION_WARNING;
|
||||
}
|
||||
attention = DownloadsCommon.ATTENTION_WARNING;
|
||||
break;
|
||||
case Downloads.Error.BLOCK_VERDICT_MALWARE:
|
||||
this.attention = DownloadsCommon.ATTENTION_SEVERE;
|
||||
attention = DownloadsCommon.ATTENTION_SEVERE;
|
||||
break;
|
||||
default:
|
||||
this.attention = DownloadsCommon.ATTENTION_SEVERE;
|
||||
attention = DownloadsCommon.ATTENTION_SEVERE;
|
||||
Cu.reportError(
|
||||
"Unknown reputation verdict: " +
|
||||
download.error.reputationCheckVerdict
|
||||
);
|
||||
}
|
||||
} else if (download.succeeded) {
|
||||
// Existing higher level attention indication trumps ATTENTION_SUCCESS.
|
||||
if (
|
||||
this._attention != DownloadsCommon.ATTENTION_SEVERE &&
|
||||
this._attention != DownloadsCommon.ATTENTION_WARNING
|
||||
) {
|
||||
this.attention = DownloadsCommon.ATTENTION_SUCCESS;
|
||||
}
|
||||
attention = DownloadsCommon.ATTENTION_SUCCESS;
|
||||
} else if (download.error) {
|
||||
// Existing higher level attention indication trumps ATTENTION_WARNING.
|
||||
if (this._attention != DownloadsCommon.ATTENTION_SEVERE) {
|
||||
this.attention = DownloadsCommon.ATTENTION_WARNING;
|
||||
}
|
||||
attention = DownloadsCommon.ATTENTION_WARNING;
|
||||
}
|
||||
download.attention = attention;
|
||||
this.updateAttention();
|
||||
},
|
||||
|
||||
onDownloadChanged(download) {
|
||||
@@ -1351,7 +1362,9 @@ DownloadsIndicatorDataCtor.prototype = {
|
||||
},
|
||||
|
||||
onDownloadRemoved(download) {
|
||||
DownloadsViewPrototype.onDownloadRemoved.call(this, download);
|
||||
this._itemCount--;
|
||||
this.updateAttention();
|
||||
this._updateViews();
|
||||
},
|
||||
|
||||
@@ -1375,12 +1388,37 @@ DownloadsIndicatorDataCtor.prototype = {
|
||||
* Indicates whether the user is interacting with downloads, thus the
|
||||
* attention indication should not be shown even if requested.
|
||||
*/
|
||||
set attentionSuppressed(aValue) {
|
||||
this._attentionSuppressed = aValue;
|
||||
this._attention = DownloadsCommon.ATTENTION_NONE;
|
||||
this._updateViews();
|
||||
set attentionSuppressed(aFlags) {
|
||||
this._attentionSuppressed = aFlags;
|
||||
if (aFlags !== DownloadsCommon.SUPPRESS_NONE) {
|
||||
for (let download of this._downloads) {
|
||||
download.attention = DownloadsCommon.ATTENTION_NONE;
|
||||
}
|
||||
this.attention = DownloadsCommon.ATTENTION_NONE;
|
||||
}
|
||||
},
|
||||
get attentionSuppressed() {
|
||||
return this._attentionSuppressed;
|
||||
},
|
||||
_attentionSuppressed: DownloadsCommon.SUPPRESS_NONE,
|
||||
|
||||
/**
|
||||
* Set the indicator's attention to the most severe attention state among the
|
||||
* unseen displayed downloads, or DownloadsCommon.ATTENTION_NONE if empty.
|
||||
*/
|
||||
updateAttention() {
|
||||
let currentAttention = DownloadsCommon.ATTENTION_NONE;
|
||||
let currentPriority = 0;
|
||||
for (let download of this._downloads) {
|
||||
let { attention } = download;
|
||||
let priority = this._attentionPriority.get(attention);
|
||||
if (priority > currentPriority) {
|
||||
currentPriority = priority;
|
||||
currentAttention = attention;
|
||||
}
|
||||
}
|
||||
this.attention = currentAttention;
|
||||
},
|
||||
_attentionSuppressed: false,
|
||||
|
||||
/**
|
||||
* Updates the specified view with the current aggregate values.
|
||||
@@ -1391,9 +1429,10 @@ DownloadsIndicatorDataCtor.prototype = {
|
||||
_updateView(aView) {
|
||||
aView.hasDownloads = this._hasDownloads;
|
||||
aView.percentComplete = this._percentComplete;
|
||||
aView.attention = this._attentionSuppressed
|
||||
? DownloadsCommon.ATTENTION_NONE
|
||||
: this._attention;
|
||||
aView.attention =
|
||||
this.attentionSuppressed !== DownloadsCommon.SUPPRESS_NONE
|
||||
? DownloadsCommon.ATTENTION_NONE
|
||||
: this._attention;
|
||||
},
|
||||
|
||||
// Property updating based on current download status
|
||||
@@ -1411,8 +1450,8 @@ DownloadsIndicatorDataCtor.prototype = {
|
||||
*/
|
||||
*_activeDownloads() {
|
||||
let downloads = this._isPrivate
|
||||
? PrivateDownloadsData.downloads
|
||||
: DownloadsData.downloads;
|
||||
? PrivateDownloadsData._downloads
|
||||
: DownloadsData._downloads;
|
||||
for (let download of downloads) {
|
||||
if (!download.stopped || (download.canceled && download.hasPartialData)) {
|
||||
yield download;
|
||||
@@ -1535,6 +1574,7 @@ DownloadsSummaryData.prototype = {
|
||||
},
|
||||
|
||||
onDownloadRemoved(download) {
|
||||
DownloadsViewPrototype.onDownloadRemoved.call(this, download);
|
||||
let itemIndex = this._downloads.indexOf(download);
|
||||
this._downloads.splice(itemIndex, 1);
|
||||
this._updateViews();
|
||||
|
||||
@@ -208,7 +208,11 @@ var DownloadsView = {
|
||||
* as they exist they "collapses" their history "counterpart" (So we don't show two
|
||||
* items for every download).
|
||||
*/
|
||||
function DownloadsPlacesView(aRichListBox, aActive = true) {
|
||||
function DownloadsPlacesView(
|
||||
aRichListBox,
|
||||
aActive = true,
|
||||
aSuppressionFlag = DownloadsCommon.SUPPRESS_ALL_DOWNLOADS_OPEN
|
||||
) {
|
||||
this._richlistbox = aRichListBox;
|
||||
this._richlistbox._placesView = this;
|
||||
window.controllers.insertControllerAt(0, this);
|
||||
@@ -227,16 +231,23 @@ function DownloadsPlacesView(aRichListBox, aActive = true) {
|
||||
this._waitingForInitialData = true;
|
||||
this._downloadsData.addView(this);
|
||||
|
||||
// Get the Download button out of the attention state since we're about to
|
||||
// view all downloads.
|
||||
DownloadsCommon.getIndicatorData(window).attention =
|
||||
DownloadsCommon.ATTENTION_NONE;
|
||||
// Pause the download indicator as user is interacting with downloads. This is
|
||||
// skipped on about:downloads because it handles this by itself.
|
||||
if (aSuppressionFlag === DownloadsCommon.SUPPRESS_ALL_DOWNLOADS_OPEN) {
|
||||
DownloadsCommon.getIndicatorData(
|
||||
window
|
||||
).attentionSuppressed |= aSuppressionFlag;
|
||||
}
|
||||
|
||||
// Make sure to unregister the view if the window is closed.
|
||||
window.addEventListener(
|
||||
"unload",
|
||||
() => {
|
||||
window.controllers.removeController(this);
|
||||
// Unpause the main window's download indicator.
|
||||
DownloadsCommon.getIndicatorData(
|
||||
window
|
||||
).attentionSuppressed &= ~aSuppressionFlag;
|
||||
this._downloadsData.removeView(this);
|
||||
this.result = null;
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ const { PrivateBrowsingUtils } = ChromeUtils.import(
|
||||
var ContentAreaDownloadsView = {
|
||||
init() {
|
||||
let box = document.getElementById("downloadsListBox");
|
||||
let suppressionFlag = DownloadsCommon.SUPPRESS_CONTENT_AREA_DOWNLOADS_OPEN;
|
||||
box.addEventListener(
|
||||
"InitialDownloadsLoaded",
|
||||
() => {
|
||||
@@ -19,10 +20,24 @@ var ContentAreaDownloadsView = {
|
||||
document
|
||||
.getElementById("downloadsListBox")
|
||||
.focus({ preventFocusRing: true });
|
||||
// Pause the indicator if the browser is active.
|
||||
if (document.visibilityState === "visible") {
|
||||
DownloadsCommon.getIndicatorData(
|
||||
window
|
||||
).attentionSuppressed |= suppressionFlag;
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
let view = new DownloadsPlacesView(box);
|
||||
let view = new DownloadsPlacesView(box, true, suppressionFlag);
|
||||
document.addEventListener("visibilitychange", aEvent => {
|
||||
let indicator = DownloadsCommon.getIndicatorData(window);
|
||||
if (document.visibilityState === "visible") {
|
||||
indicator.attentionSuppressed |= suppressionFlag;
|
||||
} else {
|
||||
indicator.attentionSuppressed &= ~suppressionFlag;
|
||||
}
|
||||
});
|
||||
// Do not display the Places downloads in private windows
|
||||
if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
|
||||
view.place = "place:transition=7&sort=4";
|
||||
|
||||
@@ -369,7 +369,8 @@ var DownloadsPanel = {
|
||||
this._state = this.kStateShown;
|
||||
|
||||
// Since at most one popup is open at any given time, we can set globally.
|
||||
DownloadsCommon.getIndicatorData(window).attentionSuppressed = true;
|
||||
DownloadsCommon.getIndicatorData(window).attentionSuppressed |=
|
||||
DownloadsCommon.SUPPRESS_PANEL_OPEN;
|
||||
|
||||
// Ensure that the first item is selected when the panel is focused.
|
||||
if (DownloadsView.richListBox.itemCount > 0) {
|
||||
@@ -399,7 +400,9 @@ var DownloadsPanel = {
|
||||
this.keyFocusing = false;
|
||||
|
||||
// Since at most one popup is open at any given time, we can set globally.
|
||||
DownloadsCommon.getIndicatorData(window).attentionSuppressed = false;
|
||||
DownloadsCommon.getIndicatorData(
|
||||
window
|
||||
).attentionSuppressed &= ~DownloadsCommon.SUPPRESS_PANEL_OPEN;
|
||||
|
||||
// Allow the anchor to be hidden.
|
||||
DownloadsButton.releaseAnchor();
|
||||
|
||||
Reference in New Issue
Block a user