gecko/toolkit/mozapps/update/nsUpdateService.js

4983 lines
172 KiB
JavaScript
Raw Normal View History

#filter substitution
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
2012-05-21 04:12:37 -07:00
# 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/.
*/
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
Components.utils.import("resource://gre/modules/AddonManager.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/ctypes.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1";
const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype";
const PREF_APP_UPDATE_AUTO = "app.update.auto";
const PREF_APP_UPDATE_BACKGROUND_INTERVAL = "app.update.download.backgroundInterval";
const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
const PREF_APP_UPDATE_CERTS_BRANCH = "app.update.certs.";
const PREF_APP_UPDATE_CERT_CHECKATTRS = "app.update.cert.checkAttributes";
const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
const PREF_APP_UPDATE_CERT_MAXERRORS = "app.update.cert.maxErrors";
const PREF_APP_UPDATE_CERT_REQUIREBUILTIN = "app.update.cert.requireBuiltIn";
const PREF_APP_UPDATE_CUSTOM = "app.update.custom";
const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
const PREF_APP_UPDATE_METRO_ENABLED = "app.update.metro.enabled";
const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
const PREF_APP_UPDATE_INCOMPATIBLE_MODE = "app.update.incompatible.mode";
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_MODE = "app.update.mode";
const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never.";
const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate";
const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
const PREF_APP_UPDATE_SILENT = "app.update.silent";
const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
const PREF_APP_UPDATE_URL = "app.update.url";
const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors";
const PREF_APP_UPDATE_SERVICE_MAX_ERRORS = "app.update.service.maxErrors";
const PREF_APP_UPDATE_SOCKET_ERRORS = "app.update.socket.maxErrors";
const PREF_APP_UPDATE_RETRY_TIMEOUT = "app.update.socket.retryTimeout";
const PREF_APP_DISTRIBUTION = "distribution.id";
const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
const PREF_APP_B2G_VERSION = "b2g.version";
const PREF_EM_HOTFIX_ID = "extensions.hotfix.id";
const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
const URI_UPDATE_NS = "http://www.mozilla.org/2005/app-update";
const CATEGORY_UPDATE_TIMER = "update-timer";
const KEY_GRED = "GreD";
const KEY_UPDROOT = "UpdRootD";
const KEY_EXECUTABLE = "XREExeF";
#ifdef MOZ_WIDGET_GONK
#define USE_UPDATE_ARCHIVE_DIR
#endif
#ifdef USE_UPDATE_ARCHIVE_DIR
const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD"
#endif
#ifdef XP_WIN
#define CHECK_CAN_USE_SERVICE
#elifdef MOZ_WIDGET_GONK
// In Gonk, the updater will remount the /system partition to move staged files
// into place, so we skip the test here to keep things isolated.
#define CHECK_CAN_USE_SERVICE
#endif
const DIR_UPDATES = "updates";
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
#ifdef XP_MACOSX
const UPDATED_DIR = "Updated.app";
#else
const UPDATED_DIR = "updated";
#endif
const FILE_UPDATE_STATUS = "update.status";
const FILE_UPDATE_VERSION = "update.version";
#ifdef MOZ_WIDGET_ANDROID
const FILE_UPDATE_ARCHIVE = "update.apk";
#else
const FILE_UPDATE_ARCHIVE = "update.mar";
#endif
const FILE_UPDATE_LINK = "update.link";
const FILE_UPDATE_LOG = "update.log";
const FILE_UPDATES_DB = "updates.xml";
const FILE_UPDATE_ACTIVE = "active-update.xml";
const FILE_PERMS_TEST = "update.test";
const FILE_LAST_LOG = "last-update.log";
const FILE_BACKUP_LOG = "backup-update.log";
const FILE_UPDATE_LOCALE = "update.locale";
const STATE_NONE = "null";
const STATE_DOWNLOADING = "downloading";
const STATE_PENDING = "pending";
const STATE_PENDING_SVC = "pending-service";
const STATE_APPLYING = "applying";
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
const STATE_APPLIED = "applied";
const STATE_APPLIED_OS = "applied-os";
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
const STATE_APPLIED_SVC = "applied-service";
const STATE_SUCCEEDED = "succeeded";
const STATE_DOWNLOAD_FAILED = "download-failed";
const STATE_FAILED = "failed";
// From updater/errors.h:
const WRITE_ERROR = 7;
// const UNEXPECTED_ERROR = 8; // Replaced with errors 38-42
const ELEVATION_CANCELED = 9;
// Windows service specific errors
const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 24;
const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 25;
const SERVICE_UPDATER_SIGN_ERROR = 26;
const SERVICE_UPDATER_COMPARE_ERROR = 27;
const SERVICE_UPDATER_IDENTITY_ERROR = 28;
const SERVICE_STILL_APPLYING_ON_SUCCESS = 29;
const SERVICE_STILL_APPLYING_ON_FAILURE = 30;
const SERVICE_UPDATER_NOT_FIXED_DRIVE = 31;
const SERVICE_COULD_NOT_LOCK_UPDATER = 32;
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
const SERVICE_INSTALLDIR_ERROR = 33;
2013-05-23 10:26:44 -07:00
const SERVICE_COULD_NOT_COPY_UPDATER = 49;
const WRITE_ERROR_ACCESS_DENIED = 35;
// const WRITE_ERROR_SHARING_VIOLATION = 36; // Replaced with errors 46-48
const WRITE_ERROR_CALLBACK_APP = 37;
const INVALID_UPDATER_STATUS_CODE = 38;
const UNEXPECTED_BZIP_ERROR = 39;
const UNEXPECTED_MAR_ERROR = 40;
const UNEXPECTED_BSPATCH_ERROR = 41;
const UNEXPECTED_FILE_OPERATION_ERROR = 42;
const FILESYSTEM_MOUNT_READWRITE_ERROR = 43;
const FOTA_GENERAL_ERROR = 44;
const FOTA_UNKNOWN_ERROR = 45;
const WRITE_ERROR_SHARING_VIOLATION_SIGNALED = 46;
const WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID = 47;
const WRITE_ERROR_SHARING_VIOLATION_NOPID = 48;
const FOTA_FILE_OPERATION_ERROR = 49;
const FOTA_RECOVERY_ERROR = 50;
const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100;
const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
const NETWORK_ERROR_OFFLINE = 111;
const FILE_ERROR_TOO_BIG = 112;
// Error codes should be < 1000. Errors above 1000 represent http status codes
const HTTP_ERROR_OFFSET = 1000;
const DOWNLOAD_CHUNK_SIZE = 300000; // bytes
const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds
const DOWNLOAD_FOREGROUND_INTERVAL = 0;
const UPDATE_WINDOW_NAME = "Update:Wizard";
// The number of consecutive failures when updating using the service before
// setting the app.update.service.enabled preference to false.
const DEFAULT_SERVICE_MAX_ERRORS = 10;
// The number of consecutive socket errors to allow before falling back to
// downloading a different MAR file or failing if already downloading the full.
const DEFAULT_SOCKET_MAX_ERRORS = 10;
// The number of milliseconds to wait before retrying a connection error.
const DEFAULT_UPDATE_RETRY_TIMEOUT = 2000;
// A background download is in progress (no notification)
const PING_BGUC_IS_DOWNLOADING = 0;
// An update is staged (no notification)
const PING_BGUC_IS_STAGED = 1;
// Invalid url for app.update.url default preference (no notification)
const PING_BGUC_INVALID_DEFAULT_URL = 2;
// Invalid url for app.update.url user preference (no notification)
const PING_BGUC_INVALID_CUSTOM_URL = 3;
// Invalid url for app.update.url.override user preference (no notification)
const PING_BGUC_INVALID_OVERRIDE_URL = 4;
// Unable to check for updates per gCanCheckForUpdates (no notification)
const PING_BGUC_UNABLE_TO_CHECK = 5;
// Already has an active update in progress (no notification)
const PING_BGUC_HAS_ACTIVEUPDATE = 6;
// Background checks disabled by preference (no notification)
const PING_BGUC_PREF_DISABLED = 7;
// Background checks disabled for the current session (no notification)
const PING_BGUC_DISABLED_FOR_SESSION = 8;
// Background checks disabled in Metro (no notification)
const PING_BGUC_METRO_DISABLED = 9;
// Unable to perform a background check while offline (no notification)
const PING_BGUC_OFFLINE = 10;
// No update found certificate check failed and threshold reached
// (possible mitm attack notification)
const PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY = 11;
// No update found certificate check failed and threshold not reached
// (no notification)
const PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT = 12;
// Update found certificate check failed and threshold reached
// (possible mitm attack notification)
const PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY = 13;
// Update found certificate check failed and threshold not reached
// (no notification)
const PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT = 14;
// General update check failure and threshold reached
// (check failure notification)
const PING_BGUC_GENERAL_ERROR_NOTIFY = 15;
// General update check failure and threshold not reached
// (no notification)
const PING_BGUC_GENERAL_ERROR_SILENT = 16;
// No update found (no notification)
const PING_BGUC_NO_UPDATE_FOUND = 17;
// No compatible update found though there were updates (no notification)
const PING_BGUC_NO_COMPAT_UPDATE_FOUND = 18;
// Update found for a previous version (no notification)
const PING_BGUC_UPDATE_PREVIOUS_VERSION = 19;
// Update found for a version with the never preference set (no notification)
const PING_BGUC_UPDATE_NEVER_PREF = 20;
// Update found without a type attribute (no notification)
const PING_BGUC_UPDATE_INVALID_TYPE = 21;
// The system is no longer supported (system unsupported notification)
const PING_BGUC_UNSUPPORTED = 22;
// Unable to apply updates (manual install to update notification)
const PING_BGUC_UNABLE_TO_APPLY = 23;
// Showing prompt due to the update.xml specifying showPrompt
// (update notification)
const PING_BGUC_SHOWPROMPT_SNIPPET = 24;
// Showing prompt due to preference (update notification)
const PING_BGUC_SHOWPROMPT_PREF = 25;
// Incompatible add-on check disabled by preference (background download)
const PING_BGUC_ADDON_PREF_DISABLED = 26;
// Incompatible add-on not checked not performed due to same update version and
// app version (background download)
const PING_BGUC_ADDON_SAME_APP_VER = 27;
// No incompatible add-ons found during incompatible check (background download)
const PING_BGUC_CHECK_NO_INCOMPAT = 28;
// Incompatible add-ons found and all of them have updates (background download)
const PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT = 29;
// Incompatible add-ons found (update notification)
const PING_BGUC_ADDON_HAVE_INCOMPAT = 30;
var gLocale = null;
#ifdef MOZ_B2G
var gVolumeMountLock = null;
XPCOMUtils.defineLazyGetter(this, "gExtStorage", function aus_gExtStorage() {
return Services.env.get("EXTERNAL_STORAGE");
});
var gSDCardMountLock = null;
#endif
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
"resource://gre/modules/UpdateChannel.jsm");
XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
});
XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
});
// shared code for suppressing bad cert dialogs
XPCOMUtils.defineLazyGetter(this, "gCertUtils", function aus_gCertUtils() {
let temp = { };
Cu.import("resource://gre/modules/CertUtils.jsm", temp);
return temp;
});
XPCOMUtils.defineLazyGetter(this, "gABI", function aus_gABI() {
let abi = null;
try {
abi = Services.appinfo.XPCOMABI;
}
catch (e) {
LOG("gABI - XPCOM ABI unknown: updates are not possible.");
}
#ifdef XP_MACOSX
// Mac universal build should report a different ABI than either macppc
// or mactel.
let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
getService(Ci.nsIMacUtils);
if (macutils.isUniversalBinary)
abi += "-u-" + macutils.architecturesInBinary;
#ifdef MOZ_SHARK
// Disambiguate optimised and shark nightlies
abi += "-shark"
#endif
#endif
return abi;
});
#ifdef MOZ_WIDGET_GONK
XPCOMUtils.defineLazyGetter(this, "gProductModel", function aus_gProductModel() {
Cu.import("resource://gre/modules/systemlibs.js");
return libcutils.property_get("ro.product.model");
});
#endif
XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
let osVersion;
let sysInfo = Cc["@mozilla.org/system-info;1"].
getService(Ci.nsIPropertyBag2);
try {
osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
}
catch (e) {
LOG("gOSVersion - OS Version unknown: updates are not possible.");
}
if (osVersion) {
#ifdef XP_WIN
const BYTE = ctypes.uint8_t;
const WORD = ctypes.uint16_t;
const DWORD = ctypes.uint32_t;
const WCHAR = ctypes.jschar;
const BOOL = ctypes.int;
// This structure is described at:
// http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
const SZCSDVERSIONLENGTH = 128;
const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
[
{dwOSVersionInfoSize: DWORD},
{dwMajorVersion: DWORD},
{dwMinorVersion: DWORD},
{dwBuildNumber: DWORD},
{dwPlatformId: DWORD},
{szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
{wServicePackMajor: WORD},
{wServicePackMinor: WORD},
{wSuiteMask: WORD},
{wProductType: BYTE},
{wReserved: BYTE}
]);
// This structure is described at:
// http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
[
{wProcessorArchitecture: WORD},
{wReserved: WORD},
{dwPageSize: DWORD},
{lpMinimumApplicationAddress: ctypes.voidptr_t},
{lpMaximumApplicationAddress: ctypes.voidptr_t},
{dwActiveProcessorMask: DWORD.ptr},
{dwNumberOfProcessors: DWORD},
{dwProcessorType: DWORD},
{dwAllocationGranularity: DWORD},
{wProcessorLevel: WORD},
{wProcessorRevision: WORD}
]);
let kernel32 = false;
try {
kernel32 = ctypes.open("Kernel32");
} catch (e) {
LOG("gOSVersion - Unable to open kernel32! " + e);
osVersion += ".unknown (unknown)";
}
if(kernel32) {
try {
// Get Service pack info
try {
let GetVersionEx = kernel32.declare("GetVersionExW",
ctypes.default_abi,
BOOL,
OSVERSIONINFOEXW.ptr);
let winVer = OSVERSIONINFOEXW();
winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
if(0 !== GetVersionEx(winVer.address())) {
osVersion += "." + winVer.wServicePackMajor
+ "." + winVer.wServicePackMinor;
} else {
LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
osVersion += ".unknown";
}
} catch (e) {
LOG("gOSVersion - error getting service pack information. Exception: " + e);
osVersion += ".unknown";
}
// Get processor architecture
let arch = "unknown";
try {
let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
ctypes.default_abi,
ctypes.void_t,
SYSTEM_INFO.ptr);
let sysInfo = SYSTEM_INFO();
// Default to unknown
sysInfo.wProcessorArchitecture = 0xffff;
GetNativeSystemInfo(sysInfo.address());
switch(sysInfo.wProcessorArchitecture) {
case 9:
arch = "x64";
break;
case 6:
arch = "IA64";
break;
case 0:
arch = "x86";
break;
}
} catch (e) {
LOG("gOSVersion - error getting processor architecture. Exception: " + e);
} finally {
osVersion += " (" + arch + ")";
}
} finally {
kernel32.close();
}
}
#endif
try {
osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
}
catch (e) {
// Not all platforms have a secondary widget library, so an error is nothing to worry about.
}
osVersion = encodeURIComponent(osVersion);
}
return osVersion;
});
/**
* Tests to make sure that we can write to a given directory.
*
* @param updateTestFile a test file in the directory that needs to be tested.
* @param createDirectory whether a test directory should be created.
* @throws if we don't have right access to the directory.
*/
function testWriteAccess(updateTestFile, createDirectory) {
const NORMAL_FILE_TYPE = Ci.nsILocalFile.NORMAL_FILE_TYPE;
const DIRECTORY_TYPE = Ci.nsILocalFile.DIRECTORY_TYPE;
if (updateTestFile.exists())
updateTestFile.remove(false);
updateTestFile.create(createDirectory ? DIRECTORY_TYPE : NORMAL_FILE_TYPE,
createDirectory ? FileUtils.PERMS_DIRECTORY : FileUtils.PERMS_FILE);
updateTestFile.remove(false);
}
#ifdef XP_WIN
/**
* Closes a Win32 handle
*
* @param handle The handle to close
*/
function closeHandle(handle) {
var lib = ctypes.open("kernel32.dll");
var CloseHandle = lib.declare("CloseHandle",
ctypes.winapi_abi,
ctypes.int32_t, /* success */
ctypes.void_t.ptr); /* handle */
CloseHandle(handle);
lib.close();
}
/**
* Creates a mutex.
*
* @param aAllowExisting false if the function should fail if the mutex exists
* @return The Win32 handle to the mutex
*/
function createMutex(name, aAllowExisting) {
if (aAllowExisting === undefined) {
aAllowExisting = true;
}
const INITIAL_OWN = 1;
const ERROR_ALREADY_EXISTS = 0xB7;
var lib = ctypes.open("kernel32.dll");
var CreateMutexW = lib.declare("CreateMutexW",
ctypes.winapi_abi,
ctypes.void_t.ptr, /* return handle */
ctypes.void_t.ptr, /* security attributes */
ctypes.int32_t, /* initial owner */
ctypes.jschar.ptr); /* name */
var handle = CreateMutexW(null, INITIAL_OWN, name);
var alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
if (handle && !handle.isNull() && !aAllowExisting && alreadyExists) {
closeHandle(handle);
handle = null;
}
lib.close();
if (handle && handle.isNull())
handle = null;
return handle;
}
/**
* Determines a unique mutex name for the installation
*
* @param aGlobal true if the function should return a global mutex. A global
* mutex is valid across different sessions
* @return Global mutex path
*/
function getPerInstallationMutexName(aGlobal) {
if (aGlobal === undefined) {
aGobal = true;
}
let hasher = Cc["@mozilla.org/security/hash;1"].
createInstance(Ci.nsICryptoHash);
hasher.init(hasher.SHA1);
var exeFile = Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsILocalFile);
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
var data = converter.convertToByteArray(exeFile.path.toLowerCase());
hasher.update(data, data.length);
return (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true);
}
#endif // XP_WIN
/**
* Whether or not the current instance has the update mutex. The update mutex
* gives protection against 2 applications from the same installation updating:
* 1) Running multiple profiles from the same installation path
* 2) Running a Metro and Desktop application at the same time from the same
* path
* 3) 2 applications running in 2 different user sessions from the same path
*
* @return true if this instance holds the update mutex
*/
function hasUpdateMutex() {
#ifdef XP_WIN
if (!this._updateMutexHandle) {
this._updateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
}
return !!this._updateMutexHandle;
#else
return true;
#endif // XP_WIN
}
XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
function submitHasPermissionsTelemetryPing(val) {
try {
let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS");
h.add(+val);
} catch(e) {
// Don't allow any exception to be propagated.
Components.utils.reportError(e);
}
}
let useService = false;
if (shouldUseService() && isServiceInstalled()) {
// No need to perform directory write checks, the maintenance service will
// be able to write to all directories.
LOG("gCanApplyUpdates - bypass the write checks because we'll use the service");
useService = true;
}
if (!useService) {
try {
var updateTestFile = getUpdateFile([FILE_PERMS_TEST]);
LOG("gCanApplyUpdates - testing write access " + updateTestFile.path);
testWriteAccess(updateTestFile, false);
#ifdef XP_WIN
var sysInfo = Cc["@mozilla.org/system-info;1"].
getService(Ci.nsIPropertyBag2);
// Example windowsVersion: Windows XP == 5.1
var windowsVersion = sysInfo.getProperty("version");
LOG("gCanApplyUpdates - windowsVersion = " + windowsVersion);
/**
# For Vista, updates can be performed to a location requiring admin
# privileges by requesting elevation via the UAC prompt when launching
# updater.exe if the appDir is under the Program Files directory
# (e.g. C:\Program Files\) and UAC is turned on and we can elevate
# (e.g. user has a split token).
#
# Note: this does note attempt to handle the case where UAC is turned on
# and the installation directory is in a restricted location that
# requires admin privileges to update other than Program Files.
*/
var userCanElevate = false;
if (parseFloat(windowsVersion) >= 6) {
try {
var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties);
// KEY_UPDROOT will fail and throw an exception if
// appDir is not under the Program Files, so we rely on that
var dir = fileLocator.get(KEY_UPDROOT, Ci.nsIFile);
// appDir is under Program Files, so check if the user can elevate
userCanElevate = Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).
userCanElevate;
LOG("gCanApplyUpdates - on Vista, userCanElevate: " + userCanElevate);
}
catch (ex) {
// When the installation directory is not under Program Files,
// fall through to checking if write access to the
// installation directory is available.
LOG("gCanApplyUpdates - on Vista, appDir is not under Program Files");
}
}
/**
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
# On Windows, we no longer store the update under the app dir.
#
# If we are on Windows (including Vista, if we can't elevate) we need to
# to check that we can create and remove files from the actual app
# directory (like C:\Program Files\Mozilla Firefox). If we can't
# (because this user is not an adminstrator, for example) canUpdate()
# should return false.
#
# For Vista, we perform this check to enable updating the application
# when the user has write access to the installation directory under the
# following scenarios:
# 1) the installation directory is not under Program Files
# (e.g. C:\Program Files)
# 2) UAC is turned off
# 3) UAC is turned on and the user is not an admin
# (e.g. the user does not have a split token)
# 4) UAC is turned on and the user is already elevated, so they can't be
# elevated again
*/
if (!userCanElevate) {
// if we're unable to create the test file this will throw an exception.
var appDirTestFile = getAppBaseDir();
appDirTestFile.append(FILE_PERMS_TEST);
LOG("gCanApplyUpdates - testing write access " + appDirTestFile.path);
if (appDirTestFile.exists())
appDirTestFile.remove(false)
appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
appDirTestFile.remove(false);
}
#endif //XP_WIN
}
catch (e) {
LOG("gCanApplyUpdates - unable to apply updates. Exception: " + e);
// No write privileges to install directory
submitHasPermissionsTelemetryPing(false);
return false;
}
} // if (!useService)
if (!hasUpdateMutex()) {
LOG("gCanApplyUpdates - unable to apply updates because another instance" +
"of the application is already handling updates for this " +
"installation.");
return false;
}
LOG("gCanApplyUpdates - able to apply updates");
submitHasPermissionsTelemetryPing(true);
return true;
});
/**
* Whether or not the application can stage an update.
*
* @return true if updates can be staged.
*/
function getCanStageUpdates() {
// If background updates are disabled, then just bail out!
if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
LOG("getCanStageUpdates - staging updates is disabled by preference " +
PREF_APP_UPDATE_STAGING_ENABLED);
return false;
}
#ifdef CHECK_CAN_USE_SERVICE
if (getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
// No need to perform directory write checks, the maintenance service will
// be able to write to all directories.
LOG("getCanStageUpdates - able to stage updates because we'll use the service");
return true;
}
#endif
if (!hasUpdateMutex()) {
LOG("getCanStageUpdates - unable to apply updates because another " +
"instance of the application is already handling updates for this " +
"installation.");
return false;
}
/**
* Whether or not the application can stage an update for the current session.
* These checks are only performed once per session due to using a lazy getter.
*
* @return true if updates can be staged for this session.
*/
XPCOMUtils.defineLazyGetter(this, "canStageUpdatesSession", function canStageUpdatesSession() {
try {
var updateTestFile = getInstallDirRoot();
updateTestFile.append(FILE_PERMS_TEST);
LOG("canStageUpdatesSession - testing write access " +
updateTestFile.path);
testWriteAccess(updateTestFile, true);
#ifndef XP_MACOSX
// On all platforms except Mac, we need to test the parent directory as
// well, as we need to be able to move files in that directory during the
// replacing step.
updateTestFile = getInstallDirRoot().parent;
updateTestFile.append(FILE_PERMS_TEST);
LOG("canStageUpdatesSession - testing write access " +
updateTestFile.path);
updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
FileUtils.PERMS_DIRECTORY);
updateTestFile.remove(false);
#endif
}
catch (e) {
LOG("canStageUpdatesSession - unable to stage updates. Exception: " +
e);
// No write privileges
return false;
}
LOG("canStageUpdatesSession - able to stage updates");
return true;
});
return canStageUpdatesSession;
}
XPCOMUtils.defineLazyGetter(this, "gMetroUpdatesEnabled", function aus_gMetroUpdatesEnabled() {
#ifdef XP_WIN
#ifdef MOZ_METRO
if (Services.metro && Services.metro.immersive) {
let metroUpdate = getPref("getBoolPref", PREF_APP_UPDATE_METRO_ENABLED, true);
if (!metroUpdate) {
LOG("gMetroUpdatesEnabled - unable to automatically check for metro " +
"updates, disabled by pref");
return false;
}
}
#endif
#endif
return true;
});
XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
// If the administrator has disabled app update and locked the preference so
// users can't check for updates. This preference check is ok in this lazy
// getter since locked prefs don't change until the application is restarted.
var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
"the preference is disabled and admistratively locked.");
return false;
}
if (!gMetroUpdatesEnabled) {
return false;
}
// If we don't know the binary platform we're updating, we can't update.
if (!gABI) {
LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI");
return false;
}
// If we don't know the OS version we're updating, we can't update.
if (!gOSVersion) {
LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
"version");
return false;
}
if (!hasUpdateMutex()) {
LOG("gCanCheckForUpdates - unable to apply updates because another " +
"instance of the application is already handling updates for this " +
"installation.");
return false;
}
LOG("gCanCheckForUpdates - able to check for updates");
return true;
});
/**
* Logs a string to the error console.
* @param string
* The string to write to the error console.
*/
function LOG(string) {
if (gLogEnabled) {
dump("*** AUS:SVC " + string + "\n");
Services.console.logStringMessage("AUS:SVC " + string);
}
}
/**
# Gets a preference value, handling the case where there is no default.
# @param func
# The name of the preference function to call, on nsIPrefBranch
# @param preference
# The name of the preference
# @param defaultValue
# The default value to return in the event the preference has
# no setting
# @return The value of the preference, or undefined if there was no
# user or default value.
*/
function getPref(func, preference, defaultValue) {
try {
return Services.prefs[func](preference);
}
catch (e) {
}
return defaultValue;
}
/**
* Convert a string containing binary values to hex.
*/
function binaryToHex(input) {
var result = "";
for (var i = 0; i < input.length; ++i) {
var hex = input.charCodeAt(i).toString(16);
if (hex.length == 1)
hex = "0" + hex;
result += hex;
}
return result;
}
/**
# Gets the specified directory at the specified hierarchy under the
# update root directory and creates it if it doesn't exist.
# @param pathArray
# An array of path components to locate beneath the directory
# specified by |key|
# @return nsIFile object for the location specified.
*/
function getUpdateDirCreate(pathArray) {
return FileUtils.getDir(KEY_UPDROOT, pathArray, true);
}
/**
# Gets the specified directory at the specified hierarchy under the
# update root directory and without creating it if it doesn't exist.
# @param pathArray
# An array of path components to locate beneath the directory
# specified by |key|
# @return nsIFile object for the location specified.
*/
function getUpdateDirNoCreate(pathArray) {
return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
}
/**
* Gets the application base directory.
*
* @return nsIFile object for the application base directory.
*/
function getAppBaseDir() {
return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent;
}
/**
* Gets the root of the installation directory which is the application
* bundle directory on Mac OS X and the location of the application binary
* on all other platforms.
*
* @return nsIFile object for the directory
*/
function getInstallDirRoot() {
var dir = getAppBaseDir();
#ifdef XP_MACOSX
// On Mac, we store the Updated.app directory inside the bundle directory.
dir = dir.parent.parent;
#endif
return dir;
}
/**
* Gets the file at the specified hierarchy under the update root directory.
* @param pathArray
* An array of path components to locate beneath the directory
* specified by |key|. The last item in this array must be the
* leaf name of a file.
* @return nsIFile object for the file specified. The file is NOT created
* if it does not exist, however all required directories along
* the way are.
*/
function getUpdateFile(pathArray) {
var file = getUpdateDirCreate(pathArray.slice(0, -1));
file.append(pathArray[pathArray.length - 1]);
return file;
}
/**
* Returns human readable status text from the updates.properties bundle
* based on an error code
* @param code
* The error code to look up human readable status text for
* @param defaultCode
* The default code to look up should human readable status text
* not exist for |code|
* @return A human readable status text string
*/
function getStatusTextFromCode(code, defaultCode) {
var reason;
try {
reason = gUpdateBundle.GetStringFromName("check_error-" + code);
LOG("getStatusTextFromCode - transfer error: " + reason + ", code: " +
code);
}
catch (e) {
// Use the default reason
reason = gUpdateBundle.GetStringFromName("check_error-" + defaultCode);
LOG("getStatusTextFromCode - transfer error: " + reason +
", default code: " + defaultCode);
}
return reason;
}
/**
* Get the Active Updates directory
* @return The active updates directory, as a nsIFile object
*/
function getUpdatesDir() {
// Right now, we only support downloading one patch at a time, so we always
// use the same target directory.
return getUpdateDirCreate([DIR_UPDATES, "0"]);
}
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
/**
* Get the Active Updates directory inside the directory where we apply the
* background updates.
* @return The active updates directory inside the updated directory, as a
* nsIFile object.
*/
function getUpdatesDirInApplyToDir() {
var dir = getAppBaseDir();
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
#ifdef XP_MACOSX
dir = dir.parent.parent; // the bundle directory
#endif
dir.append(UPDATED_DIR);
#ifdef XP_MACOSX
dir.append("Contents");
dir.append("MacOS");
#endif
dir.append(DIR_UPDATES);
if (!dir.exists()) {
dir.create(Ci.nsILocalFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
}
return dir;
}
/**
* Reads the update state from the update.status file in the specified
* directory.
* @param dir
* The dir to look for an update.status file in
* @return The status value of the update.
*/
function readStatusFile(dir) {
var statusFile = dir.clone();
statusFile.append(FILE_UPDATE_STATUS);
var status = readStringFromFile(statusFile) || STATE_NONE;
LOG("readStatusFile - status: " + status + ", path: " + statusFile.path);
return status;
}
/**
* Writes the current update operation/state to a file in the patch
* directory, indicating to the patching system that operations need
* to be performed.
* @param dir
* The patch directory where the update.status file should be
* written.
* @param state
* The state value to write.
*/
function writeStatusFile(dir, state) {
var statusFile = dir.clone();
statusFile.append(FILE_UPDATE_STATUS);
writeStringToFile(statusFile, state);
}
#ifdef MOZ_WIDGET_GONK
/**
* Reads the link file specified in the update.link file in the
* specified directory and returns the nsIFile for the
* corresponding file.
* @param dir
* The dir to look for an update.link file in
* @return A nsIFile for the file path specified in the
* update.link file or null if the update.link file
* doesn't exist.
*/
function getFileFromUpdateLink(dir) {
var linkFile = dir.clone();
linkFile.append(FILE_UPDATE_LINK);
var link = readStringFromFile(linkFile);
LOG("getFileFromUpdateLink linkFile.path: " + linkFile.path + ", link: " + link);
if (!link) {
return null;
}
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file.initWithPath(link);
return file;
}
/**
* Creates a link file, which allows the actual patch to live in
* a directory different from the update directory.
* @param dir
* The patch directory where the update.link file
* should be written.
* @param patchFile
* The fully qualified filename of the patchfile.
*/
function writeLinkFile(dir, patchFile) {
var linkFile = dir.clone();
linkFile.append(FILE_UPDATE_LINK);
writeStringToFile(linkFile, patchFile.path);
if (patchFile.path.indexOf(gExtStorage) == 0) {
// The patchfile is being stored on external storage. Try to lock it
// so that it doesn't get shared with the PC while we're downloading
// to it.
acquireSDCardMountLock();
}
}
/**
* Acquires a VolumeMountLock for the sdcard volume.
*
* This prevents the SDCard from being shared with the PC while
* we're downloading the update.
*/
function acquireSDCardMountLock() {
let volsvc = Cc["@mozilla.org/telephony/volume-service;1"].
getService(Ci.nsIVolumeService);
if (volsvc) {
gSDCardMountLock = volsvc.createMountLock("sdcard");
}
}
/**
* Determines if the state corresponds to an interrupted update.
* This could either be because the download was interrupted, or
* because staging the update was interrupted.
*
* @return true if the state corresponds to an interrupted
* update.
*/
function isInterruptedUpdate(status) {
return (status == STATE_DOWNLOADING) ||
(status == STATE_PENDING) ||
(status == STATE_APPLYING);
}
#endif // MOZ_WIDGET_GONK
/**
* Releases any SDCard mount lock that we might have.
*
* This once again allows the SDCard to be shared with the PC.
*
* This function was placed outside the #ifdef so that we didn't
* need to put #ifdefs around the callers.
*/
function releaseSDCardMountLock() {
#ifdef MOZ_WIDGET_GONK
if (gSDCardMountLock) {
gSDCardMountLock.unlock();
gSDCardMountLock = null;
}
#endif
}
/**
* Determines if the service should be used to attempt an update
* or not. For now this is only when PREF_APP_UPDATE_SERVICE_ENABLED
* is true and we have Firefox.
*
* @return true if the service should be used for updates.
*/
function shouldUseService() {
#ifdef MOZ_MAINTENANCE_SERVICE
return getPref("getBoolPref",
PREF_APP_UPDATE_SERVICE_ENABLED, false);
#else
return false;
#endif
}
/**
* Determines if the service is is installed and enabled or not.
*
* @return true if the service should be used for updates,
* is installed and enabled.
*/
function isServiceInstalled() {
#ifdef XP_WIN
let installed = 0;
try {
let wrk = Cc["@mozilla.org/windows-registry-key;1"].
createInstance(Ci.nsIWindowsRegKey);
wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
"SOFTWARE\\Mozilla\\MaintenanceService",
wrk.ACCESS_READ | wrk.WOW64_64);
installed = wrk.readIntValue("Installed");
wrk.close();
} catch(e) {
}
installed = installed == 1; // convert to bool
LOG("isServiceInstalled = " + installed);
return installed;
#else
return false;
#endif
}
/**
# Writes the update's application version to a file in the patch directory. If
# the update doesn't provide application version information via the
# appVersion attribute the string "null" will be written to the file.
# This value is compared during startup (in nsUpdateDriver.cpp) to determine if
# the update should be applied. Note that this won't provide protection from
# downgrade of the application for the nightly user case where the application
# version doesn't change.
# @param dir
# The patch directory where the update.version file should be
# written.
# @param version
# The version value to write. Will be the string "null" when the
# update doesn't provide the appVersion attribute in the update xml.
*/
function writeVersionFile(dir, version) {
var versionFile = dir.clone();
versionFile.append(FILE_UPDATE_VERSION);
writeStringToFile(versionFile, version);
}
/**
* Removes the MozUpdater folders that bgupdates/staged updates creates.
*/
function cleanUpMozUpdaterDirs() {
try {
var tmpDir = Cc["@mozilla.org/file/directory_service;1"].
getService(Ci.nsIProperties).
get("TmpD", Ci.nsIFile);
// We used to store MozUpdater-i folders directly inside the temp directory.
// We need to cleanup these directories if we detect that they still exist.
// To check if they still exist, we simply check for MozUpdater-1.
var mozUpdaterDir1 = tmpDir.clone();
mozUpdaterDir1.append("MozUpdater-1");
// Only try to delete the left over folders in "$Temp/MozUpdater-i/*" if
// MozUpdater-1 exists.
if (mozUpdaterDir1.exists()) {
LOG("cleanUpMozUpdaterDirs - Cleaning top level MozUpdater-i folders");
let i = 0;
let dirEntries = tmpDir.directoryEntries;
while (dirEntries.hasMoreElements() && i < 10) {
let file = dirEntries.getNext().QueryInterface(Ci.nsILocalFile);
if (file.leafName.startsWith("MozUpdater-") && file.leafName != "MozUpdater-1") {
file.remove(true);
i++;
}
}
// If you enumerate the whole temp directory and the count of deleted
// items is less than 10, then delete MozUpdate-1.
if (i < 10) {
mozUpdaterDir1.remove(true);
}
}
// If we reach here, we simply need to clean the MozUpdater folder. In our
// new way of storing these files, the unique subfolders are inside MozUpdater
var mozUpdaterDir = tmpDir.clone();
mozUpdaterDir.append("MozUpdater");
if (mozUpdaterDir.exists()) {
LOG("cleanUpMozUpdaterDirs - Cleaning MozUpdater folder");
mozUpdaterDir.remove(true);
}
} catch (e) {
LOG("cleanUpMozUpdaterDirs - Exception: " + e);
}
}
/**
* Removes the contents of the Updates Directory
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
*
* @param aBackgroundUpdate Whether the update has been performed in the
* background. If this is true, we move the update log file to the
* updated directory, so that it survives replacing the directories
* later on.
*/
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
function cleanUpUpdatesDir(aBackgroundUpdate) {
// Bail out if we don't have appropriate permissions
try {
var updateDir = getUpdatesDir();
}
catch (e) {
return;
}
var e = updateDir.directoryEntries;
while (e.hasMoreElements()) {
var f = e.getNext().QueryInterface(Ci.nsIFile);
// Preserve the last update log file for debugging purposes
if (f.leafName == FILE_UPDATE_LOG) {
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
var dir;
try {
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
// If we don't use the update root directory, the log files are written
// inside the application directory. In that case, we want to write
// the log files to the updated directory in the case of background
// updates, so that they would be available when we replace that
// directory with the application directory later on.
if (aBackgroundUpdate && getUpdateDirNoCreate([]).equals(getAppBaseDir())) {
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
dir = getUpdatesDirInApplyToDir();
} else {
dir = f.parent.parent;
}
var logFile = dir.clone();
logFile.append(FILE_LAST_LOG);
if (logFile.exists()) {
try {
logFile.moveTo(dir, FILE_BACKUP_LOG);
}
catch (e) {
LOG("cleanUpUpdatesDir - failed to rename file " + logFile.path +
" to " + FILE_BACKUP_LOG);
}
}
f.moveTo(dir, FILE_LAST_LOG);
if (aBackgroundUpdate) {
// We're not going to delete any files, so we can just
// bail out of the loop right now.
break;
} else {
continue;
}
}
catch (e) {
LOG("cleanUpUpdatesDir - failed to move file " + f.path + " to " +
dir.path + " and rename it to " + FILE_LAST_LOG);
}
} else if (aBackgroundUpdate) {
// Don't delete any files when an update has been staged, as
// we need to keep them around in case we would have to fall
// back to applying the update on application restart.
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
continue;
}
#ifdef MOZ_WIDGET_GONK
if (f.leafName == FILE_UPDATE_LINK) {
let linkedFile = getFileFromUpdateLink(updateDir);
if (linkedFile && linkedFile.exists()) {
linkedFile.remove(false);
}
}
#endif
// Now, recursively remove this file. The recursive removal is really
// only needed on Mac OSX because this directory will contain a copy of
// updater.app, which is itself a directory.
try {
f.remove(true);
}
catch (e) {
LOG("cleanUpUpdatesDir - failed to remove file " + f.path);
}
}
releaseSDCardMountLock();
}
/**
* Clean up updates list and the updates directory.
*/
function cleanupActiveUpdate() {
// Move the update from the Active Update list into the Past Updates list.
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
um.activeUpdate = null;
um.saveUpdates();
// Now trash the updates directory, since we're done with it
cleanUpUpdatesDir();
}
/**
* Gets the locale from the update.locale file for replacing %LOCALE% in the
* update url. The update.locale file can be located in the application
* directory or the GRE directory with preference given to it being located in
* the application directory.
*/
function getLocale() {
if (gLocale)
return gLocale;
for each (res in ['app', 'gre']) {
var channel = Services.io.newChannel("resource://" + res + "/" + FILE_UPDATE_LOCALE, null, null);
try {
var inputStream = channel.open();
gLocale = readStringFromInputStream(inputStream);
} catch(e) {}
if (gLocale)
break;
}
if (!gLocale)
throw Components.Exception(FILE_UPDATE_LOCALE + " file doesn't exist in " +
"either the application or GRE directories",
Cr.NS_ERROR_FILE_NOT_FOUND);
LOG("getLocale - getting locale from file: " + channel.originalURI.spec +
", locale: " + gLocale);
return gLocale;
}
/* Get the distribution pref values, from defaults only */
function getDistributionPrefValue(aPrefName) {
var prefValue = "default";
try {
prefValue = Services.prefs.getDefaultBranch(null).getCharPref(aPrefName);
} catch (e) {
// use default when pref not found
}
return prefValue;
}
/**
* An enumeration of items in a JS array.
* @constructor
*/
function ArrayEnumerator(aItems) {
this._index = 0;
if (aItems) {
for (var i = 0; i < aItems.length; ++i) {
if (!aItems[i])
aItems.splice(i, 1);
}
}
this._contents = aItems;
}
ArrayEnumerator.prototype = {
_index: 0,
_contents: [],
hasMoreElements: function ArrayEnumerator_hasMoreElements() {
return this._index < this._contents.length;
},
getNext: function ArrayEnumerator_getNext() {
return this._contents[this._index++];
}
};
/**
* Writes a string of text to a file. A newline will be appended to the data
* written to the file. This function only works with ASCII text.
*/
function writeStringToFile(file, text) {
var fos = FileUtils.openSafeFileOutputStream(file)
text += "\n";
fos.write(text, text.length);
FileUtils.closeSafeFileOutputStream(fos);
}
function readStringFromInputStream(inputStream) {
var sis = Cc["@mozilla.org/scriptableinputstream;1"].
createInstance(Ci.nsIScriptableInputStream);
sis.init(inputStream);
var text = sis.read(sis.available());
sis.close();
if (text[text.length - 1] == "\n")
text = text.slice(0, -1);
return text;
}
/**
* Reads a string of text from a file. A trailing newline will be removed
* before the result is returned. This function only works with ASCII text.
*/
function readStringFromFile(file) {
if (!file.exists()) {
LOG("readStringFromFile - file doesn't exist: " + file.path);
return null;
}
var fis = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fis.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
return readStringFromInputStream(fis);
}
function handleUpdateFailure(update, errorCode) {
update.errorCode = parseInt(errorCode);
if (update.errorCode == FOTA_GENERAL_ERROR ||
update.errorCode == FOTA_FILE_OPERATION_ERROR ||
update.errorCode == FOTA_RECOVERY_ERROR ||
update.errorCode == FOTA_UNKNOWN_ERROR) {
// In the case of FOTA update errors, don't reset the state to pending. This
// causes the FOTA update path to try again, which is not necessarily what
// we want.
update.statusText = gUpdateBundle.GetStringFromName("statusFailed");
Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt).
showUpdateError(update);
writeStatusFile(getUpdatesDir(), STATE_FAILED + ": " + errorCode);
cleanupActiveUpdate();
return true;
}
if (update.errorCode == WRITE_ERROR ||
update.errorCode == WRITE_ERROR_ACCESS_DENIED ||
update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED ||
update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID ||
update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID ||
update.errorCode == WRITE_ERROR_CALLBACK_APP ||
update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR) {
Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt).
showUpdateError(update);
writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
return true;
}
if (update.errorCode == ELEVATION_CANCELED) {
writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
return true;
}
if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED ||
update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS ||
update.errorCode == SERVICE_UPDATER_SIGN_ERROR ||
update.errorCode == SERVICE_UPDATER_COMPARE_ERROR ||
update.errorCode == SERVICE_UPDATER_IDENTITY_ERROR ||
update.errorCode == SERVICE_STILL_APPLYING_ON_SUCCESS ||
update.errorCode == SERVICE_STILL_APPLYING_ON_FAILURE ||
update.errorCode == SERVICE_UPDATER_NOT_FIXED_DRIVE ||
update.errorCode == SERVICE_COULD_NOT_LOCK_UPDATER ||
2013-05-23 10:26:44 -07:00
update.errorCode == SERVICE_COULD_NOT_COPY_UPDATER ||
update.errorCode == SERVICE_INSTALLDIR_ERROR) {
var failCount = getPref("getIntPref",
PREF_APP_UPDATE_SERVICE_ERRORS, 0);
var maxFail = getPref("getIntPref",
PREF_APP_UPDATE_SERVICE_MAX_ERRORS,
DEFAULT_SERVICE_MAX_ERRORS);
// As a safety, when the service reaches maximum failures, it will
// disable itself and fallback to using the normal update mechanism
// without the service.
if (failCount >= maxFail) {
Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
} else {
failCount++;
Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS,
failCount);
}
writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
try {
Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").
add(update.errorCode);
}
catch (e) {
Cu.reportError(e);
}
return true;
}
try {
Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").add(0);
}
catch (e) {
Cu.reportError(e);
}
return false;
}
/**
* Fall back to downloading a complete update in case an update has failed.
*
* @param update the update object that has failed to apply.
* @param postStaging true if we have just attempted to stage an update.
*/
function handleFallbackToCompleteUpdate(update, postStaging) {
cleanupActiveUpdate();
update.statusText = gUpdateBundle.GetStringFromName("patchApplyFailure");
var oldType = update.selectedPatch ? update.selectedPatch.type
: "complete";
if (update.selectedPatch && oldType == "partial" && update.patchCount == 2) {
// Partial patch application failed, try downloading the complete
// update in the background instead.
LOG("handleFallbackToCompleteUpdate - install of partial patch " +
"failed, downloading complete patch");
var status = Cc["@mozilla.org/updates/update-service;1"].
getService(Ci.nsIApplicationUpdateService).
downloadUpdate(update, !postStaging);
if (status == STATE_NONE)
cleanupActiveUpdate();
}
else {
LOG("handleFallbackToCompleteUpdate - install of complete or " +
"only one patch offered failed.");
}
update.QueryInterface(Ci.nsIWritablePropertyBag);
update.setProperty("patchingFailed", oldType);
}
/**
* Update Patch
* @param patch
* A <patch> element to initialize this object with
* @throws if patch has a size of 0
* @constructor
*/
function UpdatePatch(patch) {
this._properties = {};
for (var i = 0; i < patch.attributes.length; ++i) {
var attr = patch.attributes.item(i);
attr.QueryInterface(Ci.nsIDOMAttr);
switch (attr.name) {
case "selected":
this.selected = attr.value == "true";
break;
case "size":
if (0 == parseInt(attr.value)) {
LOG("UpdatePatch:init - 0-sized patch!");
throw Cr.NS_ERROR_ILLEGAL_VALUE;
}
// fall through
default:
this[attr.name] = attr.value;
break;
};
}
}
UpdatePatch.prototype = {
/**
* See nsIUpdateService.idl
*/
serialize: function UpdatePatch_serialize(updates) {
var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
patch.setAttribute("type", this.type);
patch.setAttribute("URL", this.URL);
// finalURL is not available until after the download has started
if (this.finalURL)
patch.setAttribute("finalURL", this.finalURL);
patch.setAttribute("hashFunction", this.hashFunction);
patch.setAttribute("hashValue", this.hashValue);
patch.setAttribute("size", this.size);
patch.setAttribute("selected", this.selected);
patch.setAttribute("state", this.state);
for (var p in this._properties) {
if (this._properties[p].present)
patch.setAttribute(p, this._properties[p].data);
}
return patch;
},
/**
* A hash of custom properties
*/
_properties: null,
/**
* See nsIWritablePropertyBag.idl
*/
setProperty: function UpdatePatch_setProperty(name, value) {
this._properties[name] = { data: value, present: true };
},
/**
* See nsIWritablePropertyBag.idl
*/
deleteProperty: function UpdatePatch_deleteProperty(name) {
if (name in this._properties)
this._properties[name].present = false;
else
throw Cr.NS_ERROR_FAILURE;
},
/**
* See nsIPropertyBag.idl
*/
get enumerator() {
var properties = [];
for (var p in this._properties)
properties.push(this._properties[p].data);
return new ArrayEnumerator(properties);
},
/**
* See nsIPropertyBag.idl
*/
getProperty: function UpdatePatch_getProperty(name) {
if (name in this._properties &&
this._properties[name].present)
return this._properties[name].data;
throw Cr.NS_ERROR_FAILURE;
},
/**
* Returns whether or not the update.status file for this patch exists at the
* appropriate location.
*/
get statusFileExists() {
var statusFile = getUpdatesDir();
statusFile.append(FILE_UPDATE_STATUS);
return statusFile.exists();
},
/**
* See nsIUpdateService.idl
*/
get state() {
if (this._properties.state)
return this._properties.state;
return STATE_NONE;
},
set state(val) {
this._properties.state = val;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePatch,
Ci.nsIPropertyBag,
Ci.nsIWritablePropertyBag])
};
/**
* Update
* Implements nsIUpdate
* @param update
* An <update> element to initialize this object with
* @throws if the update contains no patches
* @constructor
*/
function Update(update) {
this._properties = {};
this._patches = [];
this.isCompleteUpdate = false;
this.isOSUpdate = false;
this.showPrompt = false;
this.showNeverForVersion = false;
this.unsupported = false;
this.channel = "default";
this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
// Null <update>, assume this is a message container and do no
// further initialization
if (!update)
return;
const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
for (var i = 0; i < update.childNodes.length; ++i) {
var patchElement = update.childNodes.item(i);
if (patchElement.nodeType != ELEMENT_NODE ||
patchElement.localName != "patch")
continue;
patchElement.QueryInterface(Ci.nsIDOMElement);
try {
var patch = new UpdatePatch(patchElement);
} catch (e) {
continue;
}
this._patches.push(patch);
}
if (this._patches.length == 0 && !update.hasAttribute("unsupported"))
throw Cr.NS_ERROR_ILLEGAL_VALUE;
// Fallback to the behavior prior to bug 530872 if the update does not have an
// appVersion attribute.
if (!update.hasAttribute("appVersion")) {
if (update.getAttribute("type") == "major") {
if (update.hasAttribute("detailsURL")) {
this.billboardURL = update.getAttribute("detailsURL");
this.showPrompt = true;
this.showNeverForVersion = true;
}
}
}
for (var i = 0; i < update.attributes.length; ++i) {
var attr = update.attributes.item(i);
attr.QueryInterface(Ci.nsIDOMAttr);
if (attr.value == "undefined")
continue;
else if (attr.name == "detailsURL")
this._detailsURL = attr.value;
else if (attr.name == "extensionVersion") {
// Prevent extensionVersion from replacing appVersion if appVersion is
// present in the update xml.
if (!this.appVersion)
this.appVersion = attr.value;
}
else if (attr.name == "installDate" && attr.value)
this.installDate = parseInt(attr.value);
else if (attr.name == "isCompleteUpdate")
this.isCompleteUpdate = attr.value == "true";
else if (attr.name == "isSecurityUpdate")
this.isSecurityUpdate = attr.value == "true";
else if (attr.name == "isOSUpdate")
this.isOSUpdate = attr.value == "true";
else if (attr.name == "showNeverForVersion")
this.showNeverForVersion = attr.value == "true";
else if (attr.name == "showPrompt")
this.showPrompt = attr.value == "true";
else if (attr.name == "promptWaitTime")
{
if(!isNaN(attr.value))
this.promptWaitTime = parseInt(attr.value);
}
else if (attr.name == "unsupported")
this.unsupported = attr.value == "true";
else if (attr.name == "version") {
// Prevent version from replacing displayVersion if displayVersion is
// present in the update xml.
if (!this.displayVersion)
this.displayVersion = attr.value;
}
else {
this[attr.name] = attr.value;
switch (attr.name) {
case "appVersion":
case "billboardURL":
case "buildID":
case "channel":
case "displayVersion":
case "licenseURL":
case "name":
case "platformVersion":
case "previousAppVersion":
case "serviceURL":
case "statusText":
case "type":
break;
default:
// Save custom attributes when serializing to the local xml file but
// don't use this method for the expected attributes which are already
// handled in serialize.
this.setProperty(attr.name, attr.value);
break;
};
}
}
// Set the initial value with the current time when it doesn't already have a
// value or the value is already set to 0 (bug 316328).
if (!this.installDate && this.installDate != 0)
this.installDate = (new Date()).getTime();
// The Update Name is either the string provided by the <update> element, or
// the string: "<App Name> <Update App Version>"
var name = "";
if (update.hasAttribute("name"))
name = update.getAttribute("name");
else {
var brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES);
var appName = brandBundle.GetStringFromName("brandShortName");
name = gUpdateBundle.formatStringFromName("updateName",
[appName, this.displayVersion], 2);
}
this.name = name;
}
Update.prototype = {
/**
* See nsIUpdateService.idl
*/
get patchCount() {
return this._patches.length;
},
/**
* See nsIUpdateService.idl
*/
getPatchAt: function Update_getPatchAt(index) {
return this._patches[index];
},
/**
* See nsIUpdateService.idl
*
* We use a copy of the state cached on this object in |_state| only when
* there is no selected patch, i.e. in the case when we could not load
* |.activeUpdate| from the update manager for some reason but still have
* the update.status file to work with.
*/
_state: "",
set state(state) {
if (this.selectedPatch)
this.selectedPatch.state = state;
this._state = state;
return state;
},
get state() {
if (this.selectedPatch)
return this.selectedPatch.state;
return this._state;
},
/**
* See nsIUpdateService.idl
*/
errorCode: 0,
/**
* See nsIUpdateService.idl
*/
get selectedPatch() {
for (var i = 0; i < this.patchCount; ++i) {
if (this._patches[i].selected)
return this._patches[i];
}
return null;
},
/**
* See nsIUpdateService.idl
*/
get detailsURL() {
if (!this._detailsURL) {
try {
// Try using a default details URL supplied by the distribution
// if the update XML does not supply one.
return Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
}
catch (e) {
}
}
return this._detailsURL || "";
},
/**
* See nsIUpdateService.idl
*/
serialize: function Update_serialize(updates) {
var update = updates.createElementNS(URI_UPDATE_NS, "update");
update.setAttribute("appVersion", this.appVersion);
update.setAttribute("buildID", this.buildID);
update.setAttribute("channel", this.channel);
update.setAttribute("displayVersion", this.displayVersion);
// for backwards compatibility in case the user downgrades
update.setAttribute("extensionVersion", this.appVersion);
update.setAttribute("installDate", this.installDate);
update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
update.setAttribute("isOSUpdate", this.isOSUpdate);
update.setAttribute("name", this.name);
update.setAttribute("serviceURL", this.serviceURL);
update.setAttribute("showNeverForVersion", this.showNeverForVersion);
update.setAttribute("showPrompt", this.showPrompt);
update.setAttribute("promptWaitTime", this.promptWaitTime);
update.setAttribute("type", this.type);
// for backwards compatibility in case the user downgrades
update.setAttribute("version", this.displayVersion);
// Optional attributes
if (this.billboardURL)
update.setAttribute("billboardURL", this.billboardURL);
if (this.detailsURL)
update.setAttribute("detailsURL", this.detailsURL);
if (this.licenseURL)
update.setAttribute("licenseURL", this.licenseURL);
if (this.platformVersion)
update.setAttribute("platformVersion", this.platformVersion);
if (this.previousAppVersion)
update.setAttribute("previousAppVersion", this.previousAppVersion);
if (this.statusText)
update.setAttribute("statusText", this.statusText);
if (this.unsupported)
update.setAttribute("unsupported", this.unsupported);
updates.documentElement.appendChild(update);
for (var p in this._properties) {
if (this._properties[p].present)
update.setAttribute(p, this._properties[p].data);
}
for (var i = 0; i < this.patchCount; ++i)
update.appendChild(this.getPatchAt(i).serialize(updates));
return update;
},
/**
* A hash of custom properties
*/
_properties: null,
/**
* See nsIWritablePropertyBag.idl
*/
setProperty: function Update_setProperty(name, value) {
this._properties[name] = { data: value, present: true };
},
/**
* See nsIWritablePropertyBag.idl
*/
deleteProperty: function Update_deleteProperty(name) {
if (name in this._properties)
this._properties[name].present = false;
else
throw Cr.NS_ERROR_FAILURE;
},
/**
* See nsIPropertyBag.idl
*/
get enumerator() {
var properties = [];
for (var p in this._properties)
properties.push(this._properties[p].data);
return new ArrayEnumerator(properties);
},
/**
* See nsIPropertyBag.idl
*/
getProperty: function Update_getProperty(name) {
if (name in this._properties && this._properties[name].present)
return this._properties[name].data;
throw Cr.NS_ERROR_FAILURE;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdate,
Ci.nsIPropertyBag,
Ci.nsIWritablePropertyBag])
};
const UpdateServiceFactory = {
_instance: null,
createInstance: function (outer, iid) {
if (outer != null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return this._instance == null ? this._instance = new UpdateService() :
this._instance;
}
};
/**
* UpdateService
* A Service for managing the discovery and installation of software updates.
* @constructor
*/
function UpdateService() {
LOG("Creating UpdateService");
Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.prefs.addObserver(PREF_APP_UPDATE_LOG, this, false);
#ifdef MOZ_WIDGET_GONK
// PowerManagerService::SyncProfile (which is called for Reboot, PowerOff
// and Restart) sends the profile-change-net-teardown event. We can then
// pause the download in a similar manner to xpcom-shutdown.
Services.obs.addObserver(this, "profile-change-net-teardown", false);
#endif
}
UpdateService.prototype = {
/**
* The downloader we are using to download updates. There is only ever one of
* these.
*/
_downloader: null,
/**
* Incompatible add-on count.
*/
_incompatAddonsCount: 0,
/**
* Whether or not the service registered the "online" observer.
*/
_registeredOnlineObserver: false,
/**
* The current number of consecutive socket errors
*/
_consecutiveSocketErrors: 0,
/**
* A timer used to retry socket errors
*/
_retryTimer: null,
/**
* Whether or not a background update check was initiated by the
* application update timer notification.
*/
_isNotify: true,
/**
* Handle Observer Service notifications
* @param subject
* The subject of the notification
* @param topic
* The notification name
* @param data
* Additional data
*/
observe: function AUS_observe(subject, topic, data) {
switch (topic) {
case "post-update-processing":
// Clean up any extant updates
this._postUpdateProcessing();
break;
case "network:offline-status-changed":
this._offlineStatusChanged(data);
break;
case "nsPref:changed":
if (data == PREF_APP_UPDATE_LOG) {
gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
}
break;
#ifdef MOZ_WIDGET_GONK
case "profile-change-net-teardown": // fall thru
#endif
case "xpcom-shutdown":
Services.obs.removeObserver(this, topic);
Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);
if (this._retryTimer) {
this._retryTimer.cancel();
}
this.pauseDownload();
// Prevent leaking the downloader (bug 454964)
this._downloader = null;
break;
}
},
/**
* The following needs to happen during the post-update-processing
* notification from nsUpdateServiceStub.js:
* 1. post update processing
* 2. resume of a download that was in progress during a previous session
* 3. start of a complete update download after the failure to apply a partial
* update
*/
/**
* Perform post-processing on updates lingering in the updates directory
* from a previous application session - either report install failures (and
* optionally attempt to fetch a different version if appropriate) or
* notify the user of install success.
*/
_postUpdateProcessing: function AUS__postUpdateProcessing() {
// canCheckForUpdates will return false when metro-only updates are disabled
// from within metro. In that case we still want _postUpdateProcessing to
// run. gMetroUpdatesEnabled returns true on non Windows 8 platforms.
// We want _postUpdateProcessing to run so that it will update the history
// XML. Without updating the history XML, the about flyout will continue to
// have the "Restart to Apply Update" button (history xml indicates update
// is applied).
// TODO: I think this whole if-block should be removed since updates can
// always be applied via the about dialog, we should be running post update
// in those cases.
if (!this.canCheckForUpdates && gMetroUpdatesEnabled) {
LOG("UpdateService:_postUpdateProcessing - unable to check for " +
"updates... returning early");
return;
}
if (!this.canApplyUpdates) {
LOG("UpdateService:_postUpdateProcessing - unable to apply " +
"updates... returning early");
// If the update is present in the update directly somehow,
// it would prevent us from notifying the user of futher updates.
cleanupActiveUpdate();
return;
}
var status = readStatusFile(getUpdatesDir());
// STATE_NONE status means that the update.status file is present but a
// background download error occurred.
if (status == STATE_NONE) {
LOG("UpdateService:_postUpdateProcessing - no status, no update");
cleanupActiveUpdate();
return;
}
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
#ifdef MOZ_WIDGET_GONK
// This code is called very early in the boot process, before we've even
// had a chance to setup the UI so we can give feedback to the user.
//
// Since the download may be occuring over a link which has associated
// cost, we want to require user-consent before resuming the download.
// Also, applying an already downloaded update now is undesireable,
// since the phone will look dead while the update is being applied.
// Applying the update can take several minutes. Instead we wait until
// the UI is initialized so it is possible to give feedback to and get
// consent to update from the user.
if (isInterruptedUpdate(status)) {
LOG("UpdateService:_postUpdateProcessing - interrupted update detected - wait for user consent");
return;
}
#endif
var update = um.activeUpdate;
if (status == STATE_DOWNLOADING) {
LOG("UpdateService:_postUpdateProcessing - patch found in downloading " +
"state");
if (update && update.state != STATE_SUCCEEDED) {
// Resume download
var status = this.downloadUpdate(update, true);
if (status == STATE_NONE)
cleanupActiveUpdate();
}
return;
}
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
if (status == STATE_APPLYING) {
// This indicates that the background updater service is in either of the
// following two states:
// 1. It is in the process of applying an update in the background, and
// we just happen to be racing against that.
// 2. It has failed to apply an update for some reason, and we hit this
// case because the updater process has set the update status to
// applying, but has never finished.
// In order to differentiate between these two states, we look at the
// state field of the update object. If it's "pending", then we know
// that this is the first time we're hitting this case, so we switch
// that state to "applying" and we just wait and hope for the best.
// If it's "applying", we know that we've already been here once, so
// we really want to start from a clean state.
if (update &&
(update.state == STATE_PENDING || update.state == STATE_PENDING_SVC)) {
LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
"state for the first time");
update.state = STATE_APPLYING;
um.saveUpdates();
} else { // We get here even if we don't have an update object
LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
"state for the second time");
cleanupActiveUpdate();
}
return;
}
#ifdef MOZ_WIDGET_GONK
// The update is only applied but not selected to be installed
if (status == STATE_APPLIED && update && update.isOSUpdate) {
LOG("UpdateService:_postUpdateProcessing - update staged as applied found");
return;
}
if (status == STATE_APPLIED_OS && update && update.isOSUpdate) {
// In gonk, we need to check for OS update status after startup, since
// the recovery partition won't write to update.status for us
var recoveryService = Cc["@mozilla.org/recovery-service;1"].
getService(Ci.nsIRecoveryService);
var fotaStatus = recoveryService.getFotaUpdateStatus();
switch (fotaStatus) {
case Ci.nsIRecoveryService.FOTA_UPDATE_SUCCESS:
status = STATE_SUCCEEDED;
break;
case Ci.nsIRecoveryService.FOTA_UPDATE_FAIL:
status = STATE_FAILED + ": " + FOTA_GENERAL_ERROR;
break;
case Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN:
default:
status = STATE_FAILED + ": " + FOTA_UNKNOWN_ERROR;
break;
}
}
#endif
if (!update) {
if (status != STATE_SUCCEEDED) {
LOG("UpdateService:_postUpdateProcessing - previous patch failed " +
"and no patch available");
cleanupActiveUpdate();
return;
}
update = new Update(null);
}
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
update.state = status;
this._sendStatusCodeTelemetryPing(status);
if (status == STATE_SUCCEEDED) {
update.statusText = gUpdateBundle.GetStringFromName("installSuccess");
// Update the patch's metadata.
um.activeUpdate = update;
Services.prefs.setBoolPref(PREF_APP_UPDATE_POSTUPDATE, true);
prompter.showUpdateInstalled();
// Done with this update. Clean it up.
cleanupActiveUpdate();
}
else {
// If we hit an error, then the error code will be included in the status
// string following a colon and a space. If we had an I/O error, then we
// assume that the patch is not invalid, and we re-stage the patch so that
// it can be attempted again the next time we restart. This will leave a
// space at the beginning of the error code when there is a failure which
// will be removed by using parseInt below. This prevents panic which has
// occurred numerous times previously (see bug 569642 comment #9 for one
// example) when testing releases due to forgetting to include the space.
var ary = status.split(":");
update.state = ary[0];
if (update.state == STATE_FAILED && ary[1]) {
if (handleUpdateFailure(update, ary[1])) {
return;
}
}
// Something went wrong with the patch application process.
handleFallbackToCompleteUpdate(update, false);
prompter.showUpdateError(update);
}
// Now trash the MozUpdater folders which staged/bgupdates uses.
cleanUpMozUpdaterDirs();
},
/**
* Submit a telemetry ping with the boolean value of a pref for a histogram
*
* @param pref
* The preference to report
* @param histogram
* The histogram ID to report to
*/
_sendBoolPrefTelemetryPing: function AUS__boolTelemetryPing(pref, histogram) {
try {
// The getPref is already wrapped in a try/catch but we never
// want telemetry pings breaking app update so we just put it
// inside the try to be safe.
let val = getPref("getBoolPref", pref, false);
Services.telemetry.getHistogramById(histogram).add(+val);
} catch(e) {
// Don't allow any exception to be propagated.
Cu.reportError(e);
}
},
#ifdef XP_WIN
/**
* Submit a telemetry ping with a boolean value which indicates if the service
* is installed.
* Also submits a telemetry ping with a boolean value which indicates if the
* service was at some point installed, but is now uninstalled.
*/
_sendServiceInstalledTelemetryPing: function AUS__svcInstallTelemetryPing() {
let installed = isServiceInstalled(); // Is the service installed?
let attempted = 0;
try {
let wrk = Cc["@mozilla.org/windows-registry-key;1"].
createInstance(Ci.nsIWindowsRegKey);
wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
"SOFTWARE\\Mozilla\\MaintenanceService",
wrk.ACCESS_READ | wrk.WOW64_64);
// Was the service at some point installed, but is now uninstalled?
attempted = wrk.readIntValue("Attempted");
wrk.close();
} catch(e) {
}
try {
let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_INSTALLED");
h.add(Number(installed));
} catch(e) {
// Don't allow any exception to be propagated.
Cu.reportError(e);
}
try {
let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_MANUALLY_UNINSTALLED");
h.add(!installed && attempted ? 1 : 0);
} catch(e) {
// Don't allow any exception to be propagated.
Cu.reportError(e);
}
},
#endif
/**
* Submit a telemetry ping with the int value of a pref for a histogram
*
* @param pref
* The preference to report
* @param histogram
* The histogram ID to report to
*/
_sendIntPrefTelemetryPing: function AUS__intTelemetryPing(pref, histogram) {
try {
// The getPref is already wrapped in a try/catch but we never
// want telemetry pings breaking app update so we just put it
// inside the try to be safe.
let val = getPref("getIntPref", pref, 0);
Services.telemetry.getHistogramById(histogram).add(val);
} catch(e) {
// Don't allow any exception to be propagated.
Cu.reportError(e);
}
},
/**
* Submit the results of applying the update via telemetry.
*
* @param status
* The status of the update as read from the update.status file
*/
_sendStatusCodeTelemetryPing: function AUS__statusTelemetryPing(status) {
try {
let parts = status.split(":");
if ((parts.length == 1 && status != STATE_SUCCEEDED) ||
(parts.length > 1 && parts[0] != STATE_FAILED)) {
// Should also report STATE_DOWNLOAD_FAILED
return;
}
let result = 0; // 0 means success
if (parts.length > 1) {
result = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
}
Services.telemetry.getHistogramById("UPDATER_STATUS_CODES").add(result);
} catch(e) {
// Don't allow any exception to be propagated.
Cu.reportError(e);
}
},
/**
* Submit the interval in days since the last notification for this background
* update check.
*/
_sendLastNotifyIntervalPing: function AUS__notifyIntervalPing() {
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LASTUPDATETIME)) {
let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL";
let lastUpdateTimeSeconds = getPref("getIntPref",
PREF_APP_UPDATE_LASTUPDATETIME, 0);
if (lastUpdateTimeSeconds) {
let currentTimeSeconds = Math.round(Date.now() / 1000);
if (lastUpdateTimeSeconds > currentTimeSeconds) {
try {
Services.telemetry.
getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
add(1);
} catch(e) {
Cu.reportError(e);
}
}
else {
let intervalDays = (currentTimeSeconds - lastUpdateTimeSeconds) /
(60 * 60 * 24);
try {
Services.telemetry.
getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
add(0);
Services.telemetry.
getHistogramById("UPDATER_LAST_NOTIFY_INTERVAL_DAYS_" + idSuffix).
add(intervalDays);
} catch(e) {
Cu.reportError(e);
}
}
}
}
},
/**
* Submit the result for the background update check.
*
* @param code
* An integer value as defined by the PING_BGUC_* constants.
*/
_backgroundUpdateCheckCodePing: function AUS__backgroundUpdateCheckCodePing(code) {
try {
let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL";
Services.telemetry.
getHistogramById("UPDATER_BACKGROUND_CHECK_CODE_" + idSuffix).add(code);
}
catch (e) {
Cu.reportError(e);
}
},
/**
* Register an observer when the network comes online, so we can short-circuit
* the app.update.interval when there isn't connectivity
*/
_registerOnlineObserver: function AUS__registerOnlineObserver() {
if (this._registeredOnlineObserver) {
LOG("UpdateService:_registerOnlineObserver - observer already registered");
return;
}
LOG("UpdateService:_registerOnlineObserver - waiting for the network to " +
"be online, then forcing another check");
Services.obs.addObserver(this, "network:offline-status-changed", false);
this._registeredOnlineObserver = true;
},
/**
* Called from the network:offline-status-changed observer.
*/
_offlineStatusChanged: function AUS__offlineStatusChanged(status) {
if (status !== "online") {
return;
}
Services.obs.removeObserver(this, "network:offline-status-changed");
this._registeredOnlineObserver = false;
LOG("UpdateService:_offlineStatusChanged - network is online, forcing " +
"another background check");
// the background checker is contained in notify
this._attemptResume();
},
onCheckComplete: function AUS_onCheckComplete(request, updates, updateCount) {
this._selectAndInstallUpdate(updates);
},
onError: function AUS_onError(request, update) {
LOG("UpdateService:onError - error during background update. error code: " +
update.errorCode + ", status text: " + update.statusText);
var maxErrors;
var errCount;
if (update.errorCode == NETWORK_ERROR_OFFLINE) {
// Register an online observer to try again
this._registerOnlineObserver();
this._backgroundUpdateCheckCodePing(PING_BGUC_OFFLINE);
return;
}
if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) {
errCount = getPref("getIntPref", PREF_APP_UPDATE_CERT_ERRORS, 0);
errCount++;
Services.prefs.setIntPref(PREF_APP_UPDATE_CERT_ERRORS, errCount);
maxErrors = getPref("getIntPref", PREF_APP_UPDATE_CERT_MAXERRORS, 5);
}
else {
update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
errCount++;
Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
maxErrors = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS,
10);
}
var pingCode;
if (errCount >= maxErrors) {
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateError(update);
switch (update.errorCode) {
case CERT_ATTR_CHECK_FAILED_NO_UPDATE:
pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY;
break;
case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY;
break;
default:
pingCode = PING_BGUC_GENERAL_ERROR_NOTIFY;
}
}
else {
switch (update.errorCode) {
case CERT_ATTR_CHECK_FAILED_NO_UPDATE:
pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT;
break;
case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT;
break;
default:
pingCode = PING_BGUC_GENERAL_ERROR_SILENT;
}
}
this._backgroundUpdateCheckCodePing(pingCode);
},
/**
* Called when a connection should be resumed
*/
_attemptResume: function AUS_attemptResume() {
LOG("UpdateService:_attemptResume")
// If a download is in progress, then resume it.
if (this._downloader && this._downloader._patch &&
this._downloader._patch.state == STATE_DOWNLOADING &&
this._downloader._update) {
LOG("UpdateService:_attemptResume - _patch.state: " +
this._downloader._patch.state);
// Make sure downloading is the state for selectPatch to work correctly
writeStatusFile(getUpdatesDir(), STATE_DOWNLOADING);
var status = this.downloadUpdate(this._downloader._update,
this._downloader.background);
LOG("UpdateService:_attemptResume - downloadUpdate status: " + status);
if (status == STATE_NONE) {
cleanupActiveUpdate();
}
return;
}
this.backgroundChecker.checkForUpdates(this, false);
},
/**
* Notified when a timer fires
* @param timer
* The timer that fired
*/
notify: function AUS_notify(timer) {
// The telemetry below is specific to background notification.
this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_ENABLED,
"UPDATER_UPDATES_ENABLED");
this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_METRO_ENABLED,
"UPDATER_UPDATES_METRO_ENABLED");
this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_AUTO,
"UPDATER_UPDATES_AUTOMATIC");
this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGING_ENABLED,
"UPDATER_STAGE_ENABLED");
#ifdef XP_WIN
this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ENABLED,
"UPDATER_SERVICE_ENABLED");
this._sendIntPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ERRORS,
"UPDATER_SERVICE_ERRORS");
this._sendServiceInstalledTelemetryPing();
#endif
this._checkForBackgroundUpdates(true);
},
/**
* See nsIUpdateService.idl
*/
checkForBackgroundUpdates: function AUS_checkForBackgroundUpdates() {
this._checkForBackgroundUpdates(false);
},
/**
* Checks for updates in the background.
* @param isNotify
* Whether or not a background update check was initiated by the
* application update timer notification.
*/
_checkForBackgroundUpdates: function AUS__checkForBackgroundUpdates(isNotify) {
this._isNotify = isNotify;
// From this point on, the telemetry reported differentiates between a call
// to notify and a call to checkForBackgroundUpdates so they are reported
// separately.
this._sendLastNotifyIntervalPing();
// If a download is in progress or the patch has been staged do nothing.
if (this.isDownloading) {
this._backgroundUpdateCheckCodePing(PING_BGUC_IS_DOWNLOADING);
return;
}
if (this._downloader && this._downloader.patchIsStaged) {
this._backgroundUpdateCheckCodePing(PING_BGUC_IS_STAGED);
return;
}
// The following checks will return early without notification in the call
// to checkForUpdates below. To simplify the background update check ping
// their values are checked here.
try {
if (!this.backgroundChecker.getUpdateURL(false)) {
let prefs = Services.prefs;
if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE)) {
if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL)) {
this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_DEFAULT_URL);
}
else {
this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_CUSTOM_URL);
}
}
else {
this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_OVERRIDE_URL);
}
}
else if (!gMetroUpdatesEnabled) {
this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
}
else if (!getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) {
this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED);
}
else if (!gCanCheckForUpdates) {
this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_CHECK);
}
else if (!this.backgroundChecker._enabled) {
this._backgroundUpdateCheckCodePing(PING_BGUC_DISABLED_FOR_SESSION);
}
}
catch (e) {
Cu.reportError(e);
}
this.backgroundChecker.checkForUpdates(this, false);
},
/**
* Determine the update from the specified updates that should be offered.
* If both valid major and minor updates are available the minor update will
* be offered.
* @param updates
* An array of available nsIUpdate items
* @return The nsIUpdate to offer.
*/
selectUpdate: function AUS_selectUpdate(updates) {
if (updates.length == 0) {
this._backgroundUpdateCheckCodePing(PING_BGUC_NO_UPDATE_FOUND);
return null;
}
// The ping for unsupported is sent after the call to showPrompt.
if (updates.length == 1 && updates[0].unsupported)
return updates[0];
// Choose the newest of the available minor and major updates.
var majorUpdate = null;
var minorUpdate = null;
var vc = Services.vc;
var lastPingCode = PING_BGUC_NO_COMPAT_UPDATE_FOUND;
updates.forEach(function(aUpdate) {
// Ignore updates for older versions of the application and updates for
// the same version of the application with the same build ID.
if (vc.compare(aUpdate.appVersion, Services.appinfo.version) < 0 ||
vc.compare(aUpdate.appVersion, Services.appinfo.version) == 0 &&
aUpdate.buildID == Services.appinfo.appBuildID) {
LOG("UpdateService:selectUpdate - skipping update because the " +
"update's application version is less than the current " +
"application version");
lastPingCode = PING_BGUC_UPDATE_PREVIOUS_VERSION;
return;
}
// Skip the update if the user responded with "never" to this update's
// application version and the update specifies showNeverForVersion
// (see bug 350636).
let neverPrefName = PREF_APP_UPDATE_NEVER_BRANCH + aUpdate.appVersion;
if (aUpdate.showNeverForVersion &&
getPref("getBoolPref", neverPrefName, false)) {
LOG("UpdateService:selectUpdate - skipping update because the " +
"preference " + neverPrefName + " is true");
lastPingCode = PING_BGUC_UPDATE_NEVER_PREF;
return;
}
switch (aUpdate.type) {
case "major":
if (!majorUpdate)
majorUpdate = aUpdate;
else if (vc.compare(majorUpdate.appVersion, aUpdate.appVersion) <= 0)
majorUpdate = aUpdate;
break;
case "minor":
if (!minorUpdate)
minorUpdate = aUpdate;
else if (vc.compare(minorUpdate.appVersion, aUpdate.appVersion) <= 0)
minorUpdate = aUpdate;
break;
default:
LOG("UpdateService:selectUpdate - skipping unknown update type: " +
aUpdate.type);
lastPingCode = PING_BGUC_UPDATE_INVALID_TYPE;
break;
}
});
var update = minorUpdate || majorUpdate;
if (!update)
this._backgroundUpdateCheckCodePing(lastPingCode);
return update;
},
/**
* Reference to the currently selected update for when add-on compatibility
* is checked.
*/
_update: null,
/**
* Determine which of the specified updates should be installed and begin the
* download/installation process or notify the user about the update.
* @param updates
* An array of available updates
*/
_selectAndInstallUpdate: function AUS__selectAndInstallUpdate(updates) {
// Return early if there's an active update. The user is already aware and
// is downloading or performed some user action to prevent notification.
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
if (um.activeUpdate) {
#ifdef MOZ_WIDGET_GONK
// For gonk, the user isn't necessarily aware of the update, so we need
// to show the prompt to make sure.
this._showPrompt(um.activeUpdate);
#endif
this._backgroundUpdateCheckCodePing(PING_BGUC_HAS_ACTIVEUPDATE);
return;
}
var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
if (!updateEnabled) {
this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED);
LOG("UpdateService:_selectAndInstallUpdate - not prompting because " +
"update is disabled");
return;
}
if (!gMetroUpdatesEnabled) {
this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
return;
}
var update = this.selectUpdate(updates, updates.length);
if (!update) {
return;
}
if (update.unsupported) {
LOG("UpdateService:_selectAndInstallUpdate - update not supported for " +
"this system");
if (!getPref("getBoolPref", PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, false)) {
LOG("UpdateService:_selectAndInstallUpdate - notifying that the " +
"update is not supported for this system");
this._showPrompt(update);
}
this._backgroundUpdateCheckCodePing(PING_BGUC_UNSUPPORTED);
return;
}
if (!gCanApplyUpdates) {
LOG("UpdateService:_selectAndInstallUpdate - the user is unable to " +
"apply updates... prompting");
this._showPrompt(update);
this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_APPLY);
return;
}
/**
# From this point on there are two possible outcomes:
# 1. download and install the update automatically
# 2. notify the user about the availability of an update
#
# Notes:
# a) if the app.update.auto preference is false then automatic download and
# install is disabled and the user will be notified.
# b) if the update has a showPrompt attribute the user will be notified.
# c) Mode is determined by the value of the app.update.mode preference.
#
# If the update when it is first read has an appVersion attribute the
# following behavior implemented in bug 530872 will occur:
# Mode Incompatible Add-ons Outcome
# 0 N/A Auto Install
# 1 Yes Notify
# 1 No Auto Install
#
# If the update when it is first read does not have an appVersion attribute
# the following deprecated behavior will occur:
# Update Type Mode Incompatible Add-ons Outcome
# Major all N/A Notify
# Minor 0 N/A Auto Install
# Minor 1 Yes Notify
# Minor 1 No Auto Install
*/
if (update.showPrompt) {
LOG("UpdateService:_selectAndInstallUpdate - prompting because the " +
"update snippet specified showPrompt");
this._showPrompt(update);
if (!Services.metro || !Services.metro.immersive) {
this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_SNIPPET);
return;
}
}
if (!getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true)) {
LOG("UpdateService:_selectAndInstallUpdate - prompting because silent " +
"install is disabled");
this._showPrompt(update);
if (!Services.metro || !Services.metro.immersive) {
this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_PREF);
return;
}
}
if (getPref("getIntPref", PREF_APP_UPDATE_MODE, 1) == 0) {
// Do not prompt regardless of add-on incompatibilities
LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " +
"check disabled by preference, just download the update");
var status = this.downloadUpdate(update, true);
if (status == STATE_NONE)
cleanupActiveUpdate();
this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_PREF_DISABLED);
return;
}
// Only check add-on compatibility when the version changes.
if (update.appVersion &&
Services.vc.compare(update.appVersion, Services.appinfo.version) != 0) {
this._update = update;
this._checkAddonCompatibility();
}
else {
LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " +
"check not performed due to the update version being the same as " +
"the current application version, just download the update");
var status = this.downloadUpdate(update, true);
if (status == STATE_NONE)
cleanupActiveUpdate();
this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_SAME_APP_VER);
}
},
_showPrompt: function AUS__showPrompt(update) {
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateAvailable(update);
},
_checkAddonCompatibility: function AUS__checkAddonCompatibility() {
try {
var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
}
catch (e) { }
// Get all the installed add-ons
var self = this;
AddonManager.getAllAddons(function(addons) {
self._incompatibleAddons = [];
addons.forEach(function(addon) {
// Protect against code that overrides the add-ons manager and doesn't
// implement the isCompatibleWith or the findUpdates method.
if (!("isCompatibleWith" in addon) || !("findUpdates" in addon)) {
let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
"or the findUpdates method!";
if (addon.id)
errMsg += " Add-on ID: " + addon.id;
Cu.reportError(errMsg);
return;
}
// If an add-on isn't appDisabled and isn't userDisabled then it is
// either active now or the user expects it to be active after the
// restart. If that is the case and the add-on is not installed by the
// application and is not compatible with the new application version
// then the user should be warned that the add-on will become
// incompatible. If an addon's type equals plugin it is skipped since
// checking plugins compatibility information isn't supported and
// getting the scope property of a plugin breaks in some environments
// (see bug 566787). The hotfix add-on is also ignored as it shouldn't
// block the user from upgrading.
try {
if (addon.type != "plugin" && addon.id != hotfixID &&
!addon.appDisabled && !addon.userDisabled &&
addon.scope != AddonManager.SCOPE_APPLICATION &&
addon.isCompatible &&
!addon.isCompatibleWith(self._update.appVersion,
self._update.platformVersion))
self._incompatibleAddons.push(addon);
}
catch (e) {
Cu.reportError(e);
}
});
if (self._incompatibleAddons.length > 0) {
/**
# PREF_APP_UPDATE_INCOMPATIBLE_MODE
# Controls the mode in which we check for updates as follows.
#
# PREF_APP_UPDATE_INCOMPATIBLE_MODE != 1
# We check for VersionInfo _and_ NewerVersion updates for the
# incompatible add-ons - i.e. if Foo 1.2 is installed and it is
# incompatible with the update, and we find Foo 2.0 which is but has
# not been installed, then we do NOT prompt because the user can
# download Foo 2.0 when they restart after the update during the add-on
# mismatch checking UI. This is the default, since it suppresses most
# prompt dialogs.
#
# PREF_APP_UPDATE_INCOMPATIBLE_MODE == 1
# We check for VersionInfo updates for the incompatible add-ons - i.e.
# if the situation above with Foo 1.2 and available update to 2.0
# applies, we DO show the prompt since a download operation will be
# required after the update. This is not the default and is supplied
# only as a hidden option for those that want it.
*/
self._updateCheckCount = self._incompatibleAddons.length;
LOG("UpdateService:_checkAddonCompatibility - checking for " +
"incompatible add-ons");
self._incompatibleAddons.forEach(function(addon) {
addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
this._update.appVersion, this._update.platformVersion);
}, self);
}
else {
LOG("UpdateService:_checkAddonCompatibility - no incompatible " +
"add-ons found, just download the update");
var status = self.downloadUpdate(self._update, true);
if (status == STATE_NONE)
cleanupActiveUpdate();
self._update = null;
this._backgroundUpdateCheckCodePing(PING_BGUC_CHECK_NO_INCOMPAT);
}
});
},
// AddonUpdateListener
onCompatibilityUpdateAvailable: function(addon) {
// Remove the add-on from the list of add-ons that will become incompatible
// with the new version of the application.
for (var i = 0; i < this._incompatibleAddons.length; ++i) {
if (this._incompatibleAddons[i].id == addon.id) {
LOG("UpdateService:onAddonUpdateEnded - found update for add-on ID: " +
addon.id);
this._incompatibleAddons.splice(i, 1);
}
}
},
onUpdateAvailable: function(addon, install) {
if (getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE, 0) == 1)
return;
// If the new version of this add-on is blocklisted for the new application
// then it isn't a valid update and the user should still be warned that
// the add-on will become incompatible.
let bs = Cc["@mozilla.org/extensions/blocklist;1"].
getService(Ci.nsIBlocklistService);
if (bs.isAddonBlocklisted(addon.id, install.version,
gUpdates.update.appVersion,
gUpdates.update.platformVersion))
return;
// Compatibility or new version updates mean the same thing here.
this.onCompatibilityUpdateAvailable(addon);
},
onUpdateFinished: function(addon) {
if (--this._updateCheckCount > 0)
return;
if (this._incompatibleAddons.length > 0 || !gCanApplyUpdates) {
LOG("UpdateService:onUpdateEnded - prompting because there are " +
"incompatible add-ons");
this._showPrompt(this._update);
this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_HAVE_INCOMPAT);
}
else {
LOG("UpdateService:_selectAndInstallUpdate - updates for all " +
"incompatible add-ons found, just download the update");
var status = this.downloadUpdate(this._update, true);
if (status == STATE_NONE)
cleanupActiveUpdate();
this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT);
}
this._update = null;
},
/**
* The Checker used for background update checks.
*/
_backgroundChecker: null,
/**
* See nsIUpdateService.idl
*/
get backgroundChecker() {
if (!this._backgroundChecker)
this._backgroundChecker = new Checker();
return this._backgroundChecker;
},
/**
* See nsIUpdateService.idl
*/
get canCheckForUpdates() {
return gCanCheckForUpdates;
},
/**
* See nsIUpdateService.idl
*/
get canApplyUpdates() {
return gCanApplyUpdates;
},
/**
* See nsIUpdateService.idl
*/
get canStageUpdates() {
return getCanStageUpdates();
},
/**
* See nsIUpdateService.idl
*/
get isOtherInstanceHandlingUpdates() {
return !hasUpdateMutex();
},
/**
* See nsIUpdateService.idl
*/
addDownloadListener: function AUS_addDownloadListener(listener) {
if (!this._downloader) {
LOG("UpdateService:addDownloadListener - no downloader!");
return;
}
this._downloader.addDownloadListener(listener);
},
/**
* See nsIUpdateService.idl
*/
removeDownloadListener: function AUS_removeDownloadListener(listener) {
if (!this._downloader) {
LOG("UpdateService:removeDownloadListener - no downloader!");
return;
}
this._downloader.removeDownloadListener(listener);
},
/**
* See nsIUpdateService.idl
*/
downloadUpdate: function AUS_downloadUpdate(update, background) {
if (!update)
throw Cr.NS_ERROR_NULL_POINTER;
// Don't download the update if the update's version is less than the
// current application's version or the update's version is the same as the
// application's version and the build ID is the same as the application's
// build ID.
if (update.appVersion &&
(Services.vc.compare(update.appVersion, Services.appinfo.version) < 0 ||
update.buildID && update.buildID == Services.appinfo.appBuildID &&
update.appVersion == Services.appinfo.version)) {
LOG("UpdateService:downloadUpdate - canceling download of update since " +
"it is for an earlier or same application version and build ID.\n" +
"current application version: " + Services.appinfo.version + "\n" +
"update application version : " + update.appVersion + "\n" +
"current build ID: " + Services.appinfo.appBuildID + "\n" +
"update build ID : " + update.buildID);
cleanupActiveUpdate();
return STATE_NONE;
}
// If a download request is in progress vs. a download ready to resume
if (this.isDownloading) {
if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
background == this._downloader.background) {
LOG("UpdateService:downloadUpdate - no support for downloading more " +
"than one update at a time");
return readStatusFile(getUpdatesDir());
}
this._downloader.cancel();
}
#ifdef MOZ_WIDGET_GONK
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
var activeUpdate = um.activeUpdate;
if (activeUpdate &&
(activeUpdate.appVersion != update.appVersion ||
activeUpdate.buildID != update.buildID)) {
// We have an activeUpdate (which presumably was interrupted), and are
// about start downloading a new one. Make sure we remove all traces
// of the active one (otherwise we'll start appending the new update.mar
// the the one that's been partially downloaded).
LOG("UpdateService:downloadUpdate - removing stale active update.");
cleanupActiveUpdate();
}
#endif
// Set the previous application version prior to downloading the update.
update.previousAppVersion = Services.appinfo.version;
this._downloader = new Downloader(background, this);
return this._downloader.downloadUpdate(update);
},
/**
* See nsIUpdateService.idl
*/
pauseDownload: function AUS_pauseDownload() {
if (this.isDownloading) {
this._downloader.cancel();
} else if (this._retryTimer) {
// Download status is still consider as 'downloading' during retry.
// We need to cancel both retry and download at this stage.
this._retryTimer.cancel();
this._retryTimer = null;
this._downloader.cancel();
}
},
/**
* See nsIUpdateService.idl
*/
getUpdatesDirectory: getUpdatesDir,
/**
* See nsIUpdateService.idl
*/
get isDownloading() {
return this._downloader && this._downloader.isBusy;
},
/**
* See nsIUpdateService.idl
*/
applyOsUpdate: function AUS_applyOsUpdate(aUpdate) {
if (!aUpdate.isOSUpdate || aUpdate.state != STATE_APPLIED) {
aUpdate.statusText = "fota-state-error";
throw Cr.NS_ERROR_FAILURE;
}
let osApplyToDir;
try {
aUpdate.QueryInterface(Ci.nsIWritablePropertyBag);
osApplyToDir = aUpdate.getProperty("osApplyToDir");
} catch (e) {}
if (!osApplyToDir) {
LOG("UpdateService:applyOsUpdate - Error: osApplyToDir is not defined" +
"in the nsIUpdate!");
handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
return;
}
let updateFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
updateFile.initWithPath(osApplyToDir + "/update.zip");
if (!updateFile.exists()) {
LOG("UpdateService:applyOsUpdate - Error: OS update is not found at " +
updateFile.path);
handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
return;
}
writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED_OS);
LOG("UpdateService:applyOsUpdate - Rebooting into recovery to apply " +
"FOTA update: " + updateFile.path);
try {
let recoveryService = Cc["@mozilla.org/recovery-service;1"]
.getService(Ci.nsIRecoveryService);
recoveryService.installFotaUpdate(updateFile.path);
} catch (e) {
LOG("UpdateService:applyOsUpdate - Error: Couldn't reboot into recovery" +
" to apply FOTA update " + updateFile.path);
writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED);
handleUpdateFailure(aUpdate, FOTA_RECOVERY_ERROR);
}
},
classID: UPDATESERVICE_CID,
classInfo: XPCOMUtils.generateCI({classID: UPDATESERVICE_CID,
contractID: UPDATESERVICE_CONTRACTID,
interfaces: [Ci.nsIApplicationUpdateService,
Ci.nsITimerCallback,
Ci.nsIObserver],
flags: Ci.nsIClassInfo.SINGLETON}),
_xpcom_factory: UpdateServiceFactory,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
Ci.nsIUpdateCheckListener,
Ci.nsIAddonUpdateCheckListener,
Ci.nsITimerCallback,
Ci.nsIObserver])
};
/**
* A service to manage active and past updates.
* @constructor
*/
function UpdateManager() {
// Ensure the Active Update file is loaded
var updates = this._loadXMLFileIntoArray(getUpdateFile(
[FILE_UPDATE_ACTIVE]));
if (updates.length > 0) {
// Under some edgecases such as Windows system restore the active-update.xml
// will contain a pending update without the status file which will return
// STATE_NONE. To recover from this situation clean the updates dir and
// rewrite the active-update.xml file without the broken update.
if (readStatusFile(getUpdatesDir()) == STATE_NONE) {
cleanUpUpdatesDir();
this._writeUpdatesToXMLFile([], getUpdateFile([FILE_UPDATE_ACTIVE]));
}
else
this._activeUpdate = updates[0];
}
}
UpdateManager.prototype = {
/**
* All previously downloaded and installed updates, as an array of nsIUpdate
* objects.
*/
_updates: null,
/**
* The current actively downloading/installing update, as a nsIUpdate object.
*/
_activeUpdate: null,
/**
* Handle Observer Service notifications
* @param subject
* The subject of the notification
* @param topic
* The notification name
* @param data
* Additional data
*/
observe: function UM_observe(subject, topic, data) {
// Hack to be able to run and cleanup tests by reloading the update data.
if (topic == "um-reload-update-data") {
this._updates = this._loadXMLFileIntoArray(getUpdateFile(
[FILE_UPDATES_DB]));
this._activeUpdate = null;
var updates = this._loadXMLFileIntoArray(getUpdateFile(
[FILE_UPDATE_ACTIVE]));
if (updates.length > 0)
this._activeUpdate = updates[0];
}
},
/**
* Loads an updates.xml formatted file into an array of nsIUpdate items.
* @param file
* A nsIFile for the updates.xml file
* @return The array of nsIUpdate items held in the file.
*/
_loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(file) {
if (!file.exists()) {
LOG("UpdateManager:_loadXMLFileIntoArray: XML file does not exist");
return [];
}
var result = [];
var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
try {
var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
createInstance(Ci.nsIDOMParser);
var doc = parser.parseFromStream(fileStream, "UTF-8",
fileStream.available(), "text/xml");
const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
var updateCount = doc.documentElement.childNodes.length;
for (var i = 0; i < updateCount; ++i) {
var updateElement = doc.documentElement.childNodes.item(i);
if (updateElement.nodeType != ELEMENT_NODE ||
updateElement.localName != "update")
continue;
updateElement.QueryInterface(Ci.nsIDOMElement);
try {
var update = new Update(updateElement);
} catch (e) {
LOG("UpdateManager:_loadXMLFileIntoArray - invalid update");
continue;
}
result.push(update);
}
}
catch (e) {
LOG("UpdateManager:_loadXMLFileIntoArray - error constructing update " +
"list. Exception: " + e);
}
fileStream.close();
return result;
},
/**
* Load the update manager, initializing state from state files.
*/
_ensureUpdates: function UM__ensureUpdates() {
if (!this._updates) {
this._updates = this._loadXMLFileIntoArray(getUpdateFile(
[FILE_UPDATES_DB]));
var activeUpdates = this._loadXMLFileIntoArray(getUpdateFile(
[FILE_UPDATE_ACTIVE]));
if (activeUpdates.length > 0)
this._activeUpdate = activeUpdates[0];
}
},
/**
* See nsIUpdateService.idl
*/
getUpdateAt: function UM_getUpdateAt(index) {
this._ensureUpdates();
return this._updates[index];
},
/**
* See nsIUpdateService.idl
*/
get updateCount() {
this._ensureUpdates();
return this._updates.length;
},
/**
* See nsIUpdateService.idl
*/
get activeUpdate() {
if (this._activeUpdate &&
this._activeUpdate.channel != UpdateChannel.get()) {
LOG("UpdateManager:get activeUpdate - channel has changed, " +
"reloading default preferences to workaround bug 802022");
// Workaround to get distribution preferences loaded (Bug 774618). This
// can be removed after bug 802022 is fixed.
let prefSvc = Services.prefs.QueryInterface(Ci.nsIObserver);
prefSvc.observe(null, "reload-default-prefs", null);
if (this._activeUpdate.channel != UpdateChannel.get()) {
// User switched channels, clear out any old active updates and remove
// partial downloads
this._activeUpdate = null;
this.saveUpdates();
// Destroy the updates directory, since we're done with it.
cleanUpUpdatesDir();
}
}
return this._activeUpdate;
},
set activeUpdate(activeUpdate) {
this._addUpdate(activeUpdate);
this._activeUpdate = activeUpdate;
if (!activeUpdate) {
// If |activeUpdate| is null, we have updated both lists - the active list
// and the history list, so we want to write both files.
this.saveUpdates();
}
else
this._writeUpdatesToXMLFile([this._activeUpdate],
getUpdateFile([FILE_UPDATE_ACTIVE]));
return activeUpdate;
},
/**
* Add an update to the Updates list. If the item already exists in the list,
* replace the existing value with the new value.
* @param update
* The nsIUpdate object to add.
*/
_addUpdate: function UM__addUpdate(update) {
if (!update)
return;
this._ensureUpdates();
if (this._updates) {
for (var i = 0; i < this._updates.length; ++i) {
if (this._updates[i] &&
this._updates[i].appVersion == update.appVersion &&
this._updates[i].buildID == update.buildID) {
// Replace the existing entry with the new value, updating
// all metadata.
this._updates[i] = update;
return;
}
}
}
// Otherwise add it to the front of the list.
this._updates.unshift(update);
},
/**
* Serializes an array of updates to an XML file
* @param updates
* An array of nsIUpdate objects
* @param file
* The nsIFile object to serialize to
*/
_writeUpdatesToXMLFile: function UM__writeUpdatesToXMLFile(updates, file) {
var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
createInstance(Ci.nsIFileOutputStream);
var modeFlags = FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
FileUtils.MODE_TRUNCATE;
if (!file.exists())
file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
fos.init(file, modeFlags, FileUtils.PERMS_FILE, 0);
try {
var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
createInstance(Ci.nsIDOMParser);
const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>";
var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml");
for (var i = 0; i < updates.length; ++i) {
if (updates[i])
doc.documentElement.appendChild(updates[i].serialize(doc));
}
var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
createInstance(Ci.nsIDOMSerializer);
serializer.serializeToStream(doc.documentElement, fos, null);
}
catch (e) {
}
FileUtils.closeSafeFileOutputStream(fos);
},
/**
* See nsIUpdateService.idl
*/
saveUpdates: function UM_saveUpdates() {
this._writeUpdatesToXMLFile([this._activeUpdate],
getUpdateFile([FILE_UPDATE_ACTIVE]));
if (this._activeUpdate)
this._addUpdate(this._activeUpdate);
this._ensureUpdates();
// Don't write updates that have a temporary state to the updates.xml file.
if (this._updates) {
let updates = this._updates.slice();
for (let i = updates.length - 1; i >= 0; --i) {
let state = updates[i].state;
if (state == STATE_NONE || state == STATE_DOWNLOADING ||
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
state == STATE_APPLIED || state == STATE_APPLIED_SVC ||
state == STATE_PENDING || state == STATE_PENDING_SVC) {
updates.splice(i, 1);
}
}
this._writeUpdatesToXMLFile(updates.slice(0, 10),
getUpdateFile([FILE_UPDATES_DB]));
}
},
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
refreshUpdateStatus: function UM_refreshUpdateStatus(update) {
var updateSucceeded = true;
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
var status = readStatusFile(getUpdatesDir());
var ary = status.split(":");
update.state = ary[0];
if (update.state == STATE_FAILED && ary[1]) {
updateSucceeded = false;
if (!handleUpdateFailure(update, ary[1])) {
handleFallbackToCompleteUpdate(update, true);
}
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
}
if (update.state == STATE_APPLIED && shouldUseService()) {
writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SVC);
}
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
um.saveUpdates();
if (update.state != STATE_PENDING && update.state != STATE_PENDING_SVC) {
// Destroy the updates directory, since we're done with it.
// Make sure to not do this when the updater has fallen back to
// non-staged updates.
cleanUpUpdatesDir(updateSucceeded);
}
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
// Send an observer notification which the update wizard uses in
// order to update its UI.
LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " +
"the update was staged. state: " + update.state + ", status: " + status);
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
Services.obs.notifyObservers(null, "update-staged", update.state);
// Do this after *everything* else, since it will likely cause the app
// to shut down.
#ifdef MOZ_WIDGET_GONK
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
if (update.state == STATE_APPLIED) {
// Notify the user that an update has been staged and is ready for
// installation (i.e. that they should restart the application). We do
// not notify on failed update attempts.
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateDownloaded(update, true);
} else {
releaseSDCardMountLock();
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
}
#else
// Only prompt when the UI isn't already open.
let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) ||
windowType && Services.wm.getMostRecentWindow(windowType)) {
return;
}
if (update.state == STATE_APPLIED || update.state == STATE_APPLIED_SVC ||
update.state == STATE_PENDING || update.state == STATE_PENDING_SVC) {
// Notify the user that an update has been staged and is ready for
// installation (i.e. that they should restart the application).
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateDownloaded(update, true);
}
#endif
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
},
classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
};
/**
* Checker
* Checks for new Updates
* @constructor
*/
function Checker() {
}
Checker.prototype = {
/**
* The XMLHttpRequest object that performs the connection.
*/
_request : null,
/**
* The nsIUpdateCheckListener callback
*/
_callback : null,
/**
* The URL of the update service XML file to connect to that contains details
* about available updates.
*/
getUpdateURL: function UC_getUpdateURL(force) {
this._forced = force;
// Use the override URL if specified.
var url = getPref("getCharPref", PREF_APP_UPDATE_URL_OVERRIDE, null);
// Otherwise, construct the update URL from component parts.
if (!url) {
try {
url = Services.prefs.getDefaultBranch(null).
getCharPref(PREF_APP_UPDATE_URL);
} catch (e) {
}
}
if (!url || url == "") {
LOG("Checker:getUpdateURL - update URL not defined");
return null;
}
url = url.replace(/%PRODUCT%/g, Services.appinfo.name);
url = url.replace(/%VERSION%/g, Services.appinfo.version);
url = url.replace(/%BUILD_ID%/g, Services.appinfo.appBuildID);
url = url.replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + gABI);
url = url.replace(/%OS_VERSION%/g, gOSVersion);
if (/%LOCALE%/.test(url))
url = url.replace(/%LOCALE%/g, getLocale());
url = url.replace(/%CHANNEL%/g, UpdateChannel.get());
url = url.replace(/%PLATFORM_VERSION%/g, Services.appinfo.platformVersion);
url = url.replace(/%DISTRIBUTION%/g,
getDistributionPrefValue(PREF_APP_DISTRIBUTION));
url = url.replace(/%DISTRIBUTION_VERSION%/g,
getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
url = url.replace(/%CUSTOM%/g, getPref("getCharPref", PREF_APP_UPDATE_CUSTOM, ""));
url = url.replace(/\+/g, "%2B");
#ifdef MOZ_WIDGET_GONK
url = url.replace(/%PRODUCT_MODEL%/g, gProductModel);
url = url.replace(/%B2G_VERSION%/g, getPref("getCharPref", PREF_APP_B2G_VERSION, null));
#endif
if (force)
url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
LOG("Checker:getUpdateURL - update URL: " + url);
return url;
},
/**
* See nsIUpdateService.idl
*/
checkForUpdates: function UC_checkForUpdates(listener, force) {
LOG("Checker: checkForUpdates, force: " + force);
if (!listener)
throw Cr.NS_ERROR_NULL_POINTER;
Services.obs.notifyObservers(null, "update-check-start", null);
var url = this.getUpdateURL(force);
if (!url || (!this.enabled && !force))
return;
this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsISupports);
// This is here to let unit test code override XHR
if (this._request.wrappedJSObject) {
this._request = this._request.wrappedJSObject;
}
this._request.open("GET", url, true);
var allowNonBuiltIn = !getPref("getBoolPref",
PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true);
this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(allowNonBuiltIn);
// Prevent the request from reading from the cache.
this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
// Prevent the request from writing to the cache.
this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
this._request.overrideMimeType("text/xml");
// The Cache-Control header is only interpreted by proxies and the
// final destination. It does not help if a resource is already
// cached locally.
this._request.setRequestHeader("Cache-Control", "no-cache");
// HTTP/1.0 servers might not implement Cache-Control and
// might only implement Pragma: no-cache
this._request.setRequestHeader("Pragma", "no-cache");
var self = this;
2011-09-29 09:06:36 -07:00
this._request.addEventListener("error", function(event) { self.onError(event); } ,false);
this._request.addEventListener("load", function(event) { self.onLoad(event); }, false);
LOG("Checker:checkForUpdates - sending request to: " + url);
this._request.send(null);
this._callback = listener;
},
/**
* Returns an array of nsIUpdate objects discovered by the update check.
* @throws if the XML document element node name is not updates.
*/
get _updates() {
var updatesElement = this._request.responseXML.documentElement;
if (!updatesElement) {
LOG("Checker:_updates get - empty updates document?!");
return [];
}
if (updatesElement.nodeName != "updates") {
LOG("Checker:_updates get - unexpected node name!");
throw new Error("Unexpected node name, expected: updates, got: " +
updatesElement.nodeName);
}
const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
var updates = [];
for (var i = 0; i < updatesElement.childNodes.length; ++i) {
var updateElement = updatesElement.childNodes.item(i);
if (updateElement.nodeType != ELEMENT_NODE ||
updateElement.localName != "update")
continue;
updateElement.QueryInterface(Ci.nsIDOMElement);
try {
var update = new Update(updateElement);
} catch (e) {
LOG("Checker:_updates get - invalid <update/>, ignoring...");
continue;
}
update.serviceURL = this.getUpdateURL(this._forced);
update.channel = UpdateChannel.get();
updates.push(update);
}
return updates;
},
/**
* Returns the status code for the XMLHttpRequest
*/
_getChannelStatus: function UC__getChannelStatus(request) {
var status = 0;
try {
status = request.status;
}
catch (e) {
}
if (status == 0)
status = request.channel.QueryInterface(Ci.nsIRequest).status;
return status;
},
_isHttpStatusCode: function UC__isHttpStatusCode(status) {
return status >= 100 && status <= 599;
},
/**
* The XMLHttpRequest succeeded and the document was loaded.
* @param event
* The nsIDOMEvent for the load
*/
onLoad: function UC_onLoad(event) {
LOG("Checker:onLoad - request completed downloading document");
var prefs = Services.prefs;
var certs = null;
if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE) &&
getPref("getBoolPref", PREF_APP_UPDATE_CERT_CHECKATTRS, true)) {
certs = gCertUtils.readCertPrefs(PREF_APP_UPDATE_CERTS_BRANCH);
}
try {
// Analyze the resulting DOM and determine the set of updates.
var updates = this._updates;
LOG("Checker:onLoad - number of updates available: " + updates.length);
var allowNonBuiltIn = !getPref("getBoolPref",
PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true);
gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS))
Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS);
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS))
Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
// Tell the callback about the updates
this._callback.onCheckComplete(event.target, updates, updates.length);
}
catch (e) {
LOG("Checker:onLoad - there was a problem checking for updates. " +
"Exception: " + e);
var request = event.target;
var status = this._getChannelStatus(request);
LOG("Checker:onLoad - request.status: " + status);
var update = new Update(null);
update.errorCode = status;
update.statusText = getStatusTextFromCode(status, 404);
if (this._isHttpStatusCode(status)) {
update.errorCode = HTTP_ERROR_OFFSET + status;
}
if (e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
update.errorCode = updates[0] ? CERT_ATTR_CHECK_FAILED_HAS_UPDATE
: CERT_ATTR_CHECK_FAILED_NO_UPDATE;
}
this._callback.onError(request, update);
}
this._callback = null;
this._request = null;
},
/**
* There was an error of some kind during the XMLHttpRequest
* @param event
* The nsIDOMEvent for the error
*/
onError: function UC_onError(event) {
var request = event.target;
var status = this._getChannelStatus(request);
LOG("Checker:onError - request.status: " + status);
// If we can't find an error string specific to this status code,
// just use the 200 message from above, which means everything
// "looks" fine but there was probably an XML error or a bogus file.
var update = new Update(null);
update.errorCode = status;
update.statusText = getStatusTextFromCode(status, 200);
if (status == Cr.NS_ERROR_OFFLINE) {
// We use a separate constant here because nsIUpdate.errorCode is signed
update.errorCode = NETWORK_ERROR_OFFLINE;
} else if (this._isHttpStatusCode(status)) {
update.errorCode = HTTP_ERROR_OFFSET + status;
}
this._callback.onError(request, update);
this._request = null;
},
/**
* Whether or not we are allowed to do update checking.
*/
_enabled: true,
get enabled() {
if (!gMetroUpdatesEnabled) {
return false;
}
return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
gCanCheckForUpdates && this._enabled;
},
/**
* See nsIUpdateService.idl
*/
stopChecking: function UC_stopChecking(duration) {
// Always stop the current check
if (this._request)
this._request.abort();
switch (duration) {
case Ci.nsIUpdateChecker.CURRENT_SESSION:
this._enabled = false;
break;
case Ci.nsIUpdateChecker.ANY_CHECKS:
this._enabled = false;
Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
break;
}
this._callback = null;
},
classID: Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateChecker])
};
/**
* Manages the download of updates
* @param background
* Whether or not this downloader is operating in background
* update mode.
* @param updateService
* The update service that created this downloader.
* @constructor
*/
function Downloader(background, updateService) {
LOG("Creating Downloader");
this.background = background;
this.updateService = updateService;
}
Downloader.prototype = {
/**
* The nsIUpdatePatch that we are downloading
*/
_patch: null,
/**
* The nsIUpdate that we are downloading
*/
_update: null,
/**
* The nsIIncrementalDownload object handling the download
*/
_request: null,
/**
* Whether or not the update being downloaded is a complete replacement of
* the user's existing installation or a patch representing the difference
* between the new version and the previous version.
*/
isCompleteUpdate: null,
/**
* Cancels the active download.
*/
cancel: function Downloader_cancel(cancelError) {
LOG("Downloader: cancel");
if (cancelError === undefined) {
cancelError = Cr.NS_BINDING_ABORTED;
}
if (this._request && this._request instanceof Ci.nsIRequest) {
this._request.cancel(cancelError);
}
releaseSDCardMountLock();
},
/**
* Whether or not a patch has been downloaded and staged for installation.
*/
get patchIsStaged() {
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
var readState = readStatusFile(getUpdatesDir());
// Note that if we decide to download and apply new updates after another
// update has been successfully applied in the background, we need to stop
// checking for the APPLIED state here.
return readState == STATE_PENDING || readState == STATE_PENDING_SVC ||
readState == STATE_APPLIED || readState == STATE_APPLIED_SVC;
},
/**
* Verify the downloaded file. We assume that the download is complete at
* this point.
*/
_verifyDownload: function Downloader__verifyDownload() {
LOG("Downloader:_verifyDownload called");
if (!this._request)
return false;
var destination = this._request.destination;
// Ensure that the file size matches the expected file size.
if (destination.fileSize != this._patch.size) {
LOG("Downloader:_verifyDownload downloaded size != expected size.");
return false;
}
LOG("Downloader:_verifyDownload downloaded size == expected size.");
var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
try {
var hash = Cc["@mozilla.org/security/hash;1"].
createInstance(Ci.nsICryptoHash);
var hashFunction = Ci.nsICryptoHash[this._patch.hashFunction.toUpperCase()];
if (hashFunction == undefined)
throw Cr.NS_ERROR_UNEXPECTED;
hash.init(hashFunction);
hash.updateFromStream(fileStream, -1);
// NOTE: For now, we assume that the format of _patch.hashValue is hex
// encoded binary (such as what is typically output by programs like
// sha1sum). In the future, this may change to base64 depending on how
// we choose to compute these hashes.
digest = binaryToHex(hash.finish(false));
} catch (e) {
LOG("Downloader:_verifyDownload - failed to compute hash of the " +
"downloaded update archive");
digest = "";
}
fileStream.close();
if (digest == this._patch.hashValue.toLowerCase()) {
LOG("Downloader:_verifyDownload hashes match.");
return true;
}
LOG("Downloader:_verifyDownload hashes do not match. ");
return false;
},
/**
* Select the patch to use given the current state of updateDir and the given
* set of update patches.
* @param update
* A nsIUpdate object to select a patch from
* @param updateDir
* A nsIFile representing the update directory
* @return A nsIUpdatePatch object to download
*/
_selectPatch: function Downloader__selectPatch(update, updateDir) {
// Given an update to download, we will always try to download the patch
// for a partial update over the patch for a full update.
/**
* Return the first UpdatePatch with the given type.
* @param type
* The type of the patch ("complete" or "partial")
* @return A nsIUpdatePatch object matching the type specified
*/
function getPatchOfType(type) {
for (var i = 0; i < update.patchCount; ++i) {
var patch = update.getPatchAt(i);
if (patch && patch.type == type)
return patch;
}
return null;
}
// Look to see if any of the patches in the Update object has been
// pre-selected for download, otherwise we must figure out which one
// to select ourselves.
var selectedPatch = update.selectedPatch;
var state = readStatusFile(updateDir);
// If this is a patch that we know about, then select it. If it is a patch
// that we do not know about, then remove it and use our default logic.
var useComplete = false;
if (selectedPatch) {
LOG("Downloader:_selectPatch - found existing patch with state: " +
state);
switch (state) {
case STATE_DOWNLOADING:
LOG("Downloader:_selectPatch - resuming download");
return selectedPatch;
#ifdef MOZ_WIDGET_GONK
case STATE_PENDING:
case STATE_APPLYING:
LOG("Downloader:_selectPatch - resuming interrupted apply");
return selectedPatch;
case STATE_APPLIED:
LOG("Downloader:_selectPatch - already downloaded and staged");
return null;
#else
case STATE_PENDING_SVC:
case STATE_PENDING:
LOG("Downloader:_selectPatch - already downloaded and staged");
return null;
#endif
default:
// Something went wrong when we tried to apply the previous patch.
// Try the complete patch next time.
if (update && selectedPatch.type == "partial") {
useComplete = true;
} else {
// This is a pretty fatal error. Just bail.
LOG("Downloader:_selectPatch - failed to apply complete patch!");
writeStatusFile(updateDir, STATE_NONE);
writeVersionFile(getUpdatesDir(), null);
return null;
}
}
selectedPatch = null;
}
// If we were not able to discover an update from a previous download, we
// select the best patch from the given set.
var partialPatch = getPatchOfType("partial");
if (!useComplete)
selectedPatch = partialPatch;
if (!selectedPatch) {
if (partialPatch)
partialPatch.selected = false;
selectedPatch = getPatchOfType("complete");
}
// Restore the updateDir since we may have deleted it.
updateDir = getUpdatesDir();
// if update only contains a partial patch, selectedPatch == null here if
// the partial patch has been attempted and fails and we're trying to get a
// complete patch
if (selectedPatch)
selectedPatch.selected = true;
update.isCompleteUpdate = useComplete;
// Reset the Active Update object on the Update Manager and flush the
// Active Update DB.
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
um.activeUpdate = update;
return selectedPatch;
},
/**
* Whether or not we are currently downloading something.
*/
get isBusy() {
return this._request != null;
},
/**
* Get the nsIFile to use for downloading the active update's selected patch
*/
_getUpdateArchiveFile: function Downloader__getUpdateArchiveFile() {
var updateArchive;
#ifdef USE_UPDATE_ARCHIVE_DIR
try {
updateArchive = FileUtils.getDir(KEY_UPDATE_ARCHIVE_DIR, [], true);
} catch (e) {
return null;
}
#else
updateArchive = getUpdatesDir().clone();
#endif
updateArchive.append(FILE_UPDATE_ARCHIVE);
return updateArchive;
},
/**
* Download and stage the given update.
* @param update
* A nsIUpdate object to download a patch for. Cannot be null.
*/
downloadUpdate: function Downloader_downloadUpdate(update) {
LOG("UpdateService:_downloadUpdate");
if (!update)
throw Cr.NS_ERROR_NULL_POINTER;
var updateDir = getUpdatesDir();
this._update = update;
// This function may return null, which indicates that there are no patches
// to download.
this._patch = this._selectPatch(update, updateDir);
if (!this._patch) {
LOG("Downloader:downloadUpdate - no patch to download");
return readStatusFile(updateDir);
}
this.isCompleteUpdate = this._patch.type == "complete";
var patchFile = null;
#ifdef MOZ_WIDGET_GONK
let status = readStatusFile(updateDir);
if (isInterruptedUpdate(status)) {
LOG("Downloader:downloadUpdate - interruptted update");
// The update was interrupted. Try to locate the existing patch file.
// For an interrupted download, this allows a resume rather than a
// re-download.
patchFile = getFileFromUpdateLink(updateDir);
if (!patchFile) {
// No link file. We'll just assume that the update.mar is in the
// update directory.
patchFile = updateDir.clone();
patchFile.append(FILE_UPDATE_ARCHIVE);
}
if (patchFile.exists()) {
LOG("Downloader:downloadUpdate - resuming with patchFile " + patchFile.path);
if (patchFile.fileSize == this._patch.size) {
LOG("Downloader:downloadUpdate - patchFile appears to be fully downloaded");
// Bump the status along so that we don't try to redownload again.
status = STATE_PENDING;
}
} else {
LOG("Downloader:downloadUpdate - patchFile " + patchFile.path +
" doesn't exist - performing full download");
// The patchfile doesn't exist, we might as well treat this like
// a new download.
patchFile = null;
}
if (patchFile && (status != STATE_DOWNLOADING)) {
// It looks like the patch was downloaded, but got interrupted while it
// was being verified or applied. So we'll fake the downloading portion.
writeStatusFile(updateDir, STATE_PENDING);
// Since the code expects the onStopRequest callback to happen
// asynchronously (And you have to call AUS_addDownloadListener
// after calling AUS_downloadUpdate) we need to defer this.
this._downloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._downloadTimer.initWithCallback(function() {
this._downloadTimer = null;
// Send a fake onStopRequest. Filling in the destination allows
// _verifyDownload to work, and then the update will be applied.
this._request = {destination: patchFile};
this.onStopRequest(this._request, null, Cr.NS_OK);
}.bind(this), 0, Ci.nsITimer.TYPE_ONE_SHOT);
// Returning STATE_DOWNLOADING makes UpdatePrompt think we're
// downloading. The onStopRequest that we spoofed above will make it
// look like the download finished.
return STATE_DOWNLOADING;
}
}
#endif
if (!patchFile) {
// Find a place to put the patchfile that we're going to download.
patchFile = this._getUpdateArchiveFile();
}
if (!patchFile) {
return STATE_NONE;
}
#ifdef MOZ_WIDGET_GONK
if (patchFile.path.indexOf(updateDir.path) != 0) {
// The patchFile is in a directory which is different from the
// updateDir, create a link file.
writeLinkFile(updateDir, patchFile);
if (!isInterruptedUpdate(status) && patchFile.exists()) {
// Remove stale patchFile
patchFile.remove(false);
}
}
#endif
var uri = Services.io.newURI(this._patch.URL, null, null);
this._request = Cc["@mozilla.org/network/incremental-download;1"].
createInstance(Ci.nsIIncrementalDownload);
LOG("Downloader:downloadUpdate - downloading from " + uri.spec + " to " +
patchFile.path);
var interval = this.background ? getPref("getIntPref",
PREF_APP_UPDATE_BACKGROUND_INTERVAL,
DOWNLOAD_BACKGROUND_INTERVAL)
: DOWNLOAD_FOREGROUND_INTERVAL;
this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
this._request.start(this, null);
writeStatusFile(updateDir, STATE_DOWNLOADING);
this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
this._patch.state = STATE_DOWNLOADING;
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
um.saveUpdates();
return STATE_DOWNLOADING;
},
/**
* An array of download listeners to notify when we receive
* nsIRequestObserver or nsIProgressEventSink method calls.
*/
_listeners: [],
/**
* Adds a listener to the download process
* @param listener
* A download listener, implementing nsIRequestObserver and
* nsIProgressEventSink
*/
addDownloadListener: function Downloader_addDownloadListener(listener) {
for (var i = 0; i < this._listeners.length; ++i) {
if (this._listeners[i] == listener)
return;
}
this._listeners.push(listener);
},
/**
* Removes a download listener
* @param listener
* The listener to remove.
*/
removeDownloadListener: function Downloader_removeDownloadListener(listener) {
for (var i = 0; i < this._listeners.length; ++i) {
if (this._listeners[i] == listener) {
this._listeners.splice(i, 1);
return;
}
}
},
/**
* When the async request begins
* @param request
* The nsIRequest object for the transfer
* @param context
* Additional data
*/
onStartRequest: function Downloader_onStartRequest(request, context) {
if (request instanceof Ci.nsIIncrementalDownload)
LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec +
", final URI spec: " + request.finalURI.spec);
// Always set finalURL in onStartRequest since it can change.
this._patch.finalURL = request.finalURI.spec;
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
um.saveUpdates();
var listeners = this._listeners.concat();
var listenerCount = listeners.length;
for (var i = 0; i < listenerCount; ++i)
listeners[i].onStartRequest(request, context);
},
/**
* 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 Downloader_onProgress(request, context, progress,
maxProgress) {
LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
if (progress > this._patch.size) {
LOG("Downloader:onProgress - progress: " + progress +
" is higher than patch size: " + this._patch.size);
// It's important that we use a different code than
// NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
// between a hash error and a wrong download error.
this.cancel(Cr.NS_ERROR_UNEXPECTED);
return;
}
if (maxProgress != this._patch.size) {
LOG("Downloader:onProgress - maxProgress: " + maxProgress +
" is not equal to expectd patch size: " + this._patch.size);
// It's important that we use a different code than
// NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
// between a hash error and a wrong download error.
this.cancel(Cr.NS_ERROR_UNEXPECTED);
return;
}
var listeners = this._listeners.concat();
var listenerCount = listeners.length;
for (var i = 0; i < listenerCount; ++i) {
var listener = listeners[i];
if (listener instanceof Ci.nsIProgressEventSink)
listener.onProgress(request, context, progress, maxProgress);
}
this.updateService._consecutiveSocketErrors = 0;
},
/**
* 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 Downloader_onStatus(request, context, status, statusText) {
LOG("Downloader:onStatus - status: " + status + ", statusText: " +
statusText);
var listeners = this._listeners.concat();
var listenerCount = listeners.length;
for (var i = 0; i < listenerCount; ++i) {
var listener = listeners[i];
if (listener instanceof Ci.nsIProgressEventSink)
listener.onStatus(request, context, status, 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 Downloader_onStopRequest(request, context, status) {
if (request instanceof Ci.nsIIncrementalDownload)
LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
", final URI spec: " + request.finalURI.spec + ", status: " + status);
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
// XXX ehsan shouldShowPrompt should always be false here.
// But what happens when there is already a UI showing?
var state = this._patch.state;
var shouldShowPrompt = false;
var shouldRegisterOnlineObserver = false;
var shouldRetrySoon = false;
var deleteActiveUpdate = false;
var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_RETRY_TIMEOUT,
DEFAULT_UPDATE_RETRY_TIMEOUT);
var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_ERRORS,
DEFAULT_SOCKET_MAX_ERRORS);
LOG("Downloader:onStopRequest - status: " + status + ", " +
"current fail: " + this.updateService._consecutiveSocketErrors + ", " +
"max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
if (Components.isSuccessCode(status)) {
if (this._verifyDownload()) {
state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING;
if (this.background) {
shouldShowPrompt = !getCanStageUpdates();
}
// Tell the updater.exe we're ready to apply.
writeStatusFile(getUpdatesDir(), state);
writeVersionFile(getUpdatesDir(), this._update.appVersion);
this._update.installDate = (new Date()).getTime();
this._update.statusText = gUpdateBundle.GetStringFromName("installPending");
}
else {
LOG("Downloader:onStopRequest - download verification failed");
state = STATE_DOWNLOAD_FAILED;
status = Cr.NS_ERROR_CORRUPTED_CONTENT;
// Yes, this code is a string.
const vfCode = "verification_failed";
var message = getStatusTextFromCode(vfCode, vfCode);
this._update.statusText = message;
if (this._update.isCompleteUpdate || this._update.patchCount != 2)
deleteActiveUpdate = true;
// Destroy the updates directory, since we're done with it.
cleanUpUpdatesDir();
}
} else if (status == Cr.NS_ERROR_OFFLINE) {
// Register an online observer to try again.
// The online observer will continue the incremental download by
// calling downloadUpdate on the active update which continues
// downloading the file from where it was.
LOG("Downloader:onStopRequest - offline, register online observer: true");
shouldRegisterOnlineObserver = true;
deleteActiveUpdate = false;
// Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED, and
// NS_ERROR_NET_RESET can be returned when disconnecting the internet while
// a download of a MAR is in progress. There may be others but I have not
// encountered them during testing.
} else if ((status == Cr.NS_ERROR_NET_TIMEOUT ||
status == Cr.NS_ERROR_CONNECTION_REFUSED ||
status == Cr.NS_ERROR_NET_RESET) &&
this.updateService._consecutiveSocketErrors < maxFail) {
LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true");
shouldRetrySoon = true;
deleteActiveUpdate = false;
} else if (status != Cr.NS_BINDING_ABORTED &&
status != Cr.NS_ERROR_ABORT &&
status != Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
LOG("Downloader:onStopRequest - non-verification failure");
// Some sort of other failure, log this in the |statusText| property
state = STATE_DOWNLOAD_FAILED;
// XXXben - if |request| (The Incremental Download) provided a means
// for accessing the http channel we could do more here.
this._update.statusText = getStatusTextFromCode(status,
Cr.NS_BINDING_FAILED);
#ifdef MOZ_WIDGET_GONK
// bug891009: On FirefoxOS, manaully retry OTA download will reuse
// the Update object. We need to remove selected patch so that download
// can be triggered again successfully.
this._update.selectedPatch.selected = false;
#endif
// Destroy the updates directory, since we're done with it.
cleanUpUpdatesDir();
deleteActiveUpdate = true;
}
LOG("Downloader:onStopRequest - setting state to: " + state);
this._patch.state = state;
var um = Cc["@mozilla.org/updates/update-manager;1"].
getService(Ci.nsIUpdateManager);
if (deleteActiveUpdate) {
this._update.installDate = (new Date()).getTime();
um.activeUpdate = null;
}
else {
if (um.activeUpdate)
um.activeUpdate.state = state;
}
um.saveUpdates();
// Only notify listeners about the stopped state if we
// aren't handling an internal retry.
if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
var listeners = this._listeners.concat();
var listenerCount = listeners.length;
for (var i = 0; i < listenerCount; ++i) {
listeners[i].onStopRequest(request, context, status);
}
}
this._request = null;
if (state == STATE_DOWNLOAD_FAILED) {
var allFailed = true;
// Check if there is a complete update patch that can be downloaded.
if (!this._update.isCompleteUpdate && this._update.patchCount == 2) {
LOG("Downloader:onStopRequest - verification of patch failed, " +
"downloading complete update patch");
this._update.isCompleteUpdate = true;
let updateStatus = this.downloadUpdate(this._update);
if (updateStatus == STATE_NONE) {
cleanupActiveUpdate();
} else {
allFailed = false;
}
}
if (allFailed) {
LOG("Downloader:onStopRequest - all update patch downloads failed");
// If the update UI is not open (e.g. the user closed the window while
// downloading) and if at any point this was a foreground download
// notify the user about the error. If the update was a background
// update there is no notification since the user won't be expecting it.
if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME)) {
try {
this._update.QueryInterface(Ci.nsIWritablePropertyBag);
var fgdl = this._update.getProperty("foregroundDownload");
}
catch (e) {
}
if (fgdl == "true") {
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateError(this._update);
}
}
#ifdef MOZ_WIDGET_GONK
// We always forward errors in B2G, since Gaia controls the update UI
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateError(this._update);
#endif
// Prevent leaking the update object (bug 454964).
this._update = null;
}
// A complete download has been initiated or the failure was handled.
return;
}
if (state == STATE_PENDING || state == STATE_PENDING_SVC) {
if (getCanStageUpdates()) {
LOG("Downloader:onStopRequest - attempting to stage update: " +
this._update.name);
// Initiate the update in the background
try {
Cc["@mozilla.org/updates/update-processor;1"].
createInstance(Ci.nsIUpdateProcessor).
processUpdate(this._update);
} catch (e) {
// Fail gracefully in case the application does not support the update
// processor service.
LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
e);
if (this.background) {
shouldShowPrompt = true;
}
}
}
}
// Do this after *everything* else, since it will likely cause the app
// to shut down.
if (shouldShowPrompt) {
// Notify the user that an update has been downloaded and is ready for
// installation (i.e. that they should restart the application). We do
// not notify on failed update attempts.
var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
createInstance(Ci.nsIUpdatePrompt);
prompter.showUpdateDownloaded(this._update, true);
}
Bug 307181 - Stage Firefox updates in the background after they're downloaded, and replace the application directory on restart; r=rstrong,bbondy When Firefox downloads an update, it previously kept the update around to apply it on the next restart. This patch changes this so that the updater program is launched in the background as soon as the update has finished downloading in order to stage the updated version of the application by copying the existing installation directory to a temporary location and applying the update on top of it, and replace the existing installation directory with the staged directory on the next restart. Because the replacing step is typically very fast, this patch eliminates the wait for the update to be applied on restart, making it unnecessary to show a progress dialog when restarting. --HG-- rename : toolkit/mozapps/update/test/chrome/test_0092_finishedBackground.xul => toolkit/mozapps/update/test/chrome/test_0093_stagedBackground.xul rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test/unit/test_0113_general.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test/unit/test_0114_general.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test/unit/test_0115_general.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test/unit/test_0172_fileLocked_xp_win_complete.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test/unit/test_0173_fileLocked_xp_win_partial.js rename : toolkit/mozapps/update/test/unit/test_0110_general.js => toolkit/mozapps/update/test_svc/unit/test_0113_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0111_general.js => toolkit/mozapps/update/test_svc/unit/test_0114_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0112_general.js => toolkit/mozapps/update/test_svc/unit/test_0115_general_svc.js rename : toolkit/mozapps/update/test/unit/test_0170_fileLocked_xp_win_complete.js => toolkit/mozapps/update/test_svc/unit/test_0172_fileLocked_xp_win_complete_svc.js rename : toolkit/mozapps/update/test/unit/test_0171_fileLocked_xp_win_partial.js => toolkit/mozapps/update/test_svc/unit/test_0173_fileLocked_xp_win_partial_svc.js
2012-05-22 07:50:04 -07:00
if (shouldRegisterOnlineObserver) {
LOG("Downloader:onStopRequest - Registering online observer");
this.updateService._registerOnlineObserver();
} else if (shouldRetrySoon) {
LOG("Downloader:onStopRequest - Retrying soon");
this.updateService._consecutiveSocketErrors++;
if (this.updateService._retryTimer) {
this.updateService._retryTimer.cancel();
}
this.updateService._retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.updateService._retryTimer.initWithCallback(function() {
this._attemptResume();
}.bind(this.updateService), retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
} else {
// Prevent leaking the update object (bug 454964)
this._update = null;
}
},
/**
* See nsIInterfaceRequestor.idl
*/
getInterface: function Downloader_getInterface(iid) {
// The network request may require proxy authentication, so provide the
// default nsIAuthPrompt if requested.
if (iid.equals(Ci.nsIAuthPrompt)) {
var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"].
createInstance();
return prompt.QueryInterface(iid);
}
throw Cr.NS_NOINTERFACE;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
Ci.nsIProgressEventSink,
Ci.nsIInterfaceRequestor])
};
/**
* UpdatePrompt
* An object which can prompt the user with information about updates, request
* action, etc. Embedding clients can override this component with one that
* invokes a native front end.
* @constructor
*/
function UpdatePrompt() {
}
UpdatePrompt.prototype = {
/**
* See nsIUpdateService.idl
*/
checkForUpdates: function UP_checkForUpdates() {
if (this._getAltUpdateWindow())
return;
this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
null, null);
},
/**
* See nsIUpdateService.idl
*/
showUpdateAvailable: function UP_showUpdateAvailable(update) {
if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
this._getUpdateWindow() || this._getAltUpdateWindow())
return;
var stringsPrefix = "updateAvailable_" + update.type + ".";
var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
[update.name], 1);
var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
var imageUrl = "";
this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
UPDATE_WINDOW_NAME, "updatesavailable", update,
title, text, imageUrl);
},
/**
* See nsIUpdateService.idl
*/
showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) {
if (this._getAltUpdateWindow())
return;
if (background) {
if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false))
return;
var stringsPrefix = "updateDownloaded_" + update.type + ".";
var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
[update.name], 1);
var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
var imageUrl = "";
this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
UPDATE_WINDOW_NAME, "finishedBackground", update,
title, text, imageUrl);
} else {
this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
UPDATE_WINDOW_NAME, "finishedBackground", update);
}
},
/**
* See nsIUpdateService.idl
*/
showUpdateInstalled: function UP_showUpdateInstalled() {
if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
!getPref("getBoolPref", PREF_APP_UPDATE_SHOW_INSTALLED_UI, false) ||
this._getUpdateWindow())
return;
var page = "installed";
var win = this._getUpdateWindow();
if (win) {
if (page && "setCurrentPage" in win)
win.setCurrentPage(page);
win.focus();
}
else {
var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
var arg = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
arg.data = page;
Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, null, openFeatures, arg);
}
},
/**
* See nsIUpdateService.idl
*/
showUpdateError: function UP_showUpdateError(update) {
if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
this._getAltUpdateWindow())
return;
// In some cases, we want to just show a simple alert dialog:
if (update.state == STATE_FAILED &&
(update.errorCode == WRITE_ERROR ||
update.errorCode == WRITE_ERROR_ACCESS_DENIED ||
update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED ||
update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID ||
update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID ||
update.errorCode == WRITE_ERROR_CALLBACK_APP ||
update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR ||
update.errorCode == FOTA_GENERAL_ERROR ||
update.errorCode == FOTA_FILE_OPERATION_ERROR ||
update.errorCode == FOTA_RECOVERY_ERROR ||
update.errorCode == FOTA_UNKNOWN_ERROR)) {
var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");
var text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg",
[Services.appinfo.name,
Services.appinfo.name], 2);
Services.ww.getNewPrompter(null).alert(title, text);
return;
}
if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE ||
update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
this._showUIWhenIdle(null, URI_UPDATE_PROMPT_DIALOG, null,
UPDATE_WINDOW_NAME, null, update);
return;
}
this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
"errors", update);
},
/**
* See nsIUpdateService.idl
*/
showUpdateHistory: function UP_showUpdateHistory(parent) {
this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes",
"Update:History", null, null);
},
/**
* Returns the update window if present.
*/
_getUpdateWindow: function UP__getUpdateWindow() {
return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME);
},
/**
* Returns an alternative update window if present. When a window with this
* windowtype is open the application update service won't open the normal
* application update user interface window.
*/
_getAltUpdateWindow: function UP__getAltUpdateWindow() {
let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
if (!windowType)
return null;
return Services.wm.getMostRecentWindow(windowType);
},
/**
* Initiate a less obtrusive UI, starting with a non-modal notification alert
* @param parent
* A parent window, can be null
* @param uri
* The URI string of the dialog to show
* @param name
* The Window Name of the dialog to show, in case it is already open
* and can merely be focused
* @param page
* The page of the wizard to be displayed, if one is already open.
* @param update
* An update to pass to the UI in the window arguments.
* Can be null
* @param title
* The title for the notification alert.
* @param text
* The contents of the notification alert.
* @param imageUrl
* A URL identifying the image to put in the notification alert.
*/
_showUnobtrusiveUI: function UP__showUnobUI(parent, uri, features, name, page,
update, title, text, imageUrl) {
var observer = {
updatePrompt: this,
service: null,
timer: null,
notify: function () {
// the user hasn't restarted yet => prompt when idle
this.service.removeObserver(this, "quit-application");
// If the update window is already open skip showing the UI
if (this.updatePrompt._getUpdateWindow())
return;
this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
},
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "alertclickcallback":
this.updatePrompt._showUI(parent, uri, features, name, page, update);
// fall thru
case "quit-application":
if (this.timer)
this.timer.cancel();
this.service.removeObserver(this, "quit-application");
break;
}
}
};
// bug 534090 - show the UI for update available notifications when the
// the system has been idle for at least IDLE_TIME without displaying an
// alert notification.
if (page == "updatesavailable") {
var idleService = Cc["@mozilla.org/widget/idleservice;1"].
getService(Ci.nsIIdleService);
const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
if (idleService.idleTime / 1000 >= IDLE_TIME) {
this._showUI(parent, uri, features, name, page, update);
return;
}
}
try {
var notifier = Cc["@mozilla.org/alerts-service;1"].
getService(Ci.nsIAlertsService);
notifier.showAlertNotification(imageUrl, title, text, true, "", observer);
}
catch (e) {
// Failed to retrieve alerts service, platform unsupported
this._showUIWhenIdle(parent, uri, features, name, page, update);
return;
}
observer.service = Services.obs;
observer.service.addObserver(observer, "quit-application", false);
// bug 534090 - show the UI when idle for update available notifications.
if (page == "updatesavailable") {
this._showUIWhenIdle(parent, uri, features, name, page, update);
return;
}
// Give the user x seconds to react before prompting as defined by
// promptWaitTime
observer.timer = Cc["@mozilla.org/timer;1"].
createInstance(Ci.nsITimer);
observer.timer.initWithCallback(observer, update.promptWaitTime * 1000,
observer.timer.TYPE_ONE_SHOT);
},
/**
* Show the UI when the user was idle
* @param parent
* A parent window, can be null
* @param uri
* The URI string of the dialog to show
* @param name
* The Window Name of the dialog to show, in case it is already open
* and can merely be focused
* @param page
* The page of the wizard to be displayed, if one is already open.
* @param update
* An update to pass to the UI in the window arguments.
* Can be null
*/
_showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name,
page, update) {
var idleService = Cc["@mozilla.org/widget/idleservice;1"].
getService(Ci.nsIIdleService);
const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
if (idleService.idleTime / 1000 >= IDLE_TIME) {
this._showUI(parent, uri, features, name, page, update);
} else {
var observer = {
updatePrompt: this,
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {
case "idle":
// If the update window is already open skip showing the UI
if (!this.updatePrompt._getUpdateWindow())
this.updatePrompt._showUI(parent, uri, features, name, page, update);
// fall thru
case "quit-application":
idleService.removeIdleObserver(this, IDLE_TIME);
Services.obs.removeObserver(this, "quit-application");
break;
}
}
};
idleService.addIdleObserver(observer, IDLE_TIME);
Services.obs.addObserver(observer, "quit-application", false);
}
},
/**
* Show the Update Checking UI
* @param parent
* A parent window, can be null
* @param uri
* The URI string of the dialog to show
* @param name
* The Window Name of the dialog to show, in case it is already open
* and can merely be focused
* @param page
* The page of the wizard to be displayed, if one is already open.
* @param update
* An update to pass to the UI in the window arguments.
* Can be null
*/
_showUI: function UP__showUI(parent, uri, features, name, page, update) {
var ary = null;
if (update) {
ary = Cc["@mozilla.org/supports-array;1"].
createInstance(Ci.nsISupportsArray);
ary.AppendElement(update);
}
var win = this._getUpdateWindow();
if (win) {
if (page && "setCurrentPage" in win)
win.setCurrentPage(page);
win.focus();
}
else {
var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
if (features)
openFeatures += "," + features;
Services.ww.openWindow(parent, uri, "", openFeatures, ary);
}
},
classDescription: "Update Prompt",
contractID: "@mozilla.org/updates/update-prompt;1",
classID: Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt])
};
var components = [UpdateService, Checker, UpdatePrompt, UpdateManager];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
#if 0
/**
* Logs a message and stack trace to the console.
* @param string
* The string to write to the console.
*/
function STACK(string) {
dump("*** " + string + "\n");
stackTrace(arguments.callee.caller.arguments, -1);
}
function stackTraceFunctionFormat(aFunctionName) {
var classDelimiter = aFunctionName.indexOf("_");
var className = aFunctionName.substr(0, classDelimiter);
if (!className)
className = "<global>";
var functionName = aFunctionName.substr(classDelimiter + 1, aFunctionName.length);
if (!functionName)
functionName = "<anonymous>";
return className + "::" + functionName;
}
function stackTraceArgumentsFormat(aArguments) {
arglist = "";
for (var i = 0; i < aArguments.length; i++) {
arglist += aArguments[i];
if (i < aArguments.length - 1)
arglist += ", ";
}
return arglist;
}
function stackTrace(aArguments, aMaxCount) {
dump("=[STACKTRACE]=====================================================\n");
dump("*** at: " + stackTraceFunctionFormat(aArguments.callee.name) + "(" +
stackTraceArgumentsFormat(aArguments) + ")\n");
var temp = aArguments.callee.caller;
var count = 0;
while (temp) {
dump("*** " + stackTraceFunctionFormat(temp.name) + "(" +
stackTraceArgumentsFormat(temp.arguments) + ")\n");
temp = temp.arguments.callee.caller;
if (aMaxCount > 0 && ++count == aMaxCount)
break;
}
dump("==================================================================\n");
}
function dumpFile(file) {
dump("*** file = " + file.path + ", exists = " + file.exists() + "\n");
}
#endif