fune/browser/base/content/test/appUpdate/head.js
Doug Thayer f463069928 Bug 893505 - Simplify the application update UI r=chmanchester,enndeakin+6102,Gijs,rstrong
There's quite a few changes in here. At a high level, all we're trying to do
is to replace the old update popup with a less intrusive and more modern
doorhanger (set of doorhangers) for various update failure conditions.

MozReview-Commit-ID: 24sESMTosNX

--HG--
extra : rebase_source : ee0c1e00fe3f99e16388f0de17274ff97a3b9fcf
2017-03-21 13:50:09 -07:00

360 lines
11 KiB
JavaScript

Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const IS_MACOSX = ("nsILocalFileMac" in Ci);
const IS_WIN = ("@mozilla.org/windows-registry-key;1" in Cc);
const BIN_SUFFIX = (IS_WIN ? ".exe" : "");
const FILE_UPDATER_BIN = "updater" + (IS_MACOSX ? ".app" : BIN_SUFFIX);
const FILE_UPDATER_BIN_BAK = FILE_UPDATER_BIN + ".bak";
let gRembemberedPrefs = [];
const DATA_URI_SPEC = "chrome://mochitests/content/browser/browser/base/content/test/appUpdate/";
var DEBUG_AUS_TEST = true;
var gUseTestUpdater = false;
const LOG_FUNCTION = info;
/* import-globals-from testConstants.js */
Services.scriptloader.loadSubScript(DATA_URI_SPEC + "testConstants.js", this);
/* import-globals-from ../../../../../toolkit/mozapps/update/tests/data/shared.js */
Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this);
var gURLData = URL_HOST + "/" + REL_PATH_DATA;
const URL_MANUAL_UPDATE = gURLData + "downloadPage.html";
const NOTIFICATIONS = [
"update-available",
"update-manual",
"update-restart"
];
/**
* Delay for a very short period. Useful for moving the code after this
* to the back of the event loop.
*
* @return A promise which will resolve after a very short period.
*/
function delay() {
return new Promise(resolve => executeSoon(resolve));
}
/**
* Gets the update version info for the update url parameters to send to
* update.sjs.
*
* @param aAppVersion (optional)
* The application version for the update snippet. If not specified the
* current application version will be used.
* @return The url parameters for the application and platform version to send
* to update.sjs.
*/
function getVersionParams(aAppVersion) {
let appInfo = Services.appinfo;
return "&appVersion=" + (aAppVersion ? aAppVersion : appInfo.version);
}
/**
* Clean up updates list and the updates directory.
*/
function cleanUpUpdates() {
gUpdateManager.activeUpdate = null;
gUpdateManager.saveUpdates();
removeUpdateDirsAndFiles();
}
/**
* Runs a typical update test. Will set various common prefs for using the
* updater doorhanger, runs the provided list of steps, and makes sure
* everything is cleaned up afterwards.
*
* @param updateParams
* URL-encoded params which will be sent to update.sjs.
* @param checkAttempts
* How many times to check for updates. Useful for testing the UI
* for check failures.
* @param steps
* A list of test steps to perform, specifying expected doorhangers
* and additional validation/cleanup callbacks.
* @return A promise which will resolve once all of the steps have been run
* and cleanup has been performed.
*/
function runUpdateTest(updateParams, checkAttempts, steps) {
return Task.spawn(function*() {
registerCleanupFunction(() => {
gMenuButtonUpdateBadge.uninit();
gMenuButtonUpdateBadge.init();
cleanUpUpdates();
});
yield SpecialPowers.pushPrefEnv({
set: [
[PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
[PREF_APP_UPDATE_ENABLED, true],
[PREF_APP_UPDATE_IDLETIME, 0],
[PREF_APP_UPDATE_URL_MANUAL, URL_MANUAL_UPDATE],
[PREF_APP_UPDATE_LOG, DEBUG_AUS_TEST],
]});
yield setupTestUpdater();
let url = URL_HTTP_UPDATE_SJS +
"?" + updateParams +
getVersionParams();
setUpdateURL(url);
executeSoon(() => {
Task.spawn(function*() {
gAUS.checkForBackgroundUpdates();
for (var i = 0; i < checkAttempts - 1; i++) {
yield waitForEvent("update-error", "check-attempt-failed");
gAUS.checkForBackgroundUpdates();
}
});
});
for (let step of steps) {
yield processStep(step);
}
yield finishTestRestoreUpdaterBackup();
});
}
/**
* Runs a test which processes an update. Similar to runUpdateTest.
*
* @param updates
* A list of updates to process.
* @param steps
* A list of test steps to perform, specifying expected doorhangers
* and additional validation/cleanup callbacks.
* @return A promise which will resolve once all of the steps have been run
* and cleanup has been performed.
*/
function runUpdateProcessingTest(updates, steps) {
return Task.spawn(function*() {
registerCleanupFunction(() => {
gMenuButtonUpdateBadge.reset();
cleanUpUpdates();
});
SpecialPowers.pushPrefEnv({
set: [
[PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
[PREF_APP_UPDATE_ENABLED, true],
[PREF_APP_UPDATE_IDLETIME, 0],
[PREF_APP_UPDATE_URL_MANUAL, URL_MANUAL_UPDATE],
[PREF_APP_UPDATE_LOG, DEBUG_AUS_TEST],
]});
yield setupTestUpdater();
writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
writeUpdatesToXMLFile(getLocalUpdatesXMLString(""), false);
writeStatusFile(STATE_FAILED_CRC_ERROR);
reloadUpdateManagerData();
testPostUpdateProcessing();
for (let step of steps) {
yield processStep(step);
}
yield finishTestRestoreUpdaterBackup();
});
}
function processStep({notificationId, button, beforeClick, cleanup}) {
return Task.spawn(function*() {
yield BrowserTestUtils.waitForEvent(PanelUI.notificationPanel, "popupshown");
const shownNotification = PanelUI.activeNotification.id;
is(shownNotification, notificationId, "The right notification showed up.");
if (shownNotification != notificationId) {
if (cleanup) {
yield cleanup();
}
return;
}
let notification = document.getElementById(`PanelUI-${notificationId}-notification`);
is(notification.hidden, false, `${notificationId} notification is showing`);
if (beforeClick) {
yield Task.spawn(beforeClick);
}
let buttonEl = document.getAnonymousElementByAttribute(notification, "anonid", button);
buttonEl.click();
if (cleanup) {
yield cleanup();
}
});
}
/**
* Waits for the specified topic and (optionally) status.
* @param topic
* String representing the topic to wait for.
* @param status
* Optional String representing the status on said topic to wait for.
* @return A promise which will resolve the first time an event occurs on the
* specified topic, and (optionally) with the specified status.
*/
function waitForEvent(topic, status = null) {
return new Promise(resolve => Services.obs.addObserver({
observe(subject, innerTopic, innerStatus) {
if (!status || status == innerStatus) {
Services.obs.removeObserver(this, topic);
resolve(innerStatus);
}
}
}, topic, false))
}
/**
* Ensures that the "What's new" link with the provided ID is displayed and
* matches the url parameter provided. If no URL is provided, it will instead
* ensure that the link matches the default link URL.
*
* @param id
* The ID of the "What's new" link element.
* @param url (optional)
* The URL to check against. If none is provided, a default will be used.
*/
function checkWhatsNewLink(id, url) {
let whatsNewLink = document.getElementById(id);
is(whatsNewLink.href,
url || URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS",
"What's new link points to the test_details URL");
is(whatsNewLink.hidden, false, "What's new link is not hidden.");
}
/**
* For tests that use the test updater restores the backed up real updater if
* it exists and tries again on failure since Windows debug builds at times
* leave the file in use. After success moveRealUpdater is called to continue
* the setup of the test updater. For tests that don't use the test updater
* runTest will be called.
*/
function setupTestUpdater() {
return Task.spawn(function*() {
if (gUseTestUpdater) {
try {
restoreUpdaterBackup();
} catch (e) {
logTestInfo("Attempt to restore the backed up updater failed... " +
"will try again, Exception: " + e);
yield delay();
yield setupTestUpdater();
return;
}
yield moveRealUpdater();
}
});
}
/**
* Backs up the real updater and tries again on failure since Windows debug
* builds at times leave the file in use. After success it will call
* copyTestUpdater to continue the setup of the test updater.
*/
function moveRealUpdater() {
return Task.spawn(function*() {
try {
// Move away the real updater
let baseAppDir = getAppBaseDir();
let updater = baseAppDir.clone();
updater.append(FILE_UPDATER_BIN);
updater.moveTo(baseAppDir, FILE_UPDATER_BIN_BAK);
} catch (e) {
logTestInfo("Attempt to move the real updater out of the way failed... " +
"will try again, Exception: " + e);
yield delay();
yield moveRealUpdater();
return;
}
yield copyTestUpdater();
});
}
/**
* Copies the test updater so it can be used by tests and tries again on failure
* since Windows debug builds at times leave the file in use. After success it
* will call runTest to continue the test.
*/
function copyTestUpdater() {
return Task.spawn(function*() {
try {
// Copy the test updater
let baseAppDir = getAppBaseDir();
let testUpdaterDir = Services.dirsvc.get("CurWorkD", Ci.nsILocalFile);
let relPath = REL_PATH_DATA;
let pathParts = relPath.split("/");
for (let i = 0; i < pathParts.length; ++i) {
testUpdaterDir.append(pathParts[i]);
}
let testUpdater = testUpdaterDir.clone();
testUpdater.append(FILE_UPDATER_BIN);
testUpdater.copyToFollowingLinks(baseAppDir, FILE_UPDATER_BIN);
} catch (e) {
logTestInfo("Attempt to copy the test updater failed... " +
"will try again, Exception: " + e);
yield delay();
yield copyTestUpdater();
}
});
}
/**
* Restores the updater that was backed up. This is called in setupTestUpdater
* before the backup of the real updater is done in case the previous test
* failed to restore the updater, in finishTestDefaultWaitForWindowClosed when
* the test has finished, and in test_9999_cleanup.xul after all tests have
* finished.
*/
function restoreUpdaterBackup() {
let baseAppDir = getAppBaseDir();
let updater = baseAppDir.clone();
let updaterBackup = baseAppDir.clone();
updater.append(FILE_UPDATER_BIN);
updaterBackup.append(FILE_UPDATER_BIN_BAK);
if (updaterBackup.exists()) {
if (updater.exists()) {
updater.remove(true);
}
updaterBackup.moveTo(baseAppDir, FILE_UPDATER_BIN);
}
}
/**
* When a test finishes this will repeatedly attempt to restore the real updater
* for tests that use the test updater and then call
* finishTestDefaultWaitForWindowClosed after the restore is successful.
*/
function finishTestRestoreUpdaterBackup() {
return Task.spawn(function*() {
if (gUseTestUpdater) {
try {
// Windows debug builds keep the updater file in use for a short period of
// time after the updater process exits.
restoreUpdaterBackup();
} catch (e) {
logTestInfo("Attempt to restore the backed up updater failed... " +
"will try again, Exception: " + e);
yield delay();
yield finishTestRestoreUpdaterBackup();
}
}
});
}