Mypal/toolkit/mozapps/update/content/updates.js
2019-07-08 13:08:22 +03:00

1366 lines
46 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
/* import-globals-from ../../../content/contentAreaUtils.js */
// Firefox's macBrowserOverlay.xul includes scripts that define Cc, Ci, and Cr
// so we have to use different names.
const {classes: CoC, interfaces: CoI, results: CoR, utils: CoU} = Components;
/* globals DownloadUtils, Services */
CoU.import("resource://gre/modules/DownloadUtils.jsm", this);
CoU.import("resource://gre/modules/Services.jsm", this);
const XMLNS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
const PREF_APP_UPDATE_ELEVATE_NEVER = "app.update.elevate.never";
const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
const PREF_APP_UPDATE_LOG = "app.update.log";
const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
const PREF_APP_UPDATE_TEST_LOOP = "app.update.test.loop";
const PREF_APP_UPDATE_URL_MANUAL = "app.update.url.manual";
const PREFBRANCH_APP_UPDATE_NEVER = "app.update.never.";
const UPDATE_TEST_LOOP_INTERVAL = 2000;
const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
const STATE_DOWNLOADING = "downloading";
const STATE_PENDING = "pending";
const STATE_PENDING_SERVICE = "pending-service";
const STATE_PENDING_ELEVATE = "pending-elevate";
const STATE_APPLYING = "applying";
const STATE_APPLIED = "applied";
const STATE_APPLIED_SERVICE = "applied-service";
const STATE_SUCCEEDED = "succeeded";
const STATE_DOWNLOAD_FAILED = "download-failed";
const STATE_FAILED = "failed";
const SRCEVT_FOREGROUND = 1;
const SRCEVT_BACKGROUND = 2;
const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
var gLogEnabled = false;
var gUpdatesFoundPageId;
// Notes:
// 1. use the wizard's goTo method whenever possible to change the wizard
// page since it is simpler than most other methods and behaves nicely with
// mochitests.
// 2. using a page's onPageShow method to then change to a different page will
// of course call that page's onPageShow method which can make mochitests
// overly complicated and fragile so avoid doing this if at all possible.
// This is why a page's next attribute is set prior to the page being shown
// whenever possible.
/**
* Logs a string to the error console.
* @param string
* The string to write to the error console..
*/
function LOG(module, string) {
if (gLogEnabled) {
dump("*** AUS:UI " + module + ":" + string + "\n");
Services.console.logStringMessage("AUS:UI " + module + ":" + string);
}
}
/**
* Opens a URL using the event target's url attribute for the URL. This is a
* workaround for Bug 263433 which prevents respecting tab browser preferences
* for where to open a URL.
*/
function openUpdateURL(event) {
if (event.button == 0)
openURL(event.target.getAttribute("url"));
}
/**
* A set of shared data and control functions for the wizard as a whole.
*/
var gUpdates = {
/**
* The nsIUpdate object being used by this window (either for downloading,
* notification or both).
*/
update: null,
/**
* The updates.properties <stringbundle> element.
*/
strings: null,
/**
* The Application brandShortName (e.g. "Firefox")
*/
brandName: null,
/**
* The <wizard> element
*/
wiz: null,
/**
* Whether to run the unload handler. This will be set to false when the user
* exits the wizard via onWizardCancel or onWizardFinish.
*/
_runUnload: true,
/**
* Helper function for setButtons
* Resets button to original label & accesskey if string is null.
*/
_setButton: function(button, string) {
if (string) {
var label = this.getAUSString(string);
if (label.indexOf("%S") != -1)
label = label.replace(/%S/, this.brandName);
button.label = label;
button.setAttribute("accesskey",
this.getAUSString(string + ".accesskey"));
} else {
button.label = button.defaultLabel;
button.setAttribute("accesskey", button.defaultAccesskey);
}
},
/**
* Sets the attributes needed for this Wizard's control buttons (labels,
* disabled, hidden, etc.)
* @param extra1ButtonString
* The property in the stringbundle containing the label to put on
* the first extra button, or null to hide the first extra button.
* @param extra2ButtonString
* The property in the stringbundle containing the label to put on
* the second extra button, or null to hide the second extra button.
* @param nextFinishButtonString
* The property in the stringbundle containing the label to put on
* the Next / Finish button, or null to hide the button. The Next and
* Finish buttons are never displayed at the same time in a wizard
* with the the Finish button only being displayed when there are no
* additional pages to display in the wizard.
* @param canAdvance
* true if the wizard can be advanced (e.g. the next / finish button
* should be enabled), false otherwise.
* @param showCancel
* true if the wizard's cancel button should be shown, false
* otherwise. If not specified this will default to false.
*
* Note:
* Per Bug 324121 the wizard should not look like a wizard and to accomplish
* this the back button is never displayed. This causes the wizard buttons to
* be arranged as follows on Windows with the next and finish buttons never
* being displayed at the same time.
* +--------------------------------------------------------------+
* | [ extra1 ] [ extra2 ] [ next or finish ] |
* +--------------------------------------------------------------+
*/
setButtons: function(extra1ButtonString, extra2ButtonString,
nextFinishButtonString, canAdvance, showCancel) {
this.wiz.canAdvance = canAdvance;
var bnf = this.wiz.getButton(this.wiz.onLastPage ? "finish" : "next");
var be1 = this.wiz.getButton("extra1");
var be2 = this.wiz.getButton("extra2");
var bc = this.wiz.getButton("cancel");
// Set the labels for the next / finish, extra1, and extra2 buttons
this._setButton(bnf, nextFinishButtonString);
this._setButton(be1, extra1ButtonString);
this._setButton(be2, extra2ButtonString);
bnf.hidden = bnf.disabled = !nextFinishButtonString;
be1.hidden = be1.disabled = !extra1ButtonString;
be2.hidden = be2.disabled = !extra2ButtonString;
bc.hidden = bc.disabled = !showCancel;
// Hide and disable the back button each time setButtons is called
// (see bug 464765).
var btn = this.wiz.getButton("back");
btn.hidden = btn.disabled = true;
// Hide and disable the finish button if not on the last page or the next
// button if on the last page each time setButtons is called.
btn = this.wiz.getButton(this.wiz.onLastPage ? "next" : "finish");
btn.hidden = btn.disabled = true;
},
getAUSString: function(key, strings) {
if (strings)
return this.strings.getFormattedString(key, strings);
return this.strings.getString(key);
},
never: function () {
// If the user clicks "No Thanks", we should not prompt them to update to
// this version again unless they manually select "Check for Updates..."
// which will clear all of the "never" prefs. There are currently two
// "never" prefs: the older PREFBRANCH_APP_UPDATE_NEVER as well as the
// OSX-only PREF_APP_UPDATE_ELEVATE_NEVER. We set both of these prefs (if
// applicable) to ensure that we don't prompt the user regardless of which
// pref is checked.
let neverPrefName = PREFBRANCH_APP_UPDATE_NEVER + this.update.appVersion;
Services.prefs.setBoolPref(neverPrefName, true);
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (aus.elevationRequired) {
Services.prefs.setCharPref(PREF_APP_UPDATE_ELEVATE_NEVER,
this.update.appVersion);
}
},
/**
* A hash of |pageid| attribute to page object. Can be used to dispatch
* function calls to the appropriate page.
*/
_pages: { },
/**
* Called when the user presses the "Finish" button on the wizard, dispatches
* the function call to the selected page.
*/
onWizardFinish: function() {
this._runUnload = false;
var pageid = document.documentElement.currentPage.pageid;
if ("onWizardFinish" in this._pages[pageid])
this._pages[pageid].onWizardFinish();
},
/**
* Called when the user presses the "Cancel" button on the wizard, dispatches
* the function call to the selected page.
*/
onWizardCancel: function() {
this._runUnload = false;
var pageid = document.documentElement.currentPage.pageid;
if ("onWizardCancel" in this._pages[pageid])
this._pages[pageid].onWizardCancel();
},
/**
* Called when the user presses the "Next" button on the wizard, dispatches
* the function call to the selected page.
*/
onWizardNext: function() {
var cp = document.documentElement.currentPage;
if (!cp)
return;
var pageid = cp.pageid;
if ("onWizardNext" in this._pages[pageid])
this._pages[pageid].onWizardNext();
},
/**
* The checking process that spawned this update UI. There are two types:
* SRCEVT_FOREGROUND:
* Some user-generated event caused this UI to appear, e.g. the Help
* menu item or the button in preferences. When in this mode, the UI
* should remain active for the duration of the download.
* SRCEVT_BACKGROUND:
* A background update check caused this UI to appear, probably because
* the user has the app.update.auto preference set to false.
*/
sourceEvent: SRCEVT_FOREGROUND,
/**
* Helper function for onLoad
* Saves default button label & accesskey for use by _setButton
*/
_cacheButtonStrings: function (buttonName) {
var button = this.wiz.getButton(buttonName);
button.defaultLabel = button.label;
button.defaultAccesskey = button.getAttribute("accesskey");
},
/**
* Called when the wizard UI is loaded.
*/
onLoad: function() {
this.wiz = document.documentElement;
gLogEnabled = Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
this.strings = document.getElementById("updateStrings");
var brandStrings = document.getElementById("brandStrings");
this.brandName = brandStrings.getString("brandShortName");
var pages = this.wiz.childNodes;
for (var i = 0; i < pages.length; ++i) {
var page = pages[i];
if (page.localName == "wizardpage")
this._pages[page.pageid] = eval(page.getAttribute("object"));
}
// Cache the standard button labels in case we need to restore them
this._cacheButtonStrings("next");
this._cacheButtonStrings("finish");
this._cacheButtonStrings("extra1");
this._cacheButtonStrings("extra2");
// Advance to the Start page.
this.getStartPageID(function(startPageID) {
LOG("gUpdates", "onLoad - setting current page to startpage " + startPageID);
gUpdates.wiz.currentPage = document.getElementById(startPageID);
});
},
/**
* Called when the wizard UI is unloaded.
*/
onUnload: function() {
if (this._runUnload) {
var cp = this.wiz.currentPage;
if (cp.pageid != "finished" && cp.pageid != "finishedBackground")
this.onWizardCancel();
}
},
/**
* Gets the ID of the <wizardpage> object that should be displayed first. This
* is an asynchronous method that passes the resulting object to a callback
* function.
*
* This is determined by how we were called by the update prompt:
*
* Prompt Method: Arg0: Update State: Src Event: Failed: Result:
* showUpdateAvailable nsIUpdate obj -- background -- updatesfoundbasic
* showUpdateDownloaded nsIUpdate obj pending background -- finishedBackground
* showUpdateError nsIUpdate obj failed either partial errorpatching
* showUpdateError nsIUpdate obj failed either complete errors
* checkForUpdates null -- foreground -- checking
* checkForUpdates null downloading foreground -- downloading
*
* @param aCallback
* A callback to pass the <wizardpage> object to be displayed first to.
*/
getStartPageID: function(aCallback) {
if ("arguments" in window && window.arguments[0]) {
var arg0 = window.arguments[0];
if (arg0 instanceof CoI.nsIUpdate) {
// If the first argument is a nsIUpdate object, we are notifying the
// user that the background checking found an update that requires
// their permission to install, and it's ready for download.
this.setUpdate(arg0);
if (this.update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
aCallback("errorextra");
return;
}
if (this.update.unsupported) {
aCallback("unsupported");
return;
}
var p = this.update.selectedPatch;
if (p) {
let state = p.state;
let patchFailed = this.update.getProperty("patchingFailed");
if (patchFailed) {
if (patchFailed != "partial" || this.update.patchCount != 2) {
// If the complete patch failed, which is far less likely, show
// the error text held by the update object in the generic errors
// page, triggered by the |STATE_DOWNLOAD_FAILED| state. This also
// handles the case when an elevation was cancelled on Mac OS X.
state = STATE_DOWNLOAD_FAILED;
} else {
// If the system failed to apply the partial patch, show the
// screen which best describes this condition, which is triggered
// by the |STATE_FAILED| state.
state = STATE_FAILED;
}
}
// Now select the best page to start with, given the current state of
// the Update.
switch (state) {
case STATE_PENDING:
case STATE_PENDING_SERVICE:
case STATE_PENDING_ELEVATE:
case STATE_APPLIED:
case STATE_APPLIED_SERVICE:
this.sourceEvent = SRCEVT_BACKGROUND;
aCallback("finishedBackground");
return;
case STATE_DOWNLOADING:
aCallback("downloading");
return;
case STATE_FAILED:
window.getAttention();
aCallback("errorpatching");
return;
case STATE_DOWNLOAD_FAILED:
case STATE_APPLYING:
aCallback("errors");
return;
}
}
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (!aus.canApplyUpdates) {
aCallback("manualUpdate");
return;
}
aCallback(this.updatesFoundPageId);
return;
}
}
else {
var um = CoC["@mozilla.org/updates/update-manager;1"].
getService(CoI.nsIUpdateManager);
if (um.activeUpdate) {
this.setUpdate(um.activeUpdate);
aCallback("downloading");
return;
}
}
aCallback("checking");
},
/**
* Returns the string page ID for the appropriate updates found page based
* on the update's metadata.
*/
get updatesFoundPageId() {
if (gUpdatesFoundPageId)
return gUpdatesFoundPageId;
return gUpdatesFoundPageId = "updatesfoundbasic";
},
/**
* Sets the Update object for this wizard
* @param update
* The update object
*/
setUpdate: function(update) {
this.update = update;
if (this.update)
this.update.QueryInterface(CoI.nsIWritablePropertyBag);
}
};
/**
* The "Checking for Updates" page. Provides feedback on the update checking
* process.
*/
var gCheckingPage = {
/**
* The nsIUpdateChecker that is currently checking for updates. We hold onto
* this so we can cancel the update check if the user closes the window.
*/
_checker: null,
/**
* Initialize
*/
onPageShow: function() {
gUpdates.setButtons(null, null, null, false, true);
gUpdates.wiz.getButton("cancel").focus();
// Clear all of the "never" prefs to handle the scenario where the user
// clicked "never" for an update, selected "Check for Updates...", and
// then canceled. If we don't clear the "never" prefs future
// notifications will never happen.
Services.prefs.deleteBranch(PREFBRANCH_APP_UPDATE_NEVER);
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
}
// The user will be notified if there is an error so clear the background
// check error count.
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
}
// The preference will be set back to true if the system is still
// unsupported.
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED);
}
this._checker = CoC["@mozilla.org/updates/update-checker;1"].
createInstance(CoI.nsIUpdateChecker);
this._checker.checkForUpdates(this.updateListener, true);
},
/**
* The user has closed the window, either by pressing cancel or using a Window
* Manager control, so stop checking for updates.
*/
onWizardCancel: function() {
this._checker.stopChecking(CoI.nsIUpdateChecker.CURRENT_CHECK);
},
/**
* An object implementing nsIUpdateCheckListener that is notified as the
* update check commences.
*/
updateListener: {
/**
* See nsIUpdateCheckListener
*/
onCheckComplete: function(request, updates, updateCount) {
var aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
gUpdates.setUpdate(aus.selectUpdate(updates, updates.length));
if (gUpdates.update) {
LOG("gCheckingPage", "onCheckComplete - update found");
if (gUpdates.update.unsupported) {
gUpdates.wiz.goTo("unsupported");
return;
}
if (!aus.canApplyUpdates || gUpdates.update.elevationFailure) {
// Prevent multiple notifications for the same update when the user is
// unable to apply updates.
gUpdates.never();
gUpdates.wiz.goTo("manualUpdate");
return;
}
gUpdates.wiz.goTo(gUpdates.updatesFoundPageId);
return;
}
LOG("gCheckingPage", "onCheckComplete - no update found");
gUpdates.wiz.goTo("noupdatesfound");
},
/**
* See nsIUpdateCheckListener
*/
onError: function(request, update) {
LOG("gCheckingPage", "onError - proceeding to error page");
gUpdates.setUpdate(update);
gUpdates.wiz.goTo("errors");
},
/**
* See nsISupports.idl
*/
QueryInterface: function(aIID) {
if (!aIID.equals(CoI.nsIUpdateCheckListener) &&
!aIID.equals(CoI.nsISupports))
throw CoR.NS_ERROR_NO_INTERFACE;
return this;
}
}
};
/**
* The "No Updates Are Available" page
*/
var gNoUpdatesPage = {
/**
* Initialize
*/
onPageShow: function() {
LOG("gNoUpdatesPage", "onPageShow - could not select an appropriate " +
"update. Either there were no updates or |selectUpdate| failed");
if (Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED, true))
document.getElementById("noUpdatesAutoEnabled").hidden = false;
else
document.getElementById("noUpdatesAutoDisabled").hidden = false;
gUpdates.setButtons(null, null, "okButton", true);
gUpdates.wiz.getButton("finish").focus();
}
};
/**
* The "Unable to Update" page. Provides the user information about why they
* were unable to update and a manual download url.
*/
var gManualUpdatePage = {
onPageShow: function() {
var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
var manualUpdateLinkLabel = document.getElementById("manualUpdateLinkLabel");
manualUpdateLinkLabel.value = manualURL;
manualUpdateLinkLabel.setAttribute("url", manualURL);
gUpdates.setButtons(null, null, "okButton", true);
gUpdates.wiz.getButton("finish").focus();
}
};
/**
* The "System Unsupported" page. Provides the user with information about their
* system no longer being supported and an url for more information.
*/
var gUnsupportedPage = {
onPageShow: function() {
Services.prefs.setBoolPref(PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, true);
if (gUpdates.update.detailsURL) {
let unsupportedLinkLabel = document.getElementById("unsupportedLinkLabel");
unsupportedLinkLabel.setAttribute("url", gUpdates.update.detailsURL);
}
gUpdates.setButtons(null, null, "okButton", true);
gUpdates.wiz.getButton("finish").focus();
}
};
/**
* The "Updates Are Available" page. Provides the user information about the
* available update.
*/
var gUpdatesFoundBasicPage = {
/**
* Initialize
*/
onPageShow: function() {
gUpdates.wiz.canRewind = false;
var update = gUpdates.update;
gUpdates.setButtons("askLaterButton",
update.showNeverForVersion ? "noThanksButton" : null,
"updateButton_" + update.type, true);
var btn = gUpdates.wiz.getButton("next");
btn.focus();
var updateName = update.name;
if (update.channel == "nightly") {
updateName = gUpdates.getAUSString("updateNightlyName",
[gUpdates.brandName,
update.displayVersion,
update.buildID]);
}
var updateNameElement = document.getElementById("updateName");
updateNameElement.value = updateName;
var introText = gUpdates.getAUSString("intro_" + update.type,
[gUpdates.brandName, update.displayVersion]);
var introElem = document.getElementById("updatesFoundInto");
introElem.setAttribute("severity", update.type);
introElem.textContent = introText;
var updateMoreInfoURL = document.getElementById("updateMoreInfoURL");
if (update.detailsURL)
updateMoreInfoURL.setAttribute("url", update.detailsURL);
else
updateMoreInfoURL.hidden = true;
var updateTitle = gUpdates.getAUSString("updatesfound_" + update.type +
".title");
document.getElementById("updatesFoundBasicHeader").setAttribute("label", updateTitle);
},
onExtra1: function() {
gUpdates.wiz.cancel();
},
onExtra2: function() {
gUpdates.never();
gUpdates.wiz.cancel();
}
};
/**
* The "Update is Downloading" page - provides feedback for the download
* process plus a pause/resume UI
*/
var gDownloadingPage = {
/**
* DOM Elements
*/
_downloadStatus: null,
_downloadProgress: null,
_pauseButton: null,
/**
* Whether or not we are currently paused
*/
_paused: false,
/**
* Label cache to hold the 'Connecting' string
*/
_label_downloadStatus: null,
/**
* Member variables for updating download status
*/
_lastSec: Infinity,
_startTime: null,
_pausedStatus: "",
_hiding: false,
/**
* Have we registered an observer for a background update being staged
*/
_updateApplyingObserver: false,
/**
* Initialize
*/
onPageShow: function() {
this._downloadStatus = document.getElementById("downloadStatus");
this._downloadProgress = document.getElementById("downloadProgress");
this._pauseButton = document.getElementById("pauseButton");
this._label_downloadStatus = this._downloadStatus.textContent;
this._pauseButton.setAttribute("tooltiptext",
gUpdates.getAUSString("pauseButtonPause"));
// move focus to the pause/resume button and then disable it (bug #353177)
this._pauseButton.focus();
this._pauseButton.disabled = true;
var aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
var um = CoC["@mozilla.org/updates/update-manager;1"].
getService(CoI.nsIUpdateManager);
var activeUpdate = um.activeUpdate;
if (activeUpdate) {
gUpdates.setUpdate(activeUpdate);
// It's possible the update has already been downloaded and is being
// applied by the time this page is shown, depending on how fast the
// download goes and how quickly the 'next' button is clicked to get here.
if (activeUpdate.state == STATE_PENDING ||
activeUpdate.state == STATE_PENDING_ELEVATE ||
activeUpdate.state == STATE_PENDING_SERVICE) {
if (!activeUpdate.getProperty("stagingFailed")) {
gUpdates.setButtons("hideButton", null, null, false);
gUpdates.wiz.getButton("extra1").focus();
this._setUpdateApplying();
return;
}
gUpdates.wiz.goTo("finished");
return;
}
}
if (!gUpdates.update) {
LOG("gDownloadingPage", "onPageShow - no valid update to download?!");
return;
}
this._startTime = Date.now();
try {
// Say that this was a foreground download, not a background download,
// since the user cared enough to look in on this process.
gUpdates.update.QueryInterface(CoI.nsIWritablePropertyBag);
gUpdates.update.setProperty("foregroundDownload", "true");
// Pause any active background download and restart it as a foreground
// download.
aus.pauseDownload();
var state = aus.downloadUpdate(gUpdates.update, false);
if (state == "failed") {
// We've tried as hard as we could to download a valid update -
// we fell back from a partial patch to a complete patch and even
// then we couldn't validate. Show a validation error with instructions
// on how to manually update.
this.cleanUp();
gUpdates.wiz.goTo("errors");
return;
}
// Add this UI as a listener for active downloads
aus.addDownloadListener(this);
if (activeUpdate)
this._setUIState(!aus.isDownloading);
}
catch (e) {
LOG("gDownloadingPage", "onPageShow - error: " + e);
}
gUpdates.setButtons("hideButton", null, null, false);
gUpdates.wiz.getButton("extra1").focus();
},
/**
* Updates the text status message
*/
_setStatus: function(status) {
// Don't bother setting the same text more than once. This can happen
// due to the asynchronous behavior of the downloader.
if (this._downloadStatus.textContent == status)
return;
while (this._downloadStatus.hasChildNodes())
this._downloadStatus.removeChild(this._downloadStatus.firstChild);
this._downloadStatus.appendChild(document.createTextNode(status));
},
/**
* Update download progress status to show time left, speed, and progress.
* Also updates the status needed for pausing the download.
*
* @param aCurr
* Current number of bytes transferred
* @param aMax
* Total file size of the download
* @return Current active download status
*/
_updateDownloadStatus: function(aCurr, aMax) {
let status;
// Get the download time left and progress
let rate = aCurr / (Date.now() - this._startTime) * 1000;
[status, this._lastSec] =
DownloadUtils.getDownloadStatus(aCurr, aMax, rate, this._lastSec);
// Get the download progress for pausing
this._pausedStatus = DownloadUtils.getTransferTotal(aCurr, aMax);
return status;
},
/**
* Adjust UI to suit a certain state of paused-ness
* @param paused
* Whether or not the download is paused
*/
_setUIState: function(paused) {
var u = gUpdates.update;
if (paused) {
if (this._downloadProgress.mode != "normal")
this._downloadProgress.mode = "normal";
this._pauseButton.setAttribute("tooltiptext",
gUpdates.getAUSString("pauseButtonResume"));
this._pauseButton.setAttribute("paused", "true");
var p = u.selectedPatch.QueryInterface(CoI.nsIPropertyBag);
var status = p.getProperty("status");
if (status) {
let pausedStatus = gUpdates.getAUSString("downloadPausedStatus", [status]);
this._setStatus(pausedStatus);
}
}
else {
if (this._downloadProgress.mode != "undetermined")
this._downloadProgress.mode = "undetermined";
this._pauseButton.setAttribute("paused", "false");
this._pauseButton.setAttribute("tooltiptext",
gUpdates.getAUSString("pauseButtonPause"));
this._setStatus(this._label_downloadStatus);
}
},
/**
* Wait for an update being staged in the background.
*/
_setUpdateApplying: function() {
this._downloadProgress.mode = "undetermined";
this._pauseButton.hidden = true;
let applyingStatus = gUpdates.getAUSString("applyingUpdate");
this._setStatus(applyingStatus);
Services.obs.addObserver(this, "update-staged", false);
this._updateApplyingObserver = true;
},
/**
* Clean up the listener and observer registered for the wizard.
*/
cleanUp: function() {
var aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
aus.removeDownloadListener(this);
if (this._updateApplyingObserver) {
Services.obs.removeObserver(this, "update-staged");
this._updateApplyingObserver = false;
}
},
/**
* When the user clicks the Pause/Resume button
*/
onPause: function() {
var aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (this._paused)
aus.downloadUpdate(gUpdates.update, false);
else {
var patch = gUpdates.update.selectedPatch;
patch.QueryInterface(CoI.nsIWritablePropertyBag);
patch.setProperty("status", this._pausedStatus);
aus.pauseDownload();
}
this._paused = !this._paused;
// Update the UI
this._setUIState(this._paused);
},
/**
* When the user has closed the window using a Window Manager control (this
* page doesn't have a cancel button) cancel the update in progress.
*/
onWizardCancel: function() {
if (this._hiding)
return;
this.cleanUp();
},
/**
* When the user closes the Wizard UI by clicking the Hide button
*/
onHide: function() {
// Set _hiding to true to prevent onWizardCancel from cancelling the update
// that is in progress.
this._hiding = true;
// Remove ourself as a download listener so that we don't continue to be
// fed progress and state notifications after the UI we're updating has
// gone away.
this.cleanUp();
var aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
var um = CoC["@mozilla.org/updates/update-manager;1"].
getService(CoI.nsIUpdateManager);
um.activeUpdate = gUpdates.update;
// If the download was paused by the user, ask the user if they want to
// have the update resume in the background.
var downloadInBackground = true;
if (this._paused) {
var title = gUpdates.getAUSString("resumePausedAfterCloseTitle");
var message = gUpdates.getAUSString("resumePausedAfterCloseMsg",
[gUpdates.brandName]);
var ps = Services.prompt;
var flags = ps.STD_YES_NO_BUTTONS;
// Focus the software update wizard before prompting. This will raise
// the software update wizard if it is minimized making it more obvious
// what the prompt is for and will solve the problem of windows
// obscuring the prompt. See bug #350299 for more details.
window.focus();
var rv = ps.confirmEx(window, title, message, flags, null, null, null,
null, { });
if (rv == CoI.nsIPromptService.BUTTON_POS_0)
downloadInBackground = false;
}
if (downloadInBackground) {
// Continue download in the background at full speed.
LOG("gDownloadingPage", "onHide - continuing download in background " +
"at full speed");
aus.downloadUpdate(gUpdates.update, false);
}
gUpdates.wiz.cancel();
},
/**
* When the data transfer begins
* @param request
* The nsIRequest object for the transfer
* @param context
* Additional data
*/
onStartRequest: function(request, context) {
// This !paused test is necessary because onStartRequest may fire after
// the download was paused (for those speedy clickers...)
if (this._paused)
return;
if (this._downloadProgress.mode != "undetermined")
this._downloadProgress.mode = "undetermined";
this._setStatus(this._label_downloadStatus);
},
/**
* When new data has been downloaded
* @param request
* The nsIRequest object for the transfer
* @param context
* Additional data
* @param progress
* The current number of bytes transferred
* @param maxProgress
* The total number of bytes that must be transferred
*/
onProgress: function(request, context, progress, maxProgress) {
let status = this._updateDownloadStatus(progress, maxProgress);
var currentProgress = Math.round(100 * (progress / maxProgress));
var p = gUpdates.update.selectedPatch;
p.QueryInterface(CoI.nsIWritablePropertyBag);
p.setProperty("progress", currentProgress);
p.setProperty("status", status);
// This !paused test is necessary because onProgress may fire after
// the download was paused (for those speedy clickers...)
if (this._paused)
return;
if (this._downloadProgress.mode != "normal")
this._downloadProgress.mode = "normal";
if (this._downloadProgress.value != currentProgress)
this._downloadProgress.value = currentProgress;
if (this._pauseButton.disabled)
this._pauseButton.disabled = false;
// If the update has completed downloading and the download status contains
// the original text return early to avoid an assertion in debug builds.
// Since the page will advance immmediately due to the update completing the
// download updating the status is not important.
// nsTextFrame::GetTrimmedOffsets 'Can only call this on frames that have
// been reflowed'.
if (progress == maxProgress &&
this._downloadStatus.textContent == this._label_downloadStatus)
return;
this._setStatus(status);
},
/**
* When we have new status text
* @param request
* The nsIRequest object for the transfer
* @param context
* Additional data
* @param status
* A status code
* @param statusText
* Human readable version of |status|
*/
onStatus: function(request, context, status, statusText) {
this._setStatus(statusText);
},
/**
* When data transfer ceases
* @param request
* The nsIRequest object for the transfer
* @param context
* Additional data
* @param status
* Status code containing the reason for the cessation.
*/
onStopRequest: function(request, context, status) {
if (this._downloadProgress.mode != "normal")
this._downloadProgress.mode = "normal";
var u = gUpdates.update;
switch (status) {
case CoR.NS_ERROR_CORRUPTED_CONTENT:
case CoR.NS_ERROR_UNEXPECTED:
if (u.selectedPatch.state == STATE_DOWNLOAD_FAILED &&
(u.isCompleteUpdate || u.patchCount != 2)) {
// Verification error of complete patch, informational text is held in
// the update object.
this.cleanUp();
gUpdates.wiz.goTo("errors");
break;
}
// Verification failed for a partial patch, complete patch is now
// downloading so return early and do NOT remove the download listener!
// Reset the progress meter to "undertermined" mode so that we don't
// show old progress for the new download of the "complete" patch.
this._downloadProgress.mode = "undetermined";
this._pauseButton.disabled = true;
document.getElementById("verificationFailed").hidden = false;
break;
case CoR.NS_BINDING_ABORTED:
LOG("gDownloadingPage", "onStopRequest - pausing download");
// Do not remove UI listener since the user may resume downloading again.
break;
case CoR.NS_OK:
LOG("gDownloadingPage", "onStopRequest - patch verification succeeded");
// If the background update pref is set, we should wait until the update
// is actually staged in the background.
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (aus.canStageUpdates) {
this._setUpdateApplying();
} else {
this.cleanUp();
gUpdates.wiz.goTo("finished");
}
break;
default:
LOG("gDownloadingPage", "onStopRequest - transfer failed");
// Some kind of transfer error, die.
this.cleanUp();
gUpdates.wiz.goTo("errors");
break;
}
},
/**
* See nsIObserver.idl
*/
observe: function(aSubject, aTopic, aData) {
if (aTopic == "update-staged") {
if (aData == STATE_DOWNLOADING) {
// We've fallen back to downloding the full update because the
// partial update failed to get staged in the background.
this._setStatus("downloading");
return;
}
this.cleanUp();
if (aData == STATE_APPLIED ||
aData == STATE_APPLIED_SERVICE ||
aData == STATE_PENDING ||
aData == STATE_PENDING_SERVICE ||
aData == STATE_PENDING_ELEVATE) {
// If the update is successfully applied, or if the updater has
// fallen back to non-staged updates, go to the finish page.
gUpdates.wiz.goTo("finished");
} else {
gUpdates.wiz.goTo("errors");
}
}
},
/**
* See nsISupports.idl
*/
QueryInterface: function(iid) {
if (!iid.equals(CoI.nsIRequestObserver) &&
!iid.equals(CoI.nsIProgressEventSink) &&
!iid.equals(CoI.nsIObserver) &&
!iid.equals(CoI.nsISupports))
throw CoR.NS_ERROR_NO_INTERFACE;
return this;
}
};
/**
* The "There was an error during the update" page.
*/
var gErrorsPage = {
/**
* Initialize
*/
onPageShow: function() {
gUpdates.setButtons(null, null, "okButton", true);
gUpdates.wiz.getButton("finish").focus();
var statusText = gUpdates.update.statusText;
LOG("gErrorsPage", "onPageShow - update.statusText: " + statusText);
var errorReason = document.getElementById("errorReason");
errorReason.value = statusText;
var manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
var errorLinkLabel = document.getElementById("errorLinkLabel");
errorLinkLabel.value = manualURL;
errorLinkLabel.setAttribute("url", manualURL);
}
};
/**
* The page shown when there is a background check or a certificate attribute
* error.
*/
var gErrorExtraPage = {
/**
* Initialize
*/
onPageShow: function() {
gUpdates.setButtons(null, null, "okButton", true);
gUpdates.wiz.getButton("finish").focus();
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) {
Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
}
document.getElementById("genericBackgroundErrorLabel").hidden = false;
let manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
let errorLinkLabel = document.getElementById("errorExtraLinkLabel");
errorLinkLabel.value = manualURL;
errorLinkLabel.setAttribute("url", manualURL);
}
};
/**
* The "There was an error applying a partial patch" page.
*/
var gErrorPatchingPage = {
/**
* Initialize
*/
onPageShow: function() {
gUpdates.setButtons(null, null, "okButton", true);
},
onWizardNext: function() {
switch (gUpdates.update.selectedPatch.state) {
case STATE_APPLIED:
case STATE_APPLIED_SERVICE:
gUpdates.wiz.goTo("finished");
break;
case STATE_PENDING:
case STATE_PENDING_SERVICE:
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (!aus.canStageUpdates) {
gUpdates.wiz.goTo("finished");
break;
}
// intentional fallthrough
case STATE_DOWNLOADING:
gUpdates.wiz.goTo("downloading");
break;
case STATE_DOWNLOAD_FAILED:
gUpdates.wiz.goTo("errors");
break;
}
}
};
/**
* The "Update has been downloaded" page. Shows information about what
* was downloaded.
*/
var gFinishedPage = {
/**
* Initialize
*/
onPageShow: function() {
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (aus.elevationRequired) {
LOG("gFinishedPage", "elevationRequired");
gUpdates.setButtons("restartLaterButton", "noThanksButton",
"restartNowButton", true);
} else {
LOG("gFinishedPage", "not elevationRequired");
gUpdates.setButtons("restartLaterButton", null, "restartNowButton",
true);
}
gUpdates.wiz.getButton("finish").focus();
},
/**
* Initialize the Wizard Page for a Background Source Event
*/
onPageShowBackground: function() {
this.onPageShow();
let updateFinishedName = document.getElementById("updateFinishedName");
updateFinishedName.value = gUpdates.update.name;
let link = document.getElementById("finishedBackgroundLink");
if (gUpdates.update.detailsURL) {
link.setAttribute("url", gUpdates.update.detailsURL);
// The details link is stealing focus so it is disabled by default and
// should only be enabled after onPageShow has been called.
link.disabled = false;
} else {
link.hidden = true;
}
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (aus.elevationRequired) {
let more = document.getElementById("finishedBackgroundMore");
more.setAttribute("hidden", "true");
let moreElevated =
document.getElementById("finishedBackgroundMoreElevated");
moreElevated.setAttribute("hidden", "false");
let moreElevatedLink =
document.getElementById("finishedBackgroundMoreElevatedLink");
moreElevatedLink.setAttribute("hidden", "false");
let moreElevatedLinkLabel =
document.getElementById("finishedBackgroundMoreElevatedLinkLabel");
let manualURL = Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_MANUAL);
moreElevatedLinkLabel.value = manualURL;
moreElevatedLinkLabel.setAttribute("url", manualURL);
moreElevatedLinkLabel.setAttribute("hidden", "false");
}
if (Services.prefs.getBoolPref(PREF_APP_UPDATE_TEST_LOOP, false)) {
setTimeout(function () { gUpdates.wiz.getButton("finish").click(); },
UPDATE_TEST_LOOP_INTERVAL);
}
},
/**
* Called when the wizard finishes, i.e. the "Restart Now" button is
* clicked.
*/
onWizardFinish: function() {
// Do the restart
LOG("gFinishedPage", "onWizardFinish - restarting the application");
let aus = CoC["@mozilla.org/updates/update-service;1"].
getService(CoI.nsIApplicationUpdateService);
if (aus.elevationRequired) {
let um = CoC["@mozilla.org/updates/update-manager;1"].
getService(CoI.nsIUpdateManager);
if (um) {
um.elevationOptedIn();
}
}
// disable the "finish" (Restart) and "extra1" (Later) buttons
// because the Software Update wizard is still up at the point,
// and will remain up until we return and we close the
// window with a |window.close()| in wizard.xml
// (it was the firing the "wizardfinish" event that got us here.)
// This prevents the user from switching back
// to the Software Update dialog and clicking "Restart" or "Later"
// when dealing with the "confirm close" prompts.
// See bug #350299 for more details.
gUpdates.wiz.getButton("finish").disabled = true;
gUpdates.wiz.getButton("extra1").disabled = true;
// Notify all windows that an application quit has been requested.
var cancelQuit = CoC["@mozilla.org/supports-PRBool;1"].
createInstance(CoI.nsISupportsPRBool);
Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
"restart");
// Something aborted the quit process.
if (cancelQuit.data)
return;
// If already in safe mode restart in safe mode (bug 327119)
if (Services.appinfo.inSafeMode) {
let env = CoC["@mozilla.org/process/environment;1"].
getService(CoI.nsIEnvironment);
env.set("MOZ_SAFE_MODE_RESTART", "1");
}
// Restart the application
CoC["@mozilla.org/toolkit/app-startup;1"].getService(CoI.nsIAppStartup).
quit(CoI.nsIAppStartup.eAttemptQuit | CoI.nsIAppStartup.eRestart);
},
/**
* When the user clicks the "Restart Later" instead of the Restart Now" button
* in the wizard after an update has been downloaded.
*/
onExtra1: function() {
gUpdates.wiz.cancel();
},
/**
* When elevation is required and the user clicks "No Thanks" in the wizard.
*/
onExtra2: Task.async(function*() {
Services.obs.notifyObservers(null, "update-canceled", null);
let um = CoC["@mozilla.org/updates/update-manager;1"].
getService(CoI.nsIUpdateManager);
um.cleanupActiveUpdate();
gUpdates.never();
gUpdates.wiz.cancel();
}),
};
/**
* Callback for the Update Prompt to set the current page if an Update Wizard
* window is already found to be open.
* @param pageid
* The ID of the page to switch to
*/
function setCurrentPage(pageid) {
gUpdates.wiz.currentPage = document.getElementById(pageid);
}