toolkit/mozapps/update/nsUpdateService.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 #filter substitution
michael@0 2
michael@0 3 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 4 /*
michael@0 5 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 6 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 7 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 8 */
michael@0 9
michael@0 10 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 11 Components.utils.import("resource://gre/modules/FileUtils.jsm");
michael@0 12 Components.utils.import("resource://gre/modules/AddonManager.jsm");
michael@0 13 Components.utils.import("resource://gre/modules/Services.jsm");
michael@0 14 #ifdef BUILD_CTYPES
michael@0 15 #ifdef XP_WIN
michael@0 16 Components.utils.import("resource://gre/modules/ctypes.jsm");
michael@0 17 #endif
michael@0 18 #endif
michael@0 19
michael@0 20 const Cc = Components.classes;
michael@0 21 const Ci = Components.interfaces;
michael@0 22 const Cr = Components.results;
michael@0 23 const Cu = Components.utils;
michael@0 24
michael@0 25 const UPDATESERVICE_CID = Components.ID("{B3C290A6-3943-4B89-8BBE-C01EB7B3B311}");
michael@0 26 const UPDATESERVICE_CONTRACTID = "@mozilla.org/updates/update-service;1";
michael@0 27
michael@0 28 const PREF_APP_UPDATE_ALTWINDOWTYPE = "app.update.altwindowtype";
michael@0 29 const PREF_APP_UPDATE_AUTO = "app.update.auto";
michael@0 30 const PREF_APP_UPDATE_BACKGROUND_INTERVAL = "app.update.download.backgroundInterval";
michael@0 31 const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
michael@0 32 const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
michael@0 33 const PREF_APP_UPDATE_CERTS_BRANCH = "app.update.certs.";
michael@0 34 const PREF_APP_UPDATE_CERT_CHECKATTRS = "app.update.cert.checkAttributes";
michael@0 35 const PREF_APP_UPDATE_CERT_ERRORS = "app.update.cert.errors";
michael@0 36 const PREF_APP_UPDATE_CERT_MAXERRORS = "app.update.cert.maxErrors";
michael@0 37 const PREF_APP_UPDATE_CERT_REQUIREBUILTIN = "app.update.cert.requireBuiltIn";
michael@0 38 const PREF_APP_UPDATE_CUSTOM = "app.update.custom";
michael@0 39 const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
michael@0 40 const PREF_APP_UPDATE_METRO_ENABLED = "app.update.metro.enabled";
michael@0 41 const PREF_APP_UPDATE_IDLETIME = "app.update.idletime";
michael@0 42 const PREF_APP_UPDATE_INCOMPATIBLE_MODE = "app.update.incompatible.mode";
michael@0 43 const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
michael@0 44 const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer";
michael@0 45 const PREF_APP_UPDATE_LOG = "app.update.log";
michael@0 46 const PREF_APP_UPDATE_MODE = "app.update.mode";
michael@0 47 const PREF_APP_UPDATE_NEVER_BRANCH = "app.update.never.";
michael@0 48 const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED = "app.update.notifiedUnsupported";
michael@0 49 const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate";
michael@0 50 const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
michael@0 51 const PREF_APP_UPDATE_SHOW_INSTALLED_UI = "app.update.showInstalledUI";
michael@0 52 const PREF_APP_UPDATE_SILENT = "app.update.silent";
michael@0 53 const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
michael@0 54 const PREF_APP_UPDATE_URL = "app.update.url";
michael@0 55 const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
michael@0 56 const PREF_APP_UPDATE_URL_OVERRIDE = "app.update.url.override";
michael@0 57 const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
michael@0 58 const PREF_APP_UPDATE_SERVICE_ERRORS = "app.update.service.errors";
michael@0 59 const PREF_APP_UPDATE_SERVICE_MAX_ERRORS = "app.update.service.maxErrors";
michael@0 60 const PREF_APP_UPDATE_SOCKET_ERRORS = "app.update.socket.maxErrors";
michael@0 61 const PREF_APP_UPDATE_RETRY_TIMEOUT = "app.update.socket.retryTimeout";
michael@0 62
michael@0 63 const PREF_APP_DISTRIBUTION = "distribution.id";
michael@0 64 const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
michael@0 65
michael@0 66 const PREF_APP_B2G_VERSION = "b2g.version";
michael@0 67
michael@0 68 const PREF_EM_HOTFIX_ID = "extensions.hotfix.id";
michael@0 69
michael@0 70 const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
michael@0 71 const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
michael@0 72 const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
michael@0 73 const URI_UPDATES_PROPERTIES = "chrome://mozapps/locale/update/updates.properties";
michael@0 74 const URI_UPDATE_NS = "http://www.mozilla.org/2005/app-update";
michael@0 75
michael@0 76 const CATEGORY_UPDATE_TIMER = "update-timer";
michael@0 77
michael@0 78 const KEY_GRED = "GreD";
michael@0 79 const KEY_UPDROOT = "UpdRootD";
michael@0 80 const KEY_EXECUTABLE = "XREExeF";
michael@0 81
michael@0 82 #ifdef TOR_BROWSER_VERSION
michael@0 83 #expand const TOR_BROWSER_VERSION = __TOR_BROWSER_VERSION__;
michael@0 84 #endif
michael@0 85
michael@0 86 #ifdef MOZ_WIDGET_GONK
michael@0 87 #define USE_UPDATE_ARCHIVE_DIR
michael@0 88 #endif
michael@0 89
michael@0 90 #ifdef USE_UPDATE_ARCHIVE_DIR
michael@0 91 const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD"
michael@0 92 #endif
michael@0 93
michael@0 94 #ifdef XP_WIN
michael@0 95 #define CHECK_CAN_USE_SERVICE
michael@0 96 #elifdef MOZ_WIDGET_GONK
michael@0 97 // In Gonk, the updater will remount the /system partition to move staged files
michael@0 98 // into place, so we skip the test here to keep things isolated.
michael@0 99 #define CHECK_CAN_USE_SERVICE
michael@0 100 #endif
michael@0 101
michael@0 102 const DIR_UPDATES = "updates";
michael@0 103 #ifdef XP_MACOSX
michael@0 104 const UPDATED_DIR = "Updated.app";
michael@0 105 #else
michael@0 106 const UPDATED_DIR = "updated";
michael@0 107 #endif
michael@0 108 const FILE_UPDATE_STATUS = "update.status";
michael@0 109 const FILE_UPDATE_VERSION = "update.version";
michael@0 110 #ifdef MOZ_WIDGET_ANDROID
michael@0 111 const FILE_UPDATE_ARCHIVE = "update.apk";
michael@0 112 #else
michael@0 113 const FILE_UPDATE_ARCHIVE = "update.mar";
michael@0 114 #endif
michael@0 115 const FILE_UPDATE_LINK = "update.link";
michael@0 116 const FILE_UPDATE_LOG = "update.log";
michael@0 117 const FILE_UPDATES_DB = "updates.xml";
michael@0 118 const FILE_UPDATE_ACTIVE = "active-update.xml";
michael@0 119 const FILE_PERMS_TEST = "update.test";
michael@0 120 const FILE_LAST_LOG = "last-update.log";
michael@0 121 const FILE_BACKUP_LOG = "backup-update.log";
michael@0 122 const FILE_UPDATE_LOCALE = "update.locale";
michael@0 123
michael@0 124 const STATE_NONE = "null";
michael@0 125 const STATE_DOWNLOADING = "downloading";
michael@0 126 const STATE_PENDING = "pending";
michael@0 127 const STATE_PENDING_SVC = "pending-service";
michael@0 128 const STATE_APPLYING = "applying";
michael@0 129 const STATE_APPLIED = "applied";
michael@0 130 const STATE_APPLIED_OS = "applied-os";
michael@0 131 const STATE_APPLIED_SVC = "applied-service";
michael@0 132 const STATE_SUCCEEDED = "succeeded";
michael@0 133 const STATE_DOWNLOAD_FAILED = "download-failed";
michael@0 134 const STATE_FAILED = "failed";
michael@0 135
michael@0 136 // From updater/errors.h:
michael@0 137 const WRITE_ERROR = 7;
michael@0 138 // const UNEXPECTED_ERROR = 8; // Replaced with errors 38-42
michael@0 139 const ELEVATION_CANCELED = 9;
michael@0 140
michael@0 141 // Windows service specific errors
michael@0 142 const SERVICE_UPDATER_COULD_NOT_BE_STARTED = 24;
michael@0 143 const SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS = 25;
michael@0 144 const SERVICE_UPDATER_SIGN_ERROR = 26;
michael@0 145 const SERVICE_UPDATER_COMPARE_ERROR = 27;
michael@0 146 const SERVICE_UPDATER_IDENTITY_ERROR = 28;
michael@0 147 const SERVICE_STILL_APPLYING_ON_SUCCESS = 29;
michael@0 148 const SERVICE_STILL_APPLYING_ON_FAILURE = 30;
michael@0 149 const SERVICE_UPDATER_NOT_FIXED_DRIVE = 31;
michael@0 150 const SERVICE_COULD_NOT_LOCK_UPDATER = 32;
michael@0 151 const SERVICE_INSTALLDIR_ERROR = 33;
michael@0 152 const SERVICE_COULD_NOT_COPY_UPDATER = 49;
michael@0 153
michael@0 154 const WRITE_ERROR_ACCESS_DENIED = 35;
michael@0 155 // const WRITE_ERROR_SHARING_VIOLATION = 36; // Replaced with errors 46-48
michael@0 156 const WRITE_ERROR_CALLBACK_APP = 37;
michael@0 157 const INVALID_UPDATER_STATUS_CODE = 38;
michael@0 158 const UNEXPECTED_BZIP_ERROR = 39;
michael@0 159 const UNEXPECTED_MAR_ERROR = 40;
michael@0 160 const UNEXPECTED_BSPATCH_ERROR = 41;
michael@0 161 const UNEXPECTED_FILE_OPERATION_ERROR = 42;
michael@0 162 const FILESYSTEM_MOUNT_READWRITE_ERROR = 43;
michael@0 163 const FOTA_GENERAL_ERROR = 44;
michael@0 164 const FOTA_UNKNOWN_ERROR = 45;
michael@0 165 const WRITE_ERROR_SHARING_VIOLATION_SIGNALED = 46;
michael@0 166 const WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID = 47;
michael@0 167 const WRITE_ERROR_SHARING_VIOLATION_NOPID = 48;
michael@0 168 const FOTA_FILE_OPERATION_ERROR = 49;
michael@0 169 const FOTA_RECOVERY_ERROR = 50;
michael@0 170
michael@0 171 const CERT_ATTR_CHECK_FAILED_NO_UPDATE = 100;
michael@0 172 const CERT_ATTR_CHECK_FAILED_HAS_UPDATE = 101;
michael@0 173 const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
michael@0 174 const NETWORK_ERROR_OFFLINE = 111;
michael@0 175 const FILE_ERROR_TOO_BIG = 112;
michael@0 176
michael@0 177 // Error codes should be < 1000. Errors above 1000 represent http status codes
michael@0 178 const HTTP_ERROR_OFFSET = 1000;
michael@0 179
michael@0 180 const DOWNLOAD_CHUNK_SIZE = 300000; // bytes
michael@0 181 const DOWNLOAD_BACKGROUND_INTERVAL = 600; // seconds
michael@0 182 const DOWNLOAD_FOREGROUND_INTERVAL = 0;
michael@0 183
michael@0 184 const UPDATE_WINDOW_NAME = "Update:Wizard";
michael@0 185
michael@0 186 // The number of consecutive failures when updating using the service before
michael@0 187 // setting the app.update.service.enabled preference to false.
michael@0 188 const DEFAULT_SERVICE_MAX_ERRORS = 10;
michael@0 189
michael@0 190 // The number of consecutive socket errors to allow before falling back to
michael@0 191 // downloading a different MAR file or failing if already downloading the full.
michael@0 192 const DEFAULT_SOCKET_MAX_ERRORS = 10;
michael@0 193
michael@0 194 // The number of milliseconds to wait before retrying a connection error.
michael@0 195 const DEFAULT_UPDATE_RETRY_TIMEOUT = 2000;
michael@0 196
michael@0 197 // A background download is in progress (no notification)
michael@0 198 const PING_BGUC_IS_DOWNLOADING = 0;
michael@0 199 // An update is staged (no notification)
michael@0 200 const PING_BGUC_IS_STAGED = 1;
michael@0 201 // Invalid url for app.update.url default preference (no notification)
michael@0 202 const PING_BGUC_INVALID_DEFAULT_URL = 2;
michael@0 203 // Invalid url for app.update.url user preference (no notification)
michael@0 204 const PING_BGUC_INVALID_CUSTOM_URL = 3;
michael@0 205 // Invalid url for app.update.url.override user preference (no notification)
michael@0 206 const PING_BGUC_INVALID_OVERRIDE_URL = 4;
michael@0 207 // Unable to check for updates per gCanCheckForUpdates and hasUpdateMutex()
michael@0 208 // (no notification)
michael@0 209 const PING_BGUC_UNABLE_TO_CHECK = 5;
michael@0 210 // Already has an active update in progress (no notification)
michael@0 211 const PING_BGUC_HAS_ACTIVEUPDATE = 6;
michael@0 212 // Background checks disabled by preference (no notification)
michael@0 213 const PING_BGUC_PREF_DISABLED = 7;
michael@0 214 // Background checks disabled for the current session (no notification)
michael@0 215 const PING_BGUC_DISABLED_FOR_SESSION = 8;
michael@0 216 // Background checks disabled in Metro (no notification)
michael@0 217 const PING_BGUC_METRO_DISABLED = 9;
michael@0 218 // Unable to perform a background check while offline (no notification)
michael@0 219 const PING_BGUC_OFFLINE = 10;
michael@0 220 // No update found certificate check failed and threshold reached
michael@0 221 // (possible mitm attack notification)
michael@0 222 const PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY = 11;
michael@0 223 // No update found certificate check failed and threshold not reached
michael@0 224 // (no notification)
michael@0 225 const PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT = 12;
michael@0 226 // Update found certificate check failed and threshold reached
michael@0 227 // (possible mitm attack notification)
michael@0 228 const PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY = 13;
michael@0 229 // Update found certificate check failed and threshold not reached
michael@0 230 // (no notification)
michael@0 231 const PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT = 14;
michael@0 232 // General update check failure and threshold reached
michael@0 233 // (check failure notification)
michael@0 234 const PING_BGUC_GENERAL_ERROR_NOTIFY = 15;
michael@0 235 // General update check failure and threshold not reached
michael@0 236 // (no notification)
michael@0 237 const PING_BGUC_GENERAL_ERROR_SILENT = 16;
michael@0 238 // No update found (no notification)
michael@0 239 const PING_BGUC_NO_UPDATE_FOUND = 17;
michael@0 240 // No compatible update found though there were updates (no notification)
michael@0 241 const PING_BGUC_NO_COMPAT_UPDATE_FOUND = 18;
michael@0 242 // Update found for a previous version (no notification)
michael@0 243 const PING_BGUC_UPDATE_PREVIOUS_VERSION = 19;
michael@0 244 // Update found for a version with the never preference set (no notification)
michael@0 245 const PING_BGUC_UPDATE_NEVER_PREF = 20;
michael@0 246 // Update found without a type attribute (no notification)
michael@0 247 const PING_BGUC_UPDATE_INVALID_TYPE = 21;
michael@0 248 // The system is no longer supported (system unsupported notification)
michael@0 249 const PING_BGUC_UNSUPPORTED = 22;
michael@0 250 // Unable to apply updates (manual install to update notification)
michael@0 251 const PING_BGUC_UNABLE_TO_APPLY = 23;
michael@0 252 // Showing prompt due to the update.xml specifying showPrompt
michael@0 253 // (update notification)
michael@0 254 const PING_BGUC_SHOWPROMPT_SNIPPET = 24;
michael@0 255 // Showing prompt due to preference (update notification)
michael@0 256 const PING_BGUC_SHOWPROMPT_PREF = 25;
michael@0 257 // Incompatible add-on check disabled by preference (background download)
michael@0 258 const PING_BGUC_ADDON_PREF_DISABLED = 26;
michael@0 259 // Incompatible add-on not checked not performed due to same update version and
michael@0 260 // app version (background download)
michael@0 261 const PING_BGUC_ADDON_SAME_APP_VER = 27;
michael@0 262 // No incompatible add-ons found during incompatible check (background download)
michael@0 263 const PING_BGUC_CHECK_NO_INCOMPAT = 28;
michael@0 264 // Incompatible add-ons found and all of them have updates (background download)
michael@0 265 const PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT = 29;
michael@0 266 // Incompatible add-ons found (update notification)
michael@0 267 const PING_BGUC_ADDON_HAVE_INCOMPAT = 30;
michael@0 268
michael@0 269 var gLocale = null;
michael@0 270 var gUpdateMutexHandle = null;
michael@0 271
michael@0 272 #ifdef MOZ_WIDGET_GONK
michael@0 273 var gSDCardMountLock = null;
michael@0 274
michael@0 275 XPCOMUtils.defineLazyGetter(this, "gExtStorage", function aus_gExtStorage() {
michael@0 276 return Services.env.get("EXTERNAL_STORAGE");
michael@0 277 });
michael@0 278 #endif
michael@0 279
michael@0 280 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
michael@0 281 "resource://gre/modules/UpdateChannel.jsm");
michael@0 282
michael@0 283 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
michael@0 284 return getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
michael@0 285 });
michael@0 286
michael@0 287 XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() {
michael@0 288 return Services.strings.createBundle(URI_UPDATES_PROPERTIES);
michael@0 289 });
michael@0 290
michael@0 291 // shared code for suppressing bad cert dialogs
michael@0 292 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function aus_gCertUtils() {
michael@0 293 let temp = { };
michael@0 294 Cu.import("resource://gre/modules/CertUtils.jsm", temp);
michael@0 295 return temp;
michael@0 296 });
michael@0 297
michael@0 298 XPCOMUtils.defineLazyGetter(this, "gABI", function aus_gABI() {
michael@0 299 let abi = null;
michael@0 300 try {
michael@0 301 abi = Services.appinfo.XPCOMABI;
michael@0 302 }
michael@0 303 catch (e) {
michael@0 304 LOG("gABI - XPCOM ABI unknown: updates are not possible.");
michael@0 305 }
michael@0 306 #ifdef XP_MACOSX
michael@0 307 // Mac universal build should report a different ABI than either macppc
michael@0 308 // or mactel.
michael@0 309 let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
michael@0 310 getService(Ci.nsIMacUtils);
michael@0 311
michael@0 312 if (macutils.isUniversalBinary)
michael@0 313 abi += "-u-" + macutils.architecturesInBinary;
michael@0 314 #ifdef MOZ_SHARK
michael@0 315 // Disambiguate optimised and shark nightlies
michael@0 316 abi += "-shark"
michael@0 317 #endif
michael@0 318 #endif
michael@0 319 return abi;
michael@0 320 });
michael@0 321
michael@0 322 #ifdef MOZ_WIDGET_GONK
michael@0 323 XPCOMUtils.defineLazyGetter(this, "gProductModel", function aus_gProductModel() {
michael@0 324 Cu.import("resource://gre/modules/systemlibs.js");
michael@0 325 return libcutils.property_get("ro.product.model");
michael@0 326 });
michael@0 327 #endif
michael@0 328
michael@0 329 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
michael@0 330 let osVersion;
michael@0 331 let sysInfo = Cc["@mozilla.org/system-info;1"].
michael@0 332 getService(Ci.nsIPropertyBag2);
michael@0 333 try {
michael@0 334 osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
michael@0 335 }
michael@0 336 catch (e) {
michael@0 337 LOG("gOSVersion - OS Version unknown: updates are not possible.");
michael@0 338 }
michael@0 339
michael@0 340 if (osVersion) {
michael@0 341 #ifdef BUILD_CTYPES
michael@0 342 #ifdef XP_WIN
michael@0 343 const BYTE = ctypes.uint8_t;
michael@0 344 const WORD = ctypes.uint16_t;
michael@0 345 const DWORD = ctypes.uint32_t;
michael@0 346 const WCHAR = ctypes.jschar;
michael@0 347 const BOOL = ctypes.int;
michael@0 348
michael@0 349 // This structure is described at:
michael@0 350 // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
michael@0 351 const SZCSDVERSIONLENGTH = 128;
michael@0 352 const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
michael@0 353 [
michael@0 354 {dwOSVersionInfoSize: DWORD},
michael@0 355 {dwMajorVersion: DWORD},
michael@0 356 {dwMinorVersion: DWORD},
michael@0 357 {dwBuildNumber: DWORD},
michael@0 358 {dwPlatformId: DWORD},
michael@0 359 {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
michael@0 360 {wServicePackMajor: WORD},
michael@0 361 {wServicePackMinor: WORD},
michael@0 362 {wSuiteMask: WORD},
michael@0 363 {wProductType: BYTE},
michael@0 364 {wReserved: BYTE}
michael@0 365 ]);
michael@0 366
michael@0 367 // This structure is described at:
michael@0 368 // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
michael@0 369 const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
michael@0 370 [
michael@0 371 {wProcessorArchitecture: WORD},
michael@0 372 {wReserved: WORD},
michael@0 373 {dwPageSize: DWORD},
michael@0 374 {lpMinimumApplicationAddress: ctypes.voidptr_t},
michael@0 375 {lpMaximumApplicationAddress: ctypes.voidptr_t},
michael@0 376 {dwActiveProcessorMask: DWORD.ptr},
michael@0 377 {dwNumberOfProcessors: DWORD},
michael@0 378 {dwProcessorType: DWORD},
michael@0 379 {dwAllocationGranularity: DWORD},
michael@0 380 {wProcessorLevel: WORD},
michael@0 381 {wProcessorRevision: WORD}
michael@0 382 ]);
michael@0 383
michael@0 384 let kernel32 = false;
michael@0 385 try {
michael@0 386 kernel32 = ctypes.open("Kernel32");
michael@0 387 } catch (e) {
michael@0 388 LOG("gOSVersion - Unable to open kernel32! " + e);
michael@0 389 osVersion += ".unknown (unknown)";
michael@0 390 }
michael@0 391
michael@0 392 if(kernel32) {
michael@0 393 try {
michael@0 394 // Get Service pack info
michael@0 395 try {
michael@0 396 let GetVersionEx = kernel32.declare("GetVersionExW",
michael@0 397 ctypes.default_abi,
michael@0 398 BOOL,
michael@0 399 OSVERSIONINFOEXW.ptr);
michael@0 400 let winVer = OSVERSIONINFOEXW();
michael@0 401 winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
michael@0 402
michael@0 403 if(0 !== GetVersionEx(winVer.address())) {
michael@0 404 osVersion += "." + winVer.wServicePackMajor
michael@0 405 + "." + winVer.wServicePackMinor;
michael@0 406 } else {
michael@0 407 LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
michael@0 408 osVersion += ".unknown";
michael@0 409 }
michael@0 410 } catch (e) {
michael@0 411 LOG("gOSVersion - error getting service pack information. Exception: " + e);
michael@0 412 osVersion += ".unknown";
michael@0 413 }
michael@0 414
michael@0 415 // Get processor architecture
michael@0 416 let arch = "unknown";
michael@0 417 try {
michael@0 418 let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
michael@0 419 ctypes.default_abi,
michael@0 420 ctypes.void_t,
michael@0 421 SYSTEM_INFO.ptr);
michael@0 422 let sysInfo = SYSTEM_INFO();
michael@0 423 // Default to unknown
michael@0 424 sysInfo.wProcessorArchitecture = 0xffff;
michael@0 425
michael@0 426 GetNativeSystemInfo(sysInfo.address());
michael@0 427 switch(sysInfo.wProcessorArchitecture) {
michael@0 428 case 9:
michael@0 429 arch = "x64";
michael@0 430 break;
michael@0 431 case 6:
michael@0 432 arch = "IA64";
michael@0 433 break;
michael@0 434 case 0:
michael@0 435 arch = "x86";
michael@0 436 break;
michael@0 437 }
michael@0 438 } catch (e) {
michael@0 439 LOG("gOSVersion - error getting processor architecture. Exception: " + e);
michael@0 440 } finally {
michael@0 441 osVersion += " (" + arch + ")";
michael@0 442 }
michael@0 443 } finally {
michael@0 444 kernel32.close();
michael@0 445 }
michael@0 446 }
michael@0 447 #endif
michael@0 448 #endif
michael@0 449
michael@0 450 try {
michael@0 451 osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
michael@0 452 }
michael@0 453 catch (e) {
michael@0 454 // Not all platforms have a secondary widget library, so an error is nothing to worry about.
michael@0 455 }
michael@0 456 osVersion = encodeURIComponent(osVersion);
michael@0 457 }
michael@0 458 return osVersion;
michael@0 459 });
michael@0 460
michael@0 461 /**
michael@0 462 * Tests to make sure that we can write to a given directory.
michael@0 463 *
michael@0 464 * @param updateTestFile a test file in the directory that needs to be tested.
michael@0 465 * @param createDirectory whether a test directory should be created.
michael@0 466 * @throws if we don't have right access to the directory.
michael@0 467 */
michael@0 468 function testWriteAccess(updateTestFile, createDirectory) {
michael@0 469 const NORMAL_FILE_TYPE = Ci.nsILocalFile.NORMAL_FILE_TYPE;
michael@0 470 const DIRECTORY_TYPE = Ci.nsILocalFile.DIRECTORY_TYPE;
michael@0 471 if (updateTestFile.exists())
michael@0 472 updateTestFile.remove(false);
michael@0 473 updateTestFile.create(createDirectory ? DIRECTORY_TYPE : NORMAL_FILE_TYPE,
michael@0 474 createDirectory ? FileUtils.PERMS_DIRECTORY : FileUtils.PERMS_FILE);
michael@0 475 updateTestFile.remove(false);
michael@0 476 }
michael@0 477
michael@0 478 #ifdef XP_WIN
michael@0 479
michael@0 480 /**
michael@0 481 * Closes a Win32 handle
michael@0 482 *
michael@0 483 * @param handle The handle to close
michael@0 484 */
michael@0 485 function closeHandle(handle) {
michael@0 486 var lib = ctypes.open("kernel32.dll");
michael@0 487 var CloseHandle = lib.declare("CloseHandle",
michael@0 488 ctypes.winapi_abi,
michael@0 489 ctypes.int32_t, /* success */
michael@0 490 ctypes.void_t.ptr); /* handle */
michael@0 491 CloseHandle(handle);
michael@0 492 lib.close();
michael@0 493 }
michael@0 494
michael@0 495 /**
michael@0 496 * Creates a mutex.
michael@0 497 *
michael@0 498 * @param aName
michael@0 499 * The name for the mutex.
michael@0 500 * @param aAllowExisting
michael@0 501 * If false the function will close the handle and return null.
michael@0 502 * @return The Win32 handle to the mutex.
michael@0 503 */
michael@0 504 function createMutex(aName, aAllowExisting) {
michael@0 505 if (aAllowExisting === undefined) {
michael@0 506 aAllowExisting = true;
michael@0 507 }
michael@0 508
michael@0 509 const INITIAL_OWN = 1;
michael@0 510 const ERROR_ALREADY_EXISTS = 0xB7;
michael@0 511 var lib = ctypes.open("kernel32.dll");
michael@0 512 var CreateMutexW = lib.declare("CreateMutexW",
michael@0 513 ctypes.winapi_abi,
michael@0 514 ctypes.void_t.ptr, /* return handle */
michael@0 515 ctypes.void_t.ptr, /* security attributes */
michael@0 516 ctypes.int32_t, /* initial owner */
michael@0 517 ctypes.jschar.ptr); /* name */
michael@0 518
michael@0 519 var handle = CreateMutexW(null, INITIAL_OWN, aName);
michael@0 520 var alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
michael@0 521 if (handle && !handle.isNull() && !aAllowExisting && alreadyExists) {
michael@0 522 closeHandle(handle);
michael@0 523 handle = null;
michael@0 524 }
michael@0 525 lib.close();
michael@0 526
michael@0 527 if (handle && handle.isNull())
michael@0 528 handle = null;
michael@0 529
michael@0 530 return handle;
michael@0 531 }
michael@0 532
michael@0 533 /**
michael@0 534 * Determines a unique mutex name for the installation
michael@0 535 *
michael@0 536 * @param aGlobal true if the function should return a global mutex. A global
michael@0 537 * mutex is valid across different sessions
michael@0 538 * @return Global mutex path
michael@0 539 */
michael@0 540 function getPerInstallationMutexName(aGlobal) {
michael@0 541 if (aGlobal === undefined) {
michael@0 542 aGobal = true;
michael@0 543 }
michael@0 544 let hasher = Cc["@mozilla.org/security/hash;1"].
michael@0 545 createInstance(Ci.nsICryptoHash);
michael@0 546 hasher.init(hasher.SHA1);
michael@0 547
michael@0 548 var exeFile = Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsILocalFile);
michael@0 549
michael@0 550 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
michael@0 551 createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 552 converter.charset = "UTF-8";
michael@0 553 var data = converter.convertToByteArray(exeFile.path.toLowerCase());
michael@0 554
michael@0 555 hasher.update(data, data.length);
michael@0 556 return (aGlobal ? "Global\\" : "") + "MozillaUpdateMutex-" + hasher.finish(true);
michael@0 557 }
michael@0 558 #endif // XP_WIN
michael@0 559
michael@0 560 /**
michael@0 561 * Whether or not the current instance has the update mutex. The update mutex
michael@0 562 * gives protection against 2 applications from the same installation updating:
michael@0 563 * 1) Running multiple profiles from the same installation path
michael@0 564 * 2) Running a Metro and Desktop application at the same time from the same
michael@0 565 * path
michael@0 566 * 3) 2 applications running in 2 different user sessions from the same path
michael@0 567 *
michael@0 568 * @return true if this instance holds the update mutex
michael@0 569 */
michael@0 570 function hasUpdateMutex() {
michael@0 571 #ifdef XP_WIN
michael@0 572 if (!gUpdateMutexHandle) {
michael@0 573 gUpdateMutexHandle = createMutex(getPerInstallationMutexName(true), false);
michael@0 574 }
michael@0 575
michael@0 576 return !!gUpdateMutexHandle;
michael@0 577 #else
michael@0 578 return true;
michael@0 579 #endif // XP_WIN
michael@0 580 }
michael@0 581
michael@0 582 XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
michael@0 583 function submitHasPermissionsTelemetryPing(val) {
michael@0 584 try {
michael@0 585 let h = Services.telemetry.getHistogramById("UPDATER_HAS_PERMISSIONS");
michael@0 586 h.add(+val);
michael@0 587 } catch(e) {
michael@0 588 // Don't allow any exception to be propagated.
michael@0 589 Components.utils.reportError(e);
michael@0 590 }
michael@0 591 }
michael@0 592
michael@0 593 let useService = false;
michael@0 594 if (shouldUseService() && isServiceInstalled()) {
michael@0 595 // No need to perform directory write checks, the maintenance service will
michael@0 596 // be able to write to all directories.
michael@0 597 LOG("gCanApplyUpdates - bypass the write checks because we'll use the service");
michael@0 598 useService = true;
michael@0 599 }
michael@0 600
michael@0 601 if (!useService) {
michael@0 602 try {
michael@0 603 var updateTestFile = getUpdateFile([FILE_PERMS_TEST]);
michael@0 604 LOG("gCanApplyUpdates - testing write access " + updateTestFile.path);
michael@0 605 testWriteAccess(updateTestFile, false);
michael@0 606 #ifdef XP_WIN
michael@0 607 var sysInfo = Cc["@mozilla.org/system-info;1"].
michael@0 608 getService(Ci.nsIPropertyBag2);
michael@0 609
michael@0 610 // Example windowsVersion: Windows XP == 5.1
michael@0 611 var windowsVersion = sysInfo.getProperty("version");
michael@0 612 LOG("gCanApplyUpdates - windowsVersion = " + windowsVersion);
michael@0 613
michael@0 614 /**
michael@0 615 # For Vista, updates can be performed to a location requiring admin
michael@0 616 # privileges by requesting elevation via the UAC prompt when launching
michael@0 617 # updater.exe if the appDir is under the Program Files directory
michael@0 618 # (e.g. C:\Program Files\) and UAC is turned on and we can elevate
michael@0 619 # (e.g. user has a split token).
michael@0 620 #
michael@0 621 # Note: this does note attempt to handle the case where UAC is turned on
michael@0 622 # and the installation directory is in a restricted location that
michael@0 623 # requires admin privileges to update other than Program Files.
michael@0 624 */
michael@0 625 var userCanElevate = false;
michael@0 626
michael@0 627 if (parseFloat(windowsVersion) >= 6) {
michael@0 628 try {
michael@0 629 var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
michael@0 630 getService(Ci.nsIProperties);
michael@0 631 // KEY_UPDROOT will fail and throw an exception if
michael@0 632 // appDir is not under the Program Files, so we rely on that
michael@0 633 var dir = fileLocator.get(KEY_UPDROOT, Ci.nsIFile);
michael@0 634 // appDir is under Program Files, so check if the user can elevate
michael@0 635 userCanElevate = Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).
michael@0 636 userCanElevate;
michael@0 637 LOG("gCanApplyUpdates - on Vista, userCanElevate: " + userCanElevate);
michael@0 638 }
michael@0 639 catch (ex) {
michael@0 640 // When the installation directory is not under Program Files,
michael@0 641 // fall through to checking if write access to the
michael@0 642 // installation directory is available.
michael@0 643 LOG("gCanApplyUpdates - on Vista, appDir is not under Program Files");
michael@0 644 }
michael@0 645 }
michael@0 646
michael@0 647 /**
michael@0 648 # On Windows, we no longer store the update under the app dir.
michael@0 649 #
michael@0 650 # If we are on Windows (including Vista, if we can't elevate) we need to
michael@0 651 # to check that we can create and remove files from the actual app
michael@0 652 # directory (like C:\Program Files\Mozilla Firefox). If we can't
michael@0 653 # (because this user is not an adminstrator, for example) canUpdate()
michael@0 654 # should return false.
michael@0 655 #
michael@0 656 # For Vista, we perform this check to enable updating the application
michael@0 657 # when the user has write access to the installation directory under the
michael@0 658 # following scenarios:
michael@0 659 # 1) the installation directory is not under Program Files
michael@0 660 # (e.g. C:\Program Files)
michael@0 661 # 2) UAC is turned off
michael@0 662 # 3) UAC is turned on and the user is not an admin
michael@0 663 # (e.g. the user does not have a split token)
michael@0 664 # 4) UAC is turned on and the user is already elevated, so they can't be
michael@0 665 # elevated again
michael@0 666 */
michael@0 667 if (!userCanElevate) {
michael@0 668 // if we're unable to create the test file this will throw an exception.
michael@0 669 var appDirTestFile = getAppBaseDir();
michael@0 670 appDirTestFile.append(FILE_PERMS_TEST);
michael@0 671 LOG("gCanApplyUpdates - testing write access " + appDirTestFile.path);
michael@0 672 if (appDirTestFile.exists())
michael@0 673 appDirTestFile.remove(false)
michael@0 674 appDirTestFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
michael@0 675 appDirTestFile.remove(false);
michael@0 676 }
michael@0 677 #endif //XP_WIN
michael@0 678 }
michael@0 679 catch (e) {
michael@0 680 LOG("gCanApplyUpdates - unable to apply updates. Exception: " + e);
michael@0 681 // No write privileges to install directory
michael@0 682 submitHasPermissionsTelemetryPing(false);
michael@0 683 return false;
michael@0 684 }
michael@0 685 } // if (!useService)
michael@0 686
michael@0 687 LOG("gCanApplyUpdates - able to apply updates");
michael@0 688 submitHasPermissionsTelemetryPing(true);
michael@0 689 return true;
michael@0 690 });
michael@0 691
michael@0 692 /**
michael@0 693 * Whether or not the application can stage an update.
michael@0 694 *
michael@0 695 * @return true if updates can be staged.
michael@0 696 */
michael@0 697 function getCanStageUpdates() {
michael@0 698 // If background updates are disabled, then just bail out!
michael@0 699 if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
michael@0 700 LOG("getCanStageUpdates - staging updates is disabled by preference " +
michael@0 701 PREF_APP_UPDATE_STAGING_ENABLED);
michael@0 702 return false;
michael@0 703 }
michael@0 704
michael@0 705 #ifdef CHECK_CAN_USE_SERVICE
michael@0 706 if (getPref("getBoolPref", PREF_APP_UPDATE_SERVICE_ENABLED, false)) {
michael@0 707 // No need to perform directory write checks, the maintenance service will
michael@0 708 // be able to write to all directories.
michael@0 709 LOG("getCanStageUpdates - able to stage updates because we'll use the service");
michael@0 710 return true;
michael@0 711 }
michael@0 712 #endif
michael@0 713
michael@0 714 if (!hasUpdateMutex()) {
michael@0 715 LOG("getCanStageUpdates - unable to apply updates because another " +
michael@0 716 "instance of the application is already handling updates for this " +
michael@0 717 "installation.");
michael@0 718 return false;
michael@0 719 }
michael@0 720
michael@0 721 /**
michael@0 722 * Whether or not the application can stage an update for the current session.
michael@0 723 * These checks are only performed once per session due to using a lazy getter.
michael@0 724 *
michael@0 725 * @return true if updates can be staged for this session.
michael@0 726 */
michael@0 727 XPCOMUtils.defineLazyGetter(this, "canStageUpdatesSession", function canStageUpdatesSession() {
michael@0 728 try {
michael@0 729 var updateTestFile = getInstallDirRoot();
michael@0 730 updateTestFile.append(FILE_PERMS_TEST);
michael@0 731 LOG("canStageUpdatesSession - testing write access " +
michael@0 732 updateTestFile.path);
michael@0 733 testWriteAccess(updateTestFile, true);
michael@0 734 #ifndef XP_MACOSX
michael@0 735 // On all platforms except Mac, we need to test the parent directory as
michael@0 736 // well, as we need to be able to move files in that directory during the
michael@0 737 // replacing step.
michael@0 738 updateTestFile = getInstallDirRoot().parent;
michael@0 739 updateTestFile.append(FILE_PERMS_TEST);
michael@0 740 LOG("canStageUpdatesSession - testing write access " +
michael@0 741 updateTestFile.path);
michael@0 742 updateTestFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
michael@0 743 FileUtils.PERMS_DIRECTORY);
michael@0 744 updateTestFile.remove(false);
michael@0 745 #endif
michael@0 746 }
michael@0 747 catch (e) {
michael@0 748 LOG("canStageUpdatesSession - unable to stage updates. Exception: " +
michael@0 749 e);
michael@0 750 // No write privileges
michael@0 751 return false;
michael@0 752 }
michael@0 753
michael@0 754 LOG("canStageUpdatesSession - able to stage updates");
michael@0 755 return true;
michael@0 756 });
michael@0 757
michael@0 758 return canStageUpdatesSession;
michael@0 759 }
michael@0 760
michael@0 761 XPCOMUtils.defineLazyGetter(this, "gMetroUpdatesEnabled", function aus_gMetroUpdatesEnabled() {
michael@0 762 #ifdef XP_WIN
michael@0 763 #ifdef MOZ_METRO
michael@0 764 if (Services.metro && Services.metro.immersive) {
michael@0 765 let metroUpdate = getPref("getBoolPref", PREF_APP_UPDATE_METRO_ENABLED, true);
michael@0 766 if (!metroUpdate) {
michael@0 767 LOG("gMetroUpdatesEnabled - unable to automatically check for metro " +
michael@0 768 "updates, disabled by pref");
michael@0 769 return false;
michael@0 770 }
michael@0 771 }
michael@0 772 #endif
michael@0 773 #endif
michael@0 774
michael@0 775 return true;
michael@0 776 });
michael@0 777
michael@0 778 XPCOMUtils.defineLazyGetter(this, "gCanCheckForUpdates", function aus_gCanCheckForUpdates() {
michael@0 779 // If the administrator has disabled app update and locked the preference so
michael@0 780 // users can't check for updates. This preference check is ok in this lazy
michael@0 781 // getter since locked prefs don't change until the application is restarted.
michael@0 782 var enabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
michael@0 783 if (!enabled && Services.prefs.prefIsLocked(PREF_APP_UPDATE_ENABLED)) {
michael@0 784 LOG("gCanCheckForUpdates - unable to automatically check for updates, " +
michael@0 785 "the preference is disabled and admistratively locked.");
michael@0 786 return false;
michael@0 787 }
michael@0 788
michael@0 789 if (!gMetroUpdatesEnabled) {
michael@0 790 return false;
michael@0 791 }
michael@0 792
michael@0 793 // If we don't know the binary platform we're updating, we can't update.
michael@0 794 if (!gABI) {
michael@0 795 LOG("gCanCheckForUpdates - unable to check for updates, unknown ABI");
michael@0 796 return false;
michael@0 797 }
michael@0 798
michael@0 799 // If we don't know the OS version we're updating, we can't update.
michael@0 800 if (!gOSVersion) {
michael@0 801 LOG("gCanCheckForUpdates - unable to check for updates, unknown OS " +
michael@0 802 "version");
michael@0 803 return false;
michael@0 804 }
michael@0 805
michael@0 806 LOG("gCanCheckForUpdates - able to check for updates");
michael@0 807 return true;
michael@0 808 });
michael@0 809
michael@0 810 /**
michael@0 811 * Logs a string to the error console.
michael@0 812 * @param string
michael@0 813 * The string to write to the error console.
michael@0 814 */
michael@0 815 function LOG(string) {
michael@0 816 if (gLogEnabled) {
michael@0 817 dump("*** AUS:SVC " + string + "\n");
michael@0 818 Services.console.logStringMessage("AUS:SVC " + string);
michael@0 819 }
michael@0 820 }
michael@0 821
michael@0 822 /**
michael@0 823 # Gets a preference value, handling the case where there is no default.
michael@0 824 # @param func
michael@0 825 # The name of the preference function to call, on nsIPrefBranch
michael@0 826 # @param preference
michael@0 827 # The name of the preference
michael@0 828 # @param defaultValue
michael@0 829 # The default value to return in the event the preference has
michael@0 830 # no setting
michael@0 831 # @return The value of the preference, or undefined if there was no
michael@0 832 # user or default value.
michael@0 833 */
michael@0 834 function getPref(func, preference, defaultValue) {
michael@0 835 try {
michael@0 836 return Services.prefs[func](preference);
michael@0 837 }
michael@0 838 catch (e) {
michael@0 839 }
michael@0 840 return defaultValue;
michael@0 841 }
michael@0 842
michael@0 843 /**
michael@0 844 * Convert a string containing binary values to hex.
michael@0 845 */
michael@0 846 function binaryToHex(input) {
michael@0 847 var result = "";
michael@0 848 for (var i = 0; i < input.length; ++i) {
michael@0 849 var hex = input.charCodeAt(i).toString(16);
michael@0 850 if (hex.length == 1)
michael@0 851 hex = "0" + hex;
michael@0 852 result += hex;
michael@0 853 }
michael@0 854 return result;
michael@0 855 }
michael@0 856
michael@0 857 /**
michael@0 858 # Gets the specified directory at the specified hierarchy under the
michael@0 859 # update root directory and creates it if it doesn't exist.
michael@0 860 # @param pathArray
michael@0 861 # An array of path components to locate beneath the directory
michael@0 862 # specified by |key|
michael@0 863 # @return nsIFile object for the location specified.
michael@0 864 */
michael@0 865 function getUpdateDirCreate(pathArray) {
michael@0 866 return FileUtils.getDir(KEY_UPDROOT, pathArray, true);
michael@0 867 }
michael@0 868
michael@0 869 /**
michael@0 870 # Gets the specified directory at the specified hierarchy under the
michael@0 871 # update root directory and without creating it if it doesn't exist.
michael@0 872 # @param pathArray
michael@0 873 # An array of path components to locate beneath the directory
michael@0 874 # specified by |key|
michael@0 875 # @return nsIFile object for the location specified.
michael@0 876 */
michael@0 877 function getUpdateDirNoCreate(pathArray) {
michael@0 878 return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
michael@0 879 }
michael@0 880
michael@0 881 /**
michael@0 882 * Gets the application base directory.
michael@0 883 *
michael@0 884 * @return nsIFile object for the application base directory.
michael@0 885 */
michael@0 886 function getAppBaseDir() {
michael@0 887 return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent;
michael@0 888 }
michael@0 889
michael@0 890 /**
michael@0 891 * Gets the root of the installation directory which is the application
michael@0 892 * bundle directory on Mac OS X and the location of the application binary
michael@0 893 * on all other platforms.
michael@0 894 *
michael@0 895 * @return nsIFile object for the directory
michael@0 896 */
michael@0 897 function getInstallDirRoot() {
michael@0 898 var dir = getAppBaseDir();
michael@0 899 #ifdef XP_MACOSX
michael@0 900 // On Mac, we store the Updated.app directory inside the bundle directory.
michael@0 901 dir = dir.parent.parent;
michael@0 902 #endif
michael@0 903 return dir;
michael@0 904 }
michael@0 905
michael@0 906 /**
michael@0 907 * Gets the file at the specified hierarchy under the update root directory.
michael@0 908 * @param pathArray
michael@0 909 * An array of path components to locate beneath the directory
michael@0 910 * specified by |key|. The last item in this array must be the
michael@0 911 * leaf name of a file.
michael@0 912 * @return nsIFile object for the file specified. The file is NOT created
michael@0 913 * if it does not exist, however all required directories along
michael@0 914 * the way are.
michael@0 915 */
michael@0 916 function getUpdateFile(pathArray) {
michael@0 917 var file = getUpdateDirCreate(pathArray.slice(0, -1));
michael@0 918 file.append(pathArray[pathArray.length - 1]);
michael@0 919 return file;
michael@0 920 }
michael@0 921
michael@0 922 /**
michael@0 923 * Returns human readable status text from the updates.properties bundle
michael@0 924 * based on an error code
michael@0 925 * @param code
michael@0 926 * The error code to look up human readable status text for
michael@0 927 * @param defaultCode
michael@0 928 * The default code to look up should human readable status text
michael@0 929 * not exist for |code|
michael@0 930 * @return A human readable status text string
michael@0 931 */
michael@0 932 function getStatusTextFromCode(code, defaultCode) {
michael@0 933 var reason;
michael@0 934 try {
michael@0 935 reason = gUpdateBundle.GetStringFromName("check_error-" + code);
michael@0 936 LOG("getStatusTextFromCode - transfer error: " + reason + ", code: " +
michael@0 937 code);
michael@0 938 }
michael@0 939 catch (e) {
michael@0 940 // Use the default reason
michael@0 941 reason = gUpdateBundle.GetStringFromName("check_error-" + defaultCode);
michael@0 942 LOG("getStatusTextFromCode - transfer error: " + reason +
michael@0 943 ", default code: " + defaultCode);
michael@0 944 }
michael@0 945 return reason;
michael@0 946 }
michael@0 947
michael@0 948 /**
michael@0 949 * Get the Active Updates directory
michael@0 950 * @return The active updates directory, as a nsIFile object
michael@0 951 */
michael@0 952 function getUpdatesDir() {
michael@0 953 // Right now, we only support downloading one patch at a time, so we always
michael@0 954 // use the same target directory.
michael@0 955 return getUpdateDirCreate([DIR_UPDATES, "0"]);
michael@0 956 }
michael@0 957
michael@0 958 /**
michael@0 959 * Get the Active Updates directory inside the directory where we apply the
michael@0 960 * background updates.
michael@0 961 * @return The active updates directory inside the updated directory, as a
michael@0 962 * nsIFile object.
michael@0 963 */
michael@0 964 function getUpdatesDirInApplyToDir() {
michael@0 965 var dir = getAppBaseDir();
michael@0 966 #ifdef XP_MACOSX
michael@0 967 dir = dir.parent.parent; // the bundle directory
michael@0 968 #endif
michael@0 969 dir.append(UPDATED_DIR);
michael@0 970 #ifdef XP_MACOSX
michael@0 971 dir.append("Contents");
michael@0 972 dir.append("MacOS");
michael@0 973 #endif
michael@0 974 dir.append(DIR_UPDATES);
michael@0 975 if (!dir.exists()) {
michael@0 976 dir.create(Ci.nsILocalFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
michael@0 977 }
michael@0 978 return dir;
michael@0 979 }
michael@0 980
michael@0 981 /**
michael@0 982 * Reads the update state from the update.status file in the specified
michael@0 983 * directory.
michael@0 984 * @param dir
michael@0 985 * The dir to look for an update.status file in
michael@0 986 * @return The status value of the update.
michael@0 987 */
michael@0 988 function readStatusFile(dir) {
michael@0 989 var statusFile = dir.clone();
michael@0 990 statusFile.append(FILE_UPDATE_STATUS);
michael@0 991 var status = readStringFromFile(statusFile) || STATE_NONE;
michael@0 992 LOG("readStatusFile - status: " + status + ", path: " + statusFile.path);
michael@0 993 return status;
michael@0 994 }
michael@0 995
michael@0 996 /**
michael@0 997 * Writes the current update operation/state to a file in the patch
michael@0 998 * directory, indicating to the patching system that operations need
michael@0 999 * to be performed.
michael@0 1000 * @param dir
michael@0 1001 * The patch directory where the update.status file should be
michael@0 1002 * written.
michael@0 1003 * @param state
michael@0 1004 * The state value to write.
michael@0 1005 */
michael@0 1006 function writeStatusFile(dir, state) {
michael@0 1007 var statusFile = dir.clone();
michael@0 1008 statusFile.append(FILE_UPDATE_STATUS);
michael@0 1009 writeStringToFile(statusFile, state);
michael@0 1010 }
michael@0 1011
michael@0 1012 #ifdef MOZ_WIDGET_GONK
michael@0 1013 /**
michael@0 1014 * Reads the link file specified in the update.link file in the
michael@0 1015 * specified directory and returns the nsIFile for the
michael@0 1016 * corresponding file.
michael@0 1017 * @param dir
michael@0 1018 * The dir to look for an update.link file in
michael@0 1019 * @return A nsIFile for the file path specified in the
michael@0 1020 * update.link file or null if the update.link file
michael@0 1021 * doesn't exist.
michael@0 1022 */
michael@0 1023 function getFileFromUpdateLink(dir) {
michael@0 1024 var linkFile = dir.clone();
michael@0 1025 linkFile.append(FILE_UPDATE_LINK);
michael@0 1026 var link = readStringFromFile(linkFile);
michael@0 1027 LOG("getFileFromUpdateLink linkFile.path: " + linkFile.path + ", link: " + link);
michael@0 1028 if (!link) {
michael@0 1029 return null;
michael@0 1030 }
michael@0 1031 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
michael@0 1032 file.initWithPath(link);
michael@0 1033 return file;
michael@0 1034 }
michael@0 1035
michael@0 1036 /**
michael@0 1037 * Creates a link file, which allows the actual patch to live in
michael@0 1038 * a directory different from the update directory.
michael@0 1039 * @param dir
michael@0 1040 * The patch directory where the update.link file
michael@0 1041 * should be written.
michael@0 1042 * @param patchFile
michael@0 1043 * The fully qualified filename of the patchfile.
michael@0 1044 */
michael@0 1045 function writeLinkFile(dir, patchFile) {
michael@0 1046 var linkFile = dir.clone();
michael@0 1047 linkFile.append(FILE_UPDATE_LINK);
michael@0 1048 writeStringToFile(linkFile, patchFile.path);
michael@0 1049 if (patchFile.path.indexOf(gExtStorage) == 0) {
michael@0 1050 // The patchfile is being stored on external storage. Try to lock it
michael@0 1051 // so that it doesn't get shared with the PC while we're downloading
michael@0 1052 // to it.
michael@0 1053 acquireSDCardMountLock();
michael@0 1054 }
michael@0 1055 }
michael@0 1056
michael@0 1057 /**
michael@0 1058 * Acquires a VolumeMountLock for the sdcard volume.
michael@0 1059 *
michael@0 1060 * This prevents the SDCard from being shared with the PC while
michael@0 1061 * we're downloading the update.
michael@0 1062 */
michael@0 1063 function acquireSDCardMountLock() {
michael@0 1064 let volsvc = Cc["@mozilla.org/telephony/volume-service;1"].
michael@0 1065 getService(Ci.nsIVolumeService);
michael@0 1066 if (volsvc) {
michael@0 1067 gSDCardMountLock = volsvc.createMountLock("sdcard");
michael@0 1068 }
michael@0 1069 }
michael@0 1070
michael@0 1071 /**
michael@0 1072 * Determines if the state corresponds to an interrupted update.
michael@0 1073 * This could either be because the download was interrupted, or
michael@0 1074 * because staging the update was interrupted.
michael@0 1075 *
michael@0 1076 * @return true if the state corresponds to an interrupted
michael@0 1077 * update.
michael@0 1078 */
michael@0 1079 function isInterruptedUpdate(status) {
michael@0 1080 return (status == STATE_DOWNLOADING) ||
michael@0 1081 (status == STATE_PENDING) ||
michael@0 1082 (status == STATE_APPLYING);
michael@0 1083 }
michael@0 1084 #endif // MOZ_WIDGET_GONK
michael@0 1085
michael@0 1086 /**
michael@0 1087 * Releases any SDCard mount lock that we might have.
michael@0 1088 *
michael@0 1089 * This once again allows the SDCard to be shared with the PC.
michael@0 1090 *
michael@0 1091 * This function was placed outside the #ifdef so that we didn't
michael@0 1092 * need to put #ifdefs around the callers.
michael@0 1093 */
michael@0 1094 function releaseSDCardMountLock() {
michael@0 1095 #ifdef MOZ_WIDGET_GONK
michael@0 1096 if (gSDCardMountLock) {
michael@0 1097 gSDCardMountLock.unlock();
michael@0 1098 gSDCardMountLock = null;
michael@0 1099 }
michael@0 1100 #endif
michael@0 1101 }
michael@0 1102
michael@0 1103 /**
michael@0 1104 * Determines if the service should be used to attempt an update
michael@0 1105 * or not. For now this is only when PREF_APP_UPDATE_SERVICE_ENABLED
michael@0 1106 * is true and we have Firefox.
michael@0 1107 *
michael@0 1108 * @return true if the service should be used for updates.
michael@0 1109 */
michael@0 1110 function shouldUseService() {
michael@0 1111 #ifdef MOZ_MAINTENANCE_SERVICE
michael@0 1112 return getPref("getBoolPref",
michael@0 1113 PREF_APP_UPDATE_SERVICE_ENABLED, false);
michael@0 1114 #else
michael@0 1115 return false;
michael@0 1116 #endif
michael@0 1117 }
michael@0 1118
michael@0 1119 /**
michael@0 1120 * Determines if the service is is installed and enabled or not.
michael@0 1121 *
michael@0 1122 * @return true if the service should be used for updates,
michael@0 1123 * is installed and enabled.
michael@0 1124 */
michael@0 1125 function isServiceInstalled() {
michael@0 1126 #ifdef XP_WIN
michael@0 1127 let installed = 0;
michael@0 1128 try {
michael@0 1129 let wrk = Cc["@mozilla.org/windows-registry-key;1"].
michael@0 1130 createInstance(Ci.nsIWindowsRegKey);
michael@0 1131 wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
michael@0 1132 "SOFTWARE\\Mozilla\\MaintenanceService",
michael@0 1133 wrk.ACCESS_READ | wrk.WOW64_64);
michael@0 1134 installed = wrk.readIntValue("Installed");
michael@0 1135 wrk.close();
michael@0 1136 } catch(e) {
michael@0 1137 }
michael@0 1138 installed = installed == 1; // convert to bool
michael@0 1139 LOG("isServiceInstalled = " + installed);
michael@0 1140 return installed;
michael@0 1141 #else
michael@0 1142 return false;
michael@0 1143 #endif
michael@0 1144 }
michael@0 1145
michael@0 1146 /**
michael@0 1147 # Writes the update's application version to a file in the patch directory. If
michael@0 1148 # the update doesn't provide application version information via the
michael@0 1149 # appVersion attribute the string "null" will be written to the file.
michael@0 1150 # This value is compared during startup (in nsUpdateDriver.cpp) to determine if
michael@0 1151 # the update should be applied. Note that this won't provide protection from
michael@0 1152 # downgrade of the application for the nightly user case where the application
michael@0 1153 # version doesn't change.
michael@0 1154 # @param dir
michael@0 1155 # The patch directory where the update.version file should be
michael@0 1156 # written.
michael@0 1157 # @param version
michael@0 1158 # The version value to write. Will be the string "null" when the
michael@0 1159 # update doesn't provide the appVersion attribute in the update xml.
michael@0 1160 */
michael@0 1161 function writeVersionFile(dir, version) {
michael@0 1162 var versionFile = dir.clone();
michael@0 1163 versionFile.append(FILE_UPDATE_VERSION);
michael@0 1164 writeStringToFile(versionFile, version);
michael@0 1165 }
michael@0 1166
michael@0 1167 /**
michael@0 1168 * Removes the MozUpdater folders that bgupdates/staged updates creates.
michael@0 1169 */
michael@0 1170 function cleanUpMozUpdaterDirs() {
michael@0 1171 try {
michael@0 1172 var tmpDir = Cc["@mozilla.org/file/directory_service;1"].
michael@0 1173 getService(Ci.nsIProperties).
michael@0 1174 get("TmpD", Ci.nsIFile);
michael@0 1175
michael@0 1176 // We used to store MozUpdater-i folders directly inside the temp directory.
michael@0 1177 // We need to cleanup these directories if we detect that they still exist.
michael@0 1178 // To check if they still exist, we simply check for MozUpdater-1.
michael@0 1179 var mozUpdaterDir1 = tmpDir.clone();
michael@0 1180 mozUpdaterDir1.append("MozUpdater-1");
michael@0 1181 // Only try to delete the left over folders in "$Temp/MozUpdater-i/*" if
michael@0 1182 // MozUpdater-1 exists.
michael@0 1183 if (mozUpdaterDir1.exists()) {
michael@0 1184 LOG("cleanUpMozUpdaterDirs - Cleaning top level MozUpdater-i folders");
michael@0 1185 let i = 0;
michael@0 1186 let dirEntries = tmpDir.directoryEntries;
michael@0 1187 while (dirEntries.hasMoreElements() && i < 10) {
michael@0 1188 let file = dirEntries.getNext().QueryInterface(Ci.nsILocalFile);
michael@0 1189 if (file.leafName.startsWith("MozUpdater-") && file.leafName != "MozUpdater-1") {
michael@0 1190 file.remove(true);
michael@0 1191 i++;
michael@0 1192 }
michael@0 1193 }
michael@0 1194 // If you enumerate the whole temp directory and the count of deleted
michael@0 1195 // items is less than 10, then delete MozUpdate-1.
michael@0 1196 if (i < 10) {
michael@0 1197 mozUpdaterDir1.remove(true);
michael@0 1198 }
michael@0 1199 }
michael@0 1200
michael@0 1201 // If we reach here, we simply need to clean the MozUpdater folder. In our
michael@0 1202 // new way of storing these files, the unique subfolders are inside MozUpdater
michael@0 1203 var mozUpdaterDir = tmpDir.clone();
michael@0 1204 mozUpdaterDir.append("MozUpdater");
michael@0 1205 if (mozUpdaterDir.exists()) {
michael@0 1206 LOG("cleanUpMozUpdaterDirs - Cleaning MozUpdater folder");
michael@0 1207 mozUpdaterDir.remove(true);
michael@0 1208 }
michael@0 1209 } catch (e) {
michael@0 1210 LOG("cleanUpMozUpdaterDirs - Exception: " + e);
michael@0 1211 }
michael@0 1212 }
michael@0 1213
michael@0 1214 /**
michael@0 1215 * Removes the contents of the Updates Directory
michael@0 1216 *
michael@0 1217 * @param aBackgroundUpdate Whether the update has been performed in the
michael@0 1218 * background. If this is true, we move the update log file to the
michael@0 1219 * updated directory, so that it survives replacing the directories
michael@0 1220 * later on.
michael@0 1221 */
michael@0 1222 function cleanUpUpdatesDir(aBackgroundUpdate) {
michael@0 1223 // Bail out if we don't have appropriate permissions
michael@0 1224 try {
michael@0 1225 var updateDir = getUpdatesDir();
michael@0 1226 } catch (e) {
michael@0 1227 return;
michael@0 1228 }
michael@0 1229
michael@0 1230 // Preserve the last update log file for debugging purposes.
michael@0 1231 let file = updateDir.clone();
michael@0 1232 file.append(FILE_UPDATE_LOG);
michael@0 1233 if (file.exists()) {
michael@0 1234 let dir;
michael@0 1235 if (aBackgroundUpdate && getUpdateDirNoCreate([]).equals(getAppBaseDir())) {
michael@0 1236 dir = getUpdatesDirInApplyToDir();
michael@0 1237 } else {
michael@0 1238 dir = updateDir.parent;
michael@0 1239 }
michael@0 1240 let logFile = dir.clone();
michael@0 1241 logFile.append(FILE_LAST_LOG);
michael@0 1242 if (logFile.exists()) {
michael@0 1243 try {
michael@0 1244 logFile.moveTo(dir, FILE_BACKUP_LOG);
michael@0 1245 } catch (e) {
michael@0 1246 LOG("cleanUpUpdatesDir - failed to rename file " + logFile.path +
michael@0 1247 " to " + FILE_BACKUP_LOG);
michael@0 1248 }
michael@0 1249 }
michael@0 1250
michael@0 1251 try {
michael@0 1252 file.moveTo(dir, FILE_LAST_LOG);
michael@0 1253 } catch (e) {
michael@0 1254 LOG("cleanUpUpdatesDir - failed to rename file " + file.path +
michael@0 1255 " to " + FILE_LAST_LOG);
michael@0 1256 }
michael@0 1257 }
michael@0 1258
michael@0 1259 if (!aBackgroundUpdate) {
michael@0 1260 let e = updateDir.directoryEntries;
michael@0 1261 while (e.hasMoreElements()) {
michael@0 1262 let f = e.getNext().QueryInterface(Ci.nsIFile);
michael@0 1263 #ifdef MOZ_WIDGET_GONK
michael@0 1264 if (f.leafName == FILE_UPDATE_LINK) {
michael@0 1265 let linkedFile = getFileFromUpdateLink(updateDir);
michael@0 1266 if (linkedFile && linkedFile.exists()) {
michael@0 1267 linkedFile.remove(false);
michael@0 1268 }
michael@0 1269 }
michael@0 1270 #endif
michael@0 1271
michael@0 1272 // Now, recursively remove this file. The recursive removal is needed for
michael@0 1273 // Mac OSX because this directory will contain a copy of updater.app,
michael@0 1274 // which is itself a directory.
michael@0 1275 try {
michael@0 1276 f.remove(true);
michael@0 1277 } catch (e) {
michael@0 1278 LOG("cleanUpUpdatesDir - failed to remove file " + f.path);
michael@0 1279 }
michael@0 1280 }
michael@0 1281 }
michael@0 1282 releaseSDCardMountLock();
michael@0 1283 }
michael@0 1284
michael@0 1285 /**
michael@0 1286 * Clean up updates list and the updates directory.
michael@0 1287 */
michael@0 1288 function cleanupActiveUpdate() {
michael@0 1289 // Move the update from the Active Update list into the Past Updates list.
michael@0 1290 var um = Cc["@mozilla.org/updates/update-manager;1"].
michael@0 1291 getService(Ci.nsIUpdateManager);
michael@0 1292 um.activeUpdate = null;
michael@0 1293 um.saveUpdates();
michael@0 1294
michael@0 1295 // Now trash the updates directory, since we're done with it
michael@0 1296 cleanUpUpdatesDir();
michael@0 1297 }
michael@0 1298
michael@0 1299 /**
michael@0 1300 * Gets the locale from the update.locale file for replacing %LOCALE% in the
michael@0 1301 * update url. The update.locale file can be located in the application
michael@0 1302 * directory or the GRE directory with preference given to it being located in
michael@0 1303 * the application directory.
michael@0 1304 */
michael@0 1305 function getLocale() {
michael@0 1306 if (gLocale)
michael@0 1307 return gLocale;
michael@0 1308
michael@0 1309 for (let res of ['app', 'gre']) {
michael@0 1310 var channel = Services.io.newChannel("resource://" + res + "/" + FILE_UPDATE_LOCALE, null, null);
michael@0 1311 try {
michael@0 1312 var inputStream = channel.open();
michael@0 1313 gLocale = readStringFromInputStream(inputStream);
michael@0 1314 } catch(e) {}
michael@0 1315 if (gLocale)
michael@0 1316 break;
michael@0 1317 }
michael@0 1318
michael@0 1319 if (!gLocale)
michael@0 1320 throw Components.Exception(FILE_UPDATE_LOCALE + " file doesn't exist in " +
michael@0 1321 "either the application or GRE directories",
michael@0 1322 Cr.NS_ERROR_FILE_NOT_FOUND);
michael@0 1323
michael@0 1324 LOG("getLocale - getting locale from file: " + channel.originalURI.spec +
michael@0 1325 ", locale: " + gLocale);
michael@0 1326 return gLocale;
michael@0 1327 }
michael@0 1328
michael@0 1329 /* Get the distribution pref values, from defaults only */
michael@0 1330 function getDistributionPrefValue(aPrefName) {
michael@0 1331 var prefValue = "default";
michael@0 1332
michael@0 1333 try {
michael@0 1334 prefValue = Services.prefs.getDefaultBranch(null).getCharPref(aPrefName);
michael@0 1335 } catch (e) {
michael@0 1336 // use default when pref not found
michael@0 1337 }
michael@0 1338
michael@0 1339 return prefValue;
michael@0 1340 }
michael@0 1341
michael@0 1342 /**
michael@0 1343 * An enumeration of items in a JS array.
michael@0 1344 * @constructor
michael@0 1345 */
michael@0 1346 function ArrayEnumerator(aItems) {
michael@0 1347 this._index = 0;
michael@0 1348 if (aItems) {
michael@0 1349 for (var i = 0; i < aItems.length; ++i) {
michael@0 1350 if (!aItems[i])
michael@0 1351 aItems.splice(i, 1);
michael@0 1352 }
michael@0 1353 }
michael@0 1354 this._contents = aItems;
michael@0 1355 }
michael@0 1356
michael@0 1357 ArrayEnumerator.prototype = {
michael@0 1358 _index: 0,
michael@0 1359 _contents: [],
michael@0 1360
michael@0 1361 hasMoreElements: function ArrayEnumerator_hasMoreElements() {
michael@0 1362 return this._index < this._contents.length;
michael@0 1363 },
michael@0 1364
michael@0 1365 getNext: function ArrayEnumerator_getNext() {
michael@0 1366 return this._contents[this._index++];
michael@0 1367 }
michael@0 1368 };
michael@0 1369
michael@0 1370 /**
michael@0 1371 * Writes a string of text to a file. A newline will be appended to the data
michael@0 1372 * written to the file. This function only works with ASCII text.
michael@0 1373 */
michael@0 1374 function writeStringToFile(file, text) {
michael@0 1375 var fos = FileUtils.openSafeFileOutputStream(file)
michael@0 1376 text += "\n";
michael@0 1377 fos.write(text, text.length);
michael@0 1378 FileUtils.closeSafeFileOutputStream(fos);
michael@0 1379 }
michael@0 1380
michael@0 1381 function readStringFromInputStream(inputStream) {
michael@0 1382 var sis = Cc["@mozilla.org/scriptableinputstream;1"].
michael@0 1383 createInstance(Ci.nsIScriptableInputStream);
michael@0 1384 sis.init(inputStream);
michael@0 1385 var text = sis.read(sis.available());
michael@0 1386 sis.close();
michael@0 1387 if (text[text.length - 1] == "\n")
michael@0 1388 text = text.slice(0, -1);
michael@0 1389 return text;
michael@0 1390 }
michael@0 1391
michael@0 1392 /**
michael@0 1393 * Reads a string of text from a file. A trailing newline will be removed
michael@0 1394 * before the result is returned. This function only works with ASCII text.
michael@0 1395 */
michael@0 1396 function readStringFromFile(file) {
michael@0 1397 if (!file.exists()) {
michael@0 1398 LOG("readStringFromFile - file doesn't exist: " + file.path);
michael@0 1399 return null;
michael@0 1400 }
michael@0 1401 var fis = Cc["@mozilla.org/network/file-input-stream;1"].
michael@0 1402 createInstance(Ci.nsIFileInputStream);
michael@0 1403 fis.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
michael@0 1404 return readStringFromInputStream(fis);
michael@0 1405 }
michael@0 1406
michael@0 1407 function handleUpdateFailure(update, errorCode) {
michael@0 1408 update.errorCode = parseInt(errorCode);
michael@0 1409 if (update.errorCode == FOTA_GENERAL_ERROR ||
michael@0 1410 update.errorCode == FOTA_FILE_OPERATION_ERROR ||
michael@0 1411 update.errorCode == FOTA_RECOVERY_ERROR ||
michael@0 1412 update.errorCode == FOTA_UNKNOWN_ERROR) {
michael@0 1413 // In the case of FOTA update errors, don't reset the state to pending. This
michael@0 1414 // causes the FOTA update path to try again, which is not necessarily what
michael@0 1415 // we want.
michael@0 1416 update.statusText = gUpdateBundle.GetStringFromName("statusFailed");
michael@0 1417
michael@0 1418 Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 1419 createInstance(Ci.nsIUpdatePrompt).
michael@0 1420 showUpdateError(update);
michael@0 1421 writeStatusFile(getUpdatesDir(), STATE_FAILED + ": " + errorCode);
michael@0 1422 cleanupActiveUpdate();
michael@0 1423 return true;
michael@0 1424 }
michael@0 1425
michael@0 1426 if (update.errorCode == WRITE_ERROR ||
michael@0 1427 update.errorCode == WRITE_ERROR_ACCESS_DENIED ||
michael@0 1428 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED ||
michael@0 1429 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID ||
michael@0 1430 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID ||
michael@0 1431 update.errorCode == WRITE_ERROR_CALLBACK_APP ||
michael@0 1432 update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR) {
michael@0 1433 Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 1434 createInstance(Ci.nsIUpdatePrompt).
michael@0 1435 showUpdateError(update);
michael@0 1436 writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
michael@0 1437 return true;
michael@0 1438 }
michael@0 1439
michael@0 1440 if (update.errorCode == ELEVATION_CANCELED) {
michael@0 1441 writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
michael@0 1442 return true;
michael@0 1443 }
michael@0 1444
michael@0 1445 if (update.errorCode == SERVICE_UPDATER_COULD_NOT_BE_STARTED ||
michael@0 1446 update.errorCode == SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS ||
michael@0 1447 update.errorCode == SERVICE_UPDATER_SIGN_ERROR ||
michael@0 1448 update.errorCode == SERVICE_UPDATER_COMPARE_ERROR ||
michael@0 1449 update.errorCode == SERVICE_UPDATER_IDENTITY_ERROR ||
michael@0 1450 update.errorCode == SERVICE_STILL_APPLYING_ON_SUCCESS ||
michael@0 1451 update.errorCode == SERVICE_STILL_APPLYING_ON_FAILURE ||
michael@0 1452 update.errorCode == SERVICE_UPDATER_NOT_FIXED_DRIVE ||
michael@0 1453 update.errorCode == SERVICE_COULD_NOT_LOCK_UPDATER ||
michael@0 1454 update.errorCode == SERVICE_COULD_NOT_COPY_UPDATER ||
michael@0 1455 update.errorCode == SERVICE_INSTALLDIR_ERROR) {
michael@0 1456
michael@0 1457 var failCount = getPref("getIntPref",
michael@0 1458 PREF_APP_UPDATE_SERVICE_ERRORS, 0);
michael@0 1459 var maxFail = getPref("getIntPref",
michael@0 1460 PREF_APP_UPDATE_SERVICE_MAX_ERRORS,
michael@0 1461 DEFAULT_SERVICE_MAX_ERRORS);
michael@0 1462
michael@0 1463 // As a safety, when the service reaches maximum failures, it will
michael@0 1464 // disable itself and fallback to using the normal update mechanism
michael@0 1465 // without the service.
michael@0 1466 if (failCount >= maxFail) {
michael@0 1467 Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
michael@0 1468 Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
michael@0 1469 } else {
michael@0 1470 failCount++;
michael@0 1471 Services.prefs.setIntPref(PREF_APP_UPDATE_SERVICE_ERRORS,
michael@0 1472 failCount);
michael@0 1473 }
michael@0 1474
michael@0 1475 writeStatusFile(getUpdatesDir(), update.state = STATE_PENDING);
michael@0 1476 try {
michael@0 1477 Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").
michael@0 1478 add(update.errorCode);
michael@0 1479 }
michael@0 1480 catch (e) {
michael@0 1481 Cu.reportError(e);
michael@0 1482 }
michael@0 1483 return true;
michael@0 1484 }
michael@0 1485
michael@0 1486 try {
michael@0 1487 Services.telemetry.getHistogramById("UPDATER_SERVICE_ERROR_CODE").add(0);
michael@0 1488 }
michael@0 1489 catch (e) {
michael@0 1490 Cu.reportError(e);
michael@0 1491 }
michael@0 1492 return false;
michael@0 1493 }
michael@0 1494
michael@0 1495 /**
michael@0 1496 * Fall back to downloading a complete update in case an update has failed.
michael@0 1497 *
michael@0 1498 * @param update the update object that has failed to apply.
michael@0 1499 * @param postStaging true if we have just attempted to stage an update.
michael@0 1500 */
michael@0 1501 function handleFallbackToCompleteUpdate(update, postStaging) {
michael@0 1502 cleanupActiveUpdate();
michael@0 1503
michael@0 1504 update.statusText = gUpdateBundle.GetStringFromName("patchApplyFailure");
michael@0 1505 var oldType = update.selectedPatch ? update.selectedPatch.type
michael@0 1506 : "complete";
michael@0 1507 if (update.selectedPatch && oldType == "partial" && update.patchCount == 2) {
michael@0 1508 // Partial patch application failed, try downloading the complete
michael@0 1509 // update in the background instead.
michael@0 1510 LOG("handleFallbackToCompleteUpdate - install of partial patch " +
michael@0 1511 "failed, downloading complete patch");
michael@0 1512 var status = Cc["@mozilla.org/updates/update-service;1"].
michael@0 1513 getService(Ci.nsIApplicationUpdateService).
michael@0 1514 downloadUpdate(update, !postStaging);
michael@0 1515 if (status == STATE_NONE)
michael@0 1516 cleanupActiveUpdate();
michael@0 1517 }
michael@0 1518 else {
michael@0 1519 LOG("handleFallbackToCompleteUpdate - install of complete or " +
michael@0 1520 "only one patch offered failed.");
michael@0 1521 }
michael@0 1522 update.QueryInterface(Ci.nsIWritablePropertyBag);
michael@0 1523 update.setProperty("patchingFailed", oldType);
michael@0 1524 }
michael@0 1525
michael@0 1526 /**
michael@0 1527 * Update Patch
michael@0 1528 * @param patch
michael@0 1529 * A <patch> element to initialize this object with
michael@0 1530 * @throws if patch has a size of 0
michael@0 1531 * @constructor
michael@0 1532 */
michael@0 1533 function UpdatePatch(patch) {
michael@0 1534 this._properties = {};
michael@0 1535 for (var i = 0; i < patch.attributes.length; ++i) {
michael@0 1536 var attr = patch.attributes.item(i);
michael@0 1537 attr.QueryInterface(Ci.nsIDOMAttr);
michael@0 1538 switch (attr.name) {
michael@0 1539 case "selected":
michael@0 1540 this.selected = attr.value == "true";
michael@0 1541 break;
michael@0 1542 case "size":
michael@0 1543 if (0 == parseInt(attr.value)) {
michael@0 1544 LOG("UpdatePatch:init - 0-sized patch!");
michael@0 1545 throw Cr.NS_ERROR_ILLEGAL_VALUE;
michael@0 1546 }
michael@0 1547 // fall through
michael@0 1548 default:
michael@0 1549 this[attr.name] = attr.value;
michael@0 1550 break;
michael@0 1551 };
michael@0 1552 }
michael@0 1553 }
michael@0 1554 UpdatePatch.prototype = {
michael@0 1555 /**
michael@0 1556 * See nsIUpdateService.idl
michael@0 1557 */
michael@0 1558 serialize: function UpdatePatch_serialize(updates) {
michael@0 1559 var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
michael@0 1560 patch.setAttribute("type", this.type);
michael@0 1561 patch.setAttribute("URL", this.URL);
michael@0 1562 // finalURL is not available until after the download has started
michael@0 1563 if (this.finalURL)
michael@0 1564 patch.setAttribute("finalURL", this.finalURL);
michael@0 1565 patch.setAttribute("hashFunction", this.hashFunction);
michael@0 1566 patch.setAttribute("hashValue", this.hashValue);
michael@0 1567 patch.setAttribute("size", this.size);
michael@0 1568 patch.setAttribute("selected", this.selected);
michael@0 1569 patch.setAttribute("state", this.state);
michael@0 1570
michael@0 1571 for (var p in this._properties) {
michael@0 1572 if (this._properties[p].present)
michael@0 1573 patch.setAttribute(p, this._properties[p].data);
michael@0 1574 }
michael@0 1575
michael@0 1576 return patch;
michael@0 1577 },
michael@0 1578
michael@0 1579 /**
michael@0 1580 * A hash of custom properties
michael@0 1581 */
michael@0 1582 _properties: null,
michael@0 1583
michael@0 1584 /**
michael@0 1585 * See nsIWritablePropertyBag.idl
michael@0 1586 */
michael@0 1587 setProperty: function UpdatePatch_setProperty(name, value) {
michael@0 1588 this._properties[name] = { data: value, present: true };
michael@0 1589 },
michael@0 1590
michael@0 1591 /**
michael@0 1592 * See nsIWritablePropertyBag.idl
michael@0 1593 */
michael@0 1594 deleteProperty: function UpdatePatch_deleteProperty(name) {
michael@0 1595 if (name in this._properties)
michael@0 1596 this._properties[name].present = false;
michael@0 1597 else
michael@0 1598 throw Cr.NS_ERROR_FAILURE;
michael@0 1599 },
michael@0 1600
michael@0 1601 /**
michael@0 1602 * See nsIPropertyBag.idl
michael@0 1603 */
michael@0 1604 get enumerator() {
michael@0 1605 var properties = [];
michael@0 1606 for (var p in this._properties)
michael@0 1607 properties.push(this._properties[p].data);
michael@0 1608 return new ArrayEnumerator(properties);
michael@0 1609 },
michael@0 1610
michael@0 1611 /**
michael@0 1612 * See nsIPropertyBag.idl
michael@0 1613 */
michael@0 1614 getProperty: function UpdatePatch_getProperty(name) {
michael@0 1615 if (name in this._properties &&
michael@0 1616 this._properties[name].present)
michael@0 1617 return this._properties[name].data;
michael@0 1618 throw Cr.NS_ERROR_FAILURE;
michael@0 1619 },
michael@0 1620
michael@0 1621 /**
michael@0 1622 * Returns whether or not the update.status file for this patch exists at the
michael@0 1623 * appropriate location.
michael@0 1624 */
michael@0 1625 get statusFileExists() {
michael@0 1626 var statusFile = getUpdatesDir();
michael@0 1627 statusFile.append(FILE_UPDATE_STATUS);
michael@0 1628 return statusFile.exists();
michael@0 1629 },
michael@0 1630
michael@0 1631 /**
michael@0 1632 * See nsIUpdateService.idl
michael@0 1633 */
michael@0 1634 get state() {
michael@0 1635 if (this._properties.state)
michael@0 1636 return this._properties.state;
michael@0 1637 return STATE_NONE;
michael@0 1638 },
michael@0 1639 set state(val) {
michael@0 1640 this._properties.state = val;
michael@0 1641 },
michael@0 1642
michael@0 1643 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePatch,
michael@0 1644 Ci.nsIPropertyBag,
michael@0 1645 Ci.nsIWritablePropertyBag])
michael@0 1646 };
michael@0 1647
michael@0 1648 /**
michael@0 1649 * Update
michael@0 1650 * Implements nsIUpdate
michael@0 1651 * @param update
michael@0 1652 * An <update> element to initialize this object with
michael@0 1653 * @throws if the update contains no patches
michael@0 1654 * @constructor
michael@0 1655 */
michael@0 1656 function Update(update) {
michael@0 1657 this._properties = {};
michael@0 1658 this._patches = [];
michael@0 1659 this.isCompleteUpdate = false;
michael@0 1660 this.isOSUpdate = false;
michael@0 1661 this.showPrompt = false;
michael@0 1662 this.showNeverForVersion = false;
michael@0 1663 this.unsupported = false;
michael@0 1664 this.channel = "default";
michael@0 1665 this.promptWaitTime = getPref("getIntPref", PREF_APP_UPDATE_PROMPTWAITTIME, 43200);
michael@0 1666
michael@0 1667 // Null <update>, assume this is a message container and do no
michael@0 1668 // further initialization
michael@0 1669 if (!update)
michael@0 1670 return;
michael@0 1671
michael@0 1672 const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
michael@0 1673 for (var i = 0; i < update.childNodes.length; ++i) {
michael@0 1674 var patchElement = update.childNodes.item(i);
michael@0 1675 if (patchElement.nodeType != ELEMENT_NODE ||
michael@0 1676 patchElement.localName != "patch")
michael@0 1677 continue;
michael@0 1678
michael@0 1679 patchElement.QueryInterface(Ci.nsIDOMElement);
michael@0 1680 try {
michael@0 1681 var patch = new UpdatePatch(patchElement);
michael@0 1682 } catch (e) {
michael@0 1683 continue;
michael@0 1684 }
michael@0 1685 this._patches.push(patch);
michael@0 1686 }
michael@0 1687
michael@0 1688 if (update.hasAttribute("unsupported"))
michael@0 1689 this.unsupported = ("true" == update.getAttribute("unsupported"));
michael@0 1690 else if (update.hasAttribute("minSupportedOSVersion")) {
michael@0 1691 let minOSVersion = update.getAttribute("minSupportedOSVersion");
michael@0 1692 try {
michael@0 1693 let osVersion = Services.sysinfo.getProperty("version");
michael@0 1694 this.unsupported = (Services.vc.compare(osVersion, minOSVersion) < 0);
michael@0 1695 } catch (e) {}
michael@0 1696 }
michael@0 1697
michael@0 1698 if (this._patches.length == 0 && !this.unsupported)
michael@0 1699 throw Cr.NS_ERROR_ILLEGAL_VALUE;
michael@0 1700
michael@0 1701 // Fallback to the behavior prior to bug 530872 if the update does not have an
michael@0 1702 // appVersion attribute.
michael@0 1703 if (!update.hasAttribute("appVersion")) {
michael@0 1704 if (update.getAttribute("type") == "major") {
michael@0 1705 if (update.hasAttribute("detailsURL")) {
michael@0 1706 this.billboardURL = update.getAttribute("detailsURL");
michael@0 1707 this.showPrompt = true;
michael@0 1708 this.showNeverForVersion = true;
michael@0 1709 }
michael@0 1710 }
michael@0 1711 }
michael@0 1712
michael@0 1713 for (var i = 0; i < update.attributes.length; ++i) {
michael@0 1714 var attr = update.attributes.item(i);
michael@0 1715 attr.QueryInterface(Ci.nsIDOMAttr);
michael@0 1716 if (attr.value == "undefined")
michael@0 1717 continue;
michael@0 1718 else if (attr.name == "detailsURL")
michael@0 1719 this._detailsURL = attr.value;
michael@0 1720 else if (attr.name == "extensionVersion") {
michael@0 1721 // Prevent extensionVersion from replacing appVersion if appVersion is
michael@0 1722 // present in the update xml.
michael@0 1723 if (!this.appVersion)
michael@0 1724 this.appVersion = attr.value;
michael@0 1725 }
michael@0 1726 else if (attr.name == "installDate" && attr.value)
michael@0 1727 this.installDate = parseInt(attr.value);
michael@0 1728 else if (attr.name == "isCompleteUpdate")
michael@0 1729 this.isCompleteUpdate = attr.value == "true";
michael@0 1730 else if (attr.name == "isSecurityUpdate")
michael@0 1731 this.isSecurityUpdate = attr.value == "true";
michael@0 1732 else if (attr.name == "isOSUpdate")
michael@0 1733 this.isOSUpdate = attr.value == "true";
michael@0 1734 else if (attr.name == "showNeverForVersion")
michael@0 1735 this.showNeverForVersion = attr.value == "true";
michael@0 1736 else if (attr.name == "showPrompt")
michael@0 1737 this.showPrompt = attr.value == "true";
michael@0 1738 else if (attr.name == "promptWaitTime")
michael@0 1739 {
michael@0 1740 if(!isNaN(attr.value))
michael@0 1741 this.promptWaitTime = parseInt(attr.value);
michael@0 1742 }
michael@0 1743 else if (attr.name == "version") {
michael@0 1744 // Prevent version from replacing displayVersion if displayVersion is
michael@0 1745 // present in the update xml.
michael@0 1746 if (!this.displayVersion)
michael@0 1747 this.displayVersion = attr.value;
michael@0 1748 }
michael@0 1749 else if (attr.name != "unsupported") {
michael@0 1750 this[attr.name] = attr.value;
michael@0 1751
michael@0 1752 switch (attr.name) {
michael@0 1753 case "appVersion":
michael@0 1754 case "billboardURL":
michael@0 1755 case "buildID":
michael@0 1756 case "channel":
michael@0 1757 case "displayVersion":
michael@0 1758 case "licenseURL":
michael@0 1759 case "name":
michael@0 1760 case "platformVersion":
michael@0 1761 case "previousAppVersion":
michael@0 1762 case "serviceURL":
michael@0 1763 case "statusText":
michael@0 1764 case "type":
michael@0 1765 break;
michael@0 1766 default:
michael@0 1767 // Save custom attributes when serializing to the local xml file but
michael@0 1768 // don't use this method for the expected attributes which are already
michael@0 1769 // handled in serialize.
michael@0 1770 this.setProperty(attr.name, attr.value);
michael@0 1771 break;
michael@0 1772 };
michael@0 1773 }
michael@0 1774 }
michael@0 1775
michael@0 1776 // Set the initial value with the current time when it doesn't already have a
michael@0 1777 // value or the value is already set to 0 (bug 316328).
michael@0 1778 if (!this.installDate && this.installDate != 0)
michael@0 1779 this.installDate = (new Date()).getTime();
michael@0 1780
michael@0 1781 // The Update Name is either the string provided by the <update> element, or
michael@0 1782 // the string: "<App Name> <Update App Version>"
michael@0 1783 var name = "";
michael@0 1784 if (update.hasAttribute("name"))
michael@0 1785 name = update.getAttribute("name");
michael@0 1786 else {
michael@0 1787 var brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES);
michael@0 1788 var appName = brandBundle.GetStringFromName("brandShortName");
michael@0 1789 name = gUpdateBundle.formatStringFromName("updateName",
michael@0 1790 [appName, this.displayVersion], 2);
michael@0 1791 }
michael@0 1792 this.name = name;
michael@0 1793 }
michael@0 1794 Update.prototype = {
michael@0 1795 /**
michael@0 1796 * See nsIUpdateService.idl
michael@0 1797 */
michael@0 1798 get patchCount() {
michael@0 1799 return this._patches.length;
michael@0 1800 },
michael@0 1801
michael@0 1802 /**
michael@0 1803 * See nsIUpdateService.idl
michael@0 1804 */
michael@0 1805 getPatchAt: function Update_getPatchAt(index) {
michael@0 1806 return this._patches[index];
michael@0 1807 },
michael@0 1808
michael@0 1809 /**
michael@0 1810 * See nsIUpdateService.idl
michael@0 1811 *
michael@0 1812 * We use a copy of the state cached on this object in |_state| only when
michael@0 1813 * there is no selected patch, i.e. in the case when we could not load
michael@0 1814 * |.activeUpdate| from the update manager for some reason but still have
michael@0 1815 * the update.status file to work with.
michael@0 1816 */
michael@0 1817 _state: "",
michael@0 1818 set state(state) {
michael@0 1819 if (this.selectedPatch)
michael@0 1820 this.selectedPatch.state = state;
michael@0 1821 this._state = state;
michael@0 1822 return state;
michael@0 1823 },
michael@0 1824 get state() {
michael@0 1825 if (this.selectedPatch)
michael@0 1826 return this.selectedPatch.state;
michael@0 1827 return this._state;
michael@0 1828 },
michael@0 1829
michael@0 1830 /**
michael@0 1831 * See nsIUpdateService.idl
michael@0 1832 */
michael@0 1833 errorCode: 0,
michael@0 1834
michael@0 1835 /**
michael@0 1836 * See nsIUpdateService.idl
michael@0 1837 */
michael@0 1838 get selectedPatch() {
michael@0 1839 for (var i = 0; i < this.patchCount; ++i) {
michael@0 1840 if (this._patches[i].selected)
michael@0 1841 return this._patches[i];
michael@0 1842 }
michael@0 1843 return null;
michael@0 1844 },
michael@0 1845
michael@0 1846 /**
michael@0 1847 * See nsIUpdateService.idl
michael@0 1848 */
michael@0 1849 get detailsURL() {
michael@0 1850 if (!this._detailsURL) {
michael@0 1851 try {
michael@0 1852 // Try using a default details URL supplied by the distribution
michael@0 1853 // if the update XML does not supply one.
michael@0 1854 return Services.urlFormatter.formatURLPref(PREF_APP_UPDATE_URL_DETAILS);
michael@0 1855 }
michael@0 1856 catch (e) {
michael@0 1857 }
michael@0 1858 }
michael@0 1859 return this._detailsURL || "";
michael@0 1860 },
michael@0 1861
michael@0 1862 /**
michael@0 1863 * See nsIUpdateService.idl
michael@0 1864 */
michael@0 1865 serialize: function Update_serialize(updates) {
michael@0 1866 var update = updates.createElementNS(URI_UPDATE_NS, "update");
michael@0 1867 update.setAttribute("appVersion", this.appVersion);
michael@0 1868 update.setAttribute("buildID", this.buildID);
michael@0 1869 update.setAttribute("channel", this.channel);
michael@0 1870 update.setAttribute("displayVersion", this.displayVersion);
michael@0 1871 // for backwards compatibility in case the user downgrades
michael@0 1872 update.setAttribute("extensionVersion", this.appVersion);
michael@0 1873 update.setAttribute("installDate", this.installDate);
michael@0 1874 update.setAttribute("isCompleteUpdate", this.isCompleteUpdate);
michael@0 1875 update.setAttribute("isOSUpdate", this.isOSUpdate);
michael@0 1876 update.setAttribute("name", this.name);
michael@0 1877 update.setAttribute("serviceURL", this.serviceURL);
michael@0 1878 update.setAttribute("showNeverForVersion", this.showNeverForVersion);
michael@0 1879 update.setAttribute("showPrompt", this.showPrompt);
michael@0 1880 update.setAttribute("promptWaitTime", this.promptWaitTime);
michael@0 1881 update.setAttribute("type", this.type);
michael@0 1882 // for backwards compatibility in case the user downgrades
michael@0 1883 update.setAttribute("version", this.displayVersion);
michael@0 1884
michael@0 1885 // Optional attributes
michael@0 1886 if (this.billboardURL)
michael@0 1887 update.setAttribute("billboardURL", this.billboardURL);
michael@0 1888 if (this.detailsURL)
michael@0 1889 update.setAttribute("detailsURL", this.detailsURL);
michael@0 1890 if (this.licenseURL)
michael@0 1891 update.setAttribute("licenseURL", this.licenseURL);
michael@0 1892 if (this.platformVersion)
michael@0 1893 update.setAttribute("platformVersion", this.platformVersion);
michael@0 1894 if (this.previousAppVersion)
michael@0 1895 update.setAttribute("previousAppVersion", this.previousAppVersion);
michael@0 1896 if (this.statusText)
michael@0 1897 update.setAttribute("statusText", this.statusText);
michael@0 1898 if (this.unsupported)
michael@0 1899 update.setAttribute("unsupported", this.unsupported);
michael@0 1900 updates.documentElement.appendChild(update);
michael@0 1901
michael@0 1902 for (var p in this._properties) {
michael@0 1903 if (this._properties[p].present)
michael@0 1904 update.setAttribute(p, this._properties[p].data);
michael@0 1905 }
michael@0 1906
michael@0 1907 for (var i = 0; i < this.patchCount; ++i)
michael@0 1908 update.appendChild(this.getPatchAt(i).serialize(updates));
michael@0 1909
michael@0 1910 return update;
michael@0 1911 },
michael@0 1912
michael@0 1913 /**
michael@0 1914 * A hash of custom properties
michael@0 1915 */
michael@0 1916 _properties: null,
michael@0 1917
michael@0 1918 /**
michael@0 1919 * See nsIWritablePropertyBag.idl
michael@0 1920 */
michael@0 1921 setProperty: function Update_setProperty(name, value) {
michael@0 1922 this._properties[name] = { data: value, present: true };
michael@0 1923 },
michael@0 1924
michael@0 1925 /**
michael@0 1926 * See nsIWritablePropertyBag.idl
michael@0 1927 */
michael@0 1928 deleteProperty: function Update_deleteProperty(name) {
michael@0 1929 if (name in this._properties)
michael@0 1930 this._properties[name].present = false;
michael@0 1931 else
michael@0 1932 throw Cr.NS_ERROR_FAILURE;
michael@0 1933 },
michael@0 1934
michael@0 1935 /**
michael@0 1936 * See nsIPropertyBag.idl
michael@0 1937 */
michael@0 1938 get enumerator() {
michael@0 1939 var properties = [];
michael@0 1940 for (var p in this._properties)
michael@0 1941 properties.push(this._properties[p].data);
michael@0 1942 return new ArrayEnumerator(properties);
michael@0 1943 },
michael@0 1944
michael@0 1945 /**
michael@0 1946 * See nsIPropertyBag.idl
michael@0 1947 */
michael@0 1948 getProperty: function Update_getProperty(name) {
michael@0 1949 if (name in this._properties && this._properties[name].present)
michael@0 1950 return this._properties[name].data;
michael@0 1951 throw Cr.NS_ERROR_FAILURE;
michael@0 1952 },
michael@0 1953
michael@0 1954 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdate,
michael@0 1955 Ci.nsIPropertyBag,
michael@0 1956 Ci.nsIWritablePropertyBag])
michael@0 1957 };
michael@0 1958
michael@0 1959 const UpdateServiceFactory = {
michael@0 1960 _instance: null,
michael@0 1961 createInstance: function (outer, iid) {
michael@0 1962 if (outer != null)
michael@0 1963 throw Cr.NS_ERROR_NO_AGGREGATION;
michael@0 1964 return this._instance == null ? this._instance = new UpdateService() :
michael@0 1965 this._instance;
michael@0 1966 }
michael@0 1967 };
michael@0 1968
michael@0 1969 /**
michael@0 1970 * UpdateService
michael@0 1971 * A Service for managing the discovery and installation of software updates.
michael@0 1972 * @constructor
michael@0 1973 */
michael@0 1974 function UpdateService() {
michael@0 1975 LOG("Creating UpdateService");
michael@0 1976 Services.obs.addObserver(this, "xpcom-shutdown", false);
michael@0 1977 Services.prefs.addObserver(PREF_APP_UPDATE_LOG, this, false);
michael@0 1978 #ifdef MOZ_WIDGET_GONK
michael@0 1979 // PowerManagerService::SyncProfile (which is called for Reboot, PowerOff
michael@0 1980 // and Restart) sends the profile-change-net-teardown event. We can then
michael@0 1981 // pause the download in a similar manner to xpcom-shutdown.
michael@0 1982 Services.obs.addObserver(this, "profile-change-net-teardown", false);
michael@0 1983 #endif
michael@0 1984 }
michael@0 1985
michael@0 1986 UpdateService.prototype = {
michael@0 1987 /**
michael@0 1988 * The downloader we are using to download updates. There is only ever one of
michael@0 1989 * these.
michael@0 1990 */
michael@0 1991 _downloader: null,
michael@0 1992
michael@0 1993 /**
michael@0 1994 * Incompatible add-on count.
michael@0 1995 */
michael@0 1996 _incompatAddonsCount: 0,
michael@0 1997
michael@0 1998 /**
michael@0 1999 * Whether or not the service registered the "online" observer.
michael@0 2000 */
michael@0 2001 _registeredOnlineObserver: false,
michael@0 2002
michael@0 2003 /**
michael@0 2004 * The current number of consecutive socket errors
michael@0 2005 */
michael@0 2006 _consecutiveSocketErrors: 0,
michael@0 2007
michael@0 2008 /**
michael@0 2009 * A timer used to retry socket errors
michael@0 2010 */
michael@0 2011 _retryTimer: null,
michael@0 2012
michael@0 2013 /**
michael@0 2014 * Whether or not a background update check was initiated by the
michael@0 2015 * application update timer notification.
michael@0 2016 */
michael@0 2017 _isNotify: true,
michael@0 2018
michael@0 2019 /**
michael@0 2020 * Handle Observer Service notifications
michael@0 2021 * @param subject
michael@0 2022 * The subject of the notification
michael@0 2023 * @param topic
michael@0 2024 * The notification name
michael@0 2025 * @param data
michael@0 2026 * Additional data
michael@0 2027 */
michael@0 2028 observe: function AUS_observe(subject, topic, data) {
michael@0 2029 switch (topic) {
michael@0 2030 case "post-update-processing":
michael@0 2031 // Clean up any extant updates
michael@0 2032 this._postUpdateProcessing();
michael@0 2033 break;
michael@0 2034 case "network:offline-status-changed":
michael@0 2035 this._offlineStatusChanged(data);
michael@0 2036 break;
michael@0 2037 case "nsPref:changed":
michael@0 2038 if (data == PREF_APP_UPDATE_LOG) {
michael@0 2039 gLogEnabled = getPref("getBoolPref", PREF_APP_UPDATE_LOG, false);
michael@0 2040 }
michael@0 2041 break;
michael@0 2042 #ifdef MOZ_WIDGET_GONK
michael@0 2043 case "profile-change-net-teardown": // fall thru
michael@0 2044 #endif
michael@0 2045 case "xpcom-shutdown":
michael@0 2046 Services.obs.removeObserver(this, topic);
michael@0 2047 Services.prefs.removeObserver(PREF_APP_UPDATE_LOG, this);
michael@0 2048
michael@0 2049 if (this._retryTimer) {
michael@0 2050 this._retryTimer.cancel();
michael@0 2051 }
michael@0 2052
michael@0 2053 this.pauseDownload();
michael@0 2054 // Prevent leaking the downloader (bug 454964)
michael@0 2055 this._downloader = null;
michael@0 2056 break;
michael@0 2057 }
michael@0 2058 },
michael@0 2059
michael@0 2060 /**
michael@0 2061 * The following needs to happen during the post-update-processing
michael@0 2062 * notification from nsUpdateServiceStub.js:
michael@0 2063 * 1. post update processing
michael@0 2064 * 2. resume of a download that was in progress during a previous session
michael@0 2065 * 3. start of a complete update download after the failure to apply a partial
michael@0 2066 * update
michael@0 2067 */
michael@0 2068
michael@0 2069 /**
michael@0 2070 * Perform post-processing on updates lingering in the updates directory
michael@0 2071 * from a previous application session - either report install failures (and
michael@0 2072 * optionally attempt to fetch a different version if appropriate) or
michael@0 2073 * notify the user of install success.
michael@0 2074 */
michael@0 2075 _postUpdateProcessing: function AUS__postUpdateProcessing() {
michael@0 2076 // canCheckForUpdates will return false when metro-only updates are disabled
michael@0 2077 // from within metro. In that case we still want _postUpdateProcessing to
michael@0 2078 // run. gMetroUpdatesEnabled returns true on non Windows 8 platforms.
michael@0 2079 // We want _postUpdateProcessing to run so that it will update the history
michael@0 2080 // XML. Without updating the history XML, the about flyout will continue to
michael@0 2081 // have the "Restart to Apply Update" button (history xml indicates update
michael@0 2082 // is applied).
michael@0 2083 // TODO: I think this whole if-block should be removed since updates can
michael@0 2084 // always be applied via the about dialog, we should be running post update
michael@0 2085 // in those cases.
michael@0 2086 if (!this.canCheckForUpdates && gMetroUpdatesEnabled) {
michael@0 2087 LOG("UpdateService:_postUpdateProcessing - unable to check for " +
michael@0 2088 "updates... returning early");
michael@0 2089 return;
michael@0 2090 }
michael@0 2091
michael@0 2092 if (!this.canApplyUpdates) {
michael@0 2093 LOG("UpdateService:_postUpdateProcessing - unable to apply " +
michael@0 2094 "updates... returning early");
michael@0 2095 // If the update is present in the update directly somehow,
michael@0 2096 // it would prevent us from notifying the user of futher updates.
michael@0 2097 cleanupActiveUpdate();
michael@0 2098 return;
michael@0 2099 }
michael@0 2100
michael@0 2101 var status = readStatusFile(getUpdatesDir());
michael@0 2102 // STATE_NONE status means that the update.status file is present but a
michael@0 2103 // background download error occurred.
michael@0 2104 if (status == STATE_NONE) {
michael@0 2105 LOG("UpdateService:_postUpdateProcessing - no status, no update");
michael@0 2106 cleanupActiveUpdate();
michael@0 2107 return;
michael@0 2108 }
michael@0 2109
michael@0 2110 var um = Cc["@mozilla.org/updates/update-manager;1"].
michael@0 2111 getService(Ci.nsIUpdateManager);
michael@0 2112
michael@0 2113 #ifdef MOZ_WIDGET_GONK
michael@0 2114 // This code is called very early in the boot process, before we've even
michael@0 2115 // had a chance to setup the UI so we can give feedback to the user.
michael@0 2116 //
michael@0 2117 // Since the download may be occuring over a link which has associated
michael@0 2118 // cost, we want to require user-consent before resuming the download.
michael@0 2119 // Also, applying an already downloaded update now is undesireable,
michael@0 2120 // since the phone will look dead while the update is being applied.
michael@0 2121 // Applying the update can take several minutes. Instead we wait until
michael@0 2122 // the UI is initialized so it is possible to give feedback to and get
michael@0 2123 // consent to update from the user.
michael@0 2124 if (isInterruptedUpdate(status)) {
michael@0 2125 LOG("UpdateService:_postUpdateProcessing - interrupted update detected - wait for user consent");
michael@0 2126 return;
michael@0 2127 }
michael@0 2128 #endif
michael@0 2129
michael@0 2130 var update = um.activeUpdate;
michael@0 2131
michael@0 2132 if (status == STATE_DOWNLOADING) {
michael@0 2133 LOG("UpdateService:_postUpdateProcessing - patch found in downloading " +
michael@0 2134 "state");
michael@0 2135 if (update && update.state != STATE_SUCCEEDED) {
michael@0 2136 // Resume download
michael@0 2137 var status = this.downloadUpdate(update, true);
michael@0 2138 if (status == STATE_NONE)
michael@0 2139 cleanupActiveUpdate();
michael@0 2140 }
michael@0 2141 return;
michael@0 2142 }
michael@0 2143
michael@0 2144 if (status == STATE_APPLYING) {
michael@0 2145 // This indicates that the background updater service is in either of the
michael@0 2146 // following two states:
michael@0 2147 // 1. It is in the process of applying an update in the background, and
michael@0 2148 // we just happen to be racing against that.
michael@0 2149 // 2. It has failed to apply an update for some reason, and we hit this
michael@0 2150 // case because the updater process has set the update status to
michael@0 2151 // applying, but has never finished.
michael@0 2152 // In order to differentiate between these two states, we look at the
michael@0 2153 // state field of the update object. If it's "pending", then we know
michael@0 2154 // that this is the first time we're hitting this case, so we switch
michael@0 2155 // that state to "applying" and we just wait and hope for the best.
michael@0 2156 // If it's "applying", we know that we've already been here once, so
michael@0 2157 // we really want to start from a clean state.
michael@0 2158 if (update &&
michael@0 2159 (update.state == STATE_PENDING || update.state == STATE_PENDING_SVC)) {
michael@0 2160 LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
michael@0 2161 "state for the first time");
michael@0 2162 update.state = STATE_APPLYING;
michael@0 2163 um.saveUpdates();
michael@0 2164 } else { // We get here even if we don't have an update object
michael@0 2165 LOG("UpdateService:_postUpdateProcessing - patch found in applying " +
michael@0 2166 "state for the second time");
michael@0 2167 cleanupActiveUpdate();
michael@0 2168 }
michael@0 2169 return;
michael@0 2170 }
michael@0 2171
michael@0 2172 #ifdef MOZ_WIDGET_GONK
michael@0 2173 // The update is only applied but not selected to be installed
michael@0 2174 if (status == STATE_APPLIED && update && update.isOSUpdate) {
michael@0 2175 LOG("UpdateService:_postUpdateProcessing - update staged as applied found");
michael@0 2176 return;
michael@0 2177 }
michael@0 2178
michael@0 2179 if (status == STATE_APPLIED_OS && update && update.isOSUpdate) {
michael@0 2180 // In gonk, we need to check for OS update status after startup, since
michael@0 2181 // the recovery partition won't write to update.status for us
michael@0 2182 var recoveryService = Cc["@mozilla.org/recovery-service;1"].
michael@0 2183 getService(Ci.nsIRecoveryService);
michael@0 2184
michael@0 2185 var fotaStatus = recoveryService.getFotaUpdateStatus();
michael@0 2186 switch (fotaStatus) {
michael@0 2187 case Ci.nsIRecoveryService.FOTA_UPDATE_SUCCESS:
michael@0 2188 status = STATE_SUCCEEDED;
michael@0 2189 break;
michael@0 2190 case Ci.nsIRecoveryService.FOTA_UPDATE_FAIL:
michael@0 2191 status = STATE_FAILED + ": " + FOTA_GENERAL_ERROR;
michael@0 2192 break;
michael@0 2193 case Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN:
michael@0 2194 default:
michael@0 2195 status = STATE_FAILED + ": " + FOTA_UNKNOWN_ERROR;
michael@0 2196 break;
michael@0 2197 }
michael@0 2198 }
michael@0 2199 #endif
michael@0 2200
michael@0 2201 if (!update) {
michael@0 2202 if (status != STATE_SUCCEEDED) {
michael@0 2203 LOG("UpdateService:_postUpdateProcessing - previous patch failed " +
michael@0 2204 "and no patch available");
michael@0 2205 cleanupActiveUpdate();
michael@0 2206 return;
michael@0 2207 }
michael@0 2208 update = new Update(null);
michael@0 2209 }
michael@0 2210
michael@0 2211 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 2212 createInstance(Ci.nsIUpdatePrompt);
michael@0 2213
michael@0 2214 update.state = status;
michael@0 2215 this._sendStatusCodeTelemetryPing(status);
michael@0 2216
michael@0 2217 if (status == STATE_SUCCEEDED) {
michael@0 2218 update.statusText = gUpdateBundle.GetStringFromName("installSuccess");
michael@0 2219
michael@0 2220 // Update the patch's metadata.
michael@0 2221 um.activeUpdate = update;
michael@0 2222 Services.prefs.setBoolPref(PREF_APP_UPDATE_POSTUPDATE, true);
michael@0 2223 prompter.showUpdateInstalled();
michael@0 2224
michael@0 2225 // Done with this update. Clean it up.
michael@0 2226 cleanupActiveUpdate();
michael@0 2227 }
michael@0 2228 else {
michael@0 2229 // If we hit an error, then the error code will be included in the status
michael@0 2230 // string following a colon and a space. If we had an I/O error, then we
michael@0 2231 // assume that the patch is not invalid, and we re-stage the patch so that
michael@0 2232 // it can be attempted again the next time we restart. This will leave a
michael@0 2233 // space at the beginning of the error code when there is a failure which
michael@0 2234 // will be removed by using parseInt below. This prevents panic which has
michael@0 2235 // occurred numerous times previously (see bug 569642 comment #9 for one
michael@0 2236 // example) when testing releases due to forgetting to include the space.
michael@0 2237 var ary = status.split(":");
michael@0 2238 update.state = ary[0];
michael@0 2239 if (update.state == STATE_FAILED && ary[1]) {
michael@0 2240 if (handleUpdateFailure(update, ary[1])) {
michael@0 2241 return;
michael@0 2242 }
michael@0 2243 }
michael@0 2244
michael@0 2245 // Something went wrong with the patch application process.
michael@0 2246 handleFallbackToCompleteUpdate(update, false);
michael@0 2247
michael@0 2248 prompter.showUpdateError(update);
michael@0 2249 }
michael@0 2250
michael@0 2251 // Now trash the MozUpdater folders which staged/bgupdates uses.
michael@0 2252 cleanUpMozUpdaterDirs();
michael@0 2253 },
michael@0 2254
michael@0 2255 /**
michael@0 2256 * Submit a telemetry ping with the boolean value of a pref for a histogram
michael@0 2257 *
michael@0 2258 * @param pref
michael@0 2259 * The preference to report
michael@0 2260 * @param histogram
michael@0 2261 * The histogram ID to report to
michael@0 2262 */
michael@0 2263 _sendBoolPrefTelemetryPing: function AUS__boolTelemetryPing(pref, histogram) {
michael@0 2264 try {
michael@0 2265 // The getPref is already wrapped in a try/catch but we never
michael@0 2266 // want telemetry pings breaking app update so we just put it
michael@0 2267 // inside the try to be safe.
michael@0 2268 let val = getPref("getBoolPref", pref, false);
michael@0 2269 Services.telemetry.getHistogramById(histogram).add(+val);
michael@0 2270 } catch(e) {
michael@0 2271 // Don't allow any exception to be propagated.
michael@0 2272 Cu.reportError(e);
michael@0 2273 }
michael@0 2274 },
michael@0 2275
michael@0 2276 #ifdef XP_WIN
michael@0 2277 /**
michael@0 2278 * Submit a telemetry ping with a boolean value which indicates if the service
michael@0 2279 * is installed.
michael@0 2280 * Also submits a telemetry ping with a boolean value which indicates if the
michael@0 2281 * service was at some point installed, but is now uninstalled.
michael@0 2282 */
michael@0 2283 _sendServiceInstalledTelemetryPing: function AUS__svcInstallTelemetryPing() {
michael@0 2284 let installed = isServiceInstalled(); // Is the service installed?
michael@0 2285 let attempted = 0;
michael@0 2286 try {
michael@0 2287 let wrk = Cc["@mozilla.org/windows-registry-key;1"].
michael@0 2288 createInstance(Ci.nsIWindowsRegKey);
michael@0 2289 wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
michael@0 2290 "SOFTWARE\\Mozilla\\MaintenanceService",
michael@0 2291 wrk.ACCESS_READ | wrk.WOW64_64);
michael@0 2292 // Was the service at some point installed, but is now uninstalled?
michael@0 2293 attempted = wrk.readIntValue("Attempted");
michael@0 2294 wrk.close();
michael@0 2295 } catch(e) {
michael@0 2296 }
michael@0 2297 try {
michael@0 2298 let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_INSTALLED");
michael@0 2299 h.add(Number(installed));
michael@0 2300 } catch(e) {
michael@0 2301 // Don't allow any exception to be propagated.
michael@0 2302 Cu.reportError(e);
michael@0 2303 }
michael@0 2304 try {
michael@0 2305 let h = Services.telemetry.getHistogramById("UPDATER_SERVICE_MANUALLY_UNINSTALLED");
michael@0 2306 h.add(!installed && attempted ? 1 : 0);
michael@0 2307 } catch(e) {
michael@0 2308 // Don't allow any exception to be propagated.
michael@0 2309 Cu.reportError(e);
michael@0 2310 }
michael@0 2311 },
michael@0 2312 #endif
michael@0 2313
michael@0 2314 /**
michael@0 2315 * Submit a telemetry ping with the int value of a pref for a histogram
michael@0 2316 *
michael@0 2317 * @param pref
michael@0 2318 * The preference to report
michael@0 2319 * @param histogram
michael@0 2320 * The histogram ID to report to
michael@0 2321 */
michael@0 2322 _sendIntPrefTelemetryPing: function AUS__intTelemetryPing(pref, histogram) {
michael@0 2323 try {
michael@0 2324 // The getPref is already wrapped in a try/catch but we never
michael@0 2325 // want telemetry pings breaking app update so we just put it
michael@0 2326 // inside the try to be safe.
michael@0 2327 let val = getPref("getIntPref", pref, 0);
michael@0 2328 Services.telemetry.getHistogramById(histogram).add(val);
michael@0 2329 } catch(e) {
michael@0 2330 // Don't allow any exception to be propagated.
michael@0 2331 Cu.reportError(e);
michael@0 2332 }
michael@0 2333 },
michael@0 2334
michael@0 2335
michael@0 2336 /**
michael@0 2337 * Submit the results of applying the update via telemetry.
michael@0 2338 *
michael@0 2339 * @param status
michael@0 2340 * The status of the update as read from the update.status file
michael@0 2341 */
michael@0 2342 _sendStatusCodeTelemetryPing: function AUS__statusTelemetryPing(status) {
michael@0 2343 try {
michael@0 2344 let parts = status.split(":");
michael@0 2345 if ((parts.length == 1 && status != STATE_SUCCEEDED) ||
michael@0 2346 (parts.length > 1 && parts[0] != STATE_FAILED)) {
michael@0 2347 // Should also report STATE_DOWNLOAD_FAILED
michael@0 2348 return;
michael@0 2349 }
michael@0 2350 let result = 0; // 0 means success
michael@0 2351 if (parts.length > 1) {
michael@0 2352 result = parseInt(parts[1]) || INVALID_UPDATER_STATUS_CODE;
michael@0 2353 }
michael@0 2354 Services.telemetry.getHistogramById("UPDATER_STATUS_CODES").add(result);
michael@0 2355 } catch(e) {
michael@0 2356 // Don't allow any exception to be propagated.
michael@0 2357 Cu.reportError(e);
michael@0 2358 }
michael@0 2359 },
michael@0 2360
michael@0 2361 /**
michael@0 2362 * Submit the interval in days since the last notification for this background
michael@0 2363 * update check.
michael@0 2364 */
michael@0 2365 _sendLastNotifyIntervalPing: function AUS__notifyIntervalPing() {
michael@0 2366 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LASTUPDATETIME)) {
michael@0 2367 let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL";
michael@0 2368 let lastUpdateTimeSeconds = getPref("getIntPref",
michael@0 2369 PREF_APP_UPDATE_LASTUPDATETIME, 0);
michael@0 2370 if (lastUpdateTimeSeconds) {
michael@0 2371 let currentTimeSeconds = Math.round(Date.now() / 1000);
michael@0 2372 if (lastUpdateTimeSeconds > currentTimeSeconds) {
michael@0 2373 try {
michael@0 2374 Services.telemetry.
michael@0 2375 getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
michael@0 2376 add(1);
michael@0 2377 } catch(e) {
michael@0 2378 Cu.reportError(e);
michael@0 2379 }
michael@0 2380 }
michael@0 2381 else {
michael@0 2382 let intervalDays = (currentTimeSeconds - lastUpdateTimeSeconds) /
michael@0 2383 (60 * 60 * 24);
michael@0 2384 try {
michael@0 2385 Services.telemetry.
michael@0 2386 getHistogramById("UPDATER_INVALID_LASTUPDATETIME_" + idSuffix).
michael@0 2387 add(0);
michael@0 2388 Services.telemetry.
michael@0 2389 getHistogramById("UPDATER_LAST_NOTIFY_INTERVAL_DAYS_" + idSuffix).
michael@0 2390 add(intervalDays);
michael@0 2391 } catch(e) {
michael@0 2392 Cu.reportError(e);
michael@0 2393 }
michael@0 2394 }
michael@0 2395 }
michael@0 2396 }
michael@0 2397 },
michael@0 2398
michael@0 2399 /**
michael@0 2400 * Submit the result for the background update check.
michael@0 2401 *
michael@0 2402 * @param code
michael@0 2403 * An integer value as defined by the PING_BGUC_* constants.
michael@0 2404 */
michael@0 2405 _backgroundUpdateCheckCodePing: function AUS__backgroundUpdateCheckCodePing(code) {
michael@0 2406 try {
michael@0 2407 let idSuffix = this._isNotify ? "NOTIFY" : "EXTERNAL";
michael@0 2408 Services.telemetry.
michael@0 2409 getHistogramById("UPDATER_BACKGROUND_CHECK_CODE_" + idSuffix).add(code);
michael@0 2410 }
michael@0 2411 catch (e) {
michael@0 2412 Cu.reportError(e);
michael@0 2413 }
michael@0 2414 },
michael@0 2415
michael@0 2416 /**
michael@0 2417 * Register an observer when the network comes online, so we can short-circuit
michael@0 2418 * the app.update.interval when there isn't connectivity
michael@0 2419 */
michael@0 2420 _registerOnlineObserver: function AUS__registerOnlineObserver() {
michael@0 2421 if (this._registeredOnlineObserver) {
michael@0 2422 LOG("UpdateService:_registerOnlineObserver - observer already registered");
michael@0 2423 return;
michael@0 2424 }
michael@0 2425
michael@0 2426 LOG("UpdateService:_registerOnlineObserver - waiting for the network to " +
michael@0 2427 "be online, then forcing another check");
michael@0 2428
michael@0 2429 Services.obs.addObserver(this, "network:offline-status-changed", false);
michael@0 2430 this._registeredOnlineObserver = true;
michael@0 2431 },
michael@0 2432
michael@0 2433 /**
michael@0 2434 * Called from the network:offline-status-changed observer.
michael@0 2435 */
michael@0 2436 _offlineStatusChanged: function AUS__offlineStatusChanged(status) {
michael@0 2437 if (status !== "online") {
michael@0 2438 return;
michael@0 2439 }
michael@0 2440
michael@0 2441 Services.obs.removeObserver(this, "network:offline-status-changed");
michael@0 2442 this._registeredOnlineObserver = false;
michael@0 2443
michael@0 2444 LOG("UpdateService:_offlineStatusChanged - network is online, forcing " +
michael@0 2445 "another background check");
michael@0 2446
michael@0 2447 // the background checker is contained in notify
michael@0 2448 this._attemptResume();
michael@0 2449 },
michael@0 2450
michael@0 2451 onCheckComplete: function AUS_onCheckComplete(request, updates, updateCount) {
michael@0 2452 this._selectAndInstallUpdate(updates);
michael@0 2453 },
michael@0 2454
michael@0 2455 onError: function AUS_onError(request, update) {
michael@0 2456 LOG("UpdateService:onError - error during background update. error code: " +
michael@0 2457 update.errorCode + ", status text: " + update.statusText);
michael@0 2458
michael@0 2459 var maxErrors;
michael@0 2460 var errCount;
michael@0 2461 if (update.errorCode == NETWORK_ERROR_OFFLINE) {
michael@0 2462 // Register an online observer to try again
michael@0 2463 this._registerOnlineObserver();
michael@0 2464 this._backgroundUpdateCheckCodePing(PING_BGUC_OFFLINE);
michael@0 2465 return;
michael@0 2466 }
michael@0 2467
michael@0 2468 if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
michael@0 2469 update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE) {
michael@0 2470 errCount = getPref("getIntPref", PREF_APP_UPDATE_CERT_ERRORS, 0);
michael@0 2471 errCount++;
michael@0 2472 Services.prefs.setIntPref(PREF_APP_UPDATE_CERT_ERRORS, errCount);
michael@0 2473 maxErrors = getPref("getIntPref", PREF_APP_UPDATE_CERT_MAXERRORS, 5);
michael@0 2474 }
michael@0 2475 else {
michael@0 2476 update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
michael@0 2477 errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
michael@0 2478 errCount++;
michael@0 2479 Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
michael@0 2480 maxErrors = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS,
michael@0 2481 10);
michael@0 2482 }
michael@0 2483
michael@0 2484 var pingCode;
michael@0 2485 if (errCount >= maxErrors) {
michael@0 2486 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 2487 createInstance(Ci.nsIUpdatePrompt);
michael@0 2488 prompter.showUpdateError(update);
michael@0 2489
michael@0 2490 switch (update.errorCode) {
michael@0 2491 case CERT_ATTR_CHECK_FAILED_NO_UPDATE:
michael@0 2492 pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_NOTIFY;
michael@0 2493 break;
michael@0 2494 case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
michael@0 2495 pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_NOTIFY;
michael@0 2496 break;
michael@0 2497 default:
michael@0 2498 pingCode = PING_BGUC_GENERAL_ERROR_NOTIFY;
michael@0 2499 }
michael@0 2500 }
michael@0 2501 else {
michael@0 2502 switch (update.errorCode) {
michael@0 2503 case CERT_ATTR_CHECK_FAILED_NO_UPDATE:
michael@0 2504 pingCode = PING_BGUC_CERT_ATTR_NO_UPDATE_SILENT;
michael@0 2505 break;
michael@0 2506 case CERT_ATTR_CHECK_FAILED_HAS_UPDATE:
michael@0 2507 pingCode = PING_BGUC_CERT_ATTR_WITH_UPDATE_SILENT;
michael@0 2508 break;
michael@0 2509 default:
michael@0 2510 pingCode = PING_BGUC_GENERAL_ERROR_SILENT;
michael@0 2511 }
michael@0 2512 }
michael@0 2513 this._backgroundUpdateCheckCodePing(pingCode);
michael@0 2514 },
michael@0 2515
michael@0 2516 /**
michael@0 2517 * Called when a connection should be resumed
michael@0 2518 */
michael@0 2519 _attemptResume: function AUS_attemptResume() {
michael@0 2520 LOG("UpdateService:_attemptResume")
michael@0 2521 // If a download is in progress, then resume it.
michael@0 2522 if (this._downloader && this._downloader._patch &&
michael@0 2523 this._downloader._patch.state == STATE_DOWNLOADING &&
michael@0 2524 this._downloader._update) {
michael@0 2525 LOG("UpdateService:_attemptResume - _patch.state: " +
michael@0 2526 this._downloader._patch.state);
michael@0 2527 // Make sure downloading is the state for selectPatch to work correctly
michael@0 2528 writeStatusFile(getUpdatesDir(), STATE_DOWNLOADING);
michael@0 2529 var status = this.downloadUpdate(this._downloader._update,
michael@0 2530 this._downloader.background);
michael@0 2531 LOG("UpdateService:_attemptResume - downloadUpdate status: " + status);
michael@0 2532 if (status == STATE_NONE) {
michael@0 2533 cleanupActiveUpdate();
michael@0 2534 }
michael@0 2535 return;
michael@0 2536 }
michael@0 2537
michael@0 2538 this.backgroundChecker.checkForUpdates(this, false);
michael@0 2539 },
michael@0 2540
michael@0 2541 /**
michael@0 2542 * Notified when a timer fires
michael@0 2543 * @param timer
michael@0 2544 * The timer that fired
michael@0 2545 */
michael@0 2546 notify: function AUS_notify(timer) {
michael@0 2547 // The telemetry below is specific to background notification.
michael@0 2548 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_ENABLED,
michael@0 2549 "UPDATER_UPDATES_ENABLED");
michael@0 2550 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_METRO_ENABLED,
michael@0 2551 "UPDATER_UPDATES_METRO_ENABLED");
michael@0 2552 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_AUTO,
michael@0 2553 "UPDATER_UPDATES_AUTOMATIC");
michael@0 2554 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_STAGING_ENABLED,
michael@0 2555 "UPDATER_STAGE_ENABLED");
michael@0 2556
michael@0 2557 #ifdef XP_WIN
michael@0 2558 this._sendBoolPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ENABLED,
michael@0 2559 "UPDATER_SERVICE_ENABLED");
michael@0 2560 this._sendIntPrefTelemetryPing(PREF_APP_UPDATE_SERVICE_ERRORS,
michael@0 2561 "UPDATER_SERVICE_ERRORS");
michael@0 2562 this._sendServiceInstalledTelemetryPing();
michael@0 2563 #endif
michael@0 2564
michael@0 2565 this._checkForBackgroundUpdates(true);
michael@0 2566 },
michael@0 2567
michael@0 2568 /**
michael@0 2569 * See nsIUpdateService.idl
michael@0 2570 */
michael@0 2571 checkForBackgroundUpdates: function AUS_checkForBackgroundUpdates() {
michael@0 2572 this._checkForBackgroundUpdates(false);
michael@0 2573 },
michael@0 2574
michael@0 2575 /**
michael@0 2576 * Checks for updates in the background.
michael@0 2577 * @param isNotify
michael@0 2578 * Whether or not a background update check was initiated by the
michael@0 2579 * application update timer notification.
michael@0 2580 */
michael@0 2581 _checkForBackgroundUpdates: function AUS__checkForBackgroundUpdates(isNotify) {
michael@0 2582 this._isNotify = isNotify;
michael@0 2583 // From this point on, the telemetry reported differentiates between a call
michael@0 2584 // to notify and a call to checkForBackgroundUpdates so they are reported
michael@0 2585 // separately.
michael@0 2586 this._sendLastNotifyIntervalPing();
michael@0 2587
michael@0 2588 // If a download is in progress or the patch has been staged do nothing.
michael@0 2589 if (this.isDownloading) {
michael@0 2590 this._backgroundUpdateCheckCodePing(PING_BGUC_IS_DOWNLOADING);
michael@0 2591 return;
michael@0 2592 }
michael@0 2593
michael@0 2594 if (this._downloader && this._downloader.patchIsStaged) {
michael@0 2595 this._backgroundUpdateCheckCodePing(PING_BGUC_IS_STAGED);
michael@0 2596 return;
michael@0 2597 }
michael@0 2598
michael@0 2599 // The following checks will return early without notification in the call
michael@0 2600 // to checkForUpdates below. To simplify the background update check ping
michael@0 2601 // their values are checked here.
michael@0 2602 try {
michael@0 2603 if (!this.backgroundChecker.getUpdateURL(false)) {
michael@0 2604 let prefs = Services.prefs;
michael@0 2605 if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE)) {
michael@0 2606 if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL)) {
michael@0 2607 this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_DEFAULT_URL);
michael@0 2608 }
michael@0 2609 else {
michael@0 2610 this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_CUSTOM_URL);
michael@0 2611 }
michael@0 2612 }
michael@0 2613 else {
michael@0 2614 this._backgroundUpdateCheckCodePing(PING_BGUC_INVALID_OVERRIDE_URL);
michael@0 2615 }
michael@0 2616 }
michael@0 2617 else if (!gMetroUpdatesEnabled) {
michael@0 2618 this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
michael@0 2619 }
michael@0 2620 else if (!getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true)) {
michael@0 2621 this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED);
michael@0 2622 }
michael@0 2623 else if (!(gCanCheckForUpdates && hasUpdateMutex())) {
michael@0 2624 this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_CHECK);
michael@0 2625 }
michael@0 2626 else if (!this.backgroundChecker._enabled) {
michael@0 2627 this._backgroundUpdateCheckCodePing(PING_BGUC_DISABLED_FOR_SESSION);
michael@0 2628 }
michael@0 2629 }
michael@0 2630 catch (e) {
michael@0 2631 Cu.reportError(e);
michael@0 2632 }
michael@0 2633
michael@0 2634 this.backgroundChecker.checkForUpdates(this, false);
michael@0 2635 },
michael@0 2636
michael@0 2637 /**
michael@0 2638 * Determine the update from the specified updates that should be offered.
michael@0 2639 * If both valid major and minor updates are available the minor update will
michael@0 2640 * be offered.
michael@0 2641 * @param updates
michael@0 2642 * An array of available nsIUpdate items
michael@0 2643 * @return The nsIUpdate to offer.
michael@0 2644 */
michael@0 2645 selectUpdate: function AUS_selectUpdate(updates) {
michael@0 2646 if (updates.length == 0) {
michael@0 2647 this._backgroundUpdateCheckCodePing(PING_BGUC_NO_UPDATE_FOUND);
michael@0 2648 return null;
michael@0 2649 }
michael@0 2650
michael@0 2651 // The ping for unsupported is sent after the call to showPrompt.
michael@0 2652 if (updates.length == 1 && updates[0].unsupported)
michael@0 2653 return updates[0];
michael@0 2654
michael@0 2655 // Choose the newest of the available minor and major updates.
michael@0 2656 var majorUpdate = null;
michael@0 2657 var minorUpdate = null;
michael@0 2658 var vc = Services.vc;
michael@0 2659 var lastPingCode = PING_BGUC_NO_COMPAT_UPDATE_FOUND;
michael@0 2660
michael@0 2661 updates.forEach(function(aUpdate) {
michael@0 2662 // Ignore updates for older versions of the application and updates for
michael@0 2663 // the same version of the application with the same build ID.
michael@0 2664 #ifdef TOR_BROWSER_UPDATE
michael@0 2665 var compatVersion = TOR_BROWSER_VERSION;
michael@0 2666 #else
michael@0 2667 var compatVersion = Services.appinfo.version;
michael@0 2668 #endif
michael@0 2669 var rc = vc.compare(aUpdate.appVersion, compatVersion);
michael@0 2670 if (rc < 0 || ((rc == 0) &&
michael@0 2671 (aUpdate.buildID == Services.appinfo.appBuildID))) {
michael@0 2672 LOG("UpdateService:selectUpdate - skipping update because the " +
michael@0 2673 "update's application version is less than the current " +
michael@0 2674 "application version");
michael@0 2675 lastPingCode = PING_BGUC_UPDATE_PREVIOUS_VERSION;
michael@0 2676 return;
michael@0 2677 }
michael@0 2678
michael@0 2679 // Skip the update if the user responded with "never" to this update's
michael@0 2680 // application version and the update specifies showNeverForVersion
michael@0 2681 // (see bug 350636).
michael@0 2682 let neverPrefName = PREF_APP_UPDATE_NEVER_BRANCH + aUpdate.appVersion;
michael@0 2683 if (aUpdate.showNeverForVersion &&
michael@0 2684 getPref("getBoolPref", neverPrefName, false)) {
michael@0 2685 LOG("UpdateService:selectUpdate - skipping update because the " +
michael@0 2686 "preference " + neverPrefName + " is true");
michael@0 2687 lastPingCode = PING_BGUC_UPDATE_NEVER_PREF;
michael@0 2688 return;
michael@0 2689 }
michael@0 2690
michael@0 2691 switch (aUpdate.type) {
michael@0 2692 case "major":
michael@0 2693 if (!majorUpdate)
michael@0 2694 majorUpdate = aUpdate;
michael@0 2695 else if (vc.compare(majorUpdate.appVersion, aUpdate.appVersion) <= 0)
michael@0 2696 majorUpdate = aUpdate;
michael@0 2697 break;
michael@0 2698 case "minor":
michael@0 2699 if (!minorUpdate)
michael@0 2700 minorUpdate = aUpdate;
michael@0 2701 else if (vc.compare(minorUpdate.appVersion, aUpdate.appVersion) <= 0)
michael@0 2702 minorUpdate = aUpdate;
michael@0 2703 break;
michael@0 2704 default:
michael@0 2705 LOG("UpdateService:selectUpdate - skipping unknown update type: " +
michael@0 2706 aUpdate.type);
michael@0 2707 lastPingCode = PING_BGUC_UPDATE_INVALID_TYPE;
michael@0 2708 break;
michael@0 2709 }
michael@0 2710 });
michael@0 2711
michael@0 2712 var update = minorUpdate || majorUpdate;
michael@0 2713 if (!update)
michael@0 2714 this._backgroundUpdateCheckCodePing(lastPingCode);
michael@0 2715
michael@0 2716 return update;
michael@0 2717 },
michael@0 2718
michael@0 2719 /**
michael@0 2720 * Reference to the currently selected update for when add-on compatibility
michael@0 2721 * is checked.
michael@0 2722 */
michael@0 2723 _update: null,
michael@0 2724
michael@0 2725 /**
michael@0 2726 * Determine which of the specified updates should be installed and begin the
michael@0 2727 * download/installation process or notify the user about the update.
michael@0 2728 * @param updates
michael@0 2729 * An array of available updates
michael@0 2730 */
michael@0 2731 _selectAndInstallUpdate: function AUS__selectAndInstallUpdate(updates) {
michael@0 2732 // Return early if there's an active update. The user is already aware and
michael@0 2733 // is downloading or performed some user action to prevent notification.
michael@0 2734 var um = Cc["@mozilla.org/updates/update-manager;1"].
michael@0 2735 getService(Ci.nsIUpdateManager);
michael@0 2736 if (um.activeUpdate) {
michael@0 2737 #ifdef MOZ_WIDGET_GONK
michael@0 2738 // For gonk, the user isn't necessarily aware of the update, so we need
michael@0 2739 // to show the prompt to make sure.
michael@0 2740 this._showPrompt(um.activeUpdate);
michael@0 2741 #endif
michael@0 2742 this._backgroundUpdateCheckCodePing(PING_BGUC_HAS_ACTIVEUPDATE);
michael@0 2743 return;
michael@0 2744 }
michael@0 2745
michael@0 2746 var updateEnabled = getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true);
michael@0 2747 if (!updateEnabled) {
michael@0 2748 this._backgroundUpdateCheckCodePing(PING_BGUC_PREF_DISABLED);
michael@0 2749 LOG("UpdateService:_selectAndInstallUpdate - not prompting because " +
michael@0 2750 "update is disabled");
michael@0 2751 return;
michael@0 2752 }
michael@0 2753
michael@0 2754 if (!gMetroUpdatesEnabled) {
michael@0 2755 this._backgroundUpdateCheckCodePing(PING_BGUC_METRO_DISABLED);
michael@0 2756 return;
michael@0 2757 }
michael@0 2758
michael@0 2759 var update = this.selectUpdate(updates, updates.length);
michael@0 2760 if (!update) {
michael@0 2761 return;
michael@0 2762 }
michael@0 2763
michael@0 2764 if (update.unsupported) {
michael@0 2765 LOG("UpdateService:_selectAndInstallUpdate - update not supported for " +
michael@0 2766 "this system");
michael@0 2767 if (!getPref("getBoolPref", PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED, false)) {
michael@0 2768 LOG("UpdateService:_selectAndInstallUpdate - notifying that the " +
michael@0 2769 "update is not supported for this system");
michael@0 2770 this._showPrompt(update);
michael@0 2771 }
michael@0 2772 this._backgroundUpdateCheckCodePing(PING_BGUC_UNSUPPORTED);
michael@0 2773 return;
michael@0 2774 }
michael@0 2775
michael@0 2776 if (!(gCanApplyUpdates && hasUpdateMutex())) {
michael@0 2777 LOG("UpdateService:_selectAndInstallUpdate - the user is unable to " +
michael@0 2778 "apply updates... prompting");
michael@0 2779 this._showPrompt(update);
michael@0 2780 this._backgroundUpdateCheckCodePing(PING_BGUC_UNABLE_TO_APPLY);
michael@0 2781 return;
michael@0 2782 }
michael@0 2783
michael@0 2784 /**
michael@0 2785 # From this point on there are two possible outcomes:
michael@0 2786 # 1. download and install the update automatically
michael@0 2787 # 2. notify the user about the availability of an update
michael@0 2788 #
michael@0 2789 # Notes:
michael@0 2790 # a) if the app.update.auto preference is false then automatic download and
michael@0 2791 # install is disabled and the user will be notified.
michael@0 2792 # b) if the update has a showPrompt attribute the user will be notified.
michael@0 2793 # c) Mode is determined by the value of the app.update.mode preference.
michael@0 2794 #
michael@0 2795 # If the update when it is first read has an appVersion attribute the
michael@0 2796 # following behavior implemented in bug 530872 will occur:
michael@0 2797 # Mode Incompatible Add-ons Outcome
michael@0 2798 # 0 N/A Auto Install
michael@0 2799 # 1 Yes Notify
michael@0 2800 # 1 No Auto Install
michael@0 2801 #
michael@0 2802 # If the update when it is first read does not have an appVersion attribute
michael@0 2803 # the following deprecated behavior will occur:
michael@0 2804 # Update Type Mode Incompatible Add-ons Outcome
michael@0 2805 # Major all N/A Notify
michael@0 2806 # Minor 0 N/A Auto Install
michael@0 2807 # Minor 1 Yes Notify
michael@0 2808 # Minor 1 No Auto Install
michael@0 2809 */
michael@0 2810 if (update.showPrompt) {
michael@0 2811 LOG("UpdateService:_selectAndInstallUpdate - prompting because the " +
michael@0 2812 "update snippet specified showPrompt");
michael@0 2813 this._showPrompt(update);
michael@0 2814 if (!Services.metro || !Services.metro.immersive) {
michael@0 2815 this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_SNIPPET);
michael@0 2816 return;
michael@0 2817 }
michael@0 2818 }
michael@0 2819
michael@0 2820 if (!getPref("getBoolPref", PREF_APP_UPDATE_AUTO, true)) {
michael@0 2821 LOG("UpdateService:_selectAndInstallUpdate - prompting because silent " +
michael@0 2822 "install is disabled");
michael@0 2823 this._showPrompt(update);
michael@0 2824 if (!Services.metro || !Services.metro.immersive) {
michael@0 2825 this._backgroundUpdateCheckCodePing(PING_BGUC_SHOWPROMPT_PREF);
michael@0 2826 return;
michael@0 2827 }
michael@0 2828 }
michael@0 2829
michael@0 2830 if (getPref("getIntPref", PREF_APP_UPDATE_MODE, 1) == 0) {
michael@0 2831 // Do not prompt regardless of add-on incompatibilities
michael@0 2832 LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " +
michael@0 2833 "check disabled by preference, just download the update");
michael@0 2834 var status = this.downloadUpdate(update, true);
michael@0 2835 if (status == STATE_NONE)
michael@0 2836 cleanupActiveUpdate();
michael@0 2837 this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_PREF_DISABLED);
michael@0 2838 return;
michael@0 2839 }
michael@0 2840
michael@0 2841 // Only check add-on compatibility when the version changes.
michael@0 2842 #ifdef TOR_BROWSER_UPDATE
michael@0 2843 var compatVersion = TOR_BROWSER_VERSION;
michael@0 2844 #else
michael@0 2845 var compatVersion = Services.appinfo.version;
michael@0 2846 #endif
michael@0 2847 if (update.appVersion &&
michael@0 2848 Services.vc.compare(update.appVersion, compatVersion) != 0) {
michael@0 2849 this._update = update;
michael@0 2850 this._checkAddonCompatibility();
michael@0 2851 }
michael@0 2852 else {
michael@0 2853 LOG("UpdateService:_selectAndInstallUpdate - add-on compatibility " +
michael@0 2854 "check not performed due to the update version being the same as " +
michael@0 2855 "the current application version, just download the update");
michael@0 2856 var status = this.downloadUpdate(update, true);
michael@0 2857 if (status == STATE_NONE)
michael@0 2858 cleanupActiveUpdate();
michael@0 2859 this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_SAME_APP_VER);
michael@0 2860 }
michael@0 2861 },
michael@0 2862
michael@0 2863 _showPrompt: function AUS__showPrompt(update) {
michael@0 2864 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 2865 createInstance(Ci.nsIUpdatePrompt);
michael@0 2866 prompter.showUpdateAvailable(update);
michael@0 2867 },
michael@0 2868
michael@0 2869 _checkAddonCompatibility: function AUS__checkAddonCompatibility() {
michael@0 2870 try {
michael@0 2871 var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
michael@0 2872 }
michael@0 2873 catch (e) { }
michael@0 2874
michael@0 2875 // Get all the installed add-ons
michael@0 2876 var self = this;
michael@0 2877 AddonManager.getAllAddons(function(addons) {
michael@0 2878 #ifdef TOR_BROWSER_UPDATE
michael@0 2879 let compatVersion = self._update.platformVersion;
michael@0 2880 #else
michael@0 2881 let compatVersion = self._update.appVersion;
michael@0 2882 #endif
michael@0 2883 self._incompatibleAddons = [];
michael@0 2884 addons.forEach(function(addon) {
michael@0 2885 // Protect against code that overrides the add-ons manager and doesn't
michael@0 2886 // implement the isCompatibleWith or the findUpdates method.
michael@0 2887 if (!("isCompatibleWith" in addon) || !("findUpdates" in addon)) {
michael@0 2888 let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
michael@0 2889 "or the findUpdates method!";
michael@0 2890 if (addon.id)
michael@0 2891 errMsg += " Add-on ID: " + addon.id;
michael@0 2892 Cu.reportError(errMsg);
michael@0 2893 return;
michael@0 2894 }
michael@0 2895
michael@0 2896 // If an add-on isn't appDisabled and isn't userDisabled then it is
michael@0 2897 // either active now or the user expects it to be active after the
michael@0 2898 // restart. If that is the case and the add-on is not installed by the
michael@0 2899 // application and is not compatible with the new application version
michael@0 2900 // then the user should be warned that the add-on will become
michael@0 2901 // incompatible. If an addon's type equals plugin it is skipped since
michael@0 2902 // checking plugins compatibility information isn't supported and
michael@0 2903 // getting the scope property of a plugin breaks in some environments
michael@0 2904 // (see bug 566787). The hotfix add-on is also ignored as it shouldn't
michael@0 2905 // block the user from upgrading.
michael@0 2906 try {
michael@0 2907 if (addon.type != "plugin" && addon.id != hotfixID &&
michael@0 2908 !addon.appDisabled && !addon.userDisabled &&
michael@0 2909 addon.scope != AddonManager.SCOPE_APPLICATION &&
michael@0 2910 addon.isCompatible &&
michael@0 2911 !addon.isCompatibleWith(compatVersion,
michael@0 2912 self._update.platformVersion))
michael@0 2913 self._incompatibleAddons.push(addon);
michael@0 2914 }
michael@0 2915 catch (e) {
michael@0 2916 Cu.reportError(e);
michael@0 2917 }
michael@0 2918 });
michael@0 2919
michael@0 2920 if (self._incompatibleAddons.length > 0) {
michael@0 2921 /**
michael@0 2922 # PREF_APP_UPDATE_INCOMPATIBLE_MODE
michael@0 2923 # Controls the mode in which we check for updates as follows.
michael@0 2924 #
michael@0 2925 # PREF_APP_UPDATE_INCOMPATIBLE_MODE != 1
michael@0 2926 # We check for VersionInfo _and_ NewerVersion updates for the
michael@0 2927 # incompatible add-ons - i.e. if Foo 1.2 is installed and it is
michael@0 2928 # incompatible with the update, and we find Foo 2.0 which is but has
michael@0 2929 # not been installed, then we do NOT prompt because the user can
michael@0 2930 # download Foo 2.0 when they restart after the update during the add-on
michael@0 2931 # mismatch checking UI. This is the default, since it suppresses most
michael@0 2932 # prompt dialogs.
michael@0 2933 #
michael@0 2934 # PREF_APP_UPDATE_INCOMPATIBLE_MODE == 1
michael@0 2935 # We check for VersionInfo updates for the incompatible add-ons - i.e.
michael@0 2936 # if the situation above with Foo 1.2 and available update to 2.0
michael@0 2937 # applies, we DO show the prompt since a download operation will be
michael@0 2938 # required after the update. This is not the default and is supplied
michael@0 2939 # only as a hidden option for those that want it.
michael@0 2940 */
michael@0 2941 self._updateCheckCount = self._incompatibleAddons.length;
michael@0 2942 LOG("UpdateService:_checkAddonCompatibility - checking for " +
michael@0 2943 "incompatible add-ons");
michael@0 2944
michael@0 2945 self._incompatibleAddons.forEach(function(addon) {
michael@0 2946 addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
michael@0 2947 compatVersion, this._update.platformVersion);
michael@0 2948 }, self);
michael@0 2949 }
michael@0 2950 else {
michael@0 2951 LOG("UpdateService:_checkAddonCompatibility - no incompatible " +
michael@0 2952 "add-ons found, just download the update");
michael@0 2953 var status = self.downloadUpdate(self._update, true);
michael@0 2954 if (status == STATE_NONE)
michael@0 2955 cleanupActiveUpdate();
michael@0 2956 self._update = null;
michael@0 2957 this._backgroundUpdateCheckCodePing(PING_BGUC_CHECK_NO_INCOMPAT);
michael@0 2958 }
michael@0 2959 });
michael@0 2960 },
michael@0 2961
michael@0 2962 // AddonUpdateListener
michael@0 2963 onCompatibilityUpdateAvailable: function(addon) {
michael@0 2964 // Remove the add-on from the list of add-ons that will become incompatible
michael@0 2965 // with the new version of the application.
michael@0 2966 for (var i = 0; i < this._incompatibleAddons.length; ++i) {
michael@0 2967 if (this._incompatibleAddons[i].id == addon.id) {
michael@0 2968 LOG("UpdateService:onCompatibilityUpdateAvailable - found update for " +
michael@0 2969 "add-on ID: " + addon.id);
michael@0 2970 this._incompatibleAddons.splice(i, 1);
michael@0 2971 }
michael@0 2972 }
michael@0 2973 },
michael@0 2974
michael@0 2975 onUpdateAvailable: function(addon, install) {
michael@0 2976 if (getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE, 0) == 1)
michael@0 2977 return;
michael@0 2978
michael@0 2979 // If the new version of this add-on is blocklisted for the new application
michael@0 2980 // then it isn't a valid update and the user should still be warned that
michael@0 2981 // the add-on will become incompatible.
michael@0 2982 let bs = Cc["@mozilla.org/extensions/blocklist;1"].
michael@0 2983 getService(Ci.nsIBlocklistService);
michael@0 2984 #ifdef TOR_BROWSER_UPDATE
michael@0 2985 let compatVersion = gUpdates.update.platformVersion;
michael@0 2986 #else
michael@0 2987 let compatVersion = gUpdates.update.appVersion;
michael@0 2988 #endif
michael@0 2989 if (bs.isAddonBlocklisted(addon,
michael@0 2990 compatVersion,
michael@0 2991 gUpdates.update.platformVersion))
michael@0 2992 return;
michael@0 2993
michael@0 2994 // Compatibility or new version updates mean the same thing here.
michael@0 2995 this.onCompatibilityUpdateAvailable(addon);
michael@0 2996 },
michael@0 2997
michael@0 2998 onUpdateFinished: function(addon) {
michael@0 2999 if (--this._updateCheckCount > 0)
michael@0 3000 return;
michael@0 3001
michael@0 3002 if (this._incompatibleAddons.length > 0 ||
michael@0 3003 !(gCanApplyUpdates && hasUpdateMutex())) {
michael@0 3004 LOG("UpdateService:onUpdateEnded - prompting because there are " +
michael@0 3005 "incompatible add-ons");
michael@0 3006 this._showPrompt(this._update);
michael@0 3007 this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_HAVE_INCOMPAT);
michael@0 3008 }
michael@0 3009 else {
michael@0 3010 LOG("UpdateService:_selectAndInstallUpdate - updates for all " +
michael@0 3011 "incompatible add-ons found, just download the update");
michael@0 3012 var status = this.downloadUpdate(this._update, true);
michael@0 3013 if (status == STATE_NONE)
michael@0 3014 cleanupActiveUpdate();
michael@0 3015 this._backgroundUpdateCheckCodePing(PING_BGUC_ADDON_UPDATES_FOR_INCOMPAT);
michael@0 3016 }
michael@0 3017 this._update = null;
michael@0 3018 },
michael@0 3019
michael@0 3020 /**
michael@0 3021 * The Checker used for background update checks.
michael@0 3022 */
michael@0 3023 _backgroundChecker: null,
michael@0 3024
michael@0 3025 /**
michael@0 3026 * See nsIUpdateService.idl
michael@0 3027 */
michael@0 3028 get backgroundChecker() {
michael@0 3029 if (!this._backgroundChecker)
michael@0 3030 this._backgroundChecker = new Checker();
michael@0 3031 return this._backgroundChecker;
michael@0 3032 },
michael@0 3033
michael@0 3034 /**
michael@0 3035 * See nsIUpdateService.idl
michael@0 3036 */
michael@0 3037 get canCheckForUpdates() {
michael@0 3038 return gCanCheckForUpdates && hasUpdateMutex();
michael@0 3039 },
michael@0 3040
michael@0 3041 /**
michael@0 3042 * See nsIUpdateService.idl
michael@0 3043 */
michael@0 3044 get canApplyUpdates() {
michael@0 3045 return gCanApplyUpdates && hasUpdateMutex();
michael@0 3046 },
michael@0 3047
michael@0 3048 /**
michael@0 3049 * See nsIUpdateService.idl
michael@0 3050 */
michael@0 3051 get canStageUpdates() {
michael@0 3052 return getCanStageUpdates();
michael@0 3053 },
michael@0 3054
michael@0 3055 /**
michael@0 3056 * See nsIUpdateService.idl
michael@0 3057 */
michael@0 3058 get isOtherInstanceHandlingUpdates() {
michael@0 3059 return !hasUpdateMutex();
michael@0 3060 },
michael@0 3061
michael@0 3062
michael@0 3063 /**
michael@0 3064 * See nsIUpdateService.idl
michael@0 3065 */
michael@0 3066 addDownloadListener: function AUS_addDownloadListener(listener) {
michael@0 3067 if (!this._downloader) {
michael@0 3068 LOG("UpdateService:addDownloadListener - no downloader!");
michael@0 3069 return;
michael@0 3070 }
michael@0 3071 this._downloader.addDownloadListener(listener);
michael@0 3072 },
michael@0 3073
michael@0 3074 /**
michael@0 3075 * See nsIUpdateService.idl
michael@0 3076 */
michael@0 3077 removeDownloadListener: function AUS_removeDownloadListener(listener) {
michael@0 3078 if (!this._downloader) {
michael@0 3079 LOG("UpdateService:removeDownloadListener - no downloader!");
michael@0 3080 return;
michael@0 3081 }
michael@0 3082 this._downloader.removeDownloadListener(listener);
michael@0 3083 },
michael@0 3084
michael@0 3085 /**
michael@0 3086 * See nsIUpdateService.idl
michael@0 3087 */
michael@0 3088 downloadUpdate: function AUS_downloadUpdate(update, background) {
michael@0 3089 if (!update)
michael@0 3090 throw Cr.NS_ERROR_NULL_POINTER;
michael@0 3091
michael@0 3092 // Don't download the update if the update's version is less than the
michael@0 3093 // current application's version or the update's version is the same as the
michael@0 3094 // application's version and the build ID is the same as the application's
michael@0 3095 // build ID.
michael@0 3096 #ifdef TOR_BROWSER_UPDATE
michael@0 3097 var compatVersion = TOR_BROWSER_VERSION;
michael@0 3098 #else
michael@0 3099 var compatVersion = Services.appinfo.version;
michael@0 3100 #endif
michael@0 3101 if (update.appVersion &&
michael@0 3102 (Services.vc.compare(update.appVersion, compatVersion) < 0 ||
michael@0 3103 update.buildID && update.buildID == Services.appinfo.appBuildID &&
michael@0 3104 update.appVersion == compatVersion)) {
michael@0 3105 LOG("UpdateService:downloadUpdate - canceling download of update since " +
michael@0 3106 "it is for an earlier or same application version and build ID.\n" +
michael@0 3107 #ifdef TOR_BROWSER_UPDATE
michael@0 3108 "current Tor Browser version: " + compatVersion + "\n" +
michael@0 3109 "update Tor Browser version : " + update.appVersion + "\n" +
michael@0 3110 #else
michael@0 3111 "current application version: " + compatVersion + "\n" +
michael@0 3112 "update application version : " + update.appVersion + "\n" +
michael@0 3113 #endif
michael@0 3114 "current build ID: " + Services.appinfo.appBuildID + "\n" +
michael@0 3115 "update build ID : " + update.buildID);
michael@0 3116 cleanupActiveUpdate();
michael@0 3117 return STATE_NONE;
michael@0 3118 }
michael@0 3119
michael@0 3120 // If a download request is in progress vs. a download ready to resume
michael@0 3121 if (this.isDownloading) {
michael@0 3122 if (update.isCompleteUpdate == this._downloader.isCompleteUpdate &&
michael@0 3123 background == this._downloader.background) {
michael@0 3124 LOG("UpdateService:downloadUpdate - no support for downloading more " +
michael@0 3125 "than one update at a time");
michael@0 3126 return readStatusFile(getUpdatesDir());
michael@0 3127 }
michael@0 3128 this._downloader.cancel();
michael@0 3129 }
michael@0 3130 #ifdef MOZ_WIDGET_GONK
michael@0 3131 var um = Cc["@mozilla.org/updates/update-manager;1"].
michael@0 3132 getService(Ci.nsIUpdateManager);
michael@0 3133 var activeUpdate = um.activeUpdate;
michael@0 3134 if (activeUpdate &&
michael@0 3135 (activeUpdate.appVersion != update.appVersion ||
michael@0 3136 activeUpdate.buildID != update.buildID)) {
michael@0 3137 // We have an activeUpdate (which presumably was interrupted), and are
michael@0 3138 // about start downloading a new one. Make sure we remove all traces
michael@0 3139 // of the active one (otherwise we'll start appending the new update.mar
michael@0 3140 // the the one that's been partially downloaded).
michael@0 3141 LOG("UpdateService:downloadUpdate - removing stale active update.");
michael@0 3142 cleanupActiveUpdate();
michael@0 3143 }
michael@0 3144 #endif
michael@0 3145 // Set the previous application version prior to downloading the update.
michael@0 3146 update.previousAppVersion = compatVersion;
michael@0 3147 this._downloader = new Downloader(background, this);
michael@0 3148 return this._downloader.downloadUpdate(update);
michael@0 3149 },
michael@0 3150
michael@0 3151 /**
michael@0 3152 * See nsIUpdateService.idl
michael@0 3153 */
michael@0 3154 pauseDownload: function AUS_pauseDownload() {
michael@0 3155 if (this.isDownloading) {
michael@0 3156 this._downloader.cancel();
michael@0 3157 } else if (this._retryTimer) {
michael@0 3158 // Download status is still consider as 'downloading' during retry.
michael@0 3159 // We need to cancel both retry and download at this stage.
michael@0 3160 this._retryTimer.cancel();
michael@0 3161 this._retryTimer = null;
michael@0 3162 this._downloader.cancel();
michael@0 3163 }
michael@0 3164 },
michael@0 3165
michael@0 3166 /**
michael@0 3167 * See nsIUpdateService.idl
michael@0 3168 */
michael@0 3169 getUpdatesDirectory: getUpdatesDir,
michael@0 3170
michael@0 3171 /**
michael@0 3172 * See nsIUpdateService.idl
michael@0 3173 */
michael@0 3174 get isDownloading() {
michael@0 3175 return this._downloader && this._downloader.isBusy;
michael@0 3176 },
michael@0 3177
michael@0 3178 /**
michael@0 3179 * See nsIUpdateService.idl
michael@0 3180 */
michael@0 3181 applyOsUpdate: function AUS_applyOsUpdate(aUpdate) {
michael@0 3182 if (!aUpdate.isOSUpdate || aUpdate.state != STATE_APPLIED) {
michael@0 3183 aUpdate.statusText = "fota-state-error";
michael@0 3184 throw Cr.NS_ERROR_FAILURE;
michael@0 3185 }
michael@0 3186
michael@0 3187 let osApplyToDir;
michael@0 3188 try {
michael@0 3189 aUpdate.QueryInterface(Ci.nsIWritablePropertyBag);
michael@0 3190 osApplyToDir = aUpdate.getProperty("osApplyToDir");
michael@0 3191 } catch (e) {}
michael@0 3192
michael@0 3193 if (!osApplyToDir) {
michael@0 3194 LOG("UpdateService:applyOsUpdate - Error: osApplyToDir is not defined" +
michael@0 3195 "in the nsIUpdate!");
michael@0 3196 handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
michael@0 3197 return;
michael@0 3198 }
michael@0 3199
michael@0 3200 let updateFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
michael@0 3201 updateFile.initWithPath(osApplyToDir + "/update.zip");
michael@0 3202 if (!updateFile.exists()) {
michael@0 3203 LOG("UpdateService:applyOsUpdate - Error: OS update is not found at " +
michael@0 3204 updateFile.path);
michael@0 3205 handleUpdateFailure(aUpdate, FOTA_FILE_OPERATION_ERROR);
michael@0 3206 return;
michael@0 3207 }
michael@0 3208
michael@0 3209 writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED_OS);
michael@0 3210 LOG("UpdateService:applyOsUpdate - Rebooting into recovery to apply " +
michael@0 3211 "FOTA update: " + updateFile.path);
michael@0 3212 try {
michael@0 3213 let recoveryService = Cc["@mozilla.org/recovery-service;1"]
michael@0 3214 .getService(Ci.nsIRecoveryService);
michael@0 3215 recoveryService.installFotaUpdate(updateFile.path);
michael@0 3216 } catch (e) {
michael@0 3217 LOG("UpdateService:applyOsUpdate - Error: Couldn't reboot into recovery" +
michael@0 3218 " to apply FOTA update " + updateFile.path);
michael@0 3219 writeStatusFile(getUpdatesDir(), aUpdate.state = STATE_APPLIED);
michael@0 3220 handleUpdateFailure(aUpdate, FOTA_RECOVERY_ERROR);
michael@0 3221 }
michael@0 3222 },
michael@0 3223
michael@0 3224 classID: UPDATESERVICE_CID,
michael@0 3225 classInfo: XPCOMUtils.generateCI({classID: UPDATESERVICE_CID,
michael@0 3226 contractID: UPDATESERVICE_CONTRACTID,
michael@0 3227 interfaces: [Ci.nsIApplicationUpdateService,
michael@0 3228 Ci.nsITimerCallback,
michael@0 3229 Ci.nsIObserver],
michael@0 3230 flags: Ci.nsIClassInfo.SINGLETON}),
michael@0 3231
michael@0 3232 _xpcom_factory: UpdateServiceFactory,
michael@0 3233 QueryInterface: XPCOMUtils.generateQI([Ci.nsIApplicationUpdateService,
michael@0 3234 Ci.nsIUpdateCheckListener,
michael@0 3235 Ci.nsITimerCallback,
michael@0 3236 Ci.nsIObserver])
michael@0 3237 };
michael@0 3238
michael@0 3239 /**
michael@0 3240 * A service to manage active and past updates.
michael@0 3241 * @constructor
michael@0 3242 */
michael@0 3243 function UpdateManager() {
michael@0 3244 // Ensure the Active Update file is loaded
michael@0 3245 var updates = this._loadXMLFileIntoArray(getUpdateFile(
michael@0 3246 [FILE_UPDATE_ACTIVE]));
michael@0 3247 if (updates.length > 0) {
michael@0 3248 // Under some edgecases such as Windows system restore the active-update.xml
michael@0 3249 // will contain a pending update without the status file which will return
michael@0 3250 // STATE_NONE. To recover from this situation clean the updates dir and
michael@0 3251 // rewrite the active-update.xml file without the broken update.
michael@0 3252 if (readStatusFile(getUpdatesDir()) == STATE_NONE) {
michael@0 3253 cleanUpUpdatesDir();
michael@0 3254 this._writeUpdatesToXMLFile([], getUpdateFile([FILE_UPDATE_ACTIVE]));
michael@0 3255 }
michael@0 3256 else
michael@0 3257 this._activeUpdate = updates[0];
michael@0 3258 }
michael@0 3259 }
michael@0 3260 UpdateManager.prototype = {
michael@0 3261 /**
michael@0 3262 * All previously downloaded and installed updates, as an array of nsIUpdate
michael@0 3263 * objects.
michael@0 3264 */
michael@0 3265 _updates: null,
michael@0 3266
michael@0 3267 /**
michael@0 3268 * The current actively downloading/installing update, as a nsIUpdate object.
michael@0 3269 */
michael@0 3270 _activeUpdate: null,
michael@0 3271
michael@0 3272 /**
michael@0 3273 * Handle Observer Service notifications
michael@0 3274 * @param subject
michael@0 3275 * The subject of the notification
michael@0 3276 * @param topic
michael@0 3277 * The notification name
michael@0 3278 * @param data
michael@0 3279 * Additional data
michael@0 3280 */
michael@0 3281 observe: function UM_observe(subject, topic, data) {
michael@0 3282 // Hack to be able to run and cleanup tests by reloading the update data.
michael@0 3283 if (topic == "um-reload-update-data") {
michael@0 3284 this._updates = this._loadXMLFileIntoArray(getUpdateFile(
michael@0 3285 [FILE_UPDATES_DB]));
michael@0 3286 this._activeUpdate = null;
michael@0 3287 var updates = this._loadXMLFileIntoArray(getUpdateFile(
michael@0 3288 [FILE_UPDATE_ACTIVE]));
michael@0 3289 if (updates.length > 0)
michael@0 3290 this._activeUpdate = updates[0];
michael@0 3291 }
michael@0 3292 },
michael@0 3293
michael@0 3294 /**
michael@0 3295 * Loads an updates.xml formatted file into an array of nsIUpdate items.
michael@0 3296 * @param file
michael@0 3297 * A nsIFile for the updates.xml file
michael@0 3298 * @return The array of nsIUpdate items held in the file.
michael@0 3299 */
michael@0 3300 _loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(file) {
michael@0 3301 if (!file.exists()) {
michael@0 3302 LOG("UpdateManager:_loadXMLFileIntoArray: XML file does not exist");
michael@0 3303 return [];
michael@0 3304 }
michael@0 3305
michael@0 3306 var result = [];
michael@0 3307 var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
michael@0 3308 createInstance(Ci.nsIFileInputStream);
michael@0 3309 fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
michael@0 3310 try {
michael@0 3311 var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
michael@0 3312 createInstance(Ci.nsIDOMParser);
michael@0 3313 var doc = parser.parseFromStream(fileStream, "UTF-8",
michael@0 3314 fileStream.available(), "text/xml");
michael@0 3315
michael@0 3316 const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
michael@0 3317 var updateCount = doc.documentElement.childNodes.length;
michael@0 3318 for (var i = 0; i < updateCount; ++i) {
michael@0 3319 var updateElement = doc.documentElement.childNodes.item(i);
michael@0 3320 if (updateElement.nodeType != ELEMENT_NODE ||
michael@0 3321 updateElement.localName != "update")
michael@0 3322 continue;
michael@0 3323
michael@0 3324 updateElement.QueryInterface(Ci.nsIDOMElement);
michael@0 3325 try {
michael@0 3326 var update = new Update(updateElement);
michael@0 3327 } catch (e) {
michael@0 3328 LOG("UpdateManager:_loadXMLFileIntoArray - invalid update");
michael@0 3329 continue;
michael@0 3330 }
michael@0 3331 result.push(update);
michael@0 3332 }
michael@0 3333 }
michael@0 3334 catch (e) {
michael@0 3335 LOG("UpdateManager:_loadXMLFileIntoArray - error constructing update " +
michael@0 3336 "list. Exception: " + e);
michael@0 3337 }
michael@0 3338 fileStream.close();
michael@0 3339 return result;
michael@0 3340 },
michael@0 3341
michael@0 3342 /**
michael@0 3343 * Load the update manager, initializing state from state files.
michael@0 3344 */
michael@0 3345 _ensureUpdates: function UM__ensureUpdates() {
michael@0 3346 if (!this._updates) {
michael@0 3347 this._updates = this._loadXMLFileIntoArray(getUpdateFile(
michael@0 3348 [FILE_UPDATES_DB]));
michael@0 3349 var activeUpdates = this._loadXMLFileIntoArray(getUpdateFile(
michael@0 3350 [FILE_UPDATE_ACTIVE]));
michael@0 3351 if (activeUpdates.length > 0)
michael@0 3352 this._activeUpdate = activeUpdates[0];
michael@0 3353 }
michael@0 3354 },
michael@0 3355
michael@0 3356 /**
michael@0 3357 * See nsIUpdateService.idl
michael@0 3358 */
michael@0 3359 getUpdateAt: function UM_getUpdateAt(index) {
michael@0 3360 this._ensureUpdates();
michael@0 3361 return this._updates[index];
michael@0 3362 },
michael@0 3363
michael@0 3364 /**
michael@0 3365 * See nsIUpdateService.idl
michael@0 3366 */
michael@0 3367 get updateCount() {
michael@0 3368 this._ensureUpdates();
michael@0 3369 return this._updates.length;
michael@0 3370 },
michael@0 3371
michael@0 3372 /**
michael@0 3373 * See nsIUpdateService.idl
michael@0 3374 */
michael@0 3375 get activeUpdate() {
michael@0 3376 if (this._activeUpdate &&
michael@0 3377 this._activeUpdate.channel != UpdateChannel.get()) {
michael@0 3378 LOG("UpdateManager:get activeUpdate - channel has changed, " +
michael@0 3379 "reloading default preferences to workaround bug 802022");
michael@0 3380 // Workaround to get distribution preferences loaded (Bug 774618). This
michael@0 3381 // can be removed after bug 802022 is fixed.
michael@0 3382 let prefSvc = Services.prefs.QueryInterface(Ci.nsIObserver);
michael@0 3383 prefSvc.observe(null, "reload-default-prefs", null);
michael@0 3384 if (this._activeUpdate.channel != UpdateChannel.get()) {
michael@0 3385 // User switched channels, clear out any old active updates and remove
michael@0 3386 // partial downloads
michael@0 3387 this._activeUpdate = null;
michael@0 3388 this.saveUpdates();
michael@0 3389
michael@0 3390 // Destroy the updates directory, since we're done with it.
michael@0 3391 cleanUpUpdatesDir();
michael@0 3392 }
michael@0 3393 }
michael@0 3394 return this._activeUpdate;
michael@0 3395 },
michael@0 3396 set activeUpdate(activeUpdate) {
michael@0 3397 this._addUpdate(activeUpdate);
michael@0 3398 this._activeUpdate = activeUpdate;
michael@0 3399 if (!activeUpdate) {
michael@0 3400 // If |activeUpdate| is null, we have updated both lists - the active list
michael@0 3401 // and the history list, so we want to write both files.
michael@0 3402 this.saveUpdates();
michael@0 3403 }
michael@0 3404 else
michael@0 3405 this._writeUpdatesToXMLFile([this._activeUpdate],
michael@0 3406 getUpdateFile([FILE_UPDATE_ACTIVE]));
michael@0 3407 return activeUpdate;
michael@0 3408 },
michael@0 3409
michael@0 3410 /**
michael@0 3411 * Add an update to the Updates list. If the item already exists in the list,
michael@0 3412 * replace the existing value with the new value.
michael@0 3413 * @param update
michael@0 3414 * The nsIUpdate object to add.
michael@0 3415 */
michael@0 3416 _addUpdate: function UM__addUpdate(update) {
michael@0 3417 if (!update)
michael@0 3418 return;
michael@0 3419 this._ensureUpdates();
michael@0 3420 if (this._updates) {
michael@0 3421 for (var i = 0; i < this._updates.length; ++i) {
michael@0 3422 if (this._updates[i] &&
michael@0 3423 this._updates[i].appVersion == update.appVersion &&
michael@0 3424 this._updates[i].buildID == update.buildID) {
michael@0 3425 // Replace the existing entry with the new value, updating
michael@0 3426 // all metadata.
michael@0 3427 this._updates[i] = update;
michael@0 3428 return;
michael@0 3429 }
michael@0 3430 }
michael@0 3431 }
michael@0 3432 // Otherwise add it to the front of the list.
michael@0 3433 this._updates.unshift(update);
michael@0 3434 },
michael@0 3435
michael@0 3436 /**
michael@0 3437 * Serializes an array of updates to an XML file
michael@0 3438 * @param updates
michael@0 3439 * An array of nsIUpdate objects
michael@0 3440 * @param file
michael@0 3441 * The nsIFile object to serialize to
michael@0 3442 */
michael@0 3443 _writeUpdatesToXMLFile: function UM__writeUpdatesToXMLFile(updates, file) {
michael@0 3444 var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
michael@0 3445 createInstance(Ci.nsIFileOutputStream);
michael@0 3446 var modeFlags = FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
michael@0 3447 FileUtils.MODE_TRUNCATE;
michael@0 3448 if (!file.exists())
michael@0 3449 file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
michael@0 3450 fos.init(file, modeFlags, FileUtils.PERMS_FILE, 0);
michael@0 3451
michael@0 3452 try {
michael@0 3453 var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
michael@0 3454 createInstance(Ci.nsIDOMParser);
michael@0 3455 const EMPTY_UPDATES_DOCUMENT = "<?xml version=\"1.0\"?><updates xmlns=\"http://www.mozilla.org/2005/app-update\"></updates>";
michael@0 3456 var doc = parser.parseFromString(EMPTY_UPDATES_DOCUMENT, "text/xml");
michael@0 3457
michael@0 3458 for (var i = 0; i < updates.length; ++i) {
michael@0 3459 if (updates[i])
michael@0 3460 doc.documentElement.appendChild(updates[i].serialize(doc));
michael@0 3461 }
michael@0 3462
michael@0 3463 var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
michael@0 3464 createInstance(Ci.nsIDOMSerializer);
michael@0 3465 serializer.serializeToStream(doc.documentElement, fos, null);
michael@0 3466 }
michael@0 3467 catch (e) {
michael@0 3468 }
michael@0 3469
michael@0 3470 FileUtils.closeSafeFileOutputStream(fos);
michael@0 3471 },
michael@0 3472
michael@0 3473 /**
michael@0 3474 * See nsIUpdateService.idl
michael@0 3475 */
michael@0 3476 saveUpdates: function UM_saveUpdates() {
michael@0 3477 this._writeUpdatesToXMLFile([this._activeUpdate],
michael@0 3478 getUpdateFile([FILE_UPDATE_ACTIVE]));
michael@0 3479 if (this._activeUpdate)
michael@0 3480 this._addUpdate(this._activeUpdate);
michael@0 3481
michael@0 3482 this._ensureUpdates();
michael@0 3483 // Don't write updates that have a temporary state to the updates.xml file.
michael@0 3484 if (this._updates) {
michael@0 3485 let updates = this._updates.slice();
michael@0 3486 for (let i = updates.length - 1; i >= 0; --i) {
michael@0 3487 let state = updates[i].state;
michael@0 3488 if (state == STATE_NONE || state == STATE_DOWNLOADING ||
michael@0 3489 state == STATE_APPLIED || state == STATE_APPLIED_SVC ||
michael@0 3490 state == STATE_PENDING || state == STATE_PENDING_SVC) {
michael@0 3491 updates.splice(i, 1);
michael@0 3492 }
michael@0 3493 }
michael@0 3494
michael@0 3495 this._writeUpdatesToXMLFile(updates.slice(0, 10),
michael@0 3496 getUpdateFile([FILE_UPDATES_DB]));
michael@0 3497 }
michael@0 3498 },
michael@0 3499
michael@0 3500 refreshUpdateStatus: function UM_refreshUpdateStatus(update) {
michael@0 3501 var updateSucceeded = true;
michael@0 3502 var status = readStatusFile(getUpdatesDir());
michael@0 3503 var ary = status.split(":");
michael@0 3504 update.state = ary[0];
michael@0 3505 if (update.state == STATE_FAILED && ary[1]) {
michael@0 3506 updateSucceeded = false;
michael@0 3507 if (!handleUpdateFailure(update, ary[1])) {
michael@0 3508 handleFallbackToCompleteUpdate(update, true);
michael@0 3509 }
michael@0 3510 }
michael@0 3511 if (update.state == STATE_APPLIED && shouldUseService()) {
michael@0 3512 writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SVC);
michael@0 3513 }
michael@0 3514 var um = Cc["@mozilla.org/updates/update-manager;1"].
michael@0 3515 getService(Ci.nsIUpdateManager);
michael@0 3516 um.saveUpdates();
michael@0 3517
michael@0 3518 if (update.state != STATE_PENDING && update.state != STATE_PENDING_SVC) {
michael@0 3519 // Destroy the updates directory, since we're done with it.
michael@0 3520 // Make sure to not do this when the updater has fallen back to
michael@0 3521 // non-staged updates.
michael@0 3522 cleanUpUpdatesDir(updateSucceeded);
michael@0 3523 }
michael@0 3524
michael@0 3525 // Send an observer notification which the update wizard uses in
michael@0 3526 // order to update its UI.
michael@0 3527 LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " +
michael@0 3528 "the update was staged. state: " + update.state + ", status: " + status);
michael@0 3529 Services.obs.notifyObservers(null, "update-staged", update.state);
michael@0 3530
michael@0 3531 // Do this after *everything* else, since it will likely cause the app
michael@0 3532 // to shut down.
michael@0 3533 #ifdef MOZ_WIDGET_GONK
michael@0 3534 if (update.state == STATE_APPLIED) {
michael@0 3535 // Notify the user that an update has been staged and is ready for
michael@0 3536 // installation (i.e. that they should restart the application). We do
michael@0 3537 // not notify on failed update attempts.
michael@0 3538 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 3539 createInstance(Ci.nsIUpdatePrompt);
michael@0 3540 prompter.showUpdateDownloaded(update, true);
michael@0 3541 } else {
michael@0 3542 releaseSDCardMountLock();
michael@0 3543 }
michael@0 3544 #else
michael@0 3545 // Only prompt when the UI isn't already open.
michael@0 3546 let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
michael@0 3547 if (Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME) ||
michael@0 3548 windowType && Services.wm.getMostRecentWindow(windowType)) {
michael@0 3549 return;
michael@0 3550 }
michael@0 3551
michael@0 3552 if (update.state == STATE_APPLIED || update.state == STATE_APPLIED_SVC ||
michael@0 3553 update.state == STATE_PENDING || update.state == STATE_PENDING_SVC) {
michael@0 3554 // Notify the user that an update has been staged and is ready for
michael@0 3555 // installation (i.e. that they should restart the application).
michael@0 3556 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 3557 createInstance(Ci.nsIUpdatePrompt);
michael@0 3558 prompter.showUpdateDownloaded(update, true);
michael@0 3559 }
michael@0 3560 #endif
michael@0 3561 },
michael@0 3562
michael@0 3563 classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
michael@0 3564 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateManager, Ci.nsIObserver])
michael@0 3565 };
michael@0 3566
michael@0 3567 /**
michael@0 3568 * Checker
michael@0 3569 * Checks for new Updates
michael@0 3570 * @constructor
michael@0 3571 */
michael@0 3572 function Checker() {
michael@0 3573 }
michael@0 3574 Checker.prototype = {
michael@0 3575 /**
michael@0 3576 * The XMLHttpRequest object that performs the connection.
michael@0 3577 */
michael@0 3578 _request : null,
michael@0 3579
michael@0 3580 /**
michael@0 3581 * The nsIUpdateCheckListener callback
michael@0 3582 */
michael@0 3583 _callback : null,
michael@0 3584
michael@0 3585 /**
michael@0 3586 * The URL of the update service XML file to connect to that contains details
michael@0 3587 * about available updates.
michael@0 3588 */
michael@0 3589 getUpdateURL: function UC_getUpdateURL(force) {
michael@0 3590 this._forced = force;
michael@0 3591
michael@0 3592 // Use the override URL if specified.
michael@0 3593 var url = getPref("getCharPref", PREF_APP_UPDATE_URL_OVERRIDE, null);
michael@0 3594
michael@0 3595 // Otherwise, construct the update URL from component parts.
michael@0 3596 if (!url) {
michael@0 3597 try {
michael@0 3598 url = Services.prefs.getDefaultBranch(null).
michael@0 3599 getCharPref(PREF_APP_UPDATE_URL);
michael@0 3600 } catch (e) {
michael@0 3601 }
michael@0 3602 }
michael@0 3603
michael@0 3604 if (!url || url == "") {
michael@0 3605 LOG("Checker:getUpdateURL - update URL not defined");
michael@0 3606 return null;
michael@0 3607 }
michael@0 3608
michael@0 3609 url = url.replace(/%PRODUCT%/g, Services.appinfo.name);
michael@0 3610 #ifdef TOR_BROWSER_UPDATE
michael@0 3611 url = url.replace(/%VERSION%/g, TOR_BROWSER_VERSION);
michael@0 3612 #else
michael@0 3613 url = url.replace(/%VERSION%/g, Services.appinfo.version);
michael@0 3614 #endif
michael@0 3615 url = url.replace(/%BUILD_ID%/g, Services.appinfo.appBuildID);
michael@0 3616 url = url.replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + gABI);
michael@0 3617 url = url.replace(/%OS_VERSION%/g, gOSVersion);
michael@0 3618 if (/%LOCALE%/.test(url))
michael@0 3619 url = url.replace(/%LOCALE%/g, getLocale());
michael@0 3620 url = url.replace(/%CHANNEL%/g, UpdateChannel.get());
michael@0 3621 url = url.replace(/%PLATFORM_VERSION%/g, Services.appinfo.platformVersion);
michael@0 3622 url = url.replace(/%DISTRIBUTION%/g,
michael@0 3623 getDistributionPrefValue(PREF_APP_DISTRIBUTION));
michael@0 3624 url = url.replace(/%DISTRIBUTION_VERSION%/g,
michael@0 3625 getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
michael@0 3626 url = url.replace(/%CUSTOM%/g, getPref("getCharPref", PREF_APP_UPDATE_CUSTOM, ""));
michael@0 3627 url = url.replace(/\+/g, "%2B");
michael@0 3628
michael@0 3629 #ifdef MOZ_WIDGET_GONK
michael@0 3630 url = url.replace(/%PRODUCT_MODEL%/g, gProductModel);
michael@0 3631 url = url.replace(/%B2G_VERSION%/g, getPref("getCharPref", PREF_APP_B2G_VERSION, null));
michael@0 3632 #endif
michael@0 3633
michael@0 3634 if (force)
michael@0 3635 url += (url.indexOf("?") != -1 ? "&" : "?") + "force=1";
michael@0 3636
michael@0 3637 LOG("Checker:getUpdateURL - update URL: " + url);
michael@0 3638 return url;
michael@0 3639 },
michael@0 3640
michael@0 3641 /**
michael@0 3642 * See nsIUpdateService.idl
michael@0 3643 */
michael@0 3644 checkForUpdates: function UC_checkForUpdates(listener, force) {
michael@0 3645 LOG("Checker: checkForUpdates, force: " + force);
michael@0 3646 if (!listener)
michael@0 3647 throw Cr.NS_ERROR_NULL_POINTER;
michael@0 3648
michael@0 3649 Services.obs.notifyObservers(null, "update-check-start", null);
michael@0 3650
michael@0 3651 var url = this.getUpdateURL(force);
michael@0 3652 if (!url || (!this.enabled && !force))
michael@0 3653 return;
michael@0 3654
michael@0 3655 this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
michael@0 3656 createInstance(Ci.nsISupports);
michael@0 3657 // This is here to let unit test code override XHR
michael@0 3658 if (this._request.wrappedJSObject) {
michael@0 3659 this._request = this._request.wrappedJSObject;
michael@0 3660 }
michael@0 3661 this._request.open("GET", url, true);
michael@0 3662 var allowNonBuiltIn = !getPref("getBoolPref",
michael@0 3663 PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true);
michael@0 3664 this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(allowNonBuiltIn);
michael@0 3665 // Prevent the request from reading from the cache.
michael@0 3666 this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
michael@0 3667 // Prevent the request from writing to the cache.
michael@0 3668 this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
michael@0 3669
michael@0 3670 this._request.overrideMimeType("text/xml");
michael@0 3671 // The Cache-Control header is only interpreted by proxies and the
michael@0 3672 // final destination. It does not help if a resource is already
michael@0 3673 // cached locally.
michael@0 3674 this._request.setRequestHeader("Cache-Control", "no-cache");
michael@0 3675 // HTTP/1.0 servers might not implement Cache-Control and
michael@0 3676 // might only implement Pragma: no-cache
michael@0 3677 this._request.setRequestHeader("Pragma", "no-cache");
michael@0 3678
michael@0 3679 var self = this;
michael@0 3680 this._request.addEventListener("error", function(event) { self.onError(event); } ,false);
michael@0 3681 this._request.addEventListener("load", function(event) { self.onLoad(event); }, false);
michael@0 3682
michael@0 3683 LOG("Checker:checkForUpdates - sending request to: " + url);
michael@0 3684 this._request.send(null);
michael@0 3685
michael@0 3686 this._callback = listener;
michael@0 3687 },
michael@0 3688
michael@0 3689 /**
michael@0 3690 * Returns an array of nsIUpdate objects discovered by the update check.
michael@0 3691 * @throws if the XML document element node name is not updates.
michael@0 3692 */
michael@0 3693 get _updates() {
michael@0 3694 var updatesElement = this._request.responseXML.documentElement;
michael@0 3695 if (!updatesElement) {
michael@0 3696 LOG("Checker:_updates get - empty updates document?!");
michael@0 3697 return [];
michael@0 3698 }
michael@0 3699
michael@0 3700 if (updatesElement.nodeName != "updates") {
michael@0 3701 LOG("Checker:_updates get - unexpected node name!");
michael@0 3702 throw new Error("Unexpected node name, expected: updates, got: " +
michael@0 3703 updatesElement.nodeName);
michael@0 3704 }
michael@0 3705
michael@0 3706 const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE;
michael@0 3707 var updates = [];
michael@0 3708 for (var i = 0; i < updatesElement.childNodes.length; ++i) {
michael@0 3709 var updateElement = updatesElement.childNodes.item(i);
michael@0 3710 if (updateElement.nodeType != ELEMENT_NODE ||
michael@0 3711 updateElement.localName != "update")
michael@0 3712 continue;
michael@0 3713
michael@0 3714 updateElement.QueryInterface(Ci.nsIDOMElement);
michael@0 3715 try {
michael@0 3716 var update = new Update(updateElement);
michael@0 3717 } catch (e) {
michael@0 3718 LOG("Checker:_updates get - invalid <update/>, ignoring...");
michael@0 3719 continue;
michael@0 3720 }
michael@0 3721 update.serviceURL = this.getUpdateURL(this._forced);
michael@0 3722 update.channel = UpdateChannel.get();
michael@0 3723 updates.push(update);
michael@0 3724 }
michael@0 3725
michael@0 3726 return updates;
michael@0 3727 },
michael@0 3728
michael@0 3729 /**
michael@0 3730 * Returns the status code for the XMLHttpRequest
michael@0 3731 */
michael@0 3732 _getChannelStatus: function UC__getChannelStatus(request) {
michael@0 3733 var status = 0;
michael@0 3734 try {
michael@0 3735 status = request.status;
michael@0 3736 }
michael@0 3737 catch (e) {
michael@0 3738 }
michael@0 3739
michael@0 3740 if (status == 0)
michael@0 3741 status = request.channel.QueryInterface(Ci.nsIRequest).status;
michael@0 3742 return status;
michael@0 3743 },
michael@0 3744
michael@0 3745 _isHttpStatusCode: function UC__isHttpStatusCode(status) {
michael@0 3746 return status >= 100 && status <= 599;
michael@0 3747 },
michael@0 3748
michael@0 3749 /**
michael@0 3750 * The XMLHttpRequest succeeded and the document was loaded.
michael@0 3751 * @param event
michael@0 3752 * The nsIDOMEvent for the load
michael@0 3753 */
michael@0 3754 onLoad: function UC_onLoad(event) {
michael@0 3755 LOG("Checker:onLoad - request completed downloading document");
michael@0 3756
michael@0 3757 var prefs = Services.prefs;
michael@0 3758 var certs = null;
michael@0 3759 if (!prefs.prefHasUserValue(PREF_APP_UPDATE_URL_OVERRIDE) &&
michael@0 3760 getPref("getBoolPref", PREF_APP_UPDATE_CERT_CHECKATTRS, true)) {
michael@0 3761 certs = gCertUtils.readCertPrefs(PREF_APP_UPDATE_CERTS_BRANCH);
michael@0 3762 }
michael@0 3763
michael@0 3764 try {
michael@0 3765 // Analyze the resulting DOM and determine the set of updates.
michael@0 3766 var updates = this._updates;
michael@0 3767 LOG("Checker:onLoad - number of updates available: " + updates.length);
michael@0 3768 var allowNonBuiltIn = !getPref("getBoolPref",
michael@0 3769 PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true);
michael@0 3770 gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs);
michael@0 3771
michael@0 3772 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS))
michael@0 3773 Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS);
michael@0 3774
michael@0 3775 if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS))
michael@0 3776 Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS);
michael@0 3777
michael@0 3778 // Tell the callback about the updates
michael@0 3779 this._callback.onCheckComplete(event.target, updates, updates.length);
michael@0 3780 }
michael@0 3781 catch (e) {
michael@0 3782 LOG("Checker:onLoad - there was a problem checking for updates. " +
michael@0 3783 "Exception: " + e);
michael@0 3784 var request = event.target;
michael@0 3785 var status = this._getChannelStatus(request);
michael@0 3786 LOG("Checker:onLoad - request.status: " + status);
michael@0 3787 var update = new Update(null);
michael@0 3788 update.errorCode = status;
michael@0 3789 update.statusText = getStatusTextFromCode(status, 404);
michael@0 3790
michael@0 3791 if (this._isHttpStatusCode(status)) {
michael@0 3792 update.errorCode = HTTP_ERROR_OFFSET + status;
michael@0 3793 }
michael@0 3794 if (e.result == Cr.NS_ERROR_ILLEGAL_VALUE) {
michael@0 3795 update.errorCode = updates[0] ? CERT_ATTR_CHECK_FAILED_HAS_UPDATE
michael@0 3796 : CERT_ATTR_CHECK_FAILED_NO_UPDATE;
michael@0 3797 }
michael@0 3798 this._callback.onError(request, update);
michael@0 3799 }
michael@0 3800
michael@0 3801 this._callback = null;
michael@0 3802 this._request = null;
michael@0 3803 },
michael@0 3804
michael@0 3805 /**
michael@0 3806 * There was an error of some kind during the XMLHttpRequest
michael@0 3807 * @param event
michael@0 3808 * The nsIDOMEvent for the error
michael@0 3809 */
michael@0 3810 onError: function UC_onError(event) {
michael@0 3811 var request = event.target;
michael@0 3812 var status = this._getChannelStatus(request);
michael@0 3813 LOG("Checker:onError - request.status: " + status);
michael@0 3814
michael@0 3815 // If we can't find an error string specific to this status code,
michael@0 3816 // just use the 200 message from above, which means everything
michael@0 3817 // "looks" fine but there was probably an XML error or a bogus file.
michael@0 3818 var update = new Update(null);
michael@0 3819 update.errorCode = status;
michael@0 3820 update.statusText = getStatusTextFromCode(status, 200);
michael@0 3821
michael@0 3822 if (status == Cr.NS_ERROR_OFFLINE) {
michael@0 3823 // We use a separate constant here because nsIUpdate.errorCode is signed
michael@0 3824 update.errorCode = NETWORK_ERROR_OFFLINE;
michael@0 3825 } else if (this._isHttpStatusCode(status)) {
michael@0 3826 update.errorCode = HTTP_ERROR_OFFSET + status;
michael@0 3827 }
michael@0 3828
michael@0 3829 this._callback.onError(request, update);
michael@0 3830
michael@0 3831 this._request = null;
michael@0 3832 },
michael@0 3833
michael@0 3834 /**
michael@0 3835 * Whether or not we are allowed to do update checking.
michael@0 3836 */
michael@0 3837 _enabled: true,
michael@0 3838 get enabled() {
michael@0 3839 if (!gMetroUpdatesEnabled) {
michael@0 3840 return false;
michael@0 3841 }
michael@0 3842
michael@0 3843 return getPref("getBoolPref", PREF_APP_UPDATE_ENABLED, true) &&
michael@0 3844 gCanCheckForUpdates && hasUpdateMutex() && this._enabled;
michael@0 3845 },
michael@0 3846
michael@0 3847 /**
michael@0 3848 * See nsIUpdateService.idl
michael@0 3849 */
michael@0 3850 stopChecking: function UC_stopChecking(duration) {
michael@0 3851 // Always stop the current check
michael@0 3852 if (this._request)
michael@0 3853 this._request.abort();
michael@0 3854
michael@0 3855 switch (duration) {
michael@0 3856 case Ci.nsIUpdateChecker.CURRENT_SESSION:
michael@0 3857 this._enabled = false;
michael@0 3858 break;
michael@0 3859 case Ci.nsIUpdateChecker.ANY_CHECKS:
michael@0 3860 this._enabled = false;
michael@0 3861 Services.prefs.setBoolPref(PREF_APP_UPDATE_ENABLED, this._enabled);
michael@0 3862 break;
michael@0 3863 }
michael@0 3864
michael@0 3865 this._callback = null;
michael@0 3866 },
michael@0 3867
michael@0 3868 classID: Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}"),
michael@0 3869 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateChecker])
michael@0 3870 };
michael@0 3871
michael@0 3872 /**
michael@0 3873 * Manages the download of updates
michael@0 3874 * @param background
michael@0 3875 * Whether or not this downloader is operating in background
michael@0 3876 * update mode.
michael@0 3877 * @param updateService
michael@0 3878 * The update service that created this downloader.
michael@0 3879 * @constructor
michael@0 3880 */
michael@0 3881 function Downloader(background, updateService) {
michael@0 3882 LOG("Creating Downloader");
michael@0 3883 this.background = background;
michael@0 3884 this.updateService = updateService;
michael@0 3885 }
michael@0 3886 Downloader.prototype = {
michael@0 3887 /**
michael@0 3888 * The nsIUpdatePatch that we are downloading
michael@0 3889 */
michael@0 3890 _patch: null,
michael@0 3891
michael@0 3892 /**
michael@0 3893 * The nsIUpdate that we are downloading
michael@0 3894 */
michael@0 3895 _update: null,
michael@0 3896
michael@0 3897 /**
michael@0 3898 * The nsIIncrementalDownload object handling the download
michael@0 3899 */
michael@0 3900 _request: null,
michael@0 3901
michael@0 3902 /**
michael@0 3903 * Whether or not the update being downloaded is a complete replacement of
michael@0 3904 * the user's existing installation or a patch representing the difference
michael@0 3905 * between the new version and the previous version.
michael@0 3906 */
michael@0 3907 isCompleteUpdate: null,
michael@0 3908
michael@0 3909 /**
michael@0 3910 * Cancels the active download.
michael@0 3911 */
michael@0 3912 cancel: function Downloader_cancel(cancelError) {
michael@0 3913 LOG("Downloader: cancel");
michael@0 3914 if (cancelError === undefined) {
michael@0 3915 cancelError = Cr.NS_BINDING_ABORTED;
michael@0 3916 }
michael@0 3917 if (this._request && this._request instanceof Ci.nsIRequest) {
michael@0 3918 this._request.cancel(cancelError);
michael@0 3919 }
michael@0 3920 releaseSDCardMountLock();
michael@0 3921 },
michael@0 3922
michael@0 3923 /**
michael@0 3924 * Whether or not a patch has been downloaded and staged for installation.
michael@0 3925 */
michael@0 3926 get patchIsStaged() {
michael@0 3927 var readState = readStatusFile(getUpdatesDir());
michael@0 3928 // Note that if we decide to download and apply new updates after another
michael@0 3929 // update has been successfully applied in the background, we need to stop
michael@0 3930 // checking for the APPLIED state here.
michael@0 3931 return readState == STATE_PENDING || readState == STATE_PENDING_SVC ||
michael@0 3932 readState == STATE_APPLIED || readState == STATE_APPLIED_SVC;
michael@0 3933 },
michael@0 3934
michael@0 3935 /**
michael@0 3936 * Verify the downloaded file. We assume that the download is complete at
michael@0 3937 * this point.
michael@0 3938 */
michael@0 3939 _verifyDownload: function Downloader__verifyDownload() {
michael@0 3940 LOG("Downloader:_verifyDownload called");
michael@0 3941 if (!this._request)
michael@0 3942 return false;
michael@0 3943
michael@0 3944 var destination = this._request.destination;
michael@0 3945
michael@0 3946 // Ensure that the file size matches the expected file size.
michael@0 3947 if (destination.fileSize != this._patch.size) {
michael@0 3948 LOG("Downloader:_verifyDownload downloaded size != expected size.");
michael@0 3949 return false;
michael@0 3950 }
michael@0 3951
michael@0 3952 LOG("Downloader:_verifyDownload downloaded size == expected size.");
michael@0 3953 var fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
michael@0 3954 createInstance(Ci.nsIFileInputStream);
michael@0 3955 fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
michael@0 3956
michael@0 3957 try {
michael@0 3958 var hash = Cc["@mozilla.org/security/hash;1"].
michael@0 3959 createInstance(Ci.nsICryptoHash);
michael@0 3960 var hashFunction = Ci.nsICryptoHash[this._patch.hashFunction.toUpperCase()];
michael@0 3961 if (hashFunction == undefined)
michael@0 3962 throw Cr.NS_ERROR_UNEXPECTED;
michael@0 3963 hash.init(hashFunction);
michael@0 3964 hash.updateFromStream(fileStream, -1);
michael@0 3965 // NOTE: For now, we assume that the format of _patch.hashValue is hex
michael@0 3966 // encoded binary (such as what is typically output by programs like
michael@0 3967 // sha1sum). In the future, this may change to base64 depending on how
michael@0 3968 // we choose to compute these hashes.
michael@0 3969 digest = binaryToHex(hash.finish(false));
michael@0 3970 } catch (e) {
michael@0 3971 LOG("Downloader:_verifyDownload - failed to compute hash of the " +
michael@0 3972 "downloaded update archive");
michael@0 3973 digest = "";
michael@0 3974 }
michael@0 3975
michael@0 3976 fileStream.close();
michael@0 3977
michael@0 3978 if (digest == this._patch.hashValue.toLowerCase()) {
michael@0 3979 LOG("Downloader:_verifyDownload hashes match.");
michael@0 3980 return true;
michael@0 3981 }
michael@0 3982
michael@0 3983 LOG("Downloader:_verifyDownload hashes do not match. ");
michael@0 3984 return false;
michael@0 3985 },
michael@0 3986
michael@0 3987 /**
michael@0 3988 * Select the patch to use given the current state of updateDir and the given
michael@0 3989 * set of update patches.
michael@0 3990 * @param update
michael@0 3991 * A nsIUpdate object to select a patch from
michael@0 3992 * @param updateDir
michael@0 3993 * A nsIFile representing the update directory
michael@0 3994 * @return A nsIUpdatePatch object to download
michael@0 3995 */
michael@0 3996 _selectPatch: function Downloader__selectPatch(update, updateDir) {
michael@0 3997 // Given an update to download, we will always try to download the patch
michael@0 3998 // for a partial update over the patch for a full update.
michael@0 3999
michael@0 4000 /**
michael@0 4001 * Return the first UpdatePatch with the given type.
michael@0 4002 * @param type
michael@0 4003 * The type of the patch ("complete" or "partial")
michael@0 4004 * @return A nsIUpdatePatch object matching the type specified
michael@0 4005 */
michael@0 4006 function getPatchOfType(type) {
michael@0 4007 for (var i = 0; i < update.patchCount; ++i) {
michael@0 4008 var patch = update.getPatchAt(i);
michael@0 4009 if (patch && patch.type == type)
michael@0 4010 return patch;
michael@0 4011 }
michael@0 4012 return null;
michael@0 4013 }
michael@0 4014
michael@0 4015 // Look to see if any of the patches in the Update object has been
michael@0 4016 // pre-selected for download, otherwise we must figure out which one
michael@0 4017 // to select ourselves.
michael@0 4018 var selectedPatch = update.selectedPatch;
michael@0 4019
michael@0 4020 var state = readStatusFile(updateDir);
michael@0 4021
michael@0 4022 // If this is a patch that we know about, then select it. If it is a patch
michael@0 4023 // that we do not know about, then remove it and use our default logic.
michael@0 4024 var useComplete = false;
michael@0 4025 if (selectedPatch) {
michael@0 4026 LOG("Downloader:_selectPatch - found existing patch with state: " +
michael@0 4027 state);
michael@0 4028 switch (state) {
michael@0 4029 case STATE_DOWNLOADING:
michael@0 4030 LOG("Downloader:_selectPatch - resuming download");
michael@0 4031 return selectedPatch;
michael@0 4032 #ifdef MOZ_WIDGET_GONK
michael@0 4033 case STATE_PENDING:
michael@0 4034 case STATE_APPLYING:
michael@0 4035 LOG("Downloader:_selectPatch - resuming interrupted apply");
michael@0 4036 return selectedPatch;
michael@0 4037 case STATE_APPLIED:
michael@0 4038 LOG("Downloader:_selectPatch - already downloaded and staged");
michael@0 4039 return null;
michael@0 4040 #else
michael@0 4041 case STATE_PENDING_SVC:
michael@0 4042 case STATE_PENDING:
michael@0 4043 LOG("Downloader:_selectPatch - already downloaded and staged");
michael@0 4044 return null;
michael@0 4045 #endif
michael@0 4046 default:
michael@0 4047 // Something went wrong when we tried to apply the previous patch.
michael@0 4048 // Try the complete patch next time.
michael@0 4049 if (update && selectedPatch.type == "partial") {
michael@0 4050 useComplete = true;
michael@0 4051 } else {
michael@0 4052 // This is a pretty fatal error. Just bail.
michael@0 4053 LOG("Downloader:_selectPatch - failed to apply complete patch!");
michael@0 4054 writeStatusFile(updateDir, STATE_NONE);
michael@0 4055 writeVersionFile(getUpdatesDir(), null);
michael@0 4056 return null;
michael@0 4057 }
michael@0 4058 }
michael@0 4059
michael@0 4060 selectedPatch = null;
michael@0 4061 }
michael@0 4062
michael@0 4063 // If we were not able to discover an update from a previous download, we
michael@0 4064 // select the best patch from the given set.
michael@0 4065 var partialPatch = getPatchOfType("partial");
michael@0 4066 if (!useComplete)
michael@0 4067 selectedPatch = partialPatch;
michael@0 4068 if (!selectedPatch) {
michael@0 4069 if (partialPatch)
michael@0 4070 partialPatch.selected = false;
michael@0 4071 selectedPatch = getPatchOfType("complete");
michael@0 4072 }
michael@0 4073
michael@0 4074 // Restore the updateDir since we may have deleted it.
michael@0 4075 updateDir = getUpdatesDir();
michael@0 4076
michael@0 4077 // if update only contains a partial patch, selectedPatch == null here if
michael@0 4078 // the partial patch has been attempted and fails and we're trying to get a
michael@0 4079 // complete patch
michael@0 4080 if (selectedPatch)
michael@0 4081 selectedPatch.selected = true;
michael@0 4082
michael@0 4083 update.isCompleteUpdate = useComplete;
michael@0 4084
michael@0 4085 // Reset the Active Update object on the Update Manager and flush the
michael@0 4086 // Active Update DB.
michael@0 4087 var um = Cc["@mozilla.org/updates/update-manager;1"].
michael@0 4088 getService(Ci.nsIUpdateManager);
michael@0 4089 um.activeUpdate = update;
michael@0 4090
michael@0 4091 return selectedPatch;
michael@0 4092 },
michael@0 4093
michael@0 4094 /**
michael@0 4095 * Whether or not we are currently downloading something.
michael@0 4096 */
michael@0 4097 get isBusy() {
michael@0 4098 return this._request != null;
michael@0 4099 },
michael@0 4100
michael@0 4101 /**
michael@0 4102 * Get the nsIFile to use for downloading the active update's selected patch
michael@0 4103 */
michael@0 4104 _getUpdateArchiveFile: function Downloader__getUpdateArchiveFile() {
michael@0 4105 var updateArchive;
michael@0 4106 #ifdef USE_UPDATE_ARCHIVE_DIR
michael@0 4107 try {
michael@0 4108 updateArchive = FileUtils.getDir(KEY_UPDATE_ARCHIVE_DIR, [], true);
michael@0 4109 } catch (e) {
michael@0 4110 return null;
michael@0 4111 }
michael@0 4112 #else
michael@0 4113 updateArchive = getUpdatesDir().clone();
michael@0 4114 #endif
michael@0 4115
michael@0 4116 updateArchive.append(FILE_UPDATE_ARCHIVE);
michael@0 4117 return updateArchive;
michael@0 4118 },
michael@0 4119
michael@0 4120 /**
michael@0 4121 * Download and stage the given update.
michael@0 4122 * @param update
michael@0 4123 * A nsIUpdate object to download a patch for. Cannot be null.
michael@0 4124 */
michael@0 4125 downloadUpdate: function Downloader_downloadUpdate(update) {
michael@0 4126 LOG("UpdateService:_downloadUpdate");
michael@0 4127 if (!update)
michael@0 4128 throw Cr.NS_ERROR_NULL_POINTER;
michael@0 4129
michael@0 4130 var updateDir = getUpdatesDir();
michael@0 4131
michael@0 4132 this._update = update;
michael@0 4133
michael@0 4134 // This function may return null, which indicates that there are no patches
michael@0 4135 // to download.
michael@0 4136 this._patch = this._selectPatch(update, updateDir);
michael@0 4137 if (!this._patch) {
michael@0 4138 LOG("Downloader:downloadUpdate - no patch to download");
michael@0 4139 return readStatusFile(updateDir);
michael@0 4140 }
michael@0 4141 this.isCompleteUpdate = this._patch.type == "complete";
michael@0 4142
michael@0 4143 var patchFile = null;
michael@0 4144
michael@0 4145 #ifdef MOZ_WIDGET_GONK
michael@0 4146 let status = readStatusFile(updateDir);
michael@0 4147 if (isInterruptedUpdate(status)) {
michael@0 4148 LOG("Downloader:downloadUpdate - interruptted update");
michael@0 4149 // The update was interrupted. Try to locate the existing patch file.
michael@0 4150 // For an interrupted download, this allows a resume rather than a
michael@0 4151 // re-download.
michael@0 4152 patchFile = getFileFromUpdateLink(updateDir);
michael@0 4153 if (!patchFile) {
michael@0 4154 // No link file. We'll just assume that the update.mar is in the
michael@0 4155 // update directory.
michael@0 4156 patchFile = updateDir.clone();
michael@0 4157 patchFile.append(FILE_UPDATE_ARCHIVE);
michael@0 4158 }
michael@0 4159 if (patchFile.exists()) {
michael@0 4160 LOG("Downloader:downloadUpdate - resuming with patchFile " + patchFile.path);
michael@0 4161 if (patchFile.fileSize == this._patch.size) {
michael@0 4162 LOG("Downloader:downloadUpdate - patchFile appears to be fully downloaded");
michael@0 4163 // Bump the status along so that we don't try to redownload again.
michael@0 4164 status = STATE_PENDING;
michael@0 4165 }
michael@0 4166 } else {
michael@0 4167 LOG("Downloader:downloadUpdate - patchFile " + patchFile.path +
michael@0 4168 " doesn't exist - performing full download");
michael@0 4169 // The patchfile doesn't exist, we might as well treat this like
michael@0 4170 // a new download.
michael@0 4171 patchFile = null;
michael@0 4172 }
michael@0 4173 if (patchFile && (status != STATE_DOWNLOADING)) {
michael@0 4174 // It looks like the patch was downloaded, but got interrupted while it
michael@0 4175 // was being verified or applied. So we'll fake the downloading portion.
michael@0 4176
michael@0 4177 writeStatusFile(updateDir, STATE_PENDING);
michael@0 4178
michael@0 4179 // Since the code expects the onStopRequest callback to happen
michael@0 4180 // asynchronously (And you have to call AUS_addDownloadListener
michael@0 4181 // after calling AUS_downloadUpdate) we need to defer this.
michael@0 4182
michael@0 4183 this._downloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 4184 this._downloadTimer.initWithCallback(function() {
michael@0 4185 this._downloadTimer = null;
michael@0 4186 // Send a fake onStopRequest. Filling in the destination allows
michael@0 4187 // _verifyDownload to work, and then the update will be applied.
michael@0 4188 this._request = {destination: patchFile};
michael@0 4189 this.onStopRequest(this._request, null, Cr.NS_OK);
michael@0 4190 }.bind(this), 0, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 4191
michael@0 4192 // Returning STATE_DOWNLOADING makes UpdatePrompt think we're
michael@0 4193 // downloading. The onStopRequest that we spoofed above will make it
michael@0 4194 // look like the download finished.
michael@0 4195 return STATE_DOWNLOADING;
michael@0 4196 }
michael@0 4197 }
michael@0 4198 #endif
michael@0 4199 if (!patchFile) {
michael@0 4200 // Find a place to put the patchfile that we're going to download.
michael@0 4201 patchFile = this._getUpdateArchiveFile();
michael@0 4202 }
michael@0 4203 if (!patchFile) {
michael@0 4204 return STATE_NONE;
michael@0 4205 }
michael@0 4206
michael@0 4207 #ifdef MOZ_WIDGET_GONK
michael@0 4208 if (patchFile.path.indexOf(updateDir.path) != 0) {
michael@0 4209 // The patchFile is in a directory which is different from the
michael@0 4210 // updateDir, create a link file.
michael@0 4211 writeLinkFile(updateDir, patchFile);
michael@0 4212
michael@0 4213 if (!isInterruptedUpdate(status) && patchFile.exists()) {
michael@0 4214 // Remove stale patchFile
michael@0 4215 patchFile.remove(false);
michael@0 4216 }
michael@0 4217 }
michael@0 4218 #endif
michael@0 4219
michael@0 4220 var uri = Services.io.newURI(this._patch.URL, null, null);
michael@0 4221
michael@0 4222 this._request = Cc["@mozilla.org/network/incremental-download;1"].
michael@0 4223 createInstance(Ci.nsIIncrementalDownload);
michael@0 4224
michael@0 4225 LOG("Downloader:downloadUpdate - downloading from " + uri.spec + " to " +
michael@0 4226 patchFile.path);
michael@0 4227 var interval = this.background ? getPref("getIntPref",
michael@0 4228 PREF_APP_UPDATE_BACKGROUND_INTERVAL,
michael@0 4229 DOWNLOAD_BACKGROUND_INTERVAL)
michael@0 4230 : DOWNLOAD_FOREGROUND_INTERVAL;
michael@0 4231 this._request.init(uri, patchFile, DOWNLOAD_CHUNK_SIZE, interval);
michael@0 4232 this._request.start(this, null);
michael@0 4233
michael@0 4234 writeStatusFile(updateDir, STATE_DOWNLOADING);
michael@0 4235 this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
michael@0 4236 this._patch.state = STATE_DOWNLOADING;
michael@0 4237 var um = Cc["@mozilla.org/updates/update-manager;1"].
michael@0 4238 getService(Ci.nsIUpdateManager);
michael@0 4239 um.saveUpdates();
michael@0 4240 return STATE_DOWNLOADING;
michael@0 4241 },
michael@0 4242
michael@0 4243 /**
michael@0 4244 * An array of download listeners to notify when we receive
michael@0 4245 * nsIRequestObserver or nsIProgressEventSink method calls.
michael@0 4246 */
michael@0 4247 _listeners: [],
michael@0 4248
michael@0 4249 /**
michael@0 4250 * Adds a listener to the download process
michael@0 4251 * @param listener
michael@0 4252 * A download listener, implementing nsIRequestObserver and
michael@0 4253 * nsIProgressEventSink
michael@0 4254 */
michael@0 4255 addDownloadListener: function Downloader_addDownloadListener(listener) {
michael@0 4256 for (var i = 0; i < this._listeners.length; ++i) {
michael@0 4257 if (this._listeners[i] == listener)
michael@0 4258 return;
michael@0 4259 }
michael@0 4260 this._listeners.push(listener);
michael@0 4261 },
michael@0 4262
michael@0 4263 /**
michael@0 4264 * Removes a download listener
michael@0 4265 * @param listener
michael@0 4266 * The listener to remove.
michael@0 4267 */
michael@0 4268 removeDownloadListener: function Downloader_removeDownloadListener(listener) {
michael@0 4269 for (var i = 0; i < this._listeners.length; ++i) {
michael@0 4270 if (this._listeners[i] == listener) {
michael@0 4271 this._listeners.splice(i, 1);
michael@0 4272 return;
michael@0 4273 }
michael@0 4274 }
michael@0 4275 },
michael@0 4276
michael@0 4277 /**
michael@0 4278 * When the async request begins
michael@0 4279 * @param request
michael@0 4280 * The nsIRequest object for the transfer
michael@0 4281 * @param context
michael@0 4282 * Additional data
michael@0 4283 */
michael@0 4284 onStartRequest: function Downloader_onStartRequest(request, context) {
michael@0 4285 if (request instanceof Ci.nsIIncrementalDownload)
michael@0 4286 LOG("Downloader:onStartRequest - original URI spec: " + request.URI.spec +
michael@0 4287 ", final URI spec: " + request.finalURI.spec);
michael@0 4288 // Always set finalURL in onStartRequest since it can change.
michael@0 4289 this._patch.finalURL = request.finalURI.spec;
michael@0 4290 var um = Cc["@mozilla.org/updates/update-manager;1"].
michael@0 4291 getService(Ci.nsIUpdateManager);
michael@0 4292 um.saveUpdates();
michael@0 4293
michael@0 4294 var listeners = this._listeners.concat();
michael@0 4295 var listenerCount = listeners.length;
michael@0 4296 for (var i = 0; i < listenerCount; ++i)
michael@0 4297 listeners[i].onStartRequest(request, context);
michael@0 4298 },
michael@0 4299
michael@0 4300 /**
michael@0 4301 * When new data has been downloaded
michael@0 4302 * @param request
michael@0 4303 * The nsIRequest object for the transfer
michael@0 4304 * @param context
michael@0 4305 * Additional data
michael@0 4306 * @param progress
michael@0 4307 * The current number of bytes transferred
michael@0 4308 * @param maxProgress
michael@0 4309 * The total number of bytes that must be transferred
michael@0 4310 */
michael@0 4311 onProgress: function Downloader_onProgress(request, context, progress,
michael@0 4312 maxProgress) {
michael@0 4313 LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
michael@0 4314
michael@0 4315 if (progress > this._patch.size) {
michael@0 4316 LOG("Downloader:onProgress - progress: " + progress +
michael@0 4317 " is higher than patch size: " + this._patch.size);
michael@0 4318 // It's important that we use a different code than
michael@0 4319 // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
michael@0 4320 // between a hash error and a wrong download error.
michael@0 4321 this.cancel(Cr.NS_ERROR_UNEXPECTED);
michael@0 4322 return;
michael@0 4323 }
michael@0 4324
michael@0 4325 if (maxProgress != this._patch.size) {
michael@0 4326 LOG("Downloader:onProgress - maxProgress: " + maxProgress +
michael@0 4327 " is not equal to expectd patch size: " + this._patch.size);
michael@0 4328 // It's important that we use a different code than
michael@0 4329 // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
michael@0 4330 // between a hash error and a wrong download error.
michael@0 4331 this.cancel(Cr.NS_ERROR_UNEXPECTED);
michael@0 4332 return;
michael@0 4333 }
michael@0 4334
michael@0 4335 var listeners = this._listeners.concat();
michael@0 4336 var listenerCount = listeners.length;
michael@0 4337 for (var i = 0; i < listenerCount; ++i) {
michael@0 4338 var listener = listeners[i];
michael@0 4339 if (listener instanceof Ci.nsIProgressEventSink)
michael@0 4340 listener.onProgress(request, context, progress, maxProgress);
michael@0 4341 }
michael@0 4342 this.updateService._consecutiveSocketErrors = 0;
michael@0 4343 },
michael@0 4344
michael@0 4345 /**
michael@0 4346 * When we have new status text
michael@0 4347 * @param request
michael@0 4348 * The nsIRequest object for the transfer
michael@0 4349 * @param context
michael@0 4350 * Additional data
michael@0 4351 * @param status
michael@0 4352 * A status code
michael@0 4353 * @param statusText
michael@0 4354 * Human readable version of |status|
michael@0 4355 */
michael@0 4356 onStatus: function Downloader_onStatus(request, context, status, statusText) {
michael@0 4357 LOG("Downloader:onStatus - status: " + status + ", statusText: " +
michael@0 4358 statusText);
michael@0 4359
michael@0 4360 var listeners = this._listeners.concat();
michael@0 4361 var listenerCount = listeners.length;
michael@0 4362 for (var i = 0; i < listenerCount; ++i) {
michael@0 4363 var listener = listeners[i];
michael@0 4364 if (listener instanceof Ci.nsIProgressEventSink)
michael@0 4365 listener.onStatus(request, context, status, statusText);
michael@0 4366 }
michael@0 4367 },
michael@0 4368
michael@0 4369 /**
michael@0 4370 * When data transfer ceases
michael@0 4371 * @param request
michael@0 4372 * The nsIRequest object for the transfer
michael@0 4373 * @param context
michael@0 4374 * Additional data
michael@0 4375 * @param status
michael@0 4376 * Status code containing the reason for the cessation.
michael@0 4377 */
michael@0 4378 onStopRequest: function Downloader_onStopRequest(request, context, status) {
michael@0 4379 if (request instanceof Ci.nsIIncrementalDownload)
michael@0 4380 LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
michael@0 4381 ", final URI spec: " + request.finalURI.spec + ", status: " + status);
michael@0 4382
michael@0 4383 // XXX ehsan shouldShowPrompt should always be false here.
michael@0 4384 // But what happens when there is already a UI showing?
michael@0 4385 var state = this._patch.state;
michael@0 4386 var shouldShowPrompt = false;
michael@0 4387 var shouldRegisterOnlineObserver = false;
michael@0 4388 var shouldRetrySoon = false;
michael@0 4389 var deleteActiveUpdate = false;
michael@0 4390 var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_RETRY_TIMEOUT,
michael@0 4391 DEFAULT_UPDATE_RETRY_TIMEOUT);
michael@0 4392 var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_ERRORS,
michael@0 4393 DEFAULT_SOCKET_MAX_ERRORS);
michael@0 4394 LOG("Downloader:onStopRequest - status: " + status + ", " +
michael@0 4395 "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
michael@0 4396 "max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
michael@0 4397 if (Components.isSuccessCode(status)) {
michael@0 4398 if (this._verifyDownload()) {
michael@0 4399 state = shouldUseService() ? STATE_PENDING_SVC : STATE_PENDING;
michael@0 4400 if (this.background) {
michael@0 4401 shouldShowPrompt = !getCanStageUpdates();
michael@0 4402 }
michael@0 4403
michael@0 4404 // Tell the updater.exe we're ready to apply.
michael@0 4405 writeStatusFile(getUpdatesDir(), state);
michael@0 4406 writeVersionFile(getUpdatesDir(), this._update.appVersion);
michael@0 4407 this._update.installDate = (new Date()).getTime();
michael@0 4408 this._update.statusText = gUpdateBundle.GetStringFromName("installPending");
michael@0 4409 }
michael@0 4410 else {
michael@0 4411 LOG("Downloader:onStopRequest - download verification failed");
michael@0 4412 state = STATE_DOWNLOAD_FAILED;
michael@0 4413 status = Cr.NS_ERROR_CORRUPTED_CONTENT;
michael@0 4414
michael@0 4415 // Yes, this code is a string.
michael@0 4416 const vfCode = "verification_failed";
michael@0 4417 var message = getStatusTextFromCode(vfCode, vfCode);
michael@0 4418 this._update.statusText = message;
michael@0 4419
michael@0 4420 if (this._update.isCompleteUpdate || this._update.patchCount != 2)
michael@0 4421 deleteActiveUpdate = true;
michael@0 4422
michael@0 4423 // Destroy the updates directory, since we're done with it.
michael@0 4424 cleanUpUpdatesDir();
michael@0 4425 }
michael@0 4426 } else if (status == Cr.NS_ERROR_OFFLINE) {
michael@0 4427 // Register an online observer to try again.
michael@0 4428 // The online observer will continue the incremental download by
michael@0 4429 // calling downloadUpdate on the active update which continues
michael@0 4430 // downloading the file from where it was.
michael@0 4431 LOG("Downloader:onStopRequest - offline, register online observer: true");
michael@0 4432 shouldRegisterOnlineObserver = true;
michael@0 4433 deleteActiveUpdate = false;
michael@0 4434 // Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED, and
michael@0 4435 // NS_ERROR_NET_RESET can be returned when disconnecting the internet while
michael@0 4436 // a download of a MAR is in progress. There may be others but I have not
michael@0 4437 // encountered them during testing.
michael@0 4438 } else if ((status == Cr.NS_ERROR_NET_TIMEOUT ||
michael@0 4439 status == Cr.NS_ERROR_CONNECTION_REFUSED ||
michael@0 4440 status == Cr.NS_ERROR_NET_RESET) &&
michael@0 4441 this.updateService._consecutiveSocketErrors < maxFail) {
michael@0 4442 LOG("Downloader:onStopRequest - socket error, shouldRetrySoon: true");
michael@0 4443 shouldRetrySoon = true;
michael@0 4444 deleteActiveUpdate = false;
michael@0 4445 } else if (status != Cr.NS_BINDING_ABORTED &&
michael@0 4446 status != Cr.NS_ERROR_ABORT &&
michael@0 4447 status != Cr.NS_ERROR_DOCUMENT_NOT_CACHED) {
michael@0 4448 LOG("Downloader:onStopRequest - non-verification failure");
michael@0 4449 // Some sort of other failure, log this in the |statusText| property
michael@0 4450 state = STATE_DOWNLOAD_FAILED;
michael@0 4451
michael@0 4452 // XXXben - if |request| (The Incremental Download) provided a means
michael@0 4453 // for accessing the http channel we could do more here.
michael@0 4454
michael@0 4455 this._update.statusText = getStatusTextFromCode(status,
michael@0 4456 Cr.NS_BINDING_FAILED);
michael@0 4457
michael@0 4458 #ifdef MOZ_WIDGET_GONK
michael@0 4459 // bug891009: On FirefoxOS, manaully retry OTA download will reuse
michael@0 4460 // the Update object. We need to remove selected patch so that download
michael@0 4461 // can be triggered again successfully.
michael@0 4462 this._update.selectedPatch.selected = false;
michael@0 4463 #endif
michael@0 4464
michael@0 4465 // Destroy the updates directory, since we're done with it.
michael@0 4466 cleanUpUpdatesDir();
michael@0 4467
michael@0 4468 deleteActiveUpdate = true;
michael@0 4469 }
michael@0 4470 LOG("Downloader:onStopRequest - setting state to: " + state);
michael@0 4471 this._patch.state = state;
michael@0 4472 var um = Cc["@mozilla.org/updates/update-manager;1"].
michael@0 4473 getService(Ci.nsIUpdateManager);
michael@0 4474 if (deleteActiveUpdate) {
michael@0 4475 this._update.installDate = (new Date()).getTime();
michael@0 4476 um.activeUpdate = null;
michael@0 4477 }
michael@0 4478 else {
michael@0 4479 if (um.activeUpdate)
michael@0 4480 um.activeUpdate.state = state;
michael@0 4481 }
michael@0 4482 um.saveUpdates();
michael@0 4483
michael@0 4484 // Only notify listeners about the stopped state if we
michael@0 4485 // aren't handling an internal retry.
michael@0 4486 if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
michael@0 4487 var listeners = this._listeners.concat();
michael@0 4488 var listenerCount = listeners.length;
michael@0 4489 for (var i = 0; i < listenerCount; ++i) {
michael@0 4490 listeners[i].onStopRequest(request, context, status);
michael@0 4491 }
michael@0 4492 }
michael@0 4493
michael@0 4494 this._request = null;
michael@0 4495
michael@0 4496 if (state == STATE_DOWNLOAD_FAILED) {
michael@0 4497 var allFailed = true;
michael@0 4498 // Check if there is a complete update patch that can be downloaded.
michael@0 4499 if (!this._update.isCompleteUpdate && this._update.patchCount == 2) {
michael@0 4500 LOG("Downloader:onStopRequest - verification of patch failed, " +
michael@0 4501 "downloading complete update patch");
michael@0 4502 this._update.isCompleteUpdate = true;
michael@0 4503 let updateStatus = this.downloadUpdate(this._update);
michael@0 4504
michael@0 4505 if (updateStatus == STATE_NONE) {
michael@0 4506 cleanupActiveUpdate();
michael@0 4507 } else {
michael@0 4508 allFailed = false;
michael@0 4509 }
michael@0 4510 }
michael@0 4511
michael@0 4512 if (allFailed) {
michael@0 4513 LOG("Downloader:onStopRequest - all update patch downloads failed");
michael@0 4514 // If the update UI is not open (e.g. the user closed the window while
michael@0 4515 // downloading) and if at any point this was a foreground download
michael@0 4516 // notify the user about the error. If the update was a background
michael@0 4517 // update there is no notification since the user won't be expecting it.
michael@0 4518 if (!Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME)) {
michael@0 4519 try {
michael@0 4520 this._update.QueryInterface(Ci.nsIWritablePropertyBag);
michael@0 4521 var fgdl = this._update.getProperty("foregroundDownload");
michael@0 4522 }
michael@0 4523 catch (e) {
michael@0 4524 }
michael@0 4525
michael@0 4526 if (fgdl == "true") {
michael@0 4527 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 4528 createInstance(Ci.nsIUpdatePrompt);
michael@0 4529 prompter.showUpdateError(this._update);
michael@0 4530 }
michael@0 4531 }
michael@0 4532
michael@0 4533 #ifdef MOZ_WIDGET_GONK
michael@0 4534 // We always forward errors in B2G, since Gaia controls the update UI
michael@0 4535 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 4536 createInstance(Ci.nsIUpdatePrompt);
michael@0 4537 prompter.showUpdateError(this._update);
michael@0 4538 #endif
michael@0 4539
michael@0 4540 // Prevent leaking the update object (bug 454964).
michael@0 4541 this._update = null;
michael@0 4542 }
michael@0 4543 // A complete download has been initiated or the failure was handled.
michael@0 4544 return;
michael@0 4545 }
michael@0 4546
michael@0 4547 if (state == STATE_PENDING || state == STATE_PENDING_SVC) {
michael@0 4548 if (getCanStageUpdates()) {
michael@0 4549 LOG("Downloader:onStopRequest - attempting to stage update: " +
michael@0 4550 this._update.name);
michael@0 4551
michael@0 4552 // Initiate the update in the background
michael@0 4553 try {
michael@0 4554 Cc["@mozilla.org/updates/update-processor;1"].
michael@0 4555 createInstance(Ci.nsIUpdateProcessor).
michael@0 4556 processUpdate(this._update);
michael@0 4557 } catch (e) {
michael@0 4558 // Fail gracefully in case the application does not support the update
michael@0 4559 // processor service.
michael@0 4560 LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
michael@0 4561 e);
michael@0 4562 if (this.background) {
michael@0 4563 shouldShowPrompt = true;
michael@0 4564 }
michael@0 4565 }
michael@0 4566 }
michael@0 4567 }
michael@0 4568
michael@0 4569 // Do this after *everything* else, since it will likely cause the app
michael@0 4570 // to shut down.
michael@0 4571 if (shouldShowPrompt) {
michael@0 4572 // Notify the user that an update has been downloaded and is ready for
michael@0 4573 // installation (i.e. that they should restart the application). We do
michael@0 4574 // not notify on failed update attempts.
michael@0 4575 var prompter = Cc["@mozilla.org/updates/update-prompt;1"].
michael@0 4576 createInstance(Ci.nsIUpdatePrompt);
michael@0 4577 prompter.showUpdateDownloaded(this._update, true);
michael@0 4578 }
michael@0 4579
michael@0 4580 if (shouldRegisterOnlineObserver) {
michael@0 4581 LOG("Downloader:onStopRequest - Registering online observer");
michael@0 4582 this.updateService._registerOnlineObserver();
michael@0 4583 } else if (shouldRetrySoon) {
michael@0 4584 LOG("Downloader:onStopRequest - Retrying soon");
michael@0 4585 this.updateService._consecutiveSocketErrors++;
michael@0 4586 if (this.updateService._retryTimer) {
michael@0 4587 this.updateService._retryTimer.cancel();
michael@0 4588 }
michael@0 4589 this.updateService._retryTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 4590 this.updateService._retryTimer.initWithCallback(function() {
michael@0 4591 this._attemptResume();
michael@0 4592 }.bind(this.updateService), retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 4593 } else {
michael@0 4594 // Prevent leaking the update object (bug 454964)
michael@0 4595 this._update = null;
michael@0 4596 }
michael@0 4597 },
michael@0 4598
michael@0 4599 /**
michael@0 4600 * See nsIInterfaceRequestor.idl
michael@0 4601 */
michael@0 4602 getInterface: function Downloader_getInterface(iid) {
michael@0 4603 // The network request may require proxy authentication, so provide the
michael@0 4604 // default nsIAuthPrompt if requested.
michael@0 4605 if (iid.equals(Ci.nsIAuthPrompt)) {
michael@0 4606 var prompt = Cc["@mozilla.org/network/default-auth-prompt;1"].
michael@0 4607 createInstance();
michael@0 4608 return prompt.QueryInterface(iid);
michael@0 4609 }
michael@0 4610 throw Cr.NS_NOINTERFACE;
michael@0 4611 },
michael@0 4612
michael@0 4613 QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
michael@0 4614 Ci.nsIProgressEventSink,
michael@0 4615 Ci.nsIInterfaceRequestor])
michael@0 4616 };
michael@0 4617
michael@0 4618 /**
michael@0 4619 * UpdatePrompt
michael@0 4620 * An object which can prompt the user with information about updates, request
michael@0 4621 * action, etc. Embedding clients can override this component with one that
michael@0 4622 * invokes a native front end.
michael@0 4623 * @constructor
michael@0 4624 */
michael@0 4625 function UpdatePrompt() {
michael@0 4626 }
michael@0 4627 UpdatePrompt.prototype = {
michael@0 4628 /**
michael@0 4629 * See nsIUpdateService.idl
michael@0 4630 */
michael@0 4631 checkForUpdates: function UP_checkForUpdates() {
michael@0 4632 if (this._getAltUpdateWindow())
michael@0 4633 return;
michael@0 4634
michael@0 4635 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
michael@0 4636 null, null);
michael@0 4637 },
michael@0 4638
michael@0 4639 /**
michael@0 4640 * See nsIUpdateService.idl
michael@0 4641 */
michael@0 4642 showUpdateAvailable: function UP_showUpdateAvailable(update) {
michael@0 4643 if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
michael@0 4644 this._getUpdateWindow() || this._getAltUpdateWindow())
michael@0 4645 return;
michael@0 4646
michael@0 4647 var stringsPrefix = "updateAvailable_" + update.type + ".";
michael@0 4648 var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
michael@0 4649 [update.name], 1);
michael@0 4650 var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
michael@0 4651 var imageUrl = "";
michael@0 4652 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
michael@0 4653 UPDATE_WINDOW_NAME, "updatesavailable", update,
michael@0 4654 title, text, imageUrl);
michael@0 4655 },
michael@0 4656
michael@0 4657 /**
michael@0 4658 * See nsIUpdateService.idl
michael@0 4659 */
michael@0 4660 showUpdateDownloaded: function UP_showUpdateDownloaded(update, background) {
michael@0 4661 if (this._getAltUpdateWindow())
michael@0 4662 return;
michael@0 4663
michael@0 4664 if (background) {
michael@0 4665 if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false))
michael@0 4666 return;
michael@0 4667
michael@0 4668 var stringsPrefix = "updateDownloaded_" + update.type + ".";
michael@0 4669 var title = gUpdateBundle.formatStringFromName(stringsPrefix + "title",
michael@0 4670 [update.name], 1);
michael@0 4671 var text = gUpdateBundle.GetStringFromName(stringsPrefix + "text");
michael@0 4672 var imageUrl = "";
michael@0 4673 this._showUnobtrusiveUI(null, URI_UPDATE_PROMPT_DIALOG, null,
michael@0 4674 UPDATE_WINDOW_NAME, "finishedBackground", update,
michael@0 4675 title, text, imageUrl);
michael@0 4676 } else {
michael@0 4677 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null,
michael@0 4678 UPDATE_WINDOW_NAME, "finishedBackground", update);
michael@0 4679 }
michael@0 4680 },
michael@0 4681
michael@0 4682 /**
michael@0 4683 * See nsIUpdateService.idl
michael@0 4684 */
michael@0 4685 showUpdateInstalled: function UP_showUpdateInstalled() {
michael@0 4686 if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
michael@0 4687 !getPref("getBoolPref", PREF_APP_UPDATE_SHOW_INSTALLED_UI, false) ||
michael@0 4688 this._getUpdateWindow())
michael@0 4689 return;
michael@0 4690
michael@0 4691 var page = "installed";
michael@0 4692 var win = this._getUpdateWindow();
michael@0 4693 if (win) {
michael@0 4694 if (page && "setCurrentPage" in win)
michael@0 4695 win.setCurrentPage(page);
michael@0 4696 win.focus();
michael@0 4697 }
michael@0 4698 else {
michael@0 4699 var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
michael@0 4700 var arg = Cc["@mozilla.org/supports-string;1"].
michael@0 4701 createInstance(Ci.nsISupportsString);
michael@0 4702 arg.data = page;
michael@0 4703 Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, null, openFeatures, arg);
michael@0 4704 }
michael@0 4705 },
michael@0 4706
michael@0 4707 /**
michael@0 4708 * See nsIUpdateService.idl
michael@0 4709 */
michael@0 4710 showUpdateError: function UP_showUpdateError(update) {
michael@0 4711 if (getPref("getBoolPref", PREF_APP_UPDATE_SILENT, false) ||
michael@0 4712 this._getAltUpdateWindow())
michael@0 4713 return;
michael@0 4714
michael@0 4715 // In some cases, we want to just show a simple alert dialog:
michael@0 4716 if (update.state == STATE_FAILED &&
michael@0 4717 (update.errorCode == WRITE_ERROR ||
michael@0 4718 update.errorCode == WRITE_ERROR_ACCESS_DENIED ||
michael@0 4719 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_SIGNALED ||
michael@0 4720 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPROCESSFORPID ||
michael@0 4721 update.errorCode == WRITE_ERROR_SHARING_VIOLATION_NOPID ||
michael@0 4722 update.errorCode == WRITE_ERROR_CALLBACK_APP ||
michael@0 4723 update.errorCode == FILESYSTEM_MOUNT_READWRITE_ERROR ||
michael@0 4724 update.errorCode == FOTA_GENERAL_ERROR ||
michael@0 4725 update.errorCode == FOTA_FILE_OPERATION_ERROR ||
michael@0 4726 update.errorCode == FOTA_RECOVERY_ERROR ||
michael@0 4727 update.errorCode == FOTA_UNKNOWN_ERROR)) {
michael@0 4728 var title = gUpdateBundle.GetStringFromName("updaterIOErrorTitle");
michael@0 4729 var text = gUpdateBundle.formatStringFromName("updaterIOErrorMsg",
michael@0 4730 [Services.appinfo.name,
michael@0 4731 Services.appinfo.name], 2);
michael@0 4732 Services.ww.getNewPrompter(null).alert(title, text);
michael@0 4733 return;
michael@0 4734 }
michael@0 4735
michael@0 4736 if (update.errorCode == CERT_ATTR_CHECK_FAILED_NO_UPDATE ||
michael@0 4737 update.errorCode == CERT_ATTR_CHECK_FAILED_HAS_UPDATE ||
michael@0 4738 update.errorCode == BACKGROUNDCHECK_MULTIPLE_FAILURES) {
michael@0 4739 this._showUIWhenIdle(null, URI_UPDATE_PROMPT_DIALOG, null,
michael@0 4740 UPDATE_WINDOW_NAME, null, update);
michael@0 4741 return;
michael@0 4742 }
michael@0 4743
michael@0 4744 this._showUI(null, URI_UPDATE_PROMPT_DIALOG, null, UPDATE_WINDOW_NAME,
michael@0 4745 "errors", update);
michael@0 4746 },
michael@0 4747
michael@0 4748 /**
michael@0 4749 * See nsIUpdateService.idl
michael@0 4750 */
michael@0 4751 showUpdateHistory: function UP_showUpdateHistory(parent) {
michael@0 4752 this._showUI(parent, URI_UPDATE_HISTORY_DIALOG, "modal,dialog=yes",
michael@0 4753 "Update:History", null, null);
michael@0 4754 },
michael@0 4755
michael@0 4756 /**
michael@0 4757 * Returns the update window if present.
michael@0 4758 */
michael@0 4759 _getUpdateWindow: function UP__getUpdateWindow() {
michael@0 4760 return Services.wm.getMostRecentWindow(UPDATE_WINDOW_NAME);
michael@0 4761 },
michael@0 4762
michael@0 4763 /**
michael@0 4764 * Returns an alternative update window if present. When a window with this
michael@0 4765 * windowtype is open the application update service won't open the normal
michael@0 4766 * application update user interface window.
michael@0 4767 */
michael@0 4768 _getAltUpdateWindow: function UP__getAltUpdateWindow() {
michael@0 4769 let windowType = getPref("getCharPref", PREF_APP_UPDATE_ALTWINDOWTYPE, null);
michael@0 4770 if (!windowType)
michael@0 4771 return null;
michael@0 4772 return Services.wm.getMostRecentWindow(windowType);
michael@0 4773 },
michael@0 4774
michael@0 4775 /**
michael@0 4776 * Initiate a less obtrusive UI, starting with a non-modal notification alert
michael@0 4777 * @param parent
michael@0 4778 * A parent window, can be null
michael@0 4779 * @param uri
michael@0 4780 * The URI string of the dialog to show
michael@0 4781 * @param name
michael@0 4782 * The Window Name of the dialog to show, in case it is already open
michael@0 4783 * and can merely be focused
michael@0 4784 * @param page
michael@0 4785 * The page of the wizard to be displayed, if one is already open.
michael@0 4786 * @param update
michael@0 4787 * An update to pass to the UI in the window arguments.
michael@0 4788 * Can be null
michael@0 4789 * @param title
michael@0 4790 * The title for the notification alert.
michael@0 4791 * @param text
michael@0 4792 * The contents of the notification alert.
michael@0 4793 * @param imageUrl
michael@0 4794 * A URL identifying the image to put in the notification alert.
michael@0 4795 */
michael@0 4796 _showUnobtrusiveUI: function UP__showUnobUI(parent, uri, features, name, page,
michael@0 4797 update, title, text, imageUrl) {
michael@0 4798 var observer = {
michael@0 4799 updatePrompt: this,
michael@0 4800 service: null,
michael@0 4801 timer: null,
michael@0 4802 notify: function () {
michael@0 4803 // the user hasn't restarted yet => prompt when idle
michael@0 4804 this.service.removeObserver(this, "quit-application");
michael@0 4805 // If the update window is already open skip showing the UI
michael@0 4806 if (this.updatePrompt._getUpdateWindow())
michael@0 4807 return;
michael@0 4808 this.updatePrompt._showUIWhenIdle(parent, uri, features, name, page, update);
michael@0 4809 },
michael@0 4810 observe: function (aSubject, aTopic, aData) {
michael@0 4811 switch (aTopic) {
michael@0 4812 case "alertclickcallback":
michael@0 4813 this.updatePrompt._showUI(parent, uri, features, name, page, update);
michael@0 4814 // fall thru
michael@0 4815 case "quit-application":
michael@0 4816 if (this.timer)
michael@0 4817 this.timer.cancel();
michael@0 4818 this.service.removeObserver(this, "quit-application");
michael@0 4819 break;
michael@0 4820 }
michael@0 4821 }
michael@0 4822 };
michael@0 4823
michael@0 4824 // bug 534090 - show the UI for update available notifications when the
michael@0 4825 // the system has been idle for at least IDLE_TIME without displaying an
michael@0 4826 // alert notification.
michael@0 4827 if (page == "updatesavailable") {
michael@0 4828 var idleService = Cc["@mozilla.org/widget/idleservice;1"].
michael@0 4829 getService(Ci.nsIIdleService);
michael@0 4830
michael@0 4831 const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
michael@0 4832 if (idleService.idleTime / 1000 >= IDLE_TIME) {
michael@0 4833 this._showUI(parent, uri, features, name, page, update);
michael@0 4834 return;
michael@0 4835 }
michael@0 4836 }
michael@0 4837
michael@0 4838 try {
michael@0 4839 var notifier = Cc["@mozilla.org/alerts-service;1"].
michael@0 4840 getService(Ci.nsIAlertsService);
michael@0 4841 notifier.showAlertNotification(imageUrl, title, text, true, "", observer);
michael@0 4842 }
michael@0 4843 catch (e) {
michael@0 4844 // Failed to retrieve alerts service, platform unsupported
michael@0 4845 this._showUIWhenIdle(parent, uri, features, name, page, update);
michael@0 4846 return;
michael@0 4847 }
michael@0 4848
michael@0 4849 observer.service = Services.obs;
michael@0 4850 observer.service.addObserver(observer, "quit-application", false);
michael@0 4851
michael@0 4852 // bug 534090 - show the UI when idle for update available notifications.
michael@0 4853 if (page == "updatesavailable") {
michael@0 4854 this._showUIWhenIdle(parent, uri, features, name, page, update);
michael@0 4855 return;
michael@0 4856 }
michael@0 4857
michael@0 4858 // Give the user x seconds to react before prompting as defined by
michael@0 4859 // promptWaitTime
michael@0 4860 observer.timer = Cc["@mozilla.org/timer;1"].
michael@0 4861 createInstance(Ci.nsITimer);
michael@0 4862 observer.timer.initWithCallback(observer, update.promptWaitTime * 1000,
michael@0 4863 observer.timer.TYPE_ONE_SHOT);
michael@0 4864 },
michael@0 4865
michael@0 4866 /**
michael@0 4867 * Show the UI when the user was idle
michael@0 4868 * @param parent
michael@0 4869 * A parent window, can be null
michael@0 4870 * @param uri
michael@0 4871 * The URI string of the dialog to show
michael@0 4872 * @param name
michael@0 4873 * The Window Name of the dialog to show, in case it is already open
michael@0 4874 * and can merely be focused
michael@0 4875 * @param page
michael@0 4876 * The page of the wizard to be displayed, if one is already open.
michael@0 4877 * @param update
michael@0 4878 * An update to pass to the UI in the window arguments.
michael@0 4879 * Can be null
michael@0 4880 */
michael@0 4881 _showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name,
michael@0 4882 page, update) {
michael@0 4883 var idleService = Cc["@mozilla.org/widget/idleservice;1"].
michael@0 4884 getService(Ci.nsIIdleService);
michael@0 4885
michael@0 4886 const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
michael@0 4887 if (idleService.idleTime / 1000 >= IDLE_TIME) {
michael@0 4888 this._showUI(parent, uri, features, name, page, update);
michael@0 4889 } else {
michael@0 4890 var observer = {
michael@0 4891 updatePrompt: this,
michael@0 4892 observe: function (aSubject, aTopic, aData) {
michael@0 4893 switch (aTopic) {
michael@0 4894 case "idle":
michael@0 4895 // If the update window is already open skip showing the UI
michael@0 4896 if (!this.updatePrompt._getUpdateWindow())
michael@0 4897 this.updatePrompt._showUI(parent, uri, features, name, page, update);
michael@0 4898 // fall thru
michael@0 4899 case "quit-application":
michael@0 4900 idleService.removeIdleObserver(this, IDLE_TIME);
michael@0 4901 Services.obs.removeObserver(this, "quit-application");
michael@0 4902 break;
michael@0 4903 }
michael@0 4904 }
michael@0 4905 };
michael@0 4906 idleService.addIdleObserver(observer, IDLE_TIME);
michael@0 4907 Services.obs.addObserver(observer, "quit-application", false);
michael@0 4908 }
michael@0 4909 },
michael@0 4910
michael@0 4911 /**
michael@0 4912 * Show the Update Checking UI
michael@0 4913 * @param parent
michael@0 4914 * A parent window, can be null
michael@0 4915 * @param uri
michael@0 4916 * The URI string of the dialog to show
michael@0 4917 * @param name
michael@0 4918 * The Window Name of the dialog to show, in case it is already open
michael@0 4919 * and can merely be focused
michael@0 4920 * @param page
michael@0 4921 * The page of the wizard to be displayed, if one is already open.
michael@0 4922 * @param update
michael@0 4923 * An update to pass to the UI in the window arguments.
michael@0 4924 * Can be null
michael@0 4925 */
michael@0 4926 _showUI: function UP__showUI(parent, uri, features, name, page, update) {
michael@0 4927 var ary = null;
michael@0 4928 if (update) {
michael@0 4929 ary = Cc["@mozilla.org/supports-array;1"].
michael@0 4930 createInstance(Ci.nsISupportsArray);
michael@0 4931 ary.AppendElement(update);
michael@0 4932 }
michael@0 4933
michael@0 4934 var win = this._getUpdateWindow();
michael@0 4935 if (win) {
michael@0 4936 if (page && "setCurrentPage" in win)
michael@0 4937 win.setCurrentPage(page);
michael@0 4938 win.focus();
michael@0 4939 }
michael@0 4940 else {
michael@0 4941 var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
michael@0 4942 if (features)
michael@0 4943 openFeatures += "," + features;
michael@0 4944 Services.ww.openWindow(parent, uri, "", openFeatures, ary);
michael@0 4945 }
michael@0 4946 },
michael@0 4947
michael@0 4948 classDescription: "Update Prompt",
michael@0 4949 contractID: "@mozilla.org/updates/update-prompt;1",
michael@0 4950 classID: Components.ID("{27ABA825-35B5-4018-9FDD-F99250A0E722}"),
michael@0 4951 QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt])
michael@0 4952 };
michael@0 4953
michael@0 4954 var components = [UpdateService, Checker, UpdatePrompt, UpdateManager];
michael@0 4955 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
michael@0 4956
michael@0 4957 #if 0
michael@0 4958 /**
michael@0 4959 * Logs a message and stack trace to the console.
michael@0 4960 * @param string
michael@0 4961 * The string to write to the console.
michael@0 4962 */
michael@0 4963 function STACK(string) {
michael@0 4964 dump("*** " + string + "\n");
michael@0 4965 stackTrace(arguments.callee.caller.arguments, -1);
michael@0 4966 }
michael@0 4967
michael@0 4968 function stackTraceFunctionFormat(aFunctionName) {
michael@0 4969 var classDelimiter = aFunctionName.indexOf("_");
michael@0 4970 var className = aFunctionName.substr(0, classDelimiter);
michael@0 4971 if (!className)
michael@0 4972 className = "<global>";
michael@0 4973 var functionName = aFunctionName.substr(classDelimiter + 1, aFunctionName.length);
michael@0 4974 if (!functionName)
michael@0 4975 functionName = "<anonymous>";
michael@0 4976 return className + "::" + functionName;
michael@0 4977 }
michael@0 4978
michael@0 4979 function stackTraceArgumentsFormat(aArguments) {
michael@0 4980 arglist = "";
michael@0 4981 for (var i = 0; i < aArguments.length; i++) {
michael@0 4982 arglist += aArguments[i];
michael@0 4983 if (i < aArguments.length - 1)
michael@0 4984 arglist += ", ";
michael@0 4985 }
michael@0 4986 return arglist;
michael@0 4987 }
michael@0 4988
michael@0 4989 function stackTrace(aArguments, aMaxCount) {
michael@0 4990 dump("=[STACKTRACE]=====================================================\n");
michael@0 4991 dump("*** at: " + stackTraceFunctionFormat(aArguments.callee.name) + "(" +
michael@0 4992 stackTraceArgumentsFormat(aArguments) + ")\n");
michael@0 4993 var temp = aArguments.callee.caller;
michael@0 4994 var count = 0;
michael@0 4995 while (temp) {
michael@0 4996 dump("*** " + stackTraceFunctionFormat(temp.name) + "(" +
michael@0 4997 stackTraceArgumentsFormat(temp.arguments) + ")\n");
michael@0 4998
michael@0 4999 temp = temp.arguments.callee.caller;
michael@0 5000 if (aMaxCount > 0 && ++count == aMaxCount)
michael@0 5001 break;
michael@0 5002 }
michael@0 5003 dump("==================================================================\n");
michael@0 5004 }
michael@0 5005
michael@0 5006 function dumpFile(file) {
michael@0 5007 dump("*** file = " + file.path + ", exists = " + file.exists() + "\n");
michael@0 5008 }
michael@0 5009 #endif

mercurial