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