Bug 1642404 - add an option to show that an update is being downloaded r=bytesized,fluent-reviewers,flod

Add support for a hidden preference named app.update.notifyDuringDownload
that, when set to true, causes a "Downloading update" message to appear
in the app menu during a MAR download. Clicking the message opens the
about box so the user can see detailed progress information.

Differential Revision: https://phabricator.services.mozilla.com/D77688
This commit is contained in:
Mark Smith 2020-06-22 20:24:46 +00:00
Родитель e8562a8631
Коммит a7683d117e
18 изменённых файлов: 222 добавлений и 16 удалений

Просмотреть файл

@ -127,6 +127,10 @@ pref("app.update.download.promptMaxAttempts", 2);
// download a fresh installer.
pref("app.update.elevation.promptMaxAttempts", 2);
// If set to true, a message will be displayed in the hamburger menu while
// an update is being downloaded.
pref("app.update.notifyDuringDownload", false);
// If set to true, the Update Service will automatically download updates if the
// user can apply updates. This pref is no longer used on Windows, except as the
// default value to migrate to the new location that this data is now stored

Просмотреть файл

@ -782,6 +782,7 @@ const global = this;
const listeners = {
observers: {
"update-downloading": ["UpdateListener"],
"update-staged": ["UpdateListener"],
"update-downloaded": ["UpdateListener"],
"update-available": ["UpdateListener"],

Просмотреть файл

@ -223,6 +223,8 @@
<vbox class="panel-subview-body">
<vbox id="appMenu-addon-banners"/>
<toolbarbutton id="appMenu-update-banner" class="panel-banner-item"
data-l10n-id="appmenuitem-update-banner"
data-l10n-attrs="label-update-downloading"
label-update-available="&updateAvailable.panelUI.label;"
label-update-manual="&updateManual.panelUI.label;"
label-update-unsupported="&updateUnsupported.panelUI.label;"

Просмотреть файл

@ -65,6 +65,7 @@ const PanelUI = {
Services.obs.addObserver(this, "fullscreen-nav-toolbox");
Services.obs.addObserver(this, "appMenu-notifications");
Services.obs.addObserver(this, "show-update-progress");
XPCOMUtils.defineLazyPreferenceGetter(
this,
@ -182,6 +183,7 @@ const PanelUI = {
Services.obs.removeObserver(this, "fullscreen-nav-toolbox");
Services.obs.removeObserver(this, "appMenu-notifications");
Services.obs.removeObserver(this, "show-update-progress");
window.removeEventListener("MozDOMFullscreen:Entered", this);
window.removeEventListener("MozDOMFullscreen:Exited", this);
@ -271,6 +273,9 @@ const PanelUI = {
this._notifications = AppMenuNotifications.notifications;
this._updateNotifications(true);
break;
case "show-update-progress":
openAboutDialog();
break;
}
},

Просмотреть файл

@ -156,6 +156,68 @@ add_task(async function testSecondaryActionWorkflow() {
});
});
/**
* This tests that the PanelUI update downloading badge and banner
* notification are correctly displayed and that clicking the banner
* item calls the main action.
*/
add_task(async function testDownloadingBadge() {
let options = {
gBrowser: window.gBrowser,
url: "about:blank",
};
await BrowserTestUtils.withNewTab(options, async function(browser) {
let mainActionCalled = false;
let mainAction = {
callback: () => {
mainActionCalled = true;
},
};
// The downloading notification is always displayed in a dismissed state.
AppMenuNotifications.showNotification(
"update-downloading",
mainAction,
undefined,
{ dismissed: true }
);
is(PanelUI.notificationPanel.state, "closed", "doorhanger is closed.");
is(
PanelUI.menuButton.getAttribute("badge-status"),
"update-downloading",
"Downloading badge is displaying on PanelUI button."
);
await gCUITestUtils.openMainMenu();
isnot(
PanelUI.menuButton.getAttribute("badge-status"),
"update-downloading",
"Downloading badge is hidden on PanelUI button."
);
let menuItem = PanelUI.mainView.querySelector(".panel-banner-item");
is(
menuItem.label,
menuItem.getAttribute("label-update-downloading"),
"Showing correct label (downloading)"
);
is(menuItem.hidden, false, "update-downloading menu item is showing.");
await gCUITestUtils.hideMainMenu();
is(
PanelUI.menuButton.getAttribute("badge-status"),
"update-downloading",
"Downloading badge is shown on PanelUI button."
);
await gCUITestUtils.openMainMenu();
menuItem.click();
ok(mainActionCalled, "Main action callback was called");
AppMenuNotifications.removeNotification(/.*/);
});
});
/**
* We want to ensure a few things with this:
* - Adding a doorhanger will make a badge disappear

Просмотреть файл

@ -4,6 +4,8 @@
## App Menu
appmenuitem-update-banner =
.label-update-downloading = Downloading { -brand-shorter-name } update
appmenuitem-protection-dashboard-title = Protections Dashboard
appmenuitem-customize-mode =
.label = Customize…

Просмотреть файл

@ -67,6 +67,7 @@
}
#PanelUI-menu-button[badge-status="update-available"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
#PanelUI-menu-button[badge-status="update-downloading"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
#PanelUI-menu-button[badge-status="update-manual"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
#PanelUI-menu-button[badge-status="update-restart"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
#PanelUI-menu-button[badge-status="update-unsupported"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
@ -80,6 +81,7 @@
}
#PanelUI-menu-button[badge-status="update-available"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
#PanelUI-menu-button[badge-status="update-downloading"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
#PanelUI-menu-button[badge-status="update-manual"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
#PanelUI-menu-button[badge-status="update-restart"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
background: #74BF43 url(chrome://browser/skin/update-badge.svg) no-repeat center;
@ -90,6 +92,7 @@
}
.panel-banner-item[notificationid="update-available"]::after,
.panel-banner-item[notificationid="update-downloading"]::after,
.panel-banner-item[notificationid="update-manual"]::after,
.panel-banner-item[notificationid="update-restart"]::after {
background: #74BF43 url(chrome://browser/skin/update-badge.svg) no-repeat center;

Просмотреть файл

@ -401,6 +401,7 @@ html|*#webRTC-previewVideo {
/* UPDATE */
.popup-notification-icon[popupid="update-available"],
.popup-notification-icon[popupid="update-downloading"],
.popup-notification-icon[popupid="update-manual"],
.popup-notification-icon[popupid="update-restart"] {
background: #74BF43 url(chrome://browser/skin/notification-icons/update.svg) no-repeat center;

Просмотреть файл

@ -290,6 +290,7 @@ toolbar[brighttext] {
}
#PanelUI-menu-button[badge-status="update-available"],
#PanelUI-menu-button[badge-status="update-downloading"],
#PanelUI-menu-button[badge-status="update-manual"],
#PanelUI-menu-button[badge-status="update-restart"] {
list-style-image: url("chrome://browser/skin/menu-badged.svg");

Просмотреть файл

@ -113,16 +113,18 @@ var UpdateListener = {
mainAction,
beforeShowDoorhanger
) {
const addTelemetry = id => {
// No telemetry for the "downloading" state.
if (type !== "downloading") {
Services.telemetry.getHistogramById(id).add(type);
}
};
let action = {
callback(win, fromDoorhanger) {
if (fromDoorhanger) {
Services.telemetry
.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_DOORHANGER")
.add(type);
addTelemetry("UPDATE_NOTIFICATION_MAIN_ACTION_DOORHANGER");
} else {
Services.telemetry
.getHistogramById("UPDATE_NOTIFICATION_MAIN_ACTION_MENU")
.add(type);
addTelemetry("UPDATE_NOTIFICATION_MAIN_ACTION_MENU");
}
mainAction(win);
},
@ -131,13 +133,10 @@ var UpdateListener = {
let secondaryAction = {
callback() {
Services.telemetry
.getHistogramById("UPDATE_NOTIFICATION_DISMISSED")
.add(type);
addTelemetry("UPDATE_NOTIFICATION_DISMISSED");
},
dismiss: true,
};
AppMenuNotifications.showNotification(
"update-" + type,
action,
@ -145,13 +144,9 @@ var UpdateListener = {
{ dismissed, beforeShowDoorhanger }
);
if (dismissed) {
Services.telemetry
.getHistogramById("UPDATE_NOTIFICATION_BADGE_SHOWN")
.add(type);
addTelemetry("UPDATE_NOTIFICATION_BADGE_SHOWN");
} else {
Services.telemetry
.getHistogramById("UPDATE_NOTIFICATION_SHOWN")
.add(type);
addTelemetry("UPDATE_NOTIFICATION_SHOWN");
}
},
@ -205,6 +200,15 @@ var UpdateListener = {
}
},
showUpdateDownloadingNotification() {
this.showUpdateNotification("downloading", true, true, () => {
// The user clicked on the "Downloading update" app menu item.
// Code in browser/components/customizableui/content/panelUI.js
// receives the following notification and opens the about dialog.
Services.obs.notifyObservers(null, "show-update-progress");
});
},
handleUpdateError(update, status) {
switch (status) {
case "download-attempt-failed":
@ -287,6 +291,17 @@ var UpdateListener = {
}
},
handleUpdateDownloading(status) {
switch (status) {
case "downloading":
this.showUpdateDownloadingNotification();
break;
case "idle":
this.reset();
break;
}
},
observe(subject, topic, status) {
let update = subject && subject.QueryInterface(Ci.nsIUpdate);
@ -299,6 +314,9 @@ var UpdateListener = {
}
this.handleUpdateAvailable(update, status);
break;
case "update-downloading":
this.handleUpdateDownloading(status);
break;
case "update-staged":
case "update-downloaded":
// An update check has found an update and downloaded / staged the

Просмотреть файл

@ -59,6 +59,7 @@ const PREF_APP_UPDATE_ELEVATE_ATTEMPTS = "app.update.elevate.attempts";
const PREF_APP_UPDATE_ELEVATE_MAXATTEMPTS = "app.update.elevate.maxAttempts";
const PREF_APP_UPDATE_LOG = "app.update.log";
const PREF_APP_UPDATE_LOG_FILE = "app.update.log.file";
const PREF_APP_UPDATE_NOTIFYDURINGDOWNLOAD = "app.update.notifyDuringDownload";
const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors";
@ -4432,6 +4433,24 @@ Downloader.prototype = {
return selectedPatch;
},
/**
* Whether or not the user wants to be notified that an update is being
* downloaded.
*/
get _notifyDuringDownload() {
return Services.prefs.getBoolPref(
PREF_APP_UPDATE_NOTIFYDURINGDOWNLOAD,
false
);
},
_notifyDownloadStatusObservers: function Downloader_notifyDownloadStatusObservers() {
if (this._notifyDuringDownload) {
let status = this.updateService.isDownloading ? "downloading" : "idle";
Services.obs.notifyObservers(this._update, "update-downloading", status);
}
},
/**
* Whether or not we are currently downloading something.
*/
@ -4673,6 +4692,9 @@ Downloader.prototype = {
.getService(Ci.nsIUpdateManager)
.saveUpdates();
}
this._notifyDownloadStatusObservers();
return STATE_DOWNLOADING;
},
@ -5175,6 +5197,11 @@ Downloader.prototype = {
this._request = null;
// This notification must happen after _request is set to null so that
// the correct this.updateService.isDownloading value is available in
// _notifyDownloadStatusObservers().
this._notifyDownloadStatusObservers();
if (state == STATE_DOWNLOAD_FAILED) {
var allFailed = true;
// If we haven't already, attempt to download without BITS

Просмотреть файл

@ -21,6 +21,7 @@ prefs =
# About Dialog Application Update Tests
[browser_aboutDialog_bc_downloading.js]
[browser_aboutDialog_bc_downloading_staging.js]
[browser_aboutDialog_bc_downloading_notify.js]
[browser_aboutDialog_bc_downloaded.js]
[browser_aboutDialog_bc_downloaded_staging.js]
[browser_aboutDialog_bc_downloaded_staged.js]

Просмотреть файл

@ -15,6 +15,7 @@ prefs =
# About Dialog Application Update Tests
[browser_aboutDialog_bc_downloading.js]
[browser_aboutDialog_bc_downloading_staging.js]
[browser_aboutDialog_bc_downloading_notify.js]
[browser_aboutDialog_bc_downloaded.js]
[browser_aboutDialog_bc_downloaded_staging.js]
[browser_aboutDialog_bc_downloaded_stagingFailure.js]

Просмотреть файл

@ -20,6 +20,7 @@ prefs =
# About Dialog Application Update Tests
[browser_aboutDialog_bc_downloading.js]
[browser_aboutDialog_bc_downloading_staging.js]
[browser_aboutDialog_bc_downloading_notify.js]
[browser_aboutDialog_bc_downloaded.js]
[browser_aboutDialog_bc_downloaded_staging.js]
[browser_aboutDialog_bc_downloaded_staged.js]

Просмотреть файл

@ -14,6 +14,7 @@ prefs =
# About Dialog Application Update Tests
[browser_aboutDialog_bc_downloading.js]
[browser_aboutDialog_bc_downloading_staging.js]
[browser_aboutDialog_bc_downloading_notify.js]
[browser_aboutDialog_bc_downloaded.js]
[browser_aboutDialog_bc_downloaded_staging.js]
[browser_aboutDialog_bc_downloaded_stagingFailure.js]

Просмотреть файл

@ -6,6 +6,10 @@
// Test for About Dialog background check for updates
// with the About Dialog opened during downloading.
add_task(async function aboutDialog_backgroundCheck_downloading() {
await SpecialPowers.pushPrefEnv({
set: [[PREF_APP_UPDATE_NOTIFYDURINGDOWNLOAD, false]],
});
let downloadInfo = [];
if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
@ -21,6 +25,17 @@ add_task(async function aboutDialog_backgroundCheck_downloading() {
waitForUpdateState: STATE_DOWNLOADING,
};
await runAboutDialogUpdateTest(params, [
async function aboutDialog_downloading() {
is(
PanelUI.notificationPanel.state,
"closed",
"The window's doorhanger is closed."
);
ok(
!PanelUI.menuButton.hasAttribute("badge-status"),
"The window does not have a badge."
);
},
{
panelId: "downloading",
checkActiveUpdate: { state: STATE_DOWNLOADING },
@ -33,4 +48,6 @@ add_task(async function aboutDialog_backgroundCheck_downloading() {
continueFile: null,
},
]);
await SpecialPowers.popPrefEnv();
});

Просмотреть файл

@ -0,0 +1,58 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test for About Dialog background check for updates with the
// "notify during download" feature turned on.
add_task(async function aboutDialog_backgroundCheck_downloading_notify() {
await SpecialPowers.pushPrefEnv({
set: [[PREF_APP_UPDATE_NOTIFYDURINGDOWNLOAD, true]],
});
let downloadInfo = [];
if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
} else {
downloadInfo[0] = { patchType: "partial", internalResult: "0" };
}
// Since the partial should be successful specify an invalid size for the
// complete update.
let params = {
queryString: "&useSlowDownloadMar=1&invalidCompleteSize=1",
backgroundUpdate: true,
waitForUpdateState: STATE_DOWNLOADING,
};
await runAboutDialogUpdateTest(params, [
async function aboutDialog_downloading_notification() {
is(
PanelUI.notificationPanel.state,
"closed",
"The window's doorhanger is closed."
);
ok(
PanelUI.menuButton.hasAttribute("badge-status"),
"The window has a badge."
);
is(
PanelUI.menuButton.getAttribute("badge-status"),
"update-downloading",
"The downloading badge is showing for the background window"
);
},
{
panelId: "downloading",
checkActiveUpdate: { state: STATE_DOWNLOADING },
continueFile: CONTINUE_DOWNLOAD,
downloadInfo,
},
{
panelId: "apply",
checkActiveUpdate: { state: STATE_PENDING },
continueFile: null,
},
]);
await SpecialPowers.popPrefEnv();
});

Просмотреть файл

@ -40,6 +40,7 @@ const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
const PREF_APP_UPDATE_LASTUPDATETIME =
"app.update.lastUpdateTime.background-update-timer";
const PREF_APP_UPDATE_LOG = "app.update.log";
const PREF_APP_UPDATE_NOTIFYDURINGDOWNLOAD = "app.update.notifyDuringDownload";
const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
const PREF_APP_UPDATE_RETRYTIMEOUT = "app.update.socket.retryTimeout";
const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";