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:
Shane Hughes
2022-01-21 10:49:43 +00:00
parent 10144f9900
commit e85e908b7d
4 changed files with 125 additions and 56 deletions

View File

@@ -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();

View File

@@ -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;
},

View File

@@ -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";

View File

@@ -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();